feat: group watch with peer filter, watch --peers flag, watch command cleanup
This commit is contained in:
parent
b1bca613de
commit
8ef8ea91b3
3 changed files with 96 additions and 91 deletions
|
|
@ -845,8 +845,6 @@ function cmd::group::watch() {
|
||||||
log::section "Live Monitor: Group '${name}'"
|
log::section "Live Monitor: Group '${name}'"
|
||||||
printf " Monitoring: %s\n\n" "${peers_list[*]}"
|
printf " Monitoring: %s\n\n" "${peers_list[*]}"
|
||||||
|
|
||||||
load_command logs
|
load_command watch
|
||||||
# Use follow mode — it shows all peers but user knows context
|
cmd::watch::run --peers "$peer_filter"
|
||||||
# Future: add --peers flag to follow for multi-peer filter
|
|
||||||
cmd::logs::follow "" "" "" false false "$peer_filter"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
function cmd::watch::on_load() {
|
function cmd::watch::on_load() {
|
||||||
flag::register --type
|
flag::register --type
|
||||||
flag::register --name
|
flag::register --name
|
||||||
|
flag::register --peers
|
||||||
flag::register --blocked
|
flag::register --blocked
|
||||||
flag::register --restricted
|
flag::register --restricted
|
||||||
flag::register --allowed
|
flag::register --allowed
|
||||||
|
|
@ -26,6 +27,7 @@ Shows handshakes from active clients and connection attempts from blocked client
|
||||||
Options:
|
Options:
|
||||||
--type <type> Filter by device type
|
--type <type> Filter by device type
|
||||||
--name <name> Filter by client name
|
--name <name> Filter by client name
|
||||||
|
--peers <list> Filter by comma-separated peer names (used by group watch)
|
||||||
--allowed Show only allowed client handshakes
|
--allowed Show only allowed client handshakes
|
||||||
--restricted Show only restricted client events
|
--restricted Show only restricted client events
|
||||||
--blocked Show only blocked client attempts
|
--blocked Show only blocked client attempts
|
||||||
|
|
@ -44,17 +46,14 @@ EOF
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::watch::format_event() {
|
function cmd::watch::format_event() {
|
||||||
local ts="$1"
|
local ts="${1:-}" client="${2:-}" endpoint="${3:-}"
|
||||||
local client="$2"
|
local event="${4:-}" status="${5:-}"
|
||||||
local endpoint="$3"
|
|
||||||
local event="$4"
|
|
||||||
local status="$5"
|
|
||||||
|
|
||||||
local event_color
|
local event_color
|
||||||
case "$event" in
|
case "$event" in
|
||||||
attempt) event_color="\033[1;31m" ;; # red
|
attempt) event_color="\033[1;31m" ;;
|
||||||
handshake) event_color="\033[1;32m" ;; # green
|
handshake) event_color="\033[1;32m" ;;
|
||||||
*) event_color="\033[0;37m" ;; # grey
|
*) event_color="\033[0;37m" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
local status_str=""
|
local status_str=""
|
||||||
|
|
@ -62,7 +61,6 @@ function cmd::watch::format_event() {
|
||||||
case "$status" in
|
case "$status" in
|
||||||
blocked) status_str=" \033[1;31mblocked\033[0m" ;;
|
blocked) status_str=" \033[1;31mblocked\033[0m" ;;
|
||||||
allowed) status_str=" \033[1;32mallowed\033[0m" ;;
|
allowed) status_str=" \033[1;32mallowed\033[0m" ;;
|
||||||
*) status_str="" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -77,16 +75,28 @@ function cmd::watch::header() {
|
||||||
printf " %s\n\n" "$(printf '─%.0s' {1..85})"
|
printf " %s\n\n" "$(printf '─%.0s' {1..85})"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cmd::watch::_peer_in_filter() {
|
||||||
|
local peer="$1"
|
||||||
|
shift
|
||||||
|
local peer_set=("$@")
|
||||||
|
[[ ${#peer_set[@]} -eq 0 ]] && return 0 # no filter = all pass
|
||||||
|
for p in "${peer_set[@]}"; do
|
||||||
|
[[ "$p" == "$peer" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Handshake Poller
|
# Handshake Poller
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
declare -A _WATCH_LAST_HANDSHAKES=()
|
|
||||||
|
|
||||||
function cmd::watch::poll_handshakes() {
|
function cmd::watch::poll_handshakes() {
|
||||||
local filter_name="$1"
|
local filter_name="${1:-}" filter_type="${2:-}"
|
||||||
local filter_type="$2"
|
local allowed_only="${3:-false}"
|
||||||
local allowed_only="$3"
|
local filter_peers="${4:-}"
|
||||||
|
|
||||||
|
local peer_set=()
|
||||||
|
[[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
|
||||||
|
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
local public_key ts
|
local public_key ts
|
||||||
|
|
@ -113,10 +123,12 @@ function cmd::watch::poll_handshakes() {
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
[[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
|
[[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
|
||||||
|
cmd::watch::_peer_in_filter "$client_name" "${peer_set[@]}" || continue
|
||||||
|
|
||||||
if [[ -n "$filter_type" ]]; then
|
if [[ -n "$filter_type" ]]; then
|
||||||
local ip
|
local ip
|
||||||
ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" | awk '{print $3}' | cut -d'/' -f1)
|
ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" \
|
||||||
|
| awk '{print $3}' | cut -d'/' -f1)
|
||||||
local subnet
|
local subnet
|
||||||
subnet=$(config::subnet_for "$filter_type")
|
subnet=$(config::subnet_for "$filter_type")
|
||||||
string::starts_with "$ip" "$subnet" || continue
|
string::starts_with "$ip" "$subnet" || continue
|
||||||
|
|
@ -128,6 +140,7 @@ function cmd::watch::poll_handshakes() {
|
||||||
local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
|
local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
|
||||||
local prev_ts="0"
|
local prev_ts="0"
|
||||||
[[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
|
[[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
|
||||||
|
|
||||||
if [[ "$ts" != "$prev_ts" ]]; then
|
if [[ "$ts" != "$prev_ts" ]]; then
|
||||||
echo "$ts" > "$prev_ts_file"
|
echo "$ts" > "$prev_ts_file"
|
||||||
|
|
||||||
|
|
@ -149,74 +162,63 @@ function cmd::watch::poll_handshakes() {
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::watch::tail_events() {
|
function cmd::watch::tail_events() {
|
||||||
local filter_name="$1"
|
local filter_name="${1:-}" filter_type="${2:-}"
|
||||||
local filter_type="$2"
|
local blocked_only="${3:-false}" restricted_only="${4:-false}"
|
||||||
local blocked_only="$3"
|
local allowed_only="${5:-false}" filter_peers="${6:-}"
|
||||||
local restricted_only="$4"
|
|
||||||
local allowed_only="$5"
|
local peer_set=()
|
||||||
|
[[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
|
||||||
|
|
||||||
declare -A _WATCH_LAST_ATTEMPT=()
|
declare -A _WATCH_LAST_ATTEMPT=()
|
||||||
|
|
||||||
tail -f "$(ctx::root)/daemon/events.log" 2>/dev/null | while IFS= read -r line; do
|
tail -f "$(ctx::events_log)" 2>/dev/null | while IFS= read -r line; do
|
||||||
[[ -z "$line" ]] && continue
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
local event_data
|
local event_data
|
||||||
event_data=$(json::parse_event "$line")
|
event_data=$(json::parse_event "$line")
|
||||||
|
[[ -z "$event_data" ]] && continue
|
||||||
|
|
||||||
[[ -z "$event_data" ]] && continue
|
local ts client endpoint event
|
||||||
|
IFS="|" read -r ts client endpoint event <<< "$event_data"
|
||||||
|
|
||||||
local ts client endpoint event
|
# Apply filters
|
||||||
IFS="|" read -r ts client endpoint event <<< "$event_data"
|
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
|
||||||
|
cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue
|
||||||
|
|
||||||
if $restricted_only; then
|
if [[ -n "$filter_type" ]]; then
|
||||||
local conf
|
local conf
|
||||||
conf="$(ctx::clients)/${client}.conf"
|
conf="$(ctx::clients)/${client}.conf"
|
||||||
[[ -f "$conf" ]] || continue
|
[[ -f "$conf" ]] || continue
|
||||||
cmd::list::is_restricted "$client" || continue
|
local ip
|
||||||
fi
|
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||||
|
local subnet
|
||||||
|
subnet=$(config::subnet_for "$filter_type")
|
||||||
|
string::starts_with "$ip" "$subnet" || continue
|
||||||
|
fi
|
||||||
|
|
||||||
# Apply filters
|
if $restricted_only; then
|
||||||
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
|
local conf
|
||||||
|
conf="$(ctx::clients)/${client}.conf"
|
||||||
|
[[ -f "$conf" ]] || continue
|
||||||
|
cmd::list::is_restricted "$client" || continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$filter_type" ]]; then
|
$allowed_only && [[ "$event" != "handshake" ]] && continue
|
||||||
local conf
|
|
||||||
conf="$(ctx::clients)/${client}.conf"
|
|
||||||
[[ -f "$conf" ]] || continue
|
|
||||||
local ip
|
|
||||||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
|
||||||
local subnet
|
|
||||||
subnet=$(config::subnet_for "$filter_type")
|
|
||||||
string::starts_with "$ip" "$subnet" || continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Filter by status
|
local formatted_ts
|
||||||
if $allowed_only && [[ "$event" != "handshake" ]]; then
|
formatted_ts=$(fmt::datetime_iso "$ts")
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if $restricted_only; then
|
# Dedup attempts
|
||||||
local conf
|
local now
|
||||||
conf="$(ctx::clients)/${client}.conf"
|
now=$(date +%s)
|
||||||
[[ -f "$conf" ]] || continue
|
local safe_client="${client//[-.]/_}"
|
||||||
cmd::list::is_restricted "$client" || continue
|
local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
|
||||||
fi
|
local diff=$(( now - last ))
|
||||||
|
(( diff < 30 )) && continue
|
||||||
|
_WATCH_LAST_ATTEMPT[$safe_client]="$now"
|
||||||
|
|
||||||
local formatted_ts
|
cmd::watch::format_event \
|
||||||
formatted_ts=$(fmt::datetime_iso "$ts")
|
"$formatted_ts" "$client" "${endpoint:-—}" "$event" "blocked"
|
||||||
|
|
||||||
# Before printing the event
|
|
||||||
local now
|
|
||||||
now=$(date +%s)
|
|
||||||
local safe_client="${client//[-.]/_}"
|
|
||||||
local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
|
|
||||||
local diff=$(( now - last ))
|
|
||||||
if (( diff < 30 )); then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
_WATCH_LAST_ATTEMPT[$safe_client]="$now"
|
|
||||||
|
|
||||||
cmd::watch::format_event \
|
|
||||||
"$formatted_ts" "$client" "${endpoint:-—}" "$event" "blocked"
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,19 +227,16 @@ function cmd::watch::tail_events() {
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::watch::run() {
|
function cmd::watch::run() {
|
||||||
local filter_name=""
|
local filter_name="" filter_type="" filter_peers=""
|
||||||
local filter_type=""
|
local blocked_only=false allowed_only=false restricted_only=false
|
||||||
local blocked_only=false
|
|
||||||
local allowed_only=false
|
|
||||||
local restricted_only=false
|
|
||||||
|
|
||||||
# Clean up any stale temp files from previous runs
|
|
||||||
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
|
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) filter_name="$2"; shift 2 ;;
|
--name) filter_name="$2"; shift 2 ;;
|
||||||
--type) filter_type="$2"; shift 2 ;;
|
--type) filter_type="$2"; shift 2 ;;
|
||||||
|
--peers) filter_peers="$2"; shift 2 ;;
|
||||||
--blocked) blocked_only=true; shift ;;
|
--blocked) blocked_only=true; shift ;;
|
||||||
--allowed) allowed_only=true; shift ;;
|
--allowed) allowed_only=true; shift ;;
|
||||||
--restricted) restricted_only=true; shift ;;
|
--restricted) restricted_only=true; shift ;;
|
||||||
|
|
@ -252,25 +251,25 @@ function cmd::watch::run() {
|
||||||
|
|
||||||
cmd::watch::header
|
cmd::watch::header
|
||||||
|
|
||||||
# Start event tailer in background unless --blocked not set
|
|
||||||
if ! $blocked_only && ! $restricted_only; then
|
if ! $blocked_only && ! $restricted_only; then
|
||||||
# Poll handshakes every 5 seconds in background
|
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
cmd::watch::poll_handshakes "$filter_name" "$filter_type" "$allowed_only"
|
cmd::watch::poll_handshakes \
|
||||||
|
"$filter_name" "$filter_type" "$allowed_only" "$filter_peers"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
) &
|
) &
|
||||||
local poller_pid=$!
|
local poller_pid=$!
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Tail events log for blocked attempts
|
cmd::watch::tail_events \
|
||||||
cmd::watch::tail_events "$filter_name" "$filter_type" "$blocked_only" "$restricted_only" "$allowed_only" &
|
"$filter_name" "$filter_type" \
|
||||||
|
"$blocked_only" "$restricted_only" \
|
||||||
|
"$allowed_only" "$filter_peers" &
|
||||||
local tailer_pid=$!
|
local tailer_pid=$!
|
||||||
|
|
||||||
# Trap Ctrl+C to clean up background processes
|
trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \
|
||||||
trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM
|
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM
|
||||||
|
|
||||||
# Keep main process alive
|
|
||||||
wait
|
wait
|
||||||
}
|
}
|
||||||
|
|
@ -355,7 +355,9 @@ def follow_logs(fw_file, wg_file, filter_ip, filter_type, clients_dir, filter_pe
|
||||||
|
|
||||||
proto_map = {1: 'icmp', 6: 'tcp', 17: 'udp'}
|
proto_map = {1: 'icmp', 6: 'tcp', 17: 'udp'}
|
||||||
peer_filter = set(filter_peers.split(',')) if filter_peers else set()
|
peer_filter = set(filter_peers.split(',')) if filter_peers else set()
|
||||||
|
import sys
|
||||||
|
print(f"DEBUG follow_logs: peer_filter={peer_filter}", file=sys.stderr, flush=True)
|
||||||
|
|
||||||
# Build ip->name map
|
# Build ip->name map
|
||||||
ip_to_name = {}
|
ip_to_name = {}
|
||||||
for conf in glob.glob(f"{clients_dir}/*.conf"):
|
for conf in glob.glob(f"{clients_dir}/*.conf"):
|
||||||
|
|
@ -396,6 +398,12 @@ def follow_logs(fw_file, wg_file, filter_ip, filter_type, clients_dir, filter_pe
|
||||||
continue
|
continue
|
||||||
if filter_ip and src != filter_ip:
|
if filter_ip and src != filter_ip:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Filter by peer names if specified
|
||||||
|
if peer_filter:
|
||||||
|
client_name = ip_to_name.get(src, '')
|
||||||
|
if client_name not in peer_filter:
|
||||||
|
continue
|
||||||
dst = e.get('dest_ip', '—')
|
dst = e.get('dest_ip', '—')
|
||||||
port = e.get('dest_port', '')
|
port = e.get('dest_port', '')
|
||||||
proto_num = e.get('ip.protocol', 0)
|
proto_num = e.get('ip.protocol', 0)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue