~rom1v/blog { un blog libre }

Gnirehtet

Durant ces dernières semaines chez Genymobile, j’ai développé un outil de reverse tethering pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés en USB, sans accès root (ni sur le téléphone, ni sur le PC). Il fonctionne sur GNU/Linux, Windows et Mac OS.

Nous avons décidé de le publier en open source, sous le nom de gnirehtet.

Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande bash :

rev <<< tethering

Utilisation

Il suffit de télécharger la dernière release, de l’extraire, et d’exécuter la commande suivante sur le PC :

./gnirehtet rt

Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :

key

Lisez le fichier README pour plus de détails.

Fonctionnement

Le projet est composé de deux parties :

  • une application Android (le client) ;
  • une application Java pour le PC (le serveur relais).

Depuis, je l’ai réécrit en Rust.

Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic réseau du téléphone, sous la forme de byte[] de paquets IPv4 bruts, qu’il transmet alors vers le serveur relais sur une connexion TCP (établie par-dessus adb).

Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles UDP et TCP. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.

D’une certaine manière, le serveur relais se comporte comme un NAT, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.

archi

Pour plus de détails, lisez la page développeurs.

Conception

Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.

TL;DR: J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser SOCKS pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté gnirehtet.

TUN device

Lors de mes recherches pour savoir comment implémenter le reverse tethering, j’ai d’abord trouvé des projets créant un TUN device sur l’ordinateur (vpn-reverse-tether and SimpleRT).

Cette conception fonctionne très bien, et a plusieurs avantages :

  • le traitement est effectué directement au niveau réseau, donc il n’y a pas besoin de traduction entre le niveau 3 et le niveau 5 du modèle OSI ;
  • tous les paquets sont retransmis, indépendamment de leur protocole de transport (ils sont donc tous supportés, là où gnirehtet ne supporte “que” TCP et UDP).

Cependant :

  • elle nécessite un accès root sur l’ordinateur ;
  • elle ne fonctionne pas sur autre chose que Linux.

Il se peut néanmoins que ces applications répondent davantage à vos besoins.

SOCKS

Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première idée était d’écrire un client qui parlait le protocole SOCKS (suivant le RFC 1928). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS existant, par exemple celui fourni par ssh -D.

Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :

ssh mon_serveur -ND1080

Puis configurez votre navigateur pour utiliser le proxy SOCKS localhost:1080. N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de domaine à partir de mon_serveur (dans Firefox, activez network.proxy.socks_remote_dns dans about:config).

Malheureusement, l’implémentation d’OpenSSH ne supporte pas UDP, même si le protocole SOCKS5 lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes DNS (ainsi que pour NTP).

Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :

Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?

La réponse se trouve dans la section 4 du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou un nom de domaine. Par contre, pour utiliser cette fonctionnalité, le client (par exemple Firefox) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre reverse tethering doit être transparent.

Mais tout n’est pas perdu. Certes, OpenSSH ne supporte pas UDP, mais ce n’est qu’une implémentation spécifique, nous pourrions en utiliser une autre. Malheureusement, SOCKS5 relaie UDP sur UDP, et les téléphones et l’ordinateur communiquent sur adb (grâce à adb reverse), qui ne supporte pas non plus la redirection de ports UDP.

Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à utiliser TCP, comme le fait tsocks :

tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.

Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le reverse tethering.

Gnirehtet

Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.

Ce billet de blog et différents projets open source (SimpleRT, vpn-reverse-tether, LocalVPN et ToyVpn) m’ont beaucoup aidé à comprendre comment implémenter cette solution de reverse tethering.

Conclusion

Gnirehtet permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès root. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.

J’espère qu’il pourra être utile à certains d’entre vous.

Discussions sur reddit et Hacker News.

Commentaires

Hello,

Chouette article, super projet. Bravo ! 👍

Philippe G.

J’ai testé. Bravo !

Quelle performance peut on espérer ? J’obtient environ 13Mb/s en débit et 50ms de ping. La limite est elle du à adb ? à l’usb ?

®om

J’ai créé un fichier aléatoire de 500Mio :

dd if=/dev/urandom of=test bs=1M count=500

J’ai chronométré le temps nécessaire pour le copier par adb directement :

time adb push test /sdcard/

Il a fallu 1mn28, soit 5,68Mio/s.

J’ai ensuite hébergé ce fichier sur le PC avec Apache, puis j’ai chronométré le temps nécessaire pour le télécharger depuis Firefox sur Android (tip: 10.0.2.2 est l’adresse du PC depuis le device lorsque gnirehtet est activé).

Ça a pris 1mn35, soit 5,26Mio/s.

Les résultats sont donc très proches d’adb (légèrement inférieurs sans doute parce que les paquets TCP sont découpés en plus petits bouts). Je pense donc que la limite vient d’adb (l’USB 2 et 3 sont plus rapides).

Pour info, j’ai fait le même test en wifi (le device et le laptop tous deux connectés à ma box) : 7mn20, soit 1,14Mio/s.

ronan

Merci Romain,

Gnirehtet est exactement ce qu’il me fallait …

Jacques

Salut Merci infiniment pour cet utilitaire, c’est exactement ce que je cherchais ! Par contre, quand le lance ./gnirehtet, j’obtiens un horrible “Failure [INSTALL_FAILED_OLDER_SDK]” De ce que j’ai compris, tu as spécifié un numéro de version minimum dans ton APK, et j’aurais une version trop vieille avec mon Android 4.4.2, cest bien ça ?

Y aurait-il une solution autre que de faire la mise à jour d’Android ? (ce qui ne m’enchante pas du tout)

®om

De ce que j’ai compris, tu as spécifié un numéro de version minimum dans ton APK, et j’aurais une version trop vieille avec mon Android 4.4.2, cest bien ça ?

Oui, c’est bien ça.

Y aurait-il une solution autre que de faire la mise à jour d’Android ?

Je te conseille de suivre l’issue #2.

En gros, il serait possible de supporter des versions plus anciennes :

  • soit en ajoutant une dépendance vers le NDK (ce que je voudrais éviter juste pour ça) ;
  • soit en fournissant un mécanisme dégradé pour les anciennes versions (polling).

En attendant, tu peux utiliser un autre outil de reverse tethering qui fonctionne depuis l’API 14 (mais qui nécessite d’être root sur le pc).

Dans tous les cas, et indépendamment du reverse tethering, vu le nombre de failles de sécurité découvertes depuis les versions 4, il serait raisonnable de mettre à jour ;-)

Jacques

Dans tous les cas, et indépendamment du reverse tethering, vu le nombre de failles de sécurité découvertes depuis les versions 4, il serait raisonnable de mettre à jour ;-)

Malheureusement, impossible d’obtenir une mise à jour au-delà de la 4.4.2 sans rooter l’appareil, le constructeur ne fournissant pas de version supérieure. C’est pour ça que je ne suis pas chaud pour l’upgrade.

Merci pour ta réponse, en tout cas.

paolo

Très utile. Merci !

Wyles

Salut !

Merci, ton outil est franchement génial. Il tourne nickel sous Lineage OS (OnePlus 2, 7.1 Nougat) :)

J’ignore si tu comptes le maintenir plus longtemps que ça, mais as-tu envisagé d’y ajouter quelques fonctions ? Personnellement, j’utilise NetGuard sur mon téléphone (pare-feu open-source qui s’installe en tant que VPN et qui permet de filtrer le trafic des applications), mais je suis obligé de le désactiver pour faire tourner ton application. Je ne sais pas si c’est possible sans root, mais un module permettant de faire passer NetGuard à travers gnirethet serait vraiment cool !

Je me demandais aussi s’il est obligatoire de dépendre d’un serveur DNS (comme celui de Google) pour résoudre les adresses. Je ne m’y connais pas dans ce domaine, mais n’est-il pas possible de résoudre les adresses de la même manière que l’hôte (ou n’importe quel appareil connecté à Internet) le ferait ? J’avoue que je ne suis pas fan de l’idée de dépendre d’un système tiers.

Dernièrement, ça n’a aucun rapport avec cette page mais je me permets de te conseiller d’ajouter un certificat TLS à ton site web ; des services comme Let’s Encrypt (open source) peuvent t’en générer un gratuitement.

Bonne continuation !

®om

Je ne sais pas si c’est possible sans root, mais un module permettant de faire passer NetGuard à travers gnirethet serait vraiment cool !

Pour paraphraser NetGuard : Android ne permet pas de chaîner des services VPN, donc il n’est pas possible d’utiliser gnirehtet en même temps que d’autres applications utilisant un service VPN.

Je me demandais aussi s’il est obligatoire de dépendre d’un serveur DNS (comme celui de Google) pour résoudre les adresses.

Tu peux préciser l’adresse du ou des serveurs DNS à utiliser :

./gnirehtet rt [serial] [-d DNS[,DNS2,...]]

Par exemple, pour un serveur DNS de Free :

./gnirehtet rt -d 212.27.54.252

Si tu n’en précises pas, il utilise celui de Google (8.8.8.8).

Je ne m’y connais pas dans ce domaine, mais n’est-il pas possible de résoudre les adresses de la même manière que l’hôte (ou n’importe quel appareil connecté à Internet) le ferait ?

Vu que c’est le device Android qui initie les connexions, c’est lui qui forge les requêtes DNS (qu’il envoie dans des paquets UDP qui sont relayés par gnirehtet). Pour cela, il doit connaître l’adresse du serveur DNS à utiliser.

Si tu veux le même serveur que sur le PC, utilise une adresse listée dans /etc/resolv.conf.

je me permets de te conseiller d’ajouter un certificat TLS à ton site web

Oui, j’ai ça dans la section “à faire un jour” de ma todo-list (virtuelle)… :)

mvntp

Hello, on Android 7.1.2 I have the problem that certain apps (Playstore for example) does not recognice the network connection. Other Apps are working fine.

Is there a solution for that?

®om

I have no solution (except fixing the apps).

https://github.com/Genymobile/gnirehtet/issues/37

®om

@Wyles

Dernièrement, ça n’a aucun rapport avec cette page mais je me permets de te conseiller d’ajouter un certificat TLS à ton site web ; des services comme Let’s Encrypt (open source) peuvent t’en générer un gratuitement.

Done ;-)

Jean Francois

Merci beaucoup pour ce merveilleux utilitaire. Il fonctionne très bien avec Galaxy S5 Neo.

Voici quelques lignes de gnirehtet.cmd que j’ai modifié

CALL :locate_file JAVA "C:\ProgramData\Oracle\Java\javapath\java.exe"
...
    ECHO Syntax:%~n0 [re^|un]install ^| rt ^| start ^| stop ^| relay ^| killserver ^| kill
...
:do_killserver
    SETLOCAL EnableDelayedExpansion
    SET filter="WINDOWTITLE EQ gnirehtet_relay_server"
    FOR /F "tokens=2" %%I in ('"TASKLIST /NH /FI %filter%"') DO SET PID=%%I 2>1
    TASKKILL /PID !PID!
    CALL %ADB% kill-server
    ENDLOCAL
...
:do_rt
@rem CALL :do_install
    CALL :do_relay
    CALL :do_start
    EXIT /B 0
...
:eof
    SET dns=
    SET serial=
    SET ADB=
    SET JAVA=
    SET RELAY=
    SET APK=
    SET VAR_NAME=
    SET ALL_BUT_FIRST=
    SET FNAME=
    SET dparam=
    SET filter=
    SET PID=

Merci encore

®om

Content que ce logiciel soit utile :)

Merci pour le code. Cependant, l’interface en ligne de commande n’est plus écrite dans un script séparé (donc le contenu de ce fichier n’existe plus sur la branche de développement).

Par ailleurs, les contributions de code se font par pull request.

BRUNET

Encore merci, c’est vraiment super.

xdej

Très joli, merci !

Pour faire passer du UDP en natif dans un cable USB, tu peux utiliser https://www.raspberrypi.org/forums/viewtopic.php?f=36&t=18916 : Settings / Wireless & Networks / More / Tethering & Portable Hotspot… click “USB”. Puis, si on est root sur sur l’ordi, taper

while sleep 10
do
    route del default gw 192.168.42.129 2> /dev/null &&
    date
done

histoire de virer la route (non prioritaire) de tethering. 192.168.42.129 est l’IP du téléphone, affichée par /sbin/route -n.

Bibi

Bonjour, pourquoi diable dire à la fin de l’article en anglais qu’il existe en français ? Au début c’est plus utile. Ici on dit bien au début qu’il existe en anglais.

®om

@Bibi, merci, je viens de corriger.

Les commentaires sont fermés.