remote(server): add automatic NAT configuration

This commit is contained in:
osy 2024-02-13 23:51:43 -08:00
parent ac5842c6a0
commit dd4de861d9
4 changed files with 61 additions and 0 deletions

View File

@ -53,6 +53,10 @@ struct UTMServerView: View {
ServerOverview()
Divider()
HStack {
if let address = remoteServer.externalIPAddress, let port = remoteServer.externalPort {
Text("Server IP: \(address), Port: \(String(port))")
.textSelection(.enabled)
}
Spacer()
if remoteServer.isServerActive {
Image(systemName: "circle.fill")

View File

@ -18,6 +18,7 @@ import Foundation
import Combine
import Network
import SwiftConnect
import SwiftPortmap
import UserNotifications
let service = "_utm_server._tcp"
@ -33,6 +34,7 @@ actor UTMRemoteServer {
private var listener: Task<Void, Error>?
private var pendingConnections: [State.ClientFingerprint: Connection] = [:]
private var establishedConnections: [State.ClientFingerprint: Remote] = [:]
private var natPort: SwiftPortmap.Port?
private func _replaceCancellables(with set: Set<AnyCancellable>) {
cancellables = set
@ -128,6 +130,21 @@ actor UTMRemoteServer {
registerNotifications()
listener = Task {
await withErrorNotification {
if isServerExternal && serverPort > 0 {
natPort = Port.TCP(internalPort: UInt16(serverPort))
natPort!.mappingChangedHandler = { port in
Task {
let address = try? await port.externalIpv4Address
let port = try? await port.externalPort
await self.state.setExternalAddress(address, port: port)
}
}
await withErrorNotification {
guard try await natPort!.externalPort == serverPort else {
throw ServerError.natReservationMismatch(serverPort)
}
}
}
let port = serverPort > 0 ? NWEndpoint.Port(integerLiteral: UInt16(serverPort)) : .any
for try await connection in Connection.advertise(on: port, forServiceType: service, txtRecord: metadata, identity: keyManager.identity) {
if let connection = try? await Connection(connection: connection) {
@ -135,6 +152,7 @@ actor UTMRemoteServer {
}
}
}
natPort = nil
await stop()
}
await state.setServerActive(true)
@ -149,6 +167,7 @@ actor UTMRemoteServer {
listener.cancel()
_ = await listener.result
}
await state.setExternalAddress()
await state.setServerActive(false)
}
@ -462,6 +481,10 @@ extension UTMRemoteServer {
}
}
@Published private(set) var externalIPAddress: String?
@Published private(set) var externalPort: UInt16?
init() {
var _approvedClients = Set<Client>()
if let array = UserDefaults.standard.array(forKey: "TrustedClients") {
@ -551,6 +574,11 @@ extension UTMRemoteServer {
fileprivate func setServerFingerprint(_ fingerprint: ServerFingerprint) {
serverFingerprint = fingerprint
}
fileprivate func setExternalAddress(_ address: String? = nil, port: UInt16? = nil) {
externalIPAddress = address
externalPort = port
}
}
}
@ -783,6 +811,7 @@ extension UTMRemoteServer {
extension UTMRemoteServer {
enum ServerError: LocalizedError {
case silentError(Error)
case natReservationMismatch(Int)
case notAuthenticated
case versionMismatch
case notFound(UUID)
@ -793,6 +822,8 @@ extension UTMRemoteServer {
switch self {
case .silentError(let error):
return error.localizedDescription
case .natReservationMismatch(let port):
return String.localizedStringWithFormat(NSLocalizedString("Cannot reserve port '%@' for external access from NAT. Make sure no other device on the network has reserved it.", comment: "UTMRemoteServer"), port)
case .notAuthenticated:
return NSLocalizedString("Not authenticated.", comment: "UTMRemoteServer")
case .versionMismatch:

View File

@ -886,6 +886,7 @@
CED8DF7528A120C100C34345 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = CED8DF7928A120C100C34345 /* Localizable.stringsdict */; };
CED8DF7628A120C100C34345 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = CED8DF7928A120C100C34345 /* Localizable.stringsdict */; };
CED8DF7728A120C100C34345 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = CED8DF7928A120C100C34345 /* Localizable.stringsdict */; };
CEDD11C12B7C74D7004DDAC6 /* SwiftPortmap in Frameworks */ = {isa = PBXBuildFile; productRef = CEDD11C02B7C74D7004DDAC6 /* SwiftPortmap */; };
CEDF83F9258AE24E0030E4AC /* UTMPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDF83F8258AE24E0030E4AC /* UTMPasteboard.swift */; };
CEDF83FA258AE24E0030E4AC /* UTMPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDF83F8258AE24E0030E4AC /* UTMPasteboard.swift */; };
CEE06B272B2FC89400A811AE /* UTMServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE06B262B2FC89400A811AE /* UTMServerView.swift */; };
@ -2196,6 +2197,7 @@
CE03D0CE24D9A30100F76B84 /* iconv.2.framework in Frameworks */,
CE0B6EF124AD677200FE012D /* libgstplayback.a in Frameworks */,
CE0B6EF424AD677200FE012D /* json-glib-1.0.0.framework in Frameworks */,
CEDD11C12B7C74D7004DDAC6 /* SwiftPortmap in Frameworks */,
CE0B6ED124AD677200FE012D /* phodav-2.0.0.framework in Frameworks */,
CEF83F862500947D00557D15 /* gcrypt.20.framework in Frameworks */,
CE0B6ECB24AD677200FE012D /* gstcheck-1.0.0.framework in Frameworks */,
@ -3075,6 +3077,7 @@
84B36D2127B3265400C22685 /* CocoaSpice */,
84A0A8872A47D5C50038F329 /* QEMUKit */,
CE9B15352B11A491003A32DD /* SwiftConnect */,
CEDD11C02B7C74D7004DDAC6 /* SwiftPortmap */,
);
productName = UTM;
productReference = CE2D951C24AD48BE0059923A /* UTM.app */;
@ -3241,6 +3244,7 @@
84E3A8FE293DBC290024A740 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
84A0A8862A47D5C50038F329 /* XCRemoteSwiftPackageReference "QEMUKit" */,
CE9B15342B11A491003A32DD /* XCRemoteSwiftPackageReference "SwiftConnect" */,
CEDD11BF2B7C74D7004DDAC6 /* XCRemoteSwiftPackageReference "SwiftPortmap" */,
);
productRefGroup = CE550BCA225947990063E575 /* Products */;
projectDirPath = "";
@ -5120,6 +5124,14 @@
minimumVersion = 6.5.6;
};
};
CEDD11BF2B7C74D7004DDAC6 /* XCRemoteSwiftPackageReference "SwiftPortmap" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/osy/SwiftPortmap.git";
requirement = {
branch = main;
kind = branch;
};
};
CEF7F5852AEEDCC400E34952 /* XCRemoteSwiftPackageReference "swift-log" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-log";
@ -5320,6 +5332,11 @@
package = CEA45E23263519B5002FA97D /* XCRemoteSwiftPackageReference "IQKeyboardManager" */;
productName = IQKeyboardManagerSwift;
};
CEDD11C02B7C74D7004DDAC6 /* SwiftPortmap */ = {
isa = XCSwiftPackageProductDependency;
package = CEDD11BF2B7C74D7004DDAC6 /* XCRemoteSwiftPackageReference "SwiftPortmap" */;
productName = SwiftPortmap;
};
CEF7F5842AEEDCC400E34952 /* Logging */ = {
isa = XCSwiftPackageProductDependency;
package = CEF7F5852AEEDCC400E34952 /* XCRemoteSwiftPackageReference "swift-log" */;

View File

@ -81,6 +81,15 @@
"revision" : "ac168e9e69e0dc4efbe88699e0fd712316348c55"
}
},
{
"identity" : "swiftportmap",
"kind" : "remoteSourceControl",
"location" : "https://github.com/osy/SwiftPortmap.git",
"state" : {
"branch" : "main",
"revision" : "72782141ab6f6f6db58bd16bac96d4e7ce901e9a"
}
},
{
"identity" : "swiftterm",
"kind" : "remoteSourceControl",