Raspberry Pi als wifirouter met Captiveportal
Raspberry Pi als open wifirouter met captive-portal
Als goede gastheer of -vrouw geef je je bezoek niet alleen een kopje koffie, maar vaak ook het wachtwoord van je wifi. Dat wachtwoord is vanuit veiligheidoverwegingen natuurlijk wel lekker lang met allerlei verschillende tekens. Een open wifi met een captive-portal en individuele pincode is dan een handig en toch goedkoop alternatief.
Veel mensen hebben liever dorst of honger dan dat ze geen internet hebben. Veel mensen vragen na het eerste kopje koffie meestal naar je wifiwachtwoord omdat ze je wat online foto's of zo willen laten zien en bang zijn dat ze dan te snel door hun hele databundel heen zijn. Om niet voor iedere bezoeker de deuren van je lokale netwerk meteen wagenwijd open te zetten, hebben veel moderne routers de mogelijkheid een apart gastwifi in te stellen. Zo kan bezoek met een eigen wachtwoord internet op, maar niet bij de rest van je netwerk komen.
Om ervoor te zorgen dat gasten geen complexe wifiwachtwoorden met de hand in hoeven te typen (waar altijd wel weer een fout bij gemaakt wordt), wordt bij Android-apparaten veel gebruik gemaakt van een QR-code om je snel aan te melden. Bij iOS werkt dat duidelijk moeizamer, daar kom je alleen via allerlei omwegen met een QR-code op het wifi.
Dan is een open wifi een stuk makkelijker. Een captive-portal met een pincode beschermt je dan tegen onbevoegde bezoekers. Als basis heb je alleen een Raspberry Pi 3 en een miniatuur-TFT-display van zo'n 15 euro nodig. De clou is dat je captiveportal werkt met alleen de standaardmiddelen, zonder gedoe met sudo en zonder speciale daemons voor het toegangsbeheer.
Juridisch heeft een captive-portal voordelen ten opzichte van het simpelweg geven van het wifiwachtwoord. Gasten krijgen pas toegang tot internet als ze de bij de portal getoonde gebruiksvoorwaarden voor de wifi geaccepteerd hebben. Bovendien wordt het aanmelden makkelijker als je iedere gast een eigen, tijdelijk geldige pincode geeft in plaats van een altijd geldig complex wifiwachtwoord.
Markeren en sorteren
De manier van werken lijkt veel op hoe je een Raspberry Pi normaal als wifihotspot of wifirouter zou gebruiken. De Raspberry Pi maakt met HostAP een eigen, onversleuteld wifi aan en dient tegelijkertijd als DHCP-server. De Raspberry Pi krijgt zijn internetverbinding via de netwerkpoort en leidt de data van de (aangemelde) wifigasten daar als NAT-router ook naartoe door. Tot zover niets nieuws onder de zon.
De data van onbekende en aangemelde gasten worden door de Pi onderscheiden door de markering van de pakketten: eerst worden alle wifipakketten met behulp van firewallregels voorzien van de markering 1. Een tweede firewallregel gooit bij het doorsturen al die pakketten weg, zodat ze niet naar de ethernetinterface worden doorgestuurd – de wifigasten zitten dan gevangen op de Raspberry Pi en kunnen alleen bij daarop draaiende diensten, zoals de webserver met de captive-portal.
Als een gast zich aanmeldt bij die portal en de voorwaarden accepteert, maakt systemd voor hem een individuele firewallregel aan die de wifipakketten van zijn MAC-adres van de markering 2 voorziet. Die worden door de firewall naar internet doorgestuurd, waardoor de gast dan vrijelijk kan surfen. Maar niet voor altijd: een cron-job verwijdert regelmatig vrijgegeven gasten die langer dan vier uur actief zijn. Dat voorkomt dat je buren het gastwifi met behulp van ARP-spoofing continu kunnen misbruiken.
Andere captive-portals moeten vaak wachten tot een gast een webpagina opvraagt om hem dan naar de portalpagina te leiden. Wij gebruiken de captive-herkenning van Android, iOS en Windows Phone om gasten meteen en automatisch door te sturen naar de aanmeldpagina. En ook dat weer met alleen de standaardmiddelen.
Installeren
Bij de hardware hebben we gekozen voor een Raspberry Pi 3 omdat die een ethernetpoort en wifi heeft. Je kunt echter net zo goed een Raspberry Pi 1 of 2 met een wifiadapter of een Raspberry Pi Zero W met een usb-ethernetadapter gebruiken. Als besturingssysteem adviseren we Raspbian Lite omdat de wifirouter geen grafische interface nodig heeft.
De configuratiebestanden voor het gebruik als wifirouter staan bij de link onderaan dit artikel. Pak het ziparchief uit in de lokale map van de gebruiker pi. Als je het met Windows downloadt, zet je de uitgepakte bestanden op de eerste partitie van de sd-kaart waar je van tevoren Raspbian op geïnstalleerd hebt. Onder Raspbian ga je dan eerst met cd /boot naar de map met het uitgepakte ziparchief voordat je de volgende stappen uitvoert.
Om ervoor te zorgen dat de Raspberry Pi als hotspot werkt, moet je eerst de HostAP- en de DHCP-daemon installeren:
sudo
apt-get install hostapd dnsmasq
De configuratiebestanden van beide programma's zitten in het ziparchief. Kopieer die simpelweg naar /etc:
sudo cp 1726-154/default/*
/etc/default sudo cp -a 1726-154/hostapd /etc sudo cp 1726-154/dnsmasq.conf /etc
Bovendien heb je een wifi-adapter met een statisch geconfigureerd ip-adres nodig. Daarvoor moet je met sudo pico /etc/network/interfaces het centrale netwerkconfiguratiebestand openen en daar het volgende inzetten:
auto wlan0 iface wlan0 inet static address 192.168.255.1 netmask 255.255.255.1 wireless-mode Master wireless-power off
Daarmee heb je het ip-adres 192.168.255.1 als beginadres voor alle wifigasten ingesteld en de energiebesparingsfunctie van de wifi-adapter gedeactiveerd.
Verplichte omleiding
De volgende stappen bestaan uit het configureren van de NAT-routing en de firewall. Om de Pi datapakketten überhaupt te laten doorsturen, moet je de routing in het bestand /etc/sysctl.conf activeren:
net.ipv4.ip_forward=1
Dat werkt echter pas na een herstart, maar die kun je het beste pas doen nadat je alles geïnstalleerd en geconfigureerd hebt. De firewallregel voor het markeren van het gastverkeer met 1 ziet er als volgt uit:
sudo iptables -A PREROUTING -t mangle -i wlan0 -m mark --mark 0 -j MARK --set-mark 1
De met 1 gemarkeerde pakketten van niet-aangemelde gasten moeten niet op internet terechtkomen. Die moeten in de FORWARD-chain verworpen worden:
sudo iptables -A FORWARD -m mark --mark 1 -j DROP
De NAT-routing moet alleen voor de data
van de aangemelde wifigasten gelden, waarvan de pakketten later met een 2 gemarkeerd worden:
sudo iptables -A POSTROUTING -t nat -o eth0 -m mark
--mark 2 -j MASQUERADE
Wie nog niet aangemeld is, moet om te kunnen aanmelden omgeleid worden naar de captive-portal. Daar hebben we een FakeDNS-daemon voor gebruikt die voor alle namen het ip-adres 192.168.255.1 teruglevert. Omdat clients de DNS-requests cachen, kun je geen DNS-server gebruiken die valse antwoorden levert. Anders zou je bij de eerste poging http://ct.nl op te roepen wel op de captive-portal uitkomen en je kunnen aanmelden, maar als je dan opnieuw probeert om naar http://ct.nl te gaan, kom je weer bij de captive-portal uit omdat de DNS-request nog gecachet is.
De Raspberry Pi moet dus ook voor niet-aangemelde gasten de juiste resultaten voor DNS-requests leveren. Dat doet DNSMasq standaard ook. Dan zorgt de volgende firewallregel ervoor dat je bij een poging om naar ct.nl te gaan bij de captive-portal uitkomt:
sudo iptables -A PREROUTING -t nat -i wlan0 -p tcp -m mark --mark 1 -m tcp --dport 80 -j DNAT --to-destination 192.168.255.1
Daarmee worden alle pakketten uit de wifi (-i wlan0) die met 1 gemarkeerd zijn (-m mark –-mark 1) en voor poort 80 (-dport 80) van een bepaald doel bestemd zijn, geleid naar het ip-adres 192.168.255.1 (--to-destination 192.168.255.1). De gasten komen dan bij de portalpagina van de Raspberry Pi en niet bij ct.nl.
Dat werkt echter alleen voor HTTPrequests, de captive-portal kan HTTPSrequests niet beantwoorden door het ontbreken van SSL-certificaten. Maar dat hoeft bij mobiele apparaten ook niet omdat die door een truc in de websiteconfiguratie later automatisch via HTTP naar de portal geleid worden.
Om ervoor te zorgen dat je de firewallregels niet na elke herstart opnieuw in moet voeren, installeer je nog het pakket iptables-persistent:
sudo apt-get install
iptables-persistent
Bij het installeren van dat pakket wordt je gevraagd of je de regels later wilt opslaan – dat moet je bevestigen.
Apache als portalserver
Voor de captive-portal heb je een webserver op de Raspberry Pi nodig. Die moet je ook nog installeren. Je kunt daarbij kiezen of je Nginx of Apache wilt installeren, in dit voorbeeld gaan we uit van Apache met PHP:
sudo
apt-get install apache2 php
De captive-test van Android, iOS en Windows Phone verloopt volgens hetzelfde principe: zodra een mobiel apparaat verbinding heeft met een wifi, roept elk systeem standaard een bepaald url op bij de betreffende fabrikant en verwacht dan een succesvolle pagina-oproep (200) of de foutmelding dat de pagina niet bestaat (404).
Captive-portals moeten in plaats daarvan een tijdelijk omleiding met code 302 terugleveren en de portalsite als doeladres aangeven – dan opent het mobiele apparaat de pagina meteen of krijgt de bezoeker het bericht dat hij zich eerst moet aanmelden.
Om het simpel te houden, stel je bij Apache niet alleen een omleiding in voor de drie url's van Android, iOS en Windows Phone, maar zorg je ervoor dat altijd code 302 terug geleverd wordt als een bestand niet gevonden wordt – en stuur je de bezoeker door naar de startpagina van de portal. Om dat te doen, voeg je bij het VirtualHost-deel van het bestand /etc/ apache2/sites-available/000-default.conf de volgend regel toe:
ErrorDocument 404
/
Dan komen alle wifigasten bij hun eerste toegang tot je draadloze netwerk op de startpagina van de Apache-webserver terecht.
Vrijheid voor iedereen
Om ervoor te zorgen dat een gast op internet kan rondstruinen, moet je zijn datapakketten door de firewall met een 2 laten markeren. Als onderscheidingscriterium gebruik je daar het MAC-adres van de wifiadapter voor. Hier een voorbeeld:
iptables -A PREROUTING -m mac --mac-source 99:98:97:ab:cd:ef -j MARK --set-mark
2
-t mangle
Om die regel toe te voegen, moet iptables met rootrechten aangeroepen worden. Veel captive-portals gebruiken daar een sudo-aanroep vanuit PHP voor, maar daar kan een eventuele aanvaller veel onheil mee verrichten – voor ons een no-go.
Onze captive-portal gebruikt in plaats daarvan systemd om de iptablesregels te beheren, en een onafhankelijk PHP-front-end dat bij het aanmelden van een gast een bestand aanmaakt met zijn MAC-adres als naam, in de directory /var/ www/wlanguests. Het PHP-script achterhaalt het MAC-adres door de ARP-tabel uit te lezen:
$ARP_TB = file("/proc/net/arp"); $MAC_ADDR = ""; if($ARP_TB !== false) { foreach ($ARP_TB as $ARP_E){ if(0 === strpos($ARP_E, $_SERVER["REMOTE_ADDR"]."")) { $MAC_ADDR=str_replace(':','', substr($ARP_E,41,17)); break;
}
}
}
... file_put_contents($WLANGUEST_DIR .$MAC_ADDR,$_SERVER["REMOTE_ADDR"] ."\n");
De gastdirectory, die bij de Apachegebruiker www-data behoort en voor hem beschrijfbaar moet zijn, laten we door de systemd-job wlanguests.path bewaken op veranderingen:
[Unit]
Description=Watch directory [Path] PathModified=/var/www/wlanguests/ [Install] WantedBy=multi-user.target
Systemd gebruikt dan de kernelfunctie inotify() om informatie te krijgen over veranderingen aan de directory en start dan de job wlanguests.service die bij
wlanguests.path hoort:
[Unit]
Description=Update iptables rules [Service]
Type=oneshot ExecStart=/usr/local/bin/ .wlanguestsctl
Het shellscript /usr/local/bin/wlanguestsctl stelt de iptables-regels in en verwijdert ze. Het script vergelijk daarvoor de MACadressen in de bestaande iptables-regels met de bestandsnamen in de gastdirectory – als er een bestand bestaat waarvoor nog geen regel is, wordt een nieuwe regel aangemaakt. Een regel waarvoor er geen bijpassend bestand meer is, wordt verwijderd.
De systemd-jobs en het script wlanguests staan ook in het ziparchief. Om ze te installeren, kopieer je de bestanden naar de juiste directory's en activeer je de systemd-job:
sudo cp 1726-154/wlanguests/ .wlanguests.* /etc/systemd/system sudo cp 1726-154/wlanguests/ .wlanguestsctl /usr/local/bin sudo systemctl enable wlanguests.path
Als je de PHP-code hierboven in een begroetingspagina integreert, heb je daarmee al een eenvoudige captive-portal die de gebruiksvoorwaarden laat zien zodra iemand verbinding met de wifi maakt. Maar daarmee is de kans groot dat ook je buren en voorbijgangers je open wifi zullen gaan gebruiken.
Mini-TFT als pincode-display
We hebben voor de Raspberry Pi daarom een goedkoop TFT-display gekocht van zo'n 15 euro dat voor iedere gast een individuele pincode laat zien zodra hij zich op het wifi aanmeldt. Het 1,8 inch grote display met 128 x 160 pixels heeft een ST7735-controller en een SPI-interface. Als je op internet zoekt op '1.8 128x160 SPI TFT' kom je er vast wel een paar tegen, anders kun je hem ook bij Conrad of SOS Solutions krijgen. Let erop dat er een groene lip aan de displaybeschermingsfolie zit – dat is een indicatie van de gebruikte display-controller.
Hoe je het display met de Raspberry Pi verbindt, staat in de tabel hiernaast. Om het display in gebruik te nemen, voeg je in het bestand /boot/cmdline.txt de volgende regel toe aan het eind van de eerste regel: fbcon=map:0000001 fbcon=font:10x18 :
. FRAMEBUFFER=/dev/fb1
Daarmee wordt het display aan terminal 7 toegekend (/dev/tty7) en wordt een 10x18pixelfont geselecteerd, zodat de pincode later beter te lezen is. Bovendien laad je bij het starten steeds automatisch de SPImodule en de kernelmodule voor het display door aan het bestand /etc/modules het volgende toe te voegen:
spi_bcm2835 fbtft_device
Om ervoor te zorgen dat de kernel weet hoe het display met de Pi verbonden is, maak je het bestand /etc/modprobe.d/ raspi.conf aan en voeg je daar de moduleopties aan toe:
options fbtft_device custom : . name=fb_st7735r speed=32000000 : . gpios=reset:23,cs:8,dc:24,led:25 : . rotate=90
Daarna voer je sudo raspi-config uit en activeer je bij 'Interfacing Options' de SPI-interface voordat je de Raspberry Pi herstart.
Als hij weer opgestart is, moet je ervoor zorgen dat de pincode op het display getoond gaat worden. Daar is het script wlanpindisplay uit het ziparchief verantwoordelijk voor. Dat wordt op dezelfde manier aangeroepen als het regelbeheer van systemd – de systemd-jobbestanden heten wlanpindisplay.path en wlanpindisplay.service en zitten eveneens in het ziparchief. Het installeren gaat vergelijkbaar met het regelbeheer:
sudo cp 1726-154/wlanpindisplay/ .wlanpindisplay.* /etc/systemd/system sudo cp 1726-154/wlanpindisplay/ .wlanpindisplay /usr/local/bin sudo systemctl enable
wlanpindisplay.path
Het PHP-front-end maakt ook de pincode aan en slaat die op in het bestand /var/ www/wlanguests/wlanpin:
if(!file_exists($WLAN_PIN_FILE)) file_put_contents($WLAN_PIN_FILE, random_int(100000,999999)."\n");
Het complete PHP-front-end zit ook in het ziparchief, in de directory php. Kopieer het eenvoudigweg naar de documentroot van Apache, verwijder het daar aanwezige bestand index.html en maak de gastendirectory aan:
sudo cp 1726-154/php/* /var/www/html sudo rm /var/www/html/index.html sudo mkdir /var/www/wlanguests sudo chown www-data:www-data
/var/www/wlanguests
Als laatste moet je dan nog twee cron-jobs instellen. De eerste werpt de gast er na ongeveer vier uur uit, de tweede verwijdert een aangevraagde maar niet gebruikte pincode na ongeveer twee minuten. Beide dienen ervoor om opdringerige buren af te wijzen die je gastwifi voor hun doeleinden willen gebruiken. Beide cron-jobs werken met de rechten van de webserver. Voer sudo crontab -u www-data -e uit en voer het volgende in:
0,30 * * * * /usr/local/bin/ .wlanguestsctl --purge
*/2 * * * * /usr/local/bin/ .wlanpindisplay --purge
Dan blijft het wifi voorbehouden aan je gasten, die hun pincode kunnen lezen op het display van de Raspberry Pi. (nkr)
www.ct.nl/softlink/1804098