121 lines
4.0 KiB
Swift
121 lines
4.0 KiB
Swift
|
import Foundation
|
||
|
|
||
|
struct IPAddressCheck {
|
||
|
|
||
|
private var ipv4Request: URLRequest {
|
||
|
let url = URL(string: "http://fritz.box:49000/igdupnp/control/WANIPConn1")!
|
||
|
var request = URLRequest(url: url)
|
||
|
request.setValue("text/xml; charset=utf-8",
|
||
|
forHTTPHeaderField: "Content-Type")
|
||
|
request.setValue("urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress",
|
||
|
forHTTPHeaderField: "SOAPAction")
|
||
|
let body = """
|
||
|
<?xml version="1.0" encoding="utf-8"?>
|
||
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||
|
<s:Body>
|
||
|
<u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1" />
|
||
|
</s:Body>
|
||
|
</s:Envelope>
|
||
|
"""
|
||
|
request.httpMethod = "POST"
|
||
|
request.httpBody = body.data(using: .utf8)!
|
||
|
return request
|
||
|
}
|
||
|
|
||
|
func determineIPv4() async throws -> String {
|
||
|
let data: Data
|
||
|
let response: URLResponse
|
||
|
do {
|
||
|
(data, response) = try await URLSession.shared.data(for: ipv4Request)
|
||
|
} catch {
|
||
|
throw DNSError.routerRequestFailedForIPv4(error)
|
||
|
}
|
||
|
|
||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||
|
throw DNSError.invalidRouterResponseForIPv4
|
||
|
}
|
||
|
guard httpResponse.statusCode == 200 else {
|
||
|
throw DNSError.invalidRouterResponseCodeForIPv4(httpResponse.statusCode)
|
||
|
}
|
||
|
guard let responseBody = String(data: data, encoding: .utf8) else {
|
||
|
throw DNSError.invalidRouterResponseBodyForIPv4
|
||
|
}
|
||
|
|
||
|
let ipString = responseBody
|
||
|
.components(separatedBy: "<NewExternalIPAddress>").last!
|
||
|
.components(separatedBy: "</NewExternalIPAddress>").first!
|
||
|
let parts = ipString.components(separatedBy: ".")
|
||
|
guard parts.count == 4 else {
|
||
|
throw DNSError.invalidRouterResponseIpForIPv4(ipString)
|
||
|
}
|
||
|
let numbers = parts.compactMap(Int.init)
|
||
|
guard numbers.count == 4 else {
|
||
|
throw DNSError.invalidRouterResponseIpForIPv4(ipString)
|
||
|
}
|
||
|
guard numbers.contains(where: { $0 != 0 }) else {
|
||
|
throw DNSError.invalidRouterResponseIpForIPv4(ipString)
|
||
|
}
|
||
|
return ipString
|
||
|
}
|
||
|
|
||
|
private func execute(_ command: String) throws -> (code: Int, output: String) {
|
||
|
let task = Process()
|
||
|
let pipe = Pipe()
|
||
|
|
||
|
task.standardOutput = pipe
|
||
|
task.standardError = pipe
|
||
|
task.arguments = ["-cl", command]
|
||
|
task.executableURL = URL(fileURLWithPath: "/bin/bash")
|
||
|
task.standardInput = nil
|
||
|
|
||
|
try task.run()
|
||
|
task.waitUntilExit()
|
||
|
|
||
|
let code = task.terminationStatus
|
||
|
|
||
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||
|
let output = String(data: data, encoding: .utf8)!
|
||
|
|
||
|
return (Int(code), output)
|
||
|
}
|
||
|
|
||
|
func determineIPv6() throws -> String {
|
||
|
#if os(Linux)
|
||
|
let ipv6Command = "ip addr show"
|
||
|
#elseif os(macOS)
|
||
|
let ipv6Command = "ifconfig"
|
||
|
#else
|
||
|
fatalError("Unsupported OS")
|
||
|
#endif
|
||
|
|
||
|
let code: Int
|
||
|
let output: String
|
||
|
do {
|
||
|
(code, output) = try execute(ipv6Command)
|
||
|
} catch {
|
||
|
throw DNSError.failedToPerformCommandForIPv6(error)
|
||
|
}
|
||
|
guard code == 0 else {
|
||
|
throw DNSError.invalidCommandResultForIPv6(code, output)
|
||
|
}
|
||
|
let addresses: [String] = output
|
||
|
.components(separatedBy: "inet6 ")
|
||
|
.dropFirst()
|
||
|
.compactMap { $0
|
||
|
.components(separatedBy: "/").first?
|
||
|
.components(separatedBy: " ").first
|
||
|
}
|
||
|
.compactMap {
|
||
|
let parts = $0.components(separatedBy: ":")
|
||
|
guard parts.count == 8 else {
|
||
|
return nil
|
||
|
}
|
||
|
return $0
|
||
|
}
|
||
|
guard let ip = addresses.first else {
|
||
|
throw DNSError.invalidIpAddressForIPv6(output)
|
||
|
}
|
||
|
return ip
|
||
|
}
|
||
|
}
|