diff --git a/docs/ipv6-design-doc.md b/docs/ipv6-design-doc.md new file mode 100644 index 0000000..266da9e --- /dev/null +++ b/docs/ipv6-design-doc.md @@ -0,0 +1,337 @@ +# Design Doc: IPv6 Support + +Author: [@rhansen](https://github.com/rhansen/)\ +Date: 2020-06-09\ +Signed off by: +[@SuperSandro2000](https://github.com/SuperSandro2000/) + +## Objective + +Add full IPv6 support to ddclient, including support for dual-stack +systems. + +## Background + +ddclient's current IPv6 support is limited: + + * Users can update either an IPv6 record or an IPv4 record for a + host, not both. + * If SSL is used for an HTTP request, IPv6 will be used if the + remote host has a AAAA record, even if the user would rather use + IPv4. This breaks `use=web` for IPv4 if the `web` URL's host has a + AAAA record. + * The `use=if` method only works if the user sets `if-skip` to + something that skips over all IPv4 addresses in the output of + `ifconfig` (or `ip`). If the output contains an IPv4 address after + the IPv6 address then `use=if` cannot be used for IPv6. + * There is no support for falling back to IPv4 if an IPv6 connection + fails. + * `use=if` does not filter out locally scoped or temporary IPv6 + addresses. + +Some attempts have been made to add more robust IPv6 support: + + * Debian's ddclient package applies a + [patch](https://salsa.debian.org/debian/ddclient/-/blob/67a138aa3d98d70f01766123f58ef40e98693fd4/debian/patches/usev6.diff) + that adds a new `usev6` option. The `usev6` option can be set to + `ip` or `if`, but not any of the other strategies currently + available for the `use` option (`web`, `cmd`, `fw`, `cisco`, + `cisco-asa`). When set to `ip` or `if`, only IPv6 addresses are + considered; IPv4 addresses are ignored. The patch does not change + the behavior of the `use` option, so `use=web` or `use=cmd` can be + used for IPv6 if pointed at something that only outputs an IPv6 + address. + * [ddclient-curl](https://github.com/astlinux-project/ddclient-curl) + is a fork of ddclient that uses curl as the HTTP client (instead + of ddclient's own homemade client) for more robust IPv6 support. + * PR #40 is perhaps the most comprehensive attempt at adding full + IPv6 support, but it was never merged and has since + bit-rotted. There is renewed effort to rebase the changes and get + them merged in. PR #40 adds new options and changes some existing + options. The approach taken is to completely isolate IPv4 address + detection from IPv6 address detection and require the update + protocol callbacks to handle each type of address appropriately. + +## Requirements + + * The mechanism for determining the current IPv4 address (the `use` + option) must be independently configurable from the mechanism used + to determine the current IPv6 address. + * The user must be able to disable IPv4 address updates without + affecting IPv6 updates. + * The user must be able to disable IPv6 address updates without + affecting IPv4 updates. + * If HTTP polling is used for both IPv4 and IPv6 address discovery, + the URL used to determine the IPv4 address (the `web` option) must + be independently configurable from the URL used to determine the + IPv6 address. + * The use of IPv4 or IPv6 to update a record must be independent of + the type of record being updated (IPv4 or IPv6). + * The callback for the update protocol must be given both addresses, + even if only one of the two addresses has changed. + * The callback for the update protocol must be told which addresses + have changed. + * There must be IPv6 equivalents to `use=ip`, `use=if`, `use=web`, + and `use=cmd`. For the IPv6 equivalent to `use=if`, it is + acceptable to ignore non-global and temporary addresses (the user + can always use the IPv6 equivalent to `use=cmd` to get non-global + or temporary addresses). + * Existing support for updating IPv6 records must not be lost. + * Some dynamic DNS service providers use separate credentials for + the IPv4 and IPv6 records. These providers must be supported, + either by accepting both sets of credentials in a single host's + configuration or by allowing the user to specify the same host + twice, once for IPv4 and once for IPv6. + +### Nice-to-Haves + + * The user should be able to force the update protocol to use IPv4 + or IPv6. + * Unless configured otherwise, ddclient should first attempt to + update via IPv6 and fall back to IPv4 if the IPv6 connection + fails. This behavior can be added later; for now it is acceptable + to keep the current behavior (use IPv6 without IPv4 fallback if + there is a AAAA record, use IPv4 if there is no AAAA record). + * Full backwards compatibility with existing config files and + flags. The trade-offs between migration burden, long-term + usability, and code maintenance should be carefully considered. + * IPv6 equivalents to `use=fw`, `use=cisco`, and `use=cisco-asa`. + * Add IPv6 support in protocol callbacks where IPv6 support is + currently missing. (This can be done later.) + +## Proposal + +### Configuration changes + + * Add new `usev4` and `usev6` settings that are like the current + `use` setting except they only apply to IPv4 and IPv6, + respectively. + * `usev4` can be set to one of the following values: `disabled`, + `ipv4`, `webv4`, `fwv4`, `ifv4`, `cmdv4`, `ciscov4`, + `cisco-asav4` + * `usev6` can be set to one of the following values: `disabled`, + `ipv6`, `webv6`, `fwv6`, `ifv6`, `cmdv6`, `ciscov6`, + `cisco-asav6` + * Add a new `use` strategy: `disabled`. + * The `disabled` value for `use`, `usev4`, and `usev6` causes + ddclient to act as if it was never set. This is useful for + overriding the global value for a particular host. + * For compatibility with ddclient-curl, `no` is a deprecated alias + of `disabled`. + * Add new `ipv4`, `ipv6`, `webv4`, `webv4-skip`, `webv6`, + `webv6-skip`, `ifv4`, `ifv6`, `cmdv4`, `cmdv6`, etc. settings that + behave like their versionless counterparts except they only apply + to IPv4 or IPv6. Deprecate the versionless counterparts, and + change their behavior so that they also influence the default + value of the versioned options. (Example: Suppose + `usev4=ifv4`. If `ifv4` is not set then `if` is used.) Special + notes: + * The value of `ip` will only serve as the default for `ipv4` + (or `ipv6`) if it contains an IPv4 (or IPv6) address. + * There is currently an `ipv6` boolean setting. To preserve + backward compatibility with existing configs, `ipv6` set to a + boolean value is ignored (other than a warning). + * There is no `ifv4-skip` or `ifv6-skip` because it's ddclient's + responsibility to properly parse the output of whatever tool + it uses to read the interface's addresses. + * For now there is no `cmdv4-skip` or `cmdv6-skip`. Anyone who + already knows how to write a regular expression can probably + write a wrapper script. These may be added in the future if + users request them, especially if it facilitates migration + away from the deprecated `cmd-skip` setting. + * For `usev6=ifv6`, interfaces are likely to have several IPv6 + addresses (unlike IPv4). Choosing the "right" IPv6 address is + not trivial. Fortunately, we don't have to solve this + perfectly right now; we can choose something that mostly + works and let user bug reports guide future refinements. For + the first iteration, we will try the following: + * Ignore addresses that are not global unicast. + (Unfortunately, the `ip` command from iproute2 does not + provide a way to filter out ULA addresses so we will have + to do this ourselves.) + * Ignore temporary addresses. + * If no addresses remain, log a warning and don't update the + IPv6 record. + * Otherwise, if one of the remaining addresses matches the + previously selected address, continue to use it. + * Otherwise, select one arbitrarily. + * Deprecate the `use` setting (print a loud warning) but keep its + existing semantics with an exception: If there is a conflict with + `usev4` or `usev6` then those take priority: + * If `use`, `usev4`, and `usev6` are all set then a warning is + logged and the `use` setting is ignored. + * If `use` and `usev4` are both set and the `use` strategy + discovers an IPv4 address that differs from the address + discovered by the `usev4` strategy, then the address from + `usev4` is used and a warning is logged. + * If `use` and `usev6` are both set and the `use` strategy + discovers an IPv6 address that differs from the address + discovered by the `usev6` strategy, then the address from + `usev6` is used and a warning is logged. + * If `usev4` (`usev6`) is not set: + * If `ipv4` (`usev6`) is set, ddclient acts as if `usev4` + (`usev6`) was set to `ipv4` (`ipv6`). + * Otherwise, if `ifv4` (`ifv6`) is set, ddclient acts as if + `usev4` (`usev6`) was set to `ifv4` (`ifv6`). + * Otherwise, if `cmdv4` (`cmdv6`) is set, ddclient acts as if + `usev4` (`usev6`) was set to `cmdv4` (`cmdv6`). + * Otherwise, if `fwv4` (`fwv6`) is set, ddclient acts as if + `usev4` (`usev6`) was set to `fwv4` (`fwv6`). + * Otherwise, `usev4` (`usev6`) remains unset. + * To support separate credentials for IPv4 vs. IPv6 updates, users + can specify the same host multiple times, each time with different + options. + +### Internal API changes + + * Add two new entries to the `$config{$host}` hash: + * `$config{$host}{'wantipv4'}` is set to: + * If `usev4` is enabled, the IPv4 address discovered by the + `usev4` strategy. + * Otherwise, if `use` is enabled and the `use` strategy + discovered an IPv4 address, the IPv4 address discovered by + the `use` strategy. + * Otherwise, `undef`. + * `$config{$host}{'wantipv6'}` is set to: + * If `usev6` is enabled, the IPv6 address discovered by the + `usev6` strategy. + * Otherwise, if `use` is enabled and the `use` strategy + discovered an IPv6 address, the IPv6 address discovered by + the `use` strategy. + * Otherwise, `undef`. + * Deprecate the existing `$config{$host}{'wantip'}` entry, to be + removed after all update protocol callbacks have been updated to + use the above new entries. In the meantime, this entry's value + depends on which of `use`, `usev4`, and `usev6` is enabled, and + what type of IP address is discovered by the `use` strategy (if + enabled), according to the following table: + + | `use` | `usev4` | `usev6` | resulting value | + | :---: | :---: | :---: | :--- | + | ✔(IPv4) | ✖ | ✖ | the IPv4 address discovered by the `use` strategy | + | ✔(IPv6) | ✖ | ✖ | the IPv6 address discovered by the `use` strategy | + | ✖ | ✔ | ✖ | the IPv4 address discovered by the `usev4` strategy | + | ✖ | ✖ | ✔ | the IPv6 address discovered by the `usev6` strategy | + | ✔(IPv4) | ✔ | ✖ | the IPv4 address discovered by the `usev4` strategy (and log another warning if it doesn't match the IPv4 address found by the `use` strategy) | + | ✔(IPv6) | ✔ | ✖ | the IPv6 address discovered by the `use` strategy | + | ✔(IPv4) | ✖ | ✔ | the IPv4 address discovered by the `use` strategy | + | ✔(IPv6) | ✖ | ✔ | the IPv6 address discovered by the `usev6` strategy (and log another warning if it doesn't match the IPv6 address found by the `use` strategy) | + + * To support separate credentials for IPv4 vs. IPv6 updates, convert + the `%config` hash of host configs into a list of host configs. A + second definition for the same host adds a second entry rather + than overwrites the existing entry. + +## Alternatives Considered + +### Repurpose the existing settings for v4 + +Rather than create new `usev4`, `ifv4`, `cmdv4`, etc. settings, +repurpose the existing `use`, `if`, `cmd`, etc. settings for IPv4. + +Why this was rejected: + * There is a usability advantage to the symmetry with the `v6` + settings. + * It is easier to remain compatible with existing configurations. + +### Let `use` set the default for `usev4` + +Rather than three separate IP discovery mechanisms (`use`, `usev4`, +and `usev6`), have just two (`usev4` and `usev6`) and let the old +`use` setting control the default for `usev4`: If `usev4` is not set, +then `use=foo` is equivalent to `usev4=foov4`. + +Why this was rejected: Backwards incompatibility. Specifically, +configurations that previously updated an IPv6 record would instead +(attempt to) update an IPv4 record. + +### Let `use` set the default for `usev4` and `usev6` + +Rather than three separate IP discovery mechanisms (`use`, `usev4`, +and `usev6`), have just two (`usev4` and `usev6`) and let the old +`use` setting control the default for `usev4` and `usev6`: + + * If neither `usev4` nor `usev6` is set, then `use=foo` is + equivalent to `usev4=foov4,usev6=foov6`. + * If `usev4` is set but not `usev6`, then `use=foo` is equivalent to + `usev6=foov6`. + * If `usev6` is set but not `usev4`, then `use=foo` is equivalent to + `usev4=foov4`. + * If both `usev4` and `usev6` are set, then `use=foo` is ignored. + +Why this was rejected: The new design would cause existing +configurations to trigger surprising, and possibly undesired (e.g., +timeouts or update errors), new behavior: + + * Configurations that previously updated only an IPv4 record would + also update an IPv6 record. + * Similarly, configurations that previously updated only an IPv6 + record would also update an IPv4 record. + +### Replace uses of `'wantip'` with `'wantipv4'` + +Rather than support `'wantip'`, `'wantipv4'`, and `'wantipv6'`, just +replace all `'wantip'` references to `'wantipv4'`. + +Why this was rejected: This would break compatibility for users that +are currently updating IPv6 addresses. (Compatibility would be +restored once the update protocol callbacks are updated to honor +`'wantipv6'`.) + +### Single `if` setting for both `usev4=if` and `usev6=if` + +The proposed design calls for separate `ifv4` and `ifv6` settings. If +the user sets `usev4=if,usev6=if`, then the user most likely wants to +use the same interface for both IPv4 and IPv6. Rather than create +separate `ifv4` and `ifv6` settings, have a single `if` setting used +for both `usev4` and `usev6`. + +Why this was rejected: + * Separate `v4` and `v6` settings adds consistency to the + configuration. + * There are cases where a user will want to use a different + interface. In particular, an IPv6 over IPv4 tunnel (e.g., + https://tunnelbroker.net) involves creating a separate interface + that is used only for IPv6. + +### Separate IPv4 and IPv6 credentials + +In order to support providers that use separate credentials for IPv4 +and IPv6 updates, the proposed design allows the user to define the +same host twice. We could instead add additional options so that the +user can provide both sets of credentials in a single host definition. + +Why this was rejected: + * The proposed design is easier to implement, as it does not require + any modifications to existing protocol implementations. + * The proposed design is less likely to cause problems for users + that rely on globals instead of host-specific options. For + example, a configuration file like the following might not do what + the user expects: + + ``` + ssl=true, use=if, if=eth0 + + protocol=foo + login=username-for-ipv4 + password=password-for-ipv4 + loginv6=username-for-ipv6 + passwordv6=password-for-ipv6 + myhost.example.com + + protocol=bar + login=username + password=password + # This host definition will use loginv6, passwordv6 from above + # because the user didn't end each setting with a line + # continuation: + my-other-host.example.com + ``` + + * The proposed design provides some bonus functionality: + * Users can smoothly transition between different providers by + updating both providers simultaneously until the domain + registration switches to the new registrar. + * Users can take advantage of providers that support multiple A + or multiple AAAA records for the same hostname, assuming each + record has independent credentials.