DnsUpdater/Sources/App/Model/DNSChangeResponse.swift

184 lines
5.7 KiB
Swift
Raw Permalink Normal View History

2024-11-15 10:46:29 +01:00
import Foundation
enum DNSChangeResponse: RawRepresentable, Codable {
/**
The update was successful. You should not attempt another update until your IP address changes.
The response is followed by a list of the users IP addresses
*/
case success
/**
The supplied IP address is already set for this host. You should not attempt another update until your IP address changes.
The response is followed by a list of the users IP addresses
*/
case noChange
/**
The hostname doesn't exist, or doesn't have Dynamic DNS enabled.
*/
case invalidHostname
/**
The username/password combination isn't valid for the specified host.
*/
case badAuthentication
/**
The supplied hostname isn't a valid fully-qualified domain name.
*/
case notFullyQualifiedDomainName
/**
Your Dynamic DNS client makes bad requests. Ensure the user agent is set in the request.
*/
case badUserAgent
/**
Too many hosts (more than 20) specified in an update. Also returned if trying to update a round robin (which is not allowed).
*/
case tooManyHosts
/**
Dynamic DNS access for the hostname has been blocked due to failure to interpret previous responses correctly.
*/
case abuse
/**
An error happened on the server end. Wait 5 minutes and retry.
*/
case unexpectedServerError
/**
A custom A or AAAA resource record conflicts with the update. Delete the indicated resource record within the DNS settings page and try the update again.
*/
case conflict
case noResponseString
case unknown(String)
/// The IP addresses reported after update are not the same as the requested
case ipMismatch
init(rawValue: String) {
switch rawValue {
case "good": self = .success
case "nochg": self = .noChange
case "nohost": self = .invalidHostname
case "badauth": self = .badAuthentication
case "notfqdn": self = .notFullyQualifiedDomainName
case "badagent": self = .badUserAgent
case "abuse": self = .abuse
case "numhost": self = .tooManyHosts
case "911": self = .unexpectedServerError
case "conflict": self = .conflict
case "nostring": self = .noResponseString
case "mismatch": self = .ipMismatch
default: self = .unknown(rawValue)
}
}
var rawValue: String {
switch self {
case .success: return "good"
case .noChange: return "nochg"
case .invalidHostname: return "nohost"
case .badAuthentication: return "badauth"
case .notFullyQualifiedDomainName: return "notfqdn"
case .badUserAgent: return "badagent"
case .abuse: return "abuse"
case .tooManyHosts: return "numhost"
case .unexpectedServerError: return "911"
case .conflict: return "conflict"
case .noResponseString: return "nostring"
case .ipMismatch: return "mismatch"
case .unknown(let string): return string
}
}
init(line: String, expecting addresses: Set<String>) {
let parts = line.components(separatedBy: " ").compactMap { $0.trimmed }
self.init(rawValue: parts[0])
if case .unknown = self {
// Insert full response for better logging
self = .unknown(line)
return
}
guard isSuccessStatus else {
return
}
guard Set(parts.dropFirst()) != addresses else {
return
}
self = .ipMismatch
}
var isSuccessStatus: Bool {
switch self {
case .success, .noChange, .noResponseString, .ipMismatch:
// Treat no response and ip mismatch as success,
// otherwise we risk spamming the server
return true
default:
return false
}
}
var isServerError: Bool {
switch self {
case .invalidHostname, .badAuthentication, .notFullyQualifiedDomainName, .badUserAgent, .abuse, .unexpectedServerError, .conflict, .tooManyHosts:
return true
default: return false
}
}
var isClientError: Bool {
switch self {
case .noResponseString, .unknown, .ipMismatch:
return true
default: return false
}
}
func delayTime(previous time: TimeInterval) -> TimeInterval {
switch self {
case .invalidHostname, .badAuthentication, .notFullyQualifiedDomainName, .badUserAgent, .conflict, .noChange:
// Increase wait time by 10 minutes, up to 24 hours
return min(86400, time + 600)
case .abuse:
// Increase wait time by one hour, until abuse is lifted
return time + 3600
case .unknown:
// Don't spam on unknown errors
return time + 3600
default:
// Try again next time
return 0
}
}
}
extension DNSChangeResponse: CustomStringConvertible {
var description: String {
switch self {
case .success: return "success"
case .noChange: return "No change"
case .invalidHostname: return "Invalid hostname"
case .badAuthentication: return "Bad authentication"
case .notFullyQualifiedDomainName: return "Not FQDN"
case .badUserAgent: return "Bad user agent"
case .tooManyHosts: return "Too many hosts"
case .abuse: return "Abuse"
case .unexpectedServerError: return "Server error"
case .conflict: return "Conflict"
case .noResponseString: return "No response string"
case .unknown(let string): return string
case .ipMismatch: return "IP mismatch"
}
}
}