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

184 lines
5.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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