OpenWRT/LEDE Bridging Firewall

It would be neat if my wireless access point could bridge directly to my cable modem so that clients would have proper public IP addresses. Being directly connected to the internet significantly increases vulnerability so the aim is to address this with a bridge firewall.

I will be using a Ubiquiti UniFI AC PRO access point which I have flashed version 17.04 of LEDE (formally known as OpenWRT). I set up LEDE on the device by following this tutorial.  

What is a bridge firewall

A traditional firewall appliance is a router that is selective about which traffic to route and is usually placed on the perimeter of a network. The firewall works at and above layer three of the OSI model. It is non-transparent.

A bridge firewall operates transparently at layer 2, and attaches multiple network interfaces to a virtual switch/bridge interface. Because a bridge firewall does not operate at layer 3, it doesn't require having any IP addresses configured, nor does IP forwarding need to be enabled

Preparing the system

1. Install packages
The default LEDE install is missing the required iptables-mod-physdev package, so it needs to be installed. To do this, run

opkg update ; opkg install iptables-mod-physdev

2. Configure the Linux kernel to filter bridged traffic
Next, some kernel settings need to be changed. There are two ways to do this, you can make a persistent change (applied on next reboot) by editing /etc/sysctl.conf or you can make a temporary change by writing to virtual files in /proc/sys/. When testing, I prefer the latter method

echo 1 >/proc/sys/net/bridge.bridge-nf-call-arptables
echo 1 >/proc/sys/net/net.bridge.bridge-nf-call-ip6tables
echo 1 >/proc/sys/net/net.bridge.bridge-nf-call-iptables

Having a look around, I saw there was a bridge-nf-filter-vlan-tagged option, I did some tests, and confirmed that vlan traffic will be filtered even if the setting is off. I also found a discussion on the Linux kernel messageboards about this.

3. Disable the built in firewall
To keep things simple, I've decided to disable the built in firewall service and directly manage iptables myself. At some point, I'll explore trying this with the built in firewall, as it should be possible.

service firewall disable 

4. Configure the interfaces
I set the br-wan interface to have wlan1 and eth0.10. The neat thing about bridge firewalls is that the interface does not need to have any IP addresses assigned.

Here is my complete script I put in /etc/rc.local

# Configure kernel
echo 1 > \
 /proc/sys/net/bridge/bridge-nf-call-iptables \
 /proc/sys/net/bridge/bridge-nf-call-ip6tables \
 /proc/sys/net/conf/all/disable_ipv6
echo 0 > \
 /proc/sys/net/ipv4/ip_forward \
 /proc/sys/net/ipv4/conf/all/forwarding \
 /proc/sys/net/ipv6/conf/all/forwarding

# Create ipsets
/usr/sbin/ipset -exist create wanallow hash:net,port 
/usr/sbin/ipset -exist create lanblock hash:net,port

# Stop firewall service
/etc/init.d/firewall stop || true

# iptables
/usr/sbin/iptables-restore << EOF
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:inside - [0:0]
:outside - [0:0]
-A INPUT -i br-wan -j DROP
-A FORWARD -i br-wan -m physdev --physdev-in eth+ -j outside
-A FORWARD -i br-wan -m physdev --physdev-in wlan+ -j inside
-A outside -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j RETURN
-A outside -p udp -m udp --dport 32765:65535 -j RETURN
-A outside -p udp -m udp --sport 67 --dport 68 -j RETURN
-A outside -p icmp -m icmp ! --icmp-type 5 -j RETURN
-A outside -m set --match-set wanallow src,dst -j RETURN
-A outside -m limit --limit 1/min -j LOG --log-prefix "[outside-D]:"
-A outside -j DROP
-A inside -p udp --dport 68 --sport 67 -j DROP
-A inside -m set --match-set lanblock dst,dst -j DROP
-A inside -j ACCEPT
COMMIT
EOF

# ip6tables 
/usr/sbin/ip6tables-restore << EOF
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
EOF


The firewall is designed to protect inside clients from unsolicited traffic from the outside.

The firewall needs a method for determining whether ingress traffic from the outside is actually a solicited response to a connection initiated by an internal client.

A common and reliable way of doing this is by tracking every connection (stateful firewall), but this can slow things down, so I used a different and more implicit approach.

UDP traffic with a destination port range  of 32765-65535 is allowed in, because this range is only used for returning responses. TCP traffic is allowed if it does not have the SYN flag set. A TCP conversation begins with sending a SYN packet, so ingress traffic of this type is always unsolicited.