Stop Dnsmasq sending ULA prefixes in stateless RAs

Started by OPNenthu, September 15, 2025, 12:54:10 AM

Previous topic - Next topic
September 15, 2025, 12:54:10 AM Last Edit: September 15, 2025, 01:10:53 AM by OPNenthu
My interfaces are set to WAN tracking for IPv6 which works fine with Dnsmasq using the 'ra-stateless' mode.  Clients get the dynamic /64 prefix in the RA and auto-configure their GUAs with SLAAC.

I want to additionally configure ULA ranges with DHCPv6 for internal networking and firewalling purposes.  The desire is that hosts with ULA addresses should communicate that way and only use their GUAs for internet, if this is possible.

The first thing I did was to add ULA virtual IPs (Interfaces->Virtual IPs) for each interface.  For example, on the LAN interface I added an IP alias like fd00:db8:1001::1/64.

The problem now is that Dnsmasq is picking up the ULA prefix on the interface in addition to the GUA dynamic prefix and advertising both in the RAs.  Therefore hosts are auto-configuring IPs in both ULA and GUA subnets, including privacy addresses for each.

Is there a way to prevent Dnsmasq from including the ULA prefix in the stateless RA?  I would like to manually specify a DHCPv6 range for those like fd00:db8:1001::1000 - fd00:db8:1001::ffff and for them to be excluded from host auto-configuration and privacy extensions.  Ideally, I'd like to specify the ranges as partial IPs in Dnsmasq like ::1000 - ::ffff using the interface Constructor, so that Dnsmasq will auto-register hosts in DNS.

I tried to set the 'slaac' mode in addition to the 'ra-stateless' mode as suggested in the OPNsense docs but I can't get the desired result.

September 16, 2025, 08:34:19 AM #1 Last Edit: September 16, 2025, 08:52:18 AM by OPNenthu
I've been doing some testing on OPNsense 25.7.3_7 and it works fine with Kea DHCPv6 + radvd, but Dnsmasq has some problems with this setup.  Maybe it's not capable at this time?

Starting assumption: IA_PD is configured with WAN tracking and /64 prefixes are being advertised via Unmanaged RAs (for auto-configured GUAs with SLAAC), very similar to this guide: https://forum.opnsense.org/index.php?topic=45822.0

Steps for adding ULA addressing via DHCPv6:

1) Add Virtual IP to interface (Interfaces->Virtual IPs->Settings):
- Mode: IP Alias
- Interface: <if_name>
- Network / Address: fd00:db8:abcd:1000::1/128

The /128 net mask is important so that the ULA prefix is not advertised by RA daemons.  Otherwise you get auto-configured ULAs, defeating the purpose.  As a side-effect of this, steps 2 & 3 are also needed.

2) Add gateway for ULA VIP (System->Gateways->Configuration):
- Name: ULA_<if_name>_GW
- Interface: <if_name>
- Address Family: IPv6
- IP Address: fd00:db8:abcd:1000::1

3) Add static route for subnet (System->Routes->Configuration):
- Network Address: fd00:db8:abcd:1000::/64
- Gateway: ULA_<if_name>_GW

4) Configure RAs (Services->Router Advertisements and select <if_name>):
- Router Advertisements: Assisted (M+O+A flags)
- Advertise Default Gateway: ✓
- DNS Options: Use the DNS configuration of the DHCPv6 server

5) Configure Kea DHCPv6 (Services->Kea DHCP->Kea DHCPv6):
- Settings->Enabled: ✓
- Settings->Interfaces: <if_name>
- Subnets->Subnet: fd00:db8:abcd:1000::/64
- Subnets->Interface: <if_name>
- Subnets->Pools: fd00:db8:abcd:1000::1000-fd00:db8:abcd:1000::ffff
- Subnets->DNS servers: <your DNS>
- Subnets->Domain search: <your domain>

In my case a router reboot and a client interface reset helped to get things working and the result was 2 GUAs from my ISP prefix (one is a temporary address from privacy extensions) and only one ULA address from DHCP:

$ ip -6 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
3: enp10s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fdf8:fb25:3a87:1030::1000/128 scope global dynamic noprefixroute
       valid_lft 3078sec preferred_lft 1578sec
    inet6 2601:xx:xxxx:6db3:f996:f5:7061:e6d/64 scope global temporary dynamic
       valid_lft 86069sec preferred_lft 14069sec
    inet6 2601:xx:xxxx:6db3:8551:f7a2:xxxx:xxxx/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 86069sec preferred_lft 14069sec
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

---

I tried the same with Dnsmasq and with various combinations of RA modes in the Dnsmasq ranges.  I even tried splitting the IPv6 ranges: one for GUAs and one for ULAs.  In no cases could I reproduce the desired result.

When I don't use an interface constructor and specify the ranges as full IPs, then DHCPv6 doesn't work and I get an error in Dnsmasq logs:

Warning  dnsmasq-dhcp  no address range available for DHCPv6 request via vlan0.30

When I used the interface constructor and specified the ranges as suffixes (::1000-::ffff), then my client would get assigned DHCP addresses under both the GUA and ULA prefixes.  This is bad because it conflicts with with GUAs from SLAAC with privacy extensions.

When I tried to work around it by assigning the ULA to a Loopback device and then specifying that loopback interface as the constructor for the DHCP range, it also didn't work.  I suspect this creates some kind of routing issue that trips up Dnsmasq.

When I tried without a static route and gateway, using only a VIP with a net mask >= /64 (I tried several prefix lengths), then again the ULA prefix is advertised in the RAs and auto-configured by SLAAC.  I think this option came the closest and might have worked if Dnsmasq had an option to exclude interface-assigned prefixes from RAs.


References:

https://github.com/opnsense/core/issues/7513
https://github.com/opnsense/core/issues/8455

For more complicated configurations, using radvd is fine.

You can also use dnsmasq only for dhcpv6 and pair it with radvd for RA.
Hardware:
DEC740