From f0ebc86d545d61a15ecf922d8220152b511636be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20H=C3=B6ft?= Date: Mon, 18 Jul 2022 16:42:43 +0200 Subject: [PATCH] Support ipprefix via argument Fixes #353 --- CHANGES.rst | 3 +- src/nsupdate/api/_tests/test_api.py | 12 ++++++++ src/nsupdate/api/views.py | 29 ++++++++++++++----- .../includes/tabbed_router_configuration.html | 2 +- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 28520f21..5b192f50 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,7 @@ New Features: without causing database accesses. - django-admin faults: show/reset api auth faults counter - add api_auth_faults column to django admin's Hosts view +- support ipprefix argument to pass the netmask and prefix for related domains explicitly Fixes: @@ -326,7 +327,7 @@ Other changes: you can use custom templates for this) * added some ugly logos (if you can do better ones, please help) https://github.com/nsupdate-info/nsupdate.info/issues/78 -* replaced "SSL" by "TLS" everywhere. +* replaced "SSL" by "TLS" everywhere. SSL is the old/outdated name. Since 1999, it's called TLS. * updated to latest versions on CDN: jquery, bootstrap, font-awesome diff --git a/src/nsupdate/api/_tests/test_api.py b/src/nsupdate/api/_tests/test_api.py index f6e072b6..ff4a6432 100644 --- a/src/nsupdate/api/_tests/test_api.py +++ b/src/nsupdate/api/_tests/test_api.py @@ -150,6 +150,12 @@ def test_nic_update_authorized_myip_v4(client): # now check if it updated the ipv4 related hosts also: assert query_ns(TEST_HOST_RELATED, 'A') == '1.2.3.1' # 1.2.3.4/29 + 0.0.0.1 + # custom prefix (with custom netmask) + response = client.get(reverse('nic_update') + '?myip=4.3.2.1&ipprefix=1.2.3.4/8', + HTTP_AUTHORIZATION=make_basic_auth_header(TEST_HOST, TEST_SECRET)) + assert response.status_code == 200 + assert query_ns(TEST_HOST_RELATED, 'A') == '1.0.0.1' # 1.2.3.4/29 + 0.0.0.1 + def test_nic_update_authorized_myip_v6(client): response = client.get(reverse('nic_update') + '?myip=2000::2', @@ -170,6 +176,12 @@ def test_nic_update_authorized_myip_v6(client): # now check if it updated the ipv4 related hosts also: assert query_ns(TEST_HOST_RELATED, 'AAAA') == '2000::1' # 2000::3/64 + ::1 + # custom prefix (with custom netmask) + response = client.get(reverse('nic_update') + '?myip=2000::4&ipprefix=3000:ffff:ffff::/16', + HTTP_AUTHORIZATION=make_basic_auth_header(TEST_HOST, TEST_SECRET)) + assert response.status_code == 200 + assert query_ns(TEST_HOST_RELATED, 'AAAA') == '3000::1' # 3000::/16 + ::1 + @pytest.mark.requires_sequential def test_nic_update_authorized_update_other_services(client): diff --git a/src/nsupdate/api/views.py b/src/nsupdate/api/views.py index 7be115eb..8e7b9c86 100644 --- a/src/nsupdate/api/views.py +++ b/src/nsupdate/api/views.py @@ -251,8 +251,9 @@ def get(self, request, logger=None, delete=False): ipaddr = request.GET.get('myip') if not ipaddr: # None or '' ipaddr = normalize_ip(request.META.get('REMOTE_ADDR')) + ipprefix = request.GET.get('ipprefix') secure = request.is_secure() - return _update_or_delete(host, ipaddr, secure, logger=logger, _delete=delete) + return _update_or_delete(host, ipaddr, secure, ipprefix=ipprefix, logger=logger, _delete=delete) class NicDeleteView(NicUpdateView): @@ -306,8 +307,9 @@ def get(self, request, logger=None, delete=False): ipaddr = request.GET.get('myip') if not ipaddr: # None or empty string ipaddr = normalize_ip(request.META.get('REMOTE_ADDR')) + ipprefix = request.GET.get('ipprefix') secure = request.is_secure() - return _update_or_delete(host, ipaddr, secure, logger=logger, _delete=delete) + return _update_or_delete(host, ipaddr, secure, ipprefix=ipprefix, logger=logger, _delete=delete) class AuthorizedNicDeleteView(AuthorizedNicUpdateView): @@ -323,12 +325,13 @@ def get(self, request, logger=None, delete=True): return super(AuthorizedNicDeleteView, self).get(request, logger=logger, delete=delete) -def _update_or_delete(host, ipaddr, secure=False, logger=None, _delete=False): +def _update_or_delete(host, ipaddr, secure=False, ipprefix=None, logger=None, _delete=False): """ common code shared by the 2 update/delete views :param host: host object :param ipaddr: ip addr (v4 or v6) + :param ipprefix: ip6 prefix/netmask (v4 or v6, must be of same kind as ipaddr) :param secure: True if we use TLS/https :param logger: a logger object :param _delete: True for delete, False for update @@ -362,6 +365,13 @@ def _update_or_delete(host, ipaddr, secure=False, logger=None, _delete=False): kind = check_ip(ipaddr, ('ipv4', 'ipv6')) rdtype = 'A' if kind == 'ipv4' else 'AAAA' IPNetwork(ipaddr) # raise AddrFormatError here if there is an issue with ipaddr, see #394 + if ipprefix: + ipprefix = str(ipprefix) + # do not allow to mix the kinds + network = IPNetwork(ipprefix) + if check_ip(str(network.ip)) != kind: + logger.warning("network kind of prefix is wrong") + raise ValueError("invalid kind") except (ValueError, UnicodeError, AddrFormatError): # invalid ip address string # some people manage to even give a non-ascii string instead of an ip addr @@ -405,29 +415,32 @@ def _update_or_delete(host, ipaddr, secure=False, logger=None, _delete=False): # XXX unclear what to do for "other services" we relay updates to return Response('deleted %s' % rdtype) else: # update - _on_update_success(host, fqdn, kind, ipaddr, secure, logger) + _on_update_success(host, fqdn, kind, ipaddr, ipprefix, secure, logger) return Response('good %s' % ipaddr) -def _on_update_success(host, fqdn, kind, ipaddr, secure, logger): +def _on_update_success(host, fqdn, kind, ipaddr, ipprefix, secure, logger): """after updating the host in dns, do related other updates""" # update related hosts + + if ipprefix: + network = IPNetwork(ipprefix) + else: + netmask = host.netmask_ipv4 if kind == 'ipv4' else host.netmask_ipv6 + network = IPNetwork("%s/%d" % (ipaddr, netmask)) rdtype = 'A' if kind == 'ipv4' else 'AAAA' for rh in host.relatedhosts.all(): if rh.available: if kind == 'ipv4': ifid = rh.interface_id_ipv4 - netmask = host.netmask_ipv4 else: # kind == 'ipv6': ifid = rh.interface_id_ipv6 - netmask = host.netmask_ipv6 ifid = ifid.strip() if ifid else ifid _delete = not ifid # leave ifid empty if you don't want this rh record try: rh_fqdn = FQDN(rh.name + '.' + fqdn.host, fqdn.domain) if not _delete: ifid = IPAddress(ifid) - network = IPNetwork("%s/%d" % (ipaddr, netmask)) rh_ipaddr = str(IPAddress(network.network) + int(ifid)) except (IndexError, AddrFormatError, ValueError) as e: logger.warning("trouble computing address of related host %s [%s]" % (rh, e)) diff --git a/src/nsupdate/main/templates/main/includes/tabbed_router_configuration.html b/src/nsupdate/main/templates/main/includes/tabbed_router_configuration.html index d6eb88b1..1a646b64 100644 --- a/src/nsupdate/main/templates/main/includes/tabbed_router_configuration.html +++ b/src/nsupdate/main/templates/main/includes/tabbed_router_configuration.html @@ -125,7 +125,7 @@
{% trans "Enter the following data:" %}
{% trans "If you have IPv4 and IPv6" %}
{% trans "Set Update-URL to the following (two URLs, separated by one space)" %} -
https://{{ WWW_IPV4_HOST }}/nic/update https://{{ WWW_IPV6_HOST }}/nic/update
+
https://{{ WWW_IPV4_HOST }}/nic/update https://{{ WWW_IPV6_HOST }}/nic/update?ipprefix=<ip6lanprefix>
{% trans "Forcing a dynamic DNS update" %}
{% trans "If you want to force a dynamic update for testing purposes, you can do it like this:" %}