Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b208dc7859 | |||
| 4e0247f648 | |||
| fca095173c | |||
| 06ba79bf83 | |||
| 7ca620eca7 | |||
| 0df890a699 | |||
| 28219a3bb3 | |||
| d00296737b | |||
| f86dd4c273 | |||
| 5031835484 | |||
| bd10ea9b99 | |||
| 1e74de12aa | |||
| ed45954127 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
punchnet.xcodeproj/*
|
|
||||||
@ -14,8 +14,6 @@ target_ip: \(SDLUtil.int32ToIp(self.targetIP)), target_mac: \(SDLUtil.formatMacA
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
static let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
|
||||||
|
|
||||||
// ARP操作码
|
// ARP操作码
|
||||||
enum Opcode: UInt16 {
|
enum Opcode: UInt16 {
|
||||||
case request = 0x01
|
case request = 0x01
|
||||||
@ -96,10 +94,6 @@ target_ip: \(SDLUtil.int32ToIp(self.targetIP)), target_mac: \(SDLUtil.formatMacA
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
static func isBroadcastMac(_ macAddress: Data) -> Bool {
|
|
||||||
return macAddress == broadcastMac
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ARPPacket {
|
extension ARPPacket {
|
||||||
|
|||||||
695
Tun/Punchnet/Actors/SDLContextNew.swift
Normal file
695
Tun/Punchnet/Actors/SDLContextNew.swift
Normal file
@ -0,0 +1,695 @@
|
|||||||
|
//
|
||||||
|
// SDLContext.swift
|
||||||
|
// Tun
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/2/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
import NIOCore
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
// 上下文环境变量,全局共享
|
||||||
|
/*
|
||||||
|
1. 处理rsa的加解密逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
@available(macOS 14, *)
|
||||||
|
public class SDLContextNew {
|
||||||
|
|
||||||
|
// 路由信息
|
||||||
|
struct Route {
|
||||||
|
let dstAddress: String
|
||||||
|
let subnetMask: String
|
||||||
|
|
||||||
|
var debugInfo: String {
|
||||||
|
return "\(dstAddress):\(subnetMask)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config: SDLConfiguration
|
||||||
|
|
||||||
|
// tun网络地址信息
|
||||||
|
var devAddr: SDLDevAddr
|
||||||
|
|
||||||
|
// nat映射的相关信息, 暂时没有用处
|
||||||
|
//var natAddress: SDLNatAddress?
|
||||||
|
// nat的网络类型
|
||||||
|
var natType: SDLNatProber.NatType = .blocked
|
||||||
|
|
||||||
|
// AES加密,授权通过后,对象才会被创建
|
||||||
|
var aesCipher: AESCipher
|
||||||
|
|
||||||
|
// aes
|
||||||
|
var aesKey: Data = Data()
|
||||||
|
|
||||||
|
// rsa的相关配置, public_key是本地生成的
|
||||||
|
let rsaCipher: RSACipher
|
||||||
|
|
||||||
|
// 依赖的变量
|
||||||
|
var udpHoleActor: SDLUDPHoleActor?
|
||||||
|
var superClientActor: SDLSuperClientActor?
|
||||||
|
var providerActor: SDLTunnelProviderActor
|
||||||
|
|
||||||
|
// dns的client对象
|
||||||
|
var dnsClient: DNSClient?
|
||||||
|
|
||||||
|
// 数据包读取任务
|
||||||
|
private var readTask: Task<(), Never>?
|
||||||
|
|
||||||
|
private var sessionManager: SessionManager
|
||||||
|
private var arpServer: ArpServer
|
||||||
|
|
||||||
|
// 记录最后发送的stunRequest的cookie
|
||||||
|
private var lastCookie: UInt32? = 0
|
||||||
|
|
||||||
|
// 网络状态变化的健康
|
||||||
|
private var monitor: SDLNetworkMonitor?
|
||||||
|
|
||||||
|
// 内部socket通讯
|
||||||
|
private var noticeClient: SDLNoticeClient?
|
||||||
|
|
||||||
|
// 流量统计
|
||||||
|
private var flowTracer = SDLFlowTracerActor()
|
||||||
|
private var flowTracerCancel: AnyCancellable?
|
||||||
|
|
||||||
|
// 处理holer
|
||||||
|
private var holerPublishers: [Data:PassthroughSubject<RegisterRequest, Never>] = [:]
|
||||||
|
private var bag = Set<AnyCancellable>()
|
||||||
|
private var locker = NSLock()
|
||||||
|
|
||||||
|
private let logger: SDLLogger
|
||||||
|
private var rootTask: Task<Void, Error>?
|
||||||
|
|
||||||
|
struct RegisterRequest {
|
||||||
|
let srcMac: Data
|
||||||
|
let dstMac: Data
|
||||||
|
let networkId: UInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher, logger: SDLLogger) {
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.rsaCipher = rsaCipher
|
||||||
|
self.aesCipher = aesCipher
|
||||||
|
|
||||||
|
// 生成mac地址
|
||||||
|
var devAddr = SDLDevAddr()
|
||||||
|
devAddr.mac = Self.getMacAddress()
|
||||||
|
self.devAddr = devAddr
|
||||||
|
|
||||||
|
self.sessionManager = SessionManager()
|
||||||
|
self.arpServer = ArpServer(known_macs: [:])
|
||||||
|
self.providerActor = SDLTunnelProviderActor(provider: provider, logger: logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func start() async throws {
|
||||||
|
self.rootTask = Task {
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
group.addTask {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
do {
|
||||||
|
try await self.startDnsClient()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] UDPHole get err: \(err)", level: .warning)
|
||||||
|
try await Task.sleep(for: .seconds(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
do {
|
||||||
|
try await self.startUDPHole()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] UDPHole get err: \(err)", level: .warning)
|
||||||
|
try await Task.sleep(for: .seconds(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
do {
|
||||||
|
try await self.startSuperClient()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] SuperClient get error: \(err), will restart", level: .warning)
|
||||||
|
await self.arpServer.clear()
|
||||||
|
try await Task.sleep(for: .seconds(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
await self.startMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
do {
|
||||||
|
try await self.startNoticeClient()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] noticeClient get err: \(err)", level: .warning)
|
||||||
|
try await Task.sleep(for: .seconds(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try await group.waitForAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try await self.rootTask?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
public func stop() async {
|
||||||
|
self.rootTask?.cancel()
|
||||||
|
self.superClientActor = nil
|
||||||
|
self.udpHoleActor = nil
|
||||||
|
self.noticeClient = nil
|
||||||
|
|
||||||
|
self.readTask?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startNoticeClient() async throws {
|
||||||
|
self.noticeClient = try await SDLNoticeClient(noticePort: self.config.noticePort, logger: self.logger)
|
||||||
|
try await self.noticeClient?.start()
|
||||||
|
self.logger.log("[SDLContext] notice_client task cancel", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startUDPHole() async throws {
|
||||||
|
self.udpHoleActor = try await SDLUDPHoleActor(logger: self.logger)
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
group.addTask {
|
||||||
|
try await self.udpHoleActor?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
if let udpHoleActor = self.udpHoleActor {
|
||||||
|
let cookie = await udpHoleActor.getCookieId()
|
||||||
|
var stunRequest = SDLStunRequest()
|
||||||
|
stunRequest.cookie = cookie
|
||||||
|
stunRequest.clientID = self.config.clientId
|
||||||
|
stunRequest.networkID = self.devAddr.networkID
|
||||||
|
stunRequest.ip = self.devAddr.netAddr
|
||||||
|
stunRequest.mac = self.devAddr.mac
|
||||||
|
stunRequest.natType = UInt32(self.natType.rawValue)
|
||||||
|
|
||||||
|
let remoteAddress = self.config.stunSocketAddress
|
||||||
|
await udpHoleActor.send(type: .stunRequest, data: try stunRequest.serializedData(), remoteAddress: remoteAddress)
|
||||||
|
self.lastCookie = cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
if let eventFlow = self.udpHoleActor?.eventFlow {
|
||||||
|
for try await event in eventFlow {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
try await self.handleUDPEvent(event: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = try await group.next() {
|
||||||
|
group.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startSuperClient() async throws {
|
||||||
|
self.superClientActor = try await SDLSuperClientActor(host: self.config.superHost, port: self.config.superPort, logger: self.logger)
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLContext] super client task cancel", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
try await self.superClientActor?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
if let eventFlow = self.superClientActor?.eventFlow {
|
||||||
|
for try await event in eventFlow {
|
||||||
|
try await self.handleSuperEvent(event: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = try await group.next() {
|
||||||
|
group.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startMonitor() async {
|
||||||
|
self.monitor = SDLNetworkMonitor()
|
||||||
|
for await event in self.monitor!.eventStream {
|
||||||
|
switch event {
|
||||||
|
case .changed:
|
||||||
|
// TODO 需要重新探测网络的nat类型
|
||||||
|
//self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config, logger: self.logger)
|
||||||
|
self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info)
|
||||||
|
case .unreachable:
|
||||||
|
self.logger.log("didNetworkPathUnreachable", level: .warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startDnsClient() async throws {
|
||||||
|
let remoteDnsServer = config.remoteDnsServer
|
||||||
|
let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(remoteDnsServer, port: 15353)
|
||||||
|
self.dnsClient = try await DNSClient(dnsServerAddress: dnsSocketAddress, logger: self.logger)
|
||||||
|
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLContext] dns client task cancel", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
try await self.dnsClient?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
if let packetFlow = self.dnsClient?.packetFlow {
|
||||||
|
for await packet in packetFlow {
|
||||||
|
let nePacket = NEPacket(data: packet, protocolFamily: 2)
|
||||||
|
await self.providerActor.writePackets(packets: [nePacket])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = try await group.next() {
|
||||||
|
group.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleSuperEvent(event: SDLSuperClientActor.SuperEvent) async throws {
|
||||||
|
switch event {
|
||||||
|
case .ready:
|
||||||
|
self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug)
|
||||||
|
var registerSuper = SDLRegisterSuper()
|
||||||
|
registerSuper.version = UInt32(self.config.version)
|
||||||
|
registerSuper.clientID = self.config.clientId
|
||||||
|
registerSuper.devAddr = self.devAddr
|
||||||
|
registerSuper.pubKey = self.rsaCipher.pubKey
|
||||||
|
registerSuper.token = self.config.token
|
||||||
|
registerSuper.networkCode = self.config.networkCode
|
||||||
|
registerSuper.hostname = self.config.hostname
|
||||||
|
guard let message = try await self.superClientActor?.request(type: .registerSuper, data: try registerSuper.serializedData()).get() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch message.packet {
|
||||||
|
case .registerSuperAck(let registerSuperAck):
|
||||||
|
// 需要对数据通过rsa的私钥解码
|
||||||
|
let aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey))
|
||||||
|
let upgradeType = SDLUpgradeType(rawValue: registerSuperAck.upgradeType)
|
||||||
|
|
||||||
|
self.logger.log("[SDLContext] get registerSuperAck, aes_key len: \(aesKey.count), network_id:\(registerSuperAck.devAddr.networkID)", level: .info)
|
||||||
|
self.devAddr = registerSuperAck.devAddr
|
||||||
|
|
||||||
|
if upgradeType == .force {
|
||||||
|
let forceUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress)
|
||||||
|
await self.noticeClient?.send(data: forceUpgrade)
|
||||||
|
exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器分配的tun网卡信息
|
||||||
|
do {
|
||||||
|
try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: DNSClient.Helper.dnsServer)
|
||||||
|
self.startReader()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
||||||
|
exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.aesKey = aesKey
|
||||||
|
if upgradeType == .normal {
|
||||||
|
let normalUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress)
|
||||||
|
await self.noticeClient?.send(data: normalUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .registerSuperNak(let nakPacket):
|
||||||
|
let errorMessage = nakPacket.errorMessage
|
||||||
|
guard let errorCode = SDLNAKErrorCode(rawValue: UInt8(nakPacket.errorCode)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch errorCode {
|
||||||
|
case .invalidToken, .nodeDisabled:
|
||||||
|
let alertNotice = NoticeMessage.alert(alert: errorMessage)
|
||||||
|
await self.noticeClient?.send(data: alertNotice)
|
||||||
|
exit(-1)
|
||||||
|
case .noIpAddress, .networkFault, .internalFault:
|
||||||
|
let alertNotice = NoticeMessage.alert(alert: errorMessage)
|
||||||
|
await self.noticeClient?.send(data: alertNotice)
|
||||||
|
}
|
||||||
|
self.logger.log("[SDLContext] Get a SuperNak message exit", level: .warning)
|
||||||
|
default:
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
case .event(let evt):
|
||||||
|
switch evt {
|
||||||
|
case .natChanged(let natChangedEvent):
|
||||||
|
let dstMac = natChangedEvent.mac
|
||||||
|
self.logger.log("[SDLContext] natChangedEvent, dstMac: \(dstMac)", level: .info)
|
||||||
|
await sessionManager.removeSession(dstMac: dstMac)
|
||||||
|
case .sendRegister(let sendRegisterEvent):
|
||||||
|
self.logger.log("[SDLContext] sendRegisterEvent, ip: \(sendRegisterEvent)", level: .debug)
|
||||||
|
let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp)
|
||||||
|
if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) {
|
||||||
|
// 发送register包
|
||||||
|
var register = SDLRegister()
|
||||||
|
register.networkID = self.devAddr.networkID
|
||||||
|
register.srcMac = self.devAddr.mac
|
||||||
|
register.dstMac = sendRegisterEvent.dstMac
|
||||||
|
await self.udpHoleActor?.send(type: .register, data: try register.serializedData(), remoteAddress: remoteAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .networkShutdown(let shutdownEvent):
|
||||||
|
let alertNotice = NoticeMessage.alert(alert: shutdownEvent.message)
|
||||||
|
await self.noticeClient?.send(data: alertNotice)
|
||||||
|
exit(-1)
|
||||||
|
}
|
||||||
|
case .command(let packetId, let command):
|
||||||
|
switch command {
|
||||||
|
case .changeNetwork(let changeNetworkCommand):
|
||||||
|
// 需要对数据通过rsa的私钥解码
|
||||||
|
let aesKey = try! self.rsaCipher.decode(data: Data(changeNetworkCommand.aesKey))
|
||||||
|
self.logger.log("[SDLContext] change network command get aes_key len: \(aesKey.count)", level: .info)
|
||||||
|
self.devAddr = changeNetworkCommand.devAddr
|
||||||
|
|
||||||
|
// 服务器分配的tun网卡信息
|
||||||
|
do {
|
||||||
|
try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: DNSClient.Helper.dnsServer)
|
||||||
|
self.startReader()
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
||||||
|
exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.aesKey = aesKey
|
||||||
|
|
||||||
|
var commandAck = SDLCommandAck()
|
||||||
|
commandAck.status = true
|
||||||
|
await self.superClientActor?.send(type: .commandAck, packetId: packetId, data: try commandAck.serializedData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleUDPEvent(event: SDLUDPHoleActor.UDPEvent) async throws {
|
||||||
|
switch event {
|
||||||
|
case .ready:
|
||||||
|
// 获取当前网络的类型
|
||||||
|
//self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config)
|
||||||
|
self.logger.log("[SDLContext] nat type is: \(self.natType)", level: .debug)
|
||||||
|
|
||||||
|
case .message(let remoteAddress, let message):
|
||||||
|
switch message {
|
||||||
|
case .register(let register):
|
||||||
|
self.logger.log("register packet: \(register), dev_addr: \(self.devAddr)", level: .debug)
|
||||||
|
// 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下
|
||||||
|
if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID {
|
||||||
|
// 回复ack包
|
||||||
|
var registerAck = SDLRegisterAck()
|
||||||
|
registerAck.networkID = self.devAddr.networkID
|
||||||
|
registerAck.srcMac = self.devAddr.mac
|
||||||
|
registerAck.dstMac = register.srcMac
|
||||||
|
|
||||||
|
await self.udpHoleActor?.send(type: .registerAck, data: try registerAck.serializedData(), remoteAddress: remoteAddress)
|
||||||
|
// 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址
|
||||||
|
let session = Session(dstMac: register.srcMac, natAddress: remoteAddress)
|
||||||
|
await self.sessionManager.addSession(session: session)
|
||||||
|
} else {
|
||||||
|
self.logger.log("SDLContext didReadRegister get a invalid packet, because dst_ip not matched: \(register.dstMac)", level: .warning)
|
||||||
|
}
|
||||||
|
case .registerAck(let registerAck):
|
||||||
|
// 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下
|
||||||
|
if registerAck.dstMac == self.devAddr.mac && registerAck.networkID == self.devAddr.networkID {
|
||||||
|
let session = Session(dstMac: registerAck.srcMac, natAddress: remoteAddress)
|
||||||
|
await self.sessionManager.addSession(session: session)
|
||||||
|
} else {
|
||||||
|
self.logger.log("SDLContext didReadRegisterAck get a invalid packet, because dst_mac not matched: \(registerAck.dstMac)", level: .warning)
|
||||||
|
}
|
||||||
|
case .stunReply(let stunReply):
|
||||||
|
let cookie = stunReply.cookie
|
||||||
|
if cookie == self.lastCookie {
|
||||||
|
// 记录下当前在nat上的映射信息,暂时没有用;后续会用来判断网络类型
|
||||||
|
//self.natAddress = stunReply.natAddress
|
||||||
|
self.logger.log("[SDLContext] get a stunReply: \(try! stunReply.jsonString())", level: .debug)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
case .data(let data):
|
||||||
|
let mac = LayerPacket.MacAddress(data: data.dstMac)
|
||||||
|
guard (data.dstMac == self.devAddr.mac || mac.isBroadcast() || mac.isMulticast()) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let decyptedData = try? self.aesCipher.decypt(aesKey: self.aesKey, data: Data(data.data)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let layerPacket = try LayerPacket(layerData: decyptedData)
|
||||||
|
|
||||||
|
await self.flowTracer.inc(num: decyptedData.count, type: .inbound)
|
||||||
|
// 处理arp请求
|
||||||
|
switch layerPacket.type {
|
||||||
|
case .arp:
|
||||||
|
// 判断如果收到的是arp请求
|
||||||
|
if let arpPacket = ARPPacket(data: layerPacket.data) {
|
||||||
|
if arpPacket.targetIP == self.devAddr.netAddr {
|
||||||
|
switch arpPacket.opcode {
|
||||||
|
case .request:
|
||||||
|
self.logger.log("[SDLContext] get arp request packet", level: .debug)
|
||||||
|
let response = ARPPacket.arpResponse(for: arpPacket, mac: self.devAddr.mac, ip: self.devAddr.netAddr)
|
||||||
|
await self.routeLayerPacket(dstMac: arpPacket.senderMAC, type: .arp, data: response.marshal())
|
||||||
|
case .response:
|
||||||
|
self.logger.log("[SDLContext] get arp response packet", level: .debug)
|
||||||
|
await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.logger.log("[SDLContext] get invalid arp packet: \(arpPacket), target_ip: \(SDLUtil.int32ToIp(arpPacket.targetIP)), net ip: \(SDLUtil.int32ToIp(self.devAddr.netAddr))", level: .debug)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.logger.log("[SDLContext] get invalid arp packet", level: .debug)
|
||||||
|
}
|
||||||
|
case .ipv4:
|
||||||
|
guard let ipPacket = IPPacket(layerPacket.data), ipPacket.header.destination == self.devAddr.netAddr else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let packet = NEPacket(data: ipPacket.data, protocolFamily: 2)
|
||||||
|
await self.providerActor.writePackets(packets: [packet])
|
||||||
|
default:
|
||||||
|
self.logger.log("[SDLContext] get invalid packet", level: .debug)
|
||||||
|
}
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] didReadData err: \(err)", level: .warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流量统计
|
||||||
|
// public func flowReportTask() {
|
||||||
|
// Task {
|
||||||
|
// // 每分钟汇报一次
|
||||||
|
// self.flowTracerCancel = Timer.publish(every: 60.0, on: .main, in: .common).autoconnect()
|
||||||
|
// .sink { _ in
|
||||||
|
// Task {
|
||||||
|
// let (forwardNum, p2pNum, inboundNum) = await self.flowTracer.reset()
|
||||||
|
// await self.superClient?.flowReport(forwardNum: forwardNum, p2pNum: p2pNum, inboundNum: inboundNum)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 开始读取数据, 用单独的线程处理packetFlow
|
||||||
|
private func startReader() {
|
||||||
|
// 停止之前的任务
|
||||||
|
self.readTask?.cancel()
|
||||||
|
|
||||||
|
// 开启新的任务
|
||||||
|
self.readTask = Task(priority: .high) {
|
||||||
|
repeat {
|
||||||
|
let packets = await self.providerActor.readPackets()
|
||||||
|
for packet in packets {
|
||||||
|
await self.dealPacket(data: packet)
|
||||||
|
}
|
||||||
|
} while true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理读取的每个数据包
|
||||||
|
private func dealPacket(data: Data) async {
|
||||||
|
guard let packet = IPPacket(data) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if DNSClient.Helper.isDnsRequestPacket(ipPacket: packet) {
|
||||||
|
let destIp = packet.header.destination_ip
|
||||||
|
NSLog("destIp: \(destIp), int: \(packet.header.destination)")
|
||||||
|
await self.dnsClient?.forward(ipPacket: packet)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Task.detached {
|
||||||
|
let dstIp = packet.header.destination
|
||||||
|
// 本地通讯, 目标地址是本地服务器的ip地址
|
||||||
|
if dstIp == self.devAddr.netAddr {
|
||||||
|
let nePacket = NEPacket(data: packet.data, protocolFamily: 2)
|
||||||
|
await self.providerActor.writePackets(packets: [nePacket])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找arp缓存中是否有目标mac地址
|
||||||
|
if let dstMac = await self.arpServer.query(ip: dstIp) {
|
||||||
|
await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 构造arp请求
|
||||||
|
let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||||
|
let arpReqeust = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp)
|
||||||
|
await self.routeLayerPacket(dstMac: broadcastMac, type: .arp, data: arpReqeust.marshal())
|
||||||
|
|
||||||
|
self.logger.log("[SDLContext] dstIp: \(dstIp) arp query not found", level: .debug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func routeLayerPacket(dstMac: Data, type: LayerPacket.PacketType, data: Data) async {
|
||||||
|
// 将数据封装层2层的数据包
|
||||||
|
let layerPacket = LayerPacket(dstMac: dstMac, srcMac: self.devAddr.mac, type: type, data: data)
|
||||||
|
guard let encodedPacket = try? self.aesCipher.encrypt(aesKey: self.aesKey, data: layerPacket.marshal()) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造数据包
|
||||||
|
var dataPacket = SDLData()
|
||||||
|
dataPacket.networkID = self.devAddr.networkID
|
||||||
|
dataPacket.srcMac = self.devAddr.mac
|
||||||
|
dataPacket.dstMac = dstMac
|
||||||
|
dataPacket.ttl = 255
|
||||||
|
dataPacket.data = encodedPacket
|
||||||
|
|
||||||
|
// 通过session发送到对端
|
||||||
|
if let session = await self.sessionManager.getSession(toAddress: dstMac) {
|
||||||
|
self.logger.log("[SDLContext] send packet by session: \(session)", level: .debug)
|
||||||
|
await self.udpHoleActor?.send(type: .data, data: try! dataPacket.serializedData(), remoteAddress: session.natAddress)
|
||||||
|
await self.flowTracer.inc(num: data.count, type: .p2p)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 通过super_node进行转发
|
||||||
|
let superAddress = self.config.stunSocketAddress
|
||||||
|
await self.udpHoleActor?.send(type: .data, data: try! dataPacket.serializedData(), remoteAddress: superAddress)
|
||||||
|
// 流量统计
|
||||||
|
await self.flowTracer.inc(num: data.count, type: .forward)
|
||||||
|
|
||||||
|
// 尝试打洞
|
||||||
|
let registerRequest = RegisterRequest(srcMac: self.devAddr.mac, dstMac: dstMac, networkId: self.devAddr.networkID)
|
||||||
|
self.submitRegisterRequest(request: registerRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func submitRegisterRequest(request: RegisterRequest) {
|
||||||
|
self.locker.lock()
|
||||||
|
defer {
|
||||||
|
self.locker.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
let dstMac = request.dstMac
|
||||||
|
if let publisher = self.holerPublishers[dstMac] {
|
||||||
|
publisher.send(request)
|
||||||
|
} else {
|
||||||
|
let publisher = PassthroughSubject<RegisterRequest, Never>()
|
||||||
|
publisher.throttle(for: .seconds(5), scheduler: DispatchQueue.global(), latest: true)
|
||||||
|
.sink { request in
|
||||||
|
Task {
|
||||||
|
await self.tryHole(request: request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &self.bag)
|
||||||
|
|
||||||
|
self.holerPublishers[dstMac] = publisher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tryHole(request: RegisterRequest) async {
|
||||||
|
var queryInfo = SDLQueryInfo()
|
||||||
|
queryInfo.dstMac = request.dstMac
|
||||||
|
|
||||||
|
guard let message = try? await self.superClientActor?.request(type: .queryInfo, data: try queryInfo.serializedData()).get() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch message.packet {
|
||||||
|
case .empty:
|
||||||
|
self.logger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug)
|
||||||
|
case .peerInfo(let peerInfo):
|
||||||
|
if let remoteAddress = peerInfo.v4Info.socketAddress() {
|
||||||
|
self.logger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug)
|
||||||
|
// 发送register包
|
||||||
|
var register = SDLRegister()
|
||||||
|
register.networkID = request.networkId
|
||||||
|
register.srcMac = request.srcMac
|
||||||
|
register.dstMac = request.dstMac
|
||||||
|
|
||||||
|
await self.udpHoleActor?.send(type: .register, data: try! register.serializedData(), remoteAddress: remoteAddress)
|
||||||
|
} else {
|
||||||
|
self.logger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
self.logger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.rootTask?.cancel()
|
||||||
|
self.udpHoleActor = nil
|
||||||
|
self.superClientActor = nil
|
||||||
|
self.dnsClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取mac地址
|
||||||
|
public static func getMacAddress() -> Data {
|
||||||
|
let key = "gMacAddress2"
|
||||||
|
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
if let mac = userDefaults.value(forKey: key) as? Data {
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let mac = generateMacAddress()
|
||||||
|
userDefaults.setValue(mac, forKey: key)
|
||||||
|
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机生成mac地址
|
||||||
|
private static func generateMacAddress() -> Data {
|
||||||
|
var macAddress = [UInt8](repeating: 0, count: 6)
|
||||||
|
for i in 0..<6 {
|
||||||
|
macAddress[i] = UInt8.random(in: 0...255)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Data(macAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,89 +0,0 @@
|
|||||||
//
|
|
||||||
// SDLPuncherActor.swift
|
|
||||||
// Tun
|
|
||||||
//
|
|
||||||
// Created by 安礼成 on 2026/1/7.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
actor SDLPuncherActor {
|
|
||||||
// dstMac
|
|
||||||
private var coolingDown: Set<Data> = []
|
|
||||||
private let cooldown: Duration = .seconds(5)
|
|
||||||
|
|
||||||
private var superClientActor: SDLSuperClientActor?
|
|
||||||
private var udpHoleActor: SDLUDPHoleActor?
|
|
||||||
|
|
||||||
// 处理holer
|
|
||||||
private var logger: SDLLogger
|
|
||||||
|
|
||||||
struct RegisterRequest {
|
|
||||||
let srcMac: Data
|
|
||||||
let dstMac: Data
|
|
||||||
let networkId: UInt32
|
|
||||||
}
|
|
||||||
|
|
||||||
init(logger: SDLLogger) {
|
|
||||||
self.logger = logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSuperClientActor(superClientActor: SDLSuperClientActor?) {
|
|
||||||
self.superClientActor = superClientActor
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUDPHoleActor(udpHoleActor: SDLUDPHoleActor?) {
|
|
||||||
self.udpHoleActor = udpHoleActor
|
|
||||||
}
|
|
||||||
|
|
||||||
func submitRegisterRequest(request: RegisterRequest) {
|
|
||||||
let dstMac = request.dstMac
|
|
||||||
|
|
||||||
guard !coolingDown.contains(dstMac) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发一次打洞
|
|
||||||
coolingDown.insert(dstMac)
|
|
||||||
|
|
||||||
Task {
|
|
||||||
await self.tryHole(request: request)
|
|
||||||
// 启动冷却期
|
|
||||||
try? await Task.sleep(for: .seconds(5))
|
|
||||||
self.endCooldown(for: dstMac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func endCooldown(for key: Data) {
|
|
||||||
self.coolingDown.remove(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func tryHole(request: RegisterRequest) async {
|
|
||||||
var queryInfo = SDLQueryInfo()
|
|
||||||
queryInfo.dstMac = request.dstMac
|
|
||||||
guard let message = try? await self.superClientActor?.request(type: .queryInfo, data: try queryInfo.serializedData()) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch message.packet {
|
|
||||||
case .empty:
|
|
||||||
self.logger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug)
|
|
||||||
case .peerInfo(let peerInfo):
|
|
||||||
if let remoteAddress = peerInfo.v4Info.socketAddress() {
|
|
||||||
self.logger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug)
|
|
||||||
// 发送register包
|
|
||||||
var register = SDLRegister()
|
|
||||||
register.networkID = request.networkId
|
|
||||||
register.srcMac = request.srcMac
|
|
||||||
register.dstMac = request.dstMac
|
|
||||||
|
|
||||||
await self.udpHoleActor?.send(type: .register, data: try! register.serializedData(), remoteAddress: remoteAddress)
|
|
||||||
} else {
|
|
||||||
self.logger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
self.logger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -17,8 +17,8 @@ actor SDLSuperClientActor {
|
|||||||
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
private let asyncChannel: NIOAsyncChannel<ByteBuffer,ByteBuffer>
|
private let asyncChannel: NIOAsyncChannel<ByteBuffer,ByteBuffer>
|
||||||
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: TcpMessage.self, bufferingPolicy: .unbounded)
|
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: TcpMessage.self, bufferingPolicy: .unbounded)
|
||||||
private var continuations: [UInt32:CheckedContinuation<SDLSuperInboundMessage, Error>] = [:]
|
private var callbackPromises: [UInt32:EventLoopPromise<SDLSuperInboundMessage>] = [:]
|
||||||
|
|
||||||
public let eventFlow: AsyncStream<SuperEvent>
|
public let eventFlow: AsyncStream<SuperEvent>
|
||||||
private let inboundContinuation: AsyncStream<SuperEvent>.Continuation
|
private let inboundContinuation: AsyncStream<SuperEvent>.Continuation
|
||||||
|
|
||||||
@ -34,12 +34,6 @@ actor SDLSuperClientActor {
|
|||||||
case command(UInt32, SDLCommand)
|
case command(UInt32, SDLCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SuperClientError: Error {
|
|
||||||
case timeout
|
|
||||||
case connectionClosed
|
|
||||||
case cancelled
|
|
||||||
}
|
|
||||||
|
|
||||||
init(host: String, port: Int, logger: SDLLogger) async throws {
|
init(host: String, port: Int, logger: SDLLogger) async throws {
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
@ -78,10 +72,7 @@ actor SDLSuperClientActor {
|
|||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
|
|
||||||
if let message = SDLSuperClientDecoder.decode(buffer: &packet) {
|
if let message = SDLSuperClientDecoder.decode(buffer: &packet) {
|
||||||
if !message.isPong() {
|
self.logger.log("[SDLSuperTransport] read message: \(message)", level: .debug)
|
||||||
self.logger.log("[SDLSuperClient] read message: \(message)", level: .debug)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch message.packet {
|
switch message.packet {
|
||||||
case .event(let event):
|
case .event(let event):
|
||||||
self.inboundContinuation.yield(.event(event))
|
self.inboundContinuation.yield(.event(event))
|
||||||
@ -133,9 +124,6 @@ actor SDLSuperClientActor {
|
|||||||
self.inboundContinuation.finish()
|
self.inboundContinuation.finish()
|
||||||
self.writeContinuation.finish()
|
self.writeContinuation.finish()
|
||||||
self.logger.log("[SDLSuperClient] withTaskCancellationHandler cancel")
|
self.logger.log("[SDLSuperClient] withTaskCancellationHandler cancel")
|
||||||
Task {
|
|
||||||
await self.failAllContinuations(SuperClientError.cancelled)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,17 +136,14 @@ actor SDLSuperClientActor {
|
|||||||
self.send(type: .ping, packetId: 0, data: Data())
|
self.send(type: .ping, packetId: 0, data: Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
func request(type: SDLPacketType, data: Data, timeout: Duration = .seconds(5)) async throws -> SDLSuperInboundMessage {
|
func request(type: SDLPacketType, data: Data) -> EventLoopFuture<SDLSuperInboundMessage> {
|
||||||
let packetId = idGenerator.nextId()
|
let packetId = idGenerator.nextId()
|
||||||
|
let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLSuperInboundMessage.self)
|
||||||
|
self.callbackPromises[packetId] = promise
|
||||||
|
|
||||||
return try await withCheckedThrowingContinuation { cont in
|
self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data))
|
||||||
self.continuations[packetId] = cont
|
|
||||||
self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data))
|
return promise.futureResult
|
||||||
Task {
|
|
||||||
try? await Task.sleep(for: timeout)
|
|
||||||
self.timeout(packetId: packetId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(type: SDLPacketType, packetId: UInt32, data: Data) {
|
func send(type: SDLPacketType, packetId: UInt32, data: Data) {
|
||||||
@ -167,26 +152,12 @@ actor SDLSuperClientActor {
|
|||||||
|
|
||||||
// 处理回调函数
|
// 处理回调函数
|
||||||
private func fireCallback(message: SDLSuperInboundMessage) {
|
private func fireCallback(message: SDLSuperInboundMessage) {
|
||||||
guard let cont = self.continuations.removeValue(forKey: message.msgId) else {
|
if let promise = self.callbackPromises[message.msgId] {
|
||||||
return
|
self.asyncChannel.channel.eventLoop.execute {
|
||||||
|
promise.succeed(message)
|
||||||
|
}
|
||||||
|
self.callbackPromises.removeValue(forKey: message.msgId)
|
||||||
}
|
}
|
||||||
cont.resume(returning: message)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func failAllContinuations(_ error: Error) {
|
|
||||||
let all = continuations
|
|
||||||
continuations.removeAll()
|
|
||||||
|
|
||||||
for (_, cont) in all {
|
|
||||||
cont.resume(throwing: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func timeout(packetId: UInt32) {
|
|
||||||
guard let cont = self.continuations.removeValue(forKey: packetId) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cont.resume(throwing: SuperClientError.timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@ -44,7 +44,7 @@ actor SDLTunnelProviderActor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 网络改变时需要重新配置网络信息
|
// 网络改变时需要重新配置网络信息
|
||||||
func setNetworkSettings(devAddr: SDLDevAddr, dnsServer: String) async throws -> String {
|
func setNetworkSettings(devAddr: SDLDevAddr, dnsServer: String) async throws {
|
||||||
let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen))
|
let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen))
|
||||||
let routes = [
|
let routes = [
|
||||||
Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress),
|
Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress),
|
||||||
@ -74,8 +74,6 @@ actor SDLTunnelProviderActor {
|
|||||||
networkSettings.ipv4Settings = ipv4Settings
|
networkSettings.ipv4Settings = ipv4Settings
|
||||||
// 网卡配置设置必须成功
|
// 网卡配置设置必须成功
|
||||||
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
||||||
|
|
||||||
return netAddress.ipAddress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始读取数据, 用单独的线程处理packetFlow
|
// 开始读取数据, 用单独的线程处理packetFlow
|
||||||
|
|||||||
@ -135,24 +135,24 @@ actor SDLUDPHoleActor {
|
|||||||
return self.cookieGenerator.nextId()
|
return self.cookieGenerator.nextId()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 探测tun信息
|
// // 探测tun信息
|
||||||
func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async throws -> SDLStunProbeReply {
|
// func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async throws -> SDLStunProbeReply {
|
||||||
return try await self._stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout).get()
|
// return try await self._stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout).get()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private func _stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int) -> EventLoopFuture<SDLStunProbeReply> {
|
// private func _stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int) -> EventLoopFuture<SDLStunProbeReply> {
|
||||||
let cookie = self.cookieGenerator.nextId()
|
// let cookie = self.cookieGenerator.nextId()
|
||||||
var stunProbe = SDLStunProbe()
|
// var stunProbe = SDLStunProbe()
|
||||||
stunProbe.cookie = cookie
|
// stunProbe.cookie = cookie
|
||||||
stunProbe.attr = UInt32(attr.rawValue)
|
// stunProbe.attr = UInt32(attr.rawValue)
|
||||||
self.send( type: .stunProbe, data: try! stunProbe.serializedData(), remoteAddress: remoteAddress)
|
// self.send( type: .stunProbe, data: try! stunProbe.serializedData(), remoteAddress: remoteAddress)
|
||||||
self.logger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .debug)
|
// self.logger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .debug)
|
||||||
|
//
|
||||||
let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLStunProbeReply.self)
|
// let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLStunProbeReply.self)
|
||||||
self.promises[cookie] = promise
|
// self.promises[cookie] = promise
|
||||||
|
//
|
||||||
return promise.futureResult
|
// return promise.futureResult
|
||||||
}
|
// }
|
||||||
|
|
||||||
private func trigger(probeReply: SDLStunProbeReply) {
|
private func trigger(probeReply: SDLStunProbeReply) {
|
||||||
let id = probeReply.cookie
|
let id = probeReply.cookie
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import NIOPosix
|
|||||||
|
|
||||||
// 处理和sn-server服务器之间的通讯
|
// 处理和sn-server服务器之间的通讯
|
||||||
@available(macOS 14, *)
|
@available(macOS 14, *)
|
||||||
actor SDLDNSClientActor {
|
actor DNSClient {
|
||||||
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
private let asyncChannel: NIOAsyncChannel<AddressedEnvelope<ByteBuffer>, AddressedEnvelope<ByteBuffer>>
|
private let asyncChannel: NIOAsyncChannel<AddressedEnvelope<ByteBuffer>, AddressedEnvelope<ByteBuffer>>
|
||||||
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded)
|
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded)
|
||||||
@ -101,7 +101,7 @@ actor SDLDNSClientActor {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SDLDNSClientActor {
|
extension DNSClient {
|
||||||
|
|
||||||
struct Helper {
|
struct Helper {
|
||||||
static let dnsServer: String = "100.100.100.100"
|
static let dnsServer: String = "100.100.100.100"
|
||||||
@ -16,7 +16,7 @@ import Combine
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@available(macOS 14, *)
|
@available(macOS 14, *)
|
||||||
public class SDLContext {
|
public class SDLContext: @unchecked Sendable {
|
||||||
|
|
||||||
// 路由信息
|
// 路由信息
|
||||||
struct Route {
|
struct Route {
|
||||||
@ -36,7 +36,7 @@ public class SDLContext {
|
|||||||
// nat映射的相关信息, 暂时没有用处
|
// nat映射的相关信息, 暂时没有用处
|
||||||
//var natAddress: SDLNatAddress?
|
//var natAddress: SDLNatAddress?
|
||||||
// nat的网络类型
|
// nat的网络类型
|
||||||
var natType: NatType = .blocked
|
var natType: SDLNatProber.NatType = .blocked
|
||||||
|
|
||||||
// AES加密,授权通过后,对象才会被创建
|
// AES加密,授权通过后,对象才会被创建
|
||||||
var aesCipher: AESCipher
|
var aesCipher: AESCipher
|
||||||
@ -48,16 +48,17 @@ public class SDLContext {
|
|||||||
let rsaCipher: RSACipher
|
let rsaCipher: RSACipher
|
||||||
|
|
||||||
// 依赖的变量
|
// 依赖的变量
|
||||||
var udpHoleActor: SDLUDPHoleActor?
|
var udpHole: SDLUDPHole?
|
||||||
var superClientActor: SDLSuperClientActor?
|
var superClient: SDLSuperClient?
|
||||||
var providerActor: SDLTunnelProviderActor
|
|
||||||
var puncherActor: SDLPuncherActor
|
|
||||||
// dns的client对象
|
// dns的client对象
|
||||||
var dnsClientActor: SDLDNSClientActor?
|
var dnsClient: DNSClient?
|
||||||
|
|
||||||
// 数据包读取任务
|
// 数据包读取任务
|
||||||
private var readTask: Task<(), Never>?
|
private var readTask: Task<(), Never>?
|
||||||
|
|
||||||
|
let provider: NEPacketTunnelProvider
|
||||||
|
|
||||||
private var sessionManager: SessionManager
|
private var sessionManager: SessionManager
|
||||||
private var arpServer: ArpServer
|
private var arpServer: ArpServer
|
||||||
|
|
||||||
@ -74,9 +75,21 @@ public class SDLContext {
|
|||||||
private var flowTracer = SDLFlowTracerActor()
|
private var flowTracer = SDLFlowTracerActor()
|
||||||
private var flowTracerCancel: AnyCancellable?
|
private var flowTracerCancel: AnyCancellable?
|
||||||
|
|
||||||
|
// 处理holer
|
||||||
|
private var holerPublishers: [Data:PassthroughSubject<RegisterRequest, Never>] = [:]
|
||||||
|
private var bag = Set<AnyCancellable>()
|
||||||
|
private var locker = NSLock()
|
||||||
|
|
||||||
private let logger: SDLLogger
|
private let logger: SDLLogger
|
||||||
|
|
||||||
private var rootTask: Task<Void, Error>?
|
private var rootTask: Task<Void, Error>?
|
||||||
|
|
||||||
|
struct RegisterRequest {
|
||||||
|
let srcMac: Data
|
||||||
|
let dstMac: Data
|
||||||
|
let networkId: UInt32
|
||||||
|
}
|
||||||
|
|
||||||
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher, logger: SDLLogger) {
|
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher, logger: SDLLogger) {
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
@ -89,15 +102,15 @@ public class SDLContext {
|
|||||||
devAddr.mac = Self.getMacAddress()
|
devAddr.mac = Self.getMacAddress()
|
||||||
self.devAddr = devAddr
|
self.devAddr = devAddr
|
||||||
|
|
||||||
|
self.provider = provider
|
||||||
self.sessionManager = SessionManager()
|
self.sessionManager = SessionManager()
|
||||||
self.arpServer = ArpServer(known_macs: [:])
|
self.arpServer = ArpServer(known_macs: [:])
|
||||||
self.providerActor = SDLTunnelProviderActor(provider: provider, logger: logger)
|
|
||||||
self.puncherActor = SDLPuncherActor(logger: logger)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func start() async throws {
|
public func start() async throws {
|
||||||
self.rootTask = Task {
|
self.rootTask = Task {
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
while !Task.isCancelled {
|
while !Task.isCancelled {
|
||||||
do {
|
do {
|
||||||
@ -156,8 +169,8 @@ public class SDLContext {
|
|||||||
|
|
||||||
public func stop() async {
|
public func stop() async {
|
||||||
self.rootTask?.cancel()
|
self.rootTask?.cancel()
|
||||||
self.superClientActor = nil
|
self.superClient = nil
|
||||||
self.udpHoleActor = nil
|
self.udpHole = nil
|
||||||
self.noticeClient = nil
|
self.noticeClient = nil
|
||||||
|
|
||||||
self.readTask?.cancel()
|
self.readTask?.cancel()
|
||||||
@ -170,39 +183,22 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func startUDPHole() async throws {
|
private func startUDPHole() async throws {
|
||||||
self.udpHoleActor = try await SDLUDPHoleActor(logger: self.logger)
|
self.udpHole = try await SDLUDPHole(logger: self.logger)
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
group.addTask {
|
group.addTask {
|
||||||
try await self.udpHoleActor?.start()
|
try await self.udpHole?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
while !Task.isCancelled {
|
while !Task.isCancelled {
|
||||||
try Task.checkCancellation()
|
|
||||||
try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
|
try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
|
||||||
try Task.checkCancellation()
|
self.lastCookie = await self.udpHole?.stunRequest(context: self)
|
||||||
|
|
||||||
if let udpHoleActor = self.udpHoleActor {
|
|
||||||
let cookie = await udpHoleActor.getCookieId()
|
|
||||||
var stunRequest = SDLStunRequest()
|
|
||||||
stunRequest.cookie = cookie
|
|
||||||
stunRequest.clientID = self.config.clientId
|
|
||||||
stunRequest.networkID = self.devAddr.networkID
|
|
||||||
stunRequest.ip = self.devAddr.netAddr
|
|
||||||
stunRequest.mac = self.devAddr.mac
|
|
||||||
stunRequest.natType = UInt32(self.natType.rawValue)
|
|
||||||
|
|
||||||
let remoteAddress = self.config.stunSocketAddress
|
|
||||||
await udpHoleActor.send(type: .stunRequest, data: try stunRequest.serializedData(), remoteAddress: remoteAddress)
|
|
||||||
self.lastCookie = cookie
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
if let eventFlow = self.udpHoleActor?.eventFlow {
|
if let eventFlow = self.udpHole?.eventFlow {
|
||||||
for try await event in eventFlow {
|
for try await event in eventFlow {
|
||||||
try Task.checkCancellation()
|
|
||||||
try await self.handleUDPEvent(event: event)
|
try await self.handleUDPEvent(event: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,18 +212,18 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func startSuperClient() async throws {
|
private func startSuperClient() async throws {
|
||||||
self.superClientActor = try await SDLSuperClientActor(host: self.config.superHost, port: self.config.superPort, logger: self.logger)
|
self.superClient = try await SDLSuperClient(host: self.config.superHost, port: self.config.superPort, logger: self.logger)
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
defer {
|
defer {
|
||||||
self.logger.log("[SDLContext] super client task cancel", level: .warning)
|
self.logger.log("[SDLContext] super client task cancel", level: .warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
try await self.superClientActor?.start()
|
try await self.superClient?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
if let eventFlow = self.superClientActor?.eventFlow {
|
if let eventFlow = self.superClient?.eventFlow {
|
||||||
for try await event in eventFlow {
|
for try await event in eventFlow {
|
||||||
try await self.handleSuperEvent(event: event)
|
try await self.handleSuperEvent(event: event)
|
||||||
}
|
}
|
||||||
@ -246,7 +242,7 @@ public class SDLContext {
|
|||||||
switch event {
|
switch event {
|
||||||
case .changed:
|
case .changed:
|
||||||
// 需要重新探测网络的nat类型
|
// 需要重新探测网络的nat类型
|
||||||
self.natType = await self.getNatType()
|
self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config, logger: self.logger)
|
||||||
self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info)
|
self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info)
|
||||||
case .unreachable:
|
case .unreachable:
|
||||||
self.logger.log("didNetworkPathUnreachable", level: .warning)
|
self.logger.log("didNetworkPathUnreachable", level: .warning)
|
||||||
@ -257,7 +253,7 @@ public class SDLContext {
|
|||||||
private func startDnsClient() async throws {
|
private func startDnsClient() async throws {
|
||||||
let remoteDnsServer = config.remoteDnsServer
|
let remoteDnsServer = config.remoteDnsServer
|
||||||
let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(remoteDnsServer, port: 15353)
|
let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(remoteDnsServer, port: 15353)
|
||||||
self.dnsClientActor = try await SDLDNSClientActor(dnsServerAddress: dnsSocketAddress, logger: self.logger)
|
self.dnsClient = try await DNSClient(dnsServerAddress: dnsSocketAddress, logger: self.logger)
|
||||||
|
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
defer {
|
defer {
|
||||||
@ -265,14 +261,14 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
try await self.dnsClientActor?.start()
|
try await self.dnsClient?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
if let packetFlow = self.dnsClientActor?.packetFlow {
|
if let packetFlow = self.dnsClient?.packetFlow {
|
||||||
for await packet in packetFlow {
|
for await packet in packetFlow {
|
||||||
let nePacket = NEPacket(data: packet, protocolFamily: 2)
|
let nePacket = NEPacket(data: packet, protocolFamily: 2)
|
||||||
await self.providerActor.writePackets(packets: [nePacket])
|
self.provider.packetFlow.writePacketObjects([nePacket])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,21 +280,11 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleSuperEvent(event: SDLSuperClientActor.SuperEvent) async throws {
|
private func handleSuperEvent(event: SDLSuperClient.SuperEvent) async throws {
|
||||||
switch event {
|
switch event {
|
||||||
case .ready:
|
case .ready:
|
||||||
await self.puncherActor.setSuperClientActor(superClientActor: self.superClientActor)
|
|
||||||
|
|
||||||
self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug)
|
self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug)
|
||||||
var registerSuper = SDLRegisterSuper()
|
guard let message = try await self.superClient?.registerSuper(context: self).get() else {
|
||||||
registerSuper.version = UInt32(self.config.version)
|
|
||||||
registerSuper.clientID = self.config.clientId
|
|
||||||
registerSuper.devAddr = self.devAddr
|
|
||||||
registerSuper.pubKey = self.rsaCipher.pubKey
|
|
||||||
registerSuper.token = self.config.token
|
|
||||||
registerSuper.networkCode = self.config.networkCode
|
|
||||||
registerSuper.hostname = self.config.hostname
|
|
||||||
guard let message = try await self.superClientActor?.request(type: .registerSuper, data: try registerSuper.serializedData()) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,17 +304,9 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 服务器分配的tun网卡信息
|
// 服务器分配的tun网卡信息
|
||||||
do {
|
await self.didNetworkConfigChanged(devAddr: self.devAddr, dnsServer: DNSClient.Helper.dnsServer)
|
||||||
let ipAddress = try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: SDLDNSClientActor.Helper.dnsServer)
|
|
||||||
await self.noticeClient?.send(data: NoticeMessage.ipAdress(ip: ipAddress))
|
|
||||||
|
|
||||||
self.startReader()
|
|
||||||
} catch let err {
|
|
||||||
self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
|
||||||
exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.aesKey = aesKey
|
self.aesKey = aesKey
|
||||||
|
|
||||||
if upgradeType == .normal {
|
if upgradeType == .normal {
|
||||||
let normalUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress)
|
let normalUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress)
|
||||||
await self.noticeClient?.send(data: normalUpgrade)
|
await self.noticeClient?.send(data: normalUpgrade)
|
||||||
@ -365,11 +343,7 @@ public class SDLContext {
|
|||||||
let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp)
|
let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp)
|
||||||
if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) {
|
if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) {
|
||||||
// 发送register包
|
// 发送register包
|
||||||
var register = SDLRegister()
|
await self.udpHole?.sendRegister(remoteAddress: remoteAddress, networkId: self.devAddr.networkID, srcMac: self.devAddr.mac, dst_mac: sendRegisterEvent.dstMac)
|
||||||
register.networkID = self.devAddr.networkID
|
|
||||||
register.srcMac = self.devAddr.mac
|
|
||||||
register.dstMac = sendRegisterEvent.dstMac
|
|
||||||
await self.udpHoleActor?.send(type: .register, data: try register.serializedData(), remoteAddress: remoteAddress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case .networkShutdown(let shutdownEvent):
|
case .networkShutdown(let shutdownEvent):
|
||||||
@ -386,33 +360,24 @@ public class SDLContext {
|
|||||||
self.devAddr = changeNetworkCommand.devAddr
|
self.devAddr = changeNetworkCommand.devAddr
|
||||||
|
|
||||||
// 服务器分配的tun网卡信息
|
// 服务器分配的tun网卡信息
|
||||||
do {
|
await self.didNetworkConfigChanged(devAddr: self.devAddr, dnsServer: DNSClient.Helper.dnsServer)
|
||||||
let ipAddress = try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: SDLDNSClientActor.Helper.dnsServer)
|
|
||||||
await self.noticeClient?.send(data: NoticeMessage.ipAdress(ip: ipAddress))
|
|
||||||
|
|
||||||
self.startReader()
|
|
||||||
} catch let err {
|
|
||||||
self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
|
||||||
exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.aesKey = aesKey
|
self.aesKey = aesKey
|
||||||
|
|
||||||
var commandAck = SDLCommandAck()
|
var commandAck = SDLCommandAck()
|
||||||
commandAck.status = true
|
commandAck.status = true
|
||||||
await self.superClientActor?.send(type: .commandAck, packetId: packetId, data: try commandAck.serializedData())
|
|
||||||
|
await self.superClient?.commandAck(packetId: packetId, ack: commandAck)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleUDPEvent(event: SDLUDPHoleActor.UDPEvent) async throws {
|
private func handleUDPEvent(event: SDLUDPHole.UDPEvent) async throws {
|
||||||
switch event {
|
switch event {
|
||||||
case .ready:
|
case .ready:
|
||||||
await self.puncherActor.setUDPHoleActor(udpHoleActor: self.udpHoleActor)
|
|
||||||
// 获取当前网络的类型
|
// 获取当前网络的类型
|
||||||
self.natType = await getNatType()
|
//self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config)
|
||||||
self.logger.log("[SDLContext] broadcast is: \(self.natType)", level: .debug)
|
self.logger.log("[SDLContext] nat type is: \(self.natType)", level: .debug)
|
||||||
|
|
||||||
case .message(let remoteAddress, let message):
|
case .message(let remoteAddress, let message):
|
||||||
switch message {
|
switch message {
|
||||||
@ -421,12 +386,7 @@ public class SDLContext {
|
|||||||
// 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下
|
// 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下
|
||||||
if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID {
|
if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID {
|
||||||
// 回复ack包
|
// 回复ack包
|
||||||
var registerAck = SDLRegisterAck()
|
await self.udpHole?.sendRegisterAck(context: self, remoteAddress: remoteAddress, dst_mac: register.srcMac)
|
||||||
registerAck.networkID = self.devAddr.networkID
|
|
||||||
registerAck.srcMac = self.devAddr.mac
|
|
||||||
registerAck.dstMac = register.srcMac
|
|
||||||
|
|
||||||
await self.udpHoleActor?.send(type: .registerAck, data: try registerAck.serializedData(), remoteAddress: remoteAddress)
|
|
||||||
// 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址
|
// 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址
|
||||||
let session = Session(dstMac: register.srcMac, natAddress: remoteAddress)
|
let session = Session(dstMac: register.srcMac, natAddress: remoteAddress)
|
||||||
await self.sessionManager.addSession(session: session)
|
await self.sessionManager.addSession(session: session)
|
||||||
@ -492,7 +452,7 @@ public class SDLContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let packet = NEPacket(data: ipPacket.data, protocolFamily: 2)
|
let packet = NEPacket(data: ipPacket.data, protocolFamily: 2)
|
||||||
await self.providerActor.writePackets(packets: [packet])
|
self.provider.packetFlow.writePacketObjects([packet])
|
||||||
default:
|
default:
|
||||||
self.logger.log("[SDLContext] get invalid packet", level: .debug)
|
self.logger.log("[SDLContext] get invalid packet", level: .debug)
|
||||||
}
|
}
|
||||||
@ -517,6 +477,50 @@ public class SDLContext {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 网络改变时需要重新配置网络信息
|
||||||
|
private func didNetworkConfigChanged(devAddr: SDLDevAddr, dnsServer: String) async {
|
||||||
|
let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen))
|
||||||
|
let routes = [
|
||||||
|
Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress),
|
||||||
|
Route(dstAddress: dnsServer, subnetMask: "255.255.255.255")
|
||||||
|
]
|
||||||
|
|
||||||
|
// Add code here to start the process of connecting the tunnel.
|
||||||
|
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "8.8.8.8")
|
||||||
|
networkSettings.mtu = 1460
|
||||||
|
|
||||||
|
// 设置网卡的DNS解析
|
||||||
|
|
||||||
|
let networkDomain = devAddr.networkDomain
|
||||||
|
let dnsSettings = NEDNSSettings(servers: [dnsServer])
|
||||||
|
dnsSettings.searchDomains = [networkDomain]
|
||||||
|
dnsSettings.matchDomains = [networkDomain]
|
||||||
|
dnsSettings.matchDomainsNoSearch = false
|
||||||
|
networkSettings.dnsSettings = dnsSettings
|
||||||
|
self.logger.log("[SDLContext] Tun started at network ip: \(netAddress.ipAddress), mask: \(netAddress.maskAddress)", level: .info)
|
||||||
|
|
||||||
|
let ipv4Settings = NEIPv4Settings(addresses: [netAddress.ipAddress], subnetMasks: [netAddress.maskAddress])
|
||||||
|
// 设置路由表
|
||||||
|
//NEIPv4Route.default()
|
||||||
|
ipv4Settings.includedRoutes = routes.map { route in
|
||||||
|
NEIPv4Route(destinationAddress: route.dstAddress, subnetMask: route.subnetMask)
|
||||||
|
}
|
||||||
|
networkSettings.ipv4Settings = ipv4Settings
|
||||||
|
// 网卡配置设置必须成功
|
||||||
|
do {
|
||||||
|
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
||||||
|
self.startReader()
|
||||||
|
|
||||||
|
let ipMessage = NoticeMessage.ipAdress(ip: netAddress.ipAddress)
|
||||||
|
await self.noticeClient?.send(data: ipMessage)
|
||||||
|
|
||||||
|
self.logger.log("[SDLContext] setTunnelNetworkSettings success, start read packet", level: .info)
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
||||||
|
exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 开始读取数据, 用单独的线程处理packetFlow
|
// 开始读取数据, 用单独的线程处理packetFlow
|
||||||
private func startReader() {
|
private func startReader() {
|
||||||
// 停止之前的任务
|
// 停止之前的任务
|
||||||
@ -525,9 +529,9 @@ public class SDLContext {
|
|||||||
// 开启新的任务
|
// 开启新的任务
|
||||||
self.readTask = Task(priority: .high) {
|
self.readTask = Task(priority: .high) {
|
||||||
repeat {
|
repeat {
|
||||||
let packets = await self.providerActor.readPackets()
|
let (packets, numbers) = await self.provider.packetFlow.readPackets()
|
||||||
for packet in packets {
|
for (data, number) in zip(packets, numbers) where number == 2 {
|
||||||
await self.dealPacket(data: packet)
|
await self.dealPacket(data: data)
|
||||||
}
|
}
|
||||||
} while true
|
} while true
|
||||||
}
|
}
|
||||||
@ -539,10 +543,10 @@ public class SDLContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if SDLDNSClientActor.Helper.isDnsRequestPacket(ipPacket: packet) {
|
if DNSClient.Helper.isDnsRequestPacket(ipPacket: packet) {
|
||||||
let destIp = packet.header.destination_ip
|
let destIp = packet.header.destination_ip
|
||||||
self.logger.log("[DNSQuery] destIp: \(destIp), int: \(packet.header.destination.asIpAddress())", level: .debug)
|
NSLog("destIp: \(destIp), int: \(packet.header.destination)")
|
||||||
await self.dnsClientActor?.forward(ipPacket: packet)
|
await self.dnsClient?.forward(ipPacket: packet)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
@ -550,7 +554,7 @@ public class SDLContext {
|
|||||||
// 本地通讯, 目标地址是本地服务器的ip地址
|
// 本地通讯, 目标地址是本地服务器的ip地址
|
||||||
if dstIp == self.devAddr.netAddr {
|
if dstIp == self.devAddr.netAddr {
|
||||||
let nePacket = NEPacket(data: packet.data, protocolFamily: 2)
|
let nePacket = NEPacket(data: packet.data, protocolFamily: 2)
|
||||||
await self.providerActor.writePackets(packets: [nePacket])
|
self.provider.packetFlow.writePacketObjects([nePacket])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,10 +563,12 @@ public class SDLContext {
|
|||||||
await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data)
|
await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.logger.log("[SDLContext] dstIp: \(dstIp.asIpAddress()) arp query not found, broadcast", level: .debug)
|
// 构造arp请求
|
||||||
// 构造arp广播
|
let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||||
let arpReqeust = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp)
|
let arpReqeust = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp)
|
||||||
await self.routeLayerPacket(dstMac: ARPPacket.broadcastMac , type: .arp, data: arpReqeust.marshal())
|
await self.routeLayerPacket(dstMac: broadcastMac, type: .arp, data: arpReqeust.marshal())
|
||||||
|
|
||||||
|
self.logger.log("[SDLContext] dstIp: \(dstIp) arp query not found", level: .debug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -575,43 +581,74 @@ public class SDLContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造数据包
|
// 通过session发送到对端
|
||||||
var dataPacket = SDLData()
|
if let session = await self.sessionManager.getSession(toAddress: dstMac) {
|
||||||
dataPacket.networkID = self.devAddr.networkID
|
self.logger.log("[SDLContext] send packet by session: \(session)", level: .debug)
|
||||||
dataPacket.srcMac = self.devAddr.mac
|
await self.udpHole?.sendPacket(context: self, session: session, data: encodedPacket)
|
||||||
dataPacket.dstMac = dstMac
|
|
||||||
dataPacket.ttl = 255
|
await self.flowTracer.inc(num: data.count, type: .p2p)
|
||||||
dataPacket.data = encodedPacket
|
|
||||||
|
|
||||||
let data = try! dataPacket.serializedData()
|
|
||||||
// 广播地址不要去尝试打洞
|
|
||||||
if ARPPacket.isBroadcastMac(dstMac) {
|
|
||||||
// 通过super_node进行转发
|
|
||||||
await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// 通过session发送到对端
|
// 通过super_node进行转发
|
||||||
if let session = await self.sessionManager.getSession(toAddress: dstMac) {
|
await self.udpHole?.forwardPacket(context: self, dst_mac: dstMac, data: encodedPacket)
|
||||||
self.logger.log("[SDLContext] send packet by session: \(session)", level: .debug)
|
// 流量统计
|
||||||
await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: session.natAddress)
|
await self.flowTracer.inc(num: data.count, type: .forward)
|
||||||
await self.flowTracer.inc(num: data.count, type: .p2p)
|
|
||||||
}
|
// 尝试打洞
|
||||||
else {
|
let registerRequest = RegisterRequest(srcMac: self.devAddr.mac, dstMac: dstMac, networkId: self.devAddr.networkID)
|
||||||
// 通过super_node进行转发
|
self.submitRegisterRequest(request: registerRequest)
|
||||||
await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress)
|
}
|
||||||
// 流量统计
|
}
|
||||||
await self.flowTracer.inc(num: data.count, type: .forward)
|
|
||||||
// 尝试打洞
|
private func submitRegisterRequest(request: RegisterRequest) {
|
||||||
await self.puncherActor.submitRegisterRequest(request: .init(srcMac: self.devAddr.mac, dstMac: dstMac, networkId: self.devAddr.networkID))
|
self.locker.lock()
|
||||||
|
defer {
|
||||||
|
self.locker.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
let dstMac = request.dstMac
|
||||||
|
if let publisher = self.holerPublishers[dstMac] {
|
||||||
|
publisher.send(request)
|
||||||
|
} else {
|
||||||
|
let publisher = PassthroughSubject<RegisterRequest, Never>()
|
||||||
|
publisher.throttle(for: .seconds(5), scheduler: DispatchQueue.global(), latest: true)
|
||||||
|
.sink { request in
|
||||||
|
Task {
|
||||||
|
await self.tryHole(request: request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &self.bag)
|
||||||
|
|
||||||
|
self.holerPublishers[dstMac] = publisher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tryHole(request: RegisterRequest) async {
|
||||||
|
guard let message = try? await self.superClient?.queryInfo(dst_mac: request.dstMac).get() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch message.packet {
|
||||||
|
case .empty:
|
||||||
|
self.logger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug)
|
||||||
|
case .peerInfo(let peerInfo):
|
||||||
|
if let remoteAddress = peerInfo.v4Info.socketAddress() {
|
||||||
|
self.logger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug)
|
||||||
|
// 发送register包
|
||||||
|
await self.udpHole?.sendRegister(remoteAddress: remoteAddress, networkId: request.networkId, srcMac: request.srcMac, dst_mac: request.dstMac)
|
||||||
|
} else {
|
||||||
|
self.logger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
self.logger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.rootTask?.cancel()
|
self.rootTask?.cancel()
|
||||||
self.udpHoleActor = nil
|
self.udpHole = nil
|
||||||
self.superClientActor = nil
|
self.superClient = nil
|
||||||
self.dnsClientActor = nil
|
self.dnsClient = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取mac地址
|
// 获取mac地址
|
||||||
@ -641,75 +678,3 @@ public class SDLContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 网络类型探测
|
|
||||||
extension SDLContext {
|
|
||||||
// 定义nat类型
|
|
||||||
enum NatType: UInt8, Encodable {
|
|
||||||
case blocked = 0
|
|
||||||
case noNat = 1
|
|
||||||
case fullCone = 2
|
|
||||||
case portRestricted = 3
|
|
||||||
case coneRestricted = 4
|
|
||||||
case symmetric = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前所处的网络的nat类型
|
|
||||||
func getNatType() async -> NatType {
|
|
||||||
guard let udpHole = self.udpHoleActor else {
|
|
||||||
return .blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
let addressArray = config.stunProbeSocketAddressArray
|
|
||||||
// step1: ip1:port1 <---- ip1:port1
|
|
||||||
guard let natAddress1 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .none) else {
|
|
||||||
return .blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
// 网络没有在nat下
|
|
||||||
if await natAddress1 == udpHole.localAddress {
|
|
||||||
return .noNat
|
|
||||||
}
|
|
||||||
|
|
||||||
// step2: ip2:port2 <---- ip2:port2
|
|
||||||
guard let natAddress2 = await getNatAddress(udpHole, remoteAddress: addressArray[1][1], attr: .none) else {
|
|
||||||
return .blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果natAddress2 的IP地址与上次回来的IP是不一样的,它就是对称型NAT; 这次的包也一定能发成功并收到
|
|
||||||
// 如果ip地址变了,这说明{dstIp, dstPort, srcIp, srcPort}, 其中有一个变了;则用新的ip地址
|
|
||||||
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2)", level: .debug)
|
|
||||||
if let ipAddress1 = natAddress1.ipAddress, let ipAddress2 = natAddress2.ipAddress, ipAddress1 != ipAddress2 {
|
|
||||||
return .symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
// step3: ip1:port1 <---- ip2:port2 (ip地址和port都变的情况)
|
|
||||||
// 如果能收到的,说明是完全锥形 说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。
|
|
||||||
if let natAddress3 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .peer) {
|
|
||||||
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)", level: .debug)
|
|
||||||
return .fullCone
|
|
||||||
}
|
|
||||||
|
|
||||||
// step3: ip1:port1 <---- ip1:port2 (port改变情况)
|
|
||||||
// 如果能收到的说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。
|
|
||||||
if let natAddress4 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .port) {
|
|
||||||
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)", level: .debug)
|
|
||||||
return .coneRestricted
|
|
||||||
} else {
|
|
||||||
return .portRestricted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getNatAddress(_ udpHole: SDLUDPHoleActor, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? {
|
|
||||||
let stunProbeReply = try? await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5)
|
|
||||||
return stunProbeReply?.socketAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension UInt32 {
|
|
||||||
// 转换成ip地址
|
|
||||||
func asIpAddress() -> String {
|
|
||||||
return SDLUtil.int32ToIp(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -153,14 +153,4 @@ struct SDLSuperInboundMessage {
|
|||||||
case event(SDLEvent)
|
case event(SDLEvent)
|
||||||
case command(SDLCommand)
|
case command(SDLCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPong() -> Bool {
|
|
||||||
switch self.packet {
|
|
||||||
case .pong:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
77
Tun/Punchnet/SDLNatProber.swift
Normal file
77
Tun/Punchnet/SDLNatProber.swift
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
// sdlan
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2025/7/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NIOCore
|
||||||
|
|
||||||
|
// 网络类型探测器
|
||||||
|
@available(macOS 14, *)
|
||||||
|
struct SDLNatProber {
|
||||||
|
|
||||||
|
// 定义nat类型
|
||||||
|
enum NatType: UInt8, Encodable {
|
||||||
|
case blocked = 0
|
||||||
|
case noNat = 1
|
||||||
|
case fullCone = 2
|
||||||
|
case portRestricted = 3
|
||||||
|
case coneRestricted = 4
|
||||||
|
case symmetric = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前所处的网络的nat类型
|
||||||
|
static func getNatType(udpHole: SDLUDPHole?, config: SDLConfiguration, logger: SDLLogger) async -> NatType {
|
||||||
|
guard let udpHole else {
|
||||||
|
return .blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
let addressArray = config.stunProbeSocketAddressArray
|
||||||
|
// step1: ip1:port1 <---- ip1:port1
|
||||||
|
guard let natAddress1 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .none) else {
|
||||||
|
return .blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络没有在nat下
|
||||||
|
if await natAddress1 == udpHole.localAddress {
|
||||||
|
return .noNat
|
||||||
|
}
|
||||||
|
|
||||||
|
// step2: ip2:port2 <---- ip2:port2
|
||||||
|
guard let natAddress2 = await getNatAddress(udpHole, remoteAddress: addressArray[1][1], attr: .none) else {
|
||||||
|
return .blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果natAddress2 的IP地址与上次回来的IP是不一样的,它就是对称型NAT; 这次的包也一定能发成功并收到
|
||||||
|
// 如果ip地址变了,这说明{dstIp, dstPort, srcIp, srcPort}, 其中有一个变了;则用新的ip地址
|
||||||
|
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2)", level: .debug)
|
||||||
|
if let ipAddress1 = natAddress1.ipAddress, let ipAddress2 = natAddress2.ipAddress, ipAddress1 != ipAddress2 {
|
||||||
|
return .symmetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// step3: ip1:port1 <---- ip2:port2 (ip地址和port都变的情况)
|
||||||
|
// 如果能收到的,说明是完全锥形 说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。
|
||||||
|
if let natAddress3 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .peer) {
|
||||||
|
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)", level: .debug)
|
||||||
|
return .fullCone
|
||||||
|
}
|
||||||
|
|
||||||
|
// step3: ip1:port1 <---- ip1:port2 (port改变情况)
|
||||||
|
// 如果能收到的说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。
|
||||||
|
if let natAddress4 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .port) {
|
||||||
|
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)", level: .debug)
|
||||||
|
return .coneRestricted
|
||||||
|
} else {
|
||||||
|
return .portRestricted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getNatAddress(_ udpHole: SDLUDPHole, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? {
|
||||||
|
let stunProbeReply = try? await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5)
|
||||||
|
|
||||||
|
return stunProbeReply?.socketAddress()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
330
Tun/Punchnet/SDLSuperClient.swift
Normal file
330
Tun/Punchnet/SDLSuperClient.swift
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
//
|
||||||
|
// SDLWebsocketClient.swift
|
||||||
|
// Tun
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/3/28.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NIOCore
|
||||||
|
import NIOPosix
|
||||||
|
|
||||||
|
// --MARK: 和SuperNode的客户端
|
||||||
|
@available(macOS 14, *)
|
||||||
|
actor SDLSuperClient {
|
||||||
|
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
|
private let asyncChannel: NIOAsyncChannel<ByteBuffer,ByteBuffer>
|
||||||
|
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: TcpMessage.self, bufferingPolicy: .unbounded)
|
||||||
|
private var callbackPromises: [UInt32:EventLoopPromise<SDLSuperInboundMessage>] = [:]
|
||||||
|
|
||||||
|
public let eventFlow: AsyncStream<SuperEvent>
|
||||||
|
private let inboundContinuation: AsyncStream<SuperEvent>.Continuation
|
||||||
|
|
||||||
|
// id生成器
|
||||||
|
var idGenerator = SDLIdGenerator(seed: 1)
|
||||||
|
|
||||||
|
private let logger: SDLLogger
|
||||||
|
// 发送的消息格式
|
||||||
|
struct TcpMessage {
|
||||||
|
let packetId: UInt32
|
||||||
|
let type: SDLPacketType
|
||||||
|
let data: Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义事件类型
|
||||||
|
enum SuperEvent {
|
||||||
|
case ready
|
||||||
|
case event(SDLEvent)
|
||||||
|
case command(UInt32, SDLCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(host: String, port: Int, logger: SDLLogger) async throws {
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
(self.eventFlow, self.inboundContinuation) = AsyncStream.makeStream(of: SuperEvent.self, bufferingPolicy: .unbounded)
|
||||||
|
let bootstrap = ClientBootstrap(group: self.group)
|
||||||
|
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
||||||
|
.channelInitializer { channel in
|
||||||
|
return channel.pipeline.addHandlers([
|
||||||
|
ByteToMessageHandler(FixedHeaderDecoder()),
|
||||||
|
MessageToByteHandler(FixedHeaderEncoder())
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
self.asyncChannel = try await bootstrap.connect(host: host, port: port)
|
||||||
|
.flatMapThrowing { channel in
|
||||||
|
return try NIOAsyncChannel(wrappingChannelSynchronously: channel, configuration: .init(
|
||||||
|
inboundType: ByteBuffer.self,
|
||||||
|
outboundType: ByteBuffer.self
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() async throws {
|
||||||
|
try await withTaskCancellationHandler {
|
||||||
|
try await self.asyncChannel.executeThenClose { inbound, outbound in
|
||||||
|
self.inboundContinuation.yield(.ready)
|
||||||
|
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
group.addTask {
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLSuperClient] inbound closed", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
for try await var packet in inbound {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
if let message = SDLSuperClientDecoder.decode(buffer: &packet) {
|
||||||
|
self.logger.log("[SDLSuperTransport] read message: \(message)", level: .debug)
|
||||||
|
switch message.packet {
|
||||||
|
case .event(let event):
|
||||||
|
self.inboundContinuation.yield(.event(event))
|
||||||
|
case .command(let command):
|
||||||
|
self.inboundContinuation.yield(.command(message.msgId, command))
|
||||||
|
default:
|
||||||
|
await self.fireCallback(message: message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLSuperClient] outbound closed", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
for await message in self.writeStream {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
var buffer = self.asyncChannel.channel.allocator.buffer(capacity: message.data.count + 5)
|
||||||
|
buffer.writeInteger(message.packetId, as: UInt32.self)
|
||||||
|
buffer.writeBytes([message.type.rawValue])
|
||||||
|
buffer.writeBytes(message.data)
|
||||||
|
try await outbound.write(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --MARK: 心跳机制
|
||||||
|
group.addTask {
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLSuperClient] ping task closed", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
while true {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
await self.ping()
|
||||||
|
try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迭代等待所有任务的退出, 第一个异常会被抛出
|
||||||
|
if let _ = try await group.next() {
|
||||||
|
group.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} onCancel: {
|
||||||
|
self.inboundContinuation.finish()
|
||||||
|
self.writeContinuation.finish()
|
||||||
|
self.logger.log("[SDLSuperClient] withTaskCancellationHandler cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- MARK: apis
|
||||||
|
|
||||||
|
func commandAck(packetId: UInt32, ack: SDLCommandAck) {
|
||||||
|
guard let data = try? ack.serializedData() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send(type: .commandAck, packetId: packetId, data: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerSuper(context ctx: SDLContext) throws -> EventLoopFuture<SDLSuperInboundMessage> {
|
||||||
|
var registerSuper = SDLRegisterSuper()
|
||||||
|
registerSuper.version = UInt32(ctx.config.version)
|
||||||
|
registerSuper.clientID = ctx.config.clientId
|
||||||
|
registerSuper.devAddr = ctx.devAddr
|
||||||
|
registerSuper.pubKey = ctx.rsaCipher.pubKey
|
||||||
|
registerSuper.token = ctx.config.token
|
||||||
|
registerSuper.networkCode = ctx.config.networkCode
|
||||||
|
registerSuper.hostname = ctx.config.hostname
|
||||||
|
|
||||||
|
NSLog("[SuperClient] register super request: \(registerSuper)")
|
||||||
|
|
||||||
|
let data = try! registerSuper.serializedData()
|
||||||
|
|
||||||
|
return self.write(type: .registerSuper, data: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询目标服务器的相关信息
|
||||||
|
func queryInfo(dst_mac: Data) async throws -> EventLoopFuture<SDLSuperInboundMessage> {
|
||||||
|
var queryInfo = SDLQueryInfo()
|
||||||
|
queryInfo.dstMac = dst_mac
|
||||||
|
|
||||||
|
return self.write(type: .queryInfo, data: try! queryInfo.serializedData())
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregister(context ctx: SDLContext) throws {
|
||||||
|
self.send(type: .unregisterSuper, packetId: 0, data: Data())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ping() {
|
||||||
|
self.send(type: .ping, packetId: 0, data: Data())
|
||||||
|
}
|
||||||
|
|
||||||
|
func flowReport(forwardNum: UInt32, p2pNum: UInt32, inboundNum: UInt32) {
|
||||||
|
var flow = SDLFlows()
|
||||||
|
flow.forwardNum = forwardNum
|
||||||
|
flow.p2PNum = p2pNum
|
||||||
|
flow.inboundNum = inboundNum
|
||||||
|
|
||||||
|
self.send(type: .flowTracer, packetId: 0, data: try! flow.serializedData())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func write(type: SDLPacketType, data: Data) -> EventLoopFuture<SDLSuperInboundMessage> {
|
||||||
|
let packetId = idGenerator.nextId()
|
||||||
|
let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLSuperInboundMessage.self)
|
||||||
|
self.callbackPromises[packetId] = promise
|
||||||
|
|
||||||
|
self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data))
|
||||||
|
|
||||||
|
return promise.futureResult
|
||||||
|
}
|
||||||
|
|
||||||
|
private func send(type: SDLPacketType, packetId: UInt32, data: Data) {
|
||||||
|
self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理回调函数
|
||||||
|
private func fireCallback(message: SDLSuperInboundMessage) {
|
||||||
|
if let promise = self.callbackPromises[message.msgId] {
|
||||||
|
self.asyncChannel.channel.eventLoop.execute {
|
||||||
|
promise.succeed(message)
|
||||||
|
}
|
||||||
|
self.callbackPromises.removeValue(forKey: message.msgId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
try! group.syncShutdownGracefully()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --MARK: 编解码器
|
||||||
|
private struct SDLSuperClientDecoder {
|
||||||
|
// 消息格式为: <<MsgId:32, Type:8, Body/binary>>
|
||||||
|
static func decode(buffer: inout ByteBuffer) -> SDLSuperInboundMessage? {
|
||||||
|
guard let msgId = buffer.readInteger(as: UInt32.self),
|
||||||
|
let type = buffer.readInteger(as: UInt8.self),
|
||||||
|
let messageType = SDLPacketType(rawValue: type) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch messageType {
|
||||||
|
case .empty:
|
||||||
|
return .init(msgId: msgId, packet: .empty)
|
||||||
|
case .registerSuperAck:
|
||||||
|
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
|
||||||
|
let registerSuperAck = try? SDLRegisterSuperAck(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(msgId: msgId, packet: .registerSuperAck(registerSuperAck))
|
||||||
|
|
||||||
|
case .registerSuperNak:
|
||||||
|
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
|
||||||
|
let registerSuperNak = try? SDLRegisterSuperNak(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(msgId: msgId, packet: .registerSuperNak(registerSuperNak))
|
||||||
|
|
||||||
|
case .peerInfo:
|
||||||
|
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
|
||||||
|
let peerInfo = try? SDLPeerInfo(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(msgId: msgId, packet: .peerInfo(peerInfo))
|
||||||
|
case .pong:
|
||||||
|
return .init(msgId: msgId, packet: .pong)
|
||||||
|
|
||||||
|
case .command:
|
||||||
|
guard let commandVal = buffer.readInteger(as: UInt8.self),
|
||||||
|
let command = SDLCommandType(rawValue: commandVal),
|
||||||
|
let bytes = buffer.readBytes(length: buffer.readableBytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case .changeNetwork:
|
||||||
|
guard let changeNetworkCommand = try? SDLChangeNetworkCommand(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(msgId: msgId, packet: .command(.changeNetwork(changeNetworkCommand)))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .event:
|
||||||
|
guard let eventVal = buffer.readInteger(as: UInt8.self),
|
||||||
|
let event = SDLEventType(rawValue: eventVal),
|
||||||
|
let bytes = buffer.readBytes(length: buffer.readableBytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event {
|
||||||
|
case .natChanged:
|
||||||
|
guard let natChangedEvent = try? SDLNatChangedEvent(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(msgId: msgId, packet: .event(.natChanged(natChangedEvent)))
|
||||||
|
case .sendRegister:
|
||||||
|
guard let sendRegisterEvent = try? SDLSendRegisterEvent(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(msgId: msgId, packet: .event(.sendRegister(sendRegisterEvent)))
|
||||||
|
case .networkShutdown:
|
||||||
|
guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedBytes: bytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(msgId: msgId, packet: .event(.networkShutdown(networkShutdownEvent)))
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class FixedHeaderEncoder: MessageToByteEncoder, @unchecked Sendable {
|
||||||
|
typealias InboundIn = ByteBuffer
|
||||||
|
typealias InboundOut = ByteBuffer
|
||||||
|
|
||||||
|
func encode(data: ByteBuffer, out: inout ByteBuffer) throws {
|
||||||
|
let len = data.readableBytes
|
||||||
|
out.writeInteger(UInt16(len))
|
||||||
|
out.writeBytes(data.readableBytesView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class FixedHeaderDecoder: ByteToMessageDecoder, @unchecked Sendable {
|
||||||
|
typealias InboundIn = ByteBuffer
|
||||||
|
typealias InboundOut = ByteBuffer
|
||||||
|
|
||||||
|
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
|
||||||
|
guard let len = buffer.getInteger(at: buffer.readerIndex, endianness: .big, as: UInt16.self) else {
|
||||||
|
return .needMoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.readableBytes >= len + 2 {
|
||||||
|
buffer.moveReaderIndex(forwardBy: 2)
|
||||||
|
if let bytes = buffer.readBytes(length: Int(len)) {
|
||||||
|
context.fireChannelRead(self.wrapInboundOut(ByteBuffer(bytes: bytes)))
|
||||||
|
}
|
||||||
|
return .continue
|
||||||
|
} else {
|
||||||
|
return .needMoreData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
286
Tun/Punchnet/SDLUDPHole.swift
Normal file
286
Tun/Punchnet/SDLUDPHole.swift
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
//
|
||||||
|
// SDLanServer.swift
|
||||||
|
// Tun
|
||||||
|
//
|
||||||
|
// Created by 安礼成 on 2024/1/31.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NIOCore
|
||||||
|
import NIOPosix
|
||||||
|
|
||||||
|
// 处理和sn-server服务器之间的通讯
|
||||||
|
@available(macOS 14, *)
|
||||||
|
actor SDLUDPHole {
|
||||||
|
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
|
private let asyncChannel: NIOAsyncChannel<AddressedEnvelope<ByteBuffer>, AddressedEnvelope<ByteBuffer>>
|
||||||
|
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: UDPMessage.self, bufferingPolicy: .unbounded)
|
||||||
|
|
||||||
|
private var cookieGenerator = SDLIdGenerator(seed: 1)
|
||||||
|
private var promises: [UInt32:EventLoopPromise<SDLStunProbeReply>] = [:]
|
||||||
|
public var localAddress: SocketAddress?
|
||||||
|
|
||||||
|
public let eventFlow: AsyncStream<UDPEvent>
|
||||||
|
private let eventContinuation: AsyncStream<UDPEvent>.Continuation
|
||||||
|
|
||||||
|
private let logger: SDLLogger
|
||||||
|
|
||||||
|
struct UDPMessage {
|
||||||
|
let remoteAddress: SocketAddress
|
||||||
|
let type: SDLPacketType
|
||||||
|
let data: Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义事件类型
|
||||||
|
enum UDPEvent {
|
||||||
|
case ready
|
||||||
|
case message(SocketAddress, SDLHoleInboundMessage)
|
||||||
|
case data(SDLData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动函数
|
||||||
|
init(logger: SDLLogger) async throws {
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
(self.eventFlow, self.eventContinuation) = AsyncStream.makeStream(of: UDPEvent.self, bufferingPolicy: .unbounded)
|
||||||
|
|
||||||
|
let bootstrap = DatagramBootstrap(group: group)
|
||||||
|
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
||||||
|
|
||||||
|
self.asyncChannel = try await bootstrap.bind(host: "0.0.0.0", port: 0)
|
||||||
|
.flatMapThrowing { channel in
|
||||||
|
return try NIOAsyncChannel(wrappingChannelSynchronously: channel, configuration: .init(
|
||||||
|
inboundType: AddressedEnvelope<ByteBuffer>.self,
|
||||||
|
outboundType: AddressedEnvelope<ByteBuffer>.self
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.get()
|
||||||
|
|
||||||
|
self.localAddress = self.asyncChannel.channel.localAddress
|
||||||
|
self.logger.log("[UDPHole] started and listening on: \(self.localAddress!)", level: .debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() async throws {
|
||||||
|
try await withTaskCancellationHandler {
|
||||||
|
try await self.asyncChannel.executeThenClose {inbound, outbound in
|
||||||
|
self.eventContinuation.yield(.ready)
|
||||||
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||||
|
group.addTask {
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLUDPHole] inbound closed", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
for try await envelope in inbound {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
var buffer = envelope.data
|
||||||
|
let remoteAddress = envelope.remoteAddress
|
||||||
|
do {
|
||||||
|
if let message = try Self.decode(buffer: &buffer) {
|
||||||
|
switch message {
|
||||||
|
case .data(let data):
|
||||||
|
self.logger.log("[SDLUDPHole] read data: \(data.format()), from: \(remoteAddress)", level: .debug)
|
||||||
|
self.eventContinuation.yield(.data(data))
|
||||||
|
case .stunProbeReply(let probeReply):
|
||||||
|
// 执行并移除回调
|
||||||
|
await self.trigger(probeReply: probeReply)
|
||||||
|
default:
|
||||||
|
self.eventContinuation.yield(.message(remoteAddress, message))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.logger.log("[SDLUDPHole] decode message, get null", level: .warning)
|
||||||
|
}
|
||||||
|
} catch let err {
|
||||||
|
self.logger.log("[SDLUDPHole] decode message, get error: \(err)", level: .warning)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.addTask {
|
||||||
|
defer {
|
||||||
|
self.logger.log("[SDLUDPHole] outbound closed", level: .warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
for await message in self.writeStream {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
var buffer = self.asyncChannel.channel.allocator.buffer(capacity: message.data.count + 1)
|
||||||
|
buffer.writeBytes([message.type.rawValue])
|
||||||
|
buffer.writeBytes(message.data)
|
||||||
|
|
||||||
|
let envelope = AddressedEnvelope<ByteBuffer>(remoteAddress: message.remoteAddress, data: buffer)
|
||||||
|
try await outbound.write(envelope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = try await group.next() {
|
||||||
|
group.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} onCancel: {
|
||||||
|
self.writeContinuation.finish()
|
||||||
|
self.eventContinuation.finish()
|
||||||
|
self.logger.log("[SDLUDPHole] withTaskCancellationHandler cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: super_node apis
|
||||||
|
|
||||||
|
func stunRequest(context ctx: SDLContext) -> UInt32? {
|
||||||
|
guard ctx.devAddr.networkID > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = self.cookieGenerator.nextId()
|
||||||
|
let remoteAddress = ctx.config.stunSocketAddress
|
||||||
|
|
||||||
|
var stunRequest = SDLStunRequest()
|
||||||
|
stunRequest.cookie = cookie
|
||||||
|
stunRequest.clientID = ctx.config.clientId
|
||||||
|
stunRequest.networkID = ctx.devAddr.networkID
|
||||||
|
stunRequest.ip = ctx.devAddr.netAddr
|
||||||
|
stunRequest.mac = ctx.devAddr.mac
|
||||||
|
stunRequest.natType = UInt32(ctx.natType.rawValue)
|
||||||
|
|
||||||
|
self.logger.log("[SDLUDPHole] stunRequest: \(remoteAddress), host: \(ctx.config.stunServers[0].host):\(ctx.config.stunServers[0].ports[0]), network_id: \(ctx.devAddr.networkID)", level: .debug)
|
||||||
|
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .stunRequest, data: try! stunRequest.serializedData())
|
||||||
|
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
// 探测tun信息
|
||||||
|
func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async throws -> SDLStunProbeReply {
|
||||||
|
return try await self._stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int) -> EventLoopFuture<SDLStunProbeReply> {
|
||||||
|
let cookie = self.cookieGenerator.nextId()
|
||||||
|
var stunProbe = SDLStunProbe()
|
||||||
|
stunProbe.cookie = cookie
|
||||||
|
stunProbe.attr = UInt32(attr.rawValue)
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .stunProbe, data: try! stunProbe.serializedData())
|
||||||
|
self.logger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .debug)
|
||||||
|
|
||||||
|
let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLStunProbeReply.self)
|
||||||
|
self.promises[cookie] = promise
|
||||||
|
|
||||||
|
return promise.futureResult
|
||||||
|
}
|
||||||
|
|
||||||
|
private func trigger(probeReply: SDLStunProbeReply) {
|
||||||
|
let id = probeReply.cookie
|
||||||
|
// 执行并移除回调
|
||||||
|
if let promise = self.promises[id] {
|
||||||
|
self.asyncChannel.channel.eventLoop.execute {
|
||||||
|
promise.succeed(probeReply)
|
||||||
|
}
|
||||||
|
self.promises.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: client-client apis
|
||||||
|
|
||||||
|
// 发送数据包到其他session
|
||||||
|
func sendPacket(context ctx: SDLContext, session: Session, data: Data) {
|
||||||
|
let remoteAddress = session.natAddress
|
||||||
|
|
||||||
|
var dataPacket = SDLData()
|
||||||
|
dataPacket.networkID = ctx.devAddr.networkID
|
||||||
|
dataPacket.srcMac = ctx.devAddr.mac
|
||||||
|
dataPacket.dstMac = session.dstMac
|
||||||
|
dataPacket.ttl = 255
|
||||||
|
dataPacket.data = data
|
||||||
|
if let packet = try? dataPacket.serializedData() {
|
||||||
|
self.logger.log("[SDLUDPHole] sendPacket: \(remoteAddress), count: \(packet.count)", level: .debug)
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .data, data: packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过sn服务器转发数据包, data已经是加密过后的数据
|
||||||
|
func forwardPacket(context ctx: SDLContext, dst_mac: Data, data: Data) {
|
||||||
|
let remoteAddress = ctx.config.stunSocketAddress
|
||||||
|
|
||||||
|
var dataPacket = SDLData()
|
||||||
|
dataPacket.networkID = ctx.devAddr.networkID
|
||||||
|
dataPacket.srcMac = ctx.devAddr.mac
|
||||||
|
dataPacket.dstMac = dst_mac
|
||||||
|
dataPacket.ttl = 255
|
||||||
|
dataPacket.data = data
|
||||||
|
|
||||||
|
if let packet = try? dataPacket.serializedData() {
|
||||||
|
self.logger.log("[SDLContext] forward packet, remoteAddress: \(remoteAddress), data size: \(packet.count)", level: .debug)
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .data, data: packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送register包
|
||||||
|
func sendRegister(remoteAddress: SocketAddress, networkId: UInt32, srcMac: Data, dst_mac: Data) {
|
||||||
|
var register = SDLRegister()
|
||||||
|
register.networkID = networkId
|
||||||
|
register.srcMac = srcMac
|
||||||
|
register.dstMac = dst_mac
|
||||||
|
|
||||||
|
if let packet = try? register.serializedData() {
|
||||||
|
self.logger.log("[SDLUDPHole] SendRegister: \(remoteAddress), src_mac: \(LayerPacket.MacAddress.description(data: srcMac)), dst_mac: \(LayerPacket.MacAddress.description(data: dst_mac))", level: .debug)
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .register, data: packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复registerAck
|
||||||
|
func sendRegisterAck(context ctx: SDLContext, remoteAddress: SocketAddress, dst_mac: Data) {
|
||||||
|
var registerAck = SDLRegisterAck()
|
||||||
|
registerAck.networkID = ctx.devAddr.networkID
|
||||||
|
registerAck.srcMac = ctx.devAddr.mac
|
||||||
|
registerAck.dstMac = dst_mac
|
||||||
|
|
||||||
|
if let packet = try? registerAck.serializedData() {
|
||||||
|
self.logger.log("[SDLUDPHole] SendRegisterAck: \(remoteAddress), \(registerAck)", level: .debug)
|
||||||
|
self.send(remoteAddress: remoteAddress, type: .registerAck, data: packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理写入逻辑
|
||||||
|
private func send(remoteAddress: SocketAddress, type: SDLPacketType, data: Data) {
|
||||||
|
let message = UDPMessage(remoteAddress: remoteAddress, type: type, data: data)
|
||||||
|
self.writeContinuation.yield(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--MARK: 编解码器
|
||||||
|
private static func decode(buffer: inout ByteBuffer) throws -> SDLHoleInboundMessage? {
|
||||||
|
guard let type = buffer.readInteger(as: UInt8.self),
|
||||||
|
let packetType = SDLPacketType(rawValue: type),
|
||||||
|
let bytes = buffer.readBytes(length: buffer.readableBytes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packetType {
|
||||||
|
case .data:
|
||||||
|
let dataPacket = try SDLData(serializedBytes: bytes)
|
||||||
|
return .data(dataPacket)
|
||||||
|
case .register:
|
||||||
|
let registerPacket = try SDLRegister(serializedBytes: bytes)
|
||||||
|
return .register(registerPacket)
|
||||||
|
case .registerAck:
|
||||||
|
let registerAck = try SDLRegisterAck(serializedBytes: bytes)
|
||||||
|
return .registerAck(registerAck)
|
||||||
|
case .stunReply:
|
||||||
|
let stunReply = try SDLStunReply(serializedBytes: bytes)
|
||||||
|
return .stunReply(stunReply)
|
||||||
|
case .stunProbeReply:
|
||||||
|
let stunProbeReply = try SDLStunProbeReply(serializedBytes: bytes)
|
||||||
|
return .stunProbeReply(stunProbeReply)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
try? self.group.syncShutdownGracefully()
|
||||||
|
self.writeContinuation.finish()
|
||||||
|
self.eventContinuation.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
896
punchnet.xcodeproj/project.pbxproj
Normal file
896
punchnet.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,896 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 77;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
C8A77F2A2DD1E77B00195617 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A77F292DD1E77B00195617 /* NetworkExtension.framework */; };
|
||||||
|
C8A77F322DD1E77B00195617 /* Tun.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C8A77F272DD1E77B00195617 /* Tun.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
C8A77F792DD1E93900195617 /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F782DD1E93900195617 /* NIO */; };
|
||||||
|
C8A77F7B2DD1E93900195617 /* NIOConcurrencyHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F7A2DD1E93900195617 /* NIOConcurrencyHelpers */; };
|
||||||
|
C8A77F7D2DD1E93900195617 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F7C2DD1E93900195617 /* NIOCore */; };
|
||||||
|
C8A77F7F2DD1E93900195617 /* NIOEmbedded in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F7E2DD1E93900195617 /* NIOEmbedded */; };
|
||||||
|
C8A77F812DD1E93900195617 /* NIOFoundationCompat in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F802DD1E93900195617 /* NIOFoundationCompat */; };
|
||||||
|
C8A77F882DD1EA0200195617 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F872DD1EA0200195617 /* SwiftProtobuf */; };
|
||||||
|
C8A77F8A2DD1EA0200195617 /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F892DD1EA0200195617 /* SwiftProtobufPluginLibrary */; };
|
||||||
|
C8AA72BB2E5C49E000E4C4E9 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */; };
|
||||||
|
C8AA72BD2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */; };
|
||||||
|
C8AA72C02E5C4A3100E4C4E9 /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BF2E5C4A3100E4C4E9 /* NIO */; };
|
||||||
|
C8AA72C22E5C4A3100E4C4E9 /* NIOConcurrencyHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */; };
|
||||||
|
C8AA72C42E5C4A3100E4C4E9 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C32E5C4A3100E4C4E9 /* NIOCore */; };
|
||||||
|
C8AA72C62E5C4A3100E4C4E9 /* NIOEmbedded in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */; };
|
||||||
|
C8AA72C82E5C4A3100E4C4E9 /* NIOFoundationCompat in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
C8A77F072DD1E6D100195617 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = C8A77EEB2DD1E6D000195617 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = C8A77EF22DD1E6D000195617;
|
||||||
|
remoteInfo = punchnet;
|
||||||
|
};
|
||||||
|
C8A77F112DD1E6D100195617 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = C8A77EEB2DD1E6D000195617 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = C8A77EF22DD1E6D000195617;
|
||||||
|
remoteInfo = punchnet;
|
||||||
|
};
|
||||||
|
C8A77F302DD1E77B00195617 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = C8A77EEB2DD1E6D000195617 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = C8A77F262DD1E77B00195617;
|
||||||
|
remoteInfo = Tun;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
C8A77F372DD1E77B00195617 /* Embed Foundation Extensions */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 13;
|
||||||
|
files = (
|
||||||
|
C8A77F322DD1E77B00195617 /* Tun.appex in Embed Foundation Extensions */,
|
||||||
|
);
|
||||||
|
name = "Embed Foundation Extensions";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
C8A77EF32DD1E6D000195617 /* punchnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = punchnet.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
C8A77F062DD1E6D100195617 /* punchnetTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = punchnetTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
C8A77F102DD1E6D100195617 /* punchnetUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = punchnetUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
C8A77F272DD1E77B00195617 /* Tun.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Tun.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
C8A77F292DD1E77B00195617 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
C8A77F332DD1E77B00195617 /* Exceptions for "Tun" folder in "Tun" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = C8A77F262DD1E77B00195617 /* Tun */;
|
||||||
|
};
|
||||||
|
C8A77F8C2DD1EA7900195617 /* Exceptions for "punchnet" folder in "Tun" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Core/NoticeMessage.swift,
|
||||||
|
);
|
||||||
|
target = C8A77F262DD1E77B00195617 /* Tun */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
C8A77EF52DD1E6D000195617 /* punchnet */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
C8A77F8C2DD1EA7900195617 /* Exceptions for "punchnet" folder in "Tun" target */,
|
||||||
|
);
|
||||||
|
path = punchnet;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C8A77F092DD1E6D100195617 /* punchnetTests */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = punchnetTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C8A77F132DD1E6D100195617 /* punchnetUITests */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = punchnetUITests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C8A77F2B2DD1E77B00195617 /* Tun */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
C8A77F332DD1E77B00195617 /* Exceptions for "Tun" folder in "Tun" target */,
|
||||||
|
);
|
||||||
|
path = Tun;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
C8A77EF02DD1E6D000195617 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
C8A77F7D2DD1E93900195617 /* NIOCore in Frameworks */,
|
||||||
|
C8A77F812DD1E93900195617 /* NIOFoundationCompat in Frameworks */,
|
||||||
|
C8A77F792DD1E93900195617 /* NIO in Frameworks */,
|
||||||
|
C8A77F7B2DD1E93900195617 /* NIOConcurrencyHelpers in Frameworks */,
|
||||||
|
C8A77F7F2DD1E93900195617 /* NIOEmbedded in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F032DD1E6D100195617 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F0D2DD1E6D100195617 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F242DD1E77B00195617 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
C8A77F2A2DD1E77B00195617 /* NetworkExtension.framework in Frameworks */,
|
||||||
|
C8A77F8A2DD1EA0200195617 /* SwiftProtobufPluginLibrary in Frameworks */,
|
||||||
|
C8AA72BD2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary in Frameworks */,
|
||||||
|
C8AA72C62E5C4A3100E4C4E9 /* NIOEmbedded in Frameworks */,
|
||||||
|
C8AA72C02E5C4A3100E4C4E9 /* NIO in Frameworks */,
|
||||||
|
C8AA72C22E5C4A3100E4C4E9 /* NIOConcurrencyHelpers in Frameworks */,
|
||||||
|
C8AA72C82E5C4A3100E4C4E9 /* NIOFoundationCompat in Frameworks */,
|
||||||
|
C8A77F882DD1EA0200195617 /* SwiftProtobuf in Frameworks */,
|
||||||
|
C8AA72C42E5C4A3100E4C4E9 /* NIOCore in Frameworks */,
|
||||||
|
C8AA72BB2E5C49E000E4C4E9 /* SwiftProtobuf in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
C8A77EEA2DD1E6D000195617 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C8A77EF52DD1E6D000195617 /* punchnet */,
|
||||||
|
C8A77F092DD1E6D100195617 /* punchnetTests */,
|
||||||
|
C8A77F132DD1E6D100195617 /* punchnetUITests */,
|
||||||
|
C8A77F2B2DD1E77B00195617 /* Tun */,
|
||||||
|
C8A77F282DD1E77B00195617 /* Frameworks */,
|
||||||
|
C8A77EF42DD1E6D000195617 /* Products */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C8A77EF42DD1E6D000195617 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C8A77EF32DD1E6D000195617 /* punchnet.app */,
|
||||||
|
C8A77F062DD1E6D100195617 /* punchnetTests.xctest */,
|
||||||
|
C8A77F102DD1E6D100195617 /* punchnetUITests.xctest */,
|
||||||
|
C8A77F272DD1E77B00195617 /* Tun.appex */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C8A77F282DD1E77B00195617 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C8A77F292DD1E77B00195617 /* NetworkExtension.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
C8A77EF22DD1E6D000195617 /* punchnet */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = C8A77F1A2DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnet" */;
|
||||||
|
buildPhases = (
|
||||||
|
C8A77EEF2DD1E6D000195617 /* Sources */,
|
||||||
|
C8A77EF02DD1E6D000195617 /* Frameworks */,
|
||||||
|
C8A77EF12DD1E6D000195617 /* Resources */,
|
||||||
|
C8A77F372DD1E77B00195617 /* Embed Foundation Extensions */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
C8A77F312DD1E77B00195617 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
C8A77EF52DD1E6D000195617 /* punchnet */,
|
||||||
|
);
|
||||||
|
name = punchnet;
|
||||||
|
packageProductDependencies = (
|
||||||
|
C8A77F782DD1E93900195617 /* NIO */,
|
||||||
|
C8A77F7A2DD1E93900195617 /* NIOConcurrencyHelpers */,
|
||||||
|
C8A77F7C2DD1E93900195617 /* NIOCore */,
|
||||||
|
C8A77F7E2DD1E93900195617 /* NIOEmbedded */,
|
||||||
|
C8A77F802DD1E93900195617 /* NIOFoundationCompat */,
|
||||||
|
);
|
||||||
|
productName = punchnet;
|
||||||
|
productReference = C8A77EF32DD1E6D000195617 /* punchnet.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
C8A77F052DD1E6D100195617 /* punchnetTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = C8A77F1D2DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnetTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
C8A77F022DD1E6D100195617 /* Sources */,
|
||||||
|
C8A77F032DD1E6D100195617 /* Frameworks */,
|
||||||
|
C8A77F042DD1E6D100195617 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
C8A77F082DD1E6D100195617 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
C8A77F092DD1E6D100195617 /* punchnetTests */,
|
||||||
|
);
|
||||||
|
name = punchnetTests;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = punchnetTests;
|
||||||
|
productReference = C8A77F062DD1E6D100195617 /* punchnetTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
C8A77F0F2DD1E6D100195617 /* punchnetUITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = C8A77F202DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnetUITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
C8A77F0C2DD1E6D100195617 /* Sources */,
|
||||||
|
C8A77F0D2DD1E6D100195617 /* Frameworks */,
|
||||||
|
C8A77F0E2DD1E6D100195617 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
C8A77F122DD1E6D100195617 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
C8A77F132DD1E6D100195617 /* punchnetUITests */,
|
||||||
|
);
|
||||||
|
name = punchnetUITests;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = punchnetUITests;
|
||||||
|
productReference = C8A77F102DD1E6D100195617 /* punchnetUITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
|
};
|
||||||
|
C8A77F262DD1E77B00195617 /* Tun */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = C8A77F342DD1E77B00195617 /* Build configuration list for PBXNativeTarget "Tun" */;
|
||||||
|
buildPhases = (
|
||||||
|
C8A77F232DD1E77B00195617 /* Sources */,
|
||||||
|
C8A77F242DD1E77B00195617 /* Frameworks */,
|
||||||
|
C8A77F252DD1E77B00195617 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
C8A77F2B2DD1E77B00195617 /* Tun */,
|
||||||
|
);
|
||||||
|
name = Tun;
|
||||||
|
packageProductDependencies = (
|
||||||
|
C8A77F872DD1EA0200195617 /* SwiftProtobuf */,
|
||||||
|
C8A77F892DD1EA0200195617 /* SwiftProtobufPluginLibrary */,
|
||||||
|
C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */,
|
||||||
|
C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */,
|
||||||
|
C8AA72BF2E5C4A3100E4C4E9 /* NIO */,
|
||||||
|
C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */,
|
||||||
|
C8AA72C32E5C4A3100E4C4E9 /* NIOCore */,
|
||||||
|
C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */,
|
||||||
|
C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */,
|
||||||
|
);
|
||||||
|
productName = Tun;
|
||||||
|
productReference = C8A77F272DD1E77B00195617 /* Tun.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
C8A77EEB2DD1E6D000195617 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = 1;
|
||||||
|
LastSwiftUpdateCheck = 1620;
|
||||||
|
LastUpgradeCheck = 1620;
|
||||||
|
TargetAttributes = {
|
||||||
|
C8A77EF22DD1E6D000195617 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
|
C8A77F052DD1E6D100195617 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
TestTargetID = C8A77EF22DD1E6D000195617;
|
||||||
|
};
|
||||||
|
C8A77F0F2DD1E6D100195617 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
TestTargetID = C8A77EF22DD1E6D000195617;
|
||||||
|
};
|
||||||
|
C8A77F262DD1E77B00195617 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = C8A77EEE2DD1E6D000195617 /* Build configuration list for PBXProject "punchnet" */;
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = C8A77EEA2DD1E6D000195617;
|
||||||
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
packageReferences = (
|
||||||
|
C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||||
|
C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */,
|
||||||
|
);
|
||||||
|
preferredProjectObjectVersion = 77;
|
||||||
|
productRefGroup = C8A77EF42DD1E6D000195617 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
C8A77EF22DD1E6D000195617 /* punchnet */,
|
||||||
|
C8A77F052DD1E6D100195617 /* punchnetTests */,
|
||||||
|
C8A77F0F2DD1E6D100195617 /* punchnetUITests */,
|
||||||
|
C8A77F262DD1E77B00195617 /* Tun */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
C8A77EF12DD1E6D000195617 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F042DD1E6D100195617 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F0E2DD1E6D100195617 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F252DD1E77B00195617 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
C8A77EEF2DD1E6D000195617 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F022DD1E6D100195617 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F0C2DD1E6D100195617 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
C8A77F232DD1E77B00195617 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
C8A77F082DD1E6D100195617 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = C8A77EF22DD1E6D000195617 /* punchnet */;
|
||||||
|
targetProxy = C8A77F072DD1E6D100195617 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
C8A77F122DD1E6D100195617 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = C8A77EF22DD1E6D000195617 /* punchnet */;
|
||||||
|
targetProxy = C8A77F112DD1E6D100195617 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
C8A77F312DD1E77B00195617 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = C8A77F262DD1E77B00195617 /* Tun */;
|
||||||
|
targetProxy = C8A77F302DD1E77B00195617 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
C8A77F182DD1E6D100195617 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
C8A77F192DD1E6D100195617 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
C8A77F1B2DD1E6D100195617 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = punchnet/punchnet.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"punchnet/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = PF3QG837XS;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetmac;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MacPunchnetTest;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
C8A77F1C2DD1E6D100195617 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = punchnet/punchnet.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"punchnet/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = PF3QG837XS;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetmac;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MacPunchnetTest;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
C8A77F1E2DD1E6D100195617 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = PF3QG837XS;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/punchnet.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/punchnet";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
C8A77F1F2DD1E6D100195617 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = PF3QG837XS;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/punchnet.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/punchnet";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
C8A77F212DD1E6D100195617 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = PF3QG837XS;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetUITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_TARGET_NAME = punchnet;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
C8A77F222DD1E6D100195617 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = PF3QG837XS;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetUITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_TARGET_NAME = punchnet;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
C8A77F352DD1E77B00195617 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Tun/Tun.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = PF3QG837XS;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = Tun/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Tun;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@executable_path/../../../../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetmac.tun;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MacPunchnetTunTest;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
C8A77F362DD1E77B00195617 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Tun/Tun.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = PF3QG837XS;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = Tun/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Tun;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@executable_path/../../../../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.jihe.punchnetmac.tun;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MacPunchnetTunTest;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
C8A77EEE2DD1E6D000195617 /* Build configuration list for PBXProject "punchnet" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
C8A77F182DD1E6D100195617 /* Debug */,
|
||||||
|
C8A77F192DD1E6D100195617 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
C8A77F1A2DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnet" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
C8A77F1B2DD1E6D100195617 /* Debug */,
|
||||||
|
C8A77F1C2DD1E6D100195617 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
C8A77F1D2DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnetTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
C8A77F1E2DD1E6D100195617 /* Debug */,
|
||||||
|
C8A77F1F2DD1E6D100195617 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
C8A77F202DD1E6D100195617 /* Build configuration list for PBXNativeTarget "punchnetUITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
C8A77F212DD1E6D100195617 /* Debug */,
|
||||||
|
C8A77F222DD1E6D100195617 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
C8A77F342DD1E77B00195617 /* Build configuration list for PBXNativeTarget "Tun" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
C8A77F352DD1E77B00195617 /* Debug */,
|
||||||
|
C8A77F362DD1E77B00195617 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/apple/swift-protobuf.git";
|
||||||
|
requirement = {
|
||||||
|
kind = exactVersion;
|
||||||
|
version = 1.30.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/apple/swift-nio.git";
|
||||||
|
requirement = {
|
||||||
|
kind = exactVersion;
|
||||||
|
version = 2.85.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
C8A77F782DD1E93900195617 /* NIO */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NIO;
|
||||||
|
};
|
||||||
|
C8A77F7A2DD1E93900195617 /* NIOConcurrencyHelpers */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NIOConcurrencyHelpers;
|
||||||
|
};
|
||||||
|
C8A77F7C2DD1E93900195617 /* NIOCore */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NIOCore;
|
||||||
|
};
|
||||||
|
C8A77F7E2DD1E93900195617 /* NIOEmbedded */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NIOEmbedded;
|
||||||
|
};
|
||||||
|
C8A77F802DD1E93900195617 /* NIOFoundationCompat */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NIOFoundationCompat;
|
||||||
|
};
|
||||||
|
C8A77F872DD1EA0200195617 /* SwiftProtobuf */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SwiftProtobuf;
|
||||||
|
};
|
||||||
|
C8A77F892DD1EA0200195617 /* SwiftProtobufPluginLibrary */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SwiftProtobufPluginLibrary;
|
||||||
|
};
|
||||||
|
C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||||
|
productName = SwiftProtobuf;
|
||||||
|
};
|
||||||
|
C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||||
|
productName = SwiftProtobufPluginLibrary;
|
||||||
|
};
|
||||||
|
C8AA72BF2E5C4A3100E4C4E9 /* NIO */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */;
|
||||||
|
productName = NIO;
|
||||||
|
};
|
||||||
|
C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */;
|
||||||
|
productName = NIOConcurrencyHelpers;
|
||||||
|
};
|
||||||
|
C8AA72C32E5C4A3100E4C4E9 /* NIOCore */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */;
|
||||||
|
productName = NIOCore;
|
||||||
|
};
|
||||||
|
C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */;
|
||||||
|
productName = NIOEmbedded;
|
||||||
|
};
|
||||||
|
C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */;
|
||||||
|
productName = NIOFoundationCompat;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
};
|
||||||
|
rootObject = C8A77EEB2DD1E6D000195617 /* Project object */;
|
||||||
|
}
|
||||||
7
punchnet.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
punchnet.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "03bf3695750ad5eb9d4372b7d6478fa6d8494d0400ef6f58755e25a26a9f4d8d",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "swift-atomics",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-atomics.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
|
||||||
|
"version" : "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341",
|
||||||
|
"version" : "1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-nio",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-nio.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a5fea865badcb1c993c85b0f0e8d05a4bd2270fb",
|
||||||
|
"version" : "2.85.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-protobuf",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "102a647b573f60f73afdce5613a51d71349fe507",
|
||||||
|
"version" : "1.30.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-system",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-system.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b63d24d465e237966c3f59f47dcac6c70fb0bca3",
|
||||||
|
"version" : "1.6.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>Tun.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>punchnet.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>punchnetTests.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>punchnetUITests.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>C8A77EF22DD1E6D000195617</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>C8A77F052DD1E6D100195617</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>C8A77F262DD1E77B00195617</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Loading…
x
Reference in New Issue
Block a user