fix rules

This commit is contained in:
anlicheng 2026-02-05 23:13:37 +08:00
parent 3947c1f6da
commit 2e6e1e5b3f
11 changed files with 252 additions and 41 deletions

View File

@ -6,17 +6,17 @@
// //
struct IdentityRuleMap { struct IdentityRuleMap {
let ruleMap: [UInt32: [UInt32: UInt8]] // map[proto][port]
let ruleMap: [UInt8: [UInt16: Bool]]
init(ruleMap: [UInt32: [UInt32: UInt8]]) { init(ruleMap: [UInt8: [UInt16: Bool]]) {
self.ruleMap = ruleMap self.ruleMap = ruleMap
} }
func isAllow(proto: UInt32, port: UInt32) -> Bool { func isAllow(proto: UInt8, port: UInt16) -> Bool {
if let portMap = self.ruleMap[proto], if let portMap = self.ruleMap[proto],
let allowed = portMap[port], let allowed = portMap[port] {
allowed > 0 { return allowed
return true
} else { } else {
return false return false
} }

View File

@ -5,28 +5,106 @@
// Created by on 2026/2/5. // Created by on 2026/2/5.
// //
import Foundation import Foundation
import NIO
final class IdentitySession {
var version: UInt32
var totalNum: UInt32
private var parts: [UInt32: SDLPolicyResponse] = [:]
init(part: SDLPolicyResponse) {
self.version = part.version
self.totalNum = part.totalNum
self.parts[part.index] = part
}
func merge(part: SDLPolicyResponse) {
if part.version < version {
//
} else if part.version == version {
self.parts[part.index] = part
} else {
self.parts.removeAll()
self.parts[part.index] = part
}
}
func process() -> Data? {
// parts0total_num
let indexs = parts.keys.sorted().map { UInt32($0) }
guard indexs.count == self.totalNum && isContinuousFromZero(indexs: indexs) else {
return nil
}
var rulesData: Data = Data()
for i in 0..<totalNum {
if let part = self.parts[i] {
rulesData.append(part.rules)
}
}
SDLLogger.shared.log("[IdentitySession] get a completed rules: \(rulesData.count)")
return rulesData
}
private func isContinuousFromZero(indexs: [UInt32]) -> Bool {
guard !indexs.isEmpty else {
return false
}
return indexs.enumerated().allSatisfy { idx, value in
idx == value
}
}
}
actor IdentityStore { actor IdentityStore {
typealias IdentityID = UInt32 typealias IdentityID = UInt32
nonisolated private let alloctor = ByteBufferAllocator()
private let publisher: SnapshotPublisher<IdentitySnapshot> private let publisher: SnapshotPublisher<IdentitySnapshot>
private let identityMap: [IdentityID: IdentityRuleMap] = [:] private var identityMap: [IdentityID: IdentityRuleMap] = [:]
private var sessions: [IdentityID: IdentitySession] = [:]
init(publisher: SnapshotPublisher<IdentitySnapshot>) { init(publisher: SnapshotPublisher<IdentitySnapshot>) {
self.publisher = publisher self.publisher = publisher
} }
func apply(_ id: IdentityID, ruleBytes: Data) { func apply(policyResponse: SDLPolicyResponse) {
let id = policyResponse.srcIdentityID
let session = self.sessions[id, default: IdentitySession(part: policyResponse)]
session.merge(part: policyResponse)
// if model.affectsRuntime { //
// let snapshot = compileSnapshot(from: model) if let rulesData = session.process() {
// publisher.publish(snapshot) var buffer = alloctor.buffer(bytes: rulesData)
// } var ruleMap: [UInt8: [UInt16: Bool]] = [:]
while true {
guard let proto = buffer.readInteger(endianness: .big, as: UInt8.self),
let port = buffer.readInteger(endianness: .big, as: UInt16.self) else {
break
}
ruleMap[proto, default: [:]][port] = true
}
self.identityMap[id] = IdentityRuleMap(ruleMap: ruleMap)
// session
self.sessions.removeValue(forKey: id)
SDLLogger.shared.log("[IdentitySession] get compile Snapshot rules nums: \(self.identityMap[id]?.ruleMap.count), success: \(self.identityMap[id]?.isAllow(proto: 1, port: 80))")
//
let snapshot = compileSnapshot()
publisher.publish(snapshot)
} else {
self.sessions[id] = session
}
}
private func compileSnapshot() -> IdentitySnapshot {
return IdentitySnapshot(identityMap: identityMap)
} }
//
// func compileSnapshot() -> IdentitySnapshot {
//
// }
//
} }

View File

@ -0,0 +1,56 @@
//
// SDLPuncherActor.swift
// Tun
//
// Created by on 2026/1/7.
//
import Foundation
import NIOCore
actor PolicyRequesterActor {
nonisolated private let cooldown: Duration = .seconds(5)
// identityId
private var coolingDown: Set<UInt32> = []
// , map[identityId] = version
private var versions: [UInt32: UInt32] = [:]
// holer
nonisolated private let querySocketAddress: SocketAddress
init(querySocketAddress: SocketAddress) {
self.querySocketAddress = querySocketAddress
}
//
func submitPolicyRequest(using udpHole: SDLUDPHole?, request: inout SDLPolicyRequest) {
let identityId = request.srcIdentityID
guard let udpHole, !coolingDown.contains(identityId) else {
return
}
//
coolingDown.insert(identityId)
let version = self.versions[identityId, default: 1]
request.version = version
//
self.versions[identityId] = version + 1
//
if let queryData = try? request.serializedData() {
udpHole.send(type: .policyRequest, data: queryData, remoteAddress: self.querySocketAddress)
}
Task {
//
try? await Task.sleep(for: .seconds(5))
self.endCooldown(for: identityId)
}
}
private func endCooldown(for key: UInt32) {
self.coolingDown.remove(key)
}
}

View File

@ -13,11 +13,6 @@ final class SnapshotPublisher<IdentitySnapshot: AnyObject> {
self.atomic = ManagedAtomic(.passRetained(snapshot)) self.atomic = ManagedAtomic(.passRetained(snapshot))
} }
deinit {
let ref = atomic.load(ordering: .relaxed)
ref.release()
}
func publish(_ snapshot: IdentitySnapshot) { func publish(_ snapshot: IdentitySnapshot) {
let newRef = Unmanaged.passRetained(snapshot) let newRef = Unmanaged.passRetained(snapshot)
let oldRef = atomic.exchange(newRef, ordering: .acquiring) let oldRef = atomic.exchange(newRef, ordering: .acquiring)
@ -29,4 +24,9 @@ final class SnapshotPublisher<IdentitySnapshot: AnyObject> {
atomic.load(ordering: .relaxed).takeUnretainedValue() atomic.load(ordering: .relaxed).takeUnretainedValue()
} }
deinit {
let ref = atomic.load(ordering: .relaxed)
ref.release()
}
} }

View File

@ -81,6 +81,7 @@ public class SDLConfiguration {
let networkAddress: NetworkAddress let networkAddress: NetworkAddress
let hostname: String let hostname: String
let accessToken: String let accessToken: String
let identityId: UInt32
public init(version: UInt8, public init(version: UInt8,
installedChannel: String, installedChannel: String,
@ -92,6 +93,7 @@ public class SDLConfiguration {
hostname: String, hostname: String,
noticePort: Int, noticePort: Int,
accessToken: String, accessToken: String,
identityId: UInt32,
remoteDnsServer: String) { remoteDnsServer: String) {
self.version = version self.version = version
@ -103,6 +105,7 @@ public class SDLConfiguration {
self.networkAddress = networkAddress self.networkAddress = networkAddress
self.noticePort = noticePort self.noticePort = noticePort
self.accessToken = accessToken self.accessToken = accessToken
self.identityId = identityId
self.remoteDnsServer = remoteDnsServer self.remoteDnsServer = remoteDnsServer
self.hostname = hostname self.hostname = hostname
} }
@ -118,6 +121,7 @@ extension SDLConfiguration {
let stunServersStr = options["stun_servers"] as? String, let stunServersStr = options["stun_servers"] as? String,
let noticePort = options["notice_port"] as? Int, let noticePort = options["notice_port"] as? Int,
let accessToken = options["access_token"] as? String, let accessToken = options["access_token"] as? String,
let identityId = options["identity_id"] as? UInt32,
let clientId = options["client_id"] as? String, let clientId = options["client_id"] as? String,
let remoteDnsServer = options["remote_dns_server"] as? String, let remoteDnsServer = options["remote_dns_server"] as? String,
let hostname = options["hostname"] as? String, let hostname = options["hostname"] as? String,
@ -144,6 +148,7 @@ extension SDLConfiguration {
hostname: hostname, hostname: hostname,
noticePort: noticePort, noticePort: noticePort,
accessToken: accessToken, accessToken: accessToken,
identityId: identityId,
remoteDnsServer: remoteDnsServer) remoteDnsServer: remoteDnsServer)
} }

View File

@ -73,6 +73,7 @@ actor SDLContextActor {
// //
private let identifyStore: IdentityStore private let identifyStore: IdentityStore
private let snapshotPublisher: SnapshotPublisher<IdentitySnapshot> private let snapshotPublisher: SnapshotPublisher<IdentitySnapshot>
private let policyRequesterActor: PolicyRequesterActor
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) { public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) {
self.provider = provider self.provider = provider
@ -90,6 +91,7 @@ actor SDLContextActor {
let snapshotPublisher = SnapshotPublisher(initial: IdentitySnapshot.empty()) let snapshotPublisher = SnapshotPublisher(initial: IdentitySnapshot.empty())
self.identifyStore = IdentityStore(publisher: snapshotPublisher) self.identifyStore = IdentityStore(publisher: snapshotPublisher)
self.snapshotPublisher = snapshotPublisher self.snapshotPublisher = snapshotPublisher
self.policyRequesterActor = PolicyRequesterActor(querySocketAddress: config.stunSocketAddress)
} }
public func start() { public func start() {
@ -241,6 +243,10 @@ actor SDLContextActor {
try? await self.handleRegister(remoteAddress: remoteAddress, register: register) try? await self.handleRegister(remoteAddress: remoteAddress, register: register)
case .registerAck(let registerAck): case .registerAck(let registerAck):
await self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck) await self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck)
case .policyReponse(let policyResponse):
SDLLogger.shared.log("[SDLContext] get a policyResponse: \(policyResponse.totalNum) of \(policyResponse.index), bytes: \(policyResponse.rules.count)")
//
await self.identifyStore.apply(policyResponse: policyResponse)
} }
} }
@ -298,7 +304,6 @@ actor SDLContextActor {
stunRequest.natType = UInt32(self.natType.rawValue) stunRequest.natType = UInt32(self.natType.rawValue)
stunRequest.sessionToken = sessionToken stunRequest.sessionToken = sessionToken
SDLLogger.shared.log("[SDLContext] send stun request: \(stunRequest)")
if let stunData = try? stunRequest.serializedData() { if let stunData = try? stunRequest.serializedData() {
let remoteAddress = self.config.stunSocketAddress let remoteAddress = self.config.stunSocketAddress
self.udpHole?.send(type: .stunRequest, data: stunData, remoteAddress: remoteAddress) self.udpHole?.send(type: .stunRequest, data: stunData, remoteAddress: remoteAddress)
@ -310,6 +315,8 @@ actor SDLContextActor {
self.aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) self.aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey))
self.sessionToken = registerSuperAck.sessionToken self.sessionToken = registerSuperAck.sessionToken
await self.triggerPolicy()
SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info) SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info)
// tun // tun
do { do {
@ -496,15 +503,24 @@ actor SDLContextActor {
self.provider.packetFlow.writePacketObjects([packet]) self.provider.packetFlow.writePacketObjects([packet])
} }
} else { } else {
// todo //
if let sessionToken = self.sessionToken {
var policyRequest = SDLPolicyRequest()
policyRequest.clientID = self.config.clientId
policyRequest.networkID = self.config.networkAddress.networkId
policyRequest.mac = self.config.networkAddress.mac
policyRequest.srcIdentityID = data.identityID
policyRequest.dstIdentityID = self.config.identityId
policyRequest.sessionToken = sessionToken
await self.policyRequesterActor.submitPolicyRequest(using: self.udpHole, request: &policyRequest)
}
} }
default: default:
SDLLogger.shared.log("[SDLContext] get invalid packet", level: .debug) SDLLogger.shared.log("[SDLContext] get invalid packet", level: .debug)
} }
} }
// //
// public func flowReportTask() { // public func flowReportTask() {
// Task { // Task {
@ -585,6 +601,7 @@ actor SDLContextActor {
dataPacket.srcMac = networkAddr.mac dataPacket.srcMac = networkAddr.mac
dataPacket.dstMac = dstMac dataPacket.dstMac = dstMac
dataPacket.ttl = 255 dataPacket.ttl = 255
dataPacket.identityID = self.config.identityId
dataPacket.data = encodedPacket dataPacket.data = encodedPacket
let data = try! dataPacket.serializedData() let data = try! dataPacket.serializedData()
@ -665,6 +682,22 @@ actor SDLContextActor {
} }
} }
// todo
private func triggerPolicy() async {
//
if let sessionToken = self.sessionToken {
var policyRequest = SDLPolicyRequest()
policyRequest.clientID = self.config.clientId
policyRequest.networkID = self.config.networkAddress.networkId
policyRequest.mac = self.config.networkAddress.mac
policyRequest.srcIdentityID = 1234
policyRequest.dstIdentityID = self.config.identityId
policyRequest.sessionToken = sessionToken
await self.policyRequesterActor.submitPolicyRequest(using: self.udpHole, request: &policyRequest)
}
}
deinit { deinit {
self.udpHole = nil self.udpHole = nil
self.dnsClient = nil self.dnsClient = nil

View File

@ -467,12 +467,18 @@ struct SDLPolicyRequest: @unchecked Sendable {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages. // methods supported on all messages.
var clientID: String = String()
var networkID: UInt32 = 0 var networkID: UInt32 = 0
var mac: Data = Data()
var srcIdentityID: UInt32 = 0 var srcIdentityID: UInt32 = 0
var dstIdentityID: UInt32 = 0 var dstIdentityID: UInt32 = 0
var version: UInt32 = 0
var sessionToken: Data = Data() var sessionToken: Data = Data()
var unknownFields = SwiftProtobuf.UnknownStorage() var unknownFields = SwiftProtobuf.UnknownStorage()
@ -1589,10 +1595,13 @@ extension SDLArpResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
extension SDLPolicyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { extension SDLPolicyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "SDLPolicyRequest" static let protoMessageName: String = "SDLPolicyRequest"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "network_id"), 1: .standard(proto: "client_id"),
2: .standard(proto: "src_identity_id"), 2: .standard(proto: "network_id"),
3: .standard(proto: "dst_identity_id"), 3: .same(proto: "mac"),
4: .standard(proto: "session_token"), 4: .standard(proto: "src_identity_id"),
5: .standard(proto: "dst_identity_id"),
6: .same(proto: "version"),
7: .standard(proto: "session_token"),
] ]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws { mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1601,35 +1610,50 @@ extension SDLPolicyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
// allocates stack space for every case branch when no optimizations are // allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034 // enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber { switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() case 1: try { try decoder.decodeSingularStringField(value: &self.clientID) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.srcIdentityID) }() case 2: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.dstIdentityID) }() case 3: try { try decoder.decodeSingularBytesField(value: &self.mac) }()
case 4: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }() case 4: try { try decoder.decodeSingularUInt32Field(value: &self.srcIdentityID) }()
case 5: try { try decoder.decodeSingularUInt32Field(value: &self.dstIdentityID) }()
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.version) }()
case 7: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }()
default: break default: break
} }
} }
} }
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws { func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.clientID.isEmpty {
try visitor.visitSingularStringField(value: self.clientID, fieldNumber: 1)
}
if self.networkID != 0 { if self.networkID != 0 {
try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 2)
}
if !self.mac.isEmpty {
try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 3)
} }
if self.srcIdentityID != 0 { if self.srcIdentityID != 0 {
try visitor.visitSingularUInt32Field(value: self.srcIdentityID, fieldNumber: 2) try visitor.visitSingularUInt32Field(value: self.srcIdentityID, fieldNumber: 4)
} }
if self.dstIdentityID != 0 { if self.dstIdentityID != 0 {
try visitor.visitSingularUInt32Field(value: self.dstIdentityID, fieldNumber: 3) try visitor.visitSingularUInt32Field(value: self.dstIdentityID, fieldNumber: 5)
}
if self.version != 0 {
try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 6)
} }
if !self.sessionToken.isEmpty { if !self.sessionToken.isEmpty {
try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 4) try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 7)
} }
try unknownFields.traverse(visitor: &visitor) try unknownFields.traverse(visitor: &visitor)
} }
static func ==(lhs: SDLPolicyRequest, rhs: SDLPolicyRequest) -> Bool { static func ==(lhs: SDLPolicyRequest, rhs: SDLPolicyRequest) -> Bool {
if lhs.clientID != rhs.clientID {return false}
if lhs.networkID != rhs.networkID {return false} if lhs.networkID != rhs.networkID {return false}
if lhs.mac != rhs.mac {return false}
if lhs.srcIdentityID != rhs.srcIdentityID {return false} if lhs.srcIdentityID != rhs.srcIdentityID {return false}
if lhs.dstIdentityID != rhs.dstIdentityID {return false} if lhs.dstIdentityID != rhs.dstIdentityID {return false}
if lhs.version != rhs.version {return false}
if lhs.sessionToken != rhs.sessionToken {return false} if lhs.sessionToken != rhs.sessionToken {return false}
if lhs.unknownFields != rhs.unknownFields {return false} if lhs.unknownFields != rhs.unknownFields {return false}
return true return true

View File

@ -32,6 +32,10 @@ enum SDLPacketType: UInt8 {
case stunProbe = 0x32 case stunProbe = 0x32
case stunProbeReply = 0x33 case stunProbeReply = 0x33
//
case policyRequest = 0xb0
case policyResponse = 0xb1
case data = 0xFF case data = 0xFF
} }
@ -103,6 +107,8 @@ enum SDLHoleSignal {
case register(SDLRegister) case register(SDLRegister)
case registerAck(SDLRegisterAck) case registerAck(SDLRegisterAck)
case policyReponse(SDLPolicyResponse)
} }
// //

View File

@ -137,9 +137,11 @@ final class SDLUDPHole: ChannelInboundHandler {
// --MARK: // --MARK:
private func decode(buffer: inout ByteBuffer) throws -> SDLHoleMessage? { private func decode(buffer: inout ByteBuffer) throws -> SDLHoleMessage? {
let rawType = buffer.getInteger(at: 0, endianness: .big, as: UInt8.self)
guard let type = buffer.readInteger(as: UInt8.self), guard let type = buffer.readInteger(as: UInt8.self),
let packetType = SDLPacketType(rawValue: type) else { let packetType = SDLPacketType(rawValue: type) else {
SDLLogger.shared.log("[SDLUDPHole] decode error 11") SDLLogger.shared.log("[SDLUDPHole] decode error 11: \(rawType)")
return nil return nil
} }
@ -186,6 +188,12 @@ final class SDLUDPHole: ChannelInboundHandler {
return nil return nil
} }
return .signal(.peerInfo(peerInfo)) return .signal(.peerInfo(peerInfo))
case .policyResponse:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let policyResponse = try? SDLPolicyResponse(serializedBytes: bytes) else {
return nil
}
return .signal(.policyReponse(policyResponse))
case .event: case .event:
guard let eventVal = buffer.readInteger(as: UInt8.self), guard let eventVal = buffer.readInteger(as: UInt8.self),
let event = SDLEventType(rawValue: eventVal), let event = SDLEventType(rawValue: eventVal),
@ -220,7 +228,6 @@ final class SDLUDPHole: ChannelInboundHandler {
return nil return nil
} }
return .signal(.event(.refreshAuth(refreshAuthEvent))) return .signal(.event(.refreshAuth(refreshAuthEvent)))
case .networkShutdown: case .networkShutdown:
guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedBytes: bytes) else { guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedBytes: bytes) else {
SDLLogger.shared.log("[SDLUDPHole] decode error 18") SDLLogger.shared.log("[SDLUDPHole] decode error 18")

View File

@ -26,7 +26,7 @@ struct SystemConfig {
static let stunServers = "118.178.229.213:1365,1366;118.178.229.213:1365,1366" static let stunServers = "118.178.229.213:1365,1366;118.178.229.213:1365,1366"
//static let stunServers = "127.0.0.1:1265,1266;127.0.0.1:1265,1266" //static let stunServers = "127.0.0.1:1265,1266;127.0.0.1:1265,1266"
static func getOptions(networkId: UInt32, networkDomain: String, ip: String, maskLen: UInt8, accessToken: String, hostname: String, noticePort: Int) -> [String: NSObject]? { static func getOptions(networkId: UInt32, networkDomain: String, ip: String, maskLen: UInt8, accessToken: String, identityId: UInt32, hostname: String, noticePort: Int) -> [String: NSObject]? {
guard let superIp = DNSResolver.resolveAddrInfos(superHost).first else { guard let superIp = DNSResolver.resolveAddrInfos(superHost).first else {
return nil return nil
} }
@ -39,6 +39,7 @@ struct SystemConfig {
"installed_channel": installedChannel as NSObject, "installed_channel": installedChannel as NSObject,
"client_id": clientId as NSObject, "client_id": clientId as NSObject,
"access_token": accessToken as NSObject, "access_token": accessToken as NSObject,
"identity_id": identityId as NSObject,
"super_ip": superIp as NSObject, "super_ip": superIp as NSObject,
"super_port": superPort as NSObject, "super_port": superPort as NSObject,
"stun_servers": stunServers as NSObject, "stun_servers": stunServers as NSObject,

View File

@ -61,6 +61,7 @@ struct NetworkDisconnctedView: View {
ip: "10.211.179.1", ip: "10.211.179.1",
maskLen: 24, maskLen: 24,
accessToken: "accessToken1234", accessToken: "accessToken1234",
identityId: 1234,
hostname: "mysql", hostname: "mysql",
noticePort: 1234) noticePort: 1234)
// token使token // token使token