5

I have set up a Raspberry Pi Wi-Fi hotspot via hostapd, dnsmasq and iptables routing using this excellent tutorial: https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/. Although I skipped step 8, everything is perfectly working and I am using it every day for 2 years. In my situation, wlan0 (integrated Wi-Fi chip) is disabled and wlan1 (external Wi-Fi) is enabled.

Recently I noticed annoying traffic (NetBIOS among others) from some computers and I would like to block only this traffic. However, very important to me: I also would like clients to communicate with each other's. Many tries, unfortunately, I did not succeed.

Here is what I tried:

  • Filtering using iptables -> annoying packets are seen and said blocked but are still received by any computer on the WLAN
    • iptables -t raw -I PREROUTING -p tcp --dport 137 -j DROP
    • iptables -t raw -I PREROUTING -p udp --dport 137 -j DROP
    • iptables -t raw -I PREROUTING -p tcp --dport 138 -j DROP
    • iptables -t raw -I PREROUTING -p udp --dport 138 -j DROP
    • iptables -t raw -I PREROUTING -p tcp --dport 139 -j DROP
    • iptables -t raw -I PREROUTING -p udp --dport 139 -j DROP
  • Filtering using ebtables -> annoying packets are seen and said blocked but are still received by any computer on the WLAN
    • ebtables -I INPUT -i wlan1 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP
    • ebtables -I INPUT -i wlan1 -p ip --ip-protocol udp --ip-destination-port 138 -j DROP
    • ebtables -I INPUT -i wlan1 -p ip --ip-protocol udp --ip-destination-port 139 -j DROP
    • ebtables -I FORWARD -i wlan1 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP
    • ebtables -I FORWARD -i wlan1 -p ip --ip-protocol udp --ip-destination-port 138 -j DROP
    • ebtables -I FORWARD -i wlan1 -p ip --ip-protocol udp --ip-destination-port 139 -j DROP
    • ebtables -I OUTPUT -o wlan1 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP
    • ebtables -I OUTPUT -o wlan1 -p ip --ip-protocol udp --ip-destination-port 138 -j DROP
    • ebtables -I OUTPUT -o wlan1 -p ip --ip-protocol udp --ip-destination-port 139 -j DROP
  • Filtering using setting ap_isolate=1 in hostapd.conf -> annoying packets are blocked but clients can't communicate with each other's
  • Filtering using setting ap_isolate=1 in hostapd.conf plus adding iptables rules -> clients can't communicate with each other's
    • iptables -t filter -A FORWARD -i wlan1 -o wlan1 -m state --state RELATED,ESTABLISHED -j ACCEPT
    • iptables -t filter -A FORWARD -i wlan1 -o wlan1 -j ACCEPT

The iptables -L -n -v --line-number counters increment well as I send UDP broadcast frames from the test machine. Same principle for ebtables. This leads me to believe that my rules are good.

My current conclusion: iptables (OSI level 3) and ebtables (OSI level 2) see and block the traffic but don't act at the correct level since it seems hostapd (OSI level 1) already broadcasted network packets to clients ont he WLAN.

Here is a little diagram of my test configuration:

  • RaspberryPi has iptables + ebtables settings and live display of the counters via a watch -n 1.
  • Device 1 sends UDP frames in broadcast on the Wi-Fi subnet (a /24 network, so target IP x.y.z.255) to port 138
  • Device 2 uses a Wireshark to sniff the network and sees these UDP frames arrive
                                                 +-----------------+
                                                 |   Device 1      |
                                               ((| 172.18.0.240/24 |
                                                 +-----------------+
-----------+        +-----------------+
 INTERNET  |--------|  192.168.0.243  |
-----------+        |   RaspberryPi   |
                    |  172.18.0.1/24  |))
                    +-----------------+
                                                 +-----------------+
                                               ((|   Device 2      |
                                                 | 172.18.0.235/24 |
                                                 +-----------------+

I hope I have described my situation accurately enough. If not, please ask me for more details.

Therefore, my question is: "How can I block only broadcast of UDP packets to ports 137,138,139 but still allowing clients on the same WLAN to communicate to each other's for everything else?"

Many thanks for your help.

1 Answer 1

5

The wireless AP is handling a LAN: it's working at layer 2. I'll simplify and consider wireless frames are like Ethernet frames (which is almost true when considering an Access Point, even if not really: Four layer-2 addresses in 802.11 frame header ).

Setting ap_isolate=1 prevents frames to be bridged at a low level in the AP driver. Instead these frames are now sent to the network stack. This stack has not much to do at layer 2 (there is no bridge) and will transmit frames intended for the host as IP packets (as well as ARP packets, IPv6 packets etc.) to be handled by the routing stack at layer 3.

Once reaching routing at layer 3, the packet can be filtered with INPUT rules for the host, or when routed to an other IP LAN with FORWARD rules.

But in all cases frames not meant for the host are dropped instead of being sent to other STAs: no communication between STAs, that's one role of ap_isolate=1 and this can't be filtered by iptables (wrong layer) nor ebtables (no bridge available).

To be able to handle and filter frames with ebtables (Ethernet Bridge frame table administration), a bridge is needed, even if the only member of this bridge will be the single wireless card wlan1.

So here are steps to do it. These steps aren't complete. They have to be integrated in OS' configuration for proper boot setup, and a few things will have to be adapted mostly because IP settings are moved from the wlan1 interface to the br0 interface (eg: DCHP server interface, other iptables rules etc.).

  • create a bridge (you'll have to integrate this in OS settings):

    ip link add br0 up type bridge
    
  • move IPv4 configuration from wlan1 to br0 (ditto integration/boot). wlan1 must not receive any IPv4 address: 172.18.0.1/24 should now be only on br0. (wlan1 should also not participate in IPv6 anymore should this be in use: sysctl -w net.ipv6.conf.wlan1.disable_ipv6=1). There will be loss of wireless connectivity until hostapd is restarted.

    ip address flush dev wlan1
    ip address add 172.18.0.1/24 dev br0
    
  • adapt any relevant configuration that referenced wlan1 to now reference br0 (eg: DHCP server's interface if explicitly defined, and maybe some iptables rules).

  • in /etc/hostapd/hostapd.conf, in addition to ap_isolate=1 and still interface=wlan1 add:

    bridge=br0
    

    and restart hostapd. You should now get something similar to this:

    # bridge link show dev wlan1
    3: wlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 100 
    
  • enable hairpinning on the wlan1 bridge port to undo the effect of ap_isolate=1. This is required to handle this part of OP's problem:

    but clients can't communicate with each other's

    with this command:

    bridge link set dev wlan1 hairpin on
    

    A frame received on the wlan1 bridge port will now be echoed back to this same bridge port. Here the bridge port and associated media is the wlan1 interface and its radio waves.

    Client communication is now restored. Instead of doing like before:

    STA1 --> AP --> STA2
    

    It's now doing:

    STA1 --> AP --> (wlan1)--br0--(wlan1) --> AP --> STA2
    

Proper firewalling rules at the bridge level can now be added.

With more than one bridge on the system one should also use --logical-in/--logical-out, else it's not needed. Likewise I didn't include below -i wlan1/-o wlan1 so it would work on any bridge port (thus including the single bridge port wlan1 of br0). Choose what you prefer.

Example to block port 137: each line resp. to block from STA to AP (also including preventing to be routed to eth0), from STA to STA and from AP to STA (also including the routed from eth0 case).

ebtables -A INPUT --logical-in br0 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP
ebtables -A FORWARD --logical-in br0 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP
ebtables -A OUTPUT --logical-out br0 -p ip --ip-protocol udp --ip-destination-port 137 -j DROP

To block only broadcasts but not unicasts for port 137, as written in the title, the rule below can be used. There are probably more than one way to do it (here I'm using the fact that the frame carrying the IPv4 destination broadcast address in the LAN has the Ethernet destination broadcast address (ff:ff:ff:ff:ff:ff)). Showing only the FORWARD case:

ebtables -A FORWARD --logical-in br0 -d Broadcast -p ip --ip-protocol udp --ip-destination-port 137 -j DROP

notes:

  • by loading the kernel module br_netfilter which sets net.bridge.bridge-nf-call-iptables=1, it's also possible to use iptables (or even nftables) instead or in addition of ebtables to filter frames of type IPv4 (temporarily converted to IPv4 packets for the benefit of iptables) traversing the bridge path (before and in addition of the IPv4 routing path). The description is there: ebtables/iptables interaction on a Linux-based bridge. It can be very confusing for people not ready to deal with the subtle breakages this can induce so I wouldn't recommend using it.

  • nftables can also handle filtering and recent versions have better features for this (for example since kernel 5.3 it gets native conntrack support in bridge path, without relying on br_netfilter).

3
  • Thanks for the both the working answer and the quality of explanations. I noticed a small syntax mistake: the dev keywork is mandatory between set and wlan1 in bridge link set wlan1 hairpin on. Another one: ebtables didn't accept --logical-in or --logical-out, so I used -i wlan1 or -i wlan1. But it's only detail. Except these little troubles, everything was perfect :)
    – shadowpool
    Commented Jul 15, 2021 at 11:07
  • For the first command yes that's typo I made (I'll correct it later). For the --logical-in part I'm surprised. It should work and I'd rather use the bridge name than the port name. Can you run ebtables -V and tell what is the version string it reports?
    – A.B
    Commented Jul 15, 2021 at 12:20
  • --logical-in appears both in ebtables-legacy and ebtables-nft (although some early versions of ebtables-nft may have not included it in the doc which was almost empty, while they certainly implemented it anyway)
    – A.B
    Commented Jul 15, 2021 at 12:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .