#!/usr/bin/env bash # ============================================ # Lifecycle # ============================================ function cmd::fw::on_load() { flag::register --peer flag::register --type flag::register --rule flag::register --no-nflog flag::register --no-accept flag::register --no-drop } # ============================================ # Help # ============================================ function cmd::fw::help() { cat < Filter by peer name --rule Filter by rule name (shows all peers with that rule) --no-nflog Hide NFLOG rules --no-accept Hide ACCEPT rules --no-drop Hide DROP rules Examples: wgctl fw list wgctl fw list --peer phone-nuno wgctl fw list --rule guest wgctl fw list --no-nflog wgctl fw nat wgctl fw count wgctl fw flush-nat --subnet 10.1.103.0/24 EOF } # ============================================ # Run # ============================================ function cmd::fw::run() { local subcmd="${1:-list}" # If first arg is a flag, default to list if [[ "$subcmd" == --* ]]; then subcmd="list" else shift || true fi case "$subcmd" in list) cmd::fw::list "$@" ;; nat) cmd::fw::nat "$@" ;; flush-nat) cmd::fw::flush_nat "$@" ;; clean) cmd::fw::clean ;; count) cmd::fw::count ;; help) cmd::fw::help ;; *) log::error "Unknown subcommand: $subcmd"; return 1 ;; esac } function cmd::fw::list() { local peer="" type="" rule="" local show_nflog=true show_accept=true show_drop=true while [[ $# -gt 0 ]]; do case "$1" in --peer) peer="$2"; shift 2 ;; --rule) rule="$2"; shift 2 ;; --type) type="$2"; shift 2 ;; --no-nflog) show_nflog=false; shift ;; --no-accept) show_accept=false; shift ;; --no-drop) show_drop=false; shift ;; *) shift ;; esac done # Rule filter — collect all IPs for peers with this rule if [[ -n "$rule" ]]; then log::section "Firewall Rules (FORWARD) — rule: ${rule}" printf "\n" local found=false while IFS= read -r peer_name; do local ip ip=$(peers::get_ip "$peer_name") [[ -z "$ip" ]] && continue printf " \033[0;37m── %s (%s)\033[0m\n" "$peer_name" "$ip" iptables -L FORWARD -n -v | grep -F "$ip" \ | cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop" || true found=true done < <(peers::with_rule "$rule") $found || log::wg_warning "No peers found with rule: ${rule}" printf "\n" return 0 fi log::section "Firewall Rules (FORWARD)" printf "\n" if [[ -n "$peer" ]]; then local ip ip=$(peers::get_ip "$peer") [[ -z "$ip" ]] && log::error "Peer not found: $peer" && return 1 iptables -L FORWARD -n -v | grep -F "$ip" \ | cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop" || true elif [[ -n "$type" ]]; then local subnet subnet=$(config::subnet_for "$type") [[ -z "$subnet" ]] && log::error "Unknown type: $type" && return 1 iptables -L FORWARD -n -v | grep -F "$subnet" \ | cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop" else iptables -L FORWARD -n -v \ | grep -v "^Chain\|^target\|^$\|ACCEPT.*0\.0\.0\.0.*0\.0\.0\.0" \ | cmd::fw::_print_filtered "$show_nflog" "$show_accept" "$show_drop" fi printf "\n" } function cmd::fw::nat() { log::section "NAT Rules (PREROUTING)" printf "\n" iptables -t nat -L PREROUTING -n -v | while IFS= read -r rule; do [[ -z "$rule" ]] && continue ui::firewall_rule "$rule" done printf "\n" } function cmd::fw::flush_nat() { local type="" while [[ $# -gt 0 ]]; do case "$1" in --type) type="$2"; shift 2 ;; *) shift ;; esac done if [[ -z "$type" ]]; then log::error "Missing required flag: --type" return 1 fi local subnet subnet=$(config::subnet_for "$type") if [[ -z "$subnet" ]]; then log::error "Unknown type: $type" return 1 fi local nat_linenums=() while IFS= read -r linenum; do [[ -n "$linenum" ]] && nat_linenums+=("$linenum") done < <(iptables -t nat -L PREROUTING -n --line-numbers | grep -F "${subnet}" | awk '{print $1}') local count=0 for (( i=${#nat_linenums[@]}-1; i>=0; i-- )); do iptables -t nat -D PREROUTING "${nat_linenums[$i]}" 2>/dev/null || true (( count++ )) || true done log::wg_success "Flushed ${count} NAT rules for type '${type}' (${subnet}.0/24)" } function cmd::fw::stats() { log::section "Firewall Stats" iptables -L FORWARD -n -v | grep -v "^Chain\|^target\|^$" | \ awk 'NR>1 {printf " %8s pkts %8s bytes %s\n", $1, $2, $0}' } function cmd::fw::count() { log::section "Firewall Rule Counts" local drop accept nflog total drop=$(iptables -L FORWARD -n | grep -c "^DROP" || echo 0) accept=$(iptables -L FORWARD -n | grep -c "^ACCEPT" || echo 0) nflog=$(iptables -L FORWARD -n | grep -c "^NFLOG" || echo 0) total=$(( drop + accept + nflog )) printf "\n %-10s %s\n" "DROP:" "$drop" printf " %-10s %s\n" "ACCEPT:" "$accept" printf " %-10s %s\n" "NFLOG:" "$nflog" printf " %-10s %s\n" "TOTAL:" "$total" printf "\n" } function cmd::fw::clean() { log::section "Duplicate Rule Report" iptables-save | sort | uniq -d | grep "^-A FORWARD" } function cmd::fw::_print_filtered() { local show_nflog="$1" show_accept="$2" show_drop="$3" while IFS= read -r rule; do [[ -z "$rule" ]] && continue ! $show_nflog && [[ "$rule" =~ NFLOG ]] && continue ! $show_accept && [[ "$rule" =~ ACCEPT ]] && continue ! $show_drop && [[ "$rule" =~ DROP ]] && continue ui::firewall_rule "$rule" done }