Skip to content

Commit

Permalink
Support ipprefix via argument
Browse files Browse the repository at this point in the history
Fixes #353
  • Loading branch information
the-nic committed Apr 12, 2023
1 parent 68e6c73 commit 73bb0ce
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 10 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -327,7 +328,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

Expand Down
24 changes: 24 additions & 0 deletions src/nsupdate/api/_tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ 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

# mismatching prefix type
response = client.get(reverse('nic_update') + '?myip=4.3.2.1&ipprefix=3000::/16',
HTTP_AUTHORIZATION=make_basic_auth_header(TEST_HOST, TEST_SECRET))
assert response.status_code == 200
assert response.content == b'dnserr'


def test_nic_update_authorized_myip_v6(client):
response = client.get(reverse('nic_update') + '?myip=2000::2',
Expand All @@ -170,6 +182,18 @@ 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

# mismatching prefix type
response = client.get(reverse('nic_update') + '?myip=2000::4&ipprefix=127.0.0.1/16',
HTTP_AUTHORIZATION=make_basic_auth_header(TEST_HOST, TEST_SECRET))
assert response.status_code == 200
assert response.content == b'dnserr'


@pytest.mark.requires_sequential
def test_nic_update_authorized_update_other_services(client):
Expand Down
29 changes: 21 additions & 8 deletions src/nsupdate/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ <h5>{% trans "Enter the following data:" %}</h5>
</table>
<h5>{% trans "If you have IPv4 and IPv6" %}</h5>
{% trans "Set Update-URL to the following (two URLs, separated by one space)" %}
<div class="well well-sm">https://{{ WWW_IPV4_HOST }}/nic/update https://{{ WWW_IPV6_HOST }}/nic/update</div>
<div class="well well-sm">https://{{ WWW_IPV4_HOST }}/nic/update https://{{ WWW_IPV6_HOST }}/nic/update?ipprefix=&lt;ip6lanprefix&gt;</div>
<h5>{% trans "Forcing a dynamic DNS update" %}</h5>
{% trans "If you want to force a dynamic update for testing purposes, you can do it like this:" %}
<ul>
Expand Down

0 comments on commit 73bb0ce

Please sign in to comment.