From 71009ab1b6b091e26804f7232bfeee195e6d54ae Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 20:22:40 +0000 Subject: [PATCH] refactor(macos): share tailnet IPv4 detection --- .../Sources/OpenClaw/TailscaleService.swift | 50 ++----------------- .../OpenClawDiscovery/TailscaleNetwork.swift | 47 +++++++++++++++++ .../OpenClawMacCLI/ConnectCommand.swift | 48 +----------------- 3 files changed, 52 insertions(+), 93 deletions(-) create mode 100644 apps/macos/Sources/OpenClawDiscovery/TailscaleNetwork.swift diff --git a/apps/macos/Sources/OpenClaw/TailscaleService.swift b/apps/macos/Sources/OpenClaw/TailscaleService.swift index b7f716a427..2cefa69d59 100644 --- a/apps/macos/Sources/OpenClaw/TailscaleService.swift +++ b/apps/macos/Sources/OpenClaw/TailscaleService.swift @@ -1,10 +1,8 @@ import AppKit import Foundation import Observation +import OpenClawDiscovery import os -#if canImport(Darwin) -import Darwin -#endif /// Manages Tailscale integration and status checking. @Observable @@ -140,7 +138,7 @@ final class TailscaleService { self.logger.info("Tailscale API not responding; app likely not running") } - if self.tailscaleIP == nil, let fallback = Self.detectTailnetIPv4() { + if self.tailscaleIP == nil, let fallback = TailscaleNetwork.detectTailnetIPv4() { self.tailscaleIP = fallback if !self.isRunning { self.isRunning = true @@ -178,49 +176,7 @@ final class TailscaleService { } } - private nonisolated static func isTailnetIPv4(_ address: String) -> Bool { - let parts = address.split(separator: ".") - guard parts.count == 4 else { return false } - let octets = parts.compactMap { Int($0) } - guard octets.count == 4 else { return false } - let a = octets[0] - let b = octets[1] - return a == 100 && b >= 64 && b <= 127 - } - - private nonisolated static func detectTailnetIPv4() -> String? { - var addrList: UnsafeMutablePointer? - guard getifaddrs(&addrList) == 0, let first = addrList else { return nil } - defer { freeifaddrs(addrList) } - - for ptr in sequence(first: first, next: { $0.pointee.ifa_next }) { - let flags = Int32(ptr.pointee.ifa_flags) - let isUp = (flags & IFF_UP) != 0 - let isLoopback = (flags & IFF_LOOPBACK) != 0 - let family = ptr.pointee.ifa_addr.pointee.sa_family - if !isUp || isLoopback || family != UInt8(AF_INET) { continue } - - var addr = ptr.pointee.ifa_addr.pointee - var buffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - let result = getnameinfo( - &addr, - socklen_t(ptr.pointee.ifa_addr.pointee.sa_len), - &buffer, - socklen_t(buffer.count), - nil, - 0, - NI_NUMERICHOST) - guard result == 0 else { continue } - let len = buffer.prefix { $0 != 0 } - let bytes = len.map { UInt8(bitPattern: $0) } - guard let ip = String(bytes: bytes, encoding: .utf8) else { continue } - if Self.isTailnetIPv4(ip) { return ip } - } - - return nil - } - nonisolated static func fallbackTailnetIPv4() -> String? { - self.detectTailnetIPv4() + TailscaleNetwork.detectTailnetIPv4() } } diff --git a/apps/macos/Sources/OpenClawDiscovery/TailscaleNetwork.swift b/apps/macos/Sources/OpenClawDiscovery/TailscaleNetwork.swift new file mode 100644 index 0000000000..60b11306d0 --- /dev/null +++ b/apps/macos/Sources/OpenClawDiscovery/TailscaleNetwork.swift @@ -0,0 +1,47 @@ +import Darwin +import Foundation + +public enum TailscaleNetwork { + public static func isTailnetIPv4(_ address: String) -> Bool { + let parts = address.split(separator: ".") + guard parts.count == 4 else { return false } + let octets = parts.compactMap { Int($0) } + guard octets.count == 4 else { return false } + let a = octets[0] + let b = octets[1] + return a == 100 && b >= 64 && b <= 127 + } + + public static func detectTailnetIPv4() -> String? { + var addrList: UnsafeMutablePointer? + guard getifaddrs(&addrList) == 0, let first = addrList else { return nil } + defer { freeifaddrs(addrList) } + + for ptr in sequence(first: first, next: { $0.pointee.ifa_next }) { + let flags = Int32(ptr.pointee.ifa_flags) + let isUp = (flags & IFF_UP) != 0 + let isLoopback = (flags & IFF_LOOPBACK) != 0 + let family = ptr.pointee.ifa_addr.pointee.sa_family + if !isUp || isLoopback || family != UInt8(AF_INET) { continue } + + var addr = ptr.pointee.ifa_addr.pointee + var buffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + let result = getnameinfo( + &addr, + socklen_t(ptr.pointee.ifa_addr.pointee.sa_len), + &buffer, + socklen_t(buffer.count), + nil, + 0, + NI_NUMERICHOST) + guard result == 0 else { continue } + let len = buffer.prefix { $0 != 0 } + let bytes = len.map { UInt8(bitPattern: $0) } + guard let ip = String(bytes: bytes, encoding: .utf8) else { continue } + if self.isTailnetIPv4(ip) { return ip } + } + + return nil + } +} + diff --git a/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift b/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift index 2933e9242f..0989164a01 100644 --- a/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift +++ b/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift @@ -1,9 +1,7 @@ import Foundation +import OpenClawDiscovery import OpenClawKit import OpenClawProtocol -#if canImport(Darwin) -import Darwin -#endif struct ConnectOptions { var url: String? @@ -301,7 +299,7 @@ private func resolvedPassword(opts: ConnectOptions, mode: String, config: Gatewa private func resolveLocalHost(bind: String?) -> String { let normalized = (bind ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - let tailnetIP = detectTailnetIPv4() + let tailnetIP = TailscaleNetwork.detectTailnetIPv4() switch normalized { case "tailnet": return tailnetIP ?? "127.0.0.1" @@ -309,45 +307,3 @@ private func resolveLocalHost(bind: String?) -> String { return "127.0.0.1" } } - -private func detectTailnetIPv4() -> String? { - var addrList: UnsafeMutablePointer? - guard getifaddrs(&addrList) == 0, let first = addrList else { return nil } - defer { freeifaddrs(addrList) } - - for ptr in sequence(first: first, next: { $0.pointee.ifa_next }) { - let flags = Int32(ptr.pointee.ifa_flags) - let isUp = (flags & IFF_UP) != 0 - let isLoopback = (flags & IFF_LOOPBACK) != 0 - let family = ptr.pointee.ifa_addr.pointee.sa_family - if !isUp || isLoopback || family != UInt8(AF_INET) { continue } - - var addr = ptr.pointee.ifa_addr.pointee - var buffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - let result = getnameinfo( - &addr, - socklen_t(ptr.pointee.ifa_addr.pointee.sa_len), - &buffer, - socklen_t(buffer.count), - nil, - 0, - NI_NUMERICHOST) - guard result == 0 else { continue } - let len = buffer.prefix { $0 != 0 } - let bytes = len.map { UInt8(bitPattern: $0) } - guard let ip = String(bytes: bytes, encoding: .utf8) else { continue } - if isTailnetIPv4(ip) { return ip } - } - - return nil -} - -private func isTailnetIPv4(_ address: String) -> Bool { - let parts = address.split(separator: ".") - guard parts.count == 4 else { return false } - let octets = parts.compactMap { Int($0) } - guard octets.count == 4 else { return false } - let a = octets[0] - let b = octets[1] - return a == 100 && b >= 64 && b <= 127 -}