import AppKit import Foundation import Network import Observation import SwiftUI import TailscaleKit func slug(_ text: String) -> String { var out = "" var dash = false for c in text.lowercased() { if c.isASCII, c.isLetter || c.isNumber { if dash, !out.isEmpty { out += "-" } out.append(c) dash = false continue } dash = true } return out } final class TailnetNode: Sendable { let sd: tailscale let hostname = "byre-app-\(slug(macName))" private let proxyHost: String private let proxyPort: UInt16 private let proxyCredential: String init() { let dir = URL.applicationSupportDirectory.appending(path: "run.fc.byre.mac/tsnet", directoryHint: .isDirectory) do { try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) } catch { fatalError("create tsnet dir: \(error)") } sd = tailscale_new() Self.check(sd, "set hostname", unsafe tailscale_set_hostname(sd, hostname)) Self.check(sd, "set dir", unsafe tailscale_set_dir(sd, dir.path)) Self.check(sd, "set ephemeral", tailscale_set_ephemeral(sd, 0)) Self.check(sd, "set logfd", tailscale_set_logfd(sd, Self.logPipe())) Self.check(sd, "start", tailscale_start(sd)) var addr = [CChar](repeating: 0, count: 64) var proxyCred = [CChar](repeating: 0, count: 33) var apiCred = [CChar](repeating: 0, count: 33) Self.check(sd, "loopback", unsafe tailscale_loopback(sd, &addr, addr.count, &proxyCred, &apiCred)) let address = String(decoding: addr.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }, as: UTF8.self) guard let colon = address.lastIndex(of: ":"), let port = UInt16(address[address.index(after: colon)...]) else { fatalError("tsnet loopback address \(address)") } proxyHost = String(address[.. Int32 { let lines = LineBuffer() logs.fileHandleForReading.readabilityHandler = { handle in for line in lines.add(handle.availableData) { log.notice("tsnet: \(line, privacy: .public)") } } return dup(logs.fileHandleForWriting.fileDescriptor) } func peerState(_ host: String) -> String { var out: UnsafeMutablePointer? guard unsafe tailscale_status_json(sd, &out) == 0, let out = unsafe out else { return "no status: \(Self.errorMessage(sd))" } defer { unsafe free(out) } struct Peer: Decodable { let DNSName: String let Online: Bool let InNetworkMap: Bool let InMagicSock: Bool let InEngine: Bool let LastHandshake: String let CurAddr: String let Relay: String } struct Status: Decodable { let BackendState: String let Peer: [String: Peer]? } guard let status = try? JSONDecoder().decode(Status.self, from: unsafe Data(bytes: out, count: strlen(out))) else { return "undecodable status" } guard let p = (status.Peer ?? [:]).values.first(where: { $0.DNSName == host + "." || $0.DNSName == host }) else { return "state=\(status.BackendState) peer \(host) not in status" } return "state=\(status.BackendState) online=\(p.Online) netmap=\(p.InNetworkMap) magicsock=\(p.InMagicSock) engine=\(p.InEngine) handshake=\(p.LastHandshake) addr=\(p.CurAddr) relay=\(p.Relay)" } static func errorMessage(_ sd: tailscale) -> String { var buf = [CChar](repeating: 0, count: 1024) _ = unsafe tailscale_errmsg(sd, &buf, buf.count) return String(decoding: buf.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }, as: UTF8.self) } func proxy() -> ProxyConfiguration { let proxy = ProxyConfiguration(socksv5Proxy: .hostPort(host: NWEndpoint.Host(proxyHost), port: NWEndpoint.Port(rawValue: proxyPort)!)) proxy.applyCredential(username: "tsnet", password: proxyCredential) return proxy } func status() -> (state: String, authURL: URL?, servers: [URL], peers: Set)? { var out: UnsafeMutablePointer? guard unsafe tailscale_status_json(sd, &out) == 0, let out = unsafe out else { return nil } defer { unsafe free(out) } struct Node: Decodable { let DNSName: String let UserID: Int64 let Online: Bool } struct Status: Decodable { let BackendState: String let AuthURL: String let me: Node? let Peer: [String: Node]? enum CodingKeys: String, CodingKey { case BackendState, AuthURL, Peer case me = "Self" } } guard let status = try? JSONDecoder().decode(Status.self, from: unsafe Data(bytes: out, count: strlen(out))) else { return nil } let peers = (status.Peer ?? [:]).values let host = { (peer: Node) in (peer.DNSName.hasSuffix(".") ? String(peer.DNSName.dropLast()) : peer.DNSName).lowercased() } let servers = peers.compactMap { peer -> URL? in let name = host(peer).split(separator: ".").first ?? "" guard peer.Online, peer.UserID == status.me?.UserID, name.hasPrefix("byre-"), !name.hasPrefix("byre-app-") else { return nil } return parseOrigin("https://\(host(peer))") } return (status.BackendState, URL(string: status.AuthURL), servers, Set(peers.map(host))) } // Blocks until the connection is made or fails. func dial(_ address: String) throws -> Int32 { var conn: tailscale_conn = 0 guard unsafe tailscale_dial(sd, "tcp", address, &conn) == 0 else { throw ProcessFailure(errorDescription: "dial \(address) on the tailnet: \(Self.errorMessage(sd))") } return conn } } private final class LineBuffer: @unchecked Sendable { private var pending = Data() func add(_ data: Data) -> [String] { pending.append(data) var lines: [String] = [] while let nl = pending.firstIndex(of: 0x0A) { lines.append(String(decoding: pending[pending.startIndex.. = [] @ObservationIgnored private var servers: [URL] = [] var running: Bool { state == "Running" } // A node starting from its cached netmap reports Running before it knows any peer, and dials made then fail. func reachable(_ origin: URL) -> Bool { running && peers.contains(origin.host()!.lowercased()) } var needsLogin: Bool { state == "NeedsLogin" || state == "NeedsMachineAuth" || service?.state == "NeedsLogin" } private init() { Task { while true { if let status = tailnetNode.status() { state = status.state authURL = status.authURL servers = status.servers if peers != status.peers { peers = status.peers } } try? await Task.sleep(for: .seconds(1)) } } Task { await watchService() } Task { await discover() } } private func watchService() async { var loopback: URL? while true { if loopback == nil { do { loopback = URL(string: try await Server.checks().loopback) } catch { log.error("find the local service: \(errorText(error), privacy: .public)") } } if let loopback { service = try? await Server.tailnet(loopback) if let service, service.state == "Running", let origin = parseOrigin(service.origin), !Servers.shared.origins.contains(origin.absoluteString) { Servers.shared.add(origin) } } try? await Task.sleep(for: .seconds(2)) } } private func discover() async { while true { guard running else { try? await Task.sleep(for: .milliseconds(500)) continue } let candidates = servers.filter { !Servers.shared.origins.contains($0.absoluteString) } let found = await withTaskGroup(of: URL?.self) { group in for origin in candidates { group.addTask { await Self.probe(origin) ? origin : nil } } var found: [URL] = [] for await origin in group { if let origin { found.append(origin) } } return found } for origin in found { log.notice("found \(origin.absoluteString, privacy: .public) on the tailnet") Servers.shared.add(origin) } searched = true try? await Task.sleep(for: .seconds(10)) } } private nonisolated static func probe(_ origin: URL) async -> Bool { let api = API(origin: origin) defer { api.session.invalidateAndCancel() } return await withTaskGroup(of: Bool.self) { group in group.addTask { (try? await api.client.getStatus().ok) != nil } group.addTask { try? await Task.sleep(for: .seconds(5)) return false } let ok = await group.next() ?? false group.cancelAll() return ok } } } struct TailnetLoginView: View { var body: some View { let tailnet = Tailnet.shared VStack(alignment: .leading, spacing: 16) { Text("byre reaches its servers through Tailscale. Sign each of these nodes in to the tailnet your byre servers are on.") .fixedSize(horizontal: false, vertical: true) NodeLoginRow(hostname: tailnetNode.hostname, role: "this app", state: tailnet.state, authURL: tailnet.authURL) if let service = tailnet.service { NodeLoginRow(hostname: service.hostname, role: "this Mac's server", state: service.state, authURL: URL(string: service.authUrl)) } } .frame(width: 460) .padding(.vertical, 24) .frame(maxWidth: .infinity, maxHeight: .infinity) } } private struct NodeLoginRow: View { let hostname: String let role: String let state: String let authURL: URL? var body: some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { Text(hostname).font(Fonts.font(13, weight: 700)) Text(role).foregroundStyle(Palette.muted) } if state == "Running" { Text("Signed in").foregroundStyle(Palette.ok) } else { Button("Sign in to Tailscale") { if let authURL { NSWorkspace.shared.open(authURL) } } .disabled(authURL == nil) Text(authURL == nil ? "Waiting for Tailscale (\(state))…" : "Waiting for the sign-in (\(state))…") .foregroundStyle(Palette.muted) } } } }