| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- #!/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 "$@"
|