HenriWahl / dhcpy6d

MAC address aware DHCPv6 server written in Python
https://dhcpy6d.de
GNU General Public License v2.0
97 stars 27 forks source link

RNDC + bind config #42

Closed iam-TJ closed 2 years ago

iam-TJ commented 3 years ago

It'd be really good to add a working example configuration for RNDC with Bind9, including the bind rndc configuration and zone allow-update option. Current documents are fragmented, don't make clear which algorithms should be used (or if it matters) and how to monitor logs to ensure updates are being done.

We've configured for RNDC based on the documentation but do not see any attempts by dhcpy6d to contact the bind9 instance (monitoring using tcpdump) and nothing in the logs (not that we know what to look for).

We have two systemd-nspawn debian-11 containers one with bind9 (fddc:...:3) and the other with dhcpy6d (fddc:...:4). Each has its own network namespace and is connected via veth to a bridge on the host which also has ports from the LAN. Both can talk to each other and other hosts on the LAN. Clients can obtain leases from dhcpy6d and name resolution from bind9.

bind9 config:

acl dhcpy6d {
        fddc:7e00:e001:ee00::4;
};

key "rndc-key" {
        algorithm hmac-sha256;
        secret "8B6jVATeYHVXNfVigHcpC31ud0KpmKnXwq4fsovZiqM=";
};

zone "lan.example.com" {
        type master;
        file "/etc/bind/zones/lan.example.com.zone";
        notify explicit;
        allow-transfer {
                slaves;
                axfr;
        };
        allow-update { dhcpy6d; key rndc-key; };
};

dhcpy6d config:

nameserver = fddc:7e00:e001:ee00::3
really_do_it = yes
dns_use_rndc = yes
dns_update_nameserver = fddc:7e00:e001:ee00::3
dns_rndc_key = rndc-key
dns_rndc_secret = 8B6jVATeYHVXNfVigHcpC31ud0KpmKnXwq4fsovZiqM=
dns_ignore_client = yes

[address_local_soggy]
category = mac
pattern = fddc:7e00:e001:ee00:fffe:$mac$
dns_update = yes
dns_zone = lan.example.com
dns_rev_zone = 0.0.e.e.1.0.0.e.0.0.e.7.c.d.d.f.ip6.arpa

On a laptop client:

sudo dhclient -I -d -6 enp2s

Internet Systems Consortium DHCP Client 4.4.1
Copyright 2004-2018 Internet Systems Consortium.                                                                                                  
All rights reserved.               
For info, please visit https://www.isc.org/software/dhcp/

Listening on Socket/enp2s0        
Sending on   Socket/enp2s0                                               
PRC: Soliciting for leases (INIT).                                       
XMT: Forming Solicit, 0 ms elapsed.
XMT:  X-- IA_NA a4:f3:42:b4                                              
XMT:  | X-- Request renew in  +3600                                      
XMT:  | X-- Request rebind in +5400
XMT:  | X-- Request address fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4.
XMT:  | | X-- Request preferred in +7200
XMT:  | | X-- Request valid in     +10800                   
XMT: Solicit on enp2s0, interval 1010ms.
RCV: Advertise message on enp2s0 from fe80::d0a0:6dff:fed1:1e01.
RCV:  X-- Preference 255.
RCV:  X-- IA_NA a4:f3:42:b4                                              
RCV:  | X-- starts 1629310493                                            
RCV:  | X-- t1 - renew  +90      
RCV:  | X-- t2 - rebind +120                                             
RCV:  | X-- [Options]                                                    
RCV:  | | X-- IAADDR fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4
RCV:  | | | X-- Preferred lifetime 0.                         
RCV:  | | | X-- Max lifetime 0.                                                          
RCV:  X-- Server ID: 00:01:00:01:61:1d:2e:fc:47:04:6e:fb:8c:aa
RCV:  Advertisement immediately selected.
PRC: Selecting best advertised lease.
PRC: Considering best lease.
PRC:  X-- Initial candidate 00:01:00:01:61:1d:2e:fc:47:04:6e:fb:8c:aa (s: 10206, p: 255).
XMT: Forming Request, 0 ms elapsed.
XMT:  X-- IA_NA a4:f3:42:b4
XMT:  | X-- Requested renew  +3600
XMT:  | X-- Requested rebind +5400
XMT:  | | X-- IAADDR fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4
XMT:  | | | X-- Preferred lifetime +7200
XMT:  | | | X-- Max lifetime +7500
XMT:  V IA_NA appended.
XMT: Request on enp2s0, interval 990ms.
RCV: Reply message on enp2s0 from fe80::d0a0:6dff:fed1:1e01.
RCV:  X-- Preference 255.
RCV:  X-- IA_NA a4:f3:42:b4
RCV:  | X-- starts 1629310493
RCV:  | X-- t1 - renew  +90
RCV:  | X-- t2 - rebind +120
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4
RCV:  | | | X-- Preferred lifetime 180.
RCV:  | | | X-- Max lifetime 240.
RCV:  X-- Server ID: 00:01:00:01:61:1d:2e:fc:47:04:6e:fb:8c:aa
PRC: Bound to lease 00:01:00:01:61:1d:2e:fc:47:04:6e:fb:8c:aa.
PRC: Renewal event scheduled in 90 seconds, to run for 30 seconds.
PRC: Depreference scheduled in 180 seconds.
PRC: Expiration scheduled in 240 seconds.

dhcpy6d log

2021-08-18 19:14:53,088 dhcpy6d INFO SOLICIT | transaction: 96247f | addresses: fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4  | answer: normal | client_llip: fe80:0000:0000:0000:fa75:a4ff:fef3:42b4 | counter: 1 | duid: 0001000128b0064af875a4f342b4 | ia_options: [3] | iaid: a4f342b4 | interface: host0 | last_message_received_type: 1 | mac: f8:75:a4:f3:42:b4 | options_request: [23, 24, 39, 31]
2021-08-18 19:14:53,092 dhcpy6d INFO ADVERTISE | transaction: 96247f | options: [3, 7, 23, 24, 31, 39]  | addresses: fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4 | client_class: default_host0
2021-08-18 19:14:53,138 dhcpy6d INFO REQUEST | transaction: 9f8010 | addresses: fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4 | answer: normal | client_llip: fe80:0000:0000:0000:fa75:a4ff:fef3:42b4 | counter: 1 | duid: 0001000128b0064af875a4f342b4 | ia_options: [3] | iaid: a4f342b4 | interface: host0 | last_message_received_type: 3 | mac: f8:75:a4:f3:42:b4 | options_request: [23, 24, 39, 31]
2021-08-18 19:14:53,142 dhcpy6d INFO REPLY | transaction: 9f8010 | options: [3, 7, 23, 24, 31, 39]  | addresses: fddc:7e00:e001:ee00:fffe:f875:a4f3:42b4 | client_class: default_host0
HenriWahl commented 3 years ago

This part was until now only tested with hmac-md5 and with no key, so it may be it does not yet work with other algorithms. Adding more algorithms is on the todo list.

Can you test if it would work without key, just to make sure?

Edit: Which version of dhcpy6d do you use?

Edit 2: Can you please show the whole dhcpy6d.conf? The log shows the client is in class default - not sure if it is meant to be there.

iam-TJ commented 3 years ago

Thanks for responding. I did finally solve it yesterday after adding log.info() messages throughout the code. There were two causes, one in dhcpy6d documentation and the other with isc-dhcp's dhclient.

# apt-cache policy dhcpy6d
dhcpy6d:
  Installed: 1.0.3-1
  Candidate: 1.0.3-1
  Version table:
 *** 1.0.3-1 500
        500 http://deb.debian.org/debian bullseye/main amd64 Packages
        100 /var/lib/dpkg/status

(Some) examples of the dhcpy6d rndc configuration show dns_update = yes in an [address_XXXX] sub-section, which we copied. See for example:

https://dhcpy6d.de/documentation/config/full

[address_local_valid]
...
# Update these addresses in Bind DNS - defaults to "no"
dns_update = yes
# Zone to update.
dns_zone = example.com
# Reverse zone to update
dns_rev_zone = 1.0.d.f.ip6.arpa

But dns_update is a global option and is only triggered from the [dhcpy6d] section.

Secondly, the copious logging I added (which I intend to provide a PR for with it converted to log.debug()) revealed hostname ='' - empty. This obviously prevents a dynamic update in dhcpy6d/domains.py::dns_update():

        for a in transaction.client.addresses:
            log.info("transaction.client.address=%s" % (a))
            log.info("a.DNS_UPDATE=%d a.VALID=%d" %(a.DNS_UPDATE, a.VALID))
            if a.DNS_UPDATE and hostname != '' and a.VALID:
                log.info("cfg.DNS_IGNORE_CLIENT=%d transaction.dns_s=%d" %(cfg.DNS_IGNORE_CLIENT, transaction.dns_s))
                if cfg.DNS_IGNORE_CLIENT or transaction.dns_s == 1:
                    # put query into DNS query queue
                    log.info("calling dns_query_queue_put()")
                    dns_query_queue.put((action, hostname, a))

Switched attention to dhclient and after a lot of digging discovered an Ubuntu bug report from 2012 (https://bugs.launchpad.net/bugs/991360 "isc-dhcp-client does not send hostnames in DHCPv6 by default") that reported this same issue; the cause being that dhclient's "send hostname ..." command is only valid for DHCPv4, and has no effect for DHCPv6! The bug report added that for DHCPv6 /etc/dhcp/dhclient.conf also needs:

send fqdn.fqdn gethostname();

With that added, finally, dhcpy6d is sending updates to bind9. I added a further bug report (https://bugs.launchpad.net/bugs/1940481 "dhclient.conf should set send DHCPv6 host-name option") and intend preparing a patch for Debian and Ubuntu to enable send fqdn.fqdn by default

As an aside I also realised that the key algorithm doesn't come into this. I'll aim to also produce a PR to clarify the documentation and provide clear examples that work (showing the bind9 side too).

One thing that misled us early on is all the talk of RNDC inferred (or made us assume) that dhcpy6d was going to use the bind9 rndc remote control port 953 (used by the rndc command-line tool) - so we should make clear that in fact these updates are done over port 53 as part of regular queries.

HenriWahl commented 3 years ago

Good news!

Edit: see https://github.com/HenriWahl/dhcpy6d/blob/2b7e3bf5f1173c0c89df6933d42f535c8db19c71/dhcpy6d/domain.py#L44 - if you use the setting dns_use_client_hostname you could trigger to use a defined hostname instead of a transmitted one. This depends on how you manage your clients.

iam-TJ commented 3 years ago

Yes, I'd looked at that but in our case we do want the clients to be supplying their hostnames, and we chose dhcpy6d specifically so that we can enforce the inclusion of MAC addresses in the leased IPv6 address to aid identification.

What I do want to do is add code to refuse a lease to a client that doesn't provide a hostname and ensure duplicate hostnames aren't allowed - working on that now.

Earlier I added code that ensures that DDNS updates are only done if the address matches the dns_rev_zone prefix - this because we issue ULA and global prefix addresses but only want to do DDNS on the ULAs.

I also want to extend things so we have a dns_update_server per reverse zone so we can do DDNS updates to our internal fddc named and separate public global prefix named and also give more flexibility to split the address space across multiple domain.tlds.

I'll be providing a few PRs over the next week or so once I've tidied things up!

HenriWahl commented 3 years ago

But dns_update is a global option and is only triggered from the [dhcpy6d] section.

This seems to be somewhat misleading as well in code as in documentation, as the property of doing DNS updates is globally activated but address based in practice. But it might be a good moment when your PRs come in to fix this mess too.

HenriWahl commented 2 years ago

Kind of made documentation more clear