1036
This commit is contained in:
198
files/ansible-playbooks/connstats-deploy.yml
Normal file
198
files/ansible-playbooks/connstats-deploy.yml
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
---
|
||||||
|
- name: Deploy connstats (single device, linear)
|
||||||
|
hosts: all
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
vars:
|
||||||
|
ssh_user: "{{ ansible_user | default('root') }}"
|
||||||
|
ssh_pass: "{{ ansible_password | default(ansible_ssh_pass) }}"
|
||||||
|
|
||||||
|
# RabbitMQ (use controls exchange + queue_controls like the reference)
|
||||||
|
rmq_host: "{{ lookup('env','RMQ_HOST') | default('10.210.12.2', true) }}"
|
||||||
|
rmq_port: "{{ lookup('env','RMQ_PORT') | default('15672', true) }}"
|
||||||
|
rmq_user: "{{ lookup('env','RMQ_USER') | default('admin', true) }}"
|
||||||
|
rmq_pass: "{{ lookup('env','RMQ_PASS') | default('change_me', true) }}"
|
||||||
|
rmq_vhost: "{{ lookup('env','RMQ_VHOST') | default('app', true) }}"
|
||||||
|
rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}"
|
||||||
|
control_queue: "{{ lookup('env','CONTROL_QUEUE') | default('queue_controls', true) }}"
|
||||||
|
|
||||||
|
# Cron line we must ensure (preserve any other lines)
|
||||||
|
connstats_cron_line: "*/10 * * * * /root/connstats.sh --debug --always-find-offset >/dev/null 2>&1"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
- block:
|
||||||
|
|
||||||
|
- name: Check SSH connectivity (raw ping)
|
||||||
|
raw: "echo ping"
|
||||||
|
register: ping_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- block:
|
||||||
|
|
||||||
|
# Step 2 fork
|
||||||
|
- name: Compute MD5 of local connstats.sh
|
||||||
|
delegate_to: localhost
|
||||||
|
command: md5sum files/connstats.sh
|
||||||
|
register: md5_local_cstats
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Compute MD5 of remote /root/connstats.sh
|
||||||
|
raw: "md5sum /root/connstats.sh || busybox md5sum /root/connstats.sh"
|
||||||
|
register: md5_remote_cstats
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Decide if connstats.sh needs upload
|
||||||
|
set_fact:
|
||||||
|
upload_cstats: >-
|
||||||
|
{{ (md5_remote_cstats.rc != 0)
|
||||||
|
or ((md5_local_cstats.stdout.split()[0])
|
||||||
|
!= (md5_remote_cstats.stdout.split()[0] if (md5_remote_cstats.stdout is defined) else '')) }}
|
||||||
|
|
||||||
|
- name: Upload connstats.sh via scp (overwrite if changed)
|
||||||
|
when: upload_cstats | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
command: >
|
||||||
|
sshpass -p {{ ssh_pass | quote }}
|
||||||
|
scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||||
|
files/connstats.sh
|
||||||
|
{{ ssh_user }}@{{ ansible_host }}:/root/connstats.sh
|
||||||
|
register: scp_connstats
|
||||||
|
retries: 3
|
||||||
|
delay: 2
|
||||||
|
until: scp_connstats.rc == 0
|
||||||
|
|
||||||
|
- name: Ensure /root/connstats.sh is executable and owned by root
|
||||||
|
raw: |
|
||||||
|
chown root:root /root/connstats.sh && chmod 0755 /root/connstats.sh
|
||||||
|
|
||||||
|
# Step 3 fork (cron entry only, preserve anything else)
|
||||||
|
- name: Ensure /etc/crontabs/root exists (touch with perms)
|
||||||
|
raw: |
|
||||||
|
if [ ! -f /etc/crontabs/root ]; then
|
||||||
|
touch /etc/crontabs/root
|
||||||
|
fi
|
||||||
|
chown root:root /etc/crontabs/root
|
||||||
|
chmod 0644 /etc/crontabs/root
|
||||||
|
|
||||||
|
- name: Check if connstats cron line already present
|
||||||
|
raw: |
|
||||||
|
grep -Fxq '{{ connstats_cron_line }}' /etc/crontabs/root
|
||||||
|
register: cron_grep
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Upload snippet connstats-crond-root to /tmp (only if missing)
|
||||||
|
when: cron_grep.rc != 0
|
||||||
|
delegate_to: localhost
|
||||||
|
command: >
|
||||||
|
sshpass -p {{ ssh_pass | quote }}
|
||||||
|
scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||||
|
files/connstats-crond-root
|
||||||
|
{{ ssh_user }}@{{ ansible_host }}:/tmp/connstats-crond-root.snippet
|
||||||
|
|
||||||
|
- name: Append snippet to /etc/crontabs/root (only if missing)
|
||||||
|
when: cron_grep.rc != 0
|
||||||
|
raw: |
|
||||||
|
cat /tmp/connstats-crond-root.snippet >> /etc/crontabs/root && rm -f /tmp/connstats-crond-root.snippet
|
||||||
|
register: cron_append
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Set result status (success deployed or no change)
|
||||||
|
set_fact:
|
||||||
|
result_status: "{{ 'SUCCESS_DEPLOYED' if (upload_cstats | bool) else 'SUCCESS_NO_CHANGE' }}"
|
||||||
|
|
||||||
|
when: ping_result is succeeded
|
||||||
|
|
||||||
|
- name: Set status fact (no ssh)
|
||||||
|
when: ping_result is failed
|
||||||
|
set_fact:
|
||||||
|
result_status: "NO_SSH"
|
||||||
|
|
||||||
|
rescue:
|
||||||
|
- name: Mark result as failed
|
||||||
|
set_fact:
|
||||||
|
result_status: "FAILED during {{ ansible_failed_task.name }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
|
||||||
|
- name: Compute inscope device
|
||||||
|
set_fact:
|
||||||
|
inscope_device_name: "{{ ansible_hostname | default(inventory_hostname) }}"
|
||||||
|
|
||||||
|
# Custom field update (per your sample)
|
||||||
|
- name: Publish custom-field update connstats deployed to control queue
|
||||||
|
delegate_to: localhost
|
||||||
|
uri:
|
||||||
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
|
||||||
|
method: POST
|
||||||
|
user: "{{ rmq_user }}"
|
||||||
|
password: "{{ rmq_pass }}"
|
||||||
|
force_basic_auth: true
|
||||||
|
status_code: 200
|
||||||
|
headers: { content-type: "application/json" }
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
properties: { content_type: "application/json" }
|
||||||
|
routing_key: "{{ control_queue }}"
|
||||||
|
payload: "{{ {
|
||||||
|
'inscope_device': inscope_device_name,
|
||||||
|
'task_name': 'custom_field_set',
|
||||||
|
'task_add1': 'connstats',
|
||||||
|
'task_result': 'deployed'
|
||||||
|
} | to_json }}"
|
||||||
|
payload_encoding: "string"
|
||||||
|
register: rmq_cf
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# Final wrap-up journal connstats
|
||||||
|
- name: Build actions list
|
||||||
|
set_fact:
|
||||||
|
_actions_list: >-
|
||||||
|
{{
|
||||||
|
[]
|
||||||
|
+ ((upload_cstats | default(false) | bool) | ternary(['uploaded connstats.sh'], []))
|
||||||
|
+ (((cron_grep is defined) and ((cron_grep.rc | default(0)) != 0)) | ternary(['added connstats crontab entry'], []))
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Build actions string
|
||||||
|
set_fact:
|
||||||
|
_actions_str: "{{ ((_actions_list | default([])) | length > 0) | ternary((_actions_list | join(', ')), 'no changes needed') }}"
|
||||||
|
|
||||||
|
- name: Build wrap-up journal payload
|
||||||
|
delegate_to: localhost
|
||||||
|
set_fact:
|
||||||
|
wrap_payload:
|
||||||
|
inscope_device: "{{ inscope_device_name }}"
|
||||||
|
task_name: "journal_add"
|
||||||
|
task_result: >-
|
||||||
|
connstats: {{ 'success' if (result_status == 'SUCCESS_DEPLOYED' or result_status == 'SUCCESS_NO_CHANGE') else result_status | lower }}
|
||||||
|
— actions: {{ _actions_str }}
|
||||||
|
|
||||||
|
- name: Publish wrap-up journal to control queue
|
||||||
|
delegate_to: localhost
|
||||||
|
uri:
|
||||||
|
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
|
||||||
|
method: POST
|
||||||
|
user: "{{ rmq_user }}"
|
||||||
|
password: "{{ rmq_pass }}"
|
||||||
|
force_basic_auth: true
|
||||||
|
status_code: 200
|
||||||
|
headers:
|
||||||
|
content-type: "application/json"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
properties:
|
||||||
|
content_type: "application/json"
|
||||||
|
routing_key: "{{ control_queue }}"
|
||||||
|
payload: "{{ wrap_payload | to_json }}"
|
||||||
|
payload_encoding: "string"
|
||||||
|
register: rmq_wrap
|
||||||
|
changed_when: (rmq_wrap.json is defined) and (rmq_wrap.json.routed | default(false) | bool)
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "result_status: {{ result_status }}"
|
||||||
|
- "we're good"
|
||||||
656
files/files/connstats.sh
Normal file
656
files/files/connstats.sh
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# connstats.sh
|
||||||
|
# Log-derived Wi-Fi connection counters per run (intended for cron every 10 minutes).
|
||||||
|
# Writes one summary line per run to /root/connstats.log
|
||||||
|
# Optional: --debug for chatty troubleshooting output in the same log.
|
||||||
|
# Optional: --always-find-offset to calibrate syslog header time via a logger marker
|
||||||
|
# and (on fresh start / rotation / truncation) build a true last-10-min slice.
|
||||||
|
|
||||||
|
LOG_SRC="/var/log/messages"
|
||||||
|
OUT_LOG="/root/connstats.log"
|
||||||
|
STATE="/tmp/connstats.state"
|
||||||
|
TMPDIR="/tmp"
|
||||||
|
|
||||||
|
# How many tail lines to scan when we have no state (first run / after reboot / rotation)
|
||||||
|
BOOTSTRAP_BACKFILL_LINES=2000
|
||||||
|
|
||||||
|
# Used only with --always-find-offset during bootstrap-like runs
|
||||||
|
BOOTSTRAP_TIME_WINDOW_SEC=600
|
||||||
|
BOOTSTRAP_TIME_TAIL_LINES=10000
|
||||||
|
|
||||||
|
DEBUG=0
|
||||||
|
ALWAYS_FIND_OFFSET=0
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--debug) DEBUG=1 ;;
|
||||||
|
--always-find-offset) ALWAYS_FIND_OFFSET=1 ;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [--debug] [--always-find-offset]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown arg: $arg" >&2
|
||||||
|
echo "Usage: $0 [--debug] [--always-find-offset]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
ts_iso() {
|
||||||
|
# ISO-ish timestamp; works on BusyBox and GNU date
|
||||||
|
date "+%Y-%m-%dT%H:%M:%S%z"
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
# Always append to OUT_LOG
|
||||||
|
echo "[$(ts_iso)] $*" >> "$OUT_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate a short random token (8 chars) for marker grep
|
||||||
|
rand_token() {
|
||||||
|
if [ -r /dev/urandom ]; then
|
||||||
|
tr -dc 'a-z0-9' < /dev/urandom 2>/dev/null | head -c 8
|
||||||
|
else
|
||||||
|
echo "$(date -u +%s 2>/dev/null)$$" | tr -dc 'a-z0-9' | tail -c 8
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse syslog header timestamp (Mon DD HH:MM:SS) into epoch seconds (UTC) using awk mktime()
|
||||||
|
# Expects typical line like: "<13> Jan 31 12:31:00 root[-] [notice]: message"
|
||||||
|
syslog_header_to_epoch_utc() {
|
||||||
|
echo "$1" | TZ=UTC awk '
|
||||||
|
function mon2num(m) {
|
||||||
|
if (m=="Jan") return 1
|
||||||
|
if (m=="Feb") return 2
|
||||||
|
if (m=="Mar") return 3
|
||||||
|
if (m=="Apr") return 4
|
||||||
|
if (m=="May") return 5
|
||||||
|
if (m=="Jun") return 6
|
||||||
|
if (m=="Jul") return 7
|
||||||
|
if (m=="Aug") return 8
|
||||||
|
if (m=="Sep") return 9
|
||||||
|
if (m=="Oct") return 10
|
||||||
|
if (m=="Nov") return 11
|
||||||
|
if (m=="Dec") return 12
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mon=$2; day=$3; tod=$4
|
||||||
|
year=strftime("%Y")
|
||||||
|
m=mon2num(mon)
|
||||||
|
if (m==0) { print ""; exit 1 }
|
||||||
|
split(tod, t, ":")
|
||||||
|
if (length(t) != 3) { print ""; exit 1 }
|
||||||
|
hh=t[1]+0; mm=t[2]+0; ss=t[3]+0
|
||||||
|
d=day+0
|
||||||
|
print mktime(sprintf("%d %02d %02d %02d %02d %02d", year, m, d, hh, mm, ss))
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compute offset_sec = payload_epoch_utc - syslog_header_epoch_utc
|
||||||
|
# Returns offset seconds on stdout (blank on failure)
|
||||||
|
find_time_offset_sec() {
|
||||||
|
token="$(rand_token)"
|
||||||
|
now_epoch="$(date -u +%s 2>/dev/null | tr -d ' ')"
|
||||||
|
[ -z "$now_epoch" ] && now_epoch=0
|
||||||
|
|
||||||
|
logger "connstat calculation started. issueing time marker $token epoch=$now_epoch" 2>/dev/null
|
||||||
|
|
||||||
|
found_line=""
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 3 ]; do
|
||||||
|
found_line="$(tail -n 300 "$LOG_SRC" 2>/dev/null | grep "$token" | tail -n 1)"
|
||||||
|
[ -n "$found_line" ] && break
|
||||||
|
sleep 1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z "$found_line" ] && { echo ""; return; }
|
||||||
|
|
||||||
|
hdr_epoch="$(syslog_header_to_epoch_utc "$found_line" | tr -d ' ')"
|
||||||
|
[ -z "$hdr_epoch" ] && { echo ""; return; }
|
||||||
|
|
||||||
|
payload_epoch="$(echo "$found_line" | awk '{
|
||||||
|
for (i=1; i<=NF; i++) {
|
||||||
|
if ($i ~ /^epoch=[0-9]+$/) { sub(/^epoch=/,"",$i); print $i; exit }
|
||||||
|
}
|
||||||
|
}' | tr -d ' ')"
|
||||||
|
[ -z "$payload_epoch" ] && payload_epoch="$now_epoch"
|
||||||
|
|
||||||
|
echo $((payload_epoch - hdr_epoch))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Basic sanity checks
|
||||||
|
if [ ! -r "$LOG_SRC" ]; then
|
||||||
|
log "ERROR log_src_unreadable path=$LOG_SRC"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get current inode + line count (line-based incremental read is most portable)
|
||||||
|
cur_inode="$(ls -i "$LOG_SRC" 2>/dev/null | awk '{print $1}')"
|
||||||
|
cur_lines="$(wc -l < "$LOG_SRC" 2>/dev/null | tr -d ' ')"
|
||||||
|
|
||||||
|
if [ -z "$cur_inode" ] || [ -z "$cur_lines" ]; then
|
||||||
|
log "ERROR cannot_stat_log inode='$cur_inode' lines='$cur_lines'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
last_inode=""
|
||||||
|
last_line=""
|
||||||
|
|
||||||
|
if [ -f "$STATE" ]; then
|
||||||
|
# STATE format:
|
||||||
|
# inode=<num>
|
||||||
|
# line=<num>
|
||||||
|
last_inode="$(grep '^inode=' "$STATE" 2>/dev/null | head -n1 | cut -d= -f2)"
|
||||||
|
last_line="$(grep '^line=' "$STATE" 2>/dev/null | head -n1 | cut -d= -f2)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Decide where to start reading
|
||||||
|
start_line=""
|
||||||
|
|
||||||
|
backfill_start() {
|
||||||
|
start_line=$((cur_lines - BOOTSTRAP_BACKFILL_LINES + 1))
|
||||||
|
[ "$start_line" -lt 1 ] && start_line=1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the slice
|
||||||
|
tmp_slice="$TMPDIR/connstats.slice.$$"
|
||||||
|
|
||||||
|
# Grep patterns (hostapd transition events)
|
||||||
|
re_auth='hostapd.*IEEE 802\.11: authenticated'
|
||||||
|
re_assoc='hostapd.*IEEE 802\.11: associated'
|
||||||
|
re_disassoc='hostapd.*IEEE 802\.11: disassociated'
|
||||||
|
|
||||||
|
# Kernel extras for "seen_any" breadth (optional but useful)
|
||||||
|
re_kernel_seen='kernel.*(station kicked out|station timed out)'
|
||||||
|
|
||||||
|
# Are we in a bootstrap-like condition (no state OR rotation/truncation)?
|
||||||
|
bootstrap_like=0
|
||||||
|
if [ -z "$last_inode" ] || [ -z "$last_line" ]; then
|
||||||
|
bootstrap_like=1
|
||||||
|
else
|
||||||
|
if [ "$cur_inode" != "$last_inode" ] || [ "$cur_lines" -lt "$last_line" ]; then
|
||||||
|
bootstrap_like=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bootstrap handling
|
||||||
|
if [ "$ALWAYS_FIND_OFFSET" -eq 1 ] && [ "$bootstrap_like" -eq 1 ]; then
|
||||||
|
offset_sec="$(find_time_offset_sec)"
|
||||||
|
|
||||||
|
if [ -n "$offset_sec" ]; then
|
||||||
|
now_epoch="$(date -u +%s 2>/dev/null | tr -d ' ')"
|
||||||
|
[ -z "$now_epoch" ] && now_epoch=0
|
||||||
|
cutoff_epoch=$((now_epoch - BOOTSTRAP_TIME_WINDOW_SEC))
|
||||||
|
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG bootstrap_time_window_last_10m offset_sec=$offset_sec cutoff_epoch=$cutoff_epoch tail_lines=$BOOTSTRAP_TIME_TAIL_LINES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build slice = last-10-min relevant lines (hostapd transitions + kernel station kicked/timed out)
|
||||||
|
# using (syslog_header_epoch + offset_sec) >= cutoff_epoch
|
||||||
|
tail -n "$BOOTSTRAP_TIME_TAIL_LINES" "$LOG_SRC" 2>/dev/null \
|
||||||
|
| TZ=UTC awk -v cutoff="$cutoff_epoch" -v off="$offset_sec" '
|
||||||
|
function mon2num(m) {
|
||||||
|
if (m=="Jan") return 1
|
||||||
|
if (m=="Feb") return 2
|
||||||
|
if (m=="Mar") return 3
|
||||||
|
if (m=="Apr") return 4
|
||||||
|
if (m=="May") return 5
|
||||||
|
if (m=="Jun") return 6
|
||||||
|
if (m=="Jul") return 7
|
||||||
|
if (m=="Aug") return 8
|
||||||
|
if (m=="Sep") return 9
|
||||||
|
if (m=="Oct") return 10
|
||||||
|
if (m=="Nov") return 11
|
||||||
|
if (m=="Dec") return 12
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
function header_epoch( mon,day,tod,year,m,hh,mm,ss,t) {
|
||||||
|
mon=$2; day=$3; tod=$4
|
||||||
|
year=strftime("%Y")
|
||||||
|
m=mon2num(mon)
|
||||||
|
if (m==0) return -1
|
||||||
|
split(tod, t, ":")
|
||||||
|
if (length(t) != 3) return -1
|
||||||
|
hh=t[1]+0; mm=t[2]+0; ss=t[3]+0
|
||||||
|
return mktime(sprintf("%d %02d %02d %02d %02d %02d", year, m, (day+0), hh, mm, ss))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
if (line ~ /hostapd.*IEEE 802\.11: (authenticated|associated|disassociated)/ ||
|
||||||
|
line ~ /kernel.*(station kicked out|station timed out)/) {
|
||||||
|
he=header_epoch()
|
||||||
|
if (he < 0) next
|
||||||
|
corr=he + off
|
||||||
|
if (corr >= cutoff) print line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' > "$tmp_slice" 2>/dev/null
|
||||||
|
|
||||||
|
start_line="(time_window_last_10m)"
|
||||||
|
else
|
||||||
|
# Offset calc failed -> fall back to old bootstrap behavior
|
||||||
|
backfill_start
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG time_offset_failed_fallback_to_backfill cur_inode=$cur_inode cur_lines=$cur_lines start_line=$start_line backfill_lines=$BOOTSTRAP_BACKFILL_LINES"
|
||||||
|
fi
|
||||||
|
sed -n "${start_line},\$p" "$LOG_SRC" > "$tmp_slice" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# Original behavior (unchanged)
|
||||||
|
if [ -z "$last_inode" ] || [ -z "$last_line" ]; then
|
||||||
|
backfill_start
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG bootstrap_no_state_backfill cur_inode=$cur_inode cur_lines=$cur_lines start_line=$start_line backfill_lines=$BOOTSTRAP_BACKFILL_LINES"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Rotation / truncation detection
|
||||||
|
if [ "$cur_inode" != "$last_inode" ] || [ "$cur_lines" -lt "$last_line" ]; then
|
||||||
|
backfill_start
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG bootstrap_rotation_or_truncate_backfill last_inode=$last_inode cur_inode=$cur_inode last_line=$last_line cur_lines=$cur_lines start_line=$start_line backfill_lines=$BOOTSTRAP_BACKFILL_LINES"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
start_line=$((last_line + 1))
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG incremental last_line=$last_line cur_lines=$cur_lines start_line=$start_line"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -n "${start_line},\$p" "$LOG_SRC" > "$tmp_slice" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
slice_lines="$(wc -l < "$tmp_slice" 2>/dev/null | tr -d ' ')"
|
||||||
|
[ -z "$slice_lines" ] && slice_lines=0
|
||||||
|
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG slice_path=$tmp_slice slice_lines=$slice_lines"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Totals (MAC repeats allowed)
|
||||||
|
auth_events_total="$(grep -E "$re_auth" "$tmp_slice" | wc -l | tr -d ' ')"
|
||||||
|
assoc_events_total="$(grep -E "$re_assoc" "$tmp_slice" | wc -l | tr -d ' ')"
|
||||||
|
disassoc_events_total="$(grep -E "$re_disassoc" "$tmp_slice" | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
# Extract MAC right after token "STA" (from hostapd lines)
|
||||||
|
extract_sta_macs() {
|
||||||
|
awk '
|
||||||
|
{
|
||||||
|
for (i=1; i<=NF; i++) {
|
||||||
|
if ($i=="STA") {
|
||||||
|
print $(i+1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract MACs in brackets: [aa:bb:cc:dd:ee:ff]
|
||||||
|
extract_bracket_macs() {
|
||||||
|
grep -oE '\[[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}\]' | tr -d '[]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Count unique valid MACs from stdin
|
||||||
|
uniq_count() {
|
||||||
|
grep -E '^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$' \
|
||||||
|
| sort -u \
|
||||||
|
| wc -l | tr -d ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unique MACs per event type (hostapd)
|
||||||
|
unique_macs_authenticated="$(
|
||||||
|
grep -E "$re_auth" "$tmp_slice" \
|
||||||
|
| extract_sta_macs \
|
||||||
|
| uniq_count
|
||||||
|
)"
|
||||||
|
|
||||||
|
unique_macs_associated="$(
|
||||||
|
grep -E "$re_assoc" "$tmp_slice" \
|
||||||
|
| extract_sta_macs \
|
||||||
|
| uniq_count
|
||||||
|
)"
|
||||||
|
|
||||||
|
unique_macs_disassociated="$(
|
||||||
|
grep -E "$re_disassoc" "$tmp_slice" \
|
||||||
|
| extract_sta_macs \
|
||||||
|
| uniq_count
|
||||||
|
)"
|
||||||
|
|
||||||
|
# Unique MACs seen in "any relevant activity" (union: auth/assoc/disassoc + selected kernel)
|
||||||
|
unique_macs_seen_any="$(
|
||||||
|
(
|
||||||
|
grep -E 'hostapd.*IEEE 802\.11: (authenticated|associated|disassociated)' "$tmp_slice" \
|
||||||
|
| extract_sta_macs
|
||||||
|
|
||||||
|
grep -E "$re_kernel_seen" "$tmp_slice" \
|
||||||
|
| extract_bracket_macs
|
||||||
|
) | uniq_count
|
||||||
|
)"
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# B-class metrics (ephemeral state in /tmp; reboot loss accepted)
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
SESSION_STATE="/tmp/connstats.sessions.state"
|
||||||
|
SESSION_STATE_TMP="$TMPDIR/connstats.sessions.state.$$"
|
||||||
|
SESSION_STATE_TTL_SEC=21600
|
||||||
|
AUTH_ASSOC_MAX_SAMPLES=50
|
||||||
|
|
||||||
|
now_epoch_b="$(date -u +%s 2>/dev/null | tr -d ' ')"
|
||||||
|
[ -z "$now_epoch_b" ] && now_epoch_b=0
|
||||||
|
|
||||||
|
# Output file (temporary, removed each run)
|
||||||
|
B_OUT_TMP="$TMPDIR/connstats.b.out.$$"
|
||||||
|
|
||||||
|
TZ=UTC awk -v state_in="$SESSION_STATE" \
|
||||||
|
-v state_out="$SESSION_STATE_TMP" \
|
||||||
|
-v now="$now_epoch_b" \
|
||||||
|
-v ttl="$SESSION_STATE_TTL_SEC" \
|
||||||
|
-v maxs="$AUTH_ASSOC_MAX_SAMPLES" '
|
||||||
|
function mon2num(m) {
|
||||||
|
if (m=="Jan") return 1
|
||||||
|
if (m=="Feb") return 2
|
||||||
|
if (m=="Mar") return 3
|
||||||
|
if (m=="Apr") return 4
|
||||||
|
if (m=="May") return 5
|
||||||
|
if (m=="Jun") return 6
|
||||||
|
if (m=="Jul") return 7
|
||||||
|
if (m=="Aug") return 8
|
||||||
|
if (m=="Sep") return 9
|
||||||
|
if (m=="Oct") return 10
|
||||||
|
if (m=="Nov") return 11
|
||||||
|
if (m=="Dec") return 12
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
function header_epoch( mon,day,tod,year,m,hh,mm,ss,t) {
|
||||||
|
mon=$2; day=$3; tod=$4
|
||||||
|
year=strftime("%Y")
|
||||||
|
m=mon2num(mon)
|
||||||
|
if (m==0) return -1
|
||||||
|
split(tod, t, ":")
|
||||||
|
if (length(t) != 3) return -1
|
||||||
|
hh=t[1]+0; mm=t[2]+0; ss=t[3]+0
|
||||||
|
return mktime(sprintf("%d %02d %02d %02d %02d %02d", year, m, (day+0), hh, mm, ss))
|
||||||
|
}
|
||||||
|
function extract_sta_mac( i) {
|
||||||
|
for (i=1; i<=NF; i++) {
|
||||||
|
if ($i=="STA") return $(i+1)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
function mac_valid(m) {
|
||||||
|
return (m ~ /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/)
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
if (state_in != "") {
|
||||||
|
while ((getline line < state_in) > 0) {
|
||||||
|
n = split(line, a, " ")
|
||||||
|
if (n >= 2) {
|
||||||
|
m=a[1]; e=a[2]+0
|
||||||
|
if (mac_valid(m) && e > 0) last_auth[m]=e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(state_in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
if (line !~ /hostapd.*IEEE 802\.11: (authenticated|associated|disassociated)/) next
|
||||||
|
|
||||||
|
e = header_epoch()
|
||||||
|
if (e < 0) next
|
||||||
|
|
||||||
|
mac = extract_sta_mac()
|
||||||
|
if (!mac_valid(mac)) next
|
||||||
|
|
||||||
|
if (line ~ /IEEE 802\.11: authenticated/) {
|
||||||
|
last_auth[mac]=e
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line ~ /IEEE 802\.11: associated/) {
|
||||||
|
if (mac in last_auth) {
|
||||||
|
d = e - last_auth[mac]
|
||||||
|
if (d >= 0) {
|
||||||
|
ms = d * 1000
|
||||||
|
if (sample_count < maxs) {
|
||||||
|
if (samples == "") samples = ms
|
||||||
|
else samples = samples "," ms
|
||||||
|
sample_count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line ~ /IEEE 802\.11: disassociated/) {
|
||||||
|
if (mac in last_auth) {
|
||||||
|
d = e - last_auth[mac]
|
||||||
|
if (d >= 0) {
|
||||||
|
full_sessions_total++
|
||||||
|
sum_len += d
|
||||||
|
if (d < 60) early_drop_sessions_total++
|
||||||
|
delete last_auth[mac]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (full_sessions_total > 0) avg_session_length = int((sum_len / full_sessions_total) + 0.5)
|
||||||
|
else avg_session_length = 0
|
||||||
|
|
||||||
|
if (state_out != "") {
|
||||||
|
for (m in last_auth) {
|
||||||
|
e = last_auth[m] + 0
|
||||||
|
if (e > 0 && (now <= 0 || (e >= (now - ttl)))) {
|
||||||
|
print m " " e > state_out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(state_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
print full_sessions_total "\t" avg_session_length "\t" early_drop_sessions_total "\t" samples
|
||||||
|
}
|
||||||
|
' "$tmp_slice" > "$B_OUT_TMP" 2>/dev/null
|
||||||
|
|
||||||
|
# Default B fields
|
||||||
|
full_sessions_total=0
|
||||||
|
avg_session_length=0
|
||||||
|
early_drop_sessions_total=0
|
||||||
|
auth_to_assoc_time_ms_samples=""
|
||||||
|
|
||||||
|
# Parse awk output
|
||||||
|
if [ -s "$B_OUT_TMP" ]; then
|
||||||
|
b_out_line="$(head -n 1 "$B_OUT_TMP" 2>/dev/null)"
|
||||||
|
full_sessions_total="$(echo "$b_out_line" | awk -F'\t' '{print $1}' | tr -d ' ')"
|
||||||
|
avg_session_length="$(echo "$b_out_line" | awk -F'\t' '{print $2}' | tr -d ' ')"
|
||||||
|
early_drop_sessions_total="$(echo "$b_out_line" | awk -F'\t' '{print $3}' | tr -d ' ')"
|
||||||
|
auth_to_assoc_time_ms_samples="$(echo "$b_out_line" | awk -F'\t' '{print $4}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$B_OUT_TMP" 2>/dev/null
|
||||||
|
|
||||||
|
[ -z "$full_sessions_total" ] && full_sessions_total=0
|
||||||
|
[ -z "$avg_session_length" ] && avg_session_length=0
|
||||||
|
[ -z "$early_drop_sessions_total" ] && early_drop_sessions_total=0
|
||||||
|
|
||||||
|
# Move state into place (ephemeral but persistent across runs until reboot)
|
||||||
|
if [ -f "$SESSION_STATE_TMP" ]; then
|
||||||
|
mv -f "$SESSION_STATE_TMP" "$SESSION_STATE" 2>/dev/null
|
||||||
|
else
|
||||||
|
rm -f "$SESSION_STATE_TMP" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# C-class metrics (kernel kick reason codes 1..8)
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Only count kernel "station kicked out ..." (avoids PRS DRIVER_LOG "reason" lines)
|
||||||
|
re_kick_reason_base='kernel.*station kicked out.*reason[[:space:]]+'
|
||||||
|
|
||||||
|
reason_code_events_1="$(grep -E "${re_kick_reason_base}1([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_2="$(grep -E "${re_kick_reason_base}2([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_3="$(grep -E "${re_kick_reason_base}3([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_4="$(grep -E "${re_kick_reason_base}4([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_5="$(grep -E "${re_kick_reason_base}5([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_6="$(grep -E "${re_kick_reason_base}6([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_7="$(grep -E "${re_kick_reason_base}7([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
reason_code_events_8="$(grep -E "${re_kick_reason_base}8([^0-9]|$)" "$tmp_slice" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
[ -z "$reason_code_events_1" ] && reason_code_events_1=0
|
||||||
|
[ -z "$reason_code_events_2" ] && reason_code_events_2=0
|
||||||
|
[ -z "$reason_code_events_3" ] && reason_code_events_3=0
|
||||||
|
[ -z "$reason_code_events_4" ] && reason_code_events_4=0
|
||||||
|
[ -z "$reason_code_events_5" ] && reason_code_events_5=0
|
||||||
|
[ -z "$reason_code_events_6" ] && reason_code_events_6=0
|
||||||
|
[ -z "$reason_code_events_7" ] && reason_code_events_7=0
|
||||||
|
[ -z "$reason_code_events_8" ] && reason_code_events_8=0
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# D-class metrics (flaps between ath0 and ath1 within the bucket)
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
D_OUT_TMP="$TMPDIR/connstats.d.out.$$"
|
||||||
|
|
||||||
|
awk '
|
||||||
|
function extract_sta_mac( i) {
|
||||||
|
for (i=1; i<=NF; i++) if ($i=="STA") return $(i+1)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
function mac_valid(m) {
|
||||||
|
return (m ~ /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/)
|
||||||
|
}
|
||||||
|
function extract_iface( i, v) {
|
||||||
|
for (i=1; i<=NF; i++) {
|
||||||
|
v = $i
|
||||||
|
if (v ~ /^ath[0-9]+:$/) { sub(/:$/,"",v); return v }
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
if (line !~ /hostapd.*IEEE 802\.11: associated/) next
|
||||||
|
|
||||||
|
iface = extract_iface()
|
||||||
|
if (iface != "ath0" && iface != "ath1") next
|
||||||
|
|
||||||
|
mac = extract_sta_mac()
|
||||||
|
if (!mac_valid(mac)) next
|
||||||
|
|
||||||
|
if (mac in last_iface) {
|
||||||
|
prev = last_iface[mac]
|
||||||
|
if (prev != iface) {
|
||||||
|
if (prev=="ath0" && iface=="ath1") {
|
||||||
|
flap_events_0_to_1++
|
||||||
|
uniq_0_to_1[mac]=1
|
||||||
|
} else if (prev=="ath1" && iface=="ath0") {
|
||||||
|
flap_events_1_to_0++
|
||||||
|
uniq_1_to_0[mac]=1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_iface[mac]=iface
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
for (m in uniq_0_to_1) unique_0_to_1++
|
||||||
|
for (m in uniq_1_to_0) unique_1_to_0++
|
||||||
|
|
||||||
|
if (unique_0_to_1+0 < 0) unique_0_to_1=0
|
||||||
|
if (flap_events_0_to_1+0 < 0) flap_events_0_to_1=0
|
||||||
|
if (unique_1_to_0+0 < 0) unique_1_to_0=0
|
||||||
|
if (flap_events_1_to_0+0 < 0) flap_events_1_to_0=0
|
||||||
|
|
||||||
|
print unique_0_to_1 "\t" flap_events_0_to_1 "\t" unique_1_to_0 "\t" flap_events_1_to_0
|
||||||
|
}
|
||||||
|
' "$tmp_slice" > "$D_OUT_TMP" 2>/dev/null
|
||||||
|
|
||||||
|
unique_macs_flap_ath0_to_ath1=0
|
||||||
|
flap_events_total_ath0_to_ath1=0
|
||||||
|
unique_macs_flap_ath1_to_ath0=0
|
||||||
|
flap_events_total_ath1_to_ath0=0
|
||||||
|
|
||||||
|
if [ -s "$D_OUT_TMP" ]; then
|
||||||
|
d_out_line="$(head -n 1 "$D_OUT_TMP" 2>/dev/null)"
|
||||||
|
unique_macs_flap_ath0_to_ath1="$(echo "$d_out_line" | awk -F'\t' '{print $1}' | tr -d ' ')"
|
||||||
|
flap_events_total_ath0_to_ath1="$(echo "$d_out_line" | awk -F'\t' '{print $2}' | tr -d ' ')"
|
||||||
|
unique_macs_flap_ath1_to_ath0="$(echo "$d_out_line" | awk -F'\t' '{print $3}' | tr -d ' ')"
|
||||||
|
flap_events_total_ath1_to_ath0="$(echo "$d_out_line" | awk -F'\t' '{print $4}' | tr -d ' ')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$D_OUT_TMP" 2>/dev/null
|
||||||
|
|
||||||
|
[ -z "$unique_macs_flap_ath0_to_ath1" ] && unique_macs_flap_ath0_to_ath1=0
|
||||||
|
[ -z "$flap_events_total_ath0_to_ath1" ] && flap_events_total_ath0_to_ath1=0
|
||||||
|
[ -z "$unique_macs_flap_ath1_to_ath0" ] && unique_macs_flap_ath1_to_ath0=0
|
||||||
|
[ -z "$flap_events_total_ath1_to_ath0" ] && flap_events_total_ath1_to_ath0=0
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Emit one summary line
|
||||||
|
########################################################################
|
||||||
|
log "connstats bucket_run slice_lines=$slice_lines unique_macs_seen_any=$unique_macs_seen_any unique_macs_authenticated=$unique_macs_authenticated auth_events_total=$auth_events_total unique_macs_associated=$unique_macs_associated assoc_events_total=$assoc_events_total unique_macs_disassociated=$unique_macs_disassociated disassoc_events_total=$disassoc_events_total full_sessions_total=$full_sessions_total avg_session_length=$avg_session_length early_drop_sessions_total=$early_drop_sessions_total auth_to_assoc_time_ms_samples=$auth_to_assoc_time_ms_samples reason_code_events_1=$reason_code_events_1 reason_code_events_2=$reason_code_events_2 reason_code_events_3=$reason_code_events_3 reason_code_events_4=$reason_code_events_4 reason_code_events_5=$reason_code_events_5 reason_code_events_6=$reason_code_events_6 reason_code_events_7=$reason_code_events_7 reason_code_events_8=$reason_code_events_8 unique_macs_flap_ath0_to_ath1=$unique_macs_flap_ath0_to_ath1 flap_events_total_ath0_to_ath1=$flap_events_total_ath0_to_ath1 unique_macs_flap_ath1_to_ath0=$unique_macs_flap_ath1_to_ath0 flap_events_total_ath1_to_ath0=$flap_events_total_ath1_to_ath0"
|
||||||
|
|
||||||
|
if [ $DEBUG -eq 1 ]; then
|
||||||
|
log "DEBUG dump_auth_lines_begin"
|
||||||
|
grep -E "$re_auth" "$tmp_slice" | sed 's/^/DEBUG AUTH: /' >> "$OUT_LOG"
|
||||||
|
log "DEBUG dump_assoc_lines_begin"
|
||||||
|
grep -E "$re_assoc" "$tmp_slice" | sed 's/^/DEBUG ASSOC: /' >> "$OUT_LOG"
|
||||||
|
log "DEBUG dump_disassoc_lines_begin"
|
||||||
|
grep -E "$re_disassoc" "$tmp_slice" | sed 's/^/DEBUG DISASSOC: /' >> "$OUT_LOG"
|
||||||
|
|
||||||
|
log "DEBUG dump_kick_reason_lines_begin"
|
||||||
|
grep -E "${re_kick_reason_base}[1-8]([^0-9]|$)" "$tmp_slice" | sed 's/^/DEBUG KICK: /' >> "$OUT_LOG"
|
||||||
|
|
||||||
|
log "DEBUG dump_flap_assoc_lines_begin"
|
||||||
|
grep -E 'hostapd.*IEEE 802\.11: associated' "$tmp_slice" | sed 's/^/DEBUG FLAP_ASSOC: /' >> "$OUT_LOG"
|
||||||
|
|
||||||
|
log "DEBUG dump_flap_events_begin"
|
||||||
|
awk '
|
||||||
|
function extract_sta_mac( i) { for (i=1; i<=NF; i++) if ($i=="STA") return $(i+1); return "" }
|
||||||
|
function mac_valid(m) { return (m ~ /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/) }
|
||||||
|
function extract_iface( i, v) {
|
||||||
|
for (i=1; i<=NF; i++) { v=$i; if (v ~ /^ath[0-9]+:$/) { sub(/:$/,"",v); return v } }
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if ($0 !~ /hostapd.*IEEE 802\.11: associated/) next
|
||||||
|
iface=extract_iface()
|
||||||
|
if (iface!="ath0" && iface!="ath1") next
|
||||||
|
mac=extract_sta_mac()
|
||||||
|
if (!mac_valid(mac)) next
|
||||||
|
if (mac in last_iface) {
|
||||||
|
prev=last_iface[mac]
|
||||||
|
if (prev!=iface) {
|
||||||
|
if ((prev=="ath0" && iface=="ath1") || (prev=="ath1" && iface=="ath0")) {
|
||||||
|
print "mac=" mac " from=" prev " to=" iface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_iface[mac]=iface
|
||||||
|
}
|
||||||
|
' "$tmp_slice" | sed 's/^/DEBUG FLAP: /' >> "$OUT_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update state to current end-of-file line count and inode
|
||||||
|
{
|
||||||
|
echo "inode=$cur_inode"
|
||||||
|
echo "line=$cur_lines"
|
||||||
|
} > "$STATE"
|
||||||
|
|
||||||
|
rm -f "$tmp_slice" 2>/dev/null
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user