Última atividade 1 month ago

Revisão 41e9ae3461bd9046b554156687c0b6fd4086e6c8

gistfile1.txt Bruto
1#!/usr/bin/env bash
2
3# ============================================
4# Lifecycle
5# ============================================
6
7function cmd::watch::on_load() {
8 flag::register --type
9 flag::register --name
10 flag::register --peers
11 flag::register --blocked
12 flag::register --restricted
13 flag::register --allowed
14 flag::register --raw
15}
16
17# ============================================
18# Help
19# ============================================
20
21function cmd::watch::help() {
22 cat <<EOF
23Usage: wgctl watch [options]
24
25Live monitor of WireGuard activity.
26 - Handshakes from connected peers (green)
27 - Connection attempts from blocked peers (red)
28 - Firewall drops from restricted peers (red)
29
30Options:
31 --name <name> Filter by client name
32 --type <type> Filter by device type
33 --blocked Show only blocked peer attempts
34 --allowed Show only handshakes
35 --restricted Show only firewall drop events
36 --raw Show raw IPs without service annotation
37
38Examples:
39 wgctl watch
40 wgctl watch --name phone-nuno
41 wgctl watch --blocked
42 wgctl watch --type phone
43EOF
44}
45
46# ============================================
47# Run
48# ============================================
49
50function cmd::watch::run() {
51 local filter_name="" filter_type="" filter_peers=""
52 local blocked_only=false allowed_only=false restricted_only=false
53 local raw=false
54
55 rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
56
57 while [[ $# -gt 0 ]]; do
58 case "$1" in
59 --name) filter_name="$2"; shift 2 ;;
60 --type) filter_type="$2"; shift 2 ;;
61 --peers) filter_peers="$2"; shift 2 ;;
62 --blocked) blocked_only=true; shift ;;
63 --allowed) allowed_only=true; shift ;;
64 --restricted) restricted_only=true; shift ;;
65 --raw) raw=true; shift ;;
66 --help) cmd::watch::help; return ;;
67 *)
68 log::error "Unknown flag: $1"
69 cmd::watch::help
70 return 1
71 ;;
72 esac
73 done
74
75 local net_file=""
76 $raw || net_file="$(ctx::net)"
77
78 log::section "wgctl — Live Monitor (Ctrl+C to stop)"
79 printf "\n"
80
81 # Fixed display widths for watch (dynamic measurement not possible in stream)
82 local w_client=20 w_dest=18
83
84 # Handshake poller (background)
85 if ! $blocked_only && ! $restricted_only; then
86 (
87 while true; do
88 cmd::watch::_poll_handshakes \
89 "$filter_name" "$filter_type" "$filter_peers" "$w_client" "$w_dest"
90 sleep 5
91 done
92 ) &
93 local poller_pid=$!
94 fi
95
96 # Event tailer (background)
97 cmd::watch::_tail_events \
98 "$filter_name" "$filter_type" "$filter_peers" \
99 "$blocked_only" "$restricted_only" "$allowed_only" \
100 "$net_file" "$w_client" "$w_dest" &
101 local tailer_pid=$!
102
103 trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \
104 rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; printf '\n'; exit 0" INT TERM
105
106 wait
107}
108
109# ============================================
110# Handshake Poller
111# ============================================
112
113function cmd::watch::_poll_handshakes() {
114 local filter_name="${1:-}" filter_type="${2:-}" filter_peers="${3:-}"
115 local w_client="${4:-20}" w_dest="${5:-30}"
116
117 local peer_set=()
118 [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
119
120 while IFS= read -r line; do
121 local public_key ts
122 public_key=$(echo "$line" | awk '{print $1}')
123 ts=$(echo "$line" | awk '{print $2}')
124 [[ -z "$ts" || "$ts" == "0" ]] && continue
125
126 # Find client by public key
127 local client_name=""
128 for conf in "$(ctx::clients)"/*.conf; do
129 [[ -f "$conf" ]] || continue
130 local cname
131 cname=$(basename "$conf" .conf)
132 local key
133 key=$(keys::public "$cname" 2>/dev/null || echo "")
134 if [[ "$key" == "$public_key" ]]; then
135 client_name="$cname"
136 break
137 fi
138 done
139 [[ -z "$client_name" ]] && continue
140 [[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
141
142 # Dedup — only emit if handshake is new
143 local safe_key
144 safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1)
145 local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
146 local prev_ts="0"
147 [[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
148 [[ "$ts" == "$prev_ts" ]] && continue
149 echo "$ts" > "$prev_ts_file"
150
151 local ts_fmt
152 ts_fmt=$(fmt::datetime_short "$ts")
153 local endpoint
154 endpoint=$(monitor::endpoint_for_key "$public_key")
155
156 ui::watch::wg_row "$ts_fmt" "$client_name" "${endpoint:-—}" "handshake" \
157 "$w_client" "$w_dest"
158
159 done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
160}
161
162# ============================================
163# Event Tailer
164# ============================================
165
166function cmd::watch::_tail_events() {
167 local filter_name="${1:-}" filter_type="${2:-}" filter_peers="${3:-}"
168 local blocked_only="${4:-false}" restricted_only="${5:-false}" allowed_only="${6:-false}"
169 local net_file="${7:-}" w_client="${8:-20}" w_dest="${9:-30}"
170
171 local peer_set=()
172 [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
173
174 # Build ip->name map
175 declare -A ip_to_name=()
176 while IFS= read -r conf; do
177 local cname
178 cname=$(basename "$conf" .conf)
179 local ip
180 ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
181 [[ -n "$ip" && -n "$cname" ]] && ip_to_name["$ip"]="$cname"
182 done < <(find "$(ctx::clients)" -name "*.conf" 2>/dev/null)
183
184 # Load net services if not --raw
185 declare -A _svc_cache=()
186 function _resolve_dest() {
187 local dest_ip="${1:-}" dest_port="${2:-}" proto="${3:-}"
188 [[ -z "$net_file" || ! -f "$net_file" ]] && echo "" && return
189 local key="${dest_ip}:${dest_port}:${proto}"
190 if [[ -z "${_svc_cache[$key]+x}" ]]; then
191 _svc_cache[$key]=$(net::reverse_lookup "$dest_ip" "$dest_port" "$proto" 2>/dev/null || true)
192 fi
193 echo "${_svc_cache[$key]:-}"
194 }
195
196 declare -A _WATCH_LAST_FW=()
197 declare -A _WATCH_LAST_WG=()
198
199 local source_file
200 source_file=$(mktemp)
201 echo "wg" > "$source_file"
202 trap "rm -f '$source_file'" EXIT
203
204 tail -f "$(ctx::events_log)" "$(ctx::fw_events_log)" 2>/dev/null \
205 | while IFS= read -r line; do
206 [[ -z "$line" ]] && continue
207
208 if [[ "$line" == "==> "* ]]; then
209 [[ "$line" == *"fw_events"* ]] && echo "fw" > "$source_file" || echo "wg" > "$source_file"
210 continue
211 fi
212
213 local source
214 source=$(cat "$source_file")
215
216 if [[ "$source" == "fw" ]]; then
217 $allowed_only && continue
218
219 local fw_data
220 fw_data=$(python3 "$(ctx::json_helper)" parse_fw_event "$line" 2>/dev/null) || continue
221 [[ -z "$fw_data" ]] && continue
222
223 local ts src_ip dest_ip dest_port proto
224 IFS='|' read -r ts src_ip dest_ip dest_port proto <<< "$fw_data"
225 [[ -z "$src_ip" ]] && continue
226
227 local client="${ip_to_name[$src_ip]:-$src_ip}"
228 [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
229
230 # Dedup
231 local fw_key="${src_ip}:${dest_ip}:${dest_port}:${proto}"
232 local now; now=$(date +%s)
233 local window=30
234 [[ "$proto" == "17" || "$proto" == "udp" ]] && window=10
235 [[ "$proto" == "1" || "$proto" == "icmp" ]] && window=5
236 local last="${_WATCH_LAST_FW[$fw_key]:-0}"
237 (( now - last < window )) && continue
238 _WATCH_LAST_FW["$fw_key"]="$now"
239
240 local ts_fmt
241 ts_fmt=$(fmt::datetime_short "$(json::iso_to_ts "$ts" 2>/dev/null || echo 0)")
242
243 local dest_display
244 dest_display=$(resolve::dest "$dest_ip" "$dest_port" "$proto")
245
246 ui::watch::fw_row "$ts_fmt" "$client" "$dest_display" "$w_client" "$w_dest"
247
248 else
249 $restricted_only && continue
250
251 local ev_data
252 ev_data=$(python3 "$(ctx::json_helper)" parse_event "$line" 2>/dev/null) || continue
253 [[ -z "$ev_data" ]] && continue
254
255 local ts client endpoint event
256 IFS='|' read -r ts client endpoint event <<< "$ev_data"
257 [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
258 $blocked_only && [[ "$event" != "attempt" ]] && continue
259 $allowed_only && [[ "$event" != "handshake" ]] && continue
260
261 # Dedup
262 local wg_key="${client}:${endpoint}:${event}"
263 local now; now=$(date +%s)
264 local last="${_WATCH_LAST_WG[$wg_key]:-0}"
265 (( now - last < 30 )) && continue
266 _WATCH_LAST_WG["$wg_key"]="$now"
267
268 local ts_fmt
269 ts_fmt=$(fmt::datetime_short "$(json::iso_to_ts "$ts" 2>/dev/null || echo 0)")
270
271 ui::watch::wg_row "$ts_fmt" "$client" "${endpoint:-—}" "$event" \
272 "$w_client" "$w_dest"
273 fi
274 done
275
276 rm -f "$source_file"
277}