184 lines
5.7 KiB
Swift
184 lines
5.7 KiB
Swift
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 user’s 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 user’s 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"
|
||
}
|
||
}
|
||
}
|