| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- //
- // 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 <interface> — 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
- }
- }
|