hostapd: how to block only NetBIOS broadcasts to clients in same WLAN by applying network restrictions

Solution 1:

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).