punchnet-macos/Tun/Configuration/SDLConfiguration.swift

225 lines
7.2 KiB
Swift

//
// SDLConfiguration.swift
// sdlan
//
// Created by on 2025/7/14.
//
import Foundation
import NIOCore
//
public class SDLConfiguration {
//
public struct NetworkAddress {
public let networkId: UInt32
public let ip: UInt32
public let maskLen: UInt8
public let mac: Data
public let networkDomain: String
// ip
var ipAddress: String {
return SDLUtil.int32ToIp(self.ip)
}
//
var maskAddress: String {
let len0 = 32 - maskLen
let num: UInt32 = (0xFFFFFFFF >> len0) << len0
return SDLUtil.int32ToIp(num)
}
//
var netAddress: String {
let len0 = 32 - maskLen
let mask: UInt32 = (0xFFFFFFFF >> len0) << len0
return SDLUtil.int32ToIp(self.ip & mask)
}
}
//
public struct ExitNode {
let exitNodeIp: UInt32
}
public struct ACL {
let tcpPorts: Set<UInt16>
let udpPorts: Set<UInt16>
static let empty = ACL(tcpPorts: [], udpPorts: [])
}
//
public struct ResolvedServerEndpoint: Sendable {
let host: String
let ip: String
}
//
let version: Int
let serverEndpoint: ResolvedServerEndpoint
let stunSocketAddress: SocketAddress
//
let stunProbeSocketAddressArray: [[SocketAddress]]
let clientId: String
let networkAddress: NetworkAddress
let hostname: String
let accessToken: String
let identityId: UInt32
let acl: ACL
var exitNode: ExitNode?
public init(version: Int,
serverEndpoint: ResolvedServerEndpoint,
stunServers: [String],
clientId: String,
networkAddress: NetworkAddress,
hostname: String,
accessToken: String,
identityId: UInt32,
acl: ACL,
exitNode: ExitNode?) {
self.version = version
self.serverEndpoint = serverEndpoint
let stunHosts = stunServers.isEmpty ? [serverEndpoint.ip] : stunServers
self.stunSocketAddress = Self.makeStunSocketAddress(host: stunHosts[0], port: 1365)
self.stunProbeSocketAddressArray = stunHosts.map { stunServer in
[
Self.makeStunSocketAddress(host: stunServer, port: 1365),
Self.makeStunSocketAddress(host: stunServer, port: 1366)
]
}
self.clientId = clientId
self.networkAddress = networkAddress
self.accessToken = accessToken
self.identityId = identityId
self.acl = acl
self.hostname = hostname
self.exitNode = exitNode
}
}
private extension SDLConfiguration {
static func resolveHostAddress(host: String, port: Int) -> String {
let address = try! SocketAddress.makeAddressResolvingHost(host, port: port)
return address.ipAddress!
}
static func makeStunSocketAddress(host: String, port: Int) -> SocketAddress {
return try! SocketAddress.makeAddressResolvingHost(host, port: port)
}
}
//
extension SDLConfiguration {
static func parse(options: [String: NSObject]) -> SDLConfiguration? {
guard let version = options["version"] as? Int,
let serverHost = options["server_host"] as? String,
let stunAssistHost = options["stun_assist_host"] as? String,
let accessToken = options["access_token"] as? String,
let identityId = options["identity_id"] as? UInt32,
let clientId = options["client_id"] as? String,
let hostname = options["hostname"] as? String,
let networkAddressDict = options["network_address"] as? [String: NSObject] else {
return nil
}
guard let networkAddress = parseNetworkAddress(networkAddressDict) else {
return nil
}
let acl = parseACL(options["acl"])
//
var exitNode: ExitNode? = nil
if let exitNodeIpStr = options["exit_node_ip"] as? String, let exitNodeIp = SDLUtil.ipv4StrToInt32(exitNodeIpStr) {
exitNode = .init(exitNodeIp: exitNodeIp)
}
let serverEndpoint = ResolvedServerEndpoint(
host: serverHost,
ip: Self.resolveHostAddress(host: serverHost, port: 1443)
)
return SDLConfiguration(version: version,
serverEndpoint: serverEndpoint,
stunServers: [serverHost, stunAssistHost],
clientId: clientId,
networkAddress: networkAddress,
hostname: hostname,
accessToken: accessToken,
identityId: identityId,
acl: acl,
exitNode: exitNode)
}
private static func parseNetworkAddress(_ config: [String: NSObject]) -> SDLConfiguration.NetworkAddress? {
guard let networkId = config["network_id"] as? UInt32,
let ipStr = config["ip"] as? String,
let ip = SDLUtil.ipv4StrToInt32(ipStr),
let maskLen = config["mask_len"] as? UInt8,
let mac = config["mac"] as? Data,
let networkDomain = config["network_domain"] as? String else {
return nil
}
return .init(networkId: networkId, ip: ip, maskLen: maskLen, mac: mac, networkDomain: networkDomain)
}
private static func parseACL(_ value: NSObject?) -> SDLConfiguration.ACL {
guard let dict = value as? [String: NSObject] else {
return .empty
}
return .init(
tcpPorts: parsePortSet(dict["tcp"]),
udpPorts: parsePortSet(dict["udp"])
)
}
private static func parsePortSet(_ value: NSObject?) -> Set<UInt16> {
let ports: [UInt32]
if let values = value as? [UInt32] {
ports = values
} else if let values = value as? [Int] {
ports = values.compactMap { $0 >= 0 ? UInt32($0) : nil }
} else if let values = value as? [NSNumber] {
ports = values.map(\.uint32Value)
} else if let values = value as? NSArray {
ports = values.compactMap { item in
if let value = item as? UInt32 {
return value
}
if let value = item as? Int, value >= 0 {
return UInt32(value)
}
if let value = item as? NSNumber {
return value.uint32Value
}
return nil
}
} else {
ports = []
}
return Set(ports.compactMap { port in
guard port > 0, port <= UInt32(UInt16.max) else {
return nil
}
return UInt16(port)
})
}
}