I'm configuring a Raspberry Pi Zero 2 W to act as a IOT hacking/development bridge.

The Primary goals of this are:

  • Maintain a managed client wlan device to download packages or connect to a device's ad hoc wireless network.
  • Provide a wireless network for the IOT device to connect to, but without internet access to isolate it.
  • Power + network with the pi over a single USB wire via USB Ethernet gadget mode
  • Interact with the IOT device from the development machine powering the pi/ethernet gadget.

what I have so far

1- The USB ethernet gadget configured with a static IP via two lines in /etc/rc.local

ip addr add dev usb0
ip link set dev usb0 up

2- The AP and Managed network co-mingling well. This was done by connecting to the wifi network with nmcli device wifi connect <myhomenetwork> password <homenetworkpw>. Once that was connected, a udev rule was used to create a second virtual wireless interface via:

#note phy0 mac *:87 and the new virtual dev mac *:89
SUBSYSTEM=="ieee80211", ACTION=="add|change", ATTR{macaddress}=="d8:3a:dd:ba:6d:87", KERNEL=="phy0", \
  RUN+="/sbin/iw phy phy0 interface add ap0 type __ap", \
  RUN+="/bin/ip link set ap0 address d8:3a:dd:ba:6d:89"

This provided me with the interface, which was then configured with the following nmcli commands

nmcli con delete dev_ap
nmcli con add type wifi ifname ap0 mode ap con-name dev_ap ssid Special_ssid autoconnect true
nmcli con modify dev_ap 802-11-wireless.band bg
nmcli con modify dev_ap 802-11-wireless.channel 3
nmcli con modify dev_ap 802-11-wireless.cloned-mac-address d8:3a:dd:ba:6d:89
nmcli con modify dev_ap ipv4.method shared ipv4.address
nmcli con modify dev_ap ipv4.never-default yes
nmcli con modify dev_ap ipv6.method disabled
nmcli con modify dev_ap wifi-sec.key-mgmt wpa-psk
nmcli con modify dev_ap wifi-sec.psk "awesomepassword"
nmcli con up dev_ap

3- A firewall to prevent peeping IOT devices

iptables -A FORWARD -i ap0 -o wlan0 -j DROP

What does this get me?

This leaves me with a wifi connection that works as you'd expect, an access point with dhcp that cannot reach the internet, but can reach the pi, and a new USB ethernet interface on my dev machine. I can set my dev machine's static ip on that interface to an address in the subnet, and SSH into the pi.

ip a reports:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 62:f4:4e:bf:3c:56 brd ff:ff:ff:ff:ff:ff
    inet scope global usb0
       valid_lft forever preferred_lft forever
    inet6 fe80::60f4:4eff:febf:3c56/64 scope link 
       valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether d8:3a:dd:ba:6d:87 brd ff:ff:ff:ff:ff:ff
    inet brd scope global dynamic noprefixroute wlan0
       valid_lft 5761sec preferred_lft 5761sec
    inet6 fe80::1e9f:328e:dbe0:abda/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: ap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether d8:3a:dd:ba:6d:89 brd ff:ff:ff:ff:ff:ff permaddr d8:3a:dd:ba:6d:87
    inet brd scope global noprefixroute ap0
       valid_lft forever preferred_lft forever

What am I missing?

Right now, a client of ap0 can ping the dev machine connected to usb0, but the dev machine cannot ping a client of ap0.

extra info

Here's the output of ip route:

default via dev wlan0 proto dhcp src metric 601 dev ap0 proto kernel scope link src metric 600 dev usb0 proto kernel scope link src dev wlan0 proto kernel scope link src metric 601 

result of nft list ruleset

table ip nm-shared-ap0 {
        chain nat_postrouting {
                type nat hook postrouting priority srcnat; policy accept;
                ip saddr ip daddr != masquerade

        chain filter_forward {
                type filter hook forward priority filter; policy accept;
                ip daddr oifname "ap0" ct state { established, related } accept
                ip saddr iifname "ap0" accept
                iifname "ap0" oifname "ap0" accept
                iifname "ap0" reject
                oifname "ap0" reject
table ip filter {
        chain FORWARD {
                type filter hook forward priority filter; policy accept;
                iifname "ap0" oifname "wlan0" counter packets 11692 bytes 763264 drop

iftables-save -c

# Generated by iptables-save v1.8.9 (nf_tables) on Wed Jan 31 03:33:00 2024
[11794:770312] -A FORWARD -i ap0 -o wlan0 -j DROP
# Completed on Wed Jan 31 03:33:00 2024
  • When a system can ping an other but not the opposite, usually that means routing is ok and that there's firewalling or NAT in the way. Can you provide all the firewall rules? If handled with iptables: iptables-save -c. If previous doesn't give more than the added FORWARD rule, maybe it's handled with nftables: nft list ruleset. Also the title talks about bridging. This has a very specific meaning in networking, but nothing beside the title suggests bridging is wanted or needed. What do you mean with "bridge"?
    – A.B
    Commented Jan 30 at 21:48
  • Apologies for the confusion, I was at a loss for the right word. I added the results of nft list ruleset and iptables-save, its my first time looking at the former. I can't tell if it would cause a block.
    – paumanok
    Commented Jan 31 at 3:33

1 Answer 1


Indeed, there are nftables rules allowing only the right sources from ap0 to itself or to outside (which is then dropped by the additional iptables-nft rule when using wlan0).

In particular this rule in filter_forward:

oifname "ap0" reject

prevents routed traffic originally initiated from elsewhere to reach ap0.

The table name starting with nm-shared was automatically created by NetworkManager settings, because of ipv4.method shared mode:

nmcli con modify dev_ap ipv4.method shared ipv4.address

Once in place, if it allows tampering (there are provisions in the future to prevent an nftables table to be changed by something else than the daemon controlling it), then this command run as root user will immediately allow traffic initiated from usb0 to ap0:

nft insert rule ip nm-shared-ap0 filter_forward index 3 iifname usb0 oifname ap0 accept

This will be reverted as soon as NetworkManager rewrites the rules for any reason (but already ongoing connections such as an established SSH connection should still be able to continue working).

Anything more durable requires changing NetworkManager settings. From the NetworkManager blog documenting this feature:

Firewall Mode and nftables

When activating a "shared" profile, NetworkManager will enable NAT for IPv4. Until now, it could only call iptables to configure masquerading. With this new version, it can also configure nftables for the same purpose. The firewall backend can be configured via the new [main].firewall-backend option in NetworkManager.conf. In absence of an explicit configuration the default is "nftables" unless /usr/sbin/nft does not exist and /usr/sbin/iptables exists.

The NetworkManager.conf link tells:


The firewall backend for configuring masquerading with shared mode. Set to either iptables, nftables or none. iptables and nftables require iptables and nft application, respectively. none means to skip firewall configuration if the users wish to manage firewall themselves. If unspecified, it will be auto detected.

The easiest for this case is to disable the use of a firewall backend and then if needed implement rules manually as OP already did here using iptables, or else using nftables. To disable NetworkManager's firewall, edit the file /etc/NetworkManager/NetworkManager.conf and add an entry under the [main] block (keep any other entries already present in this block and any other blocks) so it will start at least like:


and restart NetworkManager (or reboot).

Should later traffic from ap0 to Internet be allowed again, it's possible that the main router won't recognize the addresses used from ap0. In this case, this could be restored with this iptables rule replacing the nftables NAT rule:

iptables -t nat -I POSTROUTING -s ! -d -j MASQUERADE

If not allowing such traffic, don't bother.

  • Thank you so much, the first nft command got the traffic flowing correctly. I completely overlooked nftables and didn't come across the appropriate networkmanager documentation.
    – paumanok
    Commented Jan 31 at 13:46

You must log in to answer this question.

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