← back to Exo
app/EXO/EXO/Services/NetworkStatusService.swift
230 lines
import AppKit
import Foundation
@MainActor
final class NetworkStatusService: ObservableObject {
@Published private(set) var status: NetworkStatus = .empty
private var timer: Timer?
func refresh() async {
let fetched = await Task.detached(priority: .background) {
NetworkStatusFetcher().fetch()
}.value
status = fetched
}
func startPolling(interval: TimeInterval = 30) {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
guard let self else { return }
Task { await self.refresh() }
}
if let timer {
RunLoop.main.add(timer, forMode: .common)
}
Task { await refresh() }
}
func stopPolling() {
timer?.invalidate()
timer = nil
}
}
struct NetworkStatus: Equatable {
let thunderboltBridgeState: ThunderboltState?
let bridgeInactive: Bool?
let interfaceStatuses: [InterfaceIpStatus]
let localRdmaDevices: [String]
let localRdmaActivePorts: [RDMAPort]
static let empty = NetworkStatus(
thunderboltBridgeState: nil,
bridgeInactive: nil,
interfaceStatuses: [],
localRdmaDevices: [],
localRdmaActivePorts: []
)
}
struct RDMAPort: Equatable {
let device: String
let port: String
let state: String
}
struct InterfaceIpStatus: Equatable {
let interfaceName: String
let ipAddress: String?
}
enum ThunderboltState: Equatable {
case enabled
case disabled
case deleted
}
private struct NetworkStatusFetcher {
func fetch() -> NetworkStatus {
NetworkStatus(
thunderboltBridgeState: readThunderboltBridgeState(),
bridgeInactive: readBridgeInactive(),
interfaceStatuses: readInterfaceStatuses(),
localRdmaDevices: readRDMADevices(),
localRdmaActivePorts: readRDMAActivePorts()
)
}
private func readRDMADevices() -> [String] {
let result = runCommand(["ibv_devices"])
guard result.exitCode == 0 else { return [] }
var devices: [String] = []
for line in result.output.split(separator: "\n") {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("---") || trimmed.lowercased().hasPrefix("device")
|| trimmed.isEmpty
{
continue
}
let parts = trimmed.split(separator: " ", maxSplits: 1)
if let deviceName = parts.first {
devices.append(String(deviceName))
}
}
return devices
}
private func readRDMAActivePorts() -> [RDMAPort] {
let result = runCommand(["ibv_devinfo"])
guard result.exitCode == 0 else { return [] }
var ports: [RDMAPort] = []
var currentDevice: String?
var currentPort: String?
for line in result.output.split(separator: "\n") {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("hca_id:") {
currentDevice = trimmed.replacingOccurrences(of: "hca_id:", with: "")
.trimmingCharacters(in: .whitespaces)
} else if trimmed.hasPrefix("port:") {
currentPort = trimmed.replacingOccurrences(of: "port:", with: "")
.trimmingCharacters(in: .whitespaces)
} else if trimmed.hasPrefix("state:") {
let state = trimmed.replacingOccurrences(of: "state:", with: "").trimmingCharacters(
in: .whitespaces)
if let device = currentDevice, let port = currentPort {
if state.lowercased().contains("active") {
ports.append(RDMAPort(device: device, port: port, state: state))
}
}
}
}
return ports
}
private func readThunderboltBridgeState() -> ThunderboltState? {
// Dynamically find the Thunderbolt Bridge service (don't assume the name)
guard let serviceName = ThunderboltBridgeDetector.findThunderboltBridgeServiceName() else {
// No bridge containing Thunderbolt interfaces exists
return .deleted
}
guard let isEnabled = ThunderboltBridgeDetector.isServiceEnabled(serviceName: serviceName)
else {
return nil
}
return isEnabled ? .enabled : .disabled
}
private func readBridgeInactive() -> Bool? {
let result = runCommand(["ifconfig", "bridge0"])
guard result.exitCode == 0 else { return nil }
guard
let statusLine = result.output
.components(separatedBy: .newlines)
.first(where: { $0.contains("status:") })?
.lowercased()
else {
return nil
}
if statusLine.contains("inactive") {
return true
}
if statusLine.contains("active") {
return false
}
return nil
}
private func readInterfaceStatuses() -> [InterfaceIpStatus] {
(0...7).map { "en\($0)" }.map(readInterfaceStatus)
}
private func readInterfaceStatus(for interface: String) -> InterfaceIpStatus {
let result = runCommand(["ifconfig", interface])
guard result.exitCode == 0 else {
return InterfaceIpStatus(
interfaceName: interface,
ipAddress: nil
)
}
let output = result.output
let ip = firstInet(from: output)
return InterfaceIpStatus(
interfaceName: interface,
ipAddress: ip
)
}
private func firstInet(from ifconfigOutput: String) -> String? {
for line in ifconfigOutput.split(separator: "\n") {
let trimmed = line.trimmingCharacters(in: .whitespaces)
guard trimmed.hasPrefix("inet ") else { continue }
let parts = trimmed.split(separator: " ")
if parts.count >= 2 {
let candidate = String(parts[1])
if candidate != "127.0.0.1" {
return candidate
}
}
}
return nil
}
private struct CommandResult {
let exitCode: Int32
let output: String
let error: String
}
private func runCommand(_ arguments: [String]) -> CommandResult {
let process = Process()
process.launchPath = "/usr/bin/env"
process.arguments = arguments
let stdout = Pipe()
let stderr = Pipe()
process.standardOutput = stdout
process.standardError = stderr
do {
try process.run()
} catch {
return CommandResult(exitCode: -1, output: "", error: error.localizedDescription)
}
process.waitUntilExit()
let outputData = stdout.fileHandleForReading.readDataToEndOfFile()
let errorData = stderr.fileHandleForReading.readDataToEndOfFile()
return CommandResult(
exitCode: process.terminationStatus,
output: String(decoding: outputData, as: UTF8.self),
error: String(decoding: errorData, as: UTF8.self)
)
}
}