304 lines
8.9 KiB
Bash
304 lines
8.9 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function fw::on_load() {
|
|
system::require_command iptables
|
|
}
|
|
|
|
# ============================================
|
|
# Rule Management
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# Block / Unblock
|
|
|
|
function fw::block_ip() {
|
|
local client_ip="${1:-}" target_ip="${2:-}" mode="${3:-insert}"
|
|
fw::_block_pair "$mode" -s "$client_ip" -d "$target_ip"
|
|
}
|
|
|
|
function fw::unblock_ip() {
|
|
local client_ip="${1:-}" target_ip="${2:-}"
|
|
fw::_unblock_pair -s "$client_ip" -d "$target_ip"
|
|
}
|
|
|
|
function fw::block_port() {
|
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" \
|
|
proto="${4:-tcp}" mode="${5:-insert}"
|
|
fw::_block_pair "$mode" \
|
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
|
}
|
|
|
|
function fw::unblock_port() {
|
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
|
fw::_unblock_pair \
|
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
|
}
|
|
|
|
function fw::block_subnet() {
|
|
local client_ip="${1:-}" target_subnet="${2:-}" mode="${3:-append}"
|
|
fw::_block_pair "$mode" -s "$client_ip" -d "$target_subnet"
|
|
log::wg_block "Blocked ${client_ip} → subnet ${target_subnet}"
|
|
}
|
|
|
|
function fw::unblock_subnet() {
|
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
|
fw::_unblock_pair -s "$client_ip" -d "$target_subnet"
|
|
log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
|
|
}
|
|
|
|
function fw::block_all() {
|
|
local client_ip="${1:-}" client_name="${2:-}"
|
|
fw::_forward_exists -s "$client_ip" -j DROP \
|
|
|| iptables -A FORWARD -s "$client_ip" -j DROP
|
|
log::debug "Blocked all traffic from: ${client_ip}"
|
|
}
|
|
|
|
function fw::unblock_all() {
|
|
local client_ip="${1:-}"
|
|
fw::_forward_exists -s "$client_ip" -j DROP \
|
|
&& iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true
|
|
monitor::unwatch "$client_ip"
|
|
log::debug "Unblocked all traffic from: ${client_ip}"
|
|
}
|
|
|
|
# ============================================
|
|
# Allow / Unallow
|
|
|
|
function fw::allow_ip() {
|
|
local client_ip="${1:-}" target_ip="${2:-}"
|
|
fw::_accept_insert -s "$client_ip" -d "$target_ip"
|
|
}
|
|
|
|
function fw::unallow_ip() {
|
|
local client_ip="${1:-}" target_ip="${2:-}"
|
|
fw::_accept_remove -s "$client_ip" -d "$target_ip"
|
|
}
|
|
|
|
function fw::allow_subnet() {
|
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
|
fw::_accept_insert -s "$client_ip" -d "$target_subnet"
|
|
}
|
|
|
|
function fw::unallow_subnet() {
|
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
|
fw::_accept_remove -s "$client_ip" -d "$target_subnet"
|
|
}
|
|
|
|
function fw::allow_port() {
|
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
|
fw::_accept_insert \
|
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
|
}
|
|
|
|
function fw::unallow_port() {
|
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
|
fw::_accept_remove \
|
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
|
}
|
|
|
|
# ============================================
|
|
# Flush
|
|
# ============================================
|
|
|
|
function fw::flush_peer() {
|
|
local client_ip="${1:?client_ip required}"
|
|
log::debug "flush_peer: starting for $client_ip"
|
|
|
|
local linenums=()
|
|
while IFS= read -r linenum; do
|
|
[[ -n "$linenum" ]] && linenums+=("$linenum")
|
|
done < <(iptables -L FORWARD -n --line-numbers \
|
|
| grep -F "$client_ip" | awk '{print $1}')
|
|
|
|
local count=0
|
|
local i
|
|
for (( i=${#linenums[@]}-1; i>=0; i-- )); do
|
|
iptables -D FORWARD "${linenums[$i]}" 2>/dev/null || true
|
|
(( count++ )) || true
|
|
done
|
|
|
|
log::debug "flush_peer: removed $count FORWARD rules for: $client_ip"
|
|
}
|
|
|
|
function fw::flush_forward() {
|
|
iptables -F FORWARD
|
|
log::debug "Flushed FORWARD chain"
|
|
}
|
|
|
|
function fw::flush_nat() {
|
|
iptables -t nat -F PREROUTING
|
|
log::debug "Flushed NAT PREROUTING chain"
|
|
}
|
|
|
|
function fw::flush_all() {
|
|
fw::flush_forward
|
|
fw::flush_nat
|
|
}
|
|
|
|
# ============================================
|
|
# NAT / DNS Redirect
|
|
# ============================================
|
|
|
|
function fw::nat_add_dns_redirect() {
|
|
local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
|
|
iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
|
|
-p udp --dport 53 ! -d "$dns" \
|
|
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
|
|
iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
|
|
-p udp --dport 53 -j DNAT --to-destination "${dns}:53"
|
|
iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
|
|
-p tcp --dport 53 -j DNAT --to-destination "${dns}:53"
|
|
}
|
|
|
|
function fw::nat_remove_dns_redirect() {
|
|
local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
|
|
iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
|
|
-p udp --dport 53 ! -d "$dns" \
|
|
-j LOG --log-prefix "wgctl-dns-redirect: " \
|
|
--log-level 4 2>/dev/null || true
|
|
iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
|
|
-p udp --dport 53 -j DNAT \
|
|
--to-destination "${dns}:53" 2>/dev/null || true
|
|
iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
|
|
-p tcp --dport 53 -j DNAT \
|
|
--to-destination "${dns}:53" 2>/dev/null || true
|
|
}
|
|
|
|
# ============================================
|
|
# Display
|
|
# ============================================
|
|
|
|
function fw::forward_rules_for_ip() {
|
|
local ip="${1:-}"
|
|
iptables -L FORWARD -n -v </dev/null | grep -F "$ip"
|
|
}
|
|
|
|
function fw::proto_name() {
|
|
local proto="${1:-0}"
|
|
case "$proto" in
|
|
6) echo "tcp" ;;
|
|
17) echo "udp" ;;
|
|
1) echo "icmp" ;;
|
|
0) echo "all" ;;
|
|
tcp|udp|icmp|all) echo "$proto" ;;
|
|
*) echo "$proto" ;;
|
|
esac
|
|
}
|
|
|
|
function fw::format_rule() {
|
|
local line="${1:-}"
|
|
[[ -z "$line" ]] && return 0
|
|
|
|
local target prot src dst extra
|
|
target=$(awk '{print $3}' <<< "$line")
|
|
prot=$(awk '{print $4}' <<< "$line")
|
|
src=$(awk '{print $8}' <<< "$line")
|
|
dst=$(awk '{print $9}' <<< "$line")
|
|
extra=$(awk '{for(i=10;i<=NF;i++) printf $i" "}' <<< "$line" | xargs)
|
|
|
|
local prot_name
|
|
prot_name=$(fw::proto_name "$prot")
|
|
|
|
local dst_fmt="$dst"
|
|
if [[ "$extra" =~ dpt:([0-9]+) ]]; then
|
|
local port="${BASH_REMATCH[1]}"
|
|
dst_fmt="${dst}:${port}:${prot_name}"
|
|
fi
|
|
|
|
local formatted
|
|
formatted=$(printf " %-8s %-15s → %s" "$target" "$src" "$dst_fmt")
|
|
ui::firewall_rule "$formatted"
|
|
}
|
|
|
|
function fw::list_peer_rules() {
|
|
local ip="${1:-}" show_nflog="${2:-false}"
|
|
fw::forward_rules_for_ip "$ip" | while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
! $show_nflog && [[ "$line" =~ NFLOG ]] && continue
|
|
fw::format_rule "$line"
|
|
done
|
|
}
|
|
|
|
# ============================================
|
|
# Counts
|
|
# ============================================
|
|
|
|
function fw::count_peer_rules() {
|
|
local ip="${1:-}"
|
|
local total=0 accepts=0 drops=0
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
[[ "$line" =~ NFLOG ]] && continue
|
|
(( total++ )) || true
|
|
[[ "$line" =~ ACCEPT ]] && (( accepts++ )) || true
|
|
[[ "$line" =~ DROP ]] && (( drops++ )) || true
|
|
done < <(fw::forward_rules_for_ip "$ip")
|
|
echo "${total}|${accepts}|${drops}"
|
|
}
|
|
|
|
# ============================================
|
|
# private
|
|
# ============================================
|
|
|
|
function fw::_forward_exists() {
|
|
iptables -C FORWARD "$@" 2>/dev/null
|
|
}
|
|
|
|
function fw::_rule_exists() {
|
|
local table="${1:-filter}" chain="${2:-FORWARD}"
|
|
shift 2
|
|
iptables -t "$table" -C "$chain" "$@" 2>/dev/null
|
|
}
|
|
|
|
function fw::_nat_exists() {
|
|
fw::_rule_exists nat PREROUTING "$@"
|
|
}
|
|
|
|
# Core NFLOG+DROP block pair — insert or append
|
|
function fw::_block_pair() {
|
|
local mode="${1:-insert}" # insert | append
|
|
shift
|
|
# $@ = match args (no -j)
|
|
if [[ "$mode" == "insert" ]]; then
|
|
# insert: DROP first at pos 1, NFLOG second at pos 1 → NFLOG ends above DROP
|
|
fw::_forward_exists "$@" -j DROP \
|
|
|| iptables -I FORWARD 1 "$@" -j DROP
|
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
|| iptables -I FORWARD 1 "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
|
else
|
|
# append: NFLOG first, DROP second → NFLOG ends above DROP
|
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
|| iptables -A FORWARD "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
|
fw::_forward_exists "$@" -j DROP \
|
|
|| iptables -A FORWARD "$@" -j DROP
|
|
fi
|
|
}
|
|
|
|
# Core NFLOG+DROP removal
|
|
function fw::_unblock_pair() {
|
|
shift 0 # no mode needed for deletion
|
|
# $@ = match args
|
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
&& iptables -D FORWARD "$@" -j NFLOG \
|
|
--nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
|
fw::_forward_exists "$@" -j DROP \
|
|
&& iptables -D FORWARD "$@" -j DROP 2>/dev/null || true
|
|
}
|
|
|
|
# Core ACCEPT insert
|
|
function fw::_accept_insert() {
|
|
# $@ = match args
|
|
fw::_forward_exists "$@" -j ACCEPT \
|
|
|| iptables -I FORWARD 1 "$@" -j ACCEPT
|
|
}
|
|
|
|
# Core ACCEPT removal
|
|
function fw::_accept_remove() {
|
|
fw::_forward_exists "$@" -j ACCEPT \
|
|
&& iptables -D FORWARD "$@" -j ACCEPT 2>/dev/null || true
|
|
}
|
|
|