// // Copyright 2026 Aarav Ravindra Kharade // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import CSystem // ═══════════════════════════════════════════════════════════════════ // ArkOS Network Service // ═══════════════════════════════════════════════════════════════════ // Background service that manages network connectivity for the OS. // Handles interface monitoring, DHCP client triggering, and DNS // resolver configuration. // // Lifecycle: // 1. Enumerate all non-loopback interfaces via /sys/class/net/ // 2. Bring up interfaces via ip(8) / ifconfig(8) if available // 3. Trigger DHCP on wired interfaces via udhcpc (from busybox) // 4. Monitor link state changes and re-acquire addresses as needed // 5. Configure /etc/resolv.conf with received nameservers // // In QEMU user-mode networking, the virtual NIC gets an IP via // QEMU's built-in DHCP server (typically 10.0.2.15). // ═══════════════════════════════════════════════════════════════════ /// Manages network interface lifecycle and connectivity. public class NetworkService { /// Singleton instance (started by ServiceManager or arkrt main). public static let shared = NetworkService() /// Polling interval for link state monitoring (seconds). private let pollInterval: UInt32 = 5 /// Whether the service is currently running its monitor loop. private var isRunning = false private init() {} // ── Interface Bring-Up ──────────────────────────────────────── /// Attempts to bring up all non-loopback network interfaces and /// acquire IP addresses via DHCP. public func configureInterfaces() { let interfaces = NetworkManager.getInterfaceNames() if interfaces.isEmpty { LogManager.log("No network interfaces found", level: .warn, component: "ark.service.network") return } LogManager.log("Found \(interfaces.count) interface(s): \(interfaces)", component: "ark.service.network") for iface in interfaces { bringUpInterface(iface) } } /// Brings up a single interface and starts DHCP if it's wired. private func bringUpInterface(_ name: String) { LogManager.log("Bringing up interface '\(name)'...", component: "ark.service.network") // Try to bring the interface up using ip(8) first, fall back to ifconfig let ipResult = runCommand("/sbin/ip link set \(name) up") if ipResult != 0 { let _ = runCommand("/sbin/ifconfig \(name) up") } // Check if the interface has carrier (cable plugged / associated) let carrierPath = "/sys/class/net/\(name)/carrier" if let c = readFileContents(carrierPath), trimWhitespace(c) == "1" { LogManager.log("'\(name)' has carrier — requesting DHCP lease...", component: "ark.service.network") requestDHCP(interface: name) } else { LogManager.log("'\(name)' has no carrier — will retry on link change", component: "ark.service.network") } } // ── DHCP Client ─────────────────────────────────────────────── /// Runs udhcpc (busybox DHCP client) to acquire an IP address. /// Uses a short timeout to avoid blocking the boot process. private func requestDHCP(interface name: String) { // udhcpc flags: // -i — target interface // -n — exit if lease not obtained // -q — quit after obtaining lease // -t 3 — try 3 times // -T 2 — 2 second timeout between tries let result = runCommand("/sbin/udhcpc -i \(name) -n -q -t 3 -T 2 2>/dev/null") if result == 0 { LogManager.log("DHCP lease acquired on '\(name)'", component: "ark.service.network") configureDNS() } else { LogManager.log("DHCP failed on '\(name)' — will retry", level: .warn, component: "ark.service.network") } } // ── DNS Configuration ───────────────────────────────────────── /// Writes a basic resolv.conf with common public DNS servers. /// In production, this would be populated by DHCP lease data. private func configureDNS() { let resolv = """ # Generated by ArkOS NetworkService nameserver 8.8.8.8 nameserver 8.8.4.4 nameserver 1.1.1.1 """ if let fp = fopen("/etc/resolv.conf", "w") { fputs(resolv, fp) fclose(fp) LogManager.log("DNS resolver configured", component: "ark.service.network") } } // ── Link State Monitor ──────────────────────────────────────── /// Starts the background link monitoring loop. Checks for carrier /// changes and re-runs DHCP when a cable is plugged in. public func startMonitoring() { guard !isRunning else { return } isRunning = true LogManager.log("Starting link state monitor (poll every \(pollInterval)s)", component: "ark.service.network") ResourceController.executeOnCorePool { [self] in var previousState: [String: Bool] = [:] while isRunning { let interfaces = NetworkManager.getAllInterfaces() for iface in interfaces { let wasUp = previousState[iface.name] ?? false let isNowUp = iface.hasCarrier // Detect cable plug-in events if isNowUp && !wasUp { LogManager.log("Carrier detected on '\(iface.name)' — acquiring address...", component: "ark.service.network") requestDHCP(interface: iface.name) } // Detect cable unplug events if !isNowUp && wasUp { LogManager.log("Carrier lost on '\(iface.name)'", level: .warn, component: "ark.service.network") } previousState[iface.name] = isNowUp } sleep(pollInterval) } } } /// Stops the link monitoring loop. public func stopMonitoring() { isRunning = false LogManager.log("Link monitor stopped", component: "ark.service.network") } // ── Utility ─────────────────────────────────────────────────── /// Execute a shell command and return its exit code. @discardableResult private func runCommand(_ cmd: String) -> Int32 { let result = system(cmd) return result } }