network_manager.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // Network Manager - manages all network interfaces with priority-based logic
  2. package main
  3. import (
  4. "fmt"
  5. "log"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "sync"
  10. "syscall"
  11. "time"
  12. )
  13. const (
  14. eth0Interface = "eth0"
  15. wlan0Interface = "wlan0"
  16. apFallbackDelay = 120 * time.Second // 120 sec without connection
  17. )
  18. // NetworkState represents current network state
  19. type NetworkState int
  20. const (
  21. StateNoNetwork NetworkState = iota
  22. StateEth0Online
  23. StateWLAN0ClientOnline
  24. StateWLAN0APFallback
  25. )
  26. // NetworkManager manages all network interfaces with priority-based logic
  27. // Priority 1: eth0 (carrier detect)
  28. // Priority 2: wlan0 client (if configured)
  29. // Fallback: wlan0 AP (120 sec without connection AND without eth0 carrier)
  30. type NetworkManager struct {
  31. cfg *Config
  32. debug bool
  33. stopChan chan struct{}
  34. devicePassword string // AP fallback password from device state
  35. mu sync.Mutex
  36. // Scanners reference (needed to stop WiFi scanner when using wlan0)
  37. scanners *ScannerManager
  38. // State tracking
  39. eth0Carrier bool
  40. eth0HasIP bool
  41. eth0DhcpRunning bool
  42. wlan0HasIP bool
  43. wasOnline bool
  44. currentState NetworkState
  45. lastOnlineTime time.Time
  46. apRunning bool
  47. eth0DhcpPid int
  48. }
  49. // NewNetworkManager creates a new network manager
  50. func NewNetworkManager(cfg *Config, scanners *ScannerManager, devicePassword string) *NetworkManager {
  51. return &NetworkManager{
  52. cfg: cfg,
  53. debug: cfg.Debug,
  54. stopChan: make(chan struct{}),
  55. devicePassword: devicePassword,
  56. scanners: scanners,
  57. currentState: StateNoNetwork,
  58. lastOnlineTime: time.Now(), // Start timer immediately
  59. }
  60. }
  61. // UpdateConfig updates network manager configuration
  62. func (nm *NetworkManager) UpdateConfig(cfg *Config) {
  63. nm.cfg = cfg
  64. }
  65. // HasIP returns true if any interface has an IP address
  66. func (nm *NetworkManager) HasIP() bool {
  67. return nm.eth0HasIP || nm.wlan0HasIP
  68. }
  69. // HasCarrier returns true if eth0 has carrier
  70. func (nm *NetworkManager) HasCarrier() bool {
  71. return nm.eth0Carrier
  72. }
  73. // IsOnline returns true if device is online (eth0 or wlan0 client)
  74. func (nm *NetworkManager) IsOnline() bool {
  75. return nm.currentState == StateEth0Online || nm.currentState == StateWLAN0ClientOnline
  76. }
  77. // Start begins network management
  78. func (nm *NetworkManager) Start() {
  79. log.Println("[netmgr] Starting network manager...")
  80. log.Println("[netmgr] Priority: eth0 → wlan0 client → wlan0 AP fallback (120s)")
  81. // Start main monitoring loop
  82. go nm.monitorLoop()
  83. }
  84. // Stop stops the network manager
  85. func (nm *NetworkManager) Stop() {
  86. log.Println("[netmgr] Stopping network manager...")
  87. close(nm.stopChan)
  88. // Stop NTP daemon
  89. nm.stopNTP()
  90. // Clean up WiFi interfaces only (leave eth0 running for SSH access)
  91. nm.stopWLAN0Client()
  92. nm.stopAP()
  93. // Note: eth0 is NOT stopped to maintain SSH connectivity
  94. // Only stop eth0 DHCP process, but keep the IP address
  95. if nm.eth0DhcpPid > 0 {
  96. syscall.Kill(nm.eth0DhcpPid, syscall.SIGTERM)
  97. }
  98. exec.Command("killall", "-q", "udhcpc").Run()
  99. }
  100. // monitorLoop is the main network management loop
  101. // Polls every second and applies priority-based logic
  102. func (nm *NetworkManager) monitorLoop() {
  103. ticker := time.NewTicker(1 * time.Second)
  104. defer ticker.Stop()
  105. for {
  106. select {
  107. case <-nm.stopChan:
  108. return
  109. case <-ticker.C:
  110. nm.tick()
  111. }
  112. }
  113. }
  114. // tick performs one iteration of network state check and management
  115. func (nm *NetworkManager) tick() {
  116. // Update state
  117. nm.eth0Carrier = nm.checkCarrier(eth0Interface)
  118. nm.eth0HasIP = nm.hasIP(eth0Interface)
  119. nm.wlan0HasIP = nm.hasIP(wlan0Interface)
  120. online := nm.eth0HasIP || nm.wlan0HasIP
  121. if online {
  122. nm.lastOnlineTime = time.Now()
  123. // Start NTP when we transition from offline to online
  124. if !nm.wasOnline {
  125. log.Println("[netmgr] Network connection established - starting NTP")
  126. go nm.startNTP() // Run in goroutine to avoid blocking tick
  127. }
  128. nm.wasOnline = true
  129. } else {
  130. nm.wasOnline = false
  131. }
  132. // Priority 1: eth0 (independent, always configure if carrier UP)
  133. if nm.eth0Carrier {
  134. if !nm.eth0HasIP && !nm.eth0DhcpRunning {
  135. // Carrier UP but no IP and DHCP not running - configure network
  136. if nm.cfg.Network.Eth0.Static {
  137. log.Println("[netmgr] eth0 carrier UP - configuring static IP")
  138. nm.configureEth0Static()
  139. } else {
  140. log.Println("[netmgr] eth0 carrier UP - starting DHCP")
  141. nm.startEth0DHCP()
  142. }
  143. }
  144. } else {
  145. // No eth0 carrier - stop eth0 DHCP/IP
  146. if nm.eth0HasIP || nm.eth0DhcpRunning {
  147. log.Println("[netmgr] eth0 carrier DOWN - stopping network")
  148. nm.stopEth0()
  149. }
  150. }
  151. // Priority 2: wlan0 client (INDEPENDENT of eth0, always connect if configured)
  152. if nm.cfg.WiFi.ClientEnabled && nm.cfg.WiFi.SSID != "" {
  153. if !nm.wlan0HasIP {
  154. // Should connect
  155. log.Printf("[netmgr] Connecting wlan0 client to %s...", nm.cfg.WiFi.SSID)
  156. nm.stopAP() // Stop AP before connecting client
  157. if nm.scanners.IsWiFiRunning() {
  158. log.Println("[netmgr] Stopping WiFi scanner before connecting client")
  159. nm.scanners.StopWiFi()
  160. }
  161. if err := nm.connectWLAN0Client(); err != nil {
  162. log.Printf("[netmgr] wlan0 client connection failed: %v", err)
  163. // Will fallback to AP after 120s
  164. }
  165. }
  166. if nm.wlan0HasIP {
  167. if nm.currentState != StateWLAN0ClientOnline {
  168. log.Println("[netmgr] wlan0 client online")
  169. nm.currentState = StateWLAN0ClientOnline
  170. }
  171. nm.stopAP() // Ensure AP is stopped
  172. // Stop WiFi scanner (can't run with client)
  173. if nm.scanners.IsWiFiRunning() {
  174. log.Println("[netmgr] Stopping WiFi scanner (wlan0 client active)")
  175. nm.scanners.StopWiFi()
  176. }
  177. }
  178. return // wlan0 client configured - don't start scanner or AP
  179. } else {
  180. // wlan0 client not configured - stop it if running
  181. if nm.wlan0HasIP {
  182. log.Println("[netmgr] wlan0 client disabled - disconnecting")
  183. nm.stopWLAN0Client()
  184. }
  185. }
  186. // Determine current state
  187. if nm.eth0HasIP {
  188. nm.currentState = StateEth0Online
  189. } else if nm.wlan0HasIP {
  190. nm.currentState = StateWLAN0ClientOnline
  191. } else {
  192. nm.currentState = StateNoNetwork
  193. }
  194. // Fallback: wlan0 AP (if offline for 120 seconds) - CHECK THIS BEFORE WiFi scanner!
  195. timeSinceOnline := time.Since(nm.lastOnlineTime)
  196. if !online && timeSinceOnline >= apFallbackDelay {
  197. if !nm.apRunning {
  198. log.Printf("[netmgr] No network for %v - starting AP fallback", timeSinceOnline)
  199. // Stop scanner before starting AP
  200. if nm.scanners.IsWiFiRunning() {
  201. nm.scanners.StopWiFi()
  202. }
  203. nm.startAP()
  204. }
  205. // Keep trying wlan0 client even with AP running (if configured)
  206. if nm.cfg.WiFi.ClientEnabled && nm.cfg.WiFi.SSID != "" {
  207. // Try to reconnect every 30 seconds
  208. if int(timeSinceOnline.Seconds())%30 == 0 {
  209. log.Println("[netmgr] AP running - retrying wlan0 client...")
  210. if err := nm.connectWLAN0Client(); err != nil {
  211. if nm.debug {
  212. log.Printf("[netmgr] wlan0 client retry failed: %v", err)
  213. }
  214. } else if nm.hasIP(wlan0Interface) {
  215. log.Println("[netmgr] wlan0 client connected - stopping AP")
  216. nm.stopAP()
  217. nm.currentState = StateWLAN0ClientOnline
  218. }
  219. }
  220. }
  221. } else {
  222. // Online - stop AP if running
  223. if nm.apRunning {
  224. log.Println("[netmgr] Network online - stopping AP fallback")
  225. nm.stopAP()
  226. }
  227. }
  228. // WiFi Scanner: ONLY if wlan0 is free (no client, no AP) AND monitor enabled
  229. // IMPORTANT: Check this AFTER AP fallback logic to avoid race condition
  230. if nm.cfg.WiFi.MonitorEnabled && !nm.apRunning {
  231. if !nm.scanners.IsWiFiRunning() {
  232. log.Println("[netmgr] Starting WiFi scanner (wlan0 free)")
  233. // AIC8800 combo chip: Pause BLE scanner ONLY for mode switch
  234. bleWasRunning := nm.scanners.IsBLERunning()
  235. if bleWasRunning {
  236. log.Println("[netmgr] Pausing BLE for WiFi mode switch (AIC8800 combo chip)")
  237. nm.scanners.StopBLE()
  238. time.Sleep(500 * time.Millisecond)
  239. }
  240. // Switch wlan0 to monitor mode
  241. nm.scanners.StartWiFi(nm.cfg.ZMQAddrWiFi, nm.cfg.WiFiIface)
  242. time.Sleep(500 * time.Millisecond)
  243. // Restart BLE after mode switch (if it was running and still enabled)
  244. if bleWasRunning && nm.cfg.BLE.Enabled {
  245. log.Println("[netmgr] Restarting BLE scanner (WiFi mode switch complete)")
  246. nm.scanners.StartBLE(nm.cfg.ZMQAddrBLE)
  247. }
  248. }
  249. } else {
  250. if nm.scanners.IsWiFiRunning() {
  251. log.Println("[netmgr] Stopping WiFi scanner (wlan0 busy or monitor disabled)")
  252. nm.scanners.StopWiFi()
  253. // Restart BLE scanner if it was running and still enabled
  254. if nm.cfg.BLE.Enabled && !nm.scanners.IsBLERunning() {
  255. log.Println("[netmgr] Restarting BLE scanner (WiFi scanner stopped)")
  256. nm.scanners.StartBLE(nm.cfg.ZMQAddrBLE)
  257. }
  258. }
  259. }
  260. }
  261. // =======================================================================================
  262. // eth0 management
  263. // =======================================================================================
  264. func (nm *NetworkManager) startEth0DHCP() {
  265. // Stop any existing DHCP first
  266. nm.stopEth0DHCP()
  267. // Bring interface up (don't flush IP - udhcpc will handle it)
  268. exec.Command("ip", "link", "set", eth0Interface, "up").Run()
  269. // Start udhcpc
  270. pidFile := fmt.Sprintf("/var/run/udhcpc.%s.pid", eth0Interface)
  271. cmd := exec.Command("udhcpc", "-i", eth0Interface, "-b", "-p", pidFile)
  272. if err := cmd.Start(); err != nil {
  273. log.Printf("[netmgr] Failed to start DHCP on eth0: %v", err)
  274. return
  275. }
  276. nm.eth0DhcpRunning = true
  277. time.Sleep(500 * time.Millisecond)
  278. // Read PID
  279. pidData, err := os.ReadFile(pidFile)
  280. if err == nil {
  281. fmt.Sscanf(string(pidData), "%d", &nm.eth0DhcpPid)
  282. if nm.debug {
  283. log.Printf("[netmgr] eth0 DHCP started (PID: %d)", nm.eth0DhcpPid)
  284. }
  285. } else {
  286. log.Printf("[netmgr] Warning: Could not read udhcpc PID file: %v", err)
  287. }
  288. }
  289. func (nm *NetworkManager) configureEth0Static() {
  290. nm.stopEth0DHCP()
  291. // Bring interface up
  292. exec.Command("ip", "link", "set", eth0Interface, "up").Run()
  293. // Configure IP
  294. if nm.cfg.Network.Eth0.Address != "" {
  295. cmd := exec.Command("ip", "addr", "add", nm.cfg.Network.Eth0.Address, "dev", eth0Interface)
  296. if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "exists") {
  297. log.Printf("[netmgr] Failed to configure IP %s on eth0: %v", nm.cfg.Network.Eth0.Address, err)
  298. return
  299. }
  300. log.Printf("[netmgr] Configured static IP %s on eth0", nm.cfg.Network.Eth0.Address)
  301. }
  302. // Configure gateway
  303. if nm.cfg.Network.Eth0.Gateway != "" {
  304. exec.Command("ip", "route", "del", "default").Run()
  305. if err := exec.Command("ip", "route", "add", "default", "via", nm.cfg.Network.Eth0.Gateway).Run(); err != nil {
  306. log.Printf("[netmgr] Failed to configure gateway %s: %v", nm.cfg.Network.Eth0.Gateway, err)
  307. } else {
  308. log.Printf("[netmgr] Configured gateway %s", nm.cfg.Network.Eth0.Gateway)
  309. }
  310. }
  311. // Configure DNS
  312. if nm.cfg.Network.Eth0.DNS != "" {
  313. dnsContent := fmt.Sprintf("nameserver %s\n", nm.cfg.Network.Eth0.DNS)
  314. os.WriteFile("/etc/resolv.conf", []byte(dnsContent), 0644)
  315. }
  316. }
  317. func (nm *NetworkManager) stopEth0DHCP() {
  318. pidFile := fmt.Sprintf("/var/run/udhcpc.%s.pid", eth0Interface)
  319. pidData, err := os.ReadFile(pidFile)
  320. if err == nil {
  321. var pid int
  322. if _, err := fmt.Sscanf(string(pidData), "%d", &pid); err == nil {
  323. syscall.Kill(pid, syscall.SIGTERM)
  324. os.Remove(pidFile)
  325. }
  326. }
  327. exec.Command("killall", "-q", "udhcpc").Run()
  328. nm.eth0DhcpPid = 0
  329. nm.eth0DhcpRunning = false
  330. }
  331. func (nm *NetworkManager) stopEth0() {
  332. nm.stopEth0DHCP()
  333. exec.Command("ip", "addr", "flush", "dev", eth0Interface).Run()
  334. }
  335. // =======================================================================================
  336. // wlan0 client management
  337. // =======================================================================================
  338. func (nm *NetworkManager) connectWLAN0Client() error {
  339. // Stop WiFi scanner (can't run both)
  340. if nm.scanners.IsWiFiRunning() {
  341. log.Println("[netmgr] Stopping WiFi scanner before connecting client")
  342. nm.scanners.StopWiFi()
  343. }
  344. // Temporarily stop BLE scanner during WiFi connection (AIC8800 combo chip issue)
  345. bleWasRunning := nm.scanners.IsBLERunning()
  346. if bleWasRunning {
  347. log.Println("[netmgr] Temporarily stopping BLE scanner for WiFi connection")
  348. nm.scanners.StopBLE()
  349. defer func() {
  350. if bleWasRunning && nm.cfg.BLE.Enabled {
  351. log.Println("[netmgr] Restarting BLE scanner")
  352. nm.scanners.StartBLE(nm.cfg.ZMQAddrBLE)
  353. }
  354. }()
  355. time.Sleep(500 * time.Millisecond)
  356. }
  357. // Use wifi-connect.sh script
  358. scriptPath := "/opt/mybeacon/bin/wifi-connect.sh"
  359. cmd := exec.Command(scriptPath, nm.cfg.WiFi.SSID, nm.cfg.WiFi.PSK)
  360. output, err := cmd.CombinedOutput()
  361. if err != nil {
  362. return fmt.Errorf("wifi-connect.sh failed: %w (output: %s)", err, string(output))
  363. }
  364. return nil
  365. }
  366. func (nm *NetworkManager) stopWLAN0Client() {
  367. exec.Command("killall", "wpa_supplicant", "udhcpc", "dhcpcd").Run()
  368. exec.Command("ip", "addr", "flush", "dev", wlan0Interface).Run()
  369. os.RemoveAll("/var/run/wpa_supplicant/wlan0")
  370. }
  371. // =======================================================================================
  372. // wlan0 AP fallback management
  373. // =======================================================================================
  374. func (nm *NetworkManager) startAP() {
  375. if nm.apRunning {
  376. return
  377. }
  378. log.Println("[netmgr] Starting AP fallback mode...")
  379. // Stop client if running
  380. nm.stopWLAN0Client()
  381. // Stop WiFi scanner
  382. if nm.scanners.IsWiFiRunning() {
  383. nm.scanners.StopWiFi()
  384. }
  385. // Get MAC address from wlan0 for SSID
  386. macAddr := nm.getInterfaceMAC(wlan0Interface)
  387. if macAddr == "" {
  388. macAddr = "00:00:00:00:00:00"
  389. }
  390. // Use last 4 chars of MAC (e.g., "1b:ad" from "38:54:39:4b:1b:ad")
  391. macParts := strings.Split(macAddr, ":")
  392. macSuffix := ""
  393. if len(macParts) >= 2 {
  394. macSuffix = macParts[len(macParts)-2] + macParts[len(macParts)-1]
  395. } else {
  396. macSuffix = "0000"
  397. }
  398. apSSID := fmt.Sprintf("mybeacon_%s", macSuffix)
  399. // Get password from device state
  400. apPassword := nm.devicePassword
  401. if apPassword == "" {
  402. apPassword = "mybeacon" // fallback if not set
  403. }
  404. // Use ap-start.sh script
  405. scriptPath := "/opt/mybeacon/bin/ap-start.sh"
  406. cmd := exec.Command(scriptPath, apSSID, apPassword)
  407. output, err := cmd.CombinedOutput()
  408. if err != nil {
  409. log.Printf("[netmgr] AP start failed: %v (output: %s)", err, string(output))
  410. return
  411. }
  412. nm.apRunning = true
  413. nm.currentState = StateWLAN0APFallback
  414. log.Printf("[netmgr] AP fallback started: SSID=%s, Password=%s, IP=192.168.4.1", apSSID, apPassword)
  415. }
  416. func (nm *NetworkManager) stopAP() {
  417. if !nm.apRunning {
  418. return
  419. }
  420. log.Println("[netmgr] Stopping AP fallback...")
  421. // Use ap-stop.sh script
  422. scriptPath := "/opt/mybeacon/bin/ap-stop.sh"
  423. cmd := exec.Command(scriptPath)
  424. output, err := cmd.CombinedOutput()
  425. if err != nil {
  426. log.Printf("[netmgr] AP stop failed: %v (output: %s)", err, string(output))
  427. }
  428. nm.apRunning = false
  429. }
  430. // =======================================================================================
  431. // Helper functions
  432. // =======================================================================================
  433. func (nm *NetworkManager) checkCarrier(iface string) bool {
  434. carrierPath := fmt.Sprintf("/sys/class/net/%s/carrier", iface)
  435. data, err := os.ReadFile(carrierPath)
  436. if err != nil {
  437. return false
  438. }
  439. return strings.TrimSpace(string(data)) == "1"
  440. }
  441. func (nm *NetworkManager) hasIP(iface string) bool {
  442. cmd := exec.Command("ip", "addr", "show", iface)
  443. output, err := cmd.Output()
  444. if err != nil {
  445. return false
  446. }
  447. return strings.Contains(string(output), "inet ")
  448. }
  449. func (nm *NetworkManager) getInterfaceMAC(iface string) string {
  450. addrPath := fmt.Sprintf("/sys/class/net/%s/address", iface)
  451. data, err := os.ReadFile(addrPath)
  452. if err != nil {
  453. return ""
  454. }
  455. return strings.TrimSpace(string(data))
  456. }
  457. // startNTP starts NTP daemon for time synchronization
  458. func (nm *NetworkManager) startNTP() {
  459. nm.mu.Lock()
  460. servers := nm.cfg.Network.NTPServers
  461. nm.mu.Unlock()
  462. if len(servers) == 0 {
  463. servers = []string{"pool.ntp.org"}
  464. }
  465. // Stop any existing ntpd
  466. exec.Command("killall", "-9", "ntpd").Run()
  467. time.Sleep(200 * time.Millisecond)
  468. log.Println("[netmgr] Starting NTP sync...")
  469. // First do a one-shot sync to set time immediately
  470. for _, server := range servers {
  471. log.Printf("[netmgr] Trying NTP server: %s", server)
  472. cmd := exec.Command("ntpd", "-n", "-q", "-p", server)
  473. if err := cmd.Run(); err == nil {
  474. log.Printf("[netmgr] NTP sync successful from %s", server)
  475. break
  476. }
  477. }
  478. // Start daemon for continuous sync (use first server from list)
  479. primaryServer := servers[0]
  480. cmd := exec.Command("ntpd", "-p", primaryServer)
  481. if err := cmd.Start(); err != nil {
  482. log.Printf("[netmgr] Failed to start NTP daemon: %v", err)
  483. return
  484. }
  485. log.Printf("[netmgr] NTP daemon started (server: %s)", primaryServer)
  486. }
  487. // stopNTP stops NTP daemon
  488. func (nm *NetworkManager) stopNTP() {
  489. log.Println("[netmgr] Stopping NTP daemon...")
  490. exec.Command("killall", "-9", "ntpd").Run()
  491. }