Routeur mptcp
EN CHANTIER
Article en cours d'écriture et/ou de test. Certains éléments peuvent être incomplets et mener à un résultat non fonctionnel.Merci de ne pas rager.
Dans cette documentation, nous allons aborder la mise en place d'un routeur d'agrégat de liens Internet sous Linux exploitant les propriétés du Multipath TCP, et ceux, afin d'allier résilience d'accès WAN et addition de bandes passantes.
Principes
Situation de mise en œuvre
On part du principe que l'on se trouve dans une chambre d'hôtel avec un accès Wifi Wifirst offrant un débit moisi et une couverture Free 4G qui fonctionne vraiment bien la plupart du temps (mais pas disponible certains jours). En gros c'est la hess (#grosShlag).
Architecture et matériel
METTRE UN SCHÉMA
Dans le principe, il faudra avoir une machine faisant office de routeur centralisateur pour notre accès à Internet ainsi qu'une autre machine hébergée dans un endroit offrant une bande passante montante supérieur ou égale à la somme des débits descendants de nos accès WAN.
Mais pourquoi ça ?
Et bien si vous avez bien compris le fonctionnement du protocole MPTCP, vous savez que pour pouvoir initier une session TCP sur différents liens simultanément, il faut que la machine cliente et la machine serveur cause toute les deux MPTCP. Seulement voilà, à part les rarissimes sites de tests mis en ligne exploitants le protocole à des fins de débogage, vous n'aurez aucune chance de l'utiliser pour télécharger l'ISO de votre distribution préférée... C'est ici qu'intervient cette fameuse machine distante avec un accès montant de guedin. Nous allons l'utiliser comme proxy TCP afin de relayer nos requêtes MPTCP en TCP standard pour Internet via son super lien WAN et nous renvoyer par la suite les paquets en MPTCP de façon totalement transparente.
Pour l'hébergement de cette machine vous pouvez soit demander à un amis ne sachant que faire de tout les Mo/s de sa fibre optique gbps (humm les inégalités dans le monde...) ou alors opter pour les services d'une entreprise/association spécialisée. Le seul impératif outre la bande passante sera bien sûr de pouvoir installer son propre noyau Linux patché, ce qui est loin d'être proposé par tous (oubliez donc les hébergeurs WEB et les services "cloud" à pas cher, c'est mort d'avance). Pour ma part je suis parti sur une Dedibox de chez Online.
Pour le routeur centralisateur, j'utiliserai un Raspberry Pi 2 car bien adapté à un usage personnel en chambre pour des raisons de coûts/format/bruit (seule la partie sur la compilation change par rapport à un PC x86). Un PC ou une machine virtuelle est parfaitement utilisable pour cette tâche.
Liste des matériels utilisés dans ce document:
- Un Raspberry Pi 2 (testé également avec le 3)
- Une clé Wifi TP-LINK TL-WN722N
- Une Clé 4G HAWEI E3372h
- Une machine Linux patché et hébergé dans un endroit disposant d'un fort débit montant
Je part du principe que le Raspberry est fonctionnel car sa mise en œuvre n'est pas l'objet de ce document.
Pratique
Multipath TCP
Ce protocole est déjà expliqué dans sa page dédié, il ne sera donc pas détaillé ici (le principe étant assez simple à saisir). Il est nécessaire de compiler un noyau Linux patché MPTCP ou de télécharger le binaire déjà fait par mes soins directement dans nos fichiers et de l'installer sur les deux machines.
Sur le routeur centralisateur
Installation des paquets
apt install hostapd wpasupplicant bridge-utils shadowsocks-libev dnsmasq
Détails:
- hostapd: Gestion du point d'accès Wifi pour le LAN
- wpasupplicant: Gestion de la connexion Wifi pour le WAN (installé par défaut dans Raspbian)
- bridge-utils: Gestion du pont réseau pour englober le réseau filaire et Wifi LAN dans le même domaine de diffusion
- shadowsock-libdev: Gestion du proxy TCP
Configuration du réseau
Activation du routage
Il faut dé-commenter net.ipv4.ip_forward=1
dans /etc/sysctl.conf
.
Renommage des interfaces
Afin de ne pas avoir de surprises dans le nom des interfaces réseaux au moment de leur initialisation et pour mieux s'y retrouver. Je recommande de leur attribuer un nom en fonction de leur adresse MAC.
Nommage de l'interface LAN Ethernet
vim /etc/systemd/network/10-ethlan0.link
[Match]
MACAddress=b8:27:eb:xx:xx:xx
[Link]
Name=ethlan0
Nommage de l'interface LAN Wifi
vim /etc/systemd/network/10-wifilan0.link
[Match]
MACAddress=c0:4a:00:xx:xx:xx
[Link]
Name=wifilan0
Nommage de l'interface WAN 4G
vim /etc/systemd/network/10-4Gwan0.link
[Match]
MACAddress=0c:5b:8f:xx:xx:xx
[Link]
Name=4Gwan0
Nommage de l'interface WAN Wifirst
vim /etc/systemd/network/10-wifirst0.link
[Match]
MACAddress=c0:4a:00:xx:xx:xx
[Link]
Name=wifirst0
Les modifications prendront effets au prochain redémarrage.
Configuration des interfaces
Configuration principale
vim /etc/network/interfaces
# Montage automatique de l'interface de lien local et du pont
auto lo brlan0
iface lo inet loopback
# Configuration du lien filaire LAN
allow-hotplug ethlan0
iface ethlan0 inet manual
# Configuration du lien Wifi LAN
allow-hotplug wifilan0
iface wifilan0 inet manual
# Configuration du pont unifiant les interfaces LAN
iface brlan0 inet static
address 172.16.1.254/24
bridge_ports ethlan0 wifilan0
up systemctl restart dnsmasq.service
# Configuration du lien 4G WAN
allow-hotplug 4Gwan0
iface 4Gwan0 inet dhcp
up /root/init/pbr-4g.sh
down /root/init/pbr-dwn.sh
# Configuration du lien Wifirst WAN
allow-hotplug wifirst0
iface wifirst0 inet dhcp
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
up /root/init/pbr-wifirst.sh
down /root/init/pbr-dwn.sh
# Configuration du lien de partage de connexion mobile
allow-hotplug usb0
iface usb0 inet dhcp
up /root/init/pbr-usb.sh
down /root/init/pbr-dwn.sh
Comme vous pouvez le voir, j'ai ajouté une interface usb0 qui correspond à l'interface proposé par un téléphone Android. Avec ce paramètre, le branchement d'un tel appareil en mode partage de connexion USB pourra également servir au Multipath TCP. Elle n'a pas fait l'objet d'une modification de nom pour la bonne et simple raison qu'avec l'obsolécence programmée (de merde, il faut le dire), on est tout le temps en train de changer de téléphone et que donc on passerai notre temps à changer l'adresse MAC de cette foutue interface (de plus, un pote pourrai faire partager sa connexion aussi de temps en temps).
ASTUCE
Je recommande très fortement de réduire le temps de démarrage du service réseau de Systemd car comme à son habitude, cette pourriture nous fait poireauter 5 minutes à l'allumage de la machine (mais qui code cette merde ?!).Configuration de la connexion au point d'accès Wifirst
vim /etc/wpa_supplicant/wpa_supplicant.conf
country=FR
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
ap_scan=1
network={
ssid="Wifirst BA 105"
key_mgmt=NONE
}
INFORMATION
Pour une connexion avec sécurité WPA, référez-vous à la documentation sur le Hotspot wifi.Connexion au portail captif Wifirst Plus loin dans ce document, je fait appel au script wifirst_autoconnect. Cette étape est expliqué ici. Je le rappellerai plus loin.
Création du point d'accès Wifi LAN
Configuration du serveur DHCP
vim /etc/dnsmasq.conf
# Partie commune
domain-needed
expand-hosts
bogus-priv
interface=brlan0
# Partie DHCP
dhcp-range=brlan0,172.16.1.1,172.16.1.250,255.255.255.0,12h
# Partie cache DNS
cache-size=256
Afin de corriger un bug lié au remplissage du fichier resolv.conf
par dnsmasq (ce dernier met 127.0.0.1 en serveur DNS, autant dire que ça ne va pas fonctionner des masses). Nous allons ajouter un paramètre dans la configuration du service (ce problème est expliqué ici et là).
vim /etc/default/dnsmasq
y ajouter le paramètre suivant:
DNSMASQ_EXCEPT=lo
et vérifier par la même la présence du paramètre
ENABLED=1
Configuration du point d'accès Wifi LAN
vim /etc/hostapd/hostapd.conf
# 1. Paramètres de l'interface d'écoute
interface=wifilan0
bridge=brlan0
driver=nl80211
# 2. Paramètres du démon hostapd
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
# 3. Configuration du réseau Wifi
ssid=LeNomDeVotreRZOWifi
channel=6
hw_mode=g
ieee80211n=1
# 4. Sécurité du réseau Wifi
wpa=2
wpa_passphrase=VotreMotDePasse
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP
# 5. Logs
logger_syslog=-1
logger_syslog_level=4
logger_stdout=-1
logger_stdout_level=2
# 6. Autres paramètres
beacon_int=100
auth_algs=3
wmm_enabled=1
Test de bon fonctionnement
hostapd -dd /etc/hostapd/hostapd.conf
Lancement au démarrage du système
vim /etc/default/hostapd
DAEMON_CONF="/etc/hostapd/hostapd.conf"
RUN_DAEMON=yes
Création des scripts de routage
Chaque interface WAN doit, au moment de son branchement, créer la table de routage par la source contenant la route par défaut vers sa propre passerelle WAN.
Création du répertoire d'accueil des scripts
mkdir /root/init/
Création du script de montage pour usb0
vim /root/init/pbr-usb.sh
#!/bin/sh
# Création des règles de routage
ip rule add from 192.168.42.0/24 table 1
# Ajout des routes directement connectés à la table
ip route add 192.168.42.0/24 dev usb0 scope link table 1
# Ajout d'une route par défaut à la table
ip route add default via 192.168.42.129 dev usb0 table 1
Création du script de montage pour 4Gwan0
vim /root/init/pbr-4g.sh
#!/bin/sh
# Création des règles de routage
ip rule add from 192.168.8.0/24 table 2
# Ajout des routes directement connectés à la table
ip route add 192.168.8.0/24 dev 4Gwan0 scope link table 2
# Ajout d'une route par défaut à la table
ip route add default via 192.168.8.1 dev 4Gwan0 table 2
Création du script de montage wifirst0
vim /root/init/pbr-wifirst.sh
#!/bin/sh
# Création des règles de routage
ip rule add from 10.188.0.0/16 table 3
# Ajout des routes directement connectés à la table
ip route add 10.188.0.0/16 dev wifirst0 scope link table 3
# Ajout d'une route par défaut à la table
ip route add default via 10.188.0.1 dev wifirst0 table 3
# Authentification sur le réseau wifirst
wifirst_autoconnect
Vous noterez l'appel à un script wifirst_autoconnect. Il s'agit de l'automatisation de la connexion au portail captif Wifirst comme expliqué ici.
Lors du débranchement d'une des interfaces, on va exécuter un script commun qui aura pour effet de changer la route par défaut primaire WAN (celle qui sert au routeur concentrateur) de sorte à créer une résilience sur le chemin sortant.
Petite explication.
Malgré l'utilisation du routage par la source, notre routeur concentrateur utilisera toujours la table de routage standard pour initier la première connexion TCP (allez savoir pourquoi). Cette contrainte a pour effet, en cas d'extinction de l'interface utilisée pour la route par défaut principale (genre vous débranchez votre clé 4G pour vadrouiller et vous niquez le téléchargement programmé pour plus tard sur votre PC relié au routeur), de couper totalement les nouvelles communications qui pourraient s'effectuées via les autres interfaces (complètement con). Pour palier ce problème, nous allons créer un script commun à toute les interfaces sortantes qui se chargera d'injecter une route par défaut via chacune d'elle. Ainsi le système, ne pouvant en accepter qu'une et ne pouvant appliquer de routes via une interface inexistante, se verra obliger d'appliquer celle-ci à la première interface allumée (et donc supposément fonctionnelle). C'est un peu bourrin mais ça fonctionne et étonnamment, je n'ai strictement vu personne faire ça sur Internet alors que c'est bel et bien la seule méthode vraiment fonctionnelle (bien qu'elle ne gère pas le cas où un lien est monté mais que la connectivité réseaux ne fonctionne pas).
Aussi
Afin de permettre la résolution du nom wireless.wifirst.net à coût sûr (pour pouvoir requêter le portail captif du FAI), nous allons fixer ce dernier dans le fichier hosts
. En effet, les DNS du fichier resolv.conf
se remplissant automatiquement, leur ordre n'est pas maîtrisable. On se retrouve donc parfois avec une erreur de résolution (du fait que la requête soit transmise à un DNS externe ne connaissant pas ce NDM local) entraînant l'échec de l'authentification au portail captif et donc le non-fonctionnement de l'accès WAN Wifirst. Il faut faire ceci:
vim /etc/hosts
10.188.0.1 wireless.wifirst.net
Création du script d'extinction
vim /root/init/pbr-dwn.sh
#!/bin/sh
ip r a default via 192.168.8.1 dev 4Gwan0
ip r a default via 192.168.42.129 dev usb0
ip r a default via 10.188.0.1 dev wifirst0
INFORMATION
Il n'y a pas besoin de purger les tables car le noyau s'occupe lui même de supprimer celle qui ne correspondent à aucune adresse active configurée sur une des interfaces réseaux (je rappel qu'une interface éteinte n'applique pas sa configuration IP au système).Attribution du droit d'exécution aux scripts
chmod +x /root/init/*.sh
Création du service Systemd
Il reste quelques paramètres à appliquer à notre routeur. Afin d'automatiser notre système à l'allumage, nous allons créer un service Systemd qui se chargera de configurer le pare feux, le NAT et l'exécution du client proxy.
Création du service
vim /etc/systemd/system/mptcp-proxy.service
[Service]
ExecStart=/usr/bin/mptcp-proxy.sh
[Install]
WantedBy=default.target
et du script associé
vim /usr/bin/mptcp-proxy.sh
#!/bin/sh
# Purge d'Iptables
iptables -F
iptables -F -t nat
iptables -t nat -X SSREDIR
# Création de la table destinée au proxy
iptables -t nat -N SSREDIR
# Redirection de toute les requêtes TCP dans cette table
iptables -t nat -A PREROUTING -p tcp -j SSREDIR
# Toute les requêtes émanant du réseau local et de lien local sont redirigées vers le port 1080 TCP
iptables -t nat -A SSREDIR -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SSREDIR -d 172.16.1.0/24 -j RETURN
iptables -t nat -A SSREDIR -p tcp -j REDIRECT --to-ports 1080
# Activation du NAT pour permettre à l'ICMP et à l'UDP de communiquer
iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j MASQUERADE
# Lancement du proxy client
ss-redir -c /etc/ss_redir.json
Attribution du droit d'exécution
chmod +x /usr/bin/mptcp-proxy.sh
Chargement du service
systemctl daemon-reload
Activation au démarrage
systemctl enable mptcp-proxy.service
Service proxy client
vim /etc/ss_redir.json
{
"server" : ["ADRESSE_DE_VOTRE_SERVEUR_PROXY"],
"server_port" : 8388,
"local_address" : "0.0.0.0",
"local_port" : 1080,
"password" : "toto",
"nameserver": "80.67.169.12",
"timeout" : 300,
"method" : "aes-128-cfb",
"fast_open" : false,
}
Désactivation du service par défaut
Comme nous avons créé notre propre service, nous désactivons celui livré avec le paquet. De cette manière, seule la partie cliente sera exécuté.
systemctl disable shadowsocks-libev.service
Optimisations TCP client
Créez le fichier suivant:
vim /etc/sysctl.d/local.conf
# max open files
fs.file-max = 51200
# max read buffer
net.core.rmem_max = 67108864
# max write buffer
net.core.wmem_max = 67108864
# default read buffer
net.core.rmem_default = 65536
# default write buffer
net.core.wmem_default = 65536
# max processor input queue
net.core.netdev_max_backlog = 4096
# max backlog
net.core.somaxconn = 4096
# resist SYN flood attacks
net.ipv4.tcp_syncookies = 1
# reuse timewait sockets when safe
net.ipv4.tcp_tw_reuse = 1
# turn off fast timewait sockets recycling
net.ipv4.tcp_tw_recycle = 0
# short FIN timeout
net.ipv4.tcp_fin_timeout = 30
# short keepalive time
net.ipv4.tcp_keepalive_time = 1200
# outbound port range
net.ipv4.ip_local_port_range = 10000 65000
# max SYN backlog
net.ipv4.tcp_max_syn_backlog = 4096
# max timewait sockets held by system simultaneously
net.ipv4.tcp_max_tw_buckets = 5000
# turn on TCP Fast Open on both client and server side
net.ipv4.tcp_fastopen = 3
# TCP receive buffer
net.ipv4.tcp_rmem = 4096 87380 67108864
# TCP write buffer
net.ipv4.tcp_wmem = 4096 65536 67108864
# turn on path MTU discovery
net.ipv4.tcp_mtu_probing = 1
# for high-latency network
#net.ipv4.tcp_congestion_control = hybla
# for low-latency network, use cubic instead
# net.ipv4.tcp_congestion_control = cubic
ATTENTION
Redémarrez votre routeur pour appliquer toute les configurations.Sur le serveur proxy
Création de la configuration du proxy
vim /etc/ss_server.json
{
"server" : "0.0.0.0",
"server_port" : 8388,
"local_address" : "0.0.0.0",
"local_port" : 1080,
"password" : "toto",
"nameserver" : "80.67.169.12",
"timeout" : 300,
"method" : "aes-128-cfb",
"fast_open" : false,
Modification des paramètres d'exécution du proxy
vim /etc/default/shadowsocks-libev
Avoir les paramètres suivant définit comme tel:
START=yes
CONFFILE="/etc/ss_server.json"
DAEMON_ARGS=""
Optimisations TCP serveur
Comme pour le client, créez le fichier suivant:
vim /etc/sysctl.d/local.conf
# max open files
fs.file-max = 51200
# max read buffer
net.core.rmem_max = 67108864
# max write buffer
net.core.wmem_max = 67108864
# default read buffer
net.core.rmem_default = 65536
# default write buffer
net.core.wmem_default = 65536
# max processor input queue
net.core.netdev_max_backlog = 4096
# max backlog
net.core.somaxconn = 4096
# resist SYN flood attacks
net.ipv4.tcp_syncookies = 1
# reuse timewait sockets when safe
net.ipv4.tcp_tw_reuse = 1
# turn off fast timewait sockets recycling
net.ipv4.tcp_tw_recycle = 0
# short FIN timeout
net.ipv4.tcp_fin_timeout = 30
# short keepalive time
net.ipv4.tcp_keepalive_time = 1200
# outbound port range
net.ipv4.ip_local_port_range = 10000 65000
# max SYN backlog
net.ipv4.tcp_max_syn_backlog = 4096
# max timewait sockets held by system simultaneously
net.ipv4.tcp_max_tw_buckets = 5000
# turn on TCP Fast Open on both client and server side
net.ipv4.tcp_fastopen = 3
# TCP receive buffer
net.ipv4.tcp_rmem = 4096 87380 67108864
# TCP write buffer
net.ipv4.tcp_wmem = 4096 65536 67108864
# turn on path MTU discovery
net.ipv4.tcp_mtu_probing = 1
# for high-latency network
net.ipv4.tcp_congestion_control = hybla
# for low-latency network, use cubic instead
# net.ipv4.tcp_congestion_control = cubic
Sources
- http://mptcp.zugaina.com/index.fr.php
- https://github.com/shadowsocks/shadowsocks/wiki/Optimizing-Shadowsocks
- https://www.raspberrypi.org/documentation/linux/kernel/configuring.md
- https://github.com/Ysurac/raspberry_kernel_mptcp/
- https://multipath-tcp.org/pmwiki.php/Users/DoItYourself
- https://wiki.gentoo.org/wiki/Hostapd#Frequency_Bands
- http://antoine-schellenberger.com/linux/2014/12/14/tips_hostapd_bridge.html