Routeur mptcp

De Wiki doc

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. Il est également possible d'appliquer le changement sans redémarrer avec la commande sysctl -p.

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 192.168.8.0/24 2

# 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 10.188.0.0/16 3

# 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 192.168.42.0/24 1

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 ).

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

ip rule del from $1 table $2


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