Firewall4/nftables, rules for Android IPv6 clients

Trying to improve firewall rules for smartphone clients, mostly Android.
Bandwidth restrictions, hourly restrictions, some port forwards, etc.

IPv4 is easy: restrict traffic by default, whitelist clients in firewall using
DHCP issued static IPs. Port forward to static IP's. Thats all.

IPv6 is hard, how are you supposed to match clients in firewall?

  1. DHCPv6 doesn't work on Android, SLAAC only, can't issue static IP's.
  2. Latest Android uses random privacy IPv6 addresses everywhere: link-local, main, temporary. No corellation to MAC at all. Can't match that.
  3. Outgoing connections are using temporary addresses. Can't match that.
  4. Even if you bother to match on supposedly random-but-stable main IPv6 address for incoming connections, as soon as your upstream IPv6 prefix changes, main IP changes, your rule fails.

Basically the only hope seems to be matching by MAC. But...

  1. Firewall4 doesn't support matching by dest_mac, only by src_mac.
  2. nftables can match both by source and destination mac in prerouting and postrouting chains. But it only works reliably in IPv4, with hardware L2 devices.
  3. nftables "ether saddr/daddr" fails when upstream IPv6 interface is L3 tunnel, like Wireguard in my case. Tracing in nftables (nftrace set 1) doesn't display MAC's at all. Logging in nftables (log flags all) displays some phantom MAC's, nothing close to real ones.

Any ideas?

If you disable temporary IPs, the Androids will eventually use the same SLAAC address.

Outoing connections are usually not controlled by the firewall.

Not sure one can disable temporary IPs in Android without root, besides I don't own these devices to mess with settings.

I need to control outgoing connections, in order to exclude certain whitelisted devices from restrictive default policy.

Negative netmask matching in firewall3/4 only works for stable non-privacy IP's.
For privacy ones, as soon as prefix changes, Android regenerates and randomizes second part of IP as well, and you are back to square one.

I can in mine.

Then you should tell the users to follow your instructions, if they want to use your service.

Different SSID would be much easier.

This is not exactly a problem of OpenWrt, we work with what we have. Inform your users not to use temporary IPs, if they wish to use your service.

Sorry, I'm looking for a technical solution, not a political/educational one.
Every mainstream OS now enables temporary/privacy adresses by default, no point going against the flow.

implement separate SSID's and use a sign on portal. Like supermarkets or shopping malls do.

unrecognised mac? gotta sign up again. They will either turn off private addressing or have to jump through your hoops again.

That being said? Google are ****s for not supporting DHCPv6 and their engineer needs his balls dipped in honey and getting staked out on an ant farm.

:boom: :heart_on_fire:

Google's issue tracker - https://issuetracker.google.com/issues/36949085

Both android and apple can disable private ips. its a setting in your wifi. even desktop clients can do it.

MAC randomization can be toggled in Android WiFi settings, but I don't see any IPv6 privacy related knobs.

Just checked, my own Samsung Android 12 phone has WiFi MAC randomisation turned off, yet all three IPv6 addreses assigned to WiFi interface are completely random.

Just checked as well and my Motorola Android 11 uses the same IPv6 addresses when I disconnect and reconnect to the same SSID.

Rather than whitelisting certain "allowed" devices, create a separate SSID with a separate password and tag / match packets based on that?

Hah, found the solution!

Since filtering by source and destination MAC in kernel netfilter IP layer is broken (or perhaps incomplete when passing packets betwen L2 and L3 devices), we can drop to bridge layer.

Below is a nftables example, ratelimiting filter both outgoing and incoming traffic by MAC, doesn't matter if client is using IPv4 or IPv6 with all sort of temporary/random SLAAC address shenanigans, or if upstream IPv6 prefix suddenly changes.

Well, until Android/iOS client toggles WiFi MAC privacy switch, but then device gets dropped to default pleb list anyway.

flush ruleset bridge

table bridge mytable {
  chain postrouting {
    type filter hook postrouting priority filter; policy accept;
    ether daddr "aa:bb:cc:dd:ee:01" accept comment 'whitelisted MAC #1'
    ether daddr "aa:bb:cc:dd:ee:02" accept comment 'whitelisted MAC #2'
    ether daddr "aa:bb:cc:dd:ee:03" accept comment 'whitelisted MAC #3'
    meter m_download { ether daddr limit rate over 2 mbytes/second } drop comment "Default download speed limit"
  }
  chain prerouting {
    type filter hook prerouting priority filter; policy accept;
    ether saddr "aa:bb:cc:dd:ee:01" accept comment 'whitelisted MAC #1'
    ether saddr "aa:bb:cc:dd:ee:02" accept comment 'whitelisted MAC #2'
    ether saddr "aa:bb:cc:dd:ee:03" accept comment 'whitelisted MAC #3'
    meter m_upload { ether saddr limit rate over 1 mbytes/second } drop comment "Default upload speed limit"
  }
}

@jow , any chance to allow including nftables snippets outside of inet/fw4 scope, like you mentioned in Firewall4 / NFtables Tips and Tricks - #39 by jow ?