Portal cautivo con Raspberry Pi

El portal cautivo

En 2012 compré la primera Raspberry Pi, el modelo B. Tiene unas prestaciones hardware bastante limitadas: un procesador SoC BCM2835 con 256 MB de RAM y dos puertos USB que en realidad comparten ancho de banda con el interfaz ethernet de 100 Mbps, vídeo HDMI y compuesto por conector RCA y salida de audio analógico en jack. La memoria de almacenamiento es una tarjeta SD. No tiene interfaz wifi ni bluetooth integrados.

A pesar de estas limitaciones me ha servido para varios experimentos de radio. Uno de ellos es un portal cautivo por WiFi. Aunque es algo descrito en numerosos artículos y manuales voy a hacer un resumen, sin pretensiones de ser académico, exhaustivo, riguroso ni completo. Solamente comparto unas notas breves que había tomado para uso personal. La mayoría de la información la obtuve de este tema en el foro de RPi The Trollspot – A battery powered WiFi Captive Portal

Un portal cautivo es un servidor web, normalmente accesible por WiFi, al que inevitablemente accedes desde tu navegador. Los puntos de acceso (AP) wifi en espacios públicos como hoteles o medios de transporte suelen funcionar así. El AP tiene una red con un SSID llamativo y sin encriptación. Cuando el dispositivo cliente, normalmente portátil, se conecta, obtiene su configuración IP por DHCP (dirección, máscara, puerta de enlace y DNS). El sistema se llama portal cautivo porque, cualquier petición DNS que emita el cliente, será tratada por el servidor de nombres del portal redirigiendo el nombre consultado a la dirección IP del servidor web que reside en dicho portal. De esta manera, cuando el usuario intente acceder a cualquier sitio web acabará viendo la página que le muestre el portal. Una vez ahí puede haber una plataforma de autenticación o de pago para acceder a internet u otros servicios IP fuera de internet, como multimedia o cualquier otro. En su forma más sencilla la web es una página html estática. Se puede montar fácilmente para proporcionar una información a asistentes a un evento, sin necesidad ni de que accedan a internet ni de publicar nuestro contenido en un servidor externo. Como mi modelo de RPi no tiene Wi-Fi integrado utilizo un dongle usb.

Raspberry Pi modelo B con dongle wifi alimentada desde un powerbank

Al grano

Empezamos por actualizar el sistema e instalar los paquetes esenciales:

apt-get update & apt-get upgrade -y
apt-get install lighttpd hostapd dnsmasq

hostpad es el paquete que permite al interfaz wifi actuar como punto de acceso. dnsmasq es el servidor DNS. lighttp es un ligerísimo servidor http. Cada uno de estos servicios tiene un fichero de configuración y se puede activar a demanda o en cada incio del sistema.

Lighttp

El contenido del servidor web se guarda en /var/www/html. Puede ser algo tan sencillo como un index.html que contenga:

<!DOCTYPE html PUBLIC «-//W3C//DTD XHTML 1.0 Transitional//EN» «http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd»>
<html xmlns=»http://www.w3.org/1999/xhtml»>
<head>
<meta http-equiv=»Content-Type» content=»text/html; charset=UTF-8″ />
<title>Welcome page</title>
<style type=»text/css» media=»screen»>
body { background: #e7e7e7; font-family: Verdana, sans-serif; font-size: 11pt; }
#page { background: #ffffff; margin: 50px; border: 2px solid #c0c0c0; padding: 10px; }
#header { background: #4b6983; border: 2px solid #7590ae; text-align: center; padding: 10px; color: #ffffff; }
#header h1 { color: #ffffff; }
#body { padding: 10px; }
span.tt { font-family: monospace; }
span.bold { font-weight: bold; }
a:link { text-decoration: none; font-weight: bold; color: #C00; background: #ffc; }
a:visited { text-decoration: none; font-weight: bold; color: #999; background: #ffc; }
a:active { text-decoration: none; font-weight: bold; color: #F00; background: #FC0; }
a:hover { text-decoration: none; color: #C00; background: #FC0; }
</style>
</head>
<body>
<div id=»page»>
<div id=»header»>
<h1> BIENVENIDO</h1>
Experimento de portal cautivo
</div>
<div id=»body»>
<h2>En esta web comparto una información</h2>
En estos puntos:
<ul>
<li>Punto primero</li>
<li>Punto segundo</li>
<li>tercero….</li>
<li>…..</li>
<li>y fin</li>

</ul>
</div>
</div>

<!– s:853e9a42efca88ae0dd1a83aeb215047 –>
</body>
</html>

El fichero de configuración es /etc/lighttpd/lighttpd.conf y sirve este modelo, del que seguramente sobren muchas cosas:

qqserver.modules = (
«mod_indexfile»,
«mod_access»,
«mod_alias»,
«mod_redirect»,
«mod_accesslog»,
)

server.document-root = «/var/www/html»
server.upload-dirs = ( «/var/cache/lighttpd/uploads» )
server.errorlog = «/var/log/lighttpd/error.log»
server.pid-file = «/var/run/lighttpd.pid»
server.username = «www-data»
server.groupname = «www-data»
server.port = 80
server.error-handler-404 = «/index.html»
accesslog.filename = «/var/log/lighttpd/access.log»

# strict parsing and normalization of URL for consistency and security
# https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_http-parseoptsDetails
# (might need to explicitly set «url-path-2f-decode» = «disable»
# if a specific application is encoding URLs inside url-path)
server.http-parseopts = (
«header-strict» => «enable»,# default
«host-strict» => «enable»,# default
«host-normalize» => «enable»,# default
«url-normalize-unreserved»=> «enable»,# recommended highly
«url-normalize-required» => «enable»,# recommended
«url-ctrls-reject» => «enable»,# recommended
«url-path-2f-decode» => «enable»,# recommended highly (unless breaks app)
#»url-path-2f-reject» => «enable»,
«url-path-dotseg-remove» => «enable»,# recommended highly (unless breaks app)
#»url-path-dotseg-reject» => «enable»,
#»url-query-20-plus» => «enable»,# consistency in query string
)

index-file.names = ( «index.php», «index.html» )
url.access-deny = ( «~», «.inc» )
static-file.exclude-extensions = ( «.php», «.pl», «.fcgi» )

compress.cache-dir = «/var/cache/lighttpd/compress/»
compress.filetype = ( «application/javascript», «text/css», «text/html», «text/plain» )

# default listening port for IPv6 falls back to the IPv4 port
include_shell «/usr/share/lighttpd/use-ipv6.pl » + server.port
include_shell «/usr/share/lighttpd/create-mime.conf.pl»
include «/etc/lighttpd/conf-enabled/*.conf»

#server.compat-module-load = «disable»
server.modules += (
«mod_access»,
«mod_alias»,
«mod_compress»,
«mod_dirlisting»,
«mod_staticfile»,
«mod_redirect»,
)

$HTTP[«host»] =~ «^([^(midominio)])» {
url.redirect = (
«/^(.*)» => «http://midominio/»,
«» => «http://midominio/»,
«/» => «http://midominio/»
)
}

El servicio se lanza con:

sudo systemctl start lighttpd

Y para habilitarlo automáticamente en cada reinicio:

sudo systemctl enable lighttpd

El servidor DNS se configura en /etc/dnsmasq.conf y puede ser así:

log-dhcp
interface=wlan0 # Listening interface
dhcp-range=192.168.4.2,192.168.4.20,1h
# Pool of IP addresses served via DHCP
domain=wlan # Local wireless DNS domain
#address=/gw.wlan/192.168.4.1
# Alias for this router
address=/#/192.168.4.1
# Redirección a este equipo

Es necesario que esas direcciones sean coherentes con la configuración de red del equipo definida en /etc/network/interfaces por ejemplo así:

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and ‘man dhcpcd.conf’

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo

iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.1.2
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 192.168.1.1

allow-hotplug wlan0
#cliente normal
#iface wlan0 inet manual
#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
#iface default inet dhcp

#access point
iface wlan0 inet static
address 192.168.4.1
netmask 255.255.255.224
#dns-nameservers 192.168.4.1
wireless-mode Master

El servicio se lanza con:

sudo systemctl start dnsmasq

Y se habilita en cada reinicio con:

sudo systemctl enable dnsmasq

Por último, la configuración como punto de acceso está en /etc/hostapd/hostapd.conf y puede ser así:

country_code=ES
interface=wlan0
ssid=Mi_red_wifi
hw_mode=g
channel=7
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0

El servicio se lanza con:

sudo systemctl start hostapd

Y se habilita en cada reinicio con:

sudo systemctl enable hostapd

 

Con esto deberíamos tener el portal cautivo funcionando. El sistema operativo de los teléfonos móviles suelen verificar si una red wifi proporciona conectividad a internet consultando una dirección web fija, en segundo plano. Si esa consulta obtiene por respuesta una web pero no con el contenido esperado deduce que está en un portal cautivo y salta una notificación al usuario para que inicie sesión. Si se abre esa notificación el navegador web te lleva a la página servida por el portal cautivo.

Notificación de portal cautivo