Creación de un portal cautivo desde cero

En este post la idea es crear un portal cautivo para un esquema de este tipo:

.
                 INTERNET
                    |
                +---+---+
                |gateway|          +---------+
                | linux +----------+ red mgmt|
                +---+---+          +---------+
                    |
                +---+-----+
                |p.cautivo|
                | linux   |
                +---+-----+
                    |
        +------+----+-----+---------+
        |      |          |         |
       AP     AP         AP         PC

Usaremos:

  • Iptables y tc tanto en el gateway como el portal cautivo para crear el firewall y el control de tráfico para una salida a Internet de 10 Mbps simétrico. Usaré HTB.
  • ISC-Dhcp-server
  • FreeRadius para la authentificación
  • Dialup-Admin para la interfaz web a freeradius
  • Postgresql como base de datos que usa freeradius.
  • Apache como servidor web + php
  • Dokuwiki como CMS simple, para aquellos como yo que de hacer webs desde 0 mal.
Nota: Existen otros software ya implementados para hacer esto, por ejemplo, coovachilli que me funcionó de coña, incluso usando los atributos ChilliSpot-Bandwidth-Max-Up, ChilliSpot-Bandwidth-Max-Down en freeradius (tabla radreply) limita el ancho de banda a usar por usuario.

Lo malo de coovachilli es que esa limitación del ancho de banda se hace justamente por eso, por usuario y no por grupos de usuarios. Lo que yo quiero hacer es tener 2 clases de usuarios, los free, que esos en su conjunto solo tengan 1 mbps y los vip, que esos, en su conjunto tengan 4 mpbs. El Coovachilli, al ser por usuario, como se me junten 10 free's, ya se me comen los 10x1=10mbps, cuando yo querría que el conjunto de los 10 tuvieran 1 mbps (usease, 1 mbps/10 = 100 kbps por usuario).

Además, me he encontrado que con coovachilli, sin poner limites de transferencia, por algún motivo la interficie tun que utiliza no puede enviar a mas de 1,5 Mbps así que no me sirve ya que la idea es ofrecer 4 Mbps a los vip.

Así pues me lo he montado yo :)
GATEWAY LINUX

1. Instalar distribución (yo he usado ubuntu server 12.04 LTS)
2. En mi caso uso este server tb para openvpn y squid3 por lo que el script del firewall se complica un poquito.
3. Activar el forwarding en /proc/sys/net/ipv4/ip_forward o /etc/sysctl.conf
4. Interficies: eth0: acceso a Internet (ip pública)
                    eth1: red de management (ip 10.0.0.1/24). Red mía con servidores, etc
                    eth2: red de clientes (172.16.0.33/30). Red de conexión gateway-portal.
5. /etc/networking/interfaces:

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
        address x.x.x.x
        netmask 255.255.255.252
        gateway x.x.x.x
        # dns-* options are implemented by the resolvconf package, if installed
        dns-nameservers 8.8.8.8

auto eth1
iface eth1 inet static
        address 10.0.0.1
        netmask 255.255.255.0
        network 10.0.0.0
        broadcast 10.0.0.255

auto eth2
iface eth2 inet static
        address 172.16.0.33
        netmask 255.255.255.252
        network 172.16.0.32
        broadcast 172.16.0.35
        post-up route add -net 172.16.0.0/27 gw 172.16.0.34
        post-up route add -net 192.168.2.0/24 gw 172.16.0.34

en la eth2 incluimos 2 rutas estáticas: la de la red de interconexión gateway-portal y la de la red de clientes 192.168.2.x que gestiona el portal cautivo.

6. FIREWALL:

#!/bin/bash
########################################################
# FIREWALL
########################################################
#
# CONFIGURACIO VARIABLES
#
INET_IFACE="eth0"
INET_IP="x.x.x.x"
HOTEL_IFACE="eth1"
HOTEL_IP="10.0.0.1"
HOTEL_IP_RANGE="10.0.0.0/24"
LO_IFACE="lo"
LO_IP="127.0.0.1"
VPN_IFACE="tun+"
CLIENT_IFACE="eth2"
CLIENT_IP="172.16.0.33"
CLIENT_RANGE="192.168.2.0/24"

IPTABLES="/sbin/iptables"

#######################################################
# FILTER
#######################################################
# Politiques
$IPTABLES -P INPUT DROP
$IPTABLES -P FORWARD DROP
$IPTABLES -P OUTPUT ACCEPT
$IPTABLES -F
$IPTABLES -t nat -F
$IPTABLES -t mangle -F

#############################################################################
# Input Chains
#############################################################################
$IPTABLES -N bad_tcp_packets
$IPTABLES -N icmp_packets

# bad_tcp_paquets
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j DROP
$IPTABLES -A bad_tcp_packets -p tcp ! --syn -m state --state NEW -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,PSH SYN,FIN,PSH -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,RST SYN,FIN,RST -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,RST,PSH SYN,FIN,RST,PSH -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags ALL ALL -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$IPTABLES -A bad_tcp_packets -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

# icmp_paquets
$IPTABLES -A icmp_packets -p icmp -i $CLIENT_IFACE -d 10.0.0.1 -j DROP
$IPTABLES -A icmp_packets -p icmp -i $CLIENT_IFACE -d 172.16.0.1 -j DROP
$IPTABLES -A icmp_packets -p icmp --icmp-type 8 -j ACCEPT
$IPTABLES -A icmp_packets -p icmp --icmp-type 11 -j ACCEPT
$IPTABLES -A icmp_packets -p icmp --icmp-type 0 -j ACCEPT
$IPTABLES -A icmp_packets -p icmp --icmp-type 3 -j ACCEPT

############################################################################
# INPUT
############################################################################
$IPTABLES -A INPUT -p tcp -j bad_tcp_packets
$IPTABLES -A INPUT -p icmp -j icmp_packets
$IPTABLES -A INPUT -p ALL -m state --state ESTABLISHED,RELATED -j ACCEPT

# desde red mgmnt acceder a ssh, squid y monit
$IPTABLES -A INPUT -i $HOTEL_IFACE -s $HOTEL_IP_RANGE -p tcp --dport 22 -j ACCEPT
$IPTABLES -A INPUT -i $HOTEL_IFACE -s $HOTEL_IP_RANGE -p tcp --dport 2812 -j ACCEPT
$IPTABLES -A INPUT -i $HOTEL_IFACE -s $HOTEL_IP_RANGE -p tcp --dport 3128 -j ACCEPT

# desde la vpn podem accedir al ssh
$IPTABLES -A INPUT -i $VPN_IFACE -p tcp --dport 22 -j ACCEPT

# desde internet: server openvpn
$IPTABLES -A INPUT -i $INET_IFACE -p udp --dport xxxx -j ACCEPT

# desde el localhost ok
$IPTABLES -A INPUT -i $LO_IFACE -s $LO_IP -p ALL -j ACCEPT

#############################################################################
# Forward Chains
#############################################################################
$IPTABLES -N trafic_mgmnt
$IPTABLES -A trafic_mgmnt -p tcp --dport 25 ! -s xxx -j LOG --log-prefix 'SPAM: ' --log-level 4
#al puerto 25 solo sale nuestro servidor de mail: xxxxx
$IPTABLES -A trafic_mgmnt -p tcp --dport 25 ! -s xxxxx -j DROP
# dejar pasar la red de management
$IPTABLES -A trafic_mgmnt -o $INET_IFACE -j ACCEPT
#vpn 
$IPTABLES -A trafic_mgmnt -o $VPN_IFACE -j ACCEPT
# red gateway-cautivo (/24 para integrar 2 subredes)
$IPTABLES -A trafic_mgmnt -d 172.16.0.0/24 -j ACCEPT

$IPTABLES -N trafic_client
# no conexions smtp (spam)
$IPTABLES -A trafic_client -p tcp --dport 25 -j DROP
# clients reals SI internet
$IPTABLES -A trafic_client -s $CLIENT_RANGE -o $INET_IFACE -j ACCEPT
# portal cautivo SI internet almenos por ahora 
$IPTABLES -A trafic_client -s 172.16.0.34 -o $INET_IFACE -j ACCEPT

#############################################################################
# FORWARD
#############################################################################
$IPTABLES -A FORWARD -p tcp -j bad_tcp_packets
$IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# desde la vpn podemos acceder a la red de mgmnt y viceversa
$IPTABLES -A FORWARD -i $VPN_IFACE -o $HOTEL_IFACE -j ACCEPT
$IPTABLES -A FORWARD -i $VPN_IFACE -o $VPN_IFACE -j ACCEPT

# trafic que viene de mgmnt
$IPTABLES -A FORWARD -i $HOTEL_IFACE -s 10.0.0.0/24 -j trafic_mgmnt

# trafic que ve dels clients
$IPTABLES -A FORWARD -i $CLIENT_IFACE -j trafic_client

# trafic de internet a nuestros servidores internos -> pasa lo dirigit a https, imaps, smtps
$IPTABLES -A FORWARD -i $INET_IFACE -p tcp -d xxx --dport 443 -j ACCEPT
$IPTABLES -A FORWARD -i $INET_IFACE -p tcp -d xxx --dport 993 -j ACCEPT
$IPTABLES -A FORWARD -i $INET_IFACE -p tcp -d xxx --dport 587 -j ACCEPT


#######################################################
# NAT
########################################################
$IPTABLES -t nat -A POSTROUTING -o $INET_IFACE -j SNAT --to-source $INET_IP
$IPTABLES -t nat -A PREROUTING -p tcp -d $INET_IP --dport 443 -j DNAT --to-destination xxx:443
$IPTABLES -t nat -A PREROUTING -p tcp -d $INET_IP --dport 993 -j DNAT --to-destination xxx:993
$IPTABLES -t nat -A PREROUTING -p tcp -d $INET_IP --dport 587 -j DNAT --to-destination xxx:587

# politicas de trafico
/root/htb.sh

7. HTB

Finalmente solo nos queda crear el control de tráfico con HTB. Lo que hago es simplemente dividir mis 10 Mbps para que la red de mgmnt tenga 5 Mbps garantizados y la red de clientes tenga los otros 5 Mbps, pero si uno de los 2 no los esta usando el otro los pueda aprovechar. Ademas, dentro de la clase de la red de management la divido para el trafico de la VPN y el resto de trafico.

# Limpiar qdiscs
tc qdisc del dev eth0 root
tc qdisc del dev eth1 root
tc qdisc del dev eth2 root

# instalar htb
tc qdisc add dev eth0 root handle 1: htb default 30

# clases htb
tc class add dev eth0 parent 1: classid 1:1 htb rate 10Mbit ceil 10Mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5Mbit ceil 9Mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 4500Kbit ceil 9Mbit
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 500Kbit ceil 3Mbit

tc class add dev eth0 parent 1:10 classid 1:12 htb rate 3Mbit ceil 8Mbit
tc class add dev eth0 parent 1:10 classid 1:13 htb rate 2Mbit ceil 8Mbit

# Disciplinas de cola
tc qdisc add dev eth0 parent 1:12 handle 12: sfq perturb 10
tc qdisc add dev eth0 parent 1:13 handle 13: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10

# marcamos los paquetes
# eth0 -> internet
# eth1 -> mgmnt
# eth2 -> clients
iptables -t mangle -F
# trafico red hotel
iptables -t mangle -A FORWARD -i eth1 -p ALL -s 10.0.0.0/24 -j MARK --set-mark 13
# squid
iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 13
iptables -t mangle -A OUTPUT -p tcp --dport 443 -j MARK --set-mark 13
# trafico VPN sale por el puerto xxx udp  
iptables -t mangle -A OUTPUT -p udp --sport xxx -j MARK --set-mark 12
# trafico clientes
iptables -t mangle -A FORWARD -i eth2 -p ALL -s 192.168.2.0/24 -j MARK --set-mark 20

# classificacion de los paquetes marcados
tc filter add dev eth0 protocol ip parent 1: handle 12 fw classid 1:12
tc filter add dev eth0 protocol ip parent 1: handle 13 fw classid 1:13
tc filter add dev eth0 protocol ip parent 1: handle 20 fw classid 1:20

# inspeccionar les regles
#tc -s qdisc ls dev eth0
#tc -s class ls dev eth0

# LIMITAR DOWNLOAD RED CLIENTES
# mala idea porque entonces jodes la posible clasificacion que hace el
# portal cautivo
#tc qdisc add dev eth2 root handle 1: htb default 1
#tc class add dev eth2 parent 1: classid 1:1 htb rate 4Mbit ceil 5Mbit

PORTAL CAUTIVO

1. Instalar distro (yo ubuntu 12.04 lts)

2. Interficies

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
        address 172.16.0.34
        netmask 255.255.255.252
        network 172.16.0.32
        broadcast 172.16.0.35
        gateway 172.16.0.33
        dns-nameservers 8.8.8.8

auto eth1
iface eth1 inet static
        address 192.168.2.1
        netmask 255.255.255.0
        network 192.168.2.0
        broadcast 192.168.2.255

auto eth1:0
iface eth1:0 inet static
        address 172.16.0.1
        netmask 255.255.255.224
        network 172.16.0.0
        broadcast 172.16.0.31

la eth1:0 es una red /27 (32 hosts) que uso como red para administrar los puntos de acceso u otros dispositivos transparentes a los clientes.

3. Servidor DHCP:

apt-get install isc-dhcp-server
joe /etc/dhcp/dhcpd.conf

    ddns-update-style none;
    option domain-name "client.local";
    option domain-name-servers 8.8.8.8, 8.8.4.4;

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

    # 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 192.168.2.0 netmask 255.255.255.0 {
            range 192.168.2.30 192.168.2.250;
            option routers 192.168.2.1;
            option subnet-mask 255.255.255.0;
            option broadcast-address 192.168.2.255;
    }

joe /etc/default/isc-dhcp-server
    interfaces="eth1"
 
3. Servidor Web: Apache


apt-get install apache2 php5 php-pear
openssl genrsa -out http.key
openssl req -new -key http.key -out http.csr
openssl x509 -req -days 3650 -in http.csr -signkey http.key -out http.crt
chmod 400 *.key
mkdir /etc/apache2/ssl
cp http.* /etc/apache2/ssl/
ln -s /etc/apache2/mods-available/ssl.conf /etc/apache2/mods-enabled/ssl.conf
ln -s /etc/apache2/mods-available/ssl.load /etc/apache2/mods-enabled/ssl.load
cd sites-enabled/
joe 000-default
/etc/init.d/apache2 restart

4. Servidor Base de datos: Postgresql

apt-get install postgresql
/etc/init.d/postgresql stop
mkdir /home/postgres
chown postgres.postgres /home/postgres/
joe /etc/passwd  
cd /etc/init.d
mv postgresql postgresql.orig
joe postgresql

--------------------
#!/bin/sh
set -e

RUNDIR="/var/run/postgresql"

case "$1" in
    start)
        if [ ! -e $RUNDIR ]
        then
           mkdir $RUNDIR
           chown postgres.postgres $RUNDIR
        fi
        su - -c 'pg_ctl start -D /home/postgres/data' postgres
        ;;
    stop)
        su - -c 'pg_ctl stop -m f -D /home/postgres/data' postgres
        ;;
    restart)
        su - -c 'pg_ctl stop -m f -D /home/postgres/data' postgres
        su - -c 'pg_ctl start -D /home/postgres/data' postgres
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|reload|force-reload|status} [version ..]"
        exit 1
        ;;
esac
exit 0
----------------------
  
cp .bashrc /home/postgres/
cp .profile /home/postgres/
chown postgres.postgres /home/postgres/.bash /home/postgres/.profile
su - postgres
añadir path al .profile de postgres
     PATH="/usr/lib/postgresql/9.1/bin/:$PATH"
pg_ctl -D /home/postgres/data initdb
exit
/etc/init.d/postgresql start
su - postgres
createuser -D -E -R -S -W radiuser
createdb -O radiususer radiusdb
psql radiusdb
cd data/
joe pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
host    all             all             0.0.0.0/0               reject


joe postgresql.conf
  -> listen = 'localhost'

La manera por defecto de iniciar postgresql que tiene el instalador de ubuntu/debian no me gusta para tener bases de datos pequeñitas como las mías  por lo que hago un script que inicio postgres tal y como pone el propio manual de postgresql.org.

Después pongo la base de datos en /home/postgres porque en la partición /home es donde tengo mucha mas espacio.

Finalmente creo el usuario y la base de datos para freeradius y configuro postgres para que solo acepte conexiones de localhost ya que el servidor freeradius está en la misma máquina.

5. Servidor Auth: Freeradius

apt-get install freeradius freeradius-postgresql
cd /etc/freeradius/
joe clients.conf
  client 127.0.0.1 {
       secret = test
  }
joe sql.conf
  canviar a postgresql
  canviar readclients = yes
joe radiusd.conf
  descomentar INCLUDE sql.conf
  descomentar INCLUDE sql/postgresl/counter.conf
cd freeradius/sites-available/
joe default
  descomentar sql (linia 159) i comentar files (152)
  descomentar sql en Accounting (388)
cp /etc/freeradius/sql/postgresl/schema.sql /.../nas.sql /home/postgres
chmod 777 /home/postgres/*.sql
su - postgres
psql -U radiususer radiusdb
 \i schema.sql
 \i nas.sql
 \q
exit

6. Dialup Admin:

apt-get install php5-pgsql
apt-get install freeradius-dialupadmin
joe /etc/freeradius-dialupadmin/admin.conf
   -> posar la info de postgresql
   -> comentar sql_debug para evitar que salgan las consultas
   -> la taula de groups es radusergroup i no usergroup
ln -s /etc/freeradius-dialupadmin/apache2.conf /etc/apache2/sites-enables/001-radius
   (editar el alias por ejemplo)

ejecutar /usr/share/freeradius-dialupadmin/sql/postgresql/*.sql como
postgres para añadir tablas en la base de datos
   
/etc/init.d/apache2 restart

Creamos un par de usuarios
testeamos login de usuarios sql en freeradius
 -> /etc/init.d/freeradius stop
 -> freeradius -X
 -> radtest user pass localhost 1812 <secret de clients.conf>

me falla porque dialupadmin crea el usuario en la tabla radcheck con el Attribute User-Password := md5, si creamos un usuario a mano:
  insert into radcheck(username, attribute, value) values ('user','Password','pass');
entonces funciona bien el radtest

Para que funcione bien en /etc/freeradius-dialupadmin/admin.conf cambiar:
  -> general_encryption_method: clear
  -> sql_password_attribute: Cleartext-Password

7.  Firewall

#!/bin/bash

ipt=/sbin/iptables
clientes=192.168.2.0/24
mgmt=172.16.0.0/27

# limpieza
$ipt -F
$ipt -t nat -F

# politicas
$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT

# paquetes kaka
$ipt -N bad_tcp_packets
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j DROP
$ipt -A bad_tcp_packets -p tcp ! --syn -m state --state NEW -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,PSH SYN,FIN,PSH -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,RST SYN,FIN,RST -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags SYN,FIN,RST,PSH SYN,FIN,RST,PSH -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags ALL ALL -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$ipt -A bad_tcp_packets -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

# icmp_paquets
$ipt -N icmp_packets
$ipt -A icmp_packets -p icmp -i eth1 -d 172.16.0.0/24 -j DROP
$ipt -A icmp_packets -p icmp -i eth1 -d 172.16.0.1 -j DROP
$ipt -A icmp_packets -p icmp --icmp-type 8 -j ACCEPT
$ipt -A icmp_packets -p icmp --icmp-type 11 -j ACCEPT
$ipt -A icmp_packets -p icmp --icmp-type 0 -j ACCEPT
$ipt -A icmp_packets -p icmp --icmp-type 3 -j ACCEPT

######################
# INPUT
######################
$ipt -A INPUT -p tcp -j bad_tcp_packets
$ipt -A INPUT -p icmp -j icmp_packets
$ipt -A INPUT -p ALL -m state --state ESTABLISHED,RELATED -j ACCEPT

# por la eth0 dejamos entrar a la web i ssh
$ipt -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT
$ipt -A INPUT -i eth0 -p tcp --dport 80 -j ACCEPT

# por la eth1 (clientes) dejamos web + dhcp
$ipt -A INPUT -i eth1 -s $clientes -p tcp --dport 80 -j ACCEPT
$ipt -A INPUT -i eth1 -p udp --dport 67:68 -j ACCEPT

# desde el localhost ok
$ipt -A INPUT -i lo -s 127.0.0.1 -p ALL -j ACCEPT

#############################################################################

$ipt -N auth


############################################################################
# FORWARD
############################################################################
$ipt -A FORWARD -p tcp -j bad_tcp_packets
$ipt -A FORWARD -p ALL -m state --state ESTABLISHED,RELATED -j ACCEPT

# Desde red empresa dejar entrar a los puntos de acceso que estan en la red cliente
$ipt -A FORWARD -i eth0 -p icmp -s 10.0.0.0/24 -d $mgmt -j ACCEPT
$ipt -A FORWARD -i eth0 -p tcp -s 10.0.0.0/24 -d $mgmt --dport 80 -j ACCEPT

# peticiones dns si
$ipt -A FORWARD -i eth1 -p udp --dport 53 -j ACCEPT
# necesario para evitar que los autentificados puedan acceder a los APs
$ipt -A FORWARD -i eth1 -p ALL -d 172.16.0.0/24 -j DROP

# usuarios authentificados si
$ipt -A FORWARD -i eth1 -s $clientes -j auth

##############################################################################

$ipt -t nat -N natauth

###################
# NAT
###################

# las peticiones web las redirigimos a la web local
$ipt -t nat -A PREROUTING -i eth1 -s $clientes -p tcp --dport 80 -j natauth
$ipt -t nat -A PREROUTING -i eth1 -s $clientes -p tcp --dport 80 -j REDIRECT --to-ports 80

# aplicamos politicas de control de trafico y reiniciamos nuestros ficheros de control
/root/htb.sh
/usr/local/bin/reiniciar.sh

8. Scripts para dejar pasar un cliente

La idea es guardar en /var/spool/wifi/taula una lista de las MAC autorizadas a passar. Este ficherito, chmod 600 para que solo pueda ser leido y modificado para root.

Los scripts solo ejecutables por root (chmod 700)

hostadd.sh

#!/bin/bash
###########################
TAULA="/var/spool/wifi/taula"
MACr="$3"
TUSER="$1"
IP="$2"
ipt=/sbin/iptables

function add_host() {

  echo "$MAC $IP" >> $TAULA
  $ipt -A auth -p ALL -m mac --mac-source $MAC -j ACCEPT
  $ipt -t nat -A natauth -m mac --mac-source $MAC -j ACCEPT

  case "$TUSER" in

       "free")
              $ipt -t mangle -A FORWARD -i eth1 -p ALL -m mac --mac-source $MAC -j MARK --set-mark 40
              $ipt -t mangle -A FORWARD -o eth1 -p ALL -d $IP -j MARK --set-mark 41
              ;;
       "vip")
              $ipt -t mangle -A FORWARD -i eth1 -p ALL -m mac --mac-source $MAC -j MARK --set-mark 45
              $ipt -t mangle -A FORWARD -o eth1 -p ALL -d $IP -j MARK --set-mark 46
              ;;
  esac
}

# 0. Pasamos la MACrecibida a mayusculas
MAC=$(echo $MACr | tr '[:lower:]' '[:upper:]')

# 1. Buscamos la MAC del pc en el archivo
MAC_ARXIU=$(cat $TAULA | grep $MAC | awk '{print $1}')

# si la mac NO existe
if [ -z "$MAC_ARXIU" ]; then
   add_host
# si SI existe
else
   /usr/local/bin/hostdel.sh $MAC
   add_host
fi

hostdel.sh

#!/bin/bash
###########################
TAULA="/var/spool/wifi/taula"
MACr="$1"
ipt=/sbin/iptables

# 0. Pasamos la mac a mayusculas
MAC=$(echo $MACr | tr '[:lower:]' '[:upper:]')

# 2. Eliminar pareja mac-ip de taula
sed -i "/$MAC/d" $TAULA

# 2. Eliminar controles al firewall
$ipt -D auth -p ALL -m mac --mac-source $MAC -j ACCEPT
$ipt -t nat -D natauth -m mac --mac-source $MAC -j ACCEPT

num=$(iptables -t mangle -L FORWARD --line-numbers | grep $MAC | awk '{print $1}')
$ipt -t mangle -D FORWARD $num
$ipt -t mangle -D FORWARD $num
# en la mangle se añaden a pares, por tanto las eliminamos a pares :)

hosttest.sh

#!/bin/bash
###########################
TAULA="/var/spool/wifi/taula"
VIUS="/tmp/alive"
BORRAR="/tmp/dead"
TAULAs="/tmp/taula"

cat $TAULA | awk '{print $1}' | sort > $TAULAs

nmap -sP -T5 192.168.2.0/24 | grep MAC | awk '{print $3}' | tr '[:lower:]' '[:upper:]' | sort > $VIUS
comm -2 -3 $TAULAs $VIUS > $BORRAR

while read mac ip
do
   /usr/local/bin/hostdel.sh $mac
done < $BORRAR

el hosttest.sh lo ponemos en el cron para que se ejecute cada 1,2 o 3 minutos para que vaya vea los dispositivos que estan activos en la red de clientes, y aquellos que no lo estén los quito.

9. SUDO

Problema: estos scripts los ejecutara un php desde el portal cautivo, es decir, como el usuario www-data (o el que sea). Este usuario no puede modificar el firewall, ni ejecutar estos scripts. Primero pense en chmod +s pero resulta que ubuntu tiene implementada una proteccion en el kernel que no aplica los setgid o setuid en scripts. Así que lo que tengo que hacer es que www-data pueda hacer sudo sin password para estos scripts

visudo

Cmnd_Alias HOSTADD = /usr/local/bin/hostadd.sh
Cmnd_Alias HOSTDEL = /usr/local/bin/hostdel.sh

www-data   ALL = (root) NOPASSWD: HOSTADD
www-data   ALL = (root) NOPASSWD: HOSTDEL


10. Portal Cautivo

(a) Se trata de hacer una web con un formulario que pida user y password y su submit ejecute un php.
(b) Como no se hacer webs, he instalado dokuwiki
(c) Plugins de dokuwiki: formular, wrap, note, columns, tabinclue.
(d) Poner una template que te guste.
(e) Crear la página:
<WRAP column 45%>
<FORM "http://192.168.2.1/data/formplugin/test.php">
Textbox User "User: " 80;
Passbox Pass "Password:" 80;
Submit "Enviar" 90;
</FORM>
</WRAP>

<WRAP column 45%>
<WRAP help round left>
Introduca un usuario y contraseña para acceder a Internet.
</WRAP>
</WRAP>

 (f) Crear otras páginas de OK, de NO OK, etc.

11. PHP para permitir acceder un cliente

Así pues se ejecutará el script /data/formplugin/test.php cuando un usuario ponga su user/password en la web. Este php lo único que tiene que hacer es autentificar contra radius y llamar (o no) a los scripts que hemos creado:

<?php

// variables usuario (solo 8 caracteres iniciales)
$user = substr(trim($_POST['User']), 0, 8);
$pass = substr($_POST['Pass'], 0, 8);

if  (strlen($user) <= 2) {
   exit("ERROR:  Usuario no valido");
}

if  (strlen($pass) <= 2)  {
  exit("ERROR:  Password no entrado");
}

//Auth per RADIUS

$rad = radius_auth_open() or exit ("error radius");

if (! radius_add_server($rad, 'localhost', '1812', 'secret', 10, 3)) {
    exit("ERROR: radius_add_server");
}

if (! radius_create_request($rad, RADIUS_ACCESS_REQUEST)) {
    exit("ERROR: radius_create_request");
}

if (! radius_put_attr($rad, RADIUS_USER_NAME, $user)) {
    exit("ERROR: radius_put_attr USERNAME");
}

if (! radius_put_attr($rad, RADIUS_USER_PASSWORD, $pass)) {
    exit("ERROR: radius_put_attr  PASS");
}

function allow($ip, $usuari)
{
 exec("/bin/ping -c 1 $ip");
 $mac=shell_exec("/usr/sbin/arp -n | grep $ip | awk '{print \$3}'");

 if ( strlen($mac) > 0) {

    //ojo! ultimo parametro la mac, por algun motivo no recibe
    //mas parametros despues de la mac
    system(sprintf("%s %s %s %s", 'sudo /usr/local/bin/hostadd.sh', escapeshellcmd($usuari), $ip, $mac));
    return 0;
 }
 else {
    return 3;
 }
}
switch (radius_send_request($rad))
{
   case RADIUS_ACCESS_ACCEPT:
         $res=allow($_SERVER['REMOTE_ADDR'], $user);

         switch ($res)
         {
            case 0:
                  header('Location: http://192.168.2.1/doku.php/authok');
                  break;

            case 3:
                  header('Location: http://192.168.2.1/doku.php/nomac');
                  break;
         }
         break;
   case RADIUS_ACCESS_REJECT:
         header('Location: http://192.168.2.1/doku.php/authnook');
         break;
   case RADIUS_ACCESS_CHALLENGE:
         echo "CHALLENGE";
         break;
   case 0:
         echo "error ". radius_strerror($rad);
         break;
}

radius_close($rad);

?>

12. HTB

<?php
# Netejar qdiscs
tc qdisc del dev eth0 root
tc qdisc del dev eth1 root

# instalem htb
tc qdisc add dev eth0 root handle 1: htb default 30

# clases htb
tc class add dev eth0 parent 1: classid 1:1 htb rate 4500Kbit ceil 7Mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 500Kbit ceil 1Mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 4Mbit ceil 5Mbit
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 250Kbit ceil 3Mbit

# posem les disciplines de cua
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10

# marcamos los paquetes via iptables
# eth0 -> inet
# eth1 -> clients
iptables -t mangle -F

#los paquetes se marcan en los scripts cuando se autentifican
# 40 = free
# 45 = vip

# classificacio de paquets del hotel
tc filter add dev eth0 protocol ip parent 1: handle 40 fw classid 1:10
tc filter add dev eth0 protocol ip parent 1: handle 45 fw classid 1:20

#### CONTROL DE LO QUE RECIBEN LOS CLIENTES ####
tc qdisc add dev eth1 root handle 2: htb default 30
tc class add dev eth1 parent 2: classid 2:1 htb rate 5Mbit ceil 5Mbit
tc class add dev eth1 parent 2:1 classid 2:10 htb rate 500Kbit ceil 2Mbit
tc class add dev eth1 parent 2:1 classid 2:20 htb rate 3750Kbit ceil 4250Kbit
tc class add dev eth1 parent 2:1 classid 2:30 htb rate 250Kbit ceil 1Mbit

tc qdisc add dev eth1 parent 2:10 handle 10: sfq perturb 10
tc qdisc add dev eth1 parent 2:20 handle 20: sfq perturb 10

tc filter add dev eth1 protocol ip parent 2: handle 41 fw classid 2:10
tc filter add dev eth1 protocol ip parent 2: handle 46 fw classid 2:20

##################### INGRESS #########################
#modprobe ifb numifbs=1
#ip link set dev ifb0 up

# neteja
#tc qdisc del dev eth0 handle ffff: ingress
#tc qdisc del dev ifb0 root

# posem politca de ingress i redirigim trafic de eth0 -> ifb0
#tc qdisc add dev eth0 handle ffff: ingress
#tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0

#tc qdisc add dev ifb0 root handle 2: htb default 30
#tc class add dev ifb0 parent 2: classid 2:1 htb rate 100Mbit
#tc class add dev ifb0 parent 2:1 classid 2:10 htb rate 1Mbit ceil 1Mbit
#tc class add dev ifb0 parent 2:1 classid 2:20 htb rate 4Mbit ceil 5Mbit
#tc class add dev ifb0 parent 2:1 classid 2:30 htb rate 250Kbit ceil 1Mbit

#tc filter add dev ifb0 protocol ip parent 2: prio 1 u32 match ip dst 192.168.2.31/32 flowid 2:20

# inspeccionar las reglas
#tc -s qdisc ls dev eth0
#tc -s class ls dev eth0


Lo que se hace es limitar el ancho de banda que pueden transmitir los clientes:
  • Free: hasta 1 Mbps de subida y 2 de bajada
  • Vip: 4-5 Mbps de bajada y hasta 5 Mbps de subida.
Lo que me he encontrado es que si permito un ceil de bajada mas alto para los free, que esa era la idea para permitir que si no hay vips pues los free tengan mas ancho de banda, veo que los free se comen ancho de banda de los vips, es decir, la clase 2:20 no llega a su "garantizado" de 4Mbps porque la clase 2:10 empieza a pillar ancho de banda. Esto me ha jodido. Después de mirármelo un poco veo que esto ha sido debido a:

  1. el vip era un portátil viejo con windows xp. He visto que incluso con toda la Internet para el solito no pasaba de 3 Mbps de bajada. Evidentemente esto hacia que los demás free's pillen en ancho que este portátil no usaba. Solución: arrancar el portátil con una distro linux para hacer las pruebas :) (malditas ventanas tcp de windows xp!)
  2. el Gateway limitaba en ancho de banda de 5Mbps que enviaba por eth2 (interfaz de clientes). Esto hacia que realmente el tráfico llegase al portal cautivo ya "pseudo-clasificado" haciendo que el cautivo no pudiese reclasificar correctamente sus flujos de datos entrantes entre vip y free, por lo que a los vip no les llegaba un ancho de banda a 4 o 5 Mbps haciendo que los free's lo aprovechase, cosa que parecía como si los free's se comiesen el ancho de los vips. Solución: dejar que al portal cautivo le lleguen los flujos de Internet tal cual = hacer que el gateway no limitase el ancho de banda dirigido a la interfaz de los clientes (eth2)

13. REDIRECCIONES

Vemos que en el portal cautivo, via iptables, las mac's no autenficiadas les hace un -j REDIRECT a sus paquetes, de manera que van a parar al mismo servidor y asi el usuario va a parar al portal cautivo. Lo malo es que si el cliente intenta ir a www.google.com/search?q=holakease&noseque=nosecuantos ocurre que la redireccion hace que el cliente vaya a http://192.168.2.1/search?q=holakease&noseque=nosecuantos y hace que falle porque esa web no existe en nuestro servidor.

Vía htaccess podemos redirigir cualquier petición web a la home de nuestro server:
RewriteCond %{REQUEST_URI} !^(.*)/doku.php
RewriteCond %{REQUEST_URI} !^(.*)/lib/exe/indexer.php
RewriteCond %{REQUEST_URI} !^(.*)/lib/exe/css.php
RewriteCond %{REQUEST_URI} !^(.*)/lib/exe/js.php
RewriteCond %{REQUEST_URI} !^(.*)/lib/plugins/
RewriteCond %{REQUEST_URI} !^(.*)/lib/tpl/prsnl10
RewriteRule ^/?(.*) http://172.16.0.34/doku.php
Lo que hace es que las peticiones vayan a http://cautivo/doku.php (he usado dokuwiki) excepto las peticiones que ya sean de dokuwiki (para evitar loops infinitos y permitir la aplicación de los estilos, plantillas, etc).

De igual forma podemos crear webs para la versión móvil y redirigir segun el user_agent que visita el servidor:
RewriteCond %{HTTP_USER_AGENT} "android | blackberry | ipad  | iphone | ipod | iemobile | opera mobile | palmos | webos | googlebot-mobile" [NC]
RewriteCond %{REQUEST_URI} !^(.*)/m/
RewriteCond %{DOCUMENT_ROOT}/$1 !-f
RewriteRule ^/?doku.php/([a-z]+)/(.*)$ http://172.16.0.34/doku.php/$1/m/$2 [L,R=302]
asi si eres un mobil iras a la web con /m/ por enmedio. Ojo, la rewrite rule hace un doku/$1/m/$2 porque el $1 es la marca del idioma, ya que en mi caso el dokuwiki tiene el plugin de translation para crear páginas para cada idioma. Una url en mi dokuwiki seria:

http://server/doku.php/idioma/pagina
http://server/doku.php/idioma/m/pagina (v. móvil).

La diversíon está en que una modificación de una pagina implica hacerla en varios idiomas y en las versiones móviles de cada idioma, pero en principio, el portal cautivo, pocas modificaciones hay que hacerle.

Jarl!

Comentarios

itza ha dicho que…
saludos. soy luz, me intereso mucho tu trabajo y estoy tratando de hacer algo parecido pero tengo muchas complicaciones con eso. Soy nueva trabajando en ubuntu y todo esto, practicamente voy a ciegas me puedes ayudar por favor
Beavies ha dicho que…
Hola Luz, gracias por leerme :). No me comprometo a nada pero te ayudaré en lo que pueda.

Si ubuntu te da problemas, usa otra distro con la que te sientas mas comod@.

Para este tema va bien saber un poquillo como van los temas de:

1.- IPTABLES (firewalls)
2.- HTML - montar servidores web (apache, ngingx, lighttp) e instalar programillas web (como dokuwiki, joomla o lo que sea)
3.- Algo de programación en php

FInalmente tengo que decir que el software que traen algunos frabricantes, como UNIFI de UBNT pos ya llevan el portal cautivo integrao con varias posibilidades chulas.
itza ha dicho que…
antes de todo gracias por tu pronta respuesta. y pos el ubuntu no me da problemas mas bien la configuracion de las tarjetas de red no le entiendo mucho, tenia pensado usar ngingx como servidor web pero de igual y capaz me quedo con apache y yfi, ya que trae el portal cautivo que es muy bonito y hacerle pocas modificaciones, de igual me sirve para llevar el control del servicio.
entonces la idea mas bien es si me explicas un poco la configuracion de las tarjetas como te decia ya que tienes repetida la info y pos no le entiendo mucho :(
Beavies ha dicho que…
Bueno... en este entorno tenemos 5 redes distintas:

GATEWAY

1.- la red que se connecta a internet con la eth0
2.- una red invisible para los usuarios de la wifi, la red 10.0.0.x via eth1
3.- una mini-red que connecta la eth2 del gateway con la eth0 del portal cautivo.

Esto se ve claro en el archivo de /etc/networking/interfaces del gateway que pastee en el post.

Ademas, en la eth2 del gateway pongo estas 2 lineas:
post-up route add -net 172.16.0.0/27 gw 172.16.0.34
post-up route add -net 192.168.2.0/24 gw 172.16.0.34

que sirve para que cree las rutas (los paquetes dirigidos a la red 172.16.0.0/27 (puntos de acceso) y los dirigidos a 192.168.2.0/24 (clientes wifi) se les pasa al portal cautivo (172.16.0.34 y este sabrá que hacer con ellos)

PORTAL CAUTIVO

Este tiene 2 tarjetas de red fisicas:

- eth0: enlace entre el portal cautivo y el gateway
- eth1: red de clientes
- eth1:0: alias para la red 172.16.0.0/27 de las antenas wifi.

Tb he posteado un copy&paste del /etc/network/interfaces del portal cautivo donde se ve todo esto.

El /etc/network/interfaces es el ficherito que usa ubuntu (y supongo que las distro basadas en debian) para configurar las interficies de red cuando arrancan. (A veces me he encontrado que el network-manager interfiere y jode un poco la marrana, sobretodo si arrancas en modo gráfico)

Otra nota "importante": La red de las antenas wifi es /27, porque tengo unas 15 antenas, con 5 bits puedo direccionar 32 direcciones -2 (broadcast y red) ergo un total de 30 direcciones, por ello hago una red /27, si tuviese mas pos pillaria una red mas extensa o a la inversa.

Pero si esto te lia puedes poner las antenas en el mismo rango de la red de clientes (192.168.2.x) y asi te ahorras un enrutamiento, crear el eth1:0 en el portal cautivo y solo tienes que asegurarte que tu dhcp no le assigne a un usuario wifi una ip que uses para una antena. Eso si, los wifi i las antenas estaran en la misma red fisica y logica con lo que se verán, no tiene porque pasar nada, pero a mi me gusta mas ponerlas en redes lógicas distintas.

En fin, no se si te he ayudado o no, pero el tema de las interficies es un tema de direccionamiento IP de las diferentes redes que he utilizado
itza ha dicho que…
ok. muchas gracias.
Disculpa que no te habia contestado antes.
Unknown ha dicho que…
que hay brother oye yo tengo una pregunta sobre la web la sintaxis es diferente como podria correrlo es que no me corre y me gustaria si me podrias ayudar.
Unknown ha dicho que…
me gustaria saber si me podrias ayudar esque me gustaria aprender como instlar un portal cautivo
Unknown ha dicho que…
Hola Beavies, me gustaría saber si me puedes compartir tu proyecto(las fuentes) descargable, y si me podrías orientar sobre lo siguiente: Quiero levantar un captive portal en un pc que actué como Router/Firewall, al mismo tiempo un servidor web(Apache), db(Mysql), y PHP como el modulo de Apache. Lo que me gustaría es tener todo en una misma pc, mi acceso a Internet es por cable(eth0) y me gustaría compartirla por wifi(wlan-usb) con algunas antenas(como unir las antenas usb, direccionales para simular una?). Mi intención es limitar el ancho de banda por cada usuario, mi red wifi sera abierta y redirigir a mi servidor, mostrarles una página con x información, con un botón para darles acceso a Internet al oprimirlo, y también un link para una página de acceso a los v.i.p, los free miraran publicad de mi negocio en frames mientras navegan(no encuentro otra forma), mientras que si son v.i.p no mostrarles publicidad.

En fin, como ves mi idea? Que me recomendarías? Se puede? Que distro Linux me recomiendas?. Tengo conocimiento sobre PHP,HTML,MySQL,CSS pero no mucho sobre Redes. Saludos y espero tu pronta respuesta, Muchas-Gracias¡¡ Beavies.
Unknown ha dicho que…
Saludos. Nos podrías compartir una imagen de tu instalación hardware, el tipo de antenas que utilizaste, router o switch y tus conexiones, no estaría mal. Y el portal cautivo, ¿Que es, otro router o Servidor PC, allí van conectados las antenas? Gracias.
Anónimo ha dicho que…
me ayudas si tengo problemas al crear uno, lo necesito para un proyecto final . josue.arias.sv@gmail.com
R@ng3r_Vzla ha dicho que…
Hola estoy tratando de montar una solucion de portal cautivo pero ya he probado varias y ninguna funciona con android ya que luego de autenticar al cliente este no se redirecciona a ningun lado y queda en blanco la pagina.
Con este metodo que has publicado se puede solucionar este error???