Raspberry PI 3 As Wifi Range Extender

The Story:
I have a Workshop in our backyard, and there are some ESPs inside. Unfortunately my router is far away from them and the Wifi connection often breaks. This is a real problem for me because the ESPs are controlling my lighting in the garden and it's a bit irritating when I cannot turn on the lights. (The ESPs automatically turn off all relay channels when the Wifi disconnects, so turning off is not an issue.)
As I have wired LAN access (almost) everywhere in my house and even in my Workshop I was thinking about how to extend my Wifi range. OK. I know. There are a lot of possibilities to do it, but I wanted to choose the best and the most reliable way, and I wanted to use something I have already have.
I use a Mikrotik router with a lot of switches all around the house. Since I don't like Wifi networks I'm trying to connect as much devices as I can wired to my network, but the ESPs needs Wifi connection. There is a Raspberry PI3 already running in the workshop so it is reasonable to use that. Of course the PI has Ethernet connection to my Mikrotik router. :)

Update: I haven't published this post yet, and I've already had an update.
To make it work was much easier than I thought. --> To make it work was much complicated then I've ever thought and took more days. :(

As you will see I faced a lot of problems. But I want to believe that this post will be helpful.

Install the necessary packages

sudo apt-get install hostapd bridge-utils wicd wicd-cli wpasupplicant  

During the installation the wicd-daemon will ask you for the list of users who can use the wicd client.

Users who should be able to run wicd clients need to be added to the group "netdev".

You can modify these settings later by running dpkg-reconfigure wicd-daemon command.

Configuring Bridge Interface

My setup looks like that:

auto lo  
iface lo inet loopback  
iface eth0 inet manual  
iface wlan0 inet manual  
auto br0  
iface br0 inet dhcp  
  bridge_ports eth0 
  bridge_stp off
  bridge_fd 0
  bridge_maxwait 0
  bridge_waitport 0

Now you should restart your PI, after that you can check your config:

brctl show  
bridge name    bridge id       STP enabled interfaces  
br0        8000.b827eb26993d   no      eth0  

Check Wifi Device & Configure Hostapd

First it is recommended to check if your Wireless device supports AP mode or not. It is only necessary for example if you are using an RPI which is older than PI3. (RPI3 has built-n WiFi chip, which supports AP mode.)

  • Check your interface list
iw dev  
phy#0  
    Interface wlan0
        ifindex 3
        wdev 0x1
        addr b8:27:eb:73:cc:68
        type managed

You can see I have only one interface: phy#0. Here is an example when there are multiple interfaces:

iw dev  
phy#1  
    Interface wlan1
        ifindex 4
        wdev 0x100000001
        addr 00:e0:32:00:00:8b
        type managed
        channel 8 (2447 MHz), width: 20 MHz, center1: 2447 MHz
phy#0  
    Interface wlan0
        ifindex 3
        wdev 0x1
        addr b8:27:eb:f9:dd:be
        ssid Vinyo-Net
        type AP
        channel 2 (2417 MHz), width: 20 MHz, center1: 2417 MHz
  • Check "Supported interface modes"
iw phy phy0 info  
Wiphy phy0  
    max # scan SSIDs: 10
    max scan IEs length: 2048 bytes
    Retry short limit: 7
    Retry long limit: 4
    Coverage class: 0 (up to 0m)
    Device supports T-DLS.
    Supported Ciphers:
        * WEP40 (00-0f-ac:1)
        * WEP104 (00-0f-ac:5)
        * TKIP (00-0f-ac:2)
        * CCMP (00-0f-ac:4)
    Available Antennas: TX 0 RX 0
    Supported interface modes:
         * IBSS
         * managed
         * AP
         * P2P-client
         * P2P-GO
         * P2P-device
    Band 1:
        Capabilities: 0x1020
            HT20
            Static SM Power Save
            RX HT20 SGI
            No RX STBC
            Max AMSDU length: 3839 bytes
            DSSS/CCK HT40
...
...

As you can see, our built-in Wifi device supports the AP mode.
(By running this command (iw phy phy0 info) you get a lot of information about your Wifi device.)

  • Configure Hostapd

I don't know why, but the /etc/hostapd directory does not contain default/sample configuration file, but you can find sample configuration files here: /usr/share/doc/hostapd/examples
I used this command to get examples:

gunzip -c /usr/share/doc/hostapd/examples/hostapd.conf.gz | less  

I advise you to use a different SSID firstly then you already have to test a vanilla setup.

Here is working example file:

ctrl_interface=/var/run/hostapd  
macaddr_acl=0  
driver=nl80211  
interface=wlan0  
bridge=br0  
country_code=HU  
hw_mode=g  
ieee80211n=1  
channel=2  
ssid=WS-TST-01  
auth_algs=1  
ignore_broadcast_ssid=0  
wpa=2  
wpa_passphrase=12345678  
wpa_key_mgmt=WPA-PSK  
wpa_pairwise=CCMP  
rsn_pairwise=CCMP  

WARNING: Use a channel which is different from the one your primary device uses.

After the configuration file is created you can try to start hostapd.

hostapd /etc/hostapd/hostapd.conf  
Configuration file: /etc/hostapd/hostapd.conf  
Failed to create interface mon.wlan0: -95 (Operation not supported)  
wlan0: interface state UNINITIALIZED->COUNTRY_UPDATE  
wlan0: Could not connect to kernel driver  
Using interface wlan0 with hwaddr b8:27:eb:73:cc:68 and ssid "WS-TST-01"  
wlan0: interface state COUNTRY_UPDATE->ENABLED  
wlan0: AP-ENABLED  

We get two error messages:

  • Failed to create interface mon.wlan0: -95 (Operation not supported)
  • wlan0: Could not connect to kernel driver

I did a lot of Google searches, but failed to find any solution for this issue. Despite the failures the "AP-ENABLED" messages shows us that everything should work.
Now you can try to connect your new Wifi AP: WS-TST-01

  • Enable Auto Start hostapd daemon

By default the hostapd doesn't start at boot time. To enable it change this file:

--- hostapd_orig    2017-08-15 11:14:58.673575799 +0200
+++ hostapd    2017-08-14 21:25:47.684546287 +0200
@@ -7,7 +7,7 @@
 # file and hostapd will be started during system boot. An example configuration
 # file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
 #
-DAEMON_CONF=""
+DAEMON_CONF="/etc/hostapd/hostapd.conf"

 # Additional daemon options to be appended to hostapd command:-
 #     -d   show more debug messages (-dd for even more)

At this point I was facing a very strange and serious issue. :( It took days to find a working solution or only a workaround instead.

The problem: After restarting my PI everything seemed fine, but none of devices could see the SSID. But it could be solved with restarting the hostapd daemon. At the bottom of this post in the references section you can find some article which discuss similar issues, but without any "real" solution (only workarounds). (Example: control the hostapd daemon from interface config, ifup.d).

Now I sharing you my experiences I got during the investigation, It may (or may not) be useful.
You have to know I made uncountable tries to configure hostapd, dhcpcd, dhcp-client, bridge, etc, but none of them led to success.

1. Replace the original System V init service to systemd.

By default the hostapd daemon is started by an init.d scripts (/etc/init.d/hostapd). I wanted to move the service start to the end of the boot process to make sure that every necessary service started before hostapd. So I wrote a custom systemd config file and removed it from /etc/rc3.d.

My SystemD script:

[Unit]
Description=HOSTAPD  
Requires=multi-user.target network-online.target avahi-daemon.service smbd.service  
After=avahi-daemon.service smbd.service multi-user.target

[Service]
Type=forking  
GuessMainPID=yes  
ExecStart=/usr/sbin/hostapd -d -t -B -P /run/hostapd.pid -f /var/log/hostapd.log /etc/hostapd/hostapd.conf  
ExecStop=/usr/bin/kill -SIGINT $MAINPID  
PIDFile=/run/hostapd.pid  
Restart=always  
User=root

[Install]
WantedBy=multi-user.target  

You can check the boot order by issuing the command (after reboot):

systemd-analyze plot >/tmp/plot3.svg  

This .svg file can be opened with any type of browser. I saw that the hostapd service was started almost at the end of the boot process (I think it was the penultimate one before systemd-update-utmp-runlevel.service and after the multi-user.target).

But It did not solved my problem.

2. Restart hostapd daemon after the IP address bounded (br0)

I was reading the log files a lot, and found that something happens after the boot process has been finished with the interfaces (br0,wlan0,eth0):

In the log files you can see that the boot process has been finished at 11:09:11.
Aug 26 11:09:11 ws-rpi3 systemd[1]: Startup finished in 3.918s (kernel) + 29.036s (userspace) = 32.954s.

But after some seconds:

Aug 26 11:09:14 ws-rpi3 kernel: [   36.241794] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.242140] br0: port 1(eth0) entered disabled state  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.342138] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.342435] br0: port 1(eth0) entered blocking state  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.342446] br0: port 1(eth0) entered forwarding state  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.474142] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.474380] br0: port 1(eth0) entered disabled state  
Aug 26 11:09:14 ws-rpi3 dhcpcd[1046]: dhcpcd not running  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.591789] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.592070] br0: port 1(eth0) entered blocking state  
Aug 26 11:09:14 ws-rpi3 kernel: [   36.592074] br0: port 1(eth0) entered forwarding state  
Aug 26 11:09:16 ws-rpi3 kernel: [   38.073773] smsc95xx 1-1.1:1.0 eth0: link up, 100Mbps, full-duplex, lpa 0xC5E1  
Aug 26 11:09:16 ws-rpi3 dhcpcd[1051]: version 6.7.1 starting  
Aug 26 11:09:16 ws-rpi3 dhcpcd[1051]: eth0: interface not found or invalid  
Aug 26 11:09:16 ws-rpi3 dhcpcd[1051]: exited  
Aug 26 11:09:21 ws-rpi3 dhcpcd[1089]: dhcpcd not running  
Aug 26 11:09:21 ws-rpi3 kernel: [   43.735165] brcmfmac: power management disabled  
Aug 26 11:09:22 ws-rpi3 dhcpcd[1113]: dhcpcd not running  
Aug 26 11:09:22 ws-rpi3 kernel: [   43.866789] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:22 ws-rpi3 kernel: [   43.866935] br0: port 1(eth0) entered disabled state  
Aug 26 11:09:22 ws-rpi3 kernel: [   43.962974] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 26 11:09:22 ws-rpi3 kernel: [   43.963452] br0: port 1(eth0) entered blocking state  
Aug 26 11:09:22 ws-rpi3 kernel: [   43.963460] br0: port 1(eth0) entered forwarding state  
Aug 26 11:09:23 ws-rpi3 kernel: [   45.581772] smsc95xx 1-1.1:1.0 eth0: link up, 100Mbps, full-duplex, lpa 0xC5E1  

This is the only possible reason I could find. For some reason the interfaces changed to down and up again. I run the ping command from a remote machine and during this period the packages were lost.
(When I was fast I could see the SSID on my phone for some seconds before it disappeared.)

Here I was facing another big issue. I could not find any errors in the log files or anything else on the RPI side with which the restart could have been triggered. This means that everything seemed fine on the RPI side, but the SSID was not visible.

First I thought it is a good idea to restart hostapd daemon when the br0 get the IP address from the DHCP server (BOUND), but it wasn't. :( The dhcp client "only" renews the IP address.

Only for information I paste here my dhcp related config (/etc/dhcp/dhclient-enter-hooks.d/jvincze_custom):

#!/bin/sh

case "$reason" in

    BOUND)
        echo "[ $(date +%F\ %T ) ] - ($0) DHCP REASON: $reason ($new_ip_address , $interface)" >>/var/log/jvinczedhcp.log
        PID=$( ps aux|grep hostapd | grep -v grep | awk '{print $2}' )
        if [ ! -z $PID ] 
        then
          echo "[ $(date +%F\ %T ) ] - KILLING $PID pid" >>/var/log/jvinczedhcp.log
          sleep 30
          kill $PID
        fi
        ;;
    *)
        echo "[ $(date +%F\ %T ) ] - ($0) DHCP REASON: $reason ($new_ip_address , $interface)" >>/var/log/jvinczedhcp.log
       ;; 

esac  

First I tried this (instead of killing the process):

#[ ! -z $PID ] && while kill $PID  2> /dev/null;  do sleep 1 ;  done;
#sleep 4
#/usr/sbin/hostapd -t -B -P /run/hostapd.pid -f /var/log/hostapd.log /etc/hostapd/hostapd.conf

I did not like this, because this file contains the command line parameter of hostapd. It is not a "real" problem, but with this way this file has to be synchronized with the .service file.

After I slept a bit I found out that I can kill the process if I use Restart=always and Type=forking in the .service file (systemd will restart the process when it's dead/killed/etc.)

But nothing changed. :(

It could be a solution to modify the script to restart hostapd daemon when ip address is renewed, but in case of long lease time I could lose the wifi connection for hours. (And I did not want to change a network parameter which affects my entire network and devices.)

NOTE: After finding my final WA, I did not remove this script to make sure if the interface br0 get a new ip address hostapd will be restarted. (I don't know if it is necessary or not, but think this won't cause further problems.) If you have more than one interface using dhcp to assign IP address you can write an "if" condition to specify the interface to check. Example:

if [ ! -z $PID ] || [ "$interface" == "br0" ] ; then  

3. Another tries

Start hostapd from systemd with this command line options:

ExecStart=/usr/sbin/hostapd -d -S -P /run/hostapd.pid -f /var/log/hostapd.log /etc/hostapd/hostapd.conf  

After the RPI restarted, I stopped hostapd, backed-up/removed the hostapd.log files, and started the hostapd (again). Then I compared the two log files.

You can download my diff file from here: LINK
Example .svg file. LINK

I tried to find something special to search in google, but all of my searches led to nothing. :( So I finally gave up searching in the log files. It is obvious that this behavior was caused by the interface changes after boot. Syslog:

...
Aug 30 21:05:33 ws-rpi3 kernel: [   44.726888] br0: port 2(wlan0) entered disabled state  
Aug 30 21:05:33 ws-rpi3 dhcpcd[1020]: dhcpcd not running  
Aug 30 21:05:33 ws-rpi3 kernel: [   44.969033] smsc95xx 1-1.1:1.0 eth0: hardware isn't capable of remote wakeup  
Aug 30 21:05:33 ws-rpi3 kernel: [   44.969368] br0: port 1(eth0) entered disabled state  
...

Of course I did a lot of search for syslog entries but I could not find any relating, or useful article.

4. Final Solution (WA)

I tried a lot of things in order to make this work, but the only way is the "delayed start". :(

I completely disabled the hostapd service and restarted the PI, waited some time (~1min) and started the hostapd daemon. Everything was fine. Now the "only" thing was to figuring out how to start hostapd service delayed.

My first solution was this (.service file):

[Service]
ExecStartPre=/bin/sleep 30  

It is a working but ugly workaround. With this method the whole boot process is delayed. :( And you can see in the boot order (.svg) that the hostapd starting process take +30s compared with the "normal" case. :(

Finally I set up a systemd timer:

  • hostapd.timer
cat /lib/systemd/system/hostapd.timer  
[Unit]
Description=Runs hostapd  
After=multi-user.target

[Timer]
OnBootSec=1min  
Unit=hostapd.service

[Install]
WantedBy=multi-user.target  

I think 1 minute enough delay after boot to start hostapd.

  • hostapd.service
cat /lib/systemd/system/hostapd.service  
[Unit]
Description=HOSTAPD  
Requires=multi-user.target network-online.target avahi-daemon.service smbd.service  
After=avahi-daemon.service smbd.service multi-user.target sockets.targee

[Service]
Type=forking  
GuessMainPID=yes  
ExecStart=/usr/sbin/hostapd -d -t -B -P /run/hostapd.pid -f /var/log/hostapd.log /etc/hostapd/hostapd.conf  
#ExecStart=/usr/sbin/hostapd -d -S -t -B -P /run/hostapd.pid -f /var/log/hostapd.log /etc/hostapd/hostapd.conf
#ExecStart=/usr/sbin/hostapd -t -B  -f /var/log/hostapd.log /etc/hostapd/hostapd.conf
ExecStop=/usr/bin/kill -SIGINT $MAINPID  
PIDFile=/run/hostapd.pid  
Restart=always  
User=root

#[Install]
#WantedBy=multi-user.target

Commands:

systemctl daemon-reload  
systemctl disable hostapd.service  
Removed symlink /etc/systemd/system/multi-user.target.wants/hostapd.service.  
systemctl enable hostapd.timer  
Created symlink from /etc/systemd/system/multi-user.target.wants/hostapd.timer to /lib/systemd/system/hostapd.timer.  

Final Thoughts

When I started to write this post I thought configuring hostapd must consist of some easy steps. Actually it is right because configuring hostapd is pretty easy, I had(/have) problems with autostart on boot time.

I have been running this setup since weeks without any problem. Maybe later when I have time for this, I'll continue the investigation, or maybe in the future there will be a new kernel and/or hostapd daemon with which this problem won't occur.

If you have any idea how to solve this please let me a post on Disqus. :)

Some Useful Commands

  • Check Connected Devices
hostapd_cli all_sta  
  • Check Connected Devices (only MAC addresses)
hostapd_cli all_sta | grep  -E '^([0-9|a-f|A-F]{2,2}\:?){6,6}'  
  • Check Connected Devices & Query MAC address on MIKROTIK router
for MAC in $(hostapd_cli all_sta | grep  -E '^([0-9|a-f|A-F]{2,2}\:?){6,6}'); do echo "########## $MAC ##########" ; ssh admin@172.16.0.1 "ip dhcp-server lease print where mac-address=$MAC" ; done  
########## 60:01:94:08:31:48 ##########
Flags: X - disabled, R - radius, D - dynamic, B - blocked  
 #   ADDRESS                                      MAC-ADDRESS       HO SER.. RA
 0   172.20.0.15                                  60:01:94:08:31:48 NO def..
  • Check bridge interface
brctl show  
bridge name    bridge id       STP enabled interfaces  
br0        8000.b827eb26993d   no      eth0  
                            wlan0

NOTE: If the hostapd have been started successfully you have to see WLAN0 in the interface list.

  • Analyze boot order
systemd-analyze plot >/tmp/plot.svg  

You can open the created file in your browser.

  • Enable packet forwarding by kernel
echo 1 > /proc/sys/net/ipv4/ip_forward  
iptables --append FORWARD --in-interface eth1 -j ACCEPT  

Or permanently, add this line to /etc/sysctl.conf

net.ipv4.ip_forward=1  

Alternatively:

sudo sysctl -w net.ipv4.ip_forward=1  
  • Disable interface in dhcpcd.conf
denyinterfaces wlan0  
denyinterfaces br0  
  • Disable ipv6 (sysctl.conf)
net.ipv6.conf.all.disable_ipv6 = 1  
net.ipv6.conf.default.disable_ipv6 = 1  
net.ipv6.conf.lo.disable_ipv6 = 1  
  • Wifi Channel Setup (hostapd.conf)

If you build a WiFi infrastructure you must use different channel in all devices.
References:

From hostapd example conf:

# Channel number (IEEE 802.11)
# (default: 0, i.e., not set)
# Please note that some drivers do not use this value from hostapd and the
# channel will need to be configured separately with iwconfig.
#
# If CONFIG_ACS build option is enabled, the channel can be selected
# automatically at run time by setting channel=acs_survey or channel=0, both of
# which will enable the ACS survey based algorithm.
channel=1  
  • Bonus - Connect To Existing WiFi Network

It can be useful when you have two WiFi adapters, and you want to use your raspberry to extend WiFi signal range without ETH connection. I haven't tested this, but I think based on this post and the linked articles it can be done easily.

  • Install necessary packages
apt-get install wpasupplicant wicd wicd-cli  
  • Search for Wifi Networks
iwlist wlan1 scan  
  • Create Config File
wpa_passphrase  Wifi-NetWork 12345678 >wifi.config  
  • Test you connection
wpa_supplicant -i wlan1 -D nl80211 -c wifi.config  
  • Edit Interface Config (/etc/network/interfaces)
auto wlan1  
allow-hotplug wlan1  
iface wlan1 inet dhcp  
    wpa-conf /root/wifi.config

References:

Vincze Janos Istvan

Read more posts by this author.

Hungary

Subscribe to Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!