event-forwarder 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/bin/sh
  2. # event-forwarder - Forward OpenWrt events to central server
  3. # Copyright (c) 2026 Luis Rosales - MIT License
  4. # Size: ~20KB
  5. set -euo pipefail
  6. # Config
  7. CONFIG_FILE="/etc/config/event-forwarder"
  8. LOG_TAG="event-forwarder"
  9. # Load config
  10. load_config() {
  11. # Server URL
  12. SERVER_URL=$(uci get event-forwarder.server.url 2>/dev/null || echo "")
  13. SERVER_TOKEN=$(uci get event-forwarder.server.token 2>/dev/null || echo "")
  14. TIMEOUT=$(uci get event-forwarder.server.timeout 2>/dev/null || echo "10")
  15. RETRY=$(uci get event-forwarder.server.retry 2>/dev/null || echo "3")
  16. # Router ID (use hostname if not set)
  17. ROUTER_ID=$(uci get event-forwarder.router.id 2>/dev/null || hostname)
  18. # Event flags
  19. EVT_WIFI_CONN=$(uci get event-forwarder.events.wifi_connect 2>/dev/null || echo "1")
  20. EVT_WIFI_DISC=$(uci get event-forwarder.events.wifi_disconnect 2>/dev/null || echo "1")
  21. EVT_DHCP=$(uci get event-forwarder.events.dhcp_lease 2>/dev/null || echo "1")
  22. # Validate
  23. if [ -z "$SERVER_URL" ]; then
  24. log_err "Server URL not configured"
  25. exit 1
  26. fi
  27. }
  28. # Logger
  29. log_debug() { [ "${DEBUG:-0}" = "1" ] && logger -t "$LOG_TAG" -p user.debug "$@"; }
  30. log_info() { logger -t "$LOG_TAG" -p user.info "$@"; }
  31. log_err() { logger -t "$LOG_TAG" -p user.err "$@"; }
  32. # Send event to server
  33. send_event() {
  34. local event_json="$1"
  35. local attempt=0
  36. while [ $attempt -lt $RETRY ]; do
  37. attempt=$((attempt + 1))
  38. response=$(curl -s -w "%{http_code}" \
  39. --max-time "$TIMEOUT" \
  40. -X POST "$SERVER_URL" \
  41. -H "Authorization: Bearer $SERVER_TOKEN" \
  42. -H "Content-Type: application/json" \
  43. -d "$event_json" 2>/dev/null) || true
  44. http_code="${response: -3}"
  45. if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
  46. log_debug "Event sent successfully"
  47. return 0
  48. fi
  49. log_debug "Attempt $attempt failed (HTTP $http_code)"
  50. sleep 1
  51. done
  52. log_err "Failed to send event after $RETRY attempts"
  53. return 1
  54. }
  55. # Build JSON payload
  56. build_json() {
  57. local event_type="$1"
  58. local mac="$2"
  59. local ip="$3"
  60. local hostname="$4"
  61. local extra="$5"
  62. # Escape special chars
  63. mac="${mac:-null}"
  64. ip="${ip:-null}"
  65. hostname="${hostname:-null}"
  66. cat <<EOF
  67. {
  68. "router_id": "$ROUTER_ID",
  69. "hostname": "$(hostname)",
  70. "event_type": "$event_type",
  71. "timestamp": "$(date -Iseconds)",
  72. "payload": {
  73. "mac": "$mac",
  74. "ip": "$ip",
  75. "hostname": "$hostname"$extra
  76. }
  77. }
  78. EOF
  79. }
  80. # ===== EVENT LISTENERS =====
  81. # 1. WiFi events via hostapd
  82. listen_wifi() {
  83. local wlan_iface="$1"
  84. # Monitor hostapd control interface
  85. local ctrl_path="/var/run/hostapd-$wlan_iface"
  86. # We can't use hostapd_cli in monitor mode easily,
  87. # so we'll poll the station list as fallback
  88. while true; do
  89. # Get associated stations
  90. if [ -f "/proc/sys/net/netfilter/nf_conntrack" ]; then
  91. # Check for new WiFi clients via conntrack
  92. # This is a simplified version
  93. :
  94. fi
  95. sleep 10
  96. done
  97. }
  98. # 2. DHCP events via dnsmasq
  99. listen_dhcp() {
  100. local lease_file="/var/lib/dnsmasq/dnsmasq.leases"
  101. local old_leases=""
  102. while true; do
  103. if [ -f "$lease_file" ]; then
  104. current_leases=$(cat "$lease_file")
  105. # Compare and detect changes
  106. if [ "$current_leases" != "$old_leases" ]; then
  107. # Parse new/changed leases
  108. echo "$current_leases" | while read line; do
  109. [ -z "$line" ] && continue
  110. set -- $line
  111. local timestamp="$1"
  112. local mac="$2"
  113. local ip="$3"
  114. local hostname="$4"
  115. # New lease (timestamp changed = new)
  116. if [[ ! "$old_leases" == *"$mac"* ]]; then
  117. json=$(build_json "dhcp_lease" "$mac" "$ip" "$hostname" ', "action": "new"')
  118. log_info "DHCP lease: $mac -> $ip ($hostname)"
  119. send_event "$json"
  120. fi
  121. done
  122. old_leases="$current_leases"
  123. fi
  124. fi
  125. sleep 5
  126. done
  127. }
  128. # 3. Interface events via ubus netifd
  129. listen_interfaces() {
  130. ubus -m listen network.interface 2>/dev/null | while read event; do
  131. event_type=$(echo "$event" | jsonfilter -e '@.handler')
  132. [ -z "$event_type" ] && continue
  133. device=$(echo "$event" | jsonfilter -e '@.interface')
  134. action=$(echo "$event" | jsonfilter -e '@.action')
  135. json=$(build_json "interface_${action}" "null" "null" "null" ", \"device\": \"$device\", \"action\": \"$action\"")
  136. log_info "Interface $action: $device"
  137. send_event "$json" &
  138. done
  139. }
  140. # 4. Generic ubus events
  141. listen_ubus() {
  142. ubus -m listen 2>/dev/null | while read event; do
  143. # Filter network-related events
  144. event_json=$(echo "$event" | jsonfilter -e '@')
  145. # Check for wireless events
  146. if echo "$event" | grep -q "wpa"; then
  147. log_debug "WiFi event: $event"
  148. fi
  149. done
  150. }
  151. # Main loop - runs all listeners concurrently
  152. main() {
  153. load_config
  154. log_info "Starting event forwarder..."
  155. log_info "Server: $SERVER_URL"
  156. # Trap signals for graceful shutdown
  157. trap 'log_info "Shutting down..."; kill 0 2>/dev/null; exit 0' TERM INT
  158. # Start listeners in background
  159. listen_dhcp &
  160. local dhcp_pid=$!
  161. listen_interfaces &
  162. local if_pid=$!
  163. log_info "Event listeners started (DHCP:$dhcp_pid, IFACE:$if_pid)"
  164. # Wait for any signal
  165. wait
  166. }
  167. # Run main
  168. main "$@"