NetworkService.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import Foundation
  2. #if canImport(Glibc)
  3. import Glibc
  4. #endif
  5. // ═══════════════════════════════════════════════════════════════════
  6. // ArkOS Network Service
  7. // ═══════════════════════════════════════════════════════════════════
  8. // Background service that manages network connectivity for the OS.
  9. // Handles interface monitoring, DHCP client triggering, and DNS
  10. // resolver configuration.
  11. //
  12. // Lifecycle:
  13. // 1. Enumerate all non-loopback interfaces via /sys/class/net/
  14. // 2. Bring up interfaces via ip(8) / ifconfig(8) if available
  15. // 3. Trigger DHCP on wired interfaces via udhcpc (from busybox)
  16. // 4. Monitor link state changes and re-acquire addresses as needed
  17. // 5. Configure /etc/resolv.conf with received nameservers
  18. //
  19. // In QEMU user-mode networking, the virtual NIC gets an IP via
  20. // QEMU's built-in DHCP server (typically 10.0.2.15).
  21. // ═══════════════════════════════════════════════════════════════════
  22. /// Manages network interface lifecycle and connectivity.
  23. public class NetworkService {
  24. /// Singleton instance (started by ServiceManager or arkrt main).
  25. public static let shared = NetworkService()
  26. /// Polling interval for link state monitoring (seconds).
  27. private let pollInterval: UInt32 = 5
  28. /// Whether the service is currently running its monitor loop.
  29. private var isRunning = false
  30. private init() {}
  31. // ── Interface Bring-Up ────────────────────────────────────────
  32. /// Attempts to bring up all non-loopback network interfaces and
  33. /// acquire IP addresses via DHCP.
  34. public func configureInterfaces() {
  35. let interfaces = NetworkManager.getInterfaceNames()
  36. if interfaces.isEmpty {
  37. LogManager.log("NetworkService: No network interfaces found", level: .warn)
  38. return
  39. }
  40. LogManager.log("NetworkService: Found \(interfaces.count) interface(s): \(interfaces)")
  41. for iface in interfaces {
  42. bringUpInterface(iface)
  43. }
  44. }
  45. /// Brings up a single interface and starts DHCP if it's wired.
  46. private func bringUpInterface(_ name: String) {
  47. LogManager.log("NetworkService: Bringing up interface '\(name)'...")
  48. // Try to bring the interface up using ip(8) first, fall back to ifconfig
  49. let ipResult = runCommand("/sbin/ip link set \(name) up")
  50. if ipResult != 0 {
  51. let _ = runCommand("/sbin/ifconfig \(name) up")
  52. }
  53. // Check if the interface has carrier (cable plugged / associated)
  54. let carrierPath = "/sys/class/net/\(name)/carrier"
  55. let carrier = (try? String(contentsOfFile: carrierPath))?
  56. .trimmingCharacters(in: .whitespacesAndNewlines)
  57. if carrier == "1" {
  58. LogManager.log("NetworkService: '\(name)' has carrier — requesting DHCP lease...")
  59. requestDHCP(interface: name)
  60. } else {
  61. LogManager.log("NetworkService: '\(name)' has no carrier — will retry on link change")
  62. }
  63. }
  64. // ── DHCP Client ───────────────────────────────────────────────
  65. /// Runs udhcpc (busybox DHCP client) to acquire an IP address.
  66. /// Uses a short timeout to avoid blocking the boot process.
  67. private func requestDHCP(interface name: String) {
  68. // udhcpc flags:
  69. // -i <interface> — target interface
  70. // -n — exit if lease not obtained
  71. // -q — quit after obtaining lease
  72. // -t 3 — try 3 times
  73. // -T 2 — 2 second timeout between tries
  74. let result = runCommand("/sbin/udhcpc -i \(name) -n -q -t 3 -T 2 2>/dev/null")
  75. if result == 0 {
  76. LogManager.log("NetworkService: DHCP lease acquired on '\(name)'")
  77. configureDNS()
  78. } else {
  79. LogManager.log("NetworkService: DHCP failed on '\(name)' — will retry", level: .warn)
  80. }
  81. }
  82. // ── DNS Configuration ─────────────────────────────────────────
  83. /// Writes a basic resolv.conf with common public DNS servers.
  84. /// In production, this would be populated by DHCP lease data.
  85. private func configureDNS() {
  86. let resolv = """
  87. # Generated by ArkOS NetworkService
  88. nameserver 8.8.8.8
  89. nameserver 8.8.4.4
  90. nameserver 1.1.1.1
  91. """
  92. if let fp = fopen("/etc/resolv.conf", "w") {
  93. fputs(resolv, fp)
  94. fclose(fp)
  95. LogManager.log("NetworkService: DNS resolver configured")
  96. }
  97. }
  98. // ── Link State Monitor ────────────────────────────────────────
  99. /// Starts the background link monitoring loop. Checks for carrier
  100. /// changes and re-runs DHCP when a cable is plugged in.
  101. public func startMonitoring() {
  102. guard !isRunning else { return }
  103. isRunning = true
  104. LogManager.log("NetworkService: Starting link state monitor (poll every \(pollInterval)s)")
  105. ResourceController.executeOnCorePool { [self] in
  106. var previousState: [String: Bool] = [:]
  107. while isRunning {
  108. let interfaces = NetworkManager.getAllInterfaces()
  109. for iface in interfaces {
  110. let wasUp = previousState[iface.name] ?? false
  111. let isNowUp = iface.hasCarrier
  112. // Detect cable plug-in events
  113. if isNowUp && !wasUp {
  114. LogManager.log("NetworkService: Carrier detected on '\(iface.name)' — acquiring address...")
  115. requestDHCP(interface: iface.name)
  116. }
  117. // Detect cable unplug events
  118. if !isNowUp && wasUp {
  119. LogManager.log("NetworkService: Carrier lost on '\(iface.name)'", level: .warn)
  120. }
  121. previousState[iface.name] = isNowUp
  122. }
  123. sleep(pollInterval)
  124. }
  125. }
  126. }
  127. /// Stops the link monitoring loop.
  128. public func stopMonitoring() {
  129. isRunning = false
  130. LogManager.log("NetworkService: Link monitor stopped")
  131. }
  132. // ── Utility ───────────────────────────────────────────────────
  133. /// Execute a shell command and return its exit code.
  134. @discardableResult
  135. private func runCommand(_ cmd: String) -> Int32 {
  136. let result = system(cmd)
  137. return result
  138. }
  139. }