project/odhcpd.git
18 hours agoconfig: cap dhcpv6_pd_min_len to max instead of only logging error master
Paul Donald [Fri, 10 Oct 2025 11:44:28 +0000 (13:44 +0200)]
config: cap dhcpv6_pd_min_len to max instead of only logging error

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: clamp dhcpv6_hostid_len instead of only logging an error
Paul Donald [Fri, 10 Oct 2025 11:44:05 +0000 (13:44 +0200)]
config: clamp dhcpv6_hostid_len instead of only logging an error

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: clamp ra_mtu into 1280-65535 range
Paul Donald [Fri, 10 Oct 2025 11:42:11 +0000 (13:42 +0200)]
config: clamp ra_mtu into 1280-65535 range

(ipv6 packets can be up to 4GB in size...)

The old logic had a logic error of || instead of &&

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: cap ra_retranstime and warn instead of only logging an error
Paul Donald [Fri, 10 Oct 2025 11:43:23 +0000 (13:43 +0200)]
config: cap ra_retranstime and warn instead of only logging an error

Set to the currently defined maximum of 60,000msec via a define.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: cap ra_hoplimit to maximum and warn instead of logging an error
Paul Donald [Fri, 10 Oct 2025 11:46:44 +0000 (13:46 +0200)]
config: cap ra_hoplimit to maximum and warn instead of logging an error

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: cap ra_reachabletime to RFC maximum instead of logging error
Paul Donald [Fri, 10 Oct 2025 11:45:48 +0000 (13:45 +0200)]
config: cap ra_reachabletime to RFC maximum instead of logging error

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: drop double size lease times; they are all UINT32_MAX;
Paul Donald [Fri, 3 Oct 2025 14:36:03 +0000 (16:36 +0200)]
config: drop double size lease times; they are all UINT32_MAX;

This now prevents implicit 64 bit->32 bit truncation which may flag
compiler errors later on down the road.

All of the variables receiving from parse_leasetime() are uint32_t
anyway, so max 136 years of valid lease time will have to suffice :)

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agorouter: redefine ra_mininterval and ra_maxinterval as uint32_t
Paul Donald [Fri, 3 Oct 2025 14:35:52 +0000 (16:35 +0200)]
router: redefine ra_mininterval and ra_maxinterval as uint32_t

They never store negative values.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: clamp ra_mininterval, ra_maxinterval, ra_lifetime at load time
Paul Donald [Fri, 3 Oct 2025 14:34:15 +0000 (16:34 +0200)]
config: clamp ra_mininterval, ra_maxinterval, ra_lifetime at load time

clamp values to RFC defined limits.

First set ra_maxinterval, then ra_mininterval adjusts based on max.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agorouter: refactor calc_ra_lifetime; redefine ra_lifetime as uint32_t
Paul Donald [Thu, 9 Oct 2025 21:35:31 +0000 (23:35 +0200)]
router: refactor calc_ra_lifetime; redefine ra_lifetime as uint32_t

ra_lifetime no longer holds negative values, because we no longer do
'init' when we do calc_ra_lifetime, instead we do init at init time.

Now ra_lifetime holds only >0 values.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: do MaxRtrAdvInterval init at (ra_maxinterval) init time
Paul Donald [Fri, 3 Oct 2025 14:33:57 +0000 (16:33 +0200)]
config: do MaxRtrAdvInterval init at (ra_maxinterval) init time

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agorouter: Apply updated values from RFC9096 (updates RFC4861) to RA/ND
Paul Donald [Fri, 3 Oct 2025 14:33:49 +0000 (16:33 +0200)]
router: Apply updated values from RFC9096 (updates RFC4861) to RA/ND

https://datatracker.ietf.org/doc/html/rfc9096#section-3.4

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agorouter: Apply updated values from RFC8319 (updates RFC4861) to RA/ND
Paul Donald [Fri, 3 Oct 2025 14:33:41 +0000 (16:33 +0200)]
router: Apply updated values from RFC8319 (updates RFC4861) to RA/ND

https://www.rfc-editor.org/rfc/rfc8319#section-4

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
18 hours agoconfig: refactor parse_leasetime() - branch amount remains same
Paul Donald [Fri, 3 Oct 2025 14:33:32 +0000 (16:33 +0200)]
config: refactor parse_leasetime() - branch amount remains same

Also make 's' value a noop.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/225
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
30 hours agogithub: fix CMAKE_SYSTEM_PROCESSOR copy&paste
Álvaro Fernández Rojas [Sun, 12 Oct 2025 20:10:44 +0000 (22:10 +0200)]
github: fix CMAKE_SYSTEM_PROCESSOR copy&paste

CMAKE_SYSTEM_PROCESSOR was incorrectly defined as matrix.packages instead
of matrix.arch.

Fixes: 288206c9a2ed ("github: add CI build")
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
35 hours agogithub: add CI build github-ci
Álvaro Fernández Rojas [Mon, 6 Oct 2025 07:48:31 +0000 (09:48 +0200)]
github: add CI build

Add Github CI supporting different architectures and odhcpd build options.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
3 days agoodhcpd: fix a compilation error
David Härdeman [Thu, 9 Oct 2025 09:08:14 +0000 (11:08 +0200)]
odhcpd: fix a compilation error

odhcpd_get_interface_dns_addr() calls odhcpd_get_interface_linklocal_addr(),
the former takes a const struct interface ptr, the latter takes a non-const
struct interface ptr.

The end result is an unhappy compiler.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/272
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: allow assignments to be reassigned
David Härdeman [Sat, 20 Sep 2025 21:19:29 +0000 (23:19 +0200)]
odhcpd: allow assignments to be reassigned

In a nutshell, this allows static leases which have only been configured
with a DUID (so no IAID specified) to be reassigned to any client with
the same DUID (but potentially different IAID). This matches how
multiple MAC addresses are handled in the DHCPv4 case, and supports the
use case for e.g. moving from WiFi to ethernet.

On the other hand, it is probably not what one would want for e.g. a
server that has several NICs connected to the same network. For those
cases, defining static leases with DUID and IAID anyway seems
preferrable (and it will also ensure a static interface<->IPv6 addr
mapping).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: support multiple per-client DUIDs
David Härdeman [Fri, 1 Aug 2025 17:39:17 +0000 (19:39 +0200)]
odhcpd: support multiple per-client DUIDs

This patch allows for multiple per-client DHCPv6 DUIDs. One use-case for
multiple DUIDs is dual-boot hosts, where e.g. Windows creates it's own DUID and
Linux (say, systemd-networkd) creates it's own DUID, and the two cannot be
synced in a simple manner. This also makes DUIDs similar to multiple per-host
MAC addresses (which is already supported by dnsmasq for DHCPv4 leases).

Note that the "old" behaviour is still kept, in other words, where a
simple DUID (as opposed to a "DUID%IAID") static association is defined,
the address will be given out on a first-come-first-serve basis.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: support assignments on the basis of IAID
David Härdeman [Sat, 20 Sep 2025 17:48:13 +0000 (19:48 +0200)]
odhcpd: support assignments on the basis of IAID

With this patch, IAIDs are actually taken into account when creating
assignments, which allows per-IAID assignments.

The old odhcpd behaviour is still kept for assignments which only
specify a DUID. That behaviour is a first-come-first-serve basis, where
the first request (no matter what the IAID is) will get the static
address, and later requests with different IAIDs will get a NoAddrsAvail
error message (in other words, assignments are not re-assigned and
random adresses are not provided when the "main" address is already
assigned, if the DUID is known in a static config).

The old odhcpd behaviour can be described as follows:

Imagine a "dhcp" configuration file with a single static assignment:

config host
option name 'test'
option ip '192.168.44.2'
option mac '00:00:00:00:ca:fe'
option duid '0003000100000000cafe'
option hostid '02'

And odhcpd running on an interface with IPv6 addr fd00:aaaa::1/48, using
a simple test client (command line parameters should be self-evident
here, foo-client is the client part of a veth interface pair, odhcpd is
listening to the peer interface foo-server):

$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x02 --duid=f000 foo-client
    IPv6: fd00:aaaa::817
$ sudo dhcpdig --iaid=0x02 --duid=f000 foo-client
    IPv6: fd00:aaaa::817
$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x01 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo dhcpdig --iaid=0x01 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo dhcpdig --iaid=0x02 --duid=0003000100000000cafe foo-client
  STATUS: NoAddrsAvail (2)

IOW, unknown DUIDs get randomly assigned per-IAID IPv6 addresses. Known
statically determined DUIDs will be awarded to the first client
attempting a lease, no matter what the IAID is, but the IAID will be
remembered and subsequent requests with other IAIDs will fail.

With this patch applied, assume a "dhcp" configuration file with the
following assignments:

config host
option name 'testcafe'
option ip '192.168.44.2'
option mac '00:00:00:00:ca:fe'
option hostid '02'
option duid '0003000100000000cafe%123'

config host
option name 'testbeef'
option ip '192.168.44.3'
option mac '00:00:00:00:be:ef'
option hostid '03'
option duid '0003000100000000beef'

config host
option name 'testfood'
option ip '192.168.44.4'
option mac '00:00:00:00:f0:0d'
option hostid '04'
option duid '0003000100000000f00d'

config host
option name 'testfood2'
option ip '192.168.44.5'
option mac '00:00:00:00:f0:0d'
option hostid '05'
option duid '0003000100000000f00d%123'

Now, using the same test client:

$ sudo ./build/dhcpdig --iaid=0x000 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::9c4
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo ./build/dhcpdig --iaid=0x000 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::9c4
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000beef foo-client
    IPv6: fd00:aaaa::3
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000beef foo-client
  STATUS: NoAddrsAvail (2)
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::4
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::4
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::5

See also: https://github.com/openwrt/odhcpd/issues/175

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: support IAIDs for static DHCPv6 leases
David Härdeman [Sat, 20 Sep 2025 16:19:06 +0000 (18:19 +0200)]
odhcpd: support IAIDs for static DHCPv6 leases

Extend the string format for duids to alternatively support
"<duid>%<iaid>", which makes it possible to define separate static
leases, e.g. for a client which is connected to the same network with
more than one interface (which will both have the same DUID, as it is a
per-host identifier, but different IAIDs).

Note that this only wires up the new format, actually using it to make
DHCPv6-IA decisions will be implemented in subsequent patches.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: break up complex matching logic
David Härdeman [Sat, 20 Sep 2025 14:40:32 +0000 (16:40 +0200)]
odhcpd: break up complex matching logic

Like Winnie the Pooh, “I am a Bear of Very Little Brain, and long words
Bother me”.

Simplify the assignment checking logic by breaking it up a bit. This is
in preparation for subsequent patches.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: document the ubus interface
David Härdeman [Tue, 7 Oct 2025 12:41:44 +0000 (14:41 +0200)]
odhcpd: document the ubus interface

I guess this might be subject to change (for example, we probably want to
also broadcast events for DHCPv6 leases in the future, and I guess the
ack/release/expire events that are currently broadcast might benefit from
being renamed to something that makes it clearer that they are DHCPv4
specific events), but this is the current ubus interface.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agodhcpv4: generate dbus events on lease expiry
David Härdeman [Tue, 7 Oct 2025 12:16:11 +0000 (14:16 +0200)]
dhcpv4: generate dbus events on lease expiry

We already generate events when a lease is acquired and released, so it seems
consistent to also generate events when a lease expires.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agodhcpv4: fix ubus events
David Härdeman [Tue, 7 Oct 2025 11:50:47 +0000 (13:50 +0200)]
dhcpv4: fix ubus events

Note that req->ciaddr which was used to generate the broadcast message is
completely under client control and isn't checked, meaning that a
buggy/malicious client could cause broadcast messages containing an arbitrary
IP address.

While addressing this, move the broadcast message generation into
dhcpv4_lease(), so that the function can release the assignment straight away
and return NULL (it seems to return the released assignment just so that it can
be broadcast and later reaped by the expiry timer).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: remove mac_len argument to ubus_bcast_dhcp_event()
David Härdeman [Tue, 7 Oct 2025 11:44:08 +0000 (13:44 +0200)]
odhcpd: remove mac_len argument to ubus_bcast_dhcp_event()

All the places that call ubus_bcast_dhcp_event() have already checked that the
hardware adress is a MAC address, and it's not like we support anything else,
so remove the length argument.

The function is actually DHCPv4 specific, but that's a topic for another time.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: fix ubus support flag in help msg
David Härdeman [Tue, 7 Oct 2025 11:39:25 +0000 (13:39 +0200)]
odhcpd: fix ubus support flag in help msg

The cmake definition is a bit sneaky, the cmake variable is "UBUS", but it
leads to "WITH_UBUS" being defined for the compilation.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agoodhcpd: reduce use of WITH_UBUS defines in code
David Härdeman [Tue, 7 Oct 2025 11:18:21 +0000 (13:18 +0200)]
odhcpd: reduce use of WITH_UBUS defines in code

By defining some dummy functions in the header file, we can avoid sprinkling
WITH_UBUS defines through the code, which helps readability and also ensures
that compilation errors (e.g. when a variable is renamed) is caught in builds
with and without ubus.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/270/
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
4 days agondp: fix macOS IPv6 compatibility by using link-local source addresses
Stephen Groat [Wed, 8 Oct 2025 18:54:51 +0000 (11:54 -0700)]
ndp: fix macOS IPv6 compatibility by using link-local source addresses

macOS ignores NDP packets that don't originate from link-local addresses,
causing IPv6 connectivity issues with odhcpd. This change ensures NDP
packets (Neighbor Advertisements and ICMP Echo Requests) are sent using
link-local source addresses for RFC 4861 compliance.

Changes:
* Add ndp_from_link_local configuration flag (defaults to true)
* Add odhcpd_send_with_src() to allow explicit source address control
* Add odhcpd_try_send_with_src() helper to eliminate code duplication
* Add odhcpd_get_interface_linklocal_addr() with caching for performance
* Update send_na() and ping6() to use link-local source addresses when
  enabled
* Add RFC 4861, §4.2 comments explaining the mandated behavior
* Maintain backward compatibility with fallback behavior

Fixes: openwrt/openwrt#7561 #202
Signed-off-by: Stephen Groat <stephengroat@gmail.com>
Link: https://github.com/openwrt/odhcpd/pull/242
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agorouter: fix SLAAC on subnets > 64
Álvaro Fernández Rojas [Tue, 7 Oct 2025 08:30:15 +0000 (10:30 +0200)]
router: fix SLAAC on subnets > 64

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: simplify dhcpv4_setup_interface()
David Härdeman [Thu, 25 Sep 2025 13:45:07 +0000 (15:45 +0200)]
dhcpv4: simplify dhcpv4_setup_interface()

In the disable case, the function would close the interface uloop fd, free
assignments, check if the interface uloop fd should be closed, and return 0.

Simplify the function by bailing early in the disable case, which also removes
one level of indentation for the gist of the function body.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: make the cookie explicit in struct dhcpv4_message
David Härdeman [Thu, 25 Sep 2025 13:33:31 +0000 (15:33 +0200)]
dhcpv4: make the cookie explicit in struct dhcpv4_message

This makes the struct more explicit while allowing a number of magic offsets to
be removed.

Note that adding the packed attribute to struct dhcpv4_message is necessary
because the incoming data is cast directly to the struct. Adding the attribute
will give the struct an alignment of 1 instead of 4 (the normal alignment of
struct in_addr and uint32_t).

In the beginning of dhcpv4_handle_msg(), we cast "void *data" to struct
dhcpv4_message, but we can't know the alignment of "*data" (meaning it has
alignment 1). And by casting "*data" to a struct, we're actually promising the
compiler that "*data" has the same alignment as the struct.

Later, the address of members of struct dhcpv4_message are passed to other
functions, but since struct dhcpv4_message can be at any memory location, it's
not a given that the pointers are aligned. This issue only becomes visible once
the packed attribute is added, which is why the temporary (aligned) struct
in_addr variables are added in the WITH_UBUS blocks.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: simplify dhcpv4_fr_send() a bit
David Härdeman [Thu, 25 Sep 2025 13:16:53 +0000 (15:16 +0200)]
dhcpv4: simplify dhcpv4_fr_send() a bit

Some minor cleanups to dhcpv4_fr_send()

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: rename cookie variable
David Härdeman [Thu, 25 Sep 2025 13:10:50 +0000 (15:10 +0200)]
dhcpv4: rename cookie variable

The name is potentially confusing given that DHCPv4 includes the concept of a
magic cookie.  The variable is actually used as a cursor/write ptr, so rename
it to something more descriptive.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: remove some magic numbers
David Härdeman [Thu, 25 Sep 2025 09:44:43 +0000 (11:44 +0200)]
dhcpv4: remove some magic numbers

Add a bunch of #define's to dhcpv4.h and use the defined constants to reduce
the number of magic constants.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: update dhcpv4_msg_to_string()
David Härdeman [Wed, 24 Sep 2025 14:08:35 +0000 (16:08 +0200)]
dhcpv4: update dhcpv4_msg_to_string()

Simplify and make it cover all currently known message types.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agodhcpv4: dhcpv4_fr_rand_delay() fixups
David Härdeman [Wed, 24 Sep 2025 13:44:13 +0000 (15:44 +0200)]
dhcpv4: dhcpv4_fr_rand_delay() fixups

Move constants to dhcpv4.h, and make their names more descriptive. Also, use
abs() instead of labs(), since "msecs" is an int.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/266
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agoubus: improve correspondence between DHCPv[46]
David Härdeman [Sat, 4 Oct 2025 17:51:44 +0000 (19:51 +0200)]
ubus: improve correspondence between DHCPv[46]

The same parameter is called "accept-reconf-nonce" in the "ipv4leases" case and
"accept-reconf" in the "ipv6leases". I couldn't find anything in the OpenWrt
trees which depended on either naming, but renaming the IPv4 case seems to be
the safer bet since it is not part of the standard installation.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/267
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agoubus: minor correctness fix
David Härdeman [Sat, 4 Oct 2025 17:50:17 +0000 (19:50 +0200)]
ubus: minor correctness fix

Now, blobmsg_close_table() and blobmsg_close_array() happen to be identical, so
the actual code doesn't change at all, but this still caused me some
headscratching when I first saw it.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/267
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agoubus: don't expose ipv4leases if not supported
David Härdeman [Sat, 4 Oct 2025 17:48:02 +0000 (19:48 +0200)]
ubus: don't expose ipv4leases if not supported

This risks confusing other clients, since the "ipv4leases" method will return
an empty list. Better to not have the method available on dbus at all, so
clients will get an error when odhcpd is built without IPv4 support.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/267
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
6 days agoodhcpd: print compiled-in features in help message
David Härdeman [Sun, 5 Oct 2025 15:32:52 +0000 (17:32 +0200)]
odhcpd: print compiled-in features in help message

This is mostly useful for hacking, as a way to quickly determine from the command-line
how odhcpd has been built. But I also plan to use it in LuCI [1].

[1] https://github.com/openwrt/luci/blob/master/modules/luci-base/root/usr/share/rpcd/ucode/luci

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/268
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: rename a variable in dhcpv4_assign()
David Härdeman [Fri, 3 Oct 2025 10:19:50 +0000 (12:19 +0200)]
dhcpv4: rename a variable in dhcpv4_assign()

Rename "buf" to something more descriptive.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move the remaining forward declaration
David Härdeman [Wed, 24 Sep 2025 13:42:02 +0000 (15:42 +0200)]
dhcpv4: move the remaining forward declaration

Down to where it is clearer why it is needed (there's a circular dependency
between dhcpv4_fr_delay_timer() and dhcpv4_fr_rand_delay()).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move find_assignment_by_hwaddr()
David Härdeman [Wed, 24 Sep 2025 13:38:12 +0000 (15:38 +0200)]
dhcpv4: move find_assignment_by_hwaddr()

Move find_assignment_by_hwaddr() down to just before the sole user
(dhcpv4_lease()).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: reorder some more functions
David Härdeman [Thu, 2 Oct 2025 14:15:42 +0000 (16:15 +0200)]
dhcpv4: reorder some more functions

Move dhcpv4_free_assignment(), dhcpv4_insert_assignment(), dhcpv4_assign() and
dhcpv4_lease() further up, allowing more forward declarations to be removed.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move struct dhcpv4_dnr to header
David Härdeman [Wed, 24 Sep 2025 13:27:27 +0000 (15:27 +0200)]
dhcpv4: move struct dhcpv4_dnr to header

This helps declutter dhcpv4.c a little bit.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move dhcpv4_fr_stop() up
David Härdeman [Wed, 24 Sep 2025 13:20:26 +0000 (15:20 +0200)]
dhcpv4: move dhcpv4_fr_stop() up

Thus avoiding another forward declaration.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move dhcpv4_free_assignment()
David Härdeman [Wed, 24 Sep 2025 13:18:13 +0000 (15:18 +0200)]
dhcpv4: move dhcpv4_free_assignment()

Move dhcpv4_free_assignment() further down so it is grouped with the functions
creating the assignment.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: replace ip4toa() with inet_ntop()
David Härdeman [Wed, 24 Sep 2025 13:11:26 +0000 (15:11 +0200)]
dhcpv4: replace ip4toa() with inet_ntop()

Replace custom ip4toa() function with inet_ntop().

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move and rename handle_dhcpv4()
David Härdeman [Wed, 24 Sep 2025 13:00:57 +0000 (15:00 +0200)]
dhcpv4: move and rename handle_dhcpv4()

Move handle_dhcpv4() below the function it calls (dhcpv4_handle_msg()) and
rename it to dhcpv4_handle_dgram() for consistency with other function names.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: rename handle_addrlist_change()
David Härdeman [Wed, 24 Sep 2025 12:53:54 +0000 (14:53 +0200)]
dhcpv4: rename handle_addrlist_change()

Rename handle_addrlist_change() to dhcpv4_addrlist_change() for more
consistency with the other function names.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: rename setup_dhcpv4_addresses()
David Härdeman [Wed, 24 Sep 2025 12:52:50 +0000 (14:52 +0200)]
dhcpv4: rename setup_dhcpv4_addresses()

Rename setup_dhcpv4_addresses() to dhcpv4_setup_addresses() for consistency
with the rest of the function names.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: reorder more functions
David Härdeman [Thu, 2 Oct 2025 14:11:26 +0000 (16:11 +0200)]
dhcpv4: reorder more functions

Move dhcpv4_setup_interface(), setup_dhcpv4_addresses() and
handle_addrlist_change() towards the end of dhcpv4.c, closer to where they are
actually used, and remove two forward declarations.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: rename valid_until_cb()
David Härdeman [Wed, 24 Sep 2025 12:31:58 +0000 (14:31 +0200)]
dhcpv4: rename valid_until_cb()

Renaming valid_until_cb() to dhcpv4_valid_until_cb() is more consistent with
the other function names and is helpful if the function ever shows up in a
stack trace.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agodhcpv4: move dhcpv4_init() to end of dhcpv4.c
David Härdeman [Wed, 24 Sep 2025 12:29:01 +0000 (14:29 +0200)]
dhcpv4: move dhcpv4_init() to end of dhcpv4.c

This removes the need for some forward declarations. Also, inline the two
structs that are only used inside dhcpv4_init().

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/264
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
10 days agondp: Allow NS loopback for master iface
Haoyi Ci [Thu, 2 Oct 2025 08:14:05 +0000 (16:14 +0800)]
ndp: Allow NS loopback for master iface

This commit modifies handle_solicit() in ndp.c to correct IPv6 relay
handling of Neighbor Solicitation (NS) requests when no upstream
solicitation is received.

Background: In IPv6 relay mode, odhcpd discovers local devices only upon
receiving upstream NS packets. If no upstream NS arrives (e.g. because the
upstream router’s neighbor cache is still valid or no solicitation was
ever sent), OpenWrt may attempt neighbor resolution via the master (WAN)
interface instead of the LAN, leaving local devices undiscoverable and
breaking connectivity.

- When an NS packet is sent by the host's master interface, do not
  immediately return; instead continue searching slave interfaces for the
  target neighbor.
- When odhcpd responds to NS packets, add a check to prevent replying to
  NS packets that were sent by the host itself.

Signed-off-by: Haoyi Ci cihaoyi@outlook.com
Link: https://github.com/openwrt/odhcpd/pull/240
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
11 days agorouter: log “Sending a RA on lan” at LOG_DEBUG
Mark Mentovai [Fri, 11 Nov 2022 16:24:37 +0000 (11:24 -0500)]
router: log “Sending a RA on lan” at LOG_DEBUG

This reduces log spam when dhcp.odhcpd.loglevel ≥ 5, as neighbor
discovery router advertisements are routinely generated by timer and in
response to router solicitations.

Signed-off-by: Mark Mentovai <mark@mentovai.com>
Link: https://github.com/openwrt/odhcpd/pull/191
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
11 days agoodhcpd: update cmake file
David Härdeman [Sun, 14 Sep 2025 22:36:56 +0000 (00:36 +0200)]
odhcpd: update cmake file

Now that the minimum cmake version has been bumped to 3.13, the cmake file can
be modernized a bit. Although it might look like a lot of changes, most of them
are quite straightforward.

Every library is located in a consistent manner (allowing command-line
overrides of library locations, useful for local development, and also makes it
trivial to do static linking, if desired).

The compiler flags have been broken up to have one per line (making it easy in
the future to add/remove flags, which would create a simple one-line diff).

Although it might look like this bumps the C standard level, cmake is actually
smart enough to pick an earlier version if C11 isn't supported by the compiler
(quite unlikely on any versions of gcc currently in use in OpenWrt and even on
old distros).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/258
11 days agoodhcpd: convert README to markdown
David Härdeman [Tue, 30 Sep 2025 15:12:40 +0000 (17:12 +0200)]
odhcpd: convert README to markdown

Convert the README file to GitHub style markdown. I don't think it affects the
readability of the plain text file much (or not at all, rather). But it makes
it much easier to read for people looking at the GitHub repo.

I've intentionally kept the fancy markup at a minimum and also haven't changed
any help text messages in order to not get bogged down in how each help message
should read. That is something which can be addressed later.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/263
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
12 days agoodhcpd: allow the use of an alternative cfg file
David Härdeman [Sat, 20 Sep 2025 14:17:52 +0000 (16:17 +0200)]
odhcpd: allow the use of an alternative cfg file

This is mainly useful for development/testing, since it obviates the
need for a cfg file below the /etc hierarchy.

Also, fixup the help message (long options like "--help" are not
supported).

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/257
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
12 days agoodhcpd: remove confusing #defines
David Härdeman [Tue, 30 Sep 2025 17:45:24 +0000 (19:45 +0200)]
odhcpd: remove confusing #defines

Right now, odhcpd.h contains lines like "#define hwaddr mac", which is a great
footgun when hacking on odhcpd (like when creating a function with a local
variable named "hwaddr" and later getting compiler errors talking about how
"mac" has the wrong type...in a function which has no variable named "mac").

So, remove these definitions. I'm not sure the chosen variable names are the
most descriptive, but I erred on the side of caution and picked the alternative
that required the least amount of changes. We can always change the names of
the variables later.

Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/261
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
12 days agoodhcpd: improve RFC9096 § 3.5 SLAAC compliance
Álvaro Fernández Rojas [Thu, 7 Aug 2025 15:38:22 +0000 (17:38 +0200)]
odhcpd: improve RFC9096 § 3.5 SLAAC compliance

When a CE router provides LAN-side address-configuration information
via SLAAC:

*  A CE router sending RAs that advertise prefixes belonging to a
   dynamically learned prefix (e.g., via DHCPv6-PD) SHOULD record, on
   stable storage, the list of prefixes being advertised via PIOs on
   each network segment and the state of the "A" and "L" flags of the
   corresponding PIOs.

*  Upon changes to the advertised prefixes, and after bootstrapping,
   the CE router advertising prefix information via SLAAC proceeds as
   follows:

   -  Any prefixes that were previously advertised by the CE router
      via PIOs in RA messages, but that have now become stale, MUST
      be advertised with PIOs that have the "Valid Lifetime" and the
      "Preferred Lifetime" set to 0 and the "A" and "L" bits
      unchanged.

   -  The aforementioned advertisements MUST be performed for at
      least the "Valid Lifetime" previously employed for such
      prefixes.

This should be enabled by default with a folder in "/tmp" to avoid forcing
flash writes for all the users and at least handle the stale PIOs on PPPoe
reconnections. For example:
  uci set dhcp.odhcpd.piofolder="/tmp/odhcpd-piofolder"
  uci commit dhcp

There's a new ubus call which allows getting all the IPv6 RA PIOs, with
information about their lifetime and stale status:
  ubus call dhcp ipv6ra
  {
    "interfaces": {
      "br-lan": [
        {
          "lifetime": 5390,
          "prefix": "2001:db8:0:100::/64",
          "stale": true
        },
        {
          "prefix": "2001:db8:0:200::/64",
          "stale": false
        },
        {
          "prefix": "fd00::/64",
          "stale": false
        }
      ],
      "eth1.10": [
        {
          "lifetime": 5390,
          "prefix": "2001:db8:0:110::/64",
          "stale": true
        },
        {
          "prefix": "2001:db8:0:210::/64",
          "stale": false
        },
        {
          "prefix": "fd00:0:0:10::/64",
          "stale": false
        }
      ]
    }
  }

The current implementation performs a flash write per interface whenever
a new prefix is added or an existing prefix becomes stale.
Since some ISPs assign static or semi-static prefixes to their customers,
it should be up to the user to enable flash writes when needed.

Link: https://github.com/openwrt/odhcpd/pull/256
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2 weeks agodhcpv4: allow lease takeover (bugfix)
David Härdeman [Fri, 26 Sep 2025 17:22:34 +0000 (19:22 +0200)]
dhcpv4: allow lease takeover (bugfix)

Multiple MAC addresses are supported per static lease (congruent to dnsmasq)
Request on different MAC frees any old assignments for a given lease.

Signed-off-by: David Härdeman <david@hardeman.nu>
2 weeks agoodhcpd: bump minimum cmake version
David Härdeman [Tue, 16 Sep 2025 15:35:26 +0000 (17:35 +0200)]
odhcpd: bump minimum cmake version

This removes warnings with more recent versions of cmake.

3.13 is already required by e.g. ubus, so it seems like a reasonable
minimum.

Signed-off-by: David Härdeman <david@hardeman.nu>
3 weeks agoodhcpd: apply RFC9096 recommended lifetimes
Aviana Cruz [Sat, 16 Sep 2023 15:04:12 +0000 (15:04 +0000)]
odhcpd: apply RFC9096 recommended lifetimes

To address issues with hosts retaining stale IPv6 configuration, this change
implements the Recommended Option Lifetimes Configuration Values from
RFC 9096-Section 4.

The `ra_lifetime` is set to 2700s (45min) by default, and the DHCPv6-PD is now
capped at 2700s for preferred lifetime and 5400s (90min) for valid lifetime.

The following changes are introduced:

- The `ra_useleasetime` option is removed.
- New options `max_preferred_lifetime` and `max_valid_lifetime` are added to
  configure the upper limits for prefix lifetimes.
- The Router Advertisement, DNS, and DNR option lifetimes are now dynamically
  adjusted based on the advertised prefix lifetimes to ensure consistency.

These changes help to mitigate issues with stale configurations on client
devices, particularly after network renumbering events.

Signed-off-by: Aviana Cruz <gwencroft@proton.me>
3 weeks agoodhcpd: support multiple per-client MAC addresses
David Härdeman [Fri, 1 Aug 2025 18:14:35 +0000 (20:14 +0200)]
odhcpd: support multiple per-client MAC addresses

Allow the configuration of multiple per-client DHCPv4 MAC addresses. This is
for feature parity with dnsmasq.

Closes: #221
Closes: openwrt/openwrt#9598
Signed-off-by: David Härdeman <david@hardeman.nu>
6 weeks agorouter: replace ssize_t with size_t
Álvaro Fernández Rojas [Tue, 26 Aug 2025 14:11:36 +0000 (16:11 +0200)]
router: replace ssize_t with size_t

Both valid_addr_cnt and invalid_addr_cnt should be defined with size_t
instead of ssize_t because addr6_len and invalid_addr6_len are defined
with size_t.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
8 months agodhcpv6: add ipv6 pxe support
Hang Zhou [Sat, 4 Jan 2025 21:09:47 +0000 (08:09 +1100)]
dhcpv6: add ipv6 pxe support

1. Implement PxE in separate files where possible
2. Update README
3. User can add IPv6 PxE entries in `/etc/config/dhcp`.
4. The new section type is "boot6".
5. The compulsory "url" string specifies the URL to the bootable image.
6. The optional "arch" integer specifies the expected client machine type.
7. The last "boot6" section without "arch" defines the default bootable image.

Signed-off-by: Hang Zhou <929513338@qq.com>
9 months agorouter: move pref64 calculations to the config stage
David Härdeman [Sat, 10 Feb 2024 02:35:04 +0000 (03:35 +0100)]
router: move pref64 calculations to the config stage

If pref64 is misconfigured, it'll print a LOG_WARNING message every time a RA
is generated, which seems a bit excessive.

When fixing that, I noticed that all work is done for every RA, so I ended up
moving the gist over to the config stage where it is done once.

Signed-off-by: David Härdeman <david@hardeman.nu>
9 months agoodhcpd: add a helper function for addr6/prefix parsing
David Härdeman [Sat, 10 Feb 2024 02:02:28 +0000 (03:02 +0100)]
odhcpd: add a helper function for addr6/prefix parsing

Which allows a couple of variations of the same code to be removed...
the helper function also won't modify the input string.

Signed-off-by: David Härdeman <david@hardeman.nu>
9 months agonetlink: fix a memory leak
David Härdeman [Sat, 10 Feb 2024 00:44:48 +0000 (01:44 +0100)]
netlink: fix a memory leak

valgrind noted that addrs is allocated in netlink_get_interface_linklocal(),
but never freed in one case. While I was looking at that, I found a mysterious
double-realloc in netlink.c.

Signed-off-by: David Härdeman <david@hardeman.nu>
9 months agoodhcpd: make the IPv6 RA DNR lifetime configurable
David Härdeman [Sat, 28 Dec 2024 16:54:49 +0000 (17:54 +0100)]
odhcpd: make the IPv6 RA DNR lifetime configurable

Add a fake SVC parameter "_lifetime=" which allows explicit configuration of
the maximum time in seconds over which a given ADN announcement is valid. In
particular, this allows announcing ADNs which should no longer be used (zero
lifetime, see rfc9463, §6.1).

In the absence of an explicit lifetime, the previous logic is kept (i.e. the
lifetime is set to 3 * MaxRtrAdvInterval, as per §6.1 of RFC9463).

Signed-off-by: David Härdeman <david@hardeman.nu>
9 months agoodhcpd: add DNR (RFC 9463) support
David Härdeman [Fri, 9 Feb 2024 13:14:03 +0000 (14:14 +0100)]
odhcpd: add DNR (RFC 9463) support

Discovery of Network-designated Resolvers (DNR) allows devices on the network
to discover encrypted DNS resolvers, which has so far required either manual
configuration or other approaches (like systemd-resolved's "opportunistic"
mode).

To enable DNR, a new uci parameter has been added, which needs to contain at
the very least, the priority (1-65535, lower = higher priority) and the server
hostname (Authentication Domain Name, ADN, to use the wording of RFC9463):

config dhcp 'lan'
        …
        list dnr '100 foobar.example.com'

Optionally (and preferably), a comma-separated list of IP addresses and
SvcParams can also be specified, like this (line wrapping added):

config dhcp 'lan'
        …
        list dnr '100 resolver1.example.com
                    fda7:ab54:69fb::1,fda7:ab54:69fb::2,10.0.0.1
                    alpn=dot port=853'
        list dnr '200 resolver2.example.com
                    fda7:ab54:69fb::2,10.0.1.1,10.0.1.2
                    alpn=dot port=853'

Client support is on it's way (e.g. in systemd PR #30952 or in the Windows
Insiders program).

Signed-off-by: David Härdeman <david@hardeman.nu>
9 months agodhcpv4: store reqopts as uint8_t
David Härdeman [Sun, 11 Feb 2024 12:09:53 +0000 (13:09 +0100)]
dhcpv4: store reqopts as uint8_t

Storing request options as chars means that it's difficult to handle option
codes > 127 due to the signedness of char (and options aren't chars, they're
binary data).

Signed-off-by: David Härdeman <david@hardeman.nu>
17 months agoconfig: set RFC defaults for preferred lifetime
Paul Donald [Tue, 9 Apr 2024 03:04:10 +0000 (05:04 +0200)]
config: set RFC defaults for preferred lifetime

Update preferred lifetime default value in accordance with RFC4861.

Signed-off-by: Paul Donald <newtwen@gmail.com>
[ fix comment style ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agorouter: disambiguate and clarify 'no route' messages
Paul Donald [Tue, 9 Apr 2024 03:04:09 +0000 (05:04 +0200)]
router: disambiguate and clarify 'no route' messages

Better describe "no route" messages.

Signed-off-by: Paul Donald <newtwen@gmail.com>
[ improve commit title ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agorouter: rename minvalid to lowest_found_lifetime
Paul Donald [Tue, 9 Apr 2024 03:04:08 +0000 (05:04 +0200)]
router: rename minvalid to lowest_found_lifetime

The variable is used across all prefix lifetimes, serving as a low-tide
mark. It stores the lowest lifetime among all prefixes.

Signed-off-by: Paul Donald <newtwen@gmail.com>
[ fix comment format ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agotreewide: normalize spaces to tabs
Paul Donald [Tue, 9 Apr 2024 03:04:07 +0000 (05:04 +0200)]
treewide: normalize spaces to tabs

Normalize space to tabs.

Signed-off-by: Paul Donald <newtwen@gmail.com>
[ improve commit title and description ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agotreewide: spell-fixes and new comments for extra clarification
Paul Donald [Tue, 9 Apr 2024 03:04:06 +0000 (05:04 +0200)]
treewide: spell-fixes and new comments for extra clarification

Fix some spell mistake and add new comments for extra clatification.

Signed-off-by: Paul Donald <newtwen@gmail.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
[ improve commit title ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agorouter: limit prefix preferred_lt to valid_lt in accordance with RFC4861
Paul Donald [Tue, 9 Apr 2024 03:04:05 +0000 (05:04 +0200)]
router: limit prefix preferred_lt to valid_lt in accordance with RFC4861

Follow-up fix for bc9d317f2921 ("dhcpv6-ia: fix invalid preferred
lifetime").

https://www.rfc-editor.org/rfc/rfc4861#page-44

Fixes: bc9d317f2921 ("dhcpv6-ia: fix invalid preferred lifetime")
Signed-off-by: Paul Donald <newtwen@gmail.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
[ fix comment format and improve commit title ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agorouter: inherit user-assigned preferred_lifetime
Paul Donald [Tue, 9 Apr 2024 03:04:04 +0000 (05:04 +0200)]
router: inherit user-assigned preferred_lifetime

Inherit preferred_lifetime value irrespective of whether ra_useleasetime
is set or not.

User-provided values for preferred_lifetime are now assigned, instead of
ignored.

Before:
==
ICMPv6 Option (Prefix information : fd51:1c2a:8909::/64)
    Type: Prefix information (3)
    Length: 4 (32 bytes)
    Prefix Length: 64
    Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A)
    Valid Lifetime: Infinity (4294967295)
    Preferred Lifetime: Infinity (4294967295)
    Reserved
    Prefix: fd51:1c2a:8909::
==

After (preferred_lifetime set to 7 minutes):
==
ICMPv6 Option (Prefix information : fd51:1c2a:8909::/64)
    Type: Prefix information (3)
    Length: 4 (32 bytes)
    Prefix Length: 64
    Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A)
    Valid Lifetime: Infinity (4294967295)
    Preferred Lifetime: 420
    Reserved
    Prefix: fd51:1c2a:8909::
==

Signed-off-by: Paul Donald <newtwen@gmail.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
[ fix comment format and improve commit description ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agotreewide: refactor valid to valid_lt (lifetime)
Paul Donald [Tue, 9 Apr 2024 03:04:03 +0000 (05:04 +0200)]
treewide: refactor valid to valid_lt (lifetime)

Refactor "valid" (valid what?) to "valid_lt".

Signed-off-by: Paul Donald <newtwen@gmail.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
[ improve commit title ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
17 months agotreewide: refactor pref(erred) to preferred_lt (lifetime)
Paul Donald [Tue, 9 Apr 2024 03:04:02 +0000 (05:04 +0200)]
treewide: refactor pref(erred) to preferred_lt (lifetime)

Refactor "preferred" (preferred what?) and "pref" (obscure) to
"preferred_lt".

It is now more difficult to conflate prefix, preference and other "pref"
related terminology with preferred_lifetime.

Signed-off-by: Paul Donald <newtwen@gmail.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
[ improve commit title ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
23 months agoconfig: make sure timer is not on the timeouts list before freeing
Colin Whittaker [Sat, 24 Jun 2023 20:33:12 +0000 (13:33 -0700)]
config: make sure timer is not on the timeouts list before freeing

Signed-off-by: Colin Whittaker <colin.whittaker@adtran.com>
Signed-off-by: Chad Monroe <chad@monroe.io>
23 months agoadd hostsfile output in addition to statefile
Kevin Darbyshire-Bryant [Sat, 21 Oct 2023 18:50:25 +0000 (19:50 +0100)]
add hostsfile output in addition to statefile

a92c0a7 made the temporary state/leasefile hidden so that an atomic
change was made and dnsmasq only saw the new file on rename.  A
misguided optimisation was made to only rename the temporary file if
something had changed.  Unfortunately only address and hostnames were
considered in the change, lease durations were not.

As a result it was possible for LUCI which consumes the state/leasefile
to report DHCPv6 leases had expired when they had not.

Revert the optimisation so that the file rename occurs irrespective of
content change, this keeps LUCI reporting of state/lease expiry correct.

This leaves us back with hosts file/dnsmasq update problem. Solve this
by writing out a separate hosts file.  Update this file using the
original IP/Hostname change logic that prompts calling the 'lease'
script.

odhcpd config now supports a string 'hostsfile' which defines the path
and name of the hosts file in an identical manner to 'leasefile'.  A
state 'leasefile' must be defined IF a 'hostsfile' is also required.

eg.

leasefile '/tmp/odhcpdstate'
hostsfile '/tmp/hosts/odhcpdhosts'

Signed-off-by: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
2 years agodhcpv4: improve error when a prefix is too long
Ross Vandegrift [Mon, 16 Jan 2023 21:35:46 +0000 (13:35 -0800)]
dhcpv4: improve error when a prefix is too long

If a user tries to enable dhcpv4 on an interface with a /29, odhcp won't work.
The logs will only contain a message that doesn't help identify the problem.
It'd be idea to support any prefix with a valid pool, but at least this would
point a confused user in the right direction.

Signed-off-by: Ross Vandegrift <ross@kallisti.us>
2 years agoodhcpd: add support for dhcpv6_pd_min_len parameter
John Kohl [Sat, 24 Jun 2023 14:18:03 +0000 (10:18 -0400)]
odhcpd: add support for dhcpv6_pd_min_len parameter

The dhcpv6_pd_min_len configuration clamps the requested prefix
delegation to be at least as big as the option.  This allows a
router to manage the size of each downstream router's prefix
delegation length independently from the delegating interface's
prefix length.

This behavior is an implementation choice permitted by the RFCs.
The delegating router (us) is not required to honor the hint
(RFC3633, section 11.2, we MAY choose to use the information in the
option; RFC8168, section 3.2 has several SHOULDs about desired
choices for selecting a prefix to delegate).

This configuration allows us to conserve prefix space so that any
single router can't grab too much of it.  Consider if we have an
interface with a /56 prefix.  A requesting router could ask for a
/58 and take 1/4 of our total address space.  But if we set a
minimum of /60, we can limit each requesting router to get only 1/16
of our total address space.

sample config:

config dhcp 'pd'
    ...
    option dhcpv6_pd_min_len '60'

Signed-off-by: John Kohl <jtk.git@bostonpog.org>
[ use different comment style and fix commit description ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agorouter: Add PREF64 (RFC 8781) support
Ondřej Caletka [Sat, 4 Jun 2022 21:42:59 +0000 (23:42 +0200)]
router: Add PREF64 (RFC 8781) support

This option of IPv6 Router Advertisements allows devices connected to
a IPv6-only network to discover IPv6 prefix of the NAT64 gateway.
Devices can use this information for instance to setup client translator
(CLAT) from IPv4 to IPv6 in 464XLAT (RFC 6877) scenario or to handle
IPv4 address literal on application level.

To enable PREF64 option, a new uci parameter ra_pref64 has to contain
the NAT64 prefix, including prefix length. Only lengths of 96, 64, 56,
48, 40 and 32 bits are supported. For example, to annonce the Well-Known
Prefix:

config dhcp 'lan'
        …
        option ra_pref64 '64:ff9b::/96'

Fixes: #182
Signed-off-by: Ondřej Caletka <ondrej@caletka.cz>
[ remove extra space for Fixes tag ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agoconfig: use dedicated link local function to check interface
Christian Marangi [Mon, 3 Apr 2023 18:12:13 +0000 (20:12 +0200)]
config: use dedicated link local function to check interface

Use netlink_get_interface_addrs is wrong and doesn't actually work. The
function checks only for UNIVERSE address and is not suitable for
dumping linklocal address of an interface. Use the new and dedicated
function to get interface linklocal address to correctly check if the
interface can receive message.

Fixes: #197
Fixes: 7c0f603abc14 ("router: skip RA and wait for LINK-LOCAL to be assigned")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agonetlink: add support for getting interface linklocal
Christian Marangi [Mon, 3 Apr 2023 18:04:01 +0000 (20:04 +0200)]
netlink: add support for getting interface linklocal

Add support for getting interface linklocal address. This is needed to
make sure an interface have a valid link local address and such address
is not TENTATIVE. With these info we can check if an interface is ready
to accept packets.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agoRevert "config: recheck have_link_local on interface reload if already init"
Christian Marangi [Mon, 3 Apr 2023 17:42:43 +0000 (19:42 +0200)]
Revert "config: recheck have_link_local on interface reload if already init"

This reverts commit 29c934d7ab98ca0b5da0e3757b885a1d3c19a2f4.

Replace with a better more safe implementation.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agoconfig: fix feature for enabling service only when interface RUNNING
Christian Marangi [Tue, 28 Mar 2023 22:16:46 +0000 (00:16 +0200)]
config: fix feature for enabling service only when interface RUNNING

With ba30afcfec0a26ce4bcd96ea4d687c498b0ba4df it was found that odhcpd
service are setup even if an interface had no connection and was not
running. The commit introduced the change but required more fixup for
the feature to work correctly.

The close_interface() remove the interface from the avl list and this
cause the interface to be missing later in the code flow.
The intention of the commit was to just disable the service and enable
them later when the interface is correctly set to running with the flag
IFF_RUNNING.

Change the logic and introduce a new function reload_servies() that will
check IFF_RUNNING and enable or disable odhcp services.

This function is called on odhcpd_reload() for each interface. In
odhcpd_reload() also restore the original pattern with calling
close_interface() only when the interface is not inuse for odhcp.

Also call reload_services() on the single interface when a RTM_NEWLINK
event is fired reacting to a link change of an odhcp interface and
enabling the services if IFF_RUNNING is set.

Fixes ba30afcfec0a ("config: skip interface setup if interface not IFF_RUNNING")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agoconfig: recheck have_link_local on interface reload if already init
Christian Marangi [Thu, 23 Mar 2023 23:29:15 +0000 (00:29 +0100)]
config: recheck have_link_local on interface reload if already init

If an interface is already init in the odhcpd avl tables, have_link_local
is not set to true with a link local addr set as get ipv6 addr is skipped.

Move checking for have_link_local outside get_addr to better track when
an interface is ready and have a link local addr for interface already
init in odhcpd avl tables.

Fixes: #197
Fixes: 7c0f603abc14 ("router: skip RA and wait for LINK-LOCAL to be assigned")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2 years agorouter: skip RA and wait for LINK-LOCAL to be assigned
Christian Marangi [Thu, 16 Mar 2023 23:56:25 +0000 (00:56 +0100)]
router: skip RA and wait for LINK-LOCAL to be assigned

This fix a specific and corner case when the following error and similar
is printed in the log:

Failed to send to ff02::1%br-lan (Address not available)

The cause for this was tracked down to the lack of the interface of a
configured LINK-LOCAL IPV6 address resulting in odhcpd_send() always
failing.

A LINK-LOCAL IPV6 address is assigned only after the interface has
carrier and is set to IFF_RUNNING and require some time for the address
to be assigned due to DAD logic.

In the case where an interface was just UP, odhcpd RA may fail since the
LINK-LOCAL IPV6 address still needs to be assigned as it still need to
be "trained". From the kernel view this is flagged in the IPV6 interface
address with the flag IFA_F_TENTATIVE, that means the address still
needs to be checked and follow DAD process.

This is only a transient problem and the DAD process is required only
once till the interface is not set DOWN.

To handle this, add some check to verify if the address has to be
checked and add an additional bool to flag if the interface have a
LINK-LOCAL assigned.

Skip sending RA if the interface still doesn't have finished the DAD
process and retry at the next RA.
A notice log is added to track this special case to track problematic
case and even more corner case.

Logic to check if interface have LINK-LOCAL are:
- When interface is setup, on scanning for the interface ipv6 address
  check if at least one address is NOT in IFA_F_TENTATIVE state.
- With interface already up but with still no LINK-LOCAL react on the
  RTM_NEWADDR event and set LINK-LOCAL if the addrs added by the event
  is a LINK-LOCAL reflecting that the interface finally ended the DAD
  process and have a correct address.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Acked-by: Hans Dedecker <dedeckeh@gmail.com>
2 years agoconfig: skip interface setup if interface not IFF_RUNNING
Christian Marangi [Thu, 16 Mar 2023 22:44:43 +0000 (23:44 +0100)]
config: skip interface setup if interface not IFF_RUNNING

We currently setup odhcp service even if the interface is not running.
This is the case for bridge or specific interface that are flagged as UP
but have no carrier as nothing is connected to it.
This cause a similar error like:

Failed to send to ff02::1%br-lan (Address not available)

This is caused by the kernel assigning IPV6 address only when the
interface is set to IFF_RUNNING.
A LINK-LOCAL IPV6 address is required for odhcpd_send() to work or every
request will be rejected.

To fix this setup services only when interface is in IFF_RUNNING state.
When an interface change state, odhcpd is reloaded and the services are
correctly setup again.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Acked-by: Hans Dedecker <dedeckeh@gmail.com>
2 years agoRevert "odhcpd: Reduce error messages"
Stijn Tintel [Tue, 14 Mar 2023 23:37:44 +0000 (01:37 +0200)]
Revert "odhcpd: Reduce error messages"

Silencing an error message without properly understanding why it occurs
is terrible practice. "I think this would be better served as debug."
doesn't inspire confidence the author actually understood what was going
on, so revert this commit.

This reverts commit 90d6cc9cd48a333b95604ff90f7ffe67fe14efe3.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
2 years agoodhcpd: Reduce error messages
Peter Naulls [Tue, 24 Jan 2023 19:35:02 +0000 (14:35 -0500)]
odhcpd: Reduce error messages

When there's no network cable connected to LAN, then odhcpd does this:

Tue Jan 24 18:32:04 2023 daemon.err odhcpd[2017]: Failed to send to
ff02::1%lan@br-lan (Address not available)
Tue Jan 24 18:32:20 2023 daemon.err odhcpd[2017]: Failed to send to
ff02::1%lan@br-lan (Address not available)
Tue Jan 24 18:32:36 2023 daemon.err odhcpd[2017]: Failed to send to
ff02::1%lan@br-lan (Address not available)
Tue Jan 24 18:32:52 2023 daemon.err odhcpd[2017]: Failed to send to
ff02::1%lan@br-lan (Address not available)

Accurate, but not very interesting. I think this would be better served
as debug.

Signed-off-by: Peter Naulls <peter@chocky.org>
2 years agorouter: always check ra_default
stijn@linux-ipv6.be [Thu, 16 Feb 2023 20:30:41 +0000 (22:30 +0200)]
router: always check ra_default

We currently only check ra_default when an interface has valid
addresses. This results in ra_default being ignored in case we have an
interface with only link-local addresses. This effectively breaks the
use of value 2 for the ra_default parameter.

Fix this by always checking ra_lifetime, regardless of the interface
having public addresses or not.

Fixes: #11930
Fixes: 83e14f455817 ("router: advertise removed addresses as invalid in 3 consecutive RAs")
Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
Acked-by:Hans Dedecker <dedeckeh@gmail.com>

2 years agorouter: improve RA logging
stijn@linux-ipv6.be [Thu, 16 Feb 2023 20:30:40 +0000 (22:30 +0200)]
router: improve RA logging

We only set the RA lifetime to what is configured in UCI when there is a
default route and valid prefix. In any other case, we set it to 0. This
leads to confusion where people believe ra_lifetime is completely
ignored. In case there is a default route, but no valid prefix, a debug
message explains this, but if there is no default route, we silently
override ra_lifetime.

Add a debug message for the latter case, and explicitly mention
overriding ra_lifetime in both cases.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
Acked-by: Hans Dedecker <dedeckeh@gmail.com>
2 years agodhcpv4: detect noarp interfaces
Mikael Magnusson [Tue, 7 Feb 2023 16:53:00 +0000 (16:53 +0000)]
dhcpv4: detect noarp interfaces

Don't add ARP entries to interfaces with IFF_NOARP, it causes
problems with for example WireGuard interfaces (which requires
this change to be usable with DHCPv4-over-DHCPv6).

Signed-off-by: Mikael Magnusson <mikma@users.sourceforge.net>