keskiviikko 1. tammikuuta 2020

Using Raspberry PI zero W as an USB wlan adapter

Background

My desktop computer is located so that it is difficult to route an ethernet cable cleanly to my modem. To have an internet connection I have tried several usb wlan adapters as well as phone tethering.

Earlier I used a Netgear one, it might have been a WNA3100 (N300), which somewhat worked with ndiswrapper. I think it had poor throughput and occasionally dropped the connection.

Some time ago I bought an Asus USB-N10 nano which claimed to have Linux support (Linux was written on the box). It didn't really work properly; there was a driver for an old kernel version and the new kernel had some kind of support built in. Depending on the driver I tried, either I did not even see my network, could not connect (authentication failed) or the connection dropped very often.

In the end I compiled the experimental version of rtl8xxxu which worked the best. The connection was rather stable, but the throughput was very poor, only 1 Mbps. My laptop can easily get over 40 Mbps...

Best results I got by using my mobile phone (connected to the wifi) and enabling usb tethering. However that has its own issues. First, I cannot use my phone anywhere else while it is sharing the connection and second, it is necessary to turn on the tethering always when connecting the cable (maybe some app could enable it automatically but...).

Solution

I had some spare Raspberry PI zero Ws lying around, and I've been playing with its usb gadget mode earlier. So I tought, why not make the PI look like an USB ethernet adapter, and bridge its wlan connection to the USB connection? I had all the pieces available, I just needed to put them together!

So, I made a script to
  1. make the PI to be an CDC/ECM USB ethernet gadget using configfs
  2. enable ipv4 port forwarding and NAT, forwarding usb0 to wlan0
  3. enable forwarding from wlan0 to the usb0
  4. enable DHCP server, serving address 192.168.20.10

I would have used CDC/NCM since I think it is the fastest protocol, but the stock Raspbian does not have built-in support for that. So I went with ECM, which works well on Linux. If the gadget is to be used with Windows, one should select RNDis (by replacing ecm with rndis in the script).

To enable gadget mode, dtoverlay=dwc2 should be added to /boot/config.txt.

Following script should be placed in /usr/local/bin/usb_wifi_gadget.sh and made executable (ug+x).

#!/bin/sh
#
# This script creates an Ethernet gadget using the
# CDC / ECM (Ethernet Control Model) interface and
# then shares the wlan0 to usb0 (with NAT)
# and starts a dhcp server on usb0

# Load libcomposite
modprobe libcomposite

# Create a gadget called usb-gadgets
cd /sys/kernel/config/usb_gadget/
mkdir -p usb-gadgets
cd usb-gadgets

# Configure the gadget
# ==========================

# Configure our gadget details
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409

# Set serial number, manufacturer and product name here
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Pi Zero USB Gadget" > strings/0x409/manufacturer
echo "Pi Zero USB Gadget" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409

# This describes the only configuration, free text
echo "Config 1: Test gadget" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# =====================================
# Create gadget functions

# Ethernet gadget
# -------------------------
F_TYPE=ecm      # ECM gadget
F_NAME=usb0     # Freely selectable name

mkdir -p functions/$F_TYPE.$F_NAME

# Set configuration, see e.g.
# https://github.com/torvalds/linux/blob/master/Documentation/ABI/testing/configfs-usb-gadget-ecm

# MAC addresses, comment out for random addresses
# first byte of address must be even
HOST="32:70:05:18:ff:7a" # "HostPC"
SELF="32:70:05:18:ff:7b" # "Ethernet Gadget"
#echo $HOST > functions/$F_TYPE.$F_NAME/host_addr
#echo $SELF > functions/$F_TYPE.$F_NAME/dev_addr

# Interface name, by default will be usb0
#echo usb0 > functions/$F_TYPE.$F_NAME/ifname

# Link the function under the (only) configuration
ln -s functions/$F_TYPE.$F_NAME configs/c.1/

# End ethernet gadget
# ------------------------

# End functions
# ========================

# Enable gadgets
ls /sys/class/udc > UDC

 

# Internet connection shating
# Shares wlan0 over other connections

# Enable ipv4 forwarding
sysctl -w net.ipv4.ip_forward=1
sysctl -p

# Flush rules (flush all / flush only nat related)
#iptables -X
#iptables -F
iptables -t nat -X
iptables -t nat -F

# Enable NAT forwarding
iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -I FORWARD  -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -I POSTROUTING -o wlan0 -j MASQUERADE


# Configure static IP address for the usb interface
ifconfig usb0 up
ifconfig usb0 192.168.20.1 netmask 255.255.255.0

# Enable incoming connections for DHCP
iptables -I INPUT -p udp --dport 67 -i usb0 -j ACCEPT

# Forward all TCP ports, except 22 (ssh), from wlan0 to usb0
iptables -t nat -A PREROUTING -p tcp -i wlan0 --dport 1:21 -j DNAT --to 192.168.20.10
iptables -t nat -A PREROUTING -p tcp -i wlan0 --dport 23:65535 -j DNAT --to 192.168.20.10


# Flush dhcp lease cache
if [ -f "/var/lib/dhcp/dhcpd.leases~" ]; then rm /var/lib/dhcp/dhcpd.leases~; fi
echo "" > /var/lib/dhcp/dhcpd.leases

# Launch (Restart) DCHP server
#systemctl restart isc-dhcp-server
if [ -f "/var/run/dhcpd.pid" ]; then kill `cat /var/run/dhcpd.pid`; fi
dhcpd -q -4 -cf /etc/dhcp/dhcpd.conf usb0
 

To launch this script at boot time, I added command usb_wifi_gadget.sh & to /etc/rc.local, before the exit 0 statement.


The dhcp server requires isc-dhcp-server package. The service should be disable by default, by running sudo systemctl disable isc-dhcp-server and sudo systemctl mask isc-dhcp-server.


The following minimalistic dhcp server configuration in /etc/dhcp/dhcpd.conf seems to work:
 

# dhcpd.conf
# Very basic dhcp daemon configuration for the USB interface

default-lease-time 600;
max-lease-time 7200;

# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed. We default to the
# behavior of the version 2 packages ('none', since DHCP v2 didn't
# have support for DDNS.)
ddns-update-style none;

# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;

# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;

# Subnet declaration
subnet 192.168.20.0 netmask 255.255.255.0 {
        option routers                  192.168.20.1;
        option subnet-mask              255.255.255.0;

        option domain-name-servers      192.168.0.20;
        range   192.168.20.10   192.168.20.10;
}


I run pi-hole on my local server at 192.168.0.20 so I pointed the name server to that. One could also use some other, like Comodo DNS server at 8.26.56.26. See for example this page for information.

Now this seems to work properly, when plugging the Raspberry PI (using the USB port, not the PWR port) the computer gets IP address, and can access the internet just fine. Note that on the desktop computer it may be necessary to add following to /etc/network/interfaces:

allow-hotplug usb0
iface usb0 inet dhcp

Notes

My home network is by default 192.168.0.0/24, and the PI creates 192.168.20.0/24. It seems that the routing works so that I can access the home network from the desktop computer just fine via the PI.

I added the routing through wlan0 to usb0 so that it is possible to run server(s) on the desktop computer and they are accessible by using the IP that the PI has over the wlan.

The PI should not be abruptly powered off, but rather one should ssh into it and shut it down with sudo shutdown -h now.

Speed test

When I got the gadget to work, I run some speed tests using iperf3. The server was my Raspberry PI 3B server that has wired connection to my router.
  • Asus USB-N10 nano: 4-6 Mbps (surprisingly good, right after boot & connection)
  • USB tethering with mobile phone: 15-28 Mbps
  • Raspberry PI gadget: 20 Mbps
So my conclusion is that the Raspberry PI gadget works very well as a WLAN adapter.