Skip to content

Commit

Permalink
remote(server): add automatic NAT configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
osy committed Feb 14, 2024
1 parent 5664ee5 commit 9b52022
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Platform/macOS/UTMServerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
31 changes: 31 additions & 0 deletions Remote/UTMRemoteServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Foundation
import Combine
import Network
import SwiftConnect
import SwiftPortmap
import UserNotifications

let service = "_utm_server._tcp"
Expand All @@ -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
Expand Down Expand Up @@ -128,13 +130,29 @@ 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) {
await newRemoteConnection(connection)
}
}
}
natPort = nil
await stop()
}
await state.setServerActive(true)
Expand All @@ -149,6 +167,7 @@ actor UTMRemoteServer {
listener.cancel()
_ = await listener.result
}
await state.setExternalAddress()
await state.setServerActive(false)
}

Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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
}
}
}

Expand Down Expand Up @@ -783,6 +811,7 @@ extension UTMRemoteServer {
extension UTMRemoteServer {
enum ServerError: LocalizedError {
case silentError(Error)
case natReservationMismatch(Int)
case notAuthenticated
case versionMismatch
case notFound(UUID)
Expand All @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions UTM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -3075,6 +3077,7 @@
84B36D2127B3265400C22685 /* CocoaSpice */,
84A0A8872A47D5C50038F329 /* QEMUKit */,
CE9B15352B11A491003A32DD /* SwiftConnect */,
CEDD11C02B7C74D7004DDAC6 /* SwiftPortmap */,
);
productName = UTM;
productReference = CE2D951C24AD48BE0059923A /* UTM.app */;
Expand Down Expand Up @@ -3241,6 +3244,7 @@
84E3A8FE293DBC290024A740 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
84A0A8862A47D5C50038F329 /* XCRemoteSwiftPackageReference "QEMUKit" */,
CE9B15342B11A491003A32DD /* XCRemoteSwiftPackageReference "SwiftConnect" */,
CEDD11BF2B7C74D7004DDAC6 /* XCRemoteSwiftPackageReference "SwiftPortmap" */,
);
productRefGroup = CE550BCA225947990063E575 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 9b52022

Please sign in to comment.