Page MenuHomeVyOS Platform

kea: DHCPv4 ip allocation issue on vlan interfaces
In progress, HighPublicBUG

Description

vyos 1.5.0 dhcp-server config:

set interfaces bonding bond0 address '172.16.1.1/24'
set interfaces bonding bond0 member interface 'eth1'
set interfaces bonding bond0 member interface 'eth2'
set interfaces bonding bond0 mode '802.3ad'
set interfaces bonding bond0 vif 3 address '172.16.3.1/24'
set interfaces bonding bond0 vif 4 address '172.16.4.1/24'
set interfaces bonding bond0 vif 7 address '172.16.7.1/24'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 option default-router '172.16.4.1'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 option name-server '1.1.1.1'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 range SERVERS start '172.16.4.20'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 range SERVERS stop '172.16.4.100'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 static-mapping Generator ip-address '172.16.4.28'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 static-mapping Generator mac 'dc:a6:32:11:95:d1'
set service dhcp-server shared-network-name ServerNet subnet 172.16.4.0/24 subnet-id '1'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 option default-router '172.16.3.1'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 option name-server '172.16.3.1'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 option name-server '1.1.1.1'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 range WIFI start '172.16.3.10'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 range WIFI stop '172.16.3.254'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 static-mapping printer ip-address '172.16.3.241'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 static-mapping printer mac 'C0:74:AD:F7:99:D8'
set service dhcp-server shared-network-name WIFI subnet 172.16.3.0/24 subnet-id '2'
set service dhcp-server shared-network-name Wired subnet 172.16.7.0/24 option default-router '172.16.7.1'
set service dhcp-server shared-network-name Wired subnet 172.16.7.0/24 option name-server '1.1.1.1'
set service dhcp-server shared-network-name Wired subnet 172.16.7.0/24 range Home start '172.16.7.10'
set service dhcp-server shared-network-name Wired subnet 172.16.7.0/24 range Home stop '172.16.7.200'
set service dhcp-server shared-network-name Wired subnet 172.16.7.0/24 subnet-id '3'
set service dhcp-server shared-network-name mgt subnet 172.16.1.0/24 option default-router '172.16.1.1'
set service dhcp-server shared-network-name mgt subnet 172.16.1.0/24 option name-server '1.1.1.1'
set service dhcp-server shared-network-name mgt subnet 172.16.1.0/24 range mgt start '172.16.1.50'
set service dhcp-server shared-network-name mgt subnet 172.16.1.0/24 range mgt stop '172.16.1.200'
set service dhcp-server shared-network-name mgt subnet 172.16.1.0/24 subnet-id '4'

switch:

set interfaces bonding bond0 mode '802.3ad'                                                                                              
set interfaces bonding bond0 member interface 'eth1'                                                                                   
set interfaces bonding bond0 member interface 'eth2'                                                                                                      
set interfaces bridge br0 member interface 'bond0'
set interfaces bridge br0 member interface 'eth0'                                                                                                                                                                                                                                                          
set interfaces bridge br0 enable-vlan                                                                                                    
set interfaces bridge br0 member interface bond0 allowed-vlan '1-7'
set interfaces bridge br0 member interface bond0 native-vlan '1'                                                                         
set interfaces bridge br0 member interface eth0 allowed-vlan '3'                                                                         
set interfaces bridge br0 member interface eth0 native-vlan '3'

client:

set interfaces ethernet eth0 address 'dhcp'

KEA sends two offers:

06:15:53.188005 IP 172.16.3.1.bootps > 172.16.3.10.bootpc   ← correct (VLAN 3)                                                           
06:15:53.189202 IP 172.16.1.1.bootps > 172.16.1.52.bootpc   ← wrong (mgt subnet)

One from the correct VLAN subnet and one from 172.16.1.0/24(bond0). Both are sent within 1ms

dhcp-server listening sockets:

vyos@vyos:~$ ss -f link -p
Netid  Recv-Q  Send-Q     Local Address:Port        Peer Address:Port  Process  
p_dgr  0       0                 [8193]:*                       *               
p_raw  0       0                      *:bond0                   *               
p_raw  0       0                      *:bond0.3                 *               
p_raw  0       0                      *:bond0.4                 *               
p_raw  0       0                      *:bond0.7                 *
  1. Kea with “interfaces”: [“*”] opens raw sockets on bond0 and bond0.X
  2. DHCP DISCOVER arrives on both (kernel delivers to parent first, then VLAN)
  3. Multi-threading processes both copies independently
  4. Two offers sent: one from 172.16.1.0/24 (bond0), one from correct VLAN subnet

Details

Version
1.5.0
Is it a breaking change?
Unspecified (possibly destroys the router)
Issue type
Bug (incorrect behavior)

Event Timeline

I worked around this issue using a macvlan as per https://lists.isc.org/pipermail/kea-users/2020-February/002630.html

eth1 is my untagged LAN which also has a number of tagged VLANs (eth1.1, eth1.2 etc)

set interfaces pseudo-ethernet peth1 address 192.168.xxx.250/32 # assign a /32 in the same subnet as the eth1 LAN
set interfaces pseudo-ethernet peth1 source-interface eth1

delete service dhcp-server listen-interface eth1 # tell kea not to listen raw on eth1
set service dhcp-server listen-interface peth1 # listen on the macvlan interface instead
set service dhcp-server listen-interface eth1.2
set service dhcp-server listen-interface eth1.3

Hi Fransking, For some reason, Kea is just not playing nice with VIFs. Related: T8547. It’s quite striking that such a fundamental service as a DHCP server is not working correctly in this branch. The integration between the Kea engine and VyOS’s interface abstraction layer seems to have serious gaps, especially for unicast traffic.

Did anything land in 1.5.1 to help with this one?

Reproduced this on rolling with Kea 3.0.3-vyos.

I wrote the Kea config by hand so the templates were not part of the test. veth pair, 172.16.1.1/24 on the parent, 172.16.10.1/24 on VLAN 10. Kea listening with raw sockets on both, and each subnet had its own interface set. Client was udhcpc in a netns, discover sent on the VLAN.

Kea offered both 172.16.1.50 and 172.16.10.50 for that one discover. udhcpc took the VLAN address, but the untagged pool still handed one out. Setting interface on the subnet did not stop it. The tagged discover still shows up on the parent socket, and Kea answers that copy from the untagged subnet.

I then stopped listening on the parent and listened on a macvlan instead. Same subnets, same client. The VLAN discover only got 172.16.10.50. Kea logged the offer and the ack on the VLAN interface and nothing else. The extra offer is the parent socket.

That is the bug ISC fixed in 3.1.7 and shipped in 3.2.0 (GitLab #1117, #1738, #3792). In 3.2.0, pkt_filter_lpf.cc drops the frame on the parent when the VLAN tag is still present. I pulled the same file from the 3.0.4 tag and from the current v3_0 branch. Neither copy has that check. The 3.0.4 release notes do not mention those tickets. The changelog tags the change as a feature, and it is not on the 3.0 branch. libkea-dhcp.so.109 on the lab has no VLAN filter strings in it either.

The rolling isc-kea build in vyos-build does not have a patch for this. Our template listens on "*" with raw sockets, which is enough to hit the bug, but there is no config change that keeps DHCP working on the parent and on the VLANs at the same time. Leave the parent out of the listen list and the bad offer stops, and so does untagged DHCP. The macvlan workaround from the forum does work. I tried it. It needs a /32, arp ignore, and loose rp_filter, and I do not think we should generate that.

The fix is to ship Kea 3.2. I checked whether 3.2 would refuse the config we write today. It still accepts the old control-socket object and wraps it into the new list itself, so the templates do not have to change for the bump. I have not built 3.2 and rerun the discover against it.

I reproduced this and tested Kea 3.2.1 on a rolling VM (2026.10.16-0024-rolling).

Setup: a virtual-ethernet pair, veth0 with 172.16.1.1/24 and veth0 vif 3 with 172.16.3.1/24, one DHCP subnet for each, the client end in a network namespace with a VLAN 3 interface. The client sends a DHCPDISCOVER on the VLAN 3 interface.

Kea 3.0.3-vyos (current): the client gets two offers, one untagged from 172.16.1.1 (the subnet of the parent interface) and one tagged with vlan 3 from 172.16.3.1. This is the behavior from the description.

Kea 3.2.1: only the tagged offer from 172.16.3.1. A DISCOVER sent untagged on the parent interface still gets exactly one untagged offer from 172.16.1.1.

The fix is in the ISC ChangeLog of 3.2.1 as entry 2448: "When using raw sockets, Kea now properly handles VLAN interfaces and VLAN tagged packets (Gitlab #1117, #1738, #3792)". It was added in the 3.1 development line and is not in 3.0.x. The Debian archive has nothing newer than 3.0.x either (trixie 2.6.3, trixie-backports 3.0.3, sid 3.0.4), so the only way is our own build of the package.

Building it: the existing packaging in scripts/package-build/isc-kea builds 3.2.1 unchanged, only commit_id in package.toml and BRANCH in prebuild.sh have to be changed from Kea-3.0.3 to Kea-3.2.1.

What does not work with 3.2.1 as it is: kea-dhcp-ddns does not start. Kea refuses a TSIG key in clear text now:

use of clear text TSIG 'secret' is NOT SECURE

The template kea-dhcp-ddns.conf.j2 writes the key as secret. Kea supports secret-file since 2.5.8, so the template would have to write the key to a file with restricted permissions. Starting Kea with -X would switch off all security checks and is the worse way.

test_service_dhcp-server.py passed 15 of 17 tests with 3.2.1. One failure is the DDNS test above, the other is a test of my own pending change that my VM did not have. I did not test relay, failover/HA or the hooks, so there can be more differences between 3.0.3 and 3.2.1.

Summary: moving to Kea 3.2.x fixes this task. It needs the version change in vyos-build, the secret-file change for DDNS in vyos-1x and a full DHCP regression run. I can prepare these if the team wants Kea 3.2.

PR https://github.com/vyos/vyos-build/pull/1331 but the fix for T9493 must be merged before. I can't create teh PR in the moment cause the PR number limit.

Viacheslav changed the task status from Open to In progress.Fri, Oct 23, 9:45 AM
Viacheslav assigned this task to rherold.

Testing confirmed two ways to fix the duplicate DHCPOFFER from the VLAN parent:

  1. Kea 3.2.1 (vyos-build#1331 + vyos-1x#5590), tested on rolling.
  2. Kea 3.0.3 with the three upstream commits for Gitlab #1117 backported (fed3ece02e, dfec956a55, 4313cbb478), tested on rolling and circinus.

In all runs the client got its address from the VLAN subnet (no NAK, no lease in the parent subnet)

I'd start with patches this time; this way, we can backport it to the LTS without additional changes or potential new risks.
Then, when we are ready, bump the KEA version for the rolling.

ollowing the discussion here I looked at the patch route for Kea 3.0.3.

The fix is ChangeLog entry 2448 of Kea 3.1.7 / 3.2.0 ("When using raw sockets, Kea now properly handles VLAN interfaces and VLAN tagged packets", GitLab #1117, #1738, #3792). The commits are fed3ece02e (drop packets with a VLAN tag), dfec956a55 (BSD BPF only) and 4313cbb478 (review comments, enables PACKET_AUXDATA). On Linux two of them are needed, both only touch src/lib/dhcp/pkt_filter_lpf.cc. They apply to 3.0.3 in order with patch --fuzz=0 (offset only), as two patches taken with git format-patch from the upstream commits. The patched file differs from the 3.2.1 one only by other upstream changes (an extra zero filter that drains the socket, GitLab #4279), nothing about VLANs.

I built 3.0.3 with the patches through scripts/package-build/isc-kea (patches in patches/isc-kea/, dpkg-source applies both) and tested on a VyOS 2026.10.16 VM: veth0 with 172.16.1.0/24 and VLAN 3 on it with 172.16.3.0/24, DHCP server for both, a client on VLAN 3, tcpdump on the peer.

  • Kea 3.0.3 (installed): 1 DISCOVER on the wire, 2 OFFER. One tagged (172.16.3.10 from 172.16.3.1), one untagged from the parent (172.16.1.50 from 172.16.1.1).
  • Kea 3.0.3 + patch: 1 DISCOVER, 1 OFFER (172.16.3.10 from 172.16.3.1).
  • An untagged client on the peer still gets exactly one offer (172.16.1.50 from 172.16.1.1) with the patch.
  • test_service_dhcp-server.py passes with the patched packages (18 tests).

The patches are in a draft PR on rolling: https://github.com/vyos/vyos-build/pull/1338 (isc-kea: T8461: ..., two patch files). For the LTS branch: should the patch go to circinus as a backport after it is merged on rolling, or does a separate PR make it easier for you?

The Kea 3.2.x update stays as it is (vyos-build #1331, it needs the TSIG key change in vyos-1x #5590 from T9493) and can follow after this.

Not tested: bond parent, QinQ, the BSD part, an LTS image build. Side note: the vyos/vyos-build:current container has an APT source packages.vyos.net/repositories/current that answers 404 now, I had to remove it before the package build.

@rherold

Side note: the vyos/vyos-build:current container has an APT source packages.vyos.net/repositories/current that answers 404 now

The actual container is vyos/vyos-build:rolling

For the LTS branch: should the patch go to circinus as a backport after it is merged on rolling, or does a separate PR make it easier for you?

All patches have to go via rolling, then we can backport them to the next-stream/circinus and so on. No additional actions from your side.

We cannot merge PRs in the draft state.

@Viacheslav I know but I can only do draft pr cause the five pr limit on the repo