Android 16 and NDP cache

Started by reinob, April 14, 2026, 05:31:45 PM

Previous topic - Next topic
Context: Opnsense 25.7.11_9-amd64. IPv6 works perfectly well with computers (Linux desktop, Windows laptop, Android 16 smartphone (Nothing 2a), various Raspberry Pi's and many VMs running in a Proxmox computer).

Original problem: my new phone (Motorola Edge 70, with Android 16) appeared to lose IPv6 connectivity after some time (could be seconds, minutes or even hours). After lots of debugging and trying different workarounds, I have finally verified that when the phone is in that weird state, it can still send ECHO REQUESTS to a remote (linux) server (phone → router → server). This is clearly visible in the server logs (I added logging to the nftables rule to make sure).

However, the ECHO REPLY from the server reaches the router (opnsense), but it gets stuck there, because the IPv6 addresses of the smartphone expire from the NDP cache and don't appear again (apparently when Android sleeps it ignores Neighbor Solicitation messages).

The phone itself has its link-local address and two GUAs, like

inet6 2003:c9:XXXX:db00:2c6:fd40:fe92:44a9/64 scope global temporary dynamic
       valid_lft 86090sec preferred_lft 14090sec
    inet6 2003:c9:XXXX:db00:d484:d675:64b6:5235/64 scope global dynamic mngtmpaddr stable-privacy
       valid_lft 86090sec preferred_lft 14090sec
    inet6 fe80::4db0:84b2:c5ac:2aeb/64 scope link stable-privacy
       valid_lft forever preferred_lft forever

Watching (with ndp -an | grep $MAC in a loop) I can see "live" how the two addresses disappear and never appear again the NDP table.

I've tested that by adding the two GUAs as permanent entries in the NDP cache (with ndp -s $GUA1 $MAC and ndp -s $GUA2 $MAC) everything works again, and continues to work (because the entries never expire).

Given that the Interface IDs of the phone should remain constant (the "random" MAC is fixed for a given SSID, so it's stable), I want to make sure that whenever the IPv6 prefix of the LAN (bridge0) changes, the two GUAs are calculated (I have made a shell script that does that, correctly :) and added to the NDP cache. Maybe it should also delete the previous ones, if/as they become invalid (this should be easy enough, though).

Currently I have put a script in /usr/local/etc/rc.syshook.d/start which calculates the GUAs and does "ndp -s" for them.
It also (just in case) does "ndp -i bridge0 -- reachable 3600000"

Question 1: is there an easier way (set once and forget) to make sure that the IPv6 addresses of (all, or this one) devices get a permanent status in NDP and are updated automatically if/when the IPv6 prefix changes?

Question 2: how can I trigger running a script whenever the IPv6 prefix of LAN (bridge0, in particular) changes? (it is configured as a "tracked interface").

I hope I didn't write too much, and I hope the questions are clear enough (I'm by no means an expert in IPv6, and a complete newbie in Opnsense and FreeBSD).

Thanks in advance for any help/ideas.

Quote from: reinob on April 14, 2026, 05:31:45 PMQuestion 1: is there an easier way (set once and forget) to make sure that the IPv6 addresses of (all, or this one) devices get a permanent status in NDP and are updated automatically if/when the IPv6 prefix changes?
I think that's why this year they have added https://docs.opnsense.org/manual/neighbors.html#automatic-discovery to OPNsense together with some Captive Portal related features, but I am not entirely sure to be honest...
Weird guy who likes everything Linux and *BSD on PC/Laptop/Tablet/Mobile and funny little ARM based boards :)

Thanks! Yes, that seems to point in the right direction, especially the "Static Assignments".

I will test that to see if that can replace my /usr/local/etc/rc.syshook.d/start script, but I guess the issue of how to deal with prefix changes still remains.

April 15, 2026, 10:29:24 AM #3 Last Edit: April 15, 2026, 10:36:24 AM by Monviech (Cedrik)
I would be interested in how my ndp proxy works for your android phones since it sets static host routes and also replaces them dynamically when the prefix changes.

It does not override the kernel level ndp behavior though but it may interact differently with your devices that entices them to work more consistently. Thats just a guess though. (none of my android phones have issues with it)


https://docs.opnsense.org/manual/ndp-proxy-go.html

If you want to try it set the neighbor cache timeout to a higher value if the android devices are very silent while in sleep mode so the cache does not drop these neighbors prematurely.

Not sure it will bind to a bridge though, never tried it. I have a bailout if the interface is not ethernet or point to point (loopback) related and the proxy will refuse loudly to start.
Hardware:
DEC740

Thanks, Monviech.

I'll have to read up on your ndp-proxy, but I'm not sure if I can use it.

My WAN interface ("WAN_VLAN7") is pppoe1, using an ethernet port connected to the Deutsche Telekom modem.
My LAN interface ("LAN") is bridge0, which bridges 5x Ethernet ports (I'm using a weird PC-like computer with 6 ethernet ports).

There is this big fat warning
"If you receive a single /64 prefix via DHCPv6-PD on a PPPoE link, it must be terminated on a router before the proxy. This could be another OPNsense, or a device like a Fritzbox. The proxy does not listen and learn a prefix from DHCPv6. To use PPPoE as upstream, IPv6 configuration must be set to SLAAC."

which applies to my case (receive /64 prefix via DHCPv6-PD over PPPoE).

I wonder if I could set-up the NDP proxy in a separate box (upstream would be the current opnsense router and downstream would be a switch where I would connect the APs, which are currently connected to the router).

But I'm still confused, so I'll read the whole thing and see if I can make sense of it and (sensibly) make use of it ¯\_(ツ)_/¯

Thanks in any case!

April 15, 2026, 02:26:03 PM #5 Last Edit: April 15, 2026, 02:39:30 PM by Monviech (Cedrik)
The warning is only about prefix delgation, if slaac works via pppoe it works as well, some users run it like that. I also tested it via pppoe myself when inplementing it (Deutsche Telekom and Zyxel VMG Modem before Opnsense, opnsense terminating pppoe)

The only untested thing is your bridge, I don't know if it will work with that.

With pppoe and bridge its a bit too unfortunate I think, I personally terminate my PPPoE ISP with a Fritzbox and go from there so I dont have to deal with that, but it also works natively with PPPoE since thats explicitely implemented into the proxy.

I feel like showstopper will most likely be the bridge (I dont like those I rather use dedicated switches).

Though I assume just selecting a /single/ bridge member interface (not the bridge directly) will work...? But Im careful here, could be strange, very much untested.
Hardware:
DEC740

@Monviech If it's going to work at all, my "educated guess" is that you must use the bridge interface and not a single member. The latter would be a scope violation. Remember, in FreeBSD a bridge member IF must not take part in any layer 3 function/traffic.
Deciso DEC750
People who think they know everything are a great annoyance to those of us who do. (Isaac Asimov)

April 15, 2026, 03:41:03 PM #7 Last Edit: April 15, 2026, 03:43:24 PM by Monviech (Cedrik)
@Patrick makes sense, since I don't use bridges myself Im unsure what will happen. Depends on the DLT type of a bridge interface; if it's ethernet "like" it will probably work. But very unsure right now and unable to check atm.

Here is my bailout in the proxy for reference

https://github.com/Monviech/ndp-proxy-go/blob/bc34da934817427520aa63d0b54b35d25e36bb61/main.go#L63

Hardware:
DEC740

April 15, 2026, 09:49:32 PM #8 Last Edit: April 15, 2026, 09:51:10 PM by reinob
Thanks for the additional info. I may be able to test a few things during the weekend (can't afford to disturb the network while $FAMILY is busy).

Originally I thought that having 6 ports directly on the firewall mini-PC (the brand is "sharedvi" and has 6x i226) would be cool, but now I see that the Unix philosophy always wins, and I should have bought a 2-port thing and a proper switch (though I have to say that aside from this weird problem with the Motorola everything is working great).

For now I've set up a cron job (had to learn the (to me) weird — but elegant :) — way of defining cron jobs in Opnsense) which runs a shell script which generates the (expected/assumed) GUAs of the phone and adds them to NDP (and removes unexpected GUAs).

It seems to work OK, but I still have to check it for longer periods. The phone has Tailscale always on, which I hope is not affecting things. The good thing is that if I set my opnsense router (which also has tailscale) as exit node, everything works perfectly, both at home and away (5G, other WLANs, etc.).

But I'd prefer not being dependent on this (at the expense of being dependent of a workaround in Opnsense, but hey).

Ideally (even though it's also a workaround) it would be nice to disable IPv6 for this specific client, but Android doesn't allow that, and I don't think radvd/dnsmasq (or the whole RA concept) allows for that.

But just to ask: can RA work with unicast instead of multicast?

(I know, many questions, all dumped into a single thread, but I hope that's OK)

Quote from: reinob on April 15, 2026, 09:49:32 PMOriginally I thought that having 6 ports directly on the firewall mini-PC (the brand is "sharedvi" and has 6x i226) would be cool, but now I see that the Unix philosophy always wins, and I should have bought a 2-port thing and a proper switch
I disagree : It's great to have one NIC Port for each Network and maybe only use the 6th one for assigning VLAN Interfaces to it :)

I have done the same with the 4 NIC Ports that my OPNsense has :
- NIC Port 0 = WAN
- NIC Port 1 = Default LAN as Management Network for all devices.
- NIC Port 2 = Home Network
- NIC Port 3 = Guest VLAN Interface assigned and any other stuff in the future...
Weird guy who likes everything Linux and *BSD on PC/Laptop/Tablet/Mobile and funny little ARM based boards :)

RA always works with multicast. Multicast is a mandatory part of IPv6.

To make sure you at least have the chance that everything works with the bridge as intended, make sure to

- set the two tunables from step 6 of the LAN bridge guide in the docs
- assign the LAN interface with IP addresses (v4 and v6) to the bridge interface and not any of the members
- do not assign any of the member interfaces, especially never configure an IP address on a bridge member

If that is all configured correctly, I do not see why you should not use a 6 port PC as a 5 port "switch" just like any consumer router. Only do not expect 10 G speeds. But 1 G is perfectly fine. The FreeBSD bridge is better than some say.

HTH,
Patrick
Deciso DEC750
People who think they know everything are a great annoyance to those of us who do. (Isaac Asimov)