gistfile1.txt
· 10 KiB · Text
Ham
#!/usr/bin/env bash
# ============================================
# Lifecycle
# ============================================
function cmd::watch::on_load() {
flag::register --type
flag::register --name
flag::register --peers
flag::register --blocked
flag::register --restricted
flag::register --allowed
}
# ============================================
# Help
# ============================================
function cmd::watch::help() {
cat <<EOF
Usage: wgctl watch [options]
Live monitor of WireGuard activity. Shows:
- Handshakes from connected peers (green)
- Connection attempts from blocked peers (red, SOURCE: wg)
- Firewall drops from rule-restricted peers (red, SOURCE: fw)
Options:
--name <name> Filter by client name
--type <type> Filter by device type
--peers <list> Comma-separated peer names (used internally by group watch)
--blocked Show only blocked peer attempts
--allowed Show only handshakes (allowed peers)
--restricted Show only firewall drop events
Examples:
wgctl watch
wgctl watch --blocked
wgctl watch --allowed
wgctl watch --type phone
wgctl watch --name phone-nuno
wgctl watch --name phone-nuno --type phone
EOF
}
# ============================================
# Helpers
# ============================================
function cmd::watch::format_event() {
local ts="${1:-}" source="${2:-}" client="${3:-}"
local dest="${4:-}" event="${5:-}" status="${6:-}"
local event_color
case "$event" in
attempt|drop) event_color="\033[1;31m" ;;
handshake) event_color="\033[1;32m" ;;
*) event_color="\033[0;37m" ;;
esac
local status_color=""
case "$status" in
blocked) status_color="\033[1;31m" ;;
allowed) status_color="\033[1;32m" ;;
esac
printf " %-20s %-8s %-22s %-28s ${event_color}%-14s\033[0m ${status_color}%s\033[0m\n" \
"$ts" "$source" "$client" "${dest:-—}" "$event" "$status"
}
function cmd::watch::header() {
log::section "wgctl — Live Monitor (Ctrl+C to stop)"
printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \
"TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS"
printf " %s\n\n" "$(printf '─%.0s' {1..105})"
}
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
# ============================================
function cmd::watch::poll_handshakes() {
local filter_name="${1:-}" filter_type="${2:-}"
local allowed_only="${3:-false}"
local filter_peers="${4:-}"
local peer_set=()
[[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
while IFS= read -r line; do
local public_key ts
public_key=$(echo "$line" | awk '{print $1}')
ts=$(echo "$line" | awk '{print $2}')
[[ -z "$ts" || "$ts" == "0" ]] && continue
# Find client by public key
local client_name=""
for conf in "$(ctx::clients)"/*.conf; do
[[ -f "$conf" ]] || continue
local name
name=$(basename "$conf" .conf)
local key
key=$(keys::public "$name" 2>/dev/null || echo "")
if [[ "$key" == "$public_key" ]]; then
client_name="$name"
break
fi
done
[[ -z "$client_name" ]] && continue
# Apply filters
[[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
cmd::watch::_peer_in_filter "$client_name" "${peer_set[@]}" || continue
if [[ -n "$filter_type" ]]; then
local ip
ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" \
| awk '{print $3}' | cut -d'/' -f1)
local subnet
subnet=$(config::subnet_for "$filter_type")
string::starts_with "$ip" "$subnet" || continue
fi
# Only emit if handshake is new
local safe_key
safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1)
local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
local prev_ts="0"
[[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
if [[ "$ts" != "$prev_ts" ]]; then
echo "$ts" > "$prev_ts_file"
local formatted_ts
formatted_ts=$(fmt::datetime "$ts")
local endpoint
endpoint=$(monitor::endpoint_for_key "$public_key")
cmd::watch::format_event \
"$formatted_ts" "wg" "$client_name" "${endpoint:-—}" "handshake" "allowed"
fi
done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
}
# ============================================
# Event Tailer
# ============================================
function cmd::watch::tail_events() {
local filter_name="${1:-}" filter_type="${2:-}"
local blocked_only="${3:-false}" restricted_only="${4:-false}"
local allowed_only="${5:-false}" filter_peers="${6:-}"
declare -A _WATCH_LAST_ATTEMPT=()
declare -A _WATCH_LAST_FW=()
local peer_set=()
[[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"
declare -A _WATCH_LAST_ATTEMPT=()
# Build ip->name map for fw events
declare -A ip_to_name=()
local conf_file
while IFS= read -r conf_file; do
local name
name=$(basename "$conf_file" .conf)
local ip
ip=$(grep "^Address" "$conf_file" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
[[ -n "$ip" && -n "$name" ]] && ip_to_name["$ip"]="$name"
done < <(find "$(ctx::clients)" -name "*.conf" 2>/dev/null)
# Source tracker via temp file (persists across subshell iterations)
local source_file
source_file=$(mktemp)
echo "wg" > "$source_file"
# Cleanup temp file on exit
trap "rm -f '$source_file'" EXIT
tail -f "$(ctx::events_log)" "$(ctx::fw_events_log)" 2>/dev/null \
| while IFS= read -r line; do
[[ -z "$line" ]] && continue
# Handle tail -f file headers
if [[ "$line" == "==> "* ]]; then
if [[ "$line" == *"fw_events"* ]]; then
echo "fw" > "$source_file"
else
echo "wg" > "$source_file"
fi
continue
fi
local source
source=$(cat "$source_file")
if [[ "$source" == "wg" ]]; then
$allowed_only && continue # wg events are attempts/blocked
local event_data
event_data=$(json::parse_event "$line")
[[ -z "$event_data" ]] && continue
local ts client endpoint event
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 [[ -n "$filter_type" ]]; then
local conf
conf="$(ctx::clients)/${client}.conf"
[[ -f "$conf" ]] || continue
local ip
ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
local subnet
subnet=$(config::subnet_for "$filter_type")
string::starts_with "$ip" "$subnet" || continue
fi
$restricted_only && { cmd::list::is_restricted "$client" || continue; }
# Dedup
local now
now=$(date +%s)
local safe_client="${client//[-.]/_}"
local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
(( now - last < 30 )) && continue
_WATCH_LAST_ATTEMPT[$safe_client]="$now"
local formatted_ts
formatted_ts=$(fmt::datetime_iso "$ts")
cmd::watch::format_event \
"$formatted_ts" "wg" "$client" "${endpoint:-—}" "$event" "blocked"
else
# FW event
$allowed_only && continue
$blocked_only && continue # fw drops aren't "blocked peers" per se
local fw_data
fw_data=$(json::parse_fw_event "$line")
[[ -z "$fw_data" ]] && continue
local ts src_ip dst_ip dst_port proto
IFS="|" read -r ts src_ip dst_ip dst_port proto <<< "$fw_data"
[[ -z "$src_ip" ]] && continue
local fw_key="${src_ip}:${dst_ip}:${dst_port}:${proto}"
local now
now=$(date +%s)
local last_fw="${_WATCH_LAST_FW[$fw_key]:-0}"
local window=30
[[ "$proto" == "17" || "$proto" == "udp" ]] && window=10
[[ "$proto" == "1" || "$proto" == "icmp" ]] && window=5
local diff=$(( now - last_fw ))
(( diff < window )) && continue
_WATCH_LAST_FW["$fw_key"]="$now"
local client="${ip_to_name[$src_ip]:-$src_ip}"
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue
if [[ -n "$filter_type" ]]; then
local peer_client="${ip_to_name[$src_ip]:-}"
[[ -z "$peer_client" ]] && continue
local conf
conf="$(ctx::clients)/${peer_client}.conf"
[[ -f "$conf" ]] || continue
local ip
ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
local subnet
subnet=$(config::subnet_for "$filter_type")
string::starts_with "$ip" "$subnet" || continue
fi
local dst_str="${dst_ip:-—}"
[[ -n "$dst_port" ]] && dst_str="${dst_ip}:${dst_port}/${proto}"
local formatted_ts
formatted_ts=$(fmt::datetime_iso "$ts")
cmd::watch::format_event \
"$formatted_ts" "fw" "$client" "$dst_str" "drop" "blocked"
fi
done
rm -f "$source_file"
}
# ============================================
# Run
# ============================================
function cmd::watch::run() {
local filter_name="" filter_type="" filter_peers=""
local blocked_only=false allowed_only=false restricted_only=false
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
while [[ $# -gt 0 ]]; do
case "$1" in
--name) filter_name="$2"; shift 2 ;;
--type) filter_type="$2"; shift 2 ;;
--peers) filter_peers="$2"; shift 2 ;;
--blocked) blocked_only=true; shift ;;
--allowed) allowed_only=true; shift ;;
--restricted) restricted_only=true; shift ;;
--help) cmd::watch::help; return ;;
*)
log::error "Unknown flag: $1"
cmd::watch::help
return 1
;;
esac
done
cmd::watch::header
if ! $blocked_only && ! $restricted_only; then
(
while true; do
cmd::watch::poll_handshakes \
"$filter_name" "$filter_type" "$allowed_only" "$filter_peers"
sleep 5
done
) &
local poller_pid=$!
fi
cmd::watch::tail_events \
"$filter_name" "$filter_type" \
"$blocked_only" "$restricted_only" \
"$allowed_only" "$filter_peers" &
local tailer_pid=$!
trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM
wait
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ============================================ |
| 4 | # Lifecycle |
| 5 | # ============================================ |
| 6 | |
| 7 | function 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 | } |
| 15 | |
| 16 | # ============================================ |
| 17 | # Help |
| 18 | # ============================================ |
| 19 | |
| 20 | function cmd::watch::help() { |
| 21 | cat <<EOF |
| 22 | Usage: wgctl watch [options] |
| 23 | |
| 24 | Live monitor of WireGuard activity. Shows: |
| 25 | - Handshakes from connected peers (green) |
| 26 | - Connection attempts from blocked peers (red, SOURCE: wg) |
| 27 | - Firewall drops from rule-restricted peers (red, SOURCE: fw) |
| 28 | |
| 29 | Options: |
| 30 | --name <name> Filter by client name |
| 31 | --type <type> Filter by device type |
| 32 | --peers <list> Comma-separated peer names (used internally by group watch) |
| 33 | --blocked Show only blocked peer attempts |
| 34 | --allowed Show only handshakes (allowed peers) |
| 35 | --restricted Show only firewall drop events |
| 36 | |
| 37 | Examples: |
| 38 | wgctl watch |
| 39 | wgctl watch --blocked |
| 40 | wgctl watch --allowed |
| 41 | wgctl watch --type phone |
| 42 | wgctl watch --name phone-nuno |
| 43 | wgctl watch --name phone-nuno --type phone |
| 44 | EOF |
| 45 | } |
| 46 | |
| 47 | # ============================================ |
| 48 | # Helpers |
| 49 | # ============================================ |
| 50 | |
| 51 | function cmd::watch::format_event() { |
| 52 | local ts="${1:-}" source="${2:-}" client="${3:-}" |
| 53 | local dest="${4:-}" event="${5:-}" status="${6:-}" |
| 54 | |
| 55 | local event_color |
| 56 | case "$event" in |
| 57 | attempt|drop) event_color="\033[1;31m" ;; |
| 58 | handshake) event_color="\033[1;32m" ;; |
| 59 | *) event_color="\033[0;37m" ;; |
| 60 | esac |
| 61 | |
| 62 | local status_color="" |
| 63 | case "$status" in |
| 64 | blocked) status_color="\033[1;31m" ;; |
| 65 | allowed) status_color="\033[1;32m" ;; |
| 66 | esac |
| 67 | |
| 68 | printf " %-20s %-8s %-22s %-28s ${event_color}%-14s\033[0m ${status_color}%s\033[0m\n" \ |
| 69 | "$ts" "$source" "$client" "${dest:-—}" "$event" "$status" |
| 70 | } |
| 71 | |
| 72 | function cmd::watch::header() { |
| 73 | log::section "wgctl — Live Monitor (Ctrl+C to stop)" |
| 74 | printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \ |
| 75 | "TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS" |
| 76 | printf " %s\n\n" "$(printf '─%.0s' {1..105})" |
| 77 | } |
| 78 | |
| 79 | function cmd::watch::_peer_in_filter() { |
| 80 | local peer="$1" |
| 81 | shift |
| 82 | local peer_set=("$@") |
| 83 | [[ ${#peer_set[@]} -eq 0 ]] && return 0 # no filter = all pass |
| 84 | for p in "${peer_set[@]}"; do |
| 85 | [[ "$p" == "$peer" ]] && return 0 |
| 86 | done |
| 87 | return 1 |
| 88 | } |
| 89 | |
| 90 | # ============================================ |
| 91 | # Handshake Poller |
| 92 | # ============================================ |
| 93 | |
| 94 | function cmd::watch::poll_handshakes() { |
| 95 | local filter_name="${1:-}" filter_type="${2:-}" |
| 96 | local allowed_only="${3:-false}" |
| 97 | local filter_peers="${4:-}" |
| 98 | |
| 99 | local peer_set=() |
| 100 | [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers" |
| 101 | |
| 102 | while IFS= read -r line; do |
| 103 | local public_key ts |
| 104 | public_key=$(echo "$line" | awk '{print $1}') |
| 105 | ts=$(echo "$line" | awk '{print $2}') |
| 106 | |
| 107 | [[ -z "$ts" || "$ts" == "0" ]] && continue |
| 108 | |
| 109 | # Find client by public key |
| 110 | local client_name="" |
| 111 | for conf in "$(ctx::clients)"/*.conf; do |
| 112 | [[ -f "$conf" ]] || continue |
| 113 | local name |
| 114 | name=$(basename "$conf" .conf) |
| 115 | local key |
| 116 | key=$(keys::public "$name" 2>/dev/null || echo "") |
| 117 | if [[ "$key" == "$public_key" ]]; then |
| 118 | client_name="$name" |
| 119 | break |
| 120 | fi |
| 121 | done |
| 122 | |
| 123 | [[ -z "$client_name" ]] && continue |
| 124 | |
| 125 | # Apply filters |
| 126 | [[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue |
| 127 | cmd::watch::_peer_in_filter "$client_name" "${peer_set[@]}" || continue |
| 128 | |
| 129 | if [[ -n "$filter_type" ]]; then |
| 130 | local ip |
| 131 | ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" \ |
| 132 | | awk '{print $3}' | cut -d'/' -f1) |
| 133 | local subnet |
| 134 | subnet=$(config::subnet_for "$filter_type") |
| 135 | string::starts_with "$ip" "$subnet" || continue |
| 136 | fi |
| 137 | |
| 138 | # Only emit if handshake is new |
| 139 | local safe_key |
| 140 | safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1) |
| 141 | local prev_ts_file="/tmp/wgctl_hs_${safe_key}" |
| 142 | local prev_ts="0" |
| 143 | [[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file") |
| 144 | |
| 145 | if [[ "$ts" != "$prev_ts" ]]; then |
| 146 | echo "$ts" > "$prev_ts_file" |
| 147 | |
| 148 | local formatted_ts |
| 149 | formatted_ts=$(fmt::datetime "$ts") |
| 150 | |
| 151 | local endpoint |
| 152 | endpoint=$(monitor::endpoint_for_key "$public_key") |
| 153 | |
| 154 | cmd::watch::format_event \ |
| 155 | "$formatted_ts" "wg" "$client_name" "${endpoint:-—}" "handshake" "allowed" |
| 156 | fi |
| 157 | |
| 158 | done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null) |
| 159 | } |
| 160 | |
| 161 | # ============================================ |
| 162 | # Event Tailer |
| 163 | # ============================================ |
| 164 | |
| 165 | function cmd::watch::tail_events() { |
| 166 | local filter_name="${1:-}" filter_type="${2:-}" |
| 167 | local blocked_only="${3:-false}" restricted_only="${4:-false}" |
| 168 | local allowed_only="${5:-false}" filter_peers="${6:-}" |
| 169 | |
| 170 | declare -A _WATCH_LAST_ATTEMPT=() |
| 171 | declare -A _WATCH_LAST_FW=() |
| 172 | |
| 173 | local peer_set=() |
| 174 | [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers" |
| 175 | |
| 176 | declare -A _WATCH_LAST_ATTEMPT=() |
| 177 | |
| 178 | # Build ip->name map for fw events |
| 179 | declare -A ip_to_name=() |
| 180 | local conf_file |
| 181 | while IFS= read -r conf_file; do |
| 182 | local name |
| 183 | name=$(basename "$conf_file" .conf) |
| 184 | local ip |
| 185 | ip=$(grep "^Address" "$conf_file" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1) |
| 186 | [[ -n "$ip" && -n "$name" ]] && ip_to_name["$ip"]="$name" |
| 187 | done < <(find "$(ctx::clients)" -name "*.conf" 2>/dev/null) |
| 188 | |
| 189 | # Source tracker via temp file (persists across subshell iterations) |
| 190 | local source_file |
| 191 | source_file=$(mktemp) |
| 192 | echo "wg" > "$source_file" |
| 193 | |
| 194 | # Cleanup temp file on exit |
| 195 | trap "rm -f '$source_file'" EXIT |
| 196 | |
| 197 | tail -f "$(ctx::events_log)" "$(ctx::fw_events_log)" 2>/dev/null \ |
| 198 | | while IFS= read -r line; do |
| 199 | [[ -z "$line" ]] && continue |
| 200 | |
| 201 | # Handle tail -f file headers |
| 202 | if [[ "$line" == "==> "* ]]; then |
| 203 | if [[ "$line" == *"fw_events"* ]]; then |
| 204 | echo "fw" > "$source_file" |
| 205 | else |
| 206 | echo "wg" > "$source_file" |
| 207 | fi |
| 208 | continue |
| 209 | fi |
| 210 | |
| 211 | local source |
| 212 | source=$(cat "$source_file") |
| 213 | |
| 214 | if [[ "$source" == "wg" ]]; then |
| 215 | $allowed_only && continue # wg events are attempts/blocked |
| 216 | |
| 217 | local event_data |
| 218 | event_data=$(json::parse_event "$line") |
| 219 | [[ -z "$event_data" ]] && continue |
| 220 | |
| 221 | local ts client endpoint event |
| 222 | IFS="|" read -r ts client endpoint event <<< "$event_data" |
| 223 | |
| 224 | [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue |
| 225 | cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue |
| 226 | |
| 227 | if [[ -n "$filter_type" ]]; then |
| 228 | local conf |
| 229 | conf="$(ctx::clients)/${client}.conf" |
| 230 | [[ -f "$conf" ]] || continue |
| 231 | local ip |
| 232 | ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1) |
| 233 | local subnet |
| 234 | subnet=$(config::subnet_for "$filter_type") |
| 235 | string::starts_with "$ip" "$subnet" || continue |
| 236 | fi |
| 237 | |
| 238 | $restricted_only && { cmd::list::is_restricted "$client" || continue; } |
| 239 | |
| 240 | # Dedup |
| 241 | local now |
| 242 | now=$(date +%s) |
| 243 | local safe_client="${client//[-.]/_}" |
| 244 | local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}" |
| 245 | (( now - last < 30 )) && continue |
| 246 | _WATCH_LAST_ATTEMPT[$safe_client]="$now" |
| 247 | |
| 248 | local formatted_ts |
| 249 | formatted_ts=$(fmt::datetime_iso "$ts") |
| 250 | |
| 251 | cmd::watch::format_event \ |
| 252 | "$formatted_ts" "wg" "$client" "${endpoint:-—}" "$event" "blocked" |
| 253 | |
| 254 | else |
| 255 | # FW event |
| 256 | $allowed_only && continue |
| 257 | $blocked_only && continue # fw drops aren't "blocked peers" per se |
| 258 | |
| 259 | local fw_data |
| 260 | fw_data=$(json::parse_fw_event "$line") |
| 261 | [[ -z "$fw_data" ]] && continue |
| 262 | |
| 263 | local ts src_ip dst_ip dst_port proto |
| 264 | IFS="|" read -r ts src_ip dst_ip dst_port proto <<< "$fw_data" |
| 265 | |
| 266 | [[ -z "$src_ip" ]] && continue |
| 267 | |
| 268 | local fw_key="${src_ip}:${dst_ip}:${dst_port}:${proto}" |
| 269 | local now |
| 270 | now=$(date +%s) |
| 271 | local last_fw="${_WATCH_LAST_FW[$fw_key]:-0}" |
| 272 | |
| 273 | local window=30 |
| 274 | [[ "$proto" == "17" || "$proto" == "udp" ]] && window=10 |
| 275 | [[ "$proto" == "1" || "$proto" == "icmp" ]] && window=5 |
| 276 | |
| 277 | local diff=$(( now - last_fw )) |
| 278 | (( diff < window )) && continue |
| 279 | _WATCH_LAST_FW["$fw_key"]="$now" |
| 280 | |
| 281 | local client="${ip_to_name[$src_ip]:-$src_ip}" |
| 282 | |
| 283 | [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue |
| 284 | cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue |
| 285 | |
| 286 | if [[ -n "$filter_type" ]]; then |
| 287 | local peer_client="${ip_to_name[$src_ip]:-}" |
| 288 | [[ -z "$peer_client" ]] && continue |
| 289 | local conf |
| 290 | conf="$(ctx::clients)/${peer_client}.conf" |
| 291 | [[ -f "$conf" ]] || continue |
| 292 | local ip |
| 293 | ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1) |
| 294 | local subnet |
| 295 | subnet=$(config::subnet_for "$filter_type") |
| 296 | string::starts_with "$ip" "$subnet" || continue |
| 297 | fi |
| 298 | |
| 299 | local dst_str="${dst_ip:-—}" |
| 300 | [[ -n "$dst_port" ]] && dst_str="${dst_ip}:${dst_port}/${proto}" |
| 301 | |
| 302 | local formatted_ts |
| 303 | formatted_ts=$(fmt::datetime_iso "$ts") |
| 304 | |
| 305 | cmd::watch::format_event \ |
| 306 | "$formatted_ts" "fw" "$client" "$dst_str" "drop" "blocked" |
| 307 | fi |
| 308 | done |
| 309 | |
| 310 | rm -f "$source_file" |
| 311 | } |
| 312 | |
| 313 | # ============================================ |
| 314 | # Run |
| 315 | # ============================================ |
| 316 | |
| 317 | function cmd::watch::run() { |
| 318 | local filter_name="" filter_type="" filter_peers="" |
| 319 | local blocked_only=false allowed_only=false restricted_only=false |
| 320 | |
| 321 | rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true |
| 322 | |
| 323 | while [[ $# -gt 0 ]]; do |
| 324 | case "$1" in |
| 325 | --name) filter_name="$2"; shift 2 ;; |
| 326 | --type) filter_type="$2"; shift 2 ;; |
| 327 | --peers) filter_peers="$2"; shift 2 ;; |
| 328 | --blocked) blocked_only=true; shift ;; |
| 329 | --allowed) allowed_only=true; shift ;; |
| 330 | --restricted) restricted_only=true; shift ;; |
| 331 | --help) cmd::watch::help; return ;; |
| 332 | *) |
| 333 | log::error "Unknown flag: $1" |
| 334 | cmd::watch::help |
| 335 | return 1 |
| 336 | ;; |
| 337 | esac |
| 338 | done |
| 339 | |
| 340 | cmd::watch::header |
| 341 | |
| 342 | if ! $blocked_only && ! $restricted_only; then |
| 343 | ( |
| 344 | while true; do |
| 345 | cmd::watch::poll_handshakes \ |
| 346 | "$filter_name" "$filter_type" "$allowed_only" "$filter_peers" |
| 347 | sleep 5 |
| 348 | done |
| 349 | ) & |
| 350 | local poller_pid=$! |
| 351 | fi |
| 352 | |
| 353 | cmd::watch::tail_events \ |
| 354 | "$filter_name" "$filter_type" \ |
| 355 | "$blocked_only" "$restricted_only" \ |
| 356 | "$allowed_only" "$filter_peers" & |
| 357 | local tailer_pid=$! |
| 358 | |
| 359 | trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \ |
| 360 | rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM |
| 361 | |
| 362 | wait |
| 363 | } |