解决系统的异常退出问题
This commit is contained in:
parent
3283c2ae61
commit
8c8006bc69
37
Tun/Punchnet/Actors/ArpServerActor.swift
Normal file
37
Tun/Punchnet/Actors/ArpServerActor.swift
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// ArpServer.swift
|
||||
// sdlan
|
||||
//
|
||||
// Created by 安礼成 on 2025/7/14.
|
||||
//
|
||||
import Foundation
|
||||
import Darwin
|
||||
|
||||
actor ArpServerActor {
|
||||
private var known_macs: [UInt32:Data] = [:]
|
||||
|
||||
init(known_macs: [UInt32:Data]) {
|
||||
self.known_macs = known_macs
|
||||
}
|
||||
|
||||
func query(ip: UInt32) -> Data? {
|
||||
return self.known_macs[ip]
|
||||
}
|
||||
|
||||
func append(ip: UInt32, mac: Data) {
|
||||
self.known_macs[ip] = mac
|
||||
}
|
||||
|
||||
func remove(ip: UInt32) {
|
||||
self.known_macs.removeValue(forKey: ip)
|
||||
}
|
||||
|
||||
func dropMacs(macs: [Data]) {
|
||||
self.known_macs = self.known_macs.filter { !macs.contains($0.value) }
|
||||
}
|
||||
|
||||
func clear() {
|
||||
self.known_macs = [:]
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
//
|
||||
// ArpServer.swift
|
||||
// sdlan
|
||||
//
|
||||
// Created by 安礼成 on 2025/7/14.
|
||||
//
|
||||
import Foundation
|
||||
import Darwin
|
||||
|
||||
final class ArpServer {
|
||||
private var known_macs: [UInt32:Data] = [:]
|
||||
private var lock = os_unfair_lock()
|
||||
|
||||
init(known_macs: [UInt32:Data]) {
|
||||
self.known_macs = known_macs
|
||||
}
|
||||
|
||||
func query(ip: UInt32) -> Data? {
|
||||
return withLock {
|
||||
return self.known_macs[ip]
|
||||
}
|
||||
}
|
||||
|
||||
func append(ip: UInt32, mac: Data) {
|
||||
withLock {
|
||||
self.known_macs[ip] = mac
|
||||
}
|
||||
}
|
||||
|
||||
func remove(ip: UInt32) {
|
||||
withLock {
|
||||
_ = self.known_macs.removeValue(forKey: ip)
|
||||
}
|
||||
}
|
||||
|
||||
func clear() {
|
||||
withLock {
|
||||
self.known_macs = [:]
|
||||
}
|
||||
}
|
||||
|
||||
private func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
os_unfair_lock_lock(&lock)
|
||||
defer{
|
||||
os_unfair_lock_unlock(&lock)
|
||||
}
|
||||
|
||||
return try body()
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,6 +25,27 @@ public class SDLConfiguration {
|
||||
public let maskLen: UInt8
|
||||
public let mac: Data
|
||||
public let networkDomain: String
|
||||
|
||||
// ip地址
|
||||
var ipAddress: String {
|
||||
return SDLUtil.int32ToIp(self.ip)
|
||||
}
|
||||
|
||||
// 掩码
|
||||
var maskAddress: String {
|
||||
let len0 = 32 - maskLen
|
||||
let num: UInt32 = (0xFFFFFFFF >> len0) << len0
|
||||
|
||||
return SDLUtil.int32ToIp(num)
|
||||
}
|
||||
|
||||
// 网络地址
|
||||
var netAddress: String {
|
||||
let len0 = 32 - maskLen
|
||||
let mask: UInt32 = (0xFFFFFFFF >> len0) << len0
|
||||
|
||||
return SDLUtil.int32ToIp(self.ip & mask)
|
||||
}
|
||||
}
|
||||
|
||||
// 当前的客户端版本
|
||||
|
||||
@ -35,7 +35,6 @@ actor SDLContextActor {
|
||||
private var udpHole: SDLUDPHole?
|
||||
private var udpHoleWorkers: [Task<Void, Never>]?
|
||||
|
||||
nonisolated let providerAdapter: SDLTunnelProviderAdapter
|
||||
// dns的client对象
|
||||
private var dnsClient: SDLDNSClient?
|
||||
private var dnsWorker: Task<Void, Never>?
|
||||
@ -48,7 +47,7 @@ actor SDLContextActor {
|
||||
private var readTask: Task<(), Never>?
|
||||
|
||||
private var sessionManager: SessionManager
|
||||
private var arpServer: ArpServer
|
||||
private var arpServer: ArpServerActor
|
||||
|
||||
// 网络状态变化的健康
|
||||
private var monitor: SDLNetworkMonitor?
|
||||
@ -63,14 +62,16 @@ actor SDLContextActor {
|
||||
// 处理内部的需要长时间运行的任务
|
||||
private var loopChildWorkers: [Task<Void, Never>] = []
|
||||
|
||||
private let provider: NEPacketTunnelProvider
|
||||
|
||||
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) {
|
||||
self.provider = provider
|
||||
self.config = config
|
||||
self.rsaCipher = rsaCipher
|
||||
self.aesCipher = aesCipher
|
||||
|
||||
self.sessionManager = SessionManager()
|
||||
self.arpServer = ArpServer(known_macs: [:])
|
||||
self.providerAdapter = SDLTunnelProviderAdapter(provider: provider)
|
||||
self.arpServer = ArpServerActor(known_macs: [:])
|
||||
|
||||
self.puncherActor = SDLPuncherActor(querySocketAddress: config.stunSocketAddress)
|
||||
self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray)
|
||||
@ -153,7 +154,7 @@ actor SDLContextActor {
|
||||
break
|
||||
}
|
||||
let nePacket = NEPacket(data: packet, protocolFamily: 2)
|
||||
self.providerAdapter.writePackets(packets: [nePacket])
|
||||
self.provider.packetFlow.writePacketObjects([nePacket])
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +189,7 @@ actor SDLContextActor {
|
||||
await self.sendStunRequest()
|
||||
}
|
||||
|
||||
SDLLogger.shared.log("[SDLContext] pingTask cancel")
|
||||
SDLLogger.shared.log("[SDLContext] udp pingTask cancel")
|
||||
}
|
||||
|
||||
// 处理数据流
|
||||
@ -199,6 +200,8 @@ actor SDLContextActor {
|
||||
}
|
||||
try? await self.handleData(data: data)
|
||||
}
|
||||
|
||||
SDLLogger.shared.log("[SDLContext] udp dataTask cancel")
|
||||
}
|
||||
|
||||
// 处理控制信号
|
||||
@ -225,6 +228,8 @@ actor SDLContextActor {
|
||||
await self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck)
|
||||
}
|
||||
}
|
||||
|
||||
SDLLogger.shared.log("[SDLContext] udp signalTask cancel")
|
||||
}
|
||||
|
||||
self.udpHole = udpHole
|
||||
@ -293,13 +298,12 @@ actor SDLContextActor {
|
||||
SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info)
|
||||
// 服务器分配的tun网卡信息
|
||||
do {
|
||||
let ipAddress = try await self.providerAdapter.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer)
|
||||
try await self.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer)
|
||||
SDLLogger.shared.log("[SDLContext] setNetworkSettings successed")
|
||||
self.noticeClient?.send(data: NoticeMessage.ipAdress(ip: ipAddress))
|
||||
self.startReader()
|
||||
} catch let err {
|
||||
SDLLogger.shared.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error)
|
||||
exit(-1)
|
||||
self.provider.cancelTunnelWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +317,10 @@ actor SDLContextActor {
|
||||
case .invalidToken, .nodeDisabled:
|
||||
let alertNotice = NoticeMessage.alert(alert: errorMessage)
|
||||
self.noticeClient?.send(data: alertNotice)
|
||||
exit(-1)
|
||||
// 报告错误并退出
|
||||
let error = NSError(domain: "com.jihe.punchnet.tun", code: -1)
|
||||
self.provider.cancelTunnelWithError(error)
|
||||
|
||||
case .noIpAddress, .networkFault, .internalFault:
|
||||
let alertNotice = NoticeMessage.alert(alert: errorMessage)
|
||||
self.noticeClient?.send(data: alertNotice)
|
||||
@ -322,12 +329,11 @@ actor SDLContextActor {
|
||||
|
||||
}
|
||||
|
||||
private func handleEvent(event: SDLEvent) throws {
|
||||
private func handleEvent(event: SDLEvent) async throws {
|
||||
switch event {
|
||||
case .dropMacs(let dropMacsEvent):
|
||||
// TODO
|
||||
SDLLogger.shared.log("[SDLContext] drop macs", level: .info)
|
||||
()
|
||||
await self.arpServer.dropMacs(macs: dropMacsEvent.macs)
|
||||
case .natChanged(let natChangedEvent):
|
||||
let dstMac = natChangedEvent.mac
|
||||
SDLLogger.shared.log("[SDLContext] natChangedEvent, dstMac: \(dstMac)", level: .info)
|
||||
@ -349,7 +355,10 @@ actor SDLContextActor {
|
||||
case .networkShutdown(let shutdownEvent):
|
||||
let alertNotice = NoticeMessage.alert(alert: shutdownEvent.message)
|
||||
self.noticeClient?.send(data: alertNotice)
|
||||
exit(-1)
|
||||
|
||||
// 报告错误并退出
|
||||
let error = NSError(domain: "com.jihe.punchnet.tun", code: -2)
|
||||
self.provider.cancelTunnelWithError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,7 +445,7 @@ actor SDLContextActor {
|
||||
await self.routeLayerPacket(dstMac: arpPacket.senderMAC, type: .arp, data: response.marshal())
|
||||
case .response:
|
||||
SDLLogger.shared.log("[SDLContext] get arp response packet", level: .debug)
|
||||
self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC)
|
||||
await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC)
|
||||
}
|
||||
} else {
|
||||
SDLLogger.shared.log("[SDLContext] get invalid arp packet: \(arpPacket), target_ip: \(SDLUtil.int32ToIp(arpPacket.targetIP)), net ip: \(SDLUtil.int32ToIp(networkAddr.ip))", level: .debug)
|
||||
@ -449,7 +458,7 @@ actor SDLContextActor {
|
||||
return
|
||||
}
|
||||
let packet = NEPacket(data: ipPacket.data, protocolFamily: 2)
|
||||
self.providerAdapter.writePackets(packets: [packet])
|
||||
self.provider.packetFlow.writePacketObjects([packet])
|
||||
default:
|
||||
SDLLogger.shared.log("[SDLContext] get invalid packet", level: .debug)
|
||||
}
|
||||
@ -482,14 +491,15 @@ actor SDLContextActor {
|
||||
return
|
||||
}
|
||||
|
||||
let packets = await self.providerAdapter.readPackets()
|
||||
let ipPackets = packets.compactMap { IPPacket($0) }
|
||||
for ipPacket in ipPackets {
|
||||
let (packets, numbers) = await self.provider.packetFlow.readPackets()
|
||||
for (data, number) in zip(packets, numbers) where number == 2 {
|
||||
if let ipPacket = IPPacket(data) {
|
||||
await self.dealPacket(packet: ipPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理读取的每个数据包
|
||||
private func dealPacket(packet: IPPacket) async {
|
||||
@ -505,12 +515,12 @@ actor SDLContextActor {
|
||||
// 本地通讯, 目标地址是本地服务器的ip地址
|
||||
if dstIp == networkAddr.ip {
|
||||
let nePacket = NEPacket(data: packet.data, protocolFamily: 2)
|
||||
self.providerAdapter.writePackets(packets: [nePacket])
|
||||
self.provider.packetFlow.writePacketObjects([nePacket])
|
||||
return
|
||||
}
|
||||
|
||||
// 查找arp缓存中是否有目标mac地址
|
||||
if let dstMac = self.arpServer.query(ip: dstIp) {
|
||||
if let dstMac = await self.arpServer.query(ip: dstIp) {
|
||||
await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data)
|
||||
}
|
||||
else {
|
||||
@ -564,6 +574,43 @@ actor SDLContextActor {
|
||||
}
|
||||
}
|
||||
|
||||
// 网络改变时需要重新配置网络信息
|
||||
private func setNetworkSettings(networkAddress: SDLConfiguration.NetworkAddress, dnsServer: String) async throws {
|
||||
let routes: [NEIPv4Route] = [
|
||||
NEIPv4Route(destinationAddress: networkAddress.netAddress, subnetMask: networkAddress.maskAddress),
|
||||
NEIPv4Route(destinationAddress: 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 = 1250
|
||||
|
||||
// 设置网卡的DNS解析
|
||||
let networkDomain = networkAddress.networkDomain
|
||||
let dnsSettings = NEDNSSettings(servers: [dnsServer])
|
||||
dnsSettings.searchDomains = [networkDomain]
|
||||
dnsSettings.matchDomains = [networkDomain]
|
||||
dnsSettings.matchDomainsNoSearch = false
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: [networkAddress.ipAddress], subnetMasks: [networkAddress.maskAddress])
|
||||
// 设置路由表
|
||||
//NEIPv4Route.default()
|
||||
ipv4Settings.includedRoutes = routes
|
||||
networkSettings.ipv4Settings = ipv4Settings
|
||||
// 网卡配置设置必须成功
|
||||
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
||||
}
|
||||
|
||||
// 开始读取数据, 用单独的线程处理packetFlow
|
||||
func readPackets() async -> [Data] {
|
||||
let (packets, numbers) = await self.provider.packetFlow.readPackets()
|
||||
return zip(packets, numbers).compactMap { (data, number) in
|
||||
return number == 2 ? data : nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func spawnLoop(_ body: @escaping () async throws -> Void) -> Task<Void, Never> {
|
||||
return Task.detached {
|
||||
while !Task.isCancelled {
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
//
|
||||
// SDLIPAddress.swift
|
||||
// Tun
|
||||
//
|
||||
// Created by 安礼成 on 2024/3/4.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SDLNetAddress {
|
||||
let ip: UInt32
|
||||
let maskLen: UInt8
|
||||
|
||||
// ip地址
|
||||
var ipAddress: String {
|
||||
return intToIpAddress(self.ip)
|
||||
}
|
||||
|
||||
// 掩码
|
||||
var maskAddress: String {
|
||||
let len0 = 32 - maskLen
|
||||
let num: UInt32 = (0xFFFFFFFF >> len0) << len0
|
||||
|
||||
return intToIpAddress(num)
|
||||
}
|
||||
|
||||
// 网络地址
|
||||
var networkAddress: String {
|
||||
let len0 = 32 - maskLen
|
||||
let mask: UInt32 = (0xFFFFFFFF >> len0) << len0
|
||||
|
||||
return intToIpAddress(self.ip & mask)
|
||||
}
|
||||
|
||||
init(ip: UInt32, maskLen: UInt8) {
|
||||
self.ip = ip
|
||||
self.maskLen = maskLen
|
||||
}
|
||||
|
||||
private func intToIpAddress(_ num: UInt32) -> String {
|
||||
let ip0 = (UInt8) (num >> 24 & 0xFF)
|
||||
let ip1 = (UInt8) (num >> 16 & 0xFF)
|
||||
let ip2 = (UInt8) (num >> 8 & 0xFF)
|
||||
let ip3 = (UInt8) (num & 0xFF)
|
||||
|
||||
return "\(ip0).\(ip1).\(ip2).\(ip3)"
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
//
|
||||
// SDLContext.swift
|
||||
// Tun
|
||||
//
|
||||
// Created by 安礼成 on 2024/2/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import NIOCore
|
||||
import Combine
|
||||
|
||||
// 上下文环境变量,全局共享
|
||||
/*
|
||||
1. 处理rsa的加解密逻辑
|
||||
*/
|
||||
|
||||
final class SDLTunnelProviderAdapter {
|
||||
|
||||
// 路由信息
|
||||
struct Route {
|
||||
let dstAddress: String
|
||||
let subnetMask: String
|
||||
|
||||
var debugInfo: String {
|
||||
return "\(dstAddress):\(subnetMask)"
|
||||
}
|
||||
}
|
||||
|
||||
let provider: NEPacketTunnelProvider
|
||||
|
||||
public init(provider: NEPacketTunnelProvider) {
|
||||
self.provider = provider
|
||||
}
|
||||
|
||||
func writePackets(packets: [NEPacket]) {
|
||||
//let packet = NEPacket(data: ipPacket.data, protocolFamily: 2)
|
||||
self.provider.packetFlow.writePacketObjects(packets)
|
||||
}
|
||||
|
||||
// 网络改变时需要重新配置网络信息
|
||||
func setNetworkSettings(networkAddress: SDLConfiguration.NetworkAddress, dnsServer: String) async throws -> String {
|
||||
let netAddress = SDLNetAddress(ip: networkAddress.ip, maskLen: networkAddress.maskLen)
|
||||
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 = 1250
|
||||
|
||||
// 设置网卡的DNS解析
|
||||
|
||||
let networkDomain = networkAddress.networkDomain
|
||||
let dnsSettings = NEDNSSettings(servers: [dnsServer])
|
||||
dnsSettings.searchDomains = [networkDomain]
|
||||
dnsSettings.matchDomains = [networkDomain]
|
||||
dnsSettings.matchDomainsNoSearch = false
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
SDLLogger.shared.log("[SDLTunnelProviderAdapter] 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
|
||||
// 网卡配置设置必须成功
|
||||
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
||||
|
||||
return netAddress.ipAddress
|
||||
}
|
||||
|
||||
// 开始读取数据, 用单独的线程处理packetFlow
|
||||
func readPackets() async -> [Data] {
|
||||
let (packets, numbers) = await self.provider.packetFlow.readPackets()
|
||||
return zip(packets, numbers).compactMap { (data, number) in
|
||||
return number == 2 ? data : nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -11,9 +11,7 @@ import NIOCore
|
||||
struct NoticeMessage {
|
||||
enum InboundMessage {
|
||||
case none
|
||||
case upgradeMessage(prompt: String, address: String)
|
||||
case alertMessage(alert: String)
|
||||
case ip(ip: String)
|
||||
}
|
||||
|
||||
static func decodeMessage(buffer: inout ByteBuffer) -> InboundMessage {
|
||||
@ -23,23 +21,10 @@ struct NoticeMessage {
|
||||
|
||||
switch type {
|
||||
case 0x01:
|
||||
if let len0 = buffer.readInteger(as: UInt16.self),
|
||||
let prompt = buffer.readString(length: Int(len0)),
|
||||
let len1 = buffer.readInteger(as: UInt16.self),
|
||||
let address = buffer.readString(length: Int(len1)) {
|
||||
return .upgradeMessage(prompt: prompt, address: address)
|
||||
}
|
||||
case 0x02:
|
||||
if let len0 = buffer.readInteger(as: UInt16.self),
|
||||
let alert = buffer.readString(length: Int(len0)) {
|
||||
return .alertMessage(alert: alert)
|
||||
}
|
||||
|
||||
case 0x03:
|
||||
if let len0 = buffer.readInteger(as: UInt16.self),
|
||||
let ipAddress = buffer.readString(length: Int(len0)) {
|
||||
return .ip(ip: ipAddress)
|
||||
}
|
||||
default:
|
||||
return .none
|
||||
}
|
||||
@ -47,39 +32,16 @@ struct NoticeMessage {
|
||||
return .none
|
||||
}
|
||||
|
||||
static func upgrade(prompt: String, address: String) -> Data {
|
||||
static func alert(alert: String) -> Data {
|
||||
var data = Data()
|
||||
data.append(contentsOf: [0x01])
|
||||
|
||||
data.append(contentsOf: lenBytes(UInt16(prompt.count)))
|
||||
data.append(prompt.data(using: .utf8)!)
|
||||
|
||||
data.append(contentsOf: lenBytes(UInt16(address.count)))
|
||||
data.append(address.data(using: .utf8)!)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static func alert(alert: String) -> Data {
|
||||
var data = Data()
|
||||
data.append(contentsOf: [0x02])
|
||||
|
||||
data.append(contentsOf: lenBytes(UInt16(alert.count)))
|
||||
data.append(alert.data(using: .utf8)!)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static func ipAdress(ip: String) -> Data {
|
||||
var data = Data()
|
||||
data.append(contentsOf: [0x03])
|
||||
|
||||
data.append(contentsOf: lenBytes(UInt16(ip.count)))
|
||||
data.append(ip.data(using: .utf8)!)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
private static func lenBytes(_ value: UInt16) -> [UInt8] {
|
||||
let byte1 = UInt8((value >> 8) & 0xFF)
|
||||
let bytes2 = UInt8(value & 0xFF)
|
||||
|
||||
@ -222,8 +222,6 @@ struct IndexView: View {
|
||||
}
|
||||
.alert(isPresented: $showStunAlert) {
|
||||
switch self.message {
|
||||
case .upgradeMessage(let prompt, _):
|
||||
Alert(title: Text(prompt))
|
||||
case .alertMessage(let alert):
|
||||
Alert(title: Text(alert))
|
||||
default:
|
||||
@ -250,9 +248,6 @@ struct IndexView: View {
|
||||
switch message {
|
||||
case .none:
|
||||
()
|
||||
case .ip(let ip):
|
||||
self.showIpAdress = true
|
||||
self.ipAddress = ip
|
||||
default:
|
||||
self.message = message
|
||||
self.showStunAlert = true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user