Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ipprefix via argument #497

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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