DS-Lite (PPPoE|DHCPv6-PD) silently broken: dhcp6c on pppoe interface not startin

Started by tobinger, May 15, 2026, 10:39:53 PM

Previous topic - Next topic
Hi all (esp. @franco),

After migrating a working DS-Lite setup (M-net München, PPPoEv4 + DHCPv6-PD + GIF to AFTR `2001:a60:0:1::ffff`) from pfSense 2.8.1 to OPNsense 26.1.2_5 on identical hardware and WAN link, I hit a silent failure: PPPoE comes up fine, link-local IPv6 is reachable on `pppoe0`, dpinger reports the WAN IPv6 gateway as Online — but `/var/etc/dhcp6c.conf` stays at 0 bytes, no `IA_PD` request ever leaves, no global IPv6 ever appears on LAN (no track6 prefix), and the gif tunnel never gets a tunnel source.

This is distinct from the race fixed in commit `315153a07` ([thread 35876]) — that one addresses gif-reload after `newwanipv6` and assumes `dhcp6c` was started. Here `dhcp6c` is never started in the first place.

I traced the three code paths that could initialize `dhcp6c` on the PPPoE interface — `interface_configure()` in `interfaces.inc`, `ppp-ipv6.php`, and `rc.newwanipv6` — and all three skip it when `<ipaddr>pppoe</ipaddr>` + `<ipaddrv6>dhcp6</ipaddrv6>` are configured on the same WAN. The guard `interface_ppps_bound()` evaluates the v6 branch as `!ipv4_mode && ipv6_mode` = `false` for this combination, so the PPP layer says "DHCPv6 is not mine" while `interface_configure()` says "PPP owns v6, skip" — and `dhcp6c.conf` ends up empty.

Full reproducer, root-cause walkthrough with `file:line` references, and a small workaround script (`/usr/local/etc/rc.syshook.d/start/99-dslite-gif-rebuild`, ~20 lines of `sh`) that reliably gets us boot-to-Internet in ~10–60 s on M-net — https://gist.github.com/tobinger/89b3f4d7cdddf98571e20876792eb081.

Happy to test patches on this DS-Lite link. Also happy to file as a GitHub issue if you'd prefer that as the formal venue.

— Tobi

When you request real IPv4 from M-Net, you can use PPPoE plus DHCPv6, but you also have to request a prefix only as M-Net does not give you a IA_NA. IDK if the same is the case with the AFTR setup.
Intel N100, 4* I226-V, 2* 82559, 16 GByte, 500 GByte NVME, ZTE F6005

1100 down / 450 up, Bufferbloat A+

Solved — and a bit of a facepalm moment on our end.

Coming from pfSense, we faithfully replicated a working DS-Lite config 1:1 (PPPoE on IPv4 + DHCPv6 on IPv6), built a hookscript to paper over the bits that didn't behave, and were happily knee-deep in `dhcp6c.conf` and `rc.syshook.d` debugging when Franco's reply landed and quietly pointed out we were doing it wrong from the start.

The fix, courtesy of Franco: on DS-Lite, set IPv4 Configuration Type = None on the WAN interface. PPPoE stays as the point-to-point device, IPv6 stays on DHCPv6 — that's it. Anyone setting this up from scratch on OPNsense would probably pick this intuitively; it's exactly the migration baggage of "but in pfSense it was PPPoE..." that led us astray.

After the change everything just works out of the standard code path: `dhcp6c` starts on `pppoe0`, the PD comes in, `gif0` builds, IPv4 and IPv6 are happy across full reboots. Custom hookscript: gone. Future upgrades: a lot less scary.

Big thanks to Franco — both for the laser-precise pointer and for the improvements that made this the right answer in the first place. Sometimes the best fix is "stop fighting the framework."

Now here too.. nice to hear! This was a fun topic to work on for 25.1 with the community. We did a few feedback rounds actually.  :)


Cheers,
Franco
"AI has absolutely reduced the cost of creating technical debt." -- ChatGPT

Good to know, that would be an alternative that saves some money on M-Net, because you can now avoid paying for "real IPv4" and stay with CG-NAT. I posted it here.

P.S.: I tried the "None" IPv4 setting for my "real IPv4" setup - it does not work, because you do not get any IPv4 then.
Intel N100, 4* I226-V, 2* 82559, 16 GByte, 500 GByte NVME, ZTE F6005

1100 down / 450 up, Bufferbloat A+

From what I know M-Net's AFTR also works when you have a dual-stack contract.