Firewall settings with relayd

How can I properly isolate devices behind a RelayD bridge using the firewall ?
The devices should only be allowed to do what’s absolutely necessary. Some should have access to internal traffic only, while others should be allowed to connect to the Internet, but only to specific addresses/ports.
Is that even reasonably possible with Relayd ? I don’t think so.

In general terms you are talking about “guest network” but you have a dull knife to cut through. With enough effort and (brain) force it will work.

Yes it is possible to attach a firewall to “our” side of relayd, but bridge firewall is totally not supported by fw4 or -3

See upstream documentation

Above general concepts probably worth exploring docker and its ebtables usage.

Feel free to ask further if you need help with firewall rules.

Sorry, I don’t quite understand that sentence. Does it mean that it does work — RelayD together with bridge firewalling — or that it’s not supported?

I’ve already managed to secure a guest network on another repeater using firewall rules without any issues.
However, in this case it’s not a guest network (which would normally be a separate IP subnet), but rather part of the local network itself.

Bridge filtering is SUPPORTED just not instrumented in any way in OpenWrt and You are to do the heavy lifting. You made through the normal routed-nat firewall with checkboxes, happy? For the bridge you will get none of that comfort.

1 Like

Thanks a lot for the explanation. Too bad there’s no LuCI GUI for this.

Work through the ton of documentation, add counter and log to rules, or trace them

https://wiki.nftables.org/wiki-nftables/index.php/Ruleset_debug/tracing

Just drop a line if you need help.

My first attempt is planned like this:

Installation:
kmod-nft-bridge
kmod-br-netfilter

Startup script (/etc/rc.local):

#Enable bridge firewall
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.bridge.bridge-nf-call-arptables=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1

#Load bridge firewall rules
nft -f /etc/config/nftables.bridge

** /etc/config/nftables.bridge:**

table bridge filter {
    chain forward {
        type filter hook forward priority filter; policy accept;

        # Always allow management IP
        ip saddr 192.168.1.31 accept
        ip daddr 192.168.1.31 accept

        # Trusted devices
        ip saddr 192.168.1.27 accept  # Heizraum-ESP
        ip daddr 192.168.1.27 accept  # Heizraum-ESP
        ip saddr 192.168.1.34 accept  # Heizraum-Heater
        ip daddr 192.168.1.34 accept  # Heizraum-Heater
        ip saddr 192.168.1.18 accept  # Smartmeter
        ip daddr 192.168.1.18 accept  # Smartmeter
        ip saddr 192.168.1.13 accept  # Sonoff
        ip daddr 192.168.1.13 accept  # Sonoff

        # MQTT port 1883
        tcp dport 1883 accept

        # Modbus port 502
        tcp dport 502 accept

        # Allow HTTP
        ip daddr 192.168.1.21 tcp dport 80 accept  # SolvisBen
        ip daddr 192.168.1.20 tcp dport 80 accept  # GoodWe

        # Log and drop everything else (simulation/analysis mode)
        log prefix "BRIDGE_DROP: " flags all drop

        # Example: logging only for SolvisBen
        # ip saddr 192.168.1.21 log prefix "BRIDGE_LOG_SRC_SOLVISBEN: " flags all
        # ip daddr 192.168.1.21 log prefix "BRIDGE_LOG_DST_SOLVISBEN: " flags all

        # Optional: block LAN <-> WWAN
        # iifname "br-lan" oifname "phy0-sta0" drop
        # iifname "phy0-sta0" oifname "br-lan" drop
    }
}

I’ll report back once I’ve tested it — unless someone with more experience sees any issues or has suggestions?

Part 1

    set green {
            typeof ip saddr
            # flags dynamic
            elements = {
            192.168.1.31 ,
            192.168.1.27 ,
            192.168.1.34 ,
            192.168.1.18 ,
            192.168.1.13
            }
    }


chain forward {
    type filter hook forward priority filter; policy accept;

    
    ip saddr \@greem accept
    ip daddr \@green accept

Part2:

This is stateless

        tcp dport 1883 accept
        tcp dport 502 accept

So you need to start with conntrack statement to turn hook content into conntrack

ct state established,related accept # dont dare to drop invalid
# host rules also after this
tcp dport {502,1883 } accept

Alternative stateless

tcp sport . tcp dport { 502 . 1-65535 , 1-65535 . 502 ….

You also need ARP broadcasts permitted. and DHCP broadcasts….

1 Like

I’m disappointed — the bridge firewall doesn’t work with RelayD. I wanted to block access to 192.168.1.12, but unfortunately it didn’t work.

My nftables:

root@Repeater-KE:~# nft list ruleset
table bridge filter {
        set green {
                typeof ip saddr
                elements = { 192.168.1.5, 192.168.1.13,
                             192.168.1.14, 192.168.1.16,
                             192.168.1.18, 192.168.1.27,
                             192.168.1.31, 192.168.1.34 }
        }

        set limited_internet {
                typeof ip saddr
                elements = { 192.168.1.20, 192.168.1.21 }
        }

        chain forward {
                type filter hook forward priority filter; policy drop;
                ip daddr 192.168.1.12 drop
                ip saddr 192.168.1.12 drop
                ether saddr 5c:cf:7f:1b:a9:89 drop
                ether daddr 5c:cf:7f:1b:a9:89 drop
                ct state established,related accept
                ether type arp accept
                udp dport { 67, 68 } accept
                ip daddr 224.0.0.0/4 accept
                ip daddr 255.255.255.255 accept
                udp dport 53 accept
                tcp dport 53 accept
                udp dport 123 accept
                ip protocol icmp icmp type { echo-reply, echo-request } accept
                ip saddr @green accept
                ip daddr @green accept
                ip saddr @limited_internet ip daddr != 192.168.1.0/24 accept
                ip saddr @limited_internet ip daddr 192.168.1.0/24 tcp dport 502 accept
                ip saddr 192.168.1.0/24 ip daddr @limited_internet accept
                log prefix "BRIDGE_DROP: " flags all drop
        }
}
root@Repeater-KE:~#

relayd is “input” probably - make rate-limited log in other possible hook points.

Okay — using the input hook instead of forward made the difference. Thanks, brada4 !

                ip daddr 192.168.1.12 drop
                ip saddr 192.168.1.12 drop
                ether saddr 5c:cf:7f:1b:a9:89 drop
                ether daddr 5c:cf:7f:1b:a9:89 drop
                ct state established,related accept
                ether type arp accept
+ # more accurate match
-                udp dport { 67, 68 } accept
+ meta nfproto ipv4 udp sport . udp dport { 67 . 68 , 68 . 67 } accept
+ # group these
-                ip daddr 224.0.0.0/4 accept
-                ip daddr 255.255.255.255 accept
+ ip daddr {224.0.0.0/4 , 255.255.255.255} accept
+ # and these
-                udp dport 53 accept
-                tcp dport 53 accept
-                udp dport 123 accept
+ meta l4proto . th dport { udp . 53 , tcp . 53 , udp . 123} accept
+ conntrack handles echo reply
-                ip protocol icmp icmp type { echo-reply, echo-request } accept
+ icmp type echo-request accept
                ip saddr @green accept
                ip daddr @green accept
                ip saddr @limited_internet ip daddr != 192.168.1.0/24 accept
                ip saddr @limited_internet ip daddr 192.168.1.0/24 tcp dport 502 accept
                ip saddr 192.168.1.0/24 ip daddr @limited_internet accept

smth like that - nft list table bridge filter | nft -c -o -f -

Maybe this is faster than address matching ymmv, you need 10k of same filter to get measurable delays

iptables-translate -A input -m addrtype --dst-type broadcast,multicast
nft 'add rule ip filter input fib daddr type { broadcast, multicast } counter'