Add local network support for VNC

This commit is contained in:
f-trycua
2025-02-14 00:51:07 +01:00
parent 4093248d9c
commit 2a067a5382
3 changed files with 83 additions and 10 deletions

View File

@@ -25,6 +25,7 @@ struct VMConfig: Codable {
private var _display: VMDisplayResolution
private var _hardwareModel: Data?
private var _machineIdentifier: Data?
private var _vncPort: Int?
// MARK: - Initialization
init(
@@ -35,7 +36,8 @@ struct VMConfig: Codable {
macAddress: String? = nil,
display: String,
hardwareModel: Data? = nil,
machineIdentifier: Data? = nil
machineIdentifier: Data? = nil,
vncPort: Int? = nil
) throws {
self.os = os
self._cpuCount = cpuCount
@@ -45,6 +47,7 @@ struct VMConfig: Codable {
self._display = VMDisplayResolution(string: display) ?? VMDisplayResolution(string: "1024x768")!
self._hardwareModel = hardwareModel
self._machineIdentifier = machineIdentifier
self._vncPort = vncPort
}
var display: VMDisplayResolution {
@@ -81,6 +84,11 @@ struct VMConfig: Codable {
get { _macAddress }
set { _macAddress = newValue }
}
var vncPort: Int? {
get { _vncPort }
set { _vncPort = newValue }
}
mutating func setCpuCount(_ count: Int) {
_cpuCount = count
@@ -110,6 +118,10 @@ struct VMConfig: Codable {
self._display = newDisplay
}
mutating func setVNCPort(_ port: Int) {
_vncPort = port
}
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case _cpuCount = "cpuCount"
@@ -120,6 +132,7 @@ struct VMConfig: Codable {
case _hardwareModel = "hardwareModel"
case _machineIdentifier = "machineIdentifier"
case os
case _vncPort = "vncPort"
}
init(from decoder: Decoder) throws {
@@ -133,6 +146,7 @@ struct VMConfig: Codable {
_display = VMDisplayResolution(string: try container.decode(String.self, forKey: .display))!
_hardwareModel = try container.decodeIfPresent(Data.self, forKey: ._hardwareModel)
_machineIdentifier = try container.decodeIfPresent(Data.self, forKey: ._machineIdentifier)
_vncPort = try container.decodeIfPresent(Int.self, forKey: ._vncPort)
}
func encode(to encoder: Encoder) throws {
@@ -146,5 +160,6 @@ struct VMConfig: Codable {
try container.encode(display.string, forKey: .display)
try container.encodeIfPresent(_hardwareModel, forKey: ._hardwareModel)
try container.encodeIfPresent(_machineIdentifier, forKey: ._machineIdentifier)
try container.encodeIfPresent(_vncPort, forKey: ._vncPort)
}
}

View File

@@ -342,7 +342,9 @@ class VM {
throw VMError.internalError("Virtualization service not initialized")
}
try await vncService.start(port: 0, virtualMachine: service.getVirtualMachine())
// Use configured port or default to 0 (auto-assign)
let port = vmDirContext.config.vncPort ?? 0
try await vncService.start(port: port, virtualMachine: service.getVirtualMachine())
guard let url = vncService.url else {
throw VMError.vncNotConfigured
@@ -399,4 +401,13 @@ class VM {
func finalize(to name: String, home: Home) throws {
try vmDirContext.finalize(to: name)
}
// Add method to set VNC port
func setVNCPort(_ port: Int) throws {
guard !isRunning else {
throw VMError.alreadyRunning(vmDirContext.name)
}
vmDirContext.config.setVNCPort(port)
try vmDirContext.saveConfig()
}
}

View File

@@ -30,8 +30,11 @@ final class DefaultVNCService: VNCService {
func start(port: Int, virtualMachine: Any?) async throws {
let password = Array(PassphraseGenerator().prefix(4)).joined(separator: "-")
let securityConfiguration = Dynamic._VZVNCAuthenticationSecurityConfiguration(password: password)
// Create VNC server with specified port
let server = Dynamic._VZVNCServer(port: port, queue: DispatchQueue.main,
securityConfiguration: securityConfiguration)
if let vm = virtualMachine as? VZVirtualMachine {
server.virtualMachine = vm
}
@@ -39,15 +42,21 @@ final class DefaultVNCService: VNCService {
vncServer = server
// Wait for port to be assigned
while true {
if let port: UInt16 = server.port.asUInt16, port != 0 {
let url = "vnc://:\(password)@127.0.0.1:\(port)"
// Wait for port to be assigned if using port 0 (auto-assign)
while port == 0 {
if let assignedPort: UInt16 = server.port.asUInt16, assignedPort != 0 {
// Get the local IP address for the URL - prefer IPv4
let hostIP = try getLocalIPAddress() ?? "127.0.0.1"
let url = "vnc://:\(password)@127.0.0.1:\(assignedPort)" // Use localhost for local connections
let externalUrl = "vnc://:\(password)@\(hostIP):\(assignedPort)" // External URL for remote connections
// Save session information
let session = VNCSession(
url: url
)
Logger.info("VNC server started", metadata: [
"local": url,
"external": externalUrl
])
// Save session information with local URL for the client
let session = VNCSession(url: url)
try vmDirectory.saveSession(session)
break
}
@@ -55,6 +64,44 @@ final class DefaultVNCService: VNCService {
}
}
// Modified to prefer IPv4 addresses
private func getLocalIPAddress() throws -> String? {
var address: String?
var ifaddr: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else {
return nil
}
defer { freeifaddrs(ifaddr) }
var ptr = ifaddr
while ptr != nil {
defer { ptr = ptr?.pointee.ifa_next }
let interface = ptr?.pointee
let family = interface?.ifa_addr.pointee.sa_family
// Only look for IPv4 addresses
if family == UInt8(AF_INET) {
let name = String(cString: (interface?.ifa_name)!)
if name == "en0" { // Primary interface
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface?.ifa_addr,
socklen_t((interface?.ifa_addr.pointee.sa_len)!),
&hostname,
socklen_t(hostname.count),
nil,
0,
NI_NUMERICHOST)
address = String(cString: hostname, encoding: .utf8)
break
}
}
}
return address
}
func stop() {
if let server = vncServer as? Dynamic {
server.stop()