HenriWahl / dhcpy6d

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

no addresses or prefixes available - Prefix delegation broken? #52

Closed FelixJacobi closed 2 years ago

FelixJacobi commented 2 years ago

Hi,

I tried to set up prefix delegation with one Debian Bullseye machine acting as host running the dhcpy6d server and another one acting as a client using WIDE DHCPv6 client.

Server configuration

/etc/dhcpy6d.conf:

router.jacobi-bs.de ~ # cat /etc/dhcpy6d.conf
# dhcpy6d configuration for IServ
#
# This file is generated automatically by iservchk.
# It is not recommended to make any changes to this file.
# If really necessary you can save changes permanently using:
#   iconf save /etc/dhcpy6d.conf

# dhcpy6d default configuration
#
# Please see the examples in /usr/share/doc/dhcpy6d and 
# https://dhcpy6d.ifw-dresden.de/documentation for more information.

[dhcpy6d]
# Interface to listen to multicast ff02::1:2.
interface = dmz dmz_office dslmodem guest lan mgmt 
# Identify and configure clients via clients.conf.
store_config = file
# SQLite DB for leases and LLIP-MAC-mapping.
store_volatile = sqlite
store_sqlite_volatile = /var/lib/dhcpy6d/volatile.sqlite
log = on
log_file = /var/log/dhcpy6d.log
domain = i.local
domain_search_list = jacobi-bs.de
log_console = yes
identification = mac
# set to yes to really answer to clients
# not necessary in Debian where it comes from /etc/default/dhcpy6d and /etc/init.d/dhcpy6
#really_do_it = no

store_file_config = /etc/dhcpy6d-clients.conf

[class_default_dmz]
addresses = dmz_0 dmz_1 temp_dmz_0 temp_dmz_1
interface = dmz
nameserver = fd00::53
ntp_server = 2003:a:838:8f17::1 fd59:3f01:6a1e:2017::1
filter_mac = .*

[address_dmz_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8f17::$eui64$
ia_type = na

[address_temp_dmz_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8f17::$random64$
ia_type = ta

[address_dmz_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e:2017::$eui64$
ia_type = na

[address_temp_dmz_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e:2017::$random64$
ia_type = ta

[class_default_dmz_office]
addresses = dmz_office_0 dmz_office_1 temp_dmz_office_0 temp_dmz_office_1
interface = dmz_office
ntp_server = 2003:a:838:8fa3::1 fd59:3f01:6a1e:3::1
filter_mac = .*

[address_dmz_office_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8fa3::$eui64$
ia_type = na

[address_temp_dmz_office_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8fa3::$random64$
ia_type = ta

[address_dmz_office_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e:3::$eui64$
ia_type = na

[address_temp_dmz_office_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e:3::$random64$
ia_type = ta

[class_default_dslmodem]
addresses = dslmodem_0 dslmodem_1 temp_dslmodem_0 temp_dslmodem_1
interface = dslmodem
nameserver = fd00::53
ntp_server = 2003:a:838:8fa2::1 fd59:3f01:6a1e:2::1
filter_mac = .*

[address_dslmodem_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8fa2::$eui64$
ia_type = na

[address_temp_dslmodem_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8fa2::$random64$
ia_type = ta

[address_dslmodem_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e:2::$eui64$
ia_type = na

[address_temp_dslmodem_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e:2::$random64$
ia_type = ta

[class_default_guest]
addresses = guest_0 guest_1 temp_guest_0 temp_guest_1
interface = guest
nameserver = fd00::53
ntp_server = 2003:a:838:8f18::1 fd59:3f01:6a1e:2018::1
filter_mac = .*

[address_guest_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8f18::$eui64$
ia_type = na

[address_temp_guest_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8f18::$random64$
ia_type = ta

[address_guest_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e:2018::$eui64$
ia_type = na

[address_temp_guest_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e:2018::$random64$
ia_type = ta

[class_default_lan]
addresses = lan_0 lan_1 temp_lan_0 temp_lan_1
interface = lan
nameserver = fd00::53
ntp_server = 2003:a:838:8f00::1 fd59:3f01:6a1e::1
filter_mac = .*

[address_lan_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8f00::$eui64$
ia_type = na

[address_temp_lan_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8f00::$random64$
ia_type = ta

[address_lan_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e::$eui64$
ia_type = na

[address_temp_lan_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e::$random64$
ia_type = ta

[class_default_mgmt]
addresses = mgmt_0 mgmt_1 temp_mgmt_0 temp_mgmt_1
interface = mgmt
nameserver = fd00::53
ntp_server = 2003:a:838:8f19::1 fd59:3f01:6a1e:2019::1
filter_mac = .*

[address_mgmt_0]
# Choosing EUI-64-based addresses.
category = eui64
pattern = 2003:a:838:8f19::$eui64$
ia_type = na

[address_temp_mgmt_0]
# Choosing random addresses.
category = random
pattern = 2003:a:838:8f19::$random64$
ia_type = ta

[address_mgmt_1]
# Choosing EUI-64-based addresses.
category = eui64
pattern = fd59:3f01:6a1e:2019::$eui64$
ia_type = na

[address_temp_mgmt_1]
# Choosing random addresses.
category = random
pattern = fd59:3f01:6a1e:2019::$random64$
ia_type = ta

[address_fixed]
category = fixed

[class_fixed_dmz_office]
addresses = fixed
advertise = addresses prefixes

/etc/dhcpy6-clients.conf:

[iserv]
class = fixed_dmz_office
address = 2003:a:838:8fa3::2 fd59:3f01:6a1e:3::2
hostname = iserv
mac = 52:54:00:9c:ae:f9
prefix = 2003:a:838:8f00::/63

Client configuration

/etc/wide-dhcpv6/dhcp6c.conf:

# wide-dhcp-client configuration for IServ
#
# This file is generated automatically by iservchk.
# It is not recommended to make any changes to this file.
# If really necessary you can save changes permanently using:
#   iconf save /etc/wide-dhcpv6/dhcp6c.conf

interface enp7s0 {
        send ia-pd 0;
        send ia-na 0;
        send rapid-commit;

        request domain-name-servers;
        request domain-name;

        script "/etc/wide-dhcpv6/dhcp6c-script";
};

id-assoc na 0 {
};

id-assoc pd 0 {
        prefix ::/63 infinity;
        prefix-interface enp1s0 {
                sla-id 0;
                sla-len 1;
                ifid 1;
        };
};

My plans are to assign prefixes to particular clients. Therefor, I decided to assign prefixes only to clients, not on class level.

But: This does not work. The WIDE DHCPv6 client always reports that it received no addresses:

Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]: client6_recv: receive reply from fe80::1%enp7s0 on enp7s0
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]: dhcp6_get_options: get DHCP option client ID, len 14
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]:   DUID: 00:01:00:01:29:4b:6c:4f:52:54:00:45:fe:f0
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]: dhcp6_get_options: get DHCP option server ID, len 14
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]:   DUID: 00:01:00:01:61:bd:2d:ba:8a:00:57:b6:3c:ad
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]: dhcp6_get_options: get DHCP option status code, len 2
Dez 22 05:14:53 iserv.jacobi-bs.de dhcp6c[552330]:   status code: no addresses

The log of dhcpy6d always says:

2021-12-22 05:13:25,111 dhcpy6d INFO SOLICIT | transaction: 1abc96 | answer: normal | client_llip: fe80:0000:0000:0000:0000:0000:0000:0002 | counter: 2 | duid: 00010001294b6c4f52540045fef0 | ia_options: [3, 25] | iaid: 00000000 | interface: dmz_office | last_message_received_type: 1 | mac: 52:54:00:9c:ae:f9 | options_request: [23, 24] | rapid_commit: True
2021-12-22 05:13:25,112 dhcpy6d WARNING REPLY | no addresses or prefixes available | transaction: 1abc96 | client_llip: fe80:0000:0000:0000:0000:0000:0000:0002

When I am removing the send ia-pd 0; on WIDE DHCPv6 side, everything works fine (except for that of course no prefixes are assigned then). I started with adding some debug statements around https://github.com/HenriWahl/dhcpy6d/blob/eb838bbb3e0bac513898a77f4f037ed2eed720b8/dhcpy6d/handler.py#L497 and their are confirmed, that prefixes are not assigned. After digging around in the code, I found, that the only place where prefixes from client configuration are added , which is located in from_config.py, which is only called from client/__init__.py. Adding further statements there, it showed me that the corresponding if/else branch in client is never called when a prefix is requested. I finally found the following https://github.com/HenriWahl/dhcpy6d/blob/eb838bbb3e0bac513898a77f4f037ed2eed720b8/dhcpy6d/client/__init__.py#L222 statement. It looks a bit wrong to me, as the comment does not match to the real behavior of this statement. I changed it to look like the following way:

            # If client gave some addresses for RENEW or REBIND consider them
            if (transaction.last_message_received_type is CONST.MESSAGE.RENEW or \
               transaction.last_message_received_type is CONST.MESSAGE.REBIND) and \
               not (len(transaction.addresses) == 0 and
                    len(transaction.prefixes) == 0):
                # use already existing lease
                reuse_lease(client=self, client_config=client_config, transaction=transaction)
            # build IA addresses from config - fixed ones and dynamic
            elif client_config is not None:
                # build client from config
                from_config(client=self, client_config=client_config, transaction=transaction)
            else:
                # use default class if host is unknown
                default(client=self, client_config=client_config, transaction=transaction)

Now, prefix delegation works with my configuration (the addresses and the prefix from client configuration entry are given to the client). Am I correct and the if statement is probably wrong? Or do I miss something in my configuration?

FelixJacobi commented 2 years ago

I also noticed that, if you have two colliding link-local addresses, that the client is identified wrong. My router is dual-homed in two subnets. In each, there is a host with a manually assigned link-local address fe80::2 (derived from global addresse like 2001:db8::2 or IPv4 addresses 192.168.1.2 to ease recognizing the hosts), but these are two different VLANs and also two different hosts with two different MAC addresses. When the first host requested and received the prefix, everything is fine. But the second host is also receiving the prefix of the first one instead of the prefix configured for it's MAC address. I saw that the Link-local address to MAC cache is just simple map. Would it make sense to include the interface name in the cache to avoid such situations?

HenriWahl commented 2 years ago

Hi @FelixJacobi,

regrarding the first issue I think you are right - either the comment or the code is lying. But according to your investigations looks like the comment is true. Can you please prepare a PR?

Regarding the second - might be worth an issue on its own. I understand it might be practical to have a predefined Link Local Address for the routers on a first sight. But in theory they would not need it as the clients do not care about how the address of the router - which announces itself as router anyway - looks like. So in your place I would give them unique addresses even of a ULA-space but not try to change anything in the LLA-space. LLAs actually are a core element to make dhcpy6d work and to work around the DHCPv6 flaws. At least this might be a short-term fix. In the log run adding the interface to the cache sounds interesting, but should be proved carefully and thus will take some time.

HenriWahl commented 2 years ago

Hi @FelixJacobi, is the interface in the class definition for class fixed_dmz_office at the bottom of your script missing?

FelixJacobi commented 2 years ago

regrarding the first issue I think you are right - either the comment or the code is lying. But according to your investigations looks like the comment is true. Can you please prepare a PR?

See #53.

FelixJacobi commented 2 years ago

Regarding the second - might be worth an issue on its own.

Yes. Definitely.

I understand it might be practical to have a predefined Link Local Address for the routers on a first sight.

But in theory they would not need it as the clients do not care about how the address of the router - which announces itself as router anyway - looks like.

Yeah, but not always. You probably have turned off the acceptance of RAs for security reasons (especially on servers) and configuring everything by hand. That's almost my setup currently, The only dynamic thing I am using on servers, is DHCPv6 to distribute the dynamic prefix by the ISP as there is no other way to receive it. ULA, (default) routes and everything else is configured by hand.

So in your place I would give them unique addresses even of a ULA-space but not try to change anything in the LLA-space.

Yes, this could work for the most systems, like Linux kernel-based ones (you can set every address there as gateway, and it works). But I already saw several systems which were only able to use link-local addresses as gateway - mostly a fe80:: prefix for the gateway address was forced on configuring and similar stuff. So I decided for strictly using link-local addresses for that case to keep things consistent.

LLAs actually are a core element to make dhcpy6d work and to work around the DHCPv6 flaws. At least this might be a short-term fix.

Yeah, it would be a sensitive area of dhcpy6d which must be changed. For now, I added a subnet number to the link local addresses. So, no need to change anything here yet.

In the log run adding Yes. the interface to the cache sounds interesting, but should be proved carefully and thus will take some time.

I will look if I will find some hours in the next time to work on this and to test and deploy it on several systems before it becomes part of the official dhcpy6d code base.

HenriWahl commented 2 years ago

For the first issue please check latest packages at https://github.com/HenriWahl/dhcpy6d/releases/tag/latest