DnsUpdater/Sources/App/Model/IPAddressCheck.swift
Christoph Hagen 9b6fd627ac First version
2024-11-15 10:46:29 +01:00

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
}
}