From 0b10eefcc812ed9dd5d05124e0e7bf05fb4c49a4 Mon Sep 17 00:00:00 2001 From: Oliver Kraitschy Date: Wed, 11 Jul 2018 10:52:25 +0200 Subject: [PATCH] Add ddns settings to the OpenWRT backend --- docs/source/backends/openwrt.rst | 159 ++++++++++++- .../backends/openwrt/converters/__init__.py | 2 + .../backends/openwrt/converters/ddns.py | 47 ++++ netjsonconfig/backends/openwrt/openwrt.py | 1 + netjsonconfig/backends/openwrt/schema.py | 209 +++++++++++++++++- tests/openwrt/test_ddns.py | 37 ++++ 6 files changed, 451 insertions(+), 4 deletions(-) create mode 100644 netjsonconfig/backends/openwrt/converters/ddns.py create mode 100644 tests/openwrt/test_ddns.py diff --git a/docs/source/backends/openwrt.rst b/docs/source/backends/openwrt.rst index 3ddfacbde..708d4aae1 100644 --- a/docs/source/backends/openwrt.rst +++ b/docs/source/backends/openwrt.rst @@ -376,7 +376,12 @@ Will be rendered as follows:: package network - config interface 'eth0' option ifname 'eth0' option ip6addr 'fdb4:5f35:e8fd::1/48' option ipaddr '10.27.251.1' option netmask '255.255.255.0' option proto 'static' + config interface 'eth0' + option ifname 'eth0' + option ip6addr 'fdb4:5f35:e8fd::1/48' + option ipaddr '10.27.251.1' + option netmask '255.255.255.0' + option proto 'static' DNS servers and search domains ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -431,7 +436,22 @@ Will return the following UCI output:: package network - config interface 'eth0' option dns '10.11.12.13 8.8.8.8' option dns_search 'openwisp.org netjson.org' option ifname 'eth0' option ipaddr '192.168.1.1' option netmask '255.255.255.0' option proto 'static' config interface 'eth1' option dns_search 'openwisp.org netjson.org' option ifname 'eth1' option proto 'dhcp' config interface 'eth1_31' option ifname 'eth1.31' option proto 'none' + config interface 'eth0' + option dns '10.11.12.13 8.8.8.8' + option dns_search 'openwisp.org netjson.org' + option ifname 'eth0' + option ipaddr '192.168.1.1' + option netmask '255.255.255.0' + option proto 'static' + + config interface 'eth1' + option dns_search 'openwisp.org netjson.org' + option ifname 'eth1' + option proto 'dhcp' + + config interface 'eth1_31' + option ifname 'eth1.31' + option proto 'none' DHCP ipv6 ethernet interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1815,6 +1835,141 @@ Will be rendered as follows:: option sysfs 'tp-link:blue:wlan2g' option trigger 'phy0tpt' +DDNS settings +------------- + +The ddns settings reside in the ``ddns`` key of the *configuration dictionary*, +which is a custom NetJSON extension not present in the original NetJSON RFC. + +The ``ddns`` key must contain a dictionary, the allowed keys are: + ++---------------------+---------+ +| key name | type | ++=====================+=========+ +| ``upd_privateip`` | boolean | ++---------------------+---------+ +| ``ddns_dateformat`` | string | ++---------------------+---------+ +| ``ddns_rundir`` | string | ++---------------------+---------+ +| ``ddns_logdir`` | string | ++---------------------+---------+ +| ``ddns_loglines`` | integer | ++---------------------+---------+ +| ``use_curl`` | boolean | ++---------------------+---------+ +| ``providers`` | list | ++---------------------+---------+ + +The ``providers`` key itself contains a list of dictionaries, the allowed keys are: + ++---------------------+---------+ +| key name | type | ++=====================+=========+ +| ``enabled`` | boolean | ++---------------------+---------+ +| ``interface`` | string | ++---------------------+---------+ +| ``ip_source`` | string | ++---------------------+---------+ +| ``lookup_host`` | string | ++---------------------+---------+ +| ``domain`` | string | ++---------------------+---------+ +| ``username`` | string | ++---------------------+---------+ +| ``password`` | string | ++---------------------+---------+ +| ``service_name`` | string | ++---------------------+---------+ +| ``update_url`` | string | ++---------------------+---------+ +| ``update_script`` | string | ++---------------------+---------+ +| ``ip_network`` | string | ++---------------------+---------+ +| ``ip_url`` | string | ++---------------------+---------+ +| ``ip_interface`` | string | ++---------------------+---------+ +| ``ip_script`` | string | ++---------------------+---------+ +| ``use_syslog`` | integer | ++---------------------+---------+ +| ``use_logfile`` | boolean | ++---------------------+---------+ + +The required keys are: + +* ``enabled`` +* ``interface`` +* ``ip_source`` +* ``lookup_host`` +* ``domain`` +* ``username`` +* ``password`` + +For the function and meaning of each key consult the relevant +`OpenWrt documentation about ddns directives `_. + +DDNS settings example +~~~~~~~~~~~~~~~~~~~~~ + +The following *configuration dictionary*: + +.. code-block:: python + + { + "ddns": { + "ddns_logdir": "/var/log/ddns", + "ddns_rundir": "/var/run/ddns", + "use_curl": false, + "upd_privateip": false, + "ddns_dateformat": "%F %R", + "ddns_loglines": 250, + "providers": [ + { + "enabled": true, + "lookup_host": "myhost.dyndns.org", + "service_name": "dyndns.org", + "domain": "myhost.dyndns.org", + "username": "myuser", + "password": "mypassword", + "use_logfile": true, + "ip_source": "interface", + "ip_interface": "pppoe-xdsl", + "use_syslog": 2, + "interface": "xdsl" + } + ], + } + } + +Will be rendered as follows:: + + package ddns + + config ddns 'global' + option ddns_logdir '/var/log/ddns' + option ddns_rundir '/var/run/ddns' + option use_curl '0' + option upd_privateip '0' + option ddns_dateformat '%F %R' + option ddns_loglines '250' + + config service 'myhost_dyndns_org' + option enabled '1' + option lookup_host 'myhost.dyndns.org' + option service_name 'dyndns.org' + option domain 'myhost.dyndns.org' + option username 'myuser' + option password 'mypassword' + option use_logfile '1' + option ip_source 'interface' + option ip_interface 'pppoe-xdsl' + option use_syslog '2' + option interface 'xdsl' + Including custom options ------------------------ diff --git a/netjsonconfig/backends/openwrt/converters/__init__.py b/netjsonconfig/backends/openwrt/converters/__init__.py index 2eb379a80..ea54b61c3 100644 --- a/netjsonconfig/backends/openwrt/converters/__init__.py +++ b/netjsonconfig/backends/openwrt/converters/__init__.py @@ -9,6 +9,7 @@ from .rules import Rules from .switch import Switch from .wireless import Wireless +from .ddns import Ddns __all__ = [ 'Default', @@ -22,4 +23,5 @@ 'Rules', 'Switch', 'Wireless', + 'Ddns', ] diff --git a/netjsonconfig/backends/openwrt/converters/ddns.py b/netjsonconfig/backends/openwrt/converters/ddns.py new file mode 100644 index 000000000..1f5f530a1 --- /dev/null +++ b/netjsonconfig/backends/openwrt/converters/ddns.py @@ -0,0 +1,47 @@ +from collections import OrderedDict + +from ..schema import schema +from .base import OpenWrtConverter + + +class Ddns(OpenWrtConverter): + netjson_key = 'ddns' + intermediate_key = 'ddns' + _uci_types = ['ddns', 'service'] + _schema = schema['properties']['ddns'] + + def to_intermediate_loop(self, block, result, index=None): + if block: + provider_list = self.__intermediate_providers(block.pop('providers', {})) + block.update({ + '.type': 'ddns', + '.name': block.pop('id', 'global'), + }) + result.setdefault('ddns', []) + result['ddns'] = [self.sorted_dict(block)] + provider_list + return result + + def __intermediate_providers(self, providers): + """ + converts NetJSON provider to + UCI intermediate data structure + """ + result = [] + for provider in providers: + uci_name = self._get_uci_name(provider['lookup_host']) + resultdict = OrderedDict((('.name', uci_name), + ('.type', 'service'))) + resultdict.update(provider) + result.append(resultdict) + return result + + def to_netjson_loop(self, block, result, index): + result['ddns'] = self.__netjson_ddns(block) + return result + + def __netjson_ddns(self, ddns): + del ddns['.type'] + _name = ddns.pop('.name') + if _name != 'ddns': + ddns['id'] = _name + return self.type_cast(ddns) diff --git a/netjsonconfig/backends/openwrt/openwrt.py b/netjsonconfig/backends/openwrt/openwrt.py index b6dc64934..771cf72d1 100644 --- a/netjsonconfig/backends/openwrt/openwrt.py +++ b/netjsonconfig/backends/openwrt/openwrt.py @@ -22,6 +22,7 @@ class OpenWrt(BaseBackend): converters.Radios, converters.Wireless, converters.OpenVpn, + converters.Ddns, converters.Default, ] parser = OpenWrtParser diff --git a/netjsonconfig/backends/openwrt/schema.py b/netjsonconfig/backends/openwrt/schema.py index 9d09b8ea6..4a22e0ac6 100644 --- a/netjsonconfig/backends/openwrt/schema.py +++ b/netjsonconfig/backends/openwrt/schema.py @@ -386,10 +386,215 @@ }, }, }, - }, - }, + "ddns": { + "type": "object", + "title": "DDNS Settings", + "additionalProperties": True, + "propertyOrder": 11, + "properties": { + "upd_privateip": { + "type": "boolean", + "title": "upd_privateip", + "description": "disallow/allow sending of private/special IP's to the DDNS provider; " + "blocked IPv4: 0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, " + "192.168/16; blocked IPv6: ::/32, f000::/4", + "default": False, + "format": "checkbox", + "propertyOrder": 1, + }, + "ddns_dateformat": { + "type": "string", + "title": "ddns_dateformat", + "description": "date format to use for displaying dates in logfiles and LuCI", + "default": "%F %R", + "propertyOrder": 2, + }, + "ddns_rundir": { + "type": "string", + "title": "ddns_rundir", + "description": "directory to use for *.pid and *.update files", + "default": "/var/run/ddns", + "propertyOrder": 3, + }, + "ddns_logdir": { + "type": "string", + "title": "ddns_logdir", + "description": "directory to use for *.log files", + "default": "/var/log/ddns", + "propertyOrder": 4, + }, + "ddns_loglines": { + "type": "integer", + "title": "ddns_loglines", + "description": "number of lines stored in *.log files before automatically truncated", + "default": 250, + "propertyOrder": 5, + }, + "use_curl": { + "type": "boolean", + "title": "use_curl", + "description": "if both wget and curl are installed, wget is used for communication " + "by default", + "default": False, + "format": "checkbox", + "propertyOrder": 6, + }, + "providers": { + "type": "array", + "title": "Service Providers", + "uniqueItems": True, + "additionalItems": True, + "propertyOrder": 7, + "items": { + "type": "object", + "title": "DDNS provider", + "additionalProperties": True, + "required": [ + "enabled", + "interface", + "ip_source", + "lookup_host", + "domain", + "username", + "password", + ], + "properties": { + "enabled": { + "type": "boolean", + "title": "enabled", + "default": False, + "format": "checkbox", + "propertyOrder": 1, + }, + "interface": { + "type": "string", + "title": "interface", + "description": "network from /etc/config/network to monitor for up/down " + "events to start the ddns update script via hotplug", + "propertyOrder": 2, + }, + "ip_source": { + "type": "string", + "title": "ip_source", + "description": "specifies the source to detect the local IP: 'network' uses " + "'ip_network', 'web' uses 'ip_url', 'interface' uses " + "'ip_interface', 'script' uses 'ip_script'", + "enum": [ + "network", + "web", + "interface", + "script" + ], + "default": "network", + "propertyOrder": 3, + }, + "lookup_host": { + "type": "string", + "title": "lookup_host", + "description": "FQDN of the host registered at the DDNS provider", + "propertyOrder": 4, + }, + "domain": { + "type": "string", + "title": "domain", + "description": "the DNS name to update; this property can also be used for " + "special multihost update configurations supported by" + " some providers", + "propertyOrder": 5, + }, + "username": { + "type": "string", + "title": "username", + "description": "username of the DDNS service account", + "propertyOrder": 6, + }, + "password": { + "type": "string", + "title": "password", + "description": "password of the DDNS service account", + "propertyOrder": 7, + }, + "service_name": { + "type": "string", + "title": "service_name", + "description": "name of the DDNS service to use", + "propertyOrder": 8, + }, + "update_url": { + "type": "string", + "title": "update_url", + "description": "url to the DDNS service to use if 'service_name' is not set", + "propertyOrder": 9, + }, + "update_script": { + "type": "string", + "title": "update_script", + "description": "script to use if 'service_name' is not set", + "propertyOrder": 10, + }, + "ip_network": { + "type": "string", + "title": "ip_network", + "description": "network from /etc/config/network to use for detecting the IP " + "if 'ip_source' is set to 'network'", + "default": "wan", + "propertyOrder": 11, + }, + "ip_url": { + "type": "string", + "title": "ip_url", + "description": "url to use for detecting the IP if 'ip_source' is set to " + "'web'", + "propertyOrder": 12, + }, + "ip_interface": { + "type": "string", + "title": "ip_interface", + "description": "local interface to use for detecting the IP if 'ip_source' is" + " set to 'interface'", + "propertyOrder": 13, + }, + "ip_script": { + "type": "string", + "title": "ip_script", + "description": "script to use for detecting the IP if 'ip_source' is set to " + "'script'", + "propertyOrder": 14, + }, + "use_syslog": { + "type": "integer", + "title": "use_syslog", + "description": "level of events logged to syslog", + "enum": [0, 1, 2, 3, 4], + "options": { + "enum_titles": [ + "0 - disable", + "1 - info, notice, warning, errors", + "2 - notice, warning, errors", + "3 - warning, errors", + "4 - errors" + ] + }, + "default": 0, + "propertyOrder": 15, + }, + "use_logfile": { + "type": "boolean", + "title": "use_logfile", + "description": "disable/enable logging to logfile", + "default": True, + "propertyOrder": 16, + } + } + } + } + } + } + } + } ) + # add OpenVPN schema schema = merge_config(schema, base_openvpn_schema) # OpenVPN customizations for OpenWRT diff --git a/tests/openwrt/test_ddns.py b/tests/openwrt/test_ddns.py new file mode 100644 index 000000000..babf31dbe --- /dev/null +++ b/tests/openwrt/test_ddns.py @@ -0,0 +1,37 @@ +import unittest + +from netjsonconfig import OpenWrt +from netjsonconfig.utils import _TabsMixin + + +class TestDdns(unittest.TestCase, _TabsMixin): + maxDiff = None + _ddns_netjson_global = { + "ddns": { + "ddns_dateformat": "%F %R", + "ddns_logdir": "/var/log/ddns", + "ddns_loglines": 250, + "ddns_rundir": "/var/run/ddns", + "upd_privateip": False, + "use_curl": False + } + } + _ddns_uci_global = """package ddns + +config ddns 'global' + option ddns_dateformat '%F %R' + option ddns_logdir '/var/log/ddns' + option ddns_loglines '250' + option ddns_rundir '/var/run/ddns' + option upd_privateip '0' + option use_curl '0' +""" + + def test_render_ddns_global(self): + o = OpenWrt(self._ddns_netjson_global) + expected = self._tabs(self._ddns_uci_global) + self.assertEqual(o.render(), expected) + + def test_parse_ddns_global(self): + o = OpenWrt(native=self._ddns_uci_global) + self.assertDictEqual(o.config, self._ddns_netjson_global)