Je vais être honnête : je n'avais aucune idée de ce que faisaient mes appareils la nuit. Une trentaine de machins connectés au Wi-Fi de la maison - une TV, trois caméras, un robot aspirateur, des prises, des téléphones, deux Raspberry Pi, une voiture - et la seule chose que je savais, c'est que la box clignotait. Vers qui ? Combien ? À quelle heure ? Aucune idée.

Alors ce week-end, j'ai mis mon réseau sur écoute. Sans rien installer sur un seul téléphone, sans toucher à un seul appareil, avec un Raspberry Pi qui traînait dans un tiroir. Voici ce que ça donne, ce que ça ne peut pas donner, et les trois pièges qui m'ont coûté la soirée.

Le cahier des charges

  • Transparent : personne à la maison ne doit configurer quoi que ce soit.
  • Tout le monde : les caméras et la TV autant que mon Mac.
  • Tout dans mon git, tout en Docker, comme le reste de l'infra.
  • Pas de nouvelle machine : le routeur est un Pi 4 sous OpenWrt, la sonde sera un autre Pi 4 (4 Go, ça va compter).

D'abord, la mauvaise nouvelle : HTTPS

Ma première idée, c'était « je veux la trace des transactions HTTP ». Toutes. Avec les URL.

Ça ne marche pas, et ce n'est pas une question d'outil. 95 % du trafic est en HTTPS. Une sonde qui regarde passer les paquets voit vers quel domaine part la connexion (le SNI dans la poignée de main TLS), quand, et combien d'octets. Elle ne verra jamais l'URL, les en-têtes, le contenu. C'est le but de TLS, et c'est tant mieux.

Ce qu'on peut capturer intégralement, c'est le HTTP en clair, port 80. Une règle DNAT sur le routeur vers un proxy (mitmproxy ou Squid en mode transparent) et vous avez tout : méthode, URL, corps, réponse. Le problème, c'est que ça représente aujourd'hui quelques pour cent du trafic : des mises à jour de firmware, des captive portals, et surtout des objets connectés mal élevés. Ce qui, on va le voir, est justement intéressant.

Et pour voir dans le HTTPS ? Ça existe, mais aucune solution n'est transparente :

  • Un proxy avec son propre certificat (mitmproxy, Squid en ssl-bump, ou les boîtiers d'inspection TLS des entreprises). Le proxy se fait passer pour chaque site, rechiffre vers le vrai serveur, et lit au milieu. Ça ne marche que si chaque appareil fait confiance au certificat du proxy, donc si vous l'installez dessus, un par un. Sur une TV ou une caméra, impossible. Sur un téléphone, les applications bancaires et une bonne partie des apps font du certificate pinning et refuseront de parler. C'est ce que font les entreprises avec des postes qu'elles administrent ; à la maison, ça revient à surveiller les appareils des autres, et ce n'est pas le sujet.
  • Sur ses propres machines, on peut demander au client de donner ses clés : la variable SSLKEYLOGFILE fait écrire à Firefox, Chrome et curl les secrets de session, et Wireshark déchiffre ensuite. Parfait pour déboguer son Mac, inutile pour le reste du LAN.
  • Un agent sur l'appareil (eBPF sur Linux qui accroche les fonctions TLS des programmes, ou un client VPN d'entreprise type Zero Trust). Même limite : il faut installer quelque chose.

Donc la règle est simple : le contenu HTTPS n'appartient qu'à l'appareil qui l'émet. Et les métadonnées suffisent largement à répondre aux questions qui comptent : qui contacte quoi, quand, combien, et est-ce normal.

L'architecture (deux Pi, un câble)

Le LAN est commuté : mon Mac et le NAS se parlent sans passer par le routeur. Tout ce qui traverse le routeur, c'est donc exactement le trafic Internet. Il suffit de le copier.

LAN ──► OpenWrt (Pi 4) : tc copie tout ce qui entre et sort de br-lan ──► tunnel VXLAN ──► rasp002 (Pi 4)
                         dnsmasq logge chaque requête DNS ──────────────► syslog ────────► Suricata → Filebeat → Elasticsearch → Kibana
  • Le routeur : deux filtres tc avec l'action mirred dupliquent chaque trame vers une interface VXLAN. Pourquoi VXLAN et pas un port miroir ? Parce que chaque Pi n'a qu'un seul port Ethernet. Le tunnel fait passer les copies dans le même câble. En bonus, dnsmasq envoie chaque requête DNS en syslog vers la sonde : je sais quel appareil a demandé quel nom, avec la réponse.
  • La sonde : Suricata écoute le port VXLAN, désencapsule lui-même et sort un JSON par événement : dns, tls (avec le SNI et l'empreinte JA3 du client), flow (durée, octets dans chaque sens), http pour le clair, quic, et alert avec les règles ET Open mises à jour chaque jour. Filebeat pousse tout dans Elasticsearch, Kibana affiche. Rétention 30 jours.

Le tout en cinq conteneurs sur un Pi de 4 Go : Elasticsearch avec 1 Go de heap, Kibana bridé à 700 Mo, Suricata sur deux threads. Ça tient, sans marge. Une carte SD n'aime pas les écritures d'Elasticsearch, le SSD USB est sur la liste.

Côté routeur, tout tient dans un script idempotent lancé au boot :

opkg install tc-full kmod-sched kmod-vxlan vxlan
uci set network.vxprobe=interface; uci set network.vxprobe.proto=vxlan
uci set network.vxprobe.peeraddr=10.79.0.213; uci set network.vxprobe.port=4789
uci set network.vxprobe.vid=42; uci set network.vxprobe.tunlink=lan; uci commit network
tc qdisc add dev br-lan clsact
tc filter add dev br-lan ingress matchall action mirred egress mirror dev vxprobe
tc filter add dev br-lan egress  matchall action mirred egress mirror dev vxprobe

Et côté sonde, Suricata se lance avec -i eth0 --set decoder.vxlan.enabled=true "udp port 4789" : il n'analyse que le miroir, jamais le trafic propre du Pi.

Ce que j'ai vu la première heure

Un million de paquets, 700 Mo, et quelques surprises.

  • La TV LG est l'appareil le plus bavard après les serveurs. Dans ses SNI : doubleverify.com, alphonso.tv, gemius.pl, fwmrm.net. Traduction : mesure d'audience publicitaire, reconnaissance automatique de contenu (l'ACR, qui identifie ce que vous regardez pour le revendre), et régies vidéo. Et elle envoie ses requêtes DNS directement à 8.8.8.8 en contournant mon routeur. Sans la sonde, je ne l'aurais jamais vu dans les logs de dnsmasq.
  • Une des caméras parle en HTTP en clair à des adresses chez Alibaba Cloud, sans jamais faire de requête DNS : les IP sont en dur dans le firmware. Je sais maintenant où, quand, et combien. Les deux autres font la même chose vers d'autres IP.
  • La prise connectée discute avec iot.i.tplinknbu.com toutes les quelques minutes. Normal, mais je ne l'avais jamais quantifié.
  • Mon téléphone est le plus discret de la liste : Apple relaie ses DNS via mask.icloud.com (Private Relay), et la sonde ne voit que ça. C'est exactement le comportement qu'on voudrait de tous les appareils.
  • Les règles IDS m'ont aussi rappelé qu'un de mes Pi fait du BitTorrent. Je le savais. Le rapport, lui, ne fait pas de sentiment.

Le tout se lit dans Kibana avec une requête par question : source.ip: 10.79.0.30 and suricata.eve.event_type: tls donne les domaines de la TV, suricata.eve.event_type: dns and not destination.ip: 10.79.0.254 les appareils qui contournent le DNS du routeur, suricata.eve.event_type: http le clair.

Les trois pièges qui m'ont coûté la soirée

La boucle de fragments. Une trame copiée fait 1 500 octets. Encapsulée en VXLAN, 1 550. C'est plus que le MTU, donc le noyau la fragmente. J'avais bien exclu du miroir les paquets vers le port 4789 pour ne pas copier les copies. Sauf que le second fragment n'a pas d'en-tête UDP, donc pas de port, donc il passait le filtre, était recopié dans le tunnel, refragmenté, et ainsi de suite. Résultat : 4 000 alertes « IPv4 Fragmentation overlap » toutes les trois minutes, en provenance du routeur. Correction : exclure tout l'UDP à destination de la sonde, port ou pas.

dnsmasq devenu muet. Pour envoyer les logs vers la sonde, il faut redémarrer le service de log d'OpenWrt. Mais dnsmasq tourne dans une jail et garde le socket de l'ancien démon. Il continue à tourner, il continue à résoudre, et plus une seule requête DNS n'est journalisée nulle part. Rien ne le dit. Un dnsmasq restart après le log restart, et c'est reparti.

Le nom de l'interface. Sur OpenWrt, une interface uci nommée vxprobe avec le protocole vxlan donne un device noyau qui s'appelle... vxprobe. Pas vxlan-vxprobe comme pour les autres protocoles tunnel. Vingt minutes de « Cannot find device » pour ça. Le script lit maintenant le nom dans ifstatus au lieu de le deviner.

Petits bonus : OpenWrt ne charge pas les modules act_mirred et cls_matchall tout seul (tc répond « bad action parsing », pas très parlant), netifd ne connaît le protocole vxlan qu'après un restart du réseau, et scp vers un routeur demande -O parce qu'il n'y a pas de serveur SFTP.

Et maintenant

Une alerte quand la sonde se tait, un dashboard « un appareil = une ligne » avec les nouveaux domaines jamais vus, et surtout des décisions : la TV va perdre son accès à 8.8.8.8 et aux régies publicitaires par une règle sur le routeur, les caméras vont être cantonnées à un VLAN sans Internet dès que je m'y mets, et la prise... la prise peut continuer à dire bonjour à TP-Link.

Le vrai gain, ce n'est pas l'IDS. C'est de savoir. Trois soirs de logs suffisent pour avoir une ligne de base par appareil, et tout ce qui en sort devient une question à poser.

La config complète (compose, filebeat, scripts routeur, playbook Ansible pour Docker) est dans mon git d'infra, et le README de la stack reprend les pièges ci-dessus en version dépannage. Si vous montez la même chose sur un autre routeur, dites-moi ce qui a changé de votre côté, ça m'intéresse.