Administration Linux

Réseau sous Linux : ip, routage, DNS, sockets, pare-feu et tunnels SSH

Intermédiaire10 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Si les problèmes réseau prennent autant de temps, c'est qu'on les diagnostique presque toujours dans le mauvais ordre. Ce guide parcourt la pile comme le fait un paquet — interface, adresse, route, nom, port — et donne pour chaque couche la commande moderne qui permet de l'inspecter. Il aborde ensuite les pare-feu avec nftables et ses front ends, la valeur diagnostique de curl et de dig, et les techniques de tunnel SSH qui vous font passer par un bastion.

Ce que vous allez apprendre

  • Inspecter et configurer interfaces, adresses et routes avec la commande ip
  • Expliquer comment un nom devient une adresse et déboguer la résolution avec dig et resolvectl
  • Lister les sockets en écoute et établis avec ss et remonter jusqu'au processus propriétaire
  • Écrire et inspecter des règles de pare-feu avec nftables, ufw ou firewalld
  • Isoler une panne de connectivité couche par couche et atteindre un service privé via un tunnel SSH

La plupart des questions de réseau sous Linux se ramènent à l'une de cinq causes : l'interface n'a pas d'adresse, il n'existe pas de route vers la destination, le nom ne se résout pas, rien n'écoute sur le port, ou un pare-feu jette le paquet. Connaître cette liste, avec une commande pour chaque point, transforme un après-midi de tâtonnements en une vérification de cinq minutes.

Les outils ont eux aussi changé. ifconfig, netstat, route et arp venaient du paquet net-tools, qui n'est plus maintenu et ne figure plus dans les installations par défaut ; ip et ss, issus d'iproute2, les ont remplacés et montrent des choses que les anciens outils ne voient pas. Ce guide utilise les commandes modernes, cite une fois les anciens noms pour que vous puissiez lire la documentation plus ancienne, et suppose un hôte récent sous Debian, Ubuntu ou une distribution de la famille RHEL.

Les couches que vous manipulez réellement

Inutile de maîtriser tout le modèle en sept couches pour être efficace, mais quatre niveaux reviennent sans cesse dans le diagnostic quotidien :

  1. Liaison — une interface réseau dotée d'une adresse MAC, connectée à un segment. C'est la couche ip link. ARP (ip neigh) fait correspondre adresses IP et adresses MAC à l'intérieur d'un segment.
  2. Réseau — adresses IP et routes. Acheminer un paquet de cet hôte vers une destination, c'est l'affaire d'ip addr et d'ip route.
  3. Transport — ports TCP et UDP, connexions, sockets en écoute. C'est la couche ss.
  4. Application — HTTP, DNS, TLS. curl, dig, openssl s_client.

Une adresse s'écrit avec une longueur de préfixe : 10.0.3.17/24 signifie que les 24 premiers bits identifient le réseau, donc que les adresses de 10.0.3.0 à 10.0.3.255 sont locales et que tout le reste doit passer par un routeur. Se tromper sur ce nombre produit ce symptôme exaspérant où certaines destinations répondent et d'autres non.

Trois plages privées — 10.0.0.0/8, 172.16.0.0/12 et 192.168.0.0/16 — sont réservées à l'usage interne et ne sont jamais routées sur l'Internet public, ce qui explique pourquoi les VPC cloud et les réseaux domestiques se ressemblent tous. 127.0.0.0/8 est la boucle locale, entièrement interne au noyau. Les adresses IPv6 cohabitent avec IPv4 sur la plupart des réseaux modernes ; les adresses fe80::/10 sont link-local, valables sur un seul segment, et portent toujours un suffixe %interface quand vous les utilisez.

Interfaces, adresses et routes

$ ip -brief addr
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             10.0.3.17/24 fe80::5054:ff:fe12:3456/64

$ ip route
default via 10.0.3.1 dev eth0 proto dhcp src 10.0.3.17 metric 100
10.0.3.0/24 dev eth0 proto kernel scope link src 10.0.3.17

L'option -brief donne la vue résumée ; ip addr seul ajoute la MTU, les flags et les durées de validité. Lisez la table de routage du préfixe le plus spécifique au moins spécifique : le noyau retient le préfixe correspondant le plus long, si bien que 10.0.3.0/24 prend en charge le segment local et que default attrape tout le reste en l'envoyant à la passerelle 10.0.3.1.

ip route get répond directement à la question et vous évite de plisser les yeux :

$ ip route get 93.184.216.34
93.184.216.34 via 10.0.3.1 dev eth0 src 10.0.3.17 uid 1000

Les modifications faites avec ip sont immédiates et perdues au redémarrage, ce qui les rend idéales pour tester et inadaptées à la configuration :

sudo ip link set eth0 up
sudo ip addr add 10.0.3.200/24 dev eth0
sudo ip route add 10.50.0.0/16 via 10.0.3.254
sudo ip addr flush dev eth0            # careful: this drops your SSH session

La configuration persistante relève du gestionnaire qu'utilise la distribution : fichiers YAML netplan dans /etc/netplan/ sur Ubuntu Server, profils de connexion NetworkManager (nmcli) sur les postes et serveurs Fedora et de la famille RHEL, ou fichiers .network de systemd-networkd sur les images minimales.

sudo netplan try                    # applies with an automatic rollback after 120 s
nmcli con mod eth0 ipv4.addresses 10.0.3.17/24 ipv4.gateway 10.0.3.1 ipv4.method manual
nmcli con up eth0

netplan try et le rollback de NetworkManager existent parce qu'une mauvaise modification d'adresse sur un hôte distant vous en ferme la porte. Servez-vous-en, ou programmez un redémarrage avant d'effectuer le changement.

Résolution de noms

Les programmes n'appellent pas le DNS directement. Ils appellent le resolver de la bibliothèque C, qui consulte /etc/nsswitch.conf pour décider de l'ordre des sources — en général files (c'est-à-dire /etc/hosts), puis un stub resolver local, puis le DNS. /etc/hosts l'emporte donc sur le DNS, ce qui en fait à la fois un moyen pratique de forcer une résolution et une cause fréquente de « ça ne marche que sur ce serveur-là ».

Sur les systèmes qui utilisent systemd-resolved — le choix par défaut sur Ubuntu, et de plus en plus ailleurs —, /etc/resolv.conf est un lien symbolique vers un fichier stub qui indique 127.0.0.53. Le modifier à la main ne sert à rien : il est régénéré. Interrogez plutôt la configuration réelle :

$ resolvectl status | sed -n '1,12p'
Global
       Protocols: LLMNR=resolve -mDNS +DNSOverTLS DNSSEC=no/unsupported
Link 2 (eth0)
Current Scopes: DNS
     DNS Servers: 10.0.0.2 10.0.0.3
      DNS Domain: internal.example.com

$ resolvectl query api.internal.example.com
api.internal.example.com: 10.0.4.21

dig est l'outil pour interroger le DNS lui-même, en contournant complètement la bibliothèque :

dig +short api.example.com            # just the answer
dig api.example.com A +noall +answer  # the answer section with TTLs
dig @1.1.1.1 example.com MX           # ask a specific server
dig +trace example.com                # follow delegation from the root
dig -x 93.184.216.34                  # reverse lookup

La distinction faite dans la FAQ ci-dessus mérite d'être bien assimilée : dig vous dit ce que répond le DNS, getent hosts name vous dit ce qu'obtiendront vos applications. Quand les deux divergent, la réponse se trouve dans /etc/hosts, nsswitch.conf ou le stub resolver, pas dans la zone.

Le cache ajoute sa propre dose de confusion. Le TTL d'un enregistrement fixe combien de temps les resolvers peuvent conserver l'ancienne réponse ; une modification qui « ne s'est pas encore propagée » n'est donc le plus souvent qu'un TTL pas encore expiré. Videz le cache local avec sudo resolvectl flush-caches et interrogez le serveur faisant autorité avec dig @ns1.example.com avant de conclure que quelque chose est cassé.

Ports et sockets

ss remplace netstat et se montre bien plus rapide sur les hôtes chargés. Les options à retenir par cœur sont -l (en écoute), -t/-u (TCP/UDP), -n (numérique, sans résolution de noms) et -p (processus propriétaire, nécessite root).

$ sudo ss -ltnp
State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
LISTEN 0      4096        127.0.0.1:5432       0.0.0.0:*     users:(("postgres",pid=1842,fd=5))
LISTEN 0      511           0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1201,fd=6))
LISTEN 0      128           0.0.0.0:22         0.0.0.0:*     users:(("sshd",pid=980,fd=3))

La colonne de l'adresse locale est la première chose à lire quand un service est injoignable. 127.0.0.1:5432 n'accepte que les connexions venant du même hôte ; 0.0.0.0:80 accepte n'importe quelle provenance sur n'importe quelle interface ; [::]:80 est l'équivalent IPv6, et sous Linux un socket lié à [::] accepte généralement aussi les connexions IPv4. Lier un service à la boucle locale puis le placer derrière un reverse proxy est un bon réflexe par défaut, et c'est exactement ce que fait le guide de la pile LAMP avec PHP-FPM.

Autres formes utiles :

ss -tan state established             # current TCP connections
ss -tn dst 10.0.4.21                  # connections to one host
ss -s                                 # summary counters by protocol
ss -tlnp 'sport = :8080'              # who owns port 8080

Sur un socket en écoute, Recv-Q indique le nombre de connexions en attente d'acceptation ; une valeur durablement non nulle signifie que l'application n'accepte pas assez vite. Sur un socket établi, les files montrent les octets non lus et non acquittés, ce qui permet de repérer un lecteur bloqué.

Les ports inférieurs à 1024 sont privilégiés : seul root, ou un processus doté de CAP_NET_BIND_SERVICE, peut s'y lier. C'est pourquoi les serveurs d'applications écoutent sur 8080 et qu'un proxy ou une unité socket systemd prend en charge 80 et 443.

Pare-feu

Les noyaux modernes filtrent avec nftables, qui a remplacé iptables. La plupart des administrateurs le pilotent au travers d'un front end.

ufw (Debian et Ubuntu) est volontairement minimaliste :

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH                 # named application profile
sudo ufw allow 80,443/tcp
sudo ufw allow from 10.0.0.0/8 to any port 5432 proto tcp
sudo ufw enable
sudo ufw status verbose

firewalld (famille RHEL) organise les règles en zones, chacune s'appliquant à un ensemble d'interfaces ou de sources :

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

L'option --permanent écrit la règle sur disque sans l'appliquer ; sans elle, la règle est appliquée sans être sauvegardée. Faire les deux, puis --reload, est la séquence fiable — et la forme runtime seule est un vrai garde-fou quand vous modifiez des règles via SSH, puisqu'un redémarrage annule l'erreur.

Même si vous passez par un front end, il vaut la peine de comprendre comment on écrit directement des règles nftables :

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    ip protocol icmp accept
    tcp dport { 22, 80, 443 } accept
    tcp dport 5432 ip saddr 10.0.0.0/8 accept
  }
}

Lisez-le de haut en bas : par défaut, tout est jeté ; les réponses aux connexions initiées par cet hôte sont acceptées grâce au suivi de connexions ; la boucle locale n'est pas restreinte ; et trois ports, plus un port de base de données limité à certaines sources, sont ouverts. sudo nft list ruleset affiche ce qui est chargé, et /etc/nftables.conf associé à systemctl enable nftables rend la configuration persistante.

Deux mises en garde. D'abord, autorisez toujours SSH avant d'appliquer une politique de refus par défaut, et gardez une seconde session ouverte. Ensuite, dans un environnement cloud, il existe un second pare-feu — un security group ou une ACL réseau — entièrement extérieur à la machine ; un paquet bloqué à ce niveau n'atteint jamais l'hôte, et rien dans nft list ruleset ne vous le laissera deviner.

Diagnostiquer de l'extérieur vers l'intérieur

ping teste l'accessibilité ICMP, que beaucoup de réseaux et la plupart des load balancers cloud bloquent délibérément : un ping qui échoue ne prouve donc pas grand-chose. Ces commandes-ci en disent davantage :

curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/health
curl -v --connect-timeout 3 https://api.internal:8443/status
curl --resolve api.example.com:443:10.0.4.21 https://api.example.com/   # bypass DNS, test one backend
openssl s_client -connect example.com:443 -servername example.com </dev/null | head -20
mtr -rwc 20 10.0.4.21                 # traceroute plus per-hop loss, over 20 cycles
tcpdump -ni eth0 'tcp port 5432 and host 10.0.4.21'

curl -v est l'outil de diagnostic le plus rentable de cette liste, parce qu'il raconte chaque étape : résolution DNS, connexion TCP, handshake TLS avec la chaîne de certificats, requête, en-têtes de réponse. L'endroit où il s'arrête vous indique la couche en défaut. Un blocage sur « Trying 10.0.4.21:443 » trahit un pare-feu qui jette les paquets en silence ; un « Connection refused » immédiat signifie que le paquet est arrivé mais que rien n'écoutait — deux problèmes très différents derrière le même symptôme côté utilisateur.

tcpdump est le dernier recours, et le plus définitif : si vous voyez partir le SYN sans qu'aucun SYN-ACK ne revienne, le problème se situe au-delà de cet hôte. Enregistrez la capture dans un fichier avec -w capture.pcap quand vous devez l'examiner en détail.

SSH comme plomberie réseau

Au-delà des connexions interactives, SSH redirige des connexions TCP : c'est ainsi que l'on atteint une base de données ou une interface d'administration qui n'est pas exposée publiquement.

# Local forward: localhost:15432 on my laptop -> db.internal:5432, via the bastion
ssh -N -L 15432:db.internal:5432 [email protected]
psql -h 127.0.0.1 -p 15432 -U app orders

# SOCKS proxy: a browser can then reach anything the bastion can
ssh -N -D 1080 [email protected]

# Jump host in one hop, without an agent on the bastion
ssh -J [email protected] [email protected]

-N signifie « pas de commande distante » : vous obtenez un tunnel, sans shell. Inscrivez les détails une fois pour toutes dans ~/.ssh/config et la complexité disparaît :

Host bastion
  HostName bastion.example.com
  User alice
  IdentityFile ~/.ssh/id_ed25519

Host db
  HostName db.internal
  User alice
  ProxyJump bastion
  LocalForward 15432 db.internal:5432

L'agent forwarding (-A) est pratique mais risqué : root sur l'hôte intermédiaire peut utiliser votre agent pour s'authentifier ailleurs en votre nom. ProxyJump obtient le même résultat sans exposer l'agent, et devrait être le choix par défaut.

Un ordre des opérations

Quand quelque chose n'arrive pas à joindre autre chose, progressez vers l'extérieur et arrêtez-vous au premier échec :

  1. Le service écoute-t-il, et sur quoi ? sudo ss -ltnp | grep :<port>.
  2. Répond-il en local ? curl -v http://127.0.0.1:<port>/. Sinon, le problème vient de l'application — consultez ses logs avec journalctl -u, comme expliqué dans processus et systemd.
  3. Le nom se résout-il, et vers la bonne adresse ? getent hosts <name> et dig +short <name>.
  4. Existe-t-il une route ? ip route get <address>.
  5. Le pare-feu de l'hôte l'autorise-t-il ? sudo nft list ruleset, ufw status, firewall-cmd --list-all.
  6. Quelque chose en dehors de l'hôte le bloque-t-il ? Security groups cloud, ACL réseau, proxy d'entreprise. Testez depuis une troisième machine du même segment pour distinguer les règles de l'hôte de celles du réseau.
  7. Que disent les paquets ? tcpdump aux deux extrémités, si vous en êtes arrivé là.

Erreurs fréquentes

  • Lier le service à 127.0.0.1 et s'attendre à un accès distant. Vérifiez l'adresse locale dans ss avant de toucher au pare-feu.
  • Modifier /etc/resolv.conf alors que systemd-resolved le gère. Le fichier est régénéré ; configurez plutôt le resolver.
  • Se fier à ping pour tester l'accessibilité. ICMP est filtré dans de nombreux environnements ; testez le port réel avec curl ou nc -vz.
  • Appliquer une politique de pare-feu à refus par défaut sans règle SSH. Ajoutez d'abord la règle d'autorisation, puis la politique, et gardez une seconde session ouverte.
  • Configurer une adresse avec ip addr add et considérer que c'est terminé. Elle disparaît au démarrage suivant ; inscrivez-la dans netplan, NetworkManager ou systemd-networkd.
  • Oublier le pare-feu côté cloud. La moitié des tickets « le port est ouvert mais je ne peux pas me connecter » se terminent sur un security group.
  • Ignorer IPv6. Un nom doté d'enregistrements A et AAAA peut être joint en IPv6, où vos règles de pare-feu ou votre routage peuvent être tout autres. Testez séparément avec curl -4 et curl -6.

Points clés à retenir

  • Diagnostiquez dans l'ordre des couches — interface, adresse, route, nom, port, pare-feu — et arrêtez-vous au premier élément qui échoue.
  • ip addr, ip route et ip route get remplacent ifconfig et route ; les modifications faites avec ip sont immédiates et, par conception, non persistantes.
  • La résolution passe par nsswitch.conf et éventuellement par un stub resolver : getent hosts reflète donc ce que voient les applications, tandis que dig reflète ce que dit le DNS.
  • ss -ltnp et sa colonne d'adresse locale règlent la plupart des cas de « service injoignable » avant même que le pare-feu n'entre en jeu.
  • nftables est le filtre du noyau ; ufw et firewalld en sont des front ends pratiques, et un security group cloud constitue un second pare-feu, invisible.
  • curl -v raconte DNS, TCP, TLS et HTTP en une seule commande, et tcpdump tranche les débats sur la question de savoir si les paquets sont vraiment partis.
  • ssh -L, -D et ProxyJump, inscrits une fois pour toutes dans ~/.ssh/config, vous donnent un accès sûr aux services privés sans les exposer.

Sources et lectures complémentaires

Questions fréquentes

Pourquoi ifconfig est-il absent de mon serveur ?
Le paquet net-tools, qui fournissait ifconfig, netstat, route et arp, n'est plus maintenu depuis des années et n'est plus installé par défaut. Ses remplaçants viennent d'iproute2 : ip addr pour ifconfig, ip route pour route, ip neigh pour arp et ss pour netstat. Ils gèrent des fonctionnalités que net-tools n'a jamais connues, comme plusieurs adresses par interface ou le routage par politique : apprenez les nouveaux noms plutôt que d'installer l'ancien paquet.
Le service tourne mais je n'arrive pas à le joindre depuis une autre machine. Par où commencer ?
Vérifiez sur quoi il écoute avant d'accuser le réseau. Lancez ss -ltnp et regardez la colonne de l'adresse locale : 127.0.0.1:8080 n'accepte que les connexions locales, alors que 0.0.0.0:8080 les accepte toutes. Si le binding est correct, testez avec curl depuis le serveur lui-même, puis depuis l'extérieur, puis vérifiez le pare-feu de l'hôte, et enfin l'éventuel security group du cloud. En procédant de l'intérieur vers l'extérieur dans cet ordre, vous isolez la couche fautive en quelques minutes.
Pourquoi dig renvoie-t-il une réponse alors que ping n'arrive pas à résoudre le même nom ?
dig interroge directement un serveur DNS et ignore le reste de la mécanique de résolution. Les programmes ordinaires passent par la bibliothèque C, qui consulte /etc/nsswitch.conf et donc /etc/hosts, mDNS et un éventuel stub resolver local, avant le DNS ou à sa place. Utilisez getent hosts nom pour reproduire exactement ce que voit une application : si les deux diffèrent, le problème est dans la configuration, pas dans le DNS.
Quelle est la différence entre redirection de port SSH locale et distante ?
La redirection locale, ssh -L, ouvre un port sur votre machine et fait passer les connexions, via le serveur SSH, vers une destination que ce serveur peut atteindre — c'est la méthode habituelle pour joindre une base de données qui n'écoute que sur un réseau privé. La redirection distante, ssh -R, ouvre un port sur le serveur SSH et renvoie les connexions vers votre machine, ce qui expose un service local au côté distant. Dans presque tous les cas, c'est la redirection locale qu'il vous faut.