diff options
author | Indrajit Raychaudhuri <irc@indrajit.com> | 2023-04-23 18:25:21 -0500 |
---|---|---|
committer | Indrajit Raychaudhuri <irc@indrajit.com> | 2023-06-03 21:54:03 -0500 |
commit | f2392055a33e537ce88c6a3124106bfd3b8b8e57 (patch) | |
tree | 6dce2e96ad5a672a7bc7bd11d4537199b687dd89 /src | |
parent | 12b7da289665c75c3839b32c77a491d8909f3e73 (diff) | |
download | vyos-1x-f2392055a33e537ce88c6a3124106bfd3b8b8e57.tar.gz vyos-1x-f2392055a33e537ce88c6a3124106bfd3b8b8e57.zip |
dns: T5144: Modernize dynamic dns operation
Apply next round of configuration tree updates to 'service dns dynamic'
with the following changes:
- Migrate `service dns dynamic interface <interface> [use-web]`
to `service dns dynamic address <interface>`
or `service dns dynamic address web [web-options]`
This communicates the intent that dynamic dns IP address is detected
in only one way - using the `<interface>` or using an external web
request, not both.
- When using external web request, (`service dns dynamic address web`),
external url is optional (`web-options url`). Ddclient defaults are
used when unspecified,
- Rename all config `login` to `username` for consistency and also to
align better with alternative ddclient backends in consideration.
- Apply global 'ipv6-enable' to per service 'ip-version: ipv6'. Selecting
usage of IPv4 or IPv6 (or both simultaneously) is now at per service
(protocol) level instead of global level. This allows more control on
the ability to select IPv4 in some cases and IPv6 in some other cases
wherever supported by the underlying ddclient protocol.
- While the IP address (and by extension, the detection mechanism) is
global, the way it is applied to a particular ddclient protocol depends
on whether it supports IPv4 or IPv6 or both.
- Related to the above, this also prevents generating incorrect config
file (`ddclient.conf`) with multiple global sections leading to an
unpredictable behavior of ddclient.
- Implement provider (protocol) specific custom tweaks whenever possible
(e.g., `zone`, `username`, `server` are not necessary in all cases).
- Move service name from a combination of 'protocol' (with protocol
config autodetected) and custom (with protocol config specified) to a
single 'service' key. This allows for consisent setup of multiple
config for the same ddclient protocol (with different options and
credentials). This also avoid ambiguity with usual networking term
'protocol' and ddclient specific term 'protocol' (and can change with
a move to a different backend).
- Apply upfront XML constraints and validations consistently wherever
applicable.
- RFC2136 specific change: Rename rfc2136 config `record` to `host-name`
for consistency.
- Cloudflare specific change: While ddclient still supports authenticating
with email and global auth key, skipping `username` in config will
indicate the intent to use API token authentication (with special
'token' literal as `username`).
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/dynamic_dns.py | 118 |
1 files changed, 45 insertions, 73 deletions
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 426e3d693..879086cc4 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -29,20 +29,14 @@ airbag.enable() config_file = r'/run/ddclient/ddclient.conf' -# Mapping of service name to service protocol -default_service_protocol = { - 'afraid': 'freedns', - 'changeip': 'changeip', - 'cloudflare': 'cloudflare', - 'dnspark': 'dnspark', - 'dslreports': 'dslreports1', - 'dyndns': 'dyndns2', - 'easydns': 'easydns', - 'namecheap': 'namecheap', - 'noip': 'noip', - 'sitelutions': 'sitelutions', - 'zoneedit': 'zoneedit1' -} +# Protocols that require zone +zone_allowed = ['cloudflare', 'godaddy', 'hetzner', 'gandi', 'nfsn'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'dyndns2', 'freedns', 'njalla'] def get_config(config=None): if config: @@ -56,24 +50,13 @@ def get_config(config=None): dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - for interface in dyndns['interface']: - if 'service' in dyndns['interface'][interface]: - # 'Autodetect' protocol used by DynDNS service - for service in dyndns['interface'][interface]['service']: - if service in default_service_protocol: - dyndns['interface'][interface]['service'][service].update( - {'protocol' : default_service_protocol.get(service)}) - else: - dyndns['interface'][interface]['service'][service].update( - {'custom': ''}) - - if 'rfc2136' in dyndns['interface'][interface]: - default_values = defaults(base_level + ['interface', 'rfc2136']) - for rfc2136 in dyndns['interface'][interface]['rfc2136']: - dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge( - default_values, dyndns['interface'][interface]['rfc2136'][rfc2136]) + for address in dyndns['address']: + # Apply service specific defaults (stype = ['rfc2136', 'service']) + for svc_type in dyndns['address'][address]: + default_values = defaults(base_level + ['address', svc_type]) + for svc_cfg in dyndns['address'][address][svc_type]: + dyndns['address'][address][svc_type][svc_cfg] = dict_merge( + default_values, dyndns['address'][address][svc_type][svc_cfg]) return dyndns @@ -82,48 +65,37 @@ def verify(dyndns): if not dyndns: return None - # A 'node' corresponds to an interface - if 'interface' not in dyndns: - return None - - for interface in dyndns['interface']: + for address in dyndns['address']: # RFC2136 - configuration validation - if 'rfc2136' in dyndns['interface'][interface]: - for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items(): - - for tmp in ['record', 'zone', 'server', 'key']: - if tmp not in config: - raise ConfigError(f'"{tmp}" required for rfc2136 based ' - f'DynDNS service on "{interface}"') - - if not os.path.isfile(config['key']): - raise ConfigError(f'"key"-file not found for rfc2136 based ' - f'DynDNS service on "{interface}"') - - # DynDNS service provider - configuration validation - if 'service' in dyndns['interface'][interface]: - for service, config in dyndns['interface'][interface]['service'].items(): - error_msg = f'required for DynDNS service "{service}" on "{interface}"' - if 'host_name' not in config: - raise ConfigError(f'"host-name" {error_msg}') - - if 'login' not in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare') - - if 'password' not in config: - raise ConfigError(f'"password" {error_msg}') - - if 'zone' in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"zone" option only supported with CloudFlare') - - if 'custom' in config: - if 'protocol' not in config: - raise ConfigError(f'"protocol" {error_msg}') - - if 'server' not in config: - raise ConfigError(f'"server" {error_msg}') + if 'rfc2136' in dyndns['address'][address]: + for config in dyndns['address'][address]['rfc2136'].values(): + for field in ['host_name', 'zone', 'server', 'key']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 ' + f'based Dynamic DNS service on "{address}"') + + # Dynamic DNS service provider - configuration validation + if 'service' in dyndns['address'][address]: + for service, config in dyndns['address'][address]['service'].items(): + error_msg = f'is required for Dynamic DNS service "{service}" on "{address}"' + + for field in ['host_name', 'password', 'protocol']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg}') + + if config['protocol'] in zone_allowed and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg}') + + if config['protocol'] not in zone_allowed and 'zone' in config: + raise ConfigError(f'"{config["protocol"]}" does not support "zone"') + + if config['protocol'] not in username_unnecessary: + if 'username' not in config: + raise ConfigError(f'"username" {error_msg}') + + if (config['protocol'] not in dualstack_supported + and config['ip_version'] == 'both'): + raise ConfigError(f'"{config["protocol"]}" does not support IPv4 and IPv6 at the same time') return None |