feat: net command, service annotations, block::restore_rules_for, fw refactor, restricted status, block system cleanup
This commit is contained in:
parent
cf90ab22db
commit
9a3ac2ae47
14 changed files with 1476 additions and 506 deletions
|
|
@ -14,6 +14,9 @@ function cmd::block::on_load() {
|
|||
flag::register --proto
|
||||
flag::register --subnet
|
||||
flag::register --block-name
|
||||
|
||||
# System - NET Services
|
||||
flag::register --service
|
||||
}
|
||||
|
||||
# ============================================
|
||||
|
|
@ -51,94 +54,119 @@ EOF
|
|||
# ============================================
|
||||
|
||||
function cmd::block::run() {
|
||||
local name=""
|
||||
local type=""
|
||||
local block_name=""
|
||||
local ips=()
|
||||
local subnets=()
|
||||
local ports=()
|
||||
local quiet=false
|
||||
local name="" type="" block_name=""
|
||||
local ips=() subnets=() ports=() services=()
|
||||
local quiet=false force=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) name="$2"; shift 2 ;;
|
||||
--type) type="$2"; shift 2 ;;
|
||||
--ip) ips+=("$2"); shift 2 ;;
|
||||
--block-name) block_name="$2"; shift 2 ;;
|
||||
--force) force=true; shift ;;
|
||||
--quiet) quiet=true; shift ;;
|
||||
--subnet) subnets+=("$2"); shift 2 ;;
|
||||
--port) ports+=("$2"); shift 2 ;;
|
||||
--help) cmd::block::help; return ;;
|
||||
--name) name="$2"; shift 2 ;;
|
||||
--type) type="$2"; shift 2 ;;
|
||||
--ip) ips+=("$2"); shift 2 ;;
|
||||
--block-name) block_name="$2"; shift 2 ;;
|
||||
--service) services+=("$2"); shift 2 ;;
|
||||
--force) force=true; shift ;;
|
||||
--quiet) quiet=true; shift ;;
|
||||
--subnet) subnets+=("$2"); shift 2 ;;
|
||||
--port) ports+=("$2"); shift 2 ;;
|
||||
--help) cmd::block::help; return ;;
|
||||
*)
|
||||
log::error "Unknown flag: $1"
|
||||
cmd::block::help
|
||||
return 1
|
||||
;;
|
||||
return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$name" ]]; then
|
||||
log::error "Missing required flag: --name"
|
||||
cmd::block::help
|
||||
return 1
|
||||
fi
|
||||
[[ -z "$name" ]] && log::error "Missing required flag: --name" && \
|
||||
cmd::block::help && return 1
|
||||
|
||||
name=$(peers::resolve_and_require "$name" "$type") || return 1
|
||||
|
||||
# Check if actually blocked
|
||||
if peers::is_blocked "$name" || block::has_file "$name"; then
|
||||
log::wg_warning "Client is already blocked: ${name}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Update cache first
|
||||
monitor::update_endpoint_cache
|
||||
|
||||
local public_key
|
||||
public_key=$(keys::public "$name") || return 1
|
||||
|
||||
local endpoint
|
||||
endpoint=$(cmd::block::_get_endpoint "$name" "$public_key")
|
||||
|
||||
local client_ip
|
||||
client_ip=$(peers::get_ip "$name") || return 1
|
||||
|
||||
# No specific target — block everything
|
||||
# Only full block if no specific targets provided
|
||||
if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && ${#subnets[@]} -eq 0 ]]; then
|
||||
# Full block if no specific targets
|
||||
if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && \
|
||||
${#subnets[@]} -eq 0 && ${#services[@]} -eq 0 ]]; then
|
||||
if peers::is_blocked "$name" || block::has_file "$name"; then
|
||||
log::wg_warning "Client is already blocked: ${name}"
|
||||
return 0
|
||||
fi
|
||||
monitor::update_endpoint_cache
|
||||
cmd::block::_block_all "$name" "$client_ip" "$quiet"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Specific rules — don't full block
|
||||
block::set_direct "$name" "$client_ip" "false" # ensure not marked as full block
|
||||
# Specific rules — check if already fully blocked
|
||||
if block::has_file "$name"; then
|
||||
local direct
|
||||
direct=$(block::is_blocked_direct "$name")
|
||||
if [[ "$direct" == "true" ]]; then
|
||||
log::wg_warning "${name} is fully blocked — unblock first to add specific rules"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Block specific IPs
|
||||
for ip in "${ips[@]}"; do
|
||||
ip::require_valid "$ip"
|
||||
fw::block_ip "$client_ip" "$ip"
|
||||
block::add_rule "$name" "$client_ip" "ip" "" "$ip"
|
||||
block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip"
|
||||
done
|
||||
|
||||
# Block specific subnets
|
||||
for subnet in "${subnets[@]}"; do
|
||||
ip::require_valid "$subnet"
|
||||
fw::block_subnet "$client_ip" "$subnet"
|
||||
block::add_rule "$name" "$client_ip" "subnet" "" "$target_ip"
|
||||
block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet"
|
||||
done
|
||||
|
||||
# Block specific ports
|
||||
for entry in "${ports[@]}"; do
|
||||
local target port proto
|
||||
IFS=":" read -r target port proto <<< "$entry"
|
||||
ip::require_valid "$target"
|
||||
|
||||
fw::block_port "$client_ip" "$target" "$port" "${proto:-tcp}"
|
||||
block::add_rule "$name" "$client_ip" "port" "" "$target" "$port" "${proto:-tcp}"
|
||||
local b_target b_port b_proto
|
||||
IFS=":" read -r b_target b_port b_proto <<< "$entry"
|
||||
ip::require_valid "$b_target"
|
||||
fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}"
|
||||
block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \
|
||||
"$b_target" "$b_port" "${b_proto:-tcp}"
|
||||
done
|
||||
|
||||
log::debug "Block rules applied for: ${name}"
|
||||
# Block services
|
||||
for svc in "${services[@]}"; do
|
||||
local resolved_lines=()
|
||||
mapfile -t resolved_lines < <(net::resolve "$svc" 2>/dev/null)
|
||||
if [[ ${#resolved_lines[@]} -eq 0 ]]; then
|
||||
log::error "Service not found or has no ports: ${svc}"
|
||||
return 1
|
||||
fi
|
||||
for resolved in "${resolved_lines[@]}"; do
|
||||
if [[ "$resolved" == *:*:* ]]; then
|
||||
local b_ip b_port b_proto
|
||||
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
|
||||
fw::block_port "$client_ip" "$b_ip" "$b_port" "$b_proto"
|
||||
block::add_rule "$name" "$client_ip" "port" "$svc" \
|
||||
"$b_ip" "$b_port" "$b_proto"
|
||||
else
|
||||
fw::block_ip "$client_ip" "$resolved"
|
||||
block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Reapply in correct order: rule ACCEPT first, then peer DROP rules
|
||||
local peer_rule
|
||||
peer_rule=$(peers::get_meta "$name" "rule")
|
||||
if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then
|
||||
fw::flush_peer "$client_ip"
|
||||
rule::apply "$peer_rule" "$client_ip" "$name"
|
||||
block::restore_rules_for "$name" "$client_ip"
|
||||
else
|
||||
# No rule assigned — peer blocks are the only fw rules, order is fine
|
||||
: # no-op
|
||||
fi
|
||||
|
||||
$quiet || log::wg_success "${name} — access restricted"
|
||||
return 0
|
||||
}
|
||||
|
||||
function cmd::block::_get_endpoint() {
|
||||
|
|
|
|||
|
|
@ -56,9 +56,12 @@ function cmd::inspect::_peer_info() {
|
|||
peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
|
||||
last_ts=$(monitor::last_attempt "$name")
|
||||
|
||||
local is_restricted="false"
|
||||
block::has_specific_rules "$name" 2>/dev/null && is_restricted="true"
|
||||
|
||||
local status last_seen endpoint
|
||||
status=$(peers::format_status "$name" "$public_key" \
|
||||
"$is_blocked" "false" "$handshake_ts" "$last_ts")
|
||||
"$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
|
||||
last_seen=$(peers::format_last_seen "$name" "$public_key" \
|
||||
"$is_blocked" "$last_ts" "" "$handshake_ts")
|
||||
endpoint=$(monitor::get_cached_endpoint "$name")
|
||||
|
|
@ -109,6 +112,107 @@ function cmd::inspect::_peer_info() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# function cmd::inspect::_rule_info() {
|
||||
# local name="${1:-}"
|
||||
# local rule
|
||||
# rule=$(peers::get_meta "$name" "rule")
|
||||
# [[ -z "$rule" ]] && return 0
|
||||
# rule::exists "$rule" || return 0
|
||||
|
||||
# cmd::inspect::_section "Rule: ${rule}"
|
||||
|
||||
# local rule_file
|
||||
# rule_file="$(rule::path "$rule")"
|
||||
|
||||
# # Check for inheritance
|
||||
# local extends_raw=()
|
||||
# mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||
|
||||
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
|
||||
# # Show inheritance tree
|
||||
# for base_name in "${extends_raw[@]}"; do
|
||||
# [[ -z "$base_name" ]] && continue
|
||||
# printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||
|
||||
# local base_allows base_blocks
|
||||
# base_allows=$(rule::get "$base_name" "allow_ports")$'\n'$(rule::get "$base_name" "allow_ips")
|
||||
# base_blocks=$(rule::get "$base_name" "block_ips")$'\n'$(rule::get "$base_name" "block_ports")
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$base_allows"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$base_blocks"
|
||||
|
||||
# local base_dns
|
||||
# base_dns=$(rule::get_own "$base_name" "dns_redirect")
|
||||
# [[ "${base_dns,,}" == "true" ]] && \
|
||||
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
# done
|
||||
|
||||
# # Own rules
|
||||
# local own_allows own_blocks
|
||||
# own_allows=$(json::get "$rule_file" "allow_ports" 2>/dev/null)$'\n'$(json::get "$rule_file" "allow_ips" 2>/dev/null)
|
||||
# own_blocks=$(json::get "$rule_file" "block_ips" 2>/dev/null)$'\n'$(json::get "$rule_file" "block_ports" 2>/dev/null)
|
||||
# local has_own=false
|
||||
|
||||
# while IFS= read -r e; do [[ -n "$e" ]] && has_own=true && break; done <<< "$own_allows$own_blocks"
|
||||
|
||||
# if $has_own; then
|
||||
# printf "\n \033[0;37mOwn:\033[0m\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$own_allows"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$own_blocks"
|
||||
# fi
|
||||
|
||||
# else
|
||||
# # No inheritance — flat view
|
||||
# local allow_ports allow_ips block_ips block_ports
|
||||
# allow_ports=$(rule::get "$rule" "allow_ports")
|
||||
# allow_ips=$(rule::get "$rule" "allow_ips")
|
||||
# block_ips=$(rule::get "$rule" "block_ips")
|
||||
# block_ports=$(rule::get "$rule" "block_ports")
|
||||
|
||||
# if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||
# printf "\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||
# fi
|
||||
|
||||
# if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||
# printf "\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$block_ips"$'\n'"$block_ports"
|
||||
# fi
|
||||
|
||||
# if [[ -z "$allow_ports" && -z "$allow_ips" && \
|
||||
# -z "$block_ips" && -z "$block_ports" ]]; then
|
||||
# printf "\n full access (no restrictions)\n"
|
||||
# fi
|
||||
|
||||
# local dns_redirect
|
||||
# dns_redirect=$(rule::get_own "$rule" "dns_redirect")
|
||||
# [[ "${dns_redirect,,}" == "true" ]] && \
|
||||
# printf "\n \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
# fi
|
||||
|
||||
# return 0
|
||||
# }
|
||||
|
||||
function cmd::inspect::_rule_info() {
|
||||
local name="${1:-}"
|
||||
local rule
|
||||
|
|
@ -118,93 +222,12 @@ function cmd::inspect::_rule_info() {
|
|||
|
||||
cmd::inspect::_section "Rule: ${rule}"
|
||||
|
||||
local rule_file
|
||||
rule_file="$(rule::path "$rule")"
|
||||
|
||||
# Check for inheritance
|
||||
local extends_raw=()
|
||||
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||
|
||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
|
||||
# Show inheritance tree
|
||||
for base_name in "${extends_raw[@]}"; do
|
||||
[[ -z "$base_name" ]] && continue
|
||||
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||
|
||||
local base_allows base_blocks
|
||||
base_allows=$(rule::get "$base_name" "allow_ports")$'\n'$(rule::get "$base_name" "allow_ips")
|
||||
base_blocks=$(rule::get "$base_name" "block_ips")$'\n'$(rule::get "$base_name" "block_ports")
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$base_allows"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$base_blocks"
|
||||
|
||||
local base_dns
|
||||
base_dns=$(rule::get_own "$base_name" "dns_redirect")
|
||||
[[ "${base_dns,,}" == "true" ]] && \
|
||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
done
|
||||
|
||||
# Own rules
|
||||
local own_allows own_blocks
|
||||
own_allows=$(json::get "$rule_file" "allow_ports" 2>/dev/null)$'\n'$(json::get "$rule_file" "allow_ips" 2>/dev/null)
|
||||
own_blocks=$(json::get "$rule_file" "block_ips" 2>/dev/null)$'\n'$(json::get "$rule_file" "block_ports" 2>/dev/null)
|
||||
local has_own=false
|
||||
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && has_own=true && break; done <<< "$own_allows$own_blocks"
|
||||
|
||||
if $has_own; then
|
||||
printf "\n \033[0;37mOwn:\033[0m\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$own_allows"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$own_blocks"
|
||||
fi
|
||||
|
||||
if rule::render_extends_tree "$rule"; then
|
||||
printf "\n"
|
||||
else
|
||||
# No inheritance — flat view
|
||||
local allow_ports allow_ips block_ips block_ports
|
||||
allow_ports=$(rule::get "$rule" "allow_ports")
|
||||
allow_ips=$(rule::get "$rule" "allow_ips")
|
||||
block_ips=$(rule::get "$rule" "block_ips")
|
||||
block_ports=$(rule::get "$rule" "block_ports")
|
||||
|
||||
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||
fi
|
||||
|
||||
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$block_ips"$'\n'"$block_ports"
|
||||
fi
|
||||
|
||||
if [[ -z "$allow_ports" && -z "$allow_ips" && \
|
||||
-z "$block_ips" && -z "$block_ports" ]]; then
|
||||
printf "\n full access (no restrictions)\n"
|
||||
fi
|
||||
|
||||
local dns_redirect
|
||||
dns_redirect=$(rule::get_own "$rule" "dns_redirect")
|
||||
[[ "${dns_redirect,,}" == "true" ]] && \
|
||||
printf "\n \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
rule::render_flat "$rule"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -317,11 +340,13 @@ function cmd::inspect::_firewall_info() {
|
|||
"$(color::red "-${drops}")" \
|
||||
"$(printf '─%.0s' {1..28})"
|
||||
|
||||
if [[ ${#rules_output[@]} -gt 0 ]]; then
|
||||
for line in "${rules_output[@]}"; do
|
||||
fw::format_rule "$line"
|
||||
done
|
||||
fi
|
||||
fw::list_peer_rules "$ip" false
|
||||
|
||||
# if [[ ${#rules_output[@]} -gt 0 ]]; then
|
||||
# for line in "${rules_output[@]}"; do
|
||||
# fw::format_rule "$line"
|
||||
# done
|
||||
# fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -381,22 +381,7 @@ function cmd::list::_precompute_all() {
|
|||
|
||||
# Block/restricted status
|
||||
declare -gA p_blocked=() p_restricted=()
|
||||
local wg_peers
|
||||
wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)
|
||||
while IFS= read -r name; do
|
||||
if block::is_blocked "$name" 2>/dev/null; then
|
||||
p_restricted["$name"]=true
|
||||
else
|
||||
p_restricted["$name"]=false
|
||||
fi
|
||||
local pubkey
|
||||
pubkey=$(keys::public "$name" 2>/dev/null || echo "")
|
||||
if [[ -n "$pubkey" ]] && ! echo "$wg_peers" | grep -qF "$pubkey"; then
|
||||
p_blocked["$name"]=true
|
||||
else
|
||||
p_blocked["$name"]=false
|
||||
fi
|
||||
done < <(peers::all)
|
||||
cmd::list::_precompute_block_status p_blocked p_restricted
|
||||
|
||||
# Public keys
|
||||
declare -gA p_pubkeys=()
|
||||
|
|
@ -440,11 +425,10 @@ function cmd::list::_precompute_block_status() {
|
|||
wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)
|
||||
|
||||
while IFS= read -r name; do
|
||||
# Restricted = has block rules but still in server (partial block)
|
||||
if block::is_blocked "$name" 2>/dev/null; then
|
||||
_restricted["$name"]=true
|
||||
else
|
||||
_restricted["$name"]=false
|
||||
if block::has_specific_rules "$name" 2>/dev/null; then
|
||||
_restricted["$name"]=true
|
||||
else
|
||||
_restricted["$name"]=false
|
||||
fi
|
||||
|
||||
# Blocked = removed from WG server
|
||||
|
|
|
|||
299
commands/net.command.sh
Normal file
299
commands/net.command.sh
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function cmd::net::on_load() {
|
||||
flag::register --name
|
||||
flag::register --ip
|
||||
flag::register --port
|
||||
flag::register --desc
|
||||
flag::register --tag
|
||||
flag::register --detailed
|
||||
flag::register --force
|
||||
}
|
||||
|
||||
function cmd::net::help() {
|
||||
cat <<EOF
|
||||
Usage: wgctl net <subcommand> [options]
|
||||
|
||||
Manage named network services for use with block/allow rules.
|
||||
Services map names to IPs and ports, making rules more readable.
|
||||
|
||||
Subcommands:
|
||||
list List all services
|
||||
show --name <name> Show service details
|
||||
add --name <name> --ip <ip> Add a service
|
||||
add --name <svc:port-name> --port <port:proto>
|
||||
Add a port to a service
|
||||
rm --name <name> Remove service or port
|
||||
rm --name <svc:ports> Remove all ports from service
|
||||
|
||||
Options for add (service):
|
||||
--name <name> Service name (e.g. proxmox)
|
||||
--ip <ip> Service IP address
|
||||
--desc <description> Optional description
|
||||
--tag <tag> Optional tag (repeatable)
|
||||
|
||||
Options for add (port):
|
||||
--name <svc:port-name> Service:port-name (e.g. proxmox:web-ui)
|
||||
--port <port:proto> Port and protocol (e.g. 8006:tcp)
|
||||
--desc <description> Optional description
|
||||
|
||||
Options for list:
|
||||
--detailed Show ports for each service
|
||||
--tag <tag> Filter by tag
|
||||
|
||||
Examples:
|
||||
wgctl net list
|
||||
wgctl net list --detailed
|
||||
wgctl net list --tag admin
|
||||
wgctl net show --name proxmox
|
||||
wgctl net add --name proxmox --ip 10.0.0.100 --desc "Proxmox VE"
|
||||
wgctl net add --name proxmox:web-ui --port 8006:tcp --desc "Web UI"
|
||||
wgctl net add --name proxmox:ssh --port 22:tcp
|
||||
wgctl net rm --name proxmox:web-ui
|
||||
wgctl net rm --name proxmox:ports
|
||||
wgctl net rm --name proxmox
|
||||
EOF
|
||||
}
|
||||
|
||||
function cmd::net::run() {
|
||||
local subcmd="${1:-list}"
|
||||
shift || true
|
||||
case "$subcmd" in
|
||||
list) cmd::net::list "$@" ;;
|
||||
show) cmd::net::show "$@" ;;
|
||||
add) cmd::net::add "$@" ;;
|
||||
rm|remove|del) cmd::net::rm "$@" ;;
|
||||
help) cmd::net::help ;;
|
||||
*)
|
||||
log::error "Unknown subcommand: '${subcmd}'"
|
||||
cmd::net::help
|
||||
return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# List
|
||||
# ============================================
|
||||
|
||||
function cmd::net::list() {
|
||||
local detailed=false filter_tag=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--detailed) detailed=true; shift ;;
|
||||
--tag) filter_tag="$2"; shift 2 ;;
|
||||
--help) cmd::net::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local net_file
|
||||
net_file="$(ctx::net)"
|
||||
|
||||
if [[ ! -f "$net_file" ]]; then
|
||||
log::wg_warning "No services configured. Use 'wgctl net add' to add one."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log::section "Network Services"
|
||||
printf "\n %-20s %-16s %-6s %s\n" "NAME" "IP" "PORTS" "DESCRIPTION"
|
||||
local divider
|
||||
divider=$(printf '─%.0s' {1..72})
|
||||
printf " %s\n" "$divider"
|
||||
|
||||
local found=false
|
||||
while IFS="|" read -r name ip desc tags ports; do
|
||||
[[ -z "$name" ]] && continue
|
||||
|
||||
# Tag filter
|
||||
if [[ -n "$filter_tag" ]]; then
|
||||
[[ "$tags" != *"$filter_tag"* ]] && continue
|
||||
fi
|
||||
|
||||
found=true
|
||||
local tag_display=""
|
||||
[[ -n "$tags" ]] && tag_display=" \033[0;37m[${tags}]\033[0m"
|
||||
|
||||
printf " %-20s %-16s %-6s %s%b\n" \
|
||||
"$name" "$ip" "${ports}p" "${desc:-—}" "$tag_display"
|
||||
|
||||
if $detailed; then
|
||||
local has_ports=false
|
||||
# Show ports inline
|
||||
while IFS="|" read -r ptype pname pport pproto pdesc; do
|
||||
[[ "$ptype" != "port" ]] && continue
|
||||
has_ports=true
|
||||
local ann
|
||||
ann=$(net::annotation "$ip" "$pport" "$pproto")
|
||||
printf " \033[0;37m%-18s %s:%s%s\033[0m\n" \
|
||||
"${pname}" "$pport" "$pproto" \
|
||||
"${pdesc:+ # $pdesc}"
|
||||
done < <(json::net_show "$net_file" "$name")
|
||||
$has_ports && printf "\n" # newline after each service with ports
|
||||
fi
|
||||
|
||||
done < <(json::net_list "$net_file")
|
||||
|
||||
if ! $found; then
|
||||
[[ -n "$filter_tag" ]] && \
|
||||
log::wg_warning "No services with tag: ${filter_tag}" || \
|
||||
log::wg_warning "No services configured"
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Show
|
||||
# ============================================
|
||||
|
||||
function cmd::net::show() {
|
||||
local name=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||
name="$2"; shift 2 ;;
|
||||
--help) cmd::net::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||
net::require_exists "$name" || return 1
|
||||
|
||||
log::section "Service: ${name}"
|
||||
printf "\n"
|
||||
|
||||
while IFS="|" read -r key val1 val2 val3 val4; do
|
||||
case "$key" in
|
||||
name) ui::row "Name" "$val1" ;;
|
||||
ip) ui::row "IP" "$val1" ;;
|
||||
desc) ui::row "Description" "${val1:-—}" ;;
|
||||
tags) ui::row "Tags" "${val1:-—}" ;;
|
||||
port)
|
||||
# val1=port_name val2=port val3=proto val4=desc
|
||||
local ann
|
||||
ann=$(net::annotation "$(json::net_resolve "$(ctx::net)" "$name")" \
|
||||
"$val2" "$val3" 2>/dev/null || true)
|
||||
printf " %-20s \033[0;36m%s\033[0m %s:%s%s\n" \
|
||||
"${val1}:" "" "$val2" "$val3" \
|
||||
"${val4:+ # $val4}"
|
||||
;;
|
||||
esac
|
||||
done < <(json::net_show "$(ctx::net)" "$name")
|
||||
|
||||
printf "\n"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Add
|
||||
# ============================================
|
||||
|
||||
function cmd::net::add() {
|
||||
local name="" ip="" port="" desc="" tags=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||
name="$2"; shift 2 ;;
|
||||
--ip) ip="$2"; shift 2 ;;
|
||||
--port) port="$2"; shift 2 ;;
|
||||
--desc) desc="$2"; shift 2 ;;
|
||||
--tag) tags+=("$2"); shift 2 ;;
|
||||
--help) cmd::net::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||
|
||||
if [[ "$name" == *:* ]]; then
|
||||
# Port mode: proxmox:web-ui
|
||||
local svc_name="${name%%:*}"
|
||||
local port_name="${name##*:}"
|
||||
|
||||
[[ -z "$port" ]] && log::error "Missing required flag: --port" && return 1
|
||||
net::require_exists "$svc_name" || return 1
|
||||
|
||||
local port_num proto
|
||||
if [[ "$port" == *:* ]]; then
|
||||
port_num="${port%%:*}"
|
||||
proto="${port##*:}"
|
||||
else
|
||||
port_num="$port"
|
||||
proto="tcp"
|
||||
fi
|
||||
|
||||
json::net_add_port "$(ctx::net)" "$svc_name" "$port_name" \
|
||||
"$port_num" "$proto" "$desc"
|
||||
|
||||
log::wg_success "Added port: ${svc_name}:${port_name} → ${port_num}/${proto}"
|
||||
else
|
||||
# Service mode: proxmox
|
||||
[[ -z "$ip" ]] && log::error "Missing required flag: --ip" && return 1
|
||||
|
||||
local tags_str
|
||||
tags_str=$(IFS=','; echo "${tags[*]}")
|
||||
|
||||
json::net_add_service "$(ctx::net)" "$name" "$ip" "$desc" "$tags_str"
|
||||
|
||||
log::wg_success "Service added: ${name} → ${ip}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Remove
|
||||
# ============================================
|
||||
|
||||
function cmd::net::rm() {
|
||||
local name="" force=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||
name="$2"; shift 2 ;;
|
||||
--force) force=true; shift ;;
|
||||
--help) cmd::net::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||
|
||||
# Validate existence
|
||||
if [[ "$name" == *:* ]]; then
|
||||
local svc_name="${name%%:*}"
|
||||
local port_name="${name##*:}"
|
||||
if [[ "$port_name" != "ports" ]]; then
|
||||
# Check specific port exists
|
||||
local exists
|
||||
exists=$(json::net_exists "$(ctx::net)" "$name")
|
||||
if [[ "$exists" != "true" ]]; then
|
||||
log::error "Port not found: ${name}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
net::require_exists "$svc_name" || return 1
|
||||
fi
|
||||
else
|
||||
net::require_exists "$name" || return 1
|
||||
fi
|
||||
|
||||
if ! $force; then
|
||||
local what="service '${name}'"
|
||||
[[ "$name" == *:ports ]] && what="all ports from '${name%%:*}'"
|
||||
[[ "$name" == *:* && "$name" != *:ports ]] && what="port '${name}'"
|
||||
read -r -p "Remove ${what}? [y/N] " confirm
|
||||
case "$confirm" in
|
||||
[yY]*) ;;
|
||||
*) log::info "Aborted"; return 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
json::net_remove "$(ctx::net)" "$name"
|
||||
log::wg_success "Removed: ${name}"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ function cmd::rule::on_load() {
|
|||
flag::register --remove-extends
|
||||
flag::register --block-ip
|
||||
flag::register --allow-ip
|
||||
flag::register --block-service
|
||||
flag::register --allow-service
|
||||
flag::register --block-port
|
||||
flag::register --allow-port
|
||||
flag::register --remove-block-ip
|
||||
|
|
@ -311,17 +313,225 @@ function cmd::rule::list() {
|
|||
# Show
|
||||
# ============================================
|
||||
|
||||
# function cmd::rule::show() {
|
||||
# local name="" show_peers=true color=false show_resolved=false
|
||||
|
||||
# while [[ $# -gt 0 ]]; do
|
||||
# case "$1" in
|
||||
# --name) util::require_flag "--name" "${2:-}" || return 1
|
||||
# name="$2"; shift 2 ;;
|
||||
# --no-peers) show_peers=false; shift ;;
|
||||
# --color) color=true; shift ;;
|
||||
# --resolved) show_resolved=true; shift ;;
|
||||
# --help) cmd::rule::help; return ;;
|
||||
# *) log::error "Unknown flag: $1"; return 1 ;;
|
||||
# esac
|
||||
# done
|
||||
|
||||
# [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||
# rule::require_exists "$name" || return 1
|
||||
|
||||
# local rule_file
|
||||
# rule_file="$(rule::path "$name")"
|
||||
|
||||
# local dns_redirect
|
||||
# dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
||||
# dns_redirect="${dns_redirect:-false}"
|
||||
|
||||
# local resolved_dns
|
||||
# resolved_dns=$(rule::get "$name" "dns_redirect")
|
||||
# resolved_dns="${resolved_dns:-false}"
|
||||
|
||||
# local dns_display
|
||||
# if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then
|
||||
# dns_display="true (inherited)"
|
||||
# elif [[ "${dns_redirect,,}" == "true" ]]; then
|
||||
# dns_display="true"
|
||||
# else
|
||||
# dns_display="false"
|
||||
# fi
|
||||
|
||||
|
||||
# log::section "Rule: ${name}"
|
||||
# printf "\n"
|
||||
|
||||
# # ── Header info ──────────────────────────────
|
||||
# local desc group dns_redirect
|
||||
# desc=$(json::get "$rule_file" "desc")
|
||||
# group=$(json::get "$rule_file" "group")
|
||||
|
||||
# ui::row "Description" "${desc:-—}"
|
||||
# ui::row "Group" "${group:-—}"
|
||||
# ui::row "DNS" "$dns_display"
|
||||
|
||||
# # ── Extends section ──────────────────────────
|
||||
# local extends_raw=()
|
||||
# mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||
|
||||
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||
# cmd::rule::_show_section "Extends"
|
||||
|
||||
# for base_name in "${extends_raw[@]}"; do
|
||||
# [[ -z "$base_name" ]] && continue
|
||||
# printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||
|
||||
# # Show resolved entries for this base
|
||||
# local base_allow_ports base_allow_ips base_block_ips base_block_ports base_dns
|
||||
# base_allow_ports=$(rule::get "$base_name" "allow_ports" 2>/dev/null || true)
|
||||
# base_allow_ips=$(rule::get "$base_name" "allow_ips" 2>/dev/null || true)
|
||||
# base_block_ips=$(rule::get "$base_name" "block_ips" 2>/dev/null || true)
|
||||
# base_block_ports=$(rule::get "$base_name" "block_ports" 2>/dev/null || true)
|
||||
# base_dns=$(json::get "$(rule::path "$base_name")" "dns_redirect" 2>/dev/null || true)
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$base_allow_ports"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$base_allow_ips"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$base_block_ips"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$base_block_ports"
|
||||
|
||||
# [[ "$base_dns" == "true" ]] && \
|
||||
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
# done
|
||||
# fi
|
||||
|
||||
# # ── Own Rules section ─────────────────────────
|
||||
# local own_allow_ports own_allow_ips own_block_ips own_block_ports
|
||||
# own_allow_ports=$(json::get "$rule_file" "allow_ports")
|
||||
# own_allow_ips=$(json::get "$rule_file" "allow_ips")
|
||||
# own_block_ips=$(json::get "$rule_file" "block_ips")
|
||||
# own_block_ports=$(json::get "$rule_file" "block_ports")
|
||||
|
||||
# local has_own=false
|
||||
# [[ -n "$own_allow_ports" || -n "$own_allow_ips" || \
|
||||
# -n "$own_block_ips" || -n "$own_block_ports" ]] && has_own=true
|
||||
|
||||
# if $has_own; then
|
||||
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||
# cmd::rule::_show_section "Own Rules"
|
||||
# printf "\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$own_allow_ports"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "+" "$e"
|
||||
# done <<< "$own_allow_ips"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$own_block_ips"
|
||||
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# net::print_entry "-" "$e"
|
||||
# done <<< "$own_block_ports"
|
||||
|
||||
# [[ "$dns_redirect" == "true" ]] && \
|
||||
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
# else
|
||||
# # No inheritance — use Allow/Block sections
|
||||
# if [[ -n "$own_allow_ports" || -n "$own_allow_ips" ]]; then
|
||||
# cmd::rule::_show_section "Allow"
|
||||
# printf "\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
# done <<< "$own_allow_ports"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
# done <<< "$own_allow_ips"
|
||||
# fi
|
||||
|
||||
# if [[ -n "$own_block_ips" || -n "$own_block_ports" ]]; then
|
||||
# cmd::rule::_show_section "Block"
|
||||
# printf "\n"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
# done <<< "$own_block_ips"
|
||||
# while IFS= read -r e; do
|
||||
# [[ -z "$e" ]] && continue
|
||||
# printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
# done <<< "$own_block_ports"
|
||||
# fi
|
||||
|
||||
# if [[ "$dns_redirect" == "true" ]]; then
|
||||
# cmd::rule::_show_section "DNS"
|
||||
# printf "\n \033[0;36m↺\033[0m Redirect all DNS → %s\n" "$(config::dns)"
|
||||
# fi
|
||||
# fi
|
||||
# elif [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]}" ]]; then
|
||||
# printf "\n"
|
||||
# ui::row "Access" "full (no restrictions)"
|
||||
# fi
|
||||
|
||||
# # ── Resolved section (optional) ──────────────
|
||||
# if $show_resolved; then
|
||||
# cmd::rule::_show_section "Resolved (applied to peers)"
|
||||
# local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
||||
# res_allow_ports=$(rule::get "$name" "allow_ports")
|
||||
# res_allow_ips=$(rule::get "$name" "allow_ips")
|
||||
# res_block_ips=$(rule::get "$name" "block_ips")
|
||||
# res_block_ports=$(rule::get "$name" "block_ports")
|
||||
|
||||
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
# printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ports"
|
||||
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
# printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ips"
|
||||
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
# printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ips"
|
||||
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
# printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ports"
|
||||
# fi
|
||||
|
||||
# # ── Peers section ─────────────────────────────
|
||||
# cmd::rule::_show_section "Peers"
|
||||
|
||||
# local peer_list=()
|
||||
# mapfile -t peer_list < <(peers::with_rule "$name")
|
||||
# local peer_count=${#peer_list[@]}
|
||||
# ui::row "Assigned" "$peer_count"
|
||||
|
||||
# if $show_peers && [[ $peer_count -gt 0 ]]; then
|
||||
# printf "\n"
|
||||
# for peer_name in "${peer_list[@]}"; do
|
||||
# local ip
|
||||
# ip=$(peers::get_ip "$peer_name")
|
||||
# printf " %-28s %s\n" "$peer_name" "$ip"
|
||||
# done
|
||||
# fi
|
||||
|
||||
# printf "\n"
|
||||
# }
|
||||
|
||||
function cmd::rule::show() {
|
||||
local name="" show_peers=true color=false show_resolved=false
|
||||
local name="" show_peers=true show_resolved=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||
name="$2"; shift 2 ;;
|
||||
--no-peers) show_peers=false; shift ;;
|
||||
--color) color=true; shift ;;
|
||||
--no-peers) show_peers=false; shift ;;
|
||||
--resolved) show_resolved=true; shift ;;
|
||||
--help) cmd::rule::help; return ;;
|
||||
--help) cmd::rule::help; return ;;
|
||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
|
@ -332,15 +542,13 @@ function cmd::rule::show() {
|
|||
local rule_file
|
||||
rule_file="$(rule::path "$name")"
|
||||
|
||||
local dns_redirect
|
||||
# ── DNS display ───────────────────────────────
|
||||
local dns_redirect resolved_dns dns_display
|
||||
dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
||||
dns_redirect="${dns_redirect:-false}"
|
||||
|
||||
local resolved_dns
|
||||
resolved_dns=$(rule::get "$name" "dns_redirect")
|
||||
resolved_dns="${resolved_dns:-false}"
|
||||
|
||||
local dns_display
|
||||
if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then
|
||||
dns_display="true (inherited)"
|
||||
elif [[ "${dns_redirect,,}" == "true" ]]; then
|
||||
|
|
@ -349,157 +557,46 @@ function cmd::rule::show() {
|
|||
dns_display="false"
|
||||
fi
|
||||
|
||||
|
||||
log::section "Rule: ${name}"
|
||||
printf "\n"
|
||||
|
||||
# ── Header info ──────────────────────────────
|
||||
local desc group dns_redirect
|
||||
local desc group
|
||||
desc=$(json::get "$rule_file" "desc")
|
||||
group=$(json::get "$rule_file" "group")
|
||||
|
||||
ui::row "Description" "${desc:-—}"
|
||||
ui::row "Group" "${group:-—}"
|
||||
ui::row "DNS" "$dns_display"
|
||||
|
||||
# ── Extends section ──────────────────────────
|
||||
local extends_raw=()
|
||||
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||
|
||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||
cmd::rule::_show_section "Extends"
|
||||
|
||||
for base_name in "${extends_raw[@]}"; do
|
||||
[[ -z "$base_name" ]] && continue
|
||||
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||
|
||||
# Show resolved entries for this base
|
||||
local base_allow_ports base_allow_ips base_block_ips base_block_ports base_dns
|
||||
base_allow_ports=$(rule::get "$base_name" "allow_ports" 2>/dev/null || true)
|
||||
base_allow_ips=$(rule::get "$base_name" "allow_ips" 2>/dev/null || true)
|
||||
base_block_ips=$(rule::get "$base_name" "block_ips" 2>/dev/null || true)
|
||||
base_block_ports=$(rule::get "$base_name" "block_ports" 2>/dev/null || true)
|
||||
base_dns=$(json::get "$(rule::path "$base_name")" "dns_redirect" 2>/dev/null || true)
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$base_allow_ports"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$base_allow_ips"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$base_block_ips"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$base_block_ports"
|
||||
[[ "$base_dns" == "true" ]] && \
|
||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
done
|
||||
fi
|
||||
|
||||
# ── Own Rules section ─────────────────────────
|
||||
local own_allow_ports own_allow_ips own_block_ips own_block_ports
|
||||
own_allow_ports=$(json::get "$rule_file" "allow_ports")
|
||||
own_allow_ips=$(json::get "$rule_file" "allow_ips")
|
||||
own_block_ips=$(json::get "$rule_file" "block_ips")
|
||||
own_block_ports=$(json::get "$rule_file" "block_ports")
|
||||
|
||||
local has_own=false
|
||||
[[ -n "$own_allow_ports" || -n "$own_allow_ips" || \
|
||||
-n "$own_block_ips" || -n "$own_block_ports" ]] && has_own=true
|
||||
|
||||
if $has_own; then
|
||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||
cmd::rule::_show_section "Own Rules"
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$own_allow_ports"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$own_allow_ips"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$own_block_ips"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$own_block_ports"
|
||||
[[ "$dns_redirect" == "true" ]] && \
|
||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||
else
|
||||
# No inheritance — use Allow/Block sections
|
||||
if [[ -n "$own_allow_ports" || -n "$own_allow_ips" ]]; then
|
||||
cmd::rule::_show_section "Allow"
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$own_allow_ports"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||
done <<< "$own_allow_ips"
|
||||
fi
|
||||
|
||||
if [[ -n "$own_block_ips" || -n "$own_block_ports" ]]; then
|
||||
cmd::rule::_show_section "Block"
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$own_block_ips"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||
done <<< "$own_block_ports"
|
||||
fi
|
||||
|
||||
if [[ "$dns_redirect" == "true" ]]; then
|
||||
cmd::rule::_show_section "DNS"
|
||||
printf "\n \033[0;36m↺\033[0m Redirect all DNS → %s\n" "$(config::dns)"
|
||||
fi
|
||||
fi
|
||||
elif [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]}" ]]; then
|
||||
# ── Extends + own rules ────────────────────────
|
||||
if rule::render_extends_tree "$name"; then
|
||||
# Has inheritance — tree already rendered
|
||||
printf "\n"
|
||||
ui::row "Access" "full (no restrictions)"
|
||||
else
|
||||
# No inheritance — flat view
|
||||
rule::render_flat "$name"
|
||||
fi
|
||||
|
||||
# ── Resolved section (optional) ──────────────
|
||||
# ── Resolved ──────────────────────────────────
|
||||
if $show_resolved; then
|
||||
cmd::rule::_show_section "Resolved (applied to peers)"
|
||||
printf "\n"
|
||||
local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
||||
res_allow_ports=$(rule::get "$name" "allow_ports")
|
||||
res_allow_ips=$(rule::get "$name" "allow_ips")
|
||||
res_block_ips=$(rule::get "$name" "block_ips")
|
||||
res_block_ports=$(rule::get "$name" "block_ports")
|
||||
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ports"
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ips"
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ips"
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||
printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ports"
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "+" "$e"; done \
|
||||
<<< "$res_allow_ports"$'\n'"$res_allow_ips"
|
||||
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "-" "$e"; done \
|
||||
<<< "$res_block_ips"$'\n'"$res_block_ports"
|
||||
fi
|
||||
|
||||
# ── Peers section ─────────────────────────────
|
||||
# ── Peers ─────────────────────────────────────
|
||||
cmd::rule::_show_section "Peers"
|
||||
|
||||
local peer_list=()
|
||||
mapfile -t peer_list < <(peers::with_rule "$name")
|
||||
local peer_count=${#peer_list[@]}
|
||||
ui::row "Assigned" "$peer_count"
|
||||
|
||||
if $show_peers && [[ $peer_count -gt 0 ]]; then
|
||||
printf "\n"
|
||||
for peer_name in "${peer_list[@]}"; do
|
||||
|
|
@ -508,8 +605,8 @@ function cmd::rule::show() {
|
|||
printf " %-28s %s\n" "$peer_name" "$ip"
|
||||
done
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
|
|
@ -520,6 +617,7 @@ function cmd::rule::add() {
|
|||
local name="" desc="" group=""
|
||||
local extends=()
|
||||
local allow_ips=() block_ips=() block_ports=() allow_ports=()
|
||||
local block_services=() allow_services=()
|
||||
local dns_redirect=false
|
||||
local is_base=false
|
||||
|
||||
|
|
@ -537,6 +635,8 @@ function cmd::rule::add() {
|
|||
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
||||
--block-ip) block_ips+=("$2"); shift 2 ;;
|
||||
--block-port) block_ports+=("$2"); shift 2 ;;
|
||||
--block-service) block_services+=("$2"); shift 2 ;;
|
||||
--allow-service) allow_services+=("$2"); shift 2 ;;
|
||||
--dns-redirect) dns_redirect=true; shift ;;
|
||||
--help) cmd::rule::help; return ;;
|
||||
*)
|
||||
|
|
@ -566,6 +666,26 @@ function cmd::rule::add() {
|
|||
rule_dir="$(ctx::rules)"
|
||||
fi
|
||||
|
||||
for svc in "${block_services[@]}"; do
|
||||
while IFS= read -r resolved; do
|
||||
if [[ "$resolved" == *:*:* ]]; then
|
||||
block_ports+=("${resolved}")
|
||||
else
|
||||
block_ips+=("${resolved}/32")
|
||||
fi
|
||||
done < <(net::resolve "$svc")
|
||||
done
|
||||
|
||||
for svc in "${allow_services[@]}"; do
|
||||
while IFS= read -r resolved; do
|
||||
if [[ "$resolved" == *:*:* ]]; then
|
||||
allow_ports+=("${resolved}")
|
||||
else
|
||||
allow_ips+=("${resolved}/32")
|
||||
fi
|
||||
done < <(net::resolve "$svc")
|
||||
done
|
||||
|
||||
local rule_file="${rule_dir}/${name}.rule"
|
||||
|
||||
local allow_str block_str port_str allow_port_str extends_str
|
||||
|
|
@ -844,4 +964,4 @@ function cmd::rule::_show_section() {
|
|||
esac
|
||||
fi
|
||||
printf "\n ${color_code}── %s ──────────────────────────────────\033[0m\n" "$title"
|
||||
}
|
||||
}
|
||||
|
|
@ -204,6 +204,34 @@ function cmd::test::section_fw() {
|
|||
cmd::test::run_cmd "fw nat" "PREROUTING" fw nat
|
||||
cmd::test::run_cmd "fw count" "TOTAL" fw count
|
||||
}
|
||||
function cmd::test::section_net() {
|
||||
test::section "Net"
|
||||
|
||||
"$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true
|
||||
|
||||
cmd::test::run_cmd "net add service" "added" \
|
||||
net add --name test-svc --ip 10.0.0.99 --desc "Test service"
|
||||
cmd::test::run_cmd "net add port" "Added" \
|
||||
net add --name test-svc:web --port 9999:tcp
|
||||
cmd::test::run_cmd "net list" "test-svc" \
|
||||
net list
|
||||
cmd::test::run_cmd "net list --detailed" "web" \
|
||||
net list --detailed
|
||||
cmd::test::run_cmd "net show" "9999" \
|
||||
net show --name test-svc
|
||||
cmd::test::run_cmd "net rm port" "Removed" \
|
||||
net rm --name test-svc:web --force
|
||||
cmd::test::run_cmd "net add port again" "Added" \
|
||||
net add --name test-svc:web --port 9999:tcp
|
||||
cmd::test::run_cmd "net rm all ports" "Removed" \
|
||||
net rm --name test-svc:ports --force
|
||||
cmd::test::run_cmd "net rm service" "Removed" \
|
||||
net rm --name test-svc --force
|
||||
cmd::test::run_cmd_fails "net show nonexistent" \
|
||||
net show --name nonexistent-svc
|
||||
cmd::test::run_cmd_fails "net add port no service" \
|
||||
net add --name nonexistent:web --port 80:tcp
|
||||
}
|
||||
|
||||
function cmd::test::section_destructive() {
|
||||
test::section "Destructive (modifying state)"
|
||||
|
|
@ -432,6 +460,7 @@ function cmd::test::run() {
|
|||
audit) cmd::test::section_audit ;;
|
||||
logs) cmd::test::section_logs ;;
|
||||
fw) cmd::test::section_fw ;;
|
||||
net) cmd::test::section_net ;;
|
||||
destructive) cmd::test::section_destructive ;;
|
||||
*)
|
||||
log::error "Unknown section: $section"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ _CTX_GROUPS="${_CTX_DATA}/groups"
|
|||
_CTX_BLOCKS="${_CTX_DATA}/blocks"
|
||||
_CTX_META="${_CTX_DATA}/meta"
|
||||
_CTX_DAEMON="${_CTX_DATA}/daemon"
|
||||
_CTX_NET="${_CTX_DATA}/services.json"
|
||||
|
||||
# ============================================
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ function ctx::groups() { echo "$_CTX_GROUPS"; }
|
|||
function ctx::blocks() { echo "$_CTX_BLOCKS"; }
|
||||
function ctx::meta() { echo "$_CTX_META"; }
|
||||
function ctx::daemon() { echo "$_CTX_DAEMON"; }
|
||||
function ctx::net() { echo "$_CTX_NET"; }
|
||||
function ctx::events_log() { echo "$(ctx::daemon)/events.log"; }
|
||||
function ctx::fw_events_log() { echo "$(ctx::daemon)/fw_events.log"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,15 @@ function json::block_remove_rule() { python3 "$JSON_HELPER" block_remove_ru
|
|||
function json::block_get_rules() { python3 "$JSON_HELPER" block_get_rules "$@" </dev/null; }
|
||||
function json::block_get_groups() { python3 "$JSON_HELPER" block_get_groups "$@" </dev/null; }
|
||||
function json::block_get_direct() { python3 "$JSON_HELPER" block_get_direct "$@" </dev/null; }
|
||||
function json::net_list() { python3 "$JSON_HELPER" net_list "$@" </dev/null; }
|
||||
function json::net_show() { python3 "$JSON_HELPER" net_show "$@" </dev/null; }
|
||||
function json::net_exists() { python3 "$JSON_HELPER" net_exists "$@" </dev/null; }
|
||||
function json::net_add_service() { python3 "$JSON_HELPER" net_add_service "$@" </dev/null; }
|
||||
function json::net_add_port() { python3 "$JSON_HELPER" net_add_port "$@" </dev/null; }
|
||||
function json::net_remove() { python3 "$JSON_HELPER" net_remove "$@" </dev/null; }
|
||||
function json::net_resolve() { python3 "$JSON_HELPER" net_resolve "$@" </dev/null; }
|
||||
function json::net_reverse_lookup() { python3 "$JSON_HELPER" net_reverse_lookup "$@" </dev/null; }
|
||||
function json::block_is_empty() { python3 "$JSON_HELPER" block_is_empty "$@" </dev/null; }
|
||||
|
||||
function json::peer_transfer() {
|
||||
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
||||
|
|
|
|||
|
|
@ -1312,6 +1312,164 @@ def block_get_direct(file):
|
|||
return
|
||||
print('true' if data.get('blocked_direct', False) else 'false')
|
||||
|
||||
# ============================================
|
||||
# Net / Services
|
||||
# ============================================
|
||||
|
||||
def _net_read(file):
|
||||
"""Read services.json, return dict or empty dict"""
|
||||
try:
|
||||
if not os.path.exists(file):
|
||||
return {}
|
||||
with open(file) as f:
|
||||
content = f.read().strip()
|
||||
if not content:
|
||||
return {}
|
||||
return json.loads(content)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _net_write(file, data):
|
||||
"""Write services.json"""
|
||||
os.makedirs(os.path.dirname(file), exist_ok=True)
|
||||
with open(file, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def net_list(file):
|
||||
"""List all service names with IP and port count"""
|
||||
data = _net_read(file)
|
||||
for name, svc in sorted(data.items()):
|
||||
ip = svc.get('ip', '')
|
||||
desc = svc.get('desc', '')
|
||||
tags = ','.join(svc.get('tags', []))
|
||||
ports = len(svc.get('ports', {}))
|
||||
print(f"{name}|{ip}|{desc}|{tags}|{ports}")
|
||||
|
||||
def net_show(file, name):
|
||||
"""Show full service details"""
|
||||
data = _net_read(file)
|
||||
if name not in data:
|
||||
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
svc = data[name]
|
||||
print(f"name|{name}")
|
||||
print(f"ip|{svc.get('ip','')}")
|
||||
print(f"desc|{svc.get('desc','')}")
|
||||
print(f"tags|{','.join(svc.get('tags',[]))}")
|
||||
for port_name, port_def in svc.get('ports', {}).items():
|
||||
port = port_def.get('port', '')
|
||||
proto = port_def.get('proto', 'tcp')
|
||||
desc = port_def.get('desc', '')
|
||||
print(f"port|{port_name}|{port}|{proto}|{desc}")
|
||||
|
||||
def net_exists(file, name):
|
||||
"""Check if service exists"""
|
||||
data = _net_read(file)
|
||||
# Handle service:port syntax
|
||||
if ':' in name:
|
||||
svc_name, port_name = name.split(':', 1)
|
||||
if port_name == 'ports':
|
||||
print('true' if svc_name in data else 'false')
|
||||
else:
|
||||
svc = data.get(svc_name, {})
|
||||
print('true' if port_name in svc.get('ports', {}) else 'false')
|
||||
else:
|
||||
print('true' if name in data else 'false')
|
||||
|
||||
def net_add_service(file, name, ip, desc='', tags=''):
|
||||
"""Add or update a service"""
|
||||
data = _net_read(file)
|
||||
if name not in data:
|
||||
data[name] = {'ip': ip, 'ports': {}}
|
||||
else:
|
||||
data[name]['ip'] = ip
|
||||
if desc:
|
||||
data[name]['desc'] = desc
|
||||
if tags:
|
||||
data[name]['tags'] = [t.strip() for t in tags.split(',') if t.strip()]
|
||||
_net_write(file, data)
|
||||
|
||||
def net_add_port(file, service, port_name, port, proto='tcp', desc=''):
|
||||
"""Add or update a port on a service"""
|
||||
data = _net_read(file)
|
||||
if service not in data:
|
||||
print(f"Error: Service not found: {service}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if 'ports' not in data[service]:
|
||||
data[service]['ports'] = {}
|
||||
entry = {'port': int(port), 'proto': proto}
|
||||
if desc:
|
||||
entry['desc'] = desc
|
||||
data[service]['ports'][port_name] = entry
|
||||
_net_write(file, data)
|
||||
|
||||
def net_remove(file, name):
|
||||
"""Remove service or port"""
|
||||
data = _net_read(file)
|
||||
if ':' in name:
|
||||
svc_name, port_name = name.split(':', 1)
|
||||
if svc_name not in data:
|
||||
print(f"Error: Service not found: {svc_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if port_name == 'ports':
|
||||
# Remove all ports
|
||||
data[svc_name]['ports'] = {}
|
||||
else:
|
||||
if port_name not in data[svc_name].get('ports', {}):
|
||||
print(f"Error: Port not found: {port_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
del data[svc_name]['ports'][port_name]
|
||||
else:
|
||||
if name not in data:
|
||||
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
del data[name]
|
||||
_net_write(file, data)
|
||||
|
||||
def net_resolve(file, name):
|
||||
"""Resolve service name to ip or ip:port:proto lines"""
|
||||
data = _net_read(file)
|
||||
if ':' in name:
|
||||
svc_name, port_name = name.split(':', 1)
|
||||
if svc_name not in data:
|
||||
print(f"Error: Service not found: {svc_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
svc = data[svc_name]
|
||||
ip = svc.get('ip', '')
|
||||
if port_name == 'ports':
|
||||
# All ports
|
||||
for pname, pdef in svc.get('ports', {}).items():
|
||||
print(f"{ip}:{pdef['port']}:{pdef.get('proto','tcp')}")
|
||||
else:
|
||||
if port_name not in svc.get('ports', {}):
|
||||
print(f"Error: Port not found: {port_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
pdef = svc['ports'][port_name]
|
||||
print(f"{ip}:{pdef['port']}:{pdef.get('proto','tcp')}")
|
||||
else:
|
||||
if name not in data:
|
||||
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(data[name].get('ip', ''))
|
||||
|
||||
def net_reverse_lookup(file, ip, port='', proto=''):
|
||||
"""Reverse lookup IP/port to service name"""
|
||||
data = _net_read(file)
|
||||
for svc_name, svc in data.items():
|
||||
if svc.get('ip') != ip:
|
||||
continue
|
||||
if not port:
|
||||
print(svc_name)
|
||||
return
|
||||
for port_name, port_def in svc.get('ports', {}).items():
|
||||
if (str(port_def.get('port','')) == str(port) and
|
||||
port_def.get('proto','tcp') == proto):
|
||||
print(f"{svc_name}:{port_name}")
|
||||
return
|
||||
# IP matched but no port match — return service name
|
||||
print(svc_name)
|
||||
return
|
||||
|
||||
commands = {
|
||||
'get': lambda args: get(args[0], args[1]),
|
||||
'set': lambda args: set_key(args[0], args[1], args[2]),
|
||||
|
|
@ -1381,6 +1539,27 @@ commands = {
|
|||
'block_get_rules': lambda args: block_get_rules(args[0]),
|
||||
'block_get_groups': lambda args: block_get_groups(args[0]),
|
||||
'block_get_direct': lambda args: block_get_direct(args[0]),
|
||||
'net_list': lambda args: net_list(args[0]),
|
||||
'net_show': lambda args: net_show(args[0], args[1]),
|
||||
'net_exists': lambda args: net_exists(args[0], args[1]),
|
||||
'net_add_service': lambda args: net_add_service(
|
||||
args[0], args[1], args[2],
|
||||
args[3] if len(args) > 3 else '',
|
||||
args[4] if len(args) > 4 else ''
|
||||
),
|
||||
'net_add_port': lambda args: net_add_port(
|
||||
args[0], args[1], args[2], args[3],
|
||||
args[4] if len(args) > 4 else 'tcp',
|
||||
args[5] if len(args) > 5 else ''
|
||||
),
|
||||
'net_remove': lambda args: net_remove(args[0], args[1]),
|
||||
'net_resolve': lambda args: net_resolve(args[0], args[1]),
|
||||
'net_reverse_lookup': lambda args: net_reverse_lookup(
|
||||
args[0], args[1],
|
||||
args[2] if len(args) > 2 else '',
|
||||
args[3] if len(args) > 3 else ''
|
||||
),
|
||||
'block_is_empty': lambda args: block_is_empty(args[0]),
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -16,6 +16,16 @@ function block::has_file() {
|
|||
[[ -f "$(block::file "$name")" ]]
|
||||
}
|
||||
|
||||
function block::has_specific_rules() {
|
||||
local name="${1:?}"
|
||||
block::has_file "$name" || return 1
|
||||
while IFS="|" read -r bname btype target port proto; do
|
||||
[[ -z "$btype" ]] && continue
|
||||
[[ "$btype" != "full" ]] && return 0
|
||||
done < <(block::get_rules "$name")
|
||||
return 1
|
||||
}
|
||||
|
||||
function block::is_blocked() {
|
||||
local name="${1:?}"
|
||||
block::has_file "$name" || return 1
|
||||
|
|
@ -128,6 +138,19 @@ function block::restore_peer() {
|
|||
return 0
|
||||
}
|
||||
|
||||
function block::restore_rules_for() {
|
||||
local name="${1:?}" client_ip="${2:?}"
|
||||
while IFS="|" read -r bname btype target port proto; do
|
||||
[[ -z "$btype" ]] && continue
|
||||
case "$btype" in
|
||||
ip) fw::block_ip "$client_ip" "$target" "append" ;;
|
||||
port) fw::block_port "$client_ip" "$target" "$port" \
|
||||
"${proto:-tcp}" "append" ;;
|
||||
subnet) fw::block_subnet "$client_ip" "$target" "append" ;;
|
||||
esac
|
||||
done < <(block::get_rules "$name")
|
||||
}
|
||||
|
||||
function block::restore_all() {
|
||||
while IFS= read -r peer_name; do
|
||||
block::has_file "$peer_name" || continue
|
||||
|
|
@ -151,17 +174,68 @@ function block::restore_all() {
|
|||
function block::format_rules() {
|
||||
local name="${1:?}"
|
||||
block::has_file "$name" || return 0
|
||||
|
||||
while IFS="|" read -r bname btype target port proto; do
|
||||
[[ -z "$btype" ]] && continue
|
||||
local display
|
||||
|
||||
local display="" ann=""
|
||||
|
||||
case "$btype" in
|
||||
full) display="all traffic" ;;
|
||||
ip) display="$target" ;;
|
||||
port) display="${target}:${port}:${proto}" ;;
|
||||
subnet) display="$target" ;;
|
||||
full)
|
||||
display="all traffic"
|
||||
;;
|
||||
ip)
|
||||
display="$target"
|
||||
ann=$(net::annotate "$target")
|
||||
;;
|
||||
port)
|
||||
display="${target}:${port}:${proto}"
|
||||
ann=$(net::annotate "${target}:${port}:${proto}")
|
||||
;;
|
||||
subnet)
|
||||
display="$target"
|
||||
ann=$(net::annotate "${target%%/*}")
|
||||
;;
|
||||
esac
|
||||
local label="${bname:-$btype}"
|
||||
printf " \033[0;31m-\033[0m %-30s \033[0;37m%s\033[0m\n" \
|
||||
"$display" "$label"
|
||||
|
||||
local label="$bname"
|
||||
|
||||
# If bname wasn't set (equals type default), clear it
|
||||
case "$label" in
|
||||
full|ip|port|subnet|"") label="" ;;
|
||||
esac
|
||||
|
||||
# Suppress label if it matches annotation
|
||||
if [[ -n "$ann" && -n "$label" && \
|
||||
("$ann" == "$label" || "$ann" == "${label}:"*) ]]; then
|
||||
label=""
|
||||
fi
|
||||
|
||||
# log::debug "label='$label' ann='$ann' match=$([ "$ann" == "$label" ] && echo yes || echo no)"
|
||||
|
||||
printf " \033[0;31m-\033[0m %-20s \033[0;37m%s%s\033[0m\n" \
|
||||
"$display" \
|
||||
"${label:+${label} }" \
|
||||
"${ann:+→ ${ann}}"
|
||||
|
||||
done < <(block::get_rules "$name")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# function block::format_rules() {
|
||||
# local name="${1:?}"
|
||||
# block::has_file "$name" || return 0
|
||||
# while IFS="|" read -r bname btype target port proto; do
|
||||
# [[ -z "$btype" ]] && continue
|
||||
# local display
|
||||
# case "$btype" in
|
||||
# full) display="all traffic" ;;
|
||||
# ip) display="$target" ;;
|
||||
# port) display="${target}:${port}:${proto}" ;;
|
||||
# subnet) display="$target" ;;
|
||||
# esac
|
||||
# local label="${bname:-$btype}"
|
||||
# printf " \033[0;31m-\033[0m %-30s \033[0;37m%s\033[0m\n" \
|
||||
# "$display" "$label"
|
||||
# done < <(block::get_rules "$name")
|
||||
# }
|
||||
|
|
@ -12,143 +12,108 @@ function fw::on_load() {
|
|||
# Rule Management
|
||||
# ============================================
|
||||
|
||||
# ============================================
|
||||
# Block / Unblock
|
||||
|
||||
function fw::block_ip() {
|
||||
local client_ip="${1:-}" target_ip="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j DROP
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
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::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j DROP 2>/dev/null || true
|
||||
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}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
|
||||
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::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP 2>/dev/null || true
|
||||
}
|
||||
|
||||
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}"
|
||||
fw::_unblock_pair \
|
||||
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
||||
}
|
||||
|
||||
function fw::block_subnet() {
|
||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j DROP
|
||||
|
||||
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::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j DROP 2>/dev/null || true
|
||||
|
||||
fw::_unblock_pair -s "$client_ip" -d "$target_subnet"
|
||||
log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
|
||||
}
|
||||
|
||||
function fw::allow_subnet() {
|
||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_subnet" -j ACCEPT
|
||||
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::unallow_subnet() {
|
||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j ACCEPT 2>/dev/null || true
|
||||
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::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j ACCEPT
|
||||
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"
|
||||
}
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j ACCEPT 2>/dev/null || true
|
||||
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::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT
|
||||
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::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true
|
||||
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"
|
||||
|
||||
# Collect line numbers into array
|
||||
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}')
|
||||
done < <(iptables -L FORWARD -n --line-numbers \
|
||||
| grep -F "$client_ip" | awk '{print $1}')
|
||||
|
||||
# Delete in reverse order (highest number first)
|
||||
local count=0
|
||||
local i
|
||||
for (( i=${#linenums[@]}-1; i>=0; i-- )); do
|
||||
|
|
@ -159,118 +124,6 @@ function fw::flush_peer() {
|
|||
log::debug "flush_peer: removed $count FORWARD rules for: $client_ip"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Guest Subnet Rules
|
||||
# ============================================
|
||||
|
||||
function fw::apply_dns_redirect() {
|
||||
if iptables -t nat -C PREROUTING -s "$(config::subnet_for guest).0/24" -p udp --dport 53 -j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
|
||||
log::wg "Guest DNS redirect already applied"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local guest_subnet dns
|
||||
guest_subnet="$(config::subnet_for "guest").0/24"
|
||||
dns="$(config::dns)"
|
||||
|
||||
# Log DNS bypass attempts (queries not directed at Pi-hole)
|
||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 \
|
||||
! -d "$dns" \
|
||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
|
||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 \
|
||||
! -d "$dns" \
|
||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
|
||||
|
||||
# Redirect all DNS to Pi-hole
|
||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53"
|
||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53"
|
||||
|
||||
log::wg_block "Guest DNS redirected to Pi-hole (${dns}), bypass attempts will be logged"
|
||||
}
|
||||
|
||||
function fw::remove_dns_redirect() {
|
||||
local guest_subnet dns
|
||||
guest_subnet="$(config::subnet_for "guest").0/24"
|
||||
dns="$(config::dns)"
|
||||
|
||||
iptables -t nat -D PREROUTING -s "$guest_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 -s "$guest_subnet" -p tcp --dport 53 \
|
||||
! -d "$dns" \
|
||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
|
||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
|
||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
|
||||
|
||||
log::debug "Removed guest DNS redirect"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Peer related
|
||||
# ============================================
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function fw::format_rule() {
|
||||
local line="${1:-}"
|
||||
[[ -z "$line" ]] && return 0
|
||||
|
||||
# Parse verbose iptables format:
|
||||
# pkts bytes target prot opt in out src dst [extra]
|
||||
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"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Helpers
|
||||
# ============================================
|
||||
|
||||
function fw::_nat_exists() {
|
||||
fw::_rule_exists nat PREROUTING "$@"
|
||||
}
|
||||
|
||||
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::flush_forward() {
|
||||
iptables -F FORWARD
|
||||
log::debug "Flushed FORWARD chain"
|
||||
|
|
@ -286,6 +139,10 @@ function fw::flush_all() {
|
|||
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" \
|
||||
|
|
@ -311,16 +168,137 @@ function fw::nat_remove_dns_redirect() {
|
|||
--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::_forward_exists() {
|
||||
iptables -C FORWARD "$@" 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
|
||||
}
|
||||
|
||||
|
|
|
|||
102
modules/net.module.sh
Normal file
102
modules/net.module.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function net::exists() {
|
||||
local name="${1:?}"
|
||||
local result
|
||||
result=$(json::net_exists "$(ctx::net)" "$name")
|
||||
[[ "$result" == "true" ]]
|
||||
}
|
||||
|
||||
function net::require_exists() {
|
||||
local name="${1:?}"
|
||||
if ! net::exists "$name"; then
|
||||
log::error "Service not found: ${name}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function net::resolve() {
|
||||
local name="${1:?}"
|
||||
json::net_resolve "$(ctx::net)" "$name"
|
||||
}
|
||||
|
||||
function net::reverse_lookup() {
|
||||
local ip="${1:-}" port="${2:-}" proto="${3:-}"
|
||||
[[ -z "$ip" ]] && return 0
|
||||
json::net_reverse_lookup "$(ctx::net)" "$ip" "$port" "$proto"
|
||||
}
|
||||
|
||||
function net::annotation() {
|
||||
# Returns " → service:port" or "" — for display use
|
||||
local ip="${1:-}" port="${2:-}" proto="${3:-}"
|
||||
local match
|
||||
match=$(net::reverse_lookup "$ip" "$port" "$proto")
|
||||
[[ -n "$match" ]] && echo " → ${match}" || echo ""
|
||||
}
|
||||
|
||||
function net::annotate() {
|
||||
# Returns " → service:port-name" or "" for display use
|
||||
local entry="${1:-}"
|
||||
[[ -z "$entry" ]] && return 0
|
||||
|
||||
local ann=""
|
||||
if [[ "$entry" == *:*:* ]]; then
|
||||
# ip:port:proto
|
||||
local b_ip b_port b_proto
|
||||
IFS=":" read -r b_ip b_port b_proto <<< "$entry"
|
||||
ann=$(net::reverse_lookup "$b_ip" "$b_port" "$b_proto")
|
||||
else
|
||||
# ip or ip/cidr
|
||||
local ip="${entry%%/*}"
|
||||
ann=$(net::reverse_lookup "$ip")
|
||||
fi
|
||||
|
||||
[[ -n "$ann" ]] && echo "${ann}" || echo ""
|
||||
}
|
||||
|
||||
# function net::print_entry() {
|
||||
# local sign="${1:-}" entry="${2:-}" indent="${3:-6}"
|
||||
|
||||
# local ann
|
||||
# ann=$(net::annotate "$entry")
|
||||
|
||||
# local color
|
||||
# [[ "$sign" == "+" ]] && color="\033[0;32m" || color="\033[0;31m"
|
||||
|
||||
# local spaces
|
||||
# spaces=$(printf '%*s' "$indent" '')
|
||||
# printf "%s%b%s\033[0m %s\033[0;37m%s\033[0m\n" \
|
||||
# "$spaces" "$color" "$sign" "$entry" "${ann:+ → ${ann}}"
|
||||
# }
|
||||
|
||||
function net::print_entry() {
|
||||
local sign="${1:-}" entry="${2:-}" indent="${3:-6}"
|
||||
local ann
|
||||
ann=$(net::annotate "$entry")
|
||||
local color
|
||||
[[ "$sign" == "+" ]] && color="\033[0;32m" || color="\033[0;31m"
|
||||
local spaces
|
||||
spaces=$(printf '%*s' "$indent" '')
|
||||
printf "%s%b%s\033[0m %-20s\033[0;37m%s\033[0m\n" \
|
||||
"$spaces" "$color" "$sign" "$entry" \
|
||||
"${ann:+ → ${ann}}"
|
||||
}
|
||||
|
||||
function net::print_dns_redirect() {
|
||||
local ip="${1:-}" indent="${2:-6}" label="${3:-DNS}"
|
||||
local spaces
|
||||
spaces=$(printf '%*s' "$indent" '')
|
||||
local ann
|
||||
ann=$(net::annotate "$ip")
|
||||
printf "%s\033[0;36m↺\033[0m %s → %s\033[0;37m%s\033[0m\n" \
|
||||
"$spaces" "$label" "$ip" "${ann:+ → ${ann}}"
|
||||
}
|
||||
|
||||
function net::print_dns_redirect_full() {
|
||||
# For rule::show — slightly different prefix
|
||||
local ip="${1:-}"
|
||||
local ann
|
||||
ann=$(net::annotate "$ip")
|
||||
printf " \033[0;36m↺\033[0m Redirect all DNS → %s\033[0;37m%s\033[0m\n" \
|
||||
"$ip" "${ann:+ → ${ann}}"
|
||||
}
|
||||
|
|
@ -288,6 +288,143 @@ function rule::restore_all() {
|
|||
log::wg "Rules restored for all peers"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Rendering
|
||||
# ============================================
|
||||
|
||||
function rule::render_flat() {
|
||||
local rule_name="${1:-}"
|
||||
|
||||
local allow_ports allow_ips block_ips block_ports dns
|
||||
allow_ports=$(rule::get "$rule_name" "allow_ports")
|
||||
allow_ips=$(rule::get "$rule_name" "allow_ips")
|
||||
block_ips=$(rule::get "$rule_name" "block_ips")
|
||||
block_ports=$(rule::get "$rule_name" "block_ports")
|
||||
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||
|
||||
local has_content=false
|
||||
[[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && \
|
||||
has_content=true
|
||||
|
||||
if ! $has_content; then
|
||||
printf "\n full access (no restrictions)\n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "+" "$e" 2
|
||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||
fi
|
||||
|
||||
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||
printf "\n"
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "-" "$e" 2
|
||||
done <<< "$block_ips"$'\n'"$block_ports"
|
||||
fi
|
||||
|
||||
[[ "${dns,,}" == "true" ]] && \
|
||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function rule::render_entries() {
|
||||
# Renders allow/block entries for a rule name with annotations and DNS
|
||||
# Usage: rule::render_entries <rule_name> <indent>
|
||||
# indent: 4 for rule show, 4 for inspect (same)
|
||||
local rule_name="${1:-}" indent="${2:-4}"
|
||||
local rule_file
|
||||
rule_file="$(rule::path "$rule_name")"
|
||||
|
||||
local allow_ports allow_ips block_ips block_ports dns
|
||||
allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
|
||||
allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
|
||||
block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
|
||||
block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
|
||||
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "+" "$e"
|
||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "-" "$e"
|
||||
done <<< "$block_ips"$'\n'"$block_ports"
|
||||
|
||||
[[ "${dns,,}" == "true" ]] && \
|
||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||
}
|
||||
|
||||
function rule::render_own_entries() {
|
||||
# Renders own (non-inherited) entries for a rule
|
||||
local rule_name="${1:-}"
|
||||
local rule_file
|
||||
rule_file="$(rule::path "$rule_name")"
|
||||
|
||||
local allow_ports allow_ips block_ips block_ports dns
|
||||
allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
|
||||
allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
|
||||
block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
|
||||
block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
|
||||
dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
|
||||
|
||||
local has_own=false
|
||||
local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
|
||||
[[ -n "${combined//[$'\n']/}" ]] && has_own=true
|
||||
|
||||
$has_own || return 0
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "+" "$e"
|
||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||
|
||||
while IFS= read -r e; do
|
||||
[[ -z "$e" ]] && continue
|
||||
net::print_entry "-" "$e"
|
||||
done <<< "$block_ips"$'\n'"$block_ports"
|
||||
|
||||
[[ "${dns,,}" == "true" ]] && \
|
||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function rule::render_extends_tree() {
|
||||
# Renders full inheritance tree for a rule
|
||||
local rule_name="${1:-}"
|
||||
local rule_file
|
||||
rule_file="$(rule::path "$rule_name")"
|
||||
|
||||
local extends_raw=()
|
||||
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||
|
||||
[[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]] && return 1
|
||||
|
||||
for base_name in "${extends_raw[@]}"; do
|
||||
[[ -z "$base_name" ]] && continue
|
||||
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||
rule::render_entries "$base_name"
|
||||
done
|
||||
|
||||
# Own rules after inherited
|
||||
local own_output
|
||||
own_output=$(rule::render_own_entries "$rule_name")
|
||||
if [[ -n "$own_output" ]]; then
|
||||
printf "\n \033[0;37mOwn:\033[0m\n"
|
||||
printf "%s\n" "$own_output"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# DNS Redirect
|
||||
# ============================================
|
||||
|
|
|
|||
4
wgctl
4
wgctl
|
|
@ -18,6 +18,7 @@ load_module firewall
|
|||
load_module monitor
|
||||
load_module rule
|
||||
load_module block
|
||||
load_module net
|
||||
load_module group
|
||||
|
||||
# ============================================
|
||||
|
|
@ -51,6 +52,9 @@ declare -A CMD_ALIASES=(
|
|||
[disable]=service
|
||||
)
|
||||
|
||||
block::has_specific_rules "phone-test" && echo "HAS SPECIFIC" || echo "NO SPECIFIC"
|
||||
block::get_rules "phone-test"
|
||||
|
||||
# ============================================
|
||||
# Dispatch
|
||||
# ============================================
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue