|
@@ -0,0 +1,212 @@
|
|
|
|
|
+#!/bin/sh
|
|
|
|
|
+# event-forwarder - Forward OpenWrt events to central server
|
|
|
|
|
+# Copyright (c) 2026 Luis Rosales - MIT License
|
|
|
|
|
+# Size: ~20KB
|
|
|
|
|
+
|
|
|
|
|
+set -euo pipefail
|
|
|
|
|
+
|
|
|
|
|
+# Config
|
|
|
|
|
+CONFIG_FILE="/etc/config/event-forwarder"
|
|
|
|
|
+LOG_TAG="event-forwarder"
|
|
|
|
|
+
|
|
|
|
|
+# Load config
|
|
|
|
|
+load_config() {
|
|
|
|
|
+ # Server URL
|
|
|
|
|
+ SERVER_URL=$(uci get event-forwarder.server.url 2>/dev/null || echo "")
|
|
|
|
|
+ SERVER_TOKEN=$(uci get event-forwarder.server.token 2>/dev/null || echo "")
|
|
|
|
|
+ TIMEOUT=$(uci get event-forwarder.server.timeout 2>/dev/null || echo "10")
|
|
|
|
|
+ RETRY=$(uci get event-forwarder.server.retry 2>/dev/null || echo "3")
|
|
|
|
|
+
|
|
|
|
|
+ # Router ID (use hostname if not set)
|
|
|
|
|
+ ROUTER_ID=$(uci get event-forwarder.router.id 2>/dev/null || hostname)
|
|
|
|
|
+
|
|
|
|
|
+ # Event flags
|
|
|
|
|
+ EVT_WIFI_CONN=$(uci get event-forwarder.events.wifi_connect 2>/dev/null || echo "1")
|
|
|
|
|
+ EVT_WIFI_DISC=$(uci get event-forwarder.events.wifi_disconnect 2>/dev/null || echo "1")
|
|
|
|
|
+ EVT_DHCP=$(uci get event-forwarder.events.dhcp_lease 2>/dev/null || echo "1")
|
|
|
|
|
+
|
|
|
|
|
+ # Validate
|
|
|
|
|
+ if [ -z "$SERVER_URL" ]; then
|
|
|
|
|
+ log_err "Server URL not configured"
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Logger
|
|
|
|
|
+log_debug() { [ "${DEBUG:-0}" = "1" ] && logger -t "$LOG_TAG" -p user.debug "$@"; }
|
|
|
|
|
+log_info() { logger -t "$LOG_TAG" -p user.info "$@"; }
|
|
|
|
|
+log_err() { logger -t "$LOG_TAG" -p user.err "$@"; }
|
|
|
|
|
+
|
|
|
|
|
+# Send event to server
|
|
|
|
|
+send_event() {
|
|
|
|
|
+ local event_json="$1"
|
|
|
|
|
+ local attempt=0
|
|
|
|
|
+
|
|
|
|
|
+ while [ $attempt -lt $RETRY ]; do
|
|
|
|
|
+ attempt=$((attempt + 1))
|
|
|
|
|
+
|
|
|
|
|
+ response=$(curl -s -w "%{http_code}" \
|
|
|
|
|
+ --max-time "$TIMEOUT" \
|
|
|
|
|
+ -X POST "$SERVER_URL" \
|
|
|
|
|
+ -H "Authorization: Bearer $SERVER_TOKEN" \
|
|
|
|
|
+ -H "Content-Type: application/json" \
|
|
|
|
|
+ -d "$event_json" 2>/dev/null) || true
|
|
|
|
|
+
|
|
|
|
|
+ http_code="${response: -3}"
|
|
|
|
|
+
|
|
|
|
|
+ if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
|
|
|
|
|
+ log_debug "Event sent successfully"
|
|
|
|
|
+ return 0
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ log_debug "Attempt $attempt failed (HTTP $http_code)"
|
|
|
|
|
+ sleep 1
|
|
|
|
|
+ done
|
|
|
|
|
+
|
|
|
|
|
+ log_err "Failed to send event after $RETRY attempts"
|
|
|
|
|
+ return 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Build JSON payload
|
|
|
|
|
+build_json() {
|
|
|
|
|
+ local event_type="$1"
|
|
|
|
|
+ local mac="$2"
|
|
|
|
|
+ local ip="$3"
|
|
|
|
|
+ local hostname="$4"
|
|
|
|
|
+ local extra="$5"
|
|
|
|
|
+
|
|
|
|
|
+ # Escape special chars
|
|
|
|
|
+ mac="${mac:-null}"
|
|
|
|
|
+ ip="${ip:-null}"
|
|
|
|
|
+ hostname="${hostname:-null}"
|
|
|
|
|
+
|
|
|
|
|
+ cat <<EOF
|
|
|
|
|
+{
|
|
|
|
|
+ "router_id": "$ROUTER_ID",
|
|
|
|
|
+ "hostname": "$(hostname)",
|
|
|
|
|
+ "event_type": "$event_type",
|
|
|
|
|
+ "timestamp": "$(date -Iseconds)",
|
|
|
|
|
+ "payload": {
|
|
|
|
|
+ "mac": "$mac",
|
|
|
|
|
+ "ip": "$ip",
|
|
|
|
|
+ "hostname": "$hostname"$extra
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+EOF
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# ===== EVENT LISTENERS =====
|
|
|
|
|
+
|
|
|
|
|
+# 1. WiFi events via hostapd
|
|
|
|
|
+listen_wifi() {
|
|
|
|
|
+ local wlan_iface="$1"
|
|
|
|
|
+
|
|
|
|
|
+ # Monitor hostapd control interface
|
|
|
|
|
+ local ctrl_path="/var/run/hostapd-$wlan_iface"
|
|
|
|
|
+
|
|
|
|
|
+ # We can't use hostapd_cli in monitor mode easily,
|
|
|
|
|
+ # so we'll poll the station list as fallback
|
|
|
|
|
+ while true; do
|
|
|
|
|
+ # Get associated stations
|
|
|
|
|
+ if [ -f "/proc/sys/net/netfilter/nf_conntrack" ]; then
|
|
|
|
|
+ # Check for new WiFi clients via conntrack
|
|
|
|
|
+ # This is a simplified version
|
|
|
|
|
+ :
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ sleep 10
|
|
|
|
|
+ done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# 2. DHCP events via dnsmasq
|
|
|
|
|
+listen_dhcp() {
|
|
|
|
|
+ local lease_file="/var/lib/dnsmasq/dnsmasq.leases"
|
|
|
|
|
+ local old_leases=""
|
|
|
|
|
+
|
|
|
|
|
+ while true; do
|
|
|
|
|
+ if [ -f "$lease_file" ]; then
|
|
|
|
|
+ current_leases=$(cat "$lease_file")
|
|
|
|
|
+
|
|
|
|
|
+ # Compare and detect changes
|
|
|
|
|
+ if [ "$current_leases" != "$old_leases" ]; then
|
|
|
|
|
+ # Parse new/changed leases
|
|
|
|
|
+ echo "$current_leases" | while read line; do
|
|
|
|
|
+ [ -z "$line" ] && continue
|
|
|
|
|
+
|
|
|
|
|
+ set -- $line
|
|
|
|
|
+ local timestamp="$1"
|
|
|
|
|
+ local mac="$2"
|
|
|
|
|
+ local ip="$3"
|
|
|
|
|
+ local hostname="$4"
|
|
|
|
|
+
|
|
|
|
|
+ # New lease (timestamp changed = new)
|
|
|
|
|
+ if [[ ! "$old_leases" == *"$mac"* ]]; then
|
|
|
|
|
+ json=$(build_json "dhcp_lease" "$mac" "$ip" "$hostname" ', "action": "new"')
|
|
|
|
|
+ log_info "DHCP lease: $mac -> $ip ($hostname)"
|
|
|
|
|
+ send_event "$json"
|
|
|
|
|
+ fi
|
|
|
|
|
+ done
|
|
|
|
|
+
|
|
|
|
|
+ old_leases="$current_leases"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ sleep 5
|
|
|
|
|
+ done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# 3. Interface events via ubus netifd
|
|
|
|
|
+listen_interfaces() {
|
|
|
|
|
+ ubus -m listen network.interface 2>/dev/null | while read event; do
|
|
|
|
|
+ event_type=$(echo "$event" | jsonfilter -e '@.handler')
|
|
|
|
|
+
|
|
|
|
|
+ [ -z "$event_type" ] && continue
|
|
|
|
|
+
|
|
|
|
|
+ device=$(echo "$event" | jsonfilter -e '@.interface')
|
|
|
|
|
+ action=$(echo "$event" | jsonfilter -e '@.action')
|
|
|
|
|
+
|
|
|
|
|
+ json=$(build_json "interface_${action}" "null" "null" "null" ", \"device\": \"$device\", \"action\": \"$action\"")
|
|
|
|
|
+ log_info "Interface $action: $device"
|
|
|
|
|
+
|
|
|
|
|
+ send_event "$json" &
|
|
|
|
|
+ done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# 4. Generic ubus events
|
|
|
|
|
+listen_ubus() {
|
|
|
|
|
+ ubus -m listen 2>/dev/null | while read event; do
|
|
|
|
|
+ # Filter network-related events
|
|
|
|
|
+ event_json=$(echo "$event" | jsonfilter -e '@')
|
|
|
|
|
+
|
|
|
|
|
+ # Check for wireless events
|
|
|
|
|
+ if echo "$event" | grep -q "wpa"; then
|
|
|
|
|
+ log_debug "WiFi event: $event"
|
|
|
|
|
+ fi
|
|
|
|
|
+ done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Main loop - runs all listeners concurrently
|
|
|
|
|
+main() {
|
|
|
|
|
+ load_config
|
|
|
|
|
+
|
|
|
|
|
+ log_info "Starting event forwarder..."
|
|
|
|
|
+ log_info "Server: $SERVER_URL"
|
|
|
|
|
+
|
|
|
|
|
+ # Trap signals for graceful shutdown
|
|
|
|
|
+ trap 'log_info "Shutting down..."; kill 0 2>/dev/null; exit 0' TERM INT
|
|
|
|
|
+
|
|
|
|
|
+ # Start listeners in background
|
|
|
|
|
+ listen_dhcp &
|
|
|
|
|
+ local dhcp_pid=$!
|
|
|
|
|
+
|
|
|
|
|
+ listen_interfaces &
|
|
|
|
|
+ local if_pid=$!
|
|
|
|
|
+
|
|
|
|
|
+ log_info "Event listeners started (DHCP:$dhcp_pid, IFACE:$if_pid)"
|
|
|
|
|
+
|
|
|
|
|
+ # Wait for any signal
|
|
|
|
|
+ wait
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Run main
|
|
|
|
|
+main "$@"
|