Chapitre 03
Découvrir qui est là, sans IP
Objectifs du chapitre
- Cartographier une cellule en couche 2, avant même de connaître ou de configurer le moindre plan d'adressage IP.
- Interroger le voisinage LLDP avec
lldpctlpour reconstruire la topologie réelle : quel équipement sur quel port de quel switch. - Utiliser le Profinet DCP (via
pnio-dcp) pour faire répondre tous les devices — nom de station, IP, MAC — sans être sur le bon réseau, et faire clignoter une LED pour repérer un équipement physiquement. - Compléter l'inventaire avec la table des voisins (
ip neigh) et arp-scan, en reconnaissant les équipements Siemens à leur OUI.
1. Pourquoi la découverte L2 d'abord
En OT, on arrive rarement dans un réseau documenté. On branche un câble sur un port libre d'un switch
d'armoire et… on ne connaît ni le plan d'adressage, ni les IP des automates, ni même le masque du
sous-réseau. Les réflexes IT (« je scanne le /24 ») supposent déjà de savoir quel
/24. Or ici, on ne le sait pas encore.
La bonne nouvelle : les équipements d'automatisme parlent avant même d'avoir une IP compatible avec la tienne. Deux protocoles de couche 2 le permettent :
- LLDP (Link Layer Discovery Protocol) — chaque switch et device annonce périodiquement son identité à ses voisins directs. On écoute, on obtient la topologie.
- Profinet DCP (Discovery and Configuration Protocol) — un appel multicast fait répondre tous les devices Profinet du segment avec leur nom de station et leur config IP, qu'ils soient ou non joignables en IP.
Ces deux moyens sont aussi les plus non-intrusifs qui soient : LLDP est purement
passif (on lit ce qui passe déjà), et un DCP Identify ne fait qu'une question à laquelle les
devices répondent de toute façon en fonctionnement normal. C'est le point de départ de référence, avant
tout ping, tout scan, toute config IP.
LLDP te dit où est branché chaque équipement (topologie physique, port par port). DCP te dit qui il est (nom de station, IP, type), même sans adresse compatible. Combinés, ils reconstruisent la vue « Topologie » de TIA Portal — sauf que là, tu la mesures sur le fil au lieu de la lire dans un projet d'ingénierie qui n'est peut-être plus à jour.
2. LLDP — le voisinage
Le démon lldpd, installé au chapitre 1, tourne en tâche de fond. Il émet les
annonces LLDP de ton poste et, surtout, il collecte celles des voisins
directs : switches Scalance, CPU, IO-devices. On l'interroge avec lldpctl (ou
lldpcli show neighbors, équivalent).
$ lldpctl
-------------------------------------------------------------------------------
LLDP neighbors:
-------------------------------------------------------------------------------
Interface: eth0, via: LLDP
Chassis:
ChassisID: mac 00:1b:1b:2a:1f:40
SysName: SW-A
SysDescr: Siemens, SIMATIC NET, SCALANCE XC208, HW: 4, FW: V4.3
MgmtIP: 192.168.0.240
Capability: Bridge, on
Port:
PortID: ifname port-0003
PortDescr: Ethernet Port 3
TTL: 120
-------------------------------------------------------------------------------
Interface: eth0, via: LLDP
Chassis:
ChassisID: mac 00:1b:1b:2a:10:01
SysName: cpu1500
SysDescr: Siemens, SIMATIC S7-1500, CPU 1516-3 PN/DP
MgmtIP: 192.168.0.1
Capability: Station, on
Port:
PortID: local port-001
PortDescr: X1 P1
TTL: 20
-------------------------------------------------------------------------------
Lecture de cette sortie. Sur notre eth0, lldpd voit deux annonces : un
switch SW-A (un Scalance XC208, MAC 00:1b:1b:…, IP de gestion
.240) et le CPU cpu1500 (.1). Le champ décisif pour la
topologie est le PortID distant : le voisin nous dit sur quel port de lui-même
nous sommes branchés. Ici, SW-A annonce port-0003 — donc notre poste est câblé sur le
port 3 du switch A. En interrogeant chaque équipement de la sorte, on reconstitue
l'arbre complet : quel device sur quel port de quel switch.
lldpctl ne montre que les voisins directs (un saut). Pour la topologie
complète, on interroge chaque switch (souvent via SNMP, cf. chapitre ultérieur, ou en s'y branchant
tour à tour). Le TTL indique depuis combien de temps l'annonce est valide : une entrée qui disparaît de
la liste = un câble débranché ou un voisin qui ne parle pas LLDP.
lldpctl ci-dessus. Le
PortID distant de chaque voisin (traits) place l'équipement sur un port précis du switch. On
obtient ainsi la carte physique mesurée, sans avoir configuré la moindre IP.Le SysName annoncé par un device Siemens en LLDP est son nom de
station Profinet (ici cpu1500, et200sp-1…). Ce nom n'est pas
cosmétique : c'est la clé d'identité par laquelle le CPU reconnaît et adresse chaque IO-device
au démarrage (on y reviendra au chapitre 7). Voir ce nom sur le fil, c'est voir exactement ce que
le contrôleur utilise pour attribuer les IP.
3. Profinet DCP — « qui es-tu ? » sans IP
LLDP est passif et ne voit que les voisins directs. Le DCP va plus loin : c'est un
protocole de couche 2 (Ethertype 0x8892, comme le Profinet temps réel), diffusé en
multicast sur tout le segment. Un message spécial, Identify All, demande à
tous les devices Profinet de se présenter. Comme c'est de la couche 2 pure, ils
répondent même si leur IP est dans un autre sous-réseau que le tien — ou s'ils n'ont
aucune IP (device neuf sorti du carton).
En Python, la bibliothèque pnio-dcp (installée au chapitre 1) fait tout le travail
de trame :
import pnio_dcp
# On lie le DCP à TON interface : IP locale ou nom d'interface ("eth0").
dcp = pnio_dcp.DCP("192.168.0.71")
# "Identify All" : tous les devices Profinet du segment répondent.
for d in dcp.identify_all():
print(f"{d.name_of_station:14} {d.IPAddress:15} {d.MAC}")
Sur notre cellule, on obtient :
$ python3 dcp_identify.py
cpu1500 192.168.0.1 00:1b:1b:2a:10:01
et200sp-1 192.168.0.20 00:1b:1b:2a:10:20
et200sp-2 192.168.0.21 00:1b:1b:2a:10:21
En une requête, on a l'inventaire Profinet complet : nom de station, IP, masque, passerelle
(attributs d.NetworkMask, d.Gateway), MAC et type de device. Aucune config
préalable, aucune connaissance du plan d'adressage.
Mais le DCP ne fait pas que lire — il permet aussi d'écrire. Sur l'objet renvoyé pour un device, on dispose de :
dcp.set_ip_address(mac, ip, netmask, gateway)— attribuer une IP (temporaire ou permanente) à un device.dcp.set_name_of_station(mac, name)— donner ou changer le nom de station.dcp.blink(mac)— faire clignoter la LED du device (souvent la LED « signal/MT ») pour le repérer physiquement dans une armoire pleine.dcp.reset_to_factory(mac)— remise à zéro des paramètres.
Ce sont des outils de mise en service redoutables : donner nom et IP à un device neuf, ou faire clignoter « celui du milieu de la rangée » pour lever un doute de câblage.
Repérer physiquement un device dont on connaît la MAC (par exemple et200sp-2) :
$ python3 -c "import pnio_dcp; \
pnio_dcp.DCP('192.168.0.71').blink('00:1b:1b:2a:10:21')"
La LED du device se met à clignoter quelques secondes. Idéal pour dire « c'est celui-là » sans compter les câbles.
identify_all() et blink() ne dérangent rien : ce sont des lectures et un
voyant. Mais set_ip_address, set_name_of_station et
reset_to_factory écrivent dans le device. Renommer ou ré-adresser un
IO-device en production, c'est rompre la clé d'identité que le CPU attend : le
device sort de l'échange, l'API passe l'entrée en défaut, la machine peut s'arrêter. Ces trois appels
sont réservés au banc ou à une intervention planifiée, jamais lancés « pour voir ».
4. Capturer le DCP au vol
On n'est pas obligé d'émettre soi-même du DCP : on peut aussi écouter celui que
les autres génèrent. Dès qu'un outil d'ingénierie (TIA Portal, PRONETA, un autre poste) lance une
découverte, des trames DCP Identify et leurs réponses passent sur le fil. On les capture avec
tshark en filtrant sur l'Ethertype Profinet :
$ sudo tshark -i eth0 -f 'ether proto 0x8892' -Y pn_dcp
1 0.000000 00:1b:1b:2a:10:71 → 01:0e:cf:00:00:00 PN-DCP Ident Req, Xid:0x1, All
2 0.004120 00:1b:1b:2a:10:01 → 00:1b:1b:2a:10:71 PN-DCP Ident Ok, NameOfStation:"cpu1500"
3 0.004980 00:1b:1b:2a:10:20 → 00:1b:1b:2a:10:71 PN-DCP Ident Ok, NameOfStation:"et200sp-1"
4 0.005610 00:1b:1b:2a:10:21 → 00:1b:1b:2a:10:71 PN-DCP Ident Ok, NameOfStation:"et200sp-2"
Le filtre de capture -f 'ether proto 0x8892' ne garde que les trames
Profinet (DCP et temps réel partagent cet Ethertype), et le filtre d'affichage
-Y pn_dcp ne montre que le DCP. On voit ici une requête Identify All (destinée
à l'adresse multicast 01:0e:cf:00:00:00) suivie des trois réponses unicast. Utile pour
diagnostiquer « qui scanne le réseau », ou pour confirmer qu'un device répond bien quand notre propre
identify_all() reste muet.
DCP et Profinet RT utilisent le même Ethertype 0x8892. Le filtre de
capture ether proto 0x8892 attrape donc aussi le trafic temps réel cyclique — qui
peut être massif (une trame toutes les 1 à 4 ms par device). D'où le second filtre
-Y pn_dcp : sans lui, le DCP se noie dans le flot RT. En capture longue, préfère
écrire un .pcap puis filtrer à la lecture.
5. Table ARP / voisins
Une fois qu'un peu de trafic IP a circulé (un ping, une session), le noyau Linux garde la
correspondance IP↔MAC de ses voisins dans le cache ARP. On le lit avec ip neigh :
$ ip neigh
192.168.0.1 dev eth0 lladdr 00:1b:1b:2a:10:01 REACHABLE
192.168.0.20 dev eth0 lladdr 00:1b:1b:2a:10:20 STALE
192.168.0.240 dev eth0 lladdr 00:1b:1b:2a:1f:40 REACHABLE
192.168.0.21 dev eth0 FAILED
Les états renseignent :
- REACHABLE — voisin confirmé récemment, joignable.
- STALE — connu, mais l'entrée n'a pas été rafraîchie depuis un moment (pas forcément un problème : juste pas de trafic récent).
- FAILED — l'ARP n'a reçu aucune réponse : l'IP
.21ne répond pas (device éteint, mauvaise IP, ou pas sur ce segment).
Contrairement à LLDP/DCP, ip neigh a besoin de trafic IP pour se
remplir : c'est une photo de « avec qui j'ai parlé en IP récemment », pas une découverte active. Si
ton poste est un pont (bridge), bridge fdb show montre en plus la table d'apprentissage
MAC↔port de couche 2.
6. arp-scan — l'inventaire éclair
Pour peupler activement cet inventaire sur un sous-réseau connu, arp-scan envoie une
requête ARP à chaque adresse du subnet et liste qui répond :
$ sudo arp-scan --interface=eth0 --localnet
Interface: eth0, type: EN10MB, MAC: 00:1b:1b:2a:10:71, IPv4: 192.168.0.71
Starting arp-scan 1.10.0 with 256 hosts
192.168.0.1 00:1b:1b:2a:10:01 Siemens AG
192.168.0.20 00:1b:1b:2a:10:20 Siemens AG
192.168.0.21 00:1b:1b:2a:10:21 Siemens AG
192.168.0.240 00:1b:1b:2a:1f:40 Siemens AG
192.168.0.241 00:1b:1b:2a:1f:41 Siemens AG
192.168.0.242 00:1b:1b:2a:1f:42 Siemens AG
6 packets received by filter, 0 packets dropped
6 responded
La troisième colonne est le constructeur, déduit de l'OUI — les trois
premiers octets de la MAC, attribués par l'IEEE à chaque fabricant. Le préfixe 00:1b:1b est
un OUI Siemens : tous nos équipements ressortent instantanément comme « Siemens AG ». Siemens
possède plusieurs OUI (00:1b:1b, 8c:f3:19, ac:64:17…) ;
arp-scan les reconnaît via sa base OUI intégrée. En un scan, on distingue les automates du
reste (un PC, une imprimante, un équipement tiers ressortiraient avec un autre constructeur).
arp-scan --localnet émet 256 requêtes ARP d'un coup. Sur un banc, c'est anodin. Sur une
cellule en production, cette rafale de broadcast reste légère mais n'est pas nulle :
privilégie une plage restreinte (--interface=eth0 192.168.0.1-192.168.0.50) et évite les
scans répétés en boucle sur un réseau temps réel sensible.
Récapitulatif
- En OT, on découvre en couche 2 d'abord : LLDP et DCP répondent sans qu'on connaisse ni ne configure la moindre IP, et ce sont les moyens les plus non-intrusifs.
- LLDP (
lldpctl) donne les voisins directs : SysName (= nom de station), IP de gestion, et surtout le PortID distant qui reconstruit la topologie physique port par port. - DCP (
pnio-dcp, Ethertype0x8892) fait répondre tous les devices Profinet en Identify All (nom, IP, MAC), même hors de ton sous-réseau. Il sait aussi écrire :set_ip_address,set_name_of_station,blink,reset_to_factory— puissant mais réservé au banc. - On capture le DCP des autres avec
tshark -f 'ether proto 0x8892' -Y pn_dcp. ip neighlit le cache ARP (états REACHABLE/STALE/FAILED, nécessite du trafic IP) ;arp-scan --localnetpeuple activement l'inventaire IP+MAC+constructeur et repère les Siemens par leur OUI (00:1b:1b…).
Exercices
Exercice 1 — Reconstruire la topologie depuis LLDP
Un collègue te transmet cette sortie lldpctl prise sur son poste (branché sur
eth0). Décris précisément : quels équipements sont visibles, sur quel port de quel
switch chacun est câblé, et où se trouve le poste du collègue.
Interface: eth0, via: LLDP
Chassis: SysName: SW-B MgmtIP: 192.168.0.241 Capability: Bridge
Port: PortID: ifname port-0005
Interface: eth0, via: LLDP
Chassis: SysName: et200sp-1 MgmtIP: 192.168.0.20 Capability: Station
Port: PortID: local port-002
Voir la solution
Deux voisins directs sont visibles depuis eth0 :
- Le switch SW-B (Scalance, IP de gestion
192.168.0.241), qui annonce nous voir arriver sur son port 5 (port-0005). Autrement dit, le poste du collègue est branché sur le port 5 de SW-B. - L'IO-device et200sp-1 (
192.168.0.20, capacité « Station » = c'est un device, pas un switch), joignable directement — il partage donc le même domaine de collision/port voisin. Son PortIDport-002est son port à lui.
Topologie déduite : poste → port-5 de SW-B, et et200sp-1 voisin
direct sur ce segment. Le PortID distant d'un switch localise toujours ton
point de branchement ; le SysName + MgmtIP identifient l'équipement. En interrogeant SW-B
lui-même (SNMP), on obtiendrait la liste complète de ce qui pend à ses autres ports.
Exercice 2 — Identify All puis clignotement ciblé
Écris un court script pnio-dcp qui liste tous les devices Profinet (nom, IP, MAC) depuis
l'interface 192.168.0.71, puis fais clignoter la LED de la station nommée
et200sp-2 pour la repérer dans l'armoire.
Voir la solution
import pnio_dcp
dcp = pnio_dcp.DCP("192.168.0.71")
# 1) Inventaire complet
devices = dcp.identify_all()
for d in devices:
print(f"{d.name_of_station:14} {d.IPAddress:15} {d.MAC}")
# 2) Retrouver et200sp-2, puis faire clignoter sa LED
cible = next(d for d in devices if d.name_of_station == "et200sp-2")
dcp.blink(cible.MAC)
print(f"Clignotement de {cible.name_of_station} ({cible.MAC})…")
$ python3 exo2.py
cpu1500 192.168.0.1 00:1b:1b:2a:10:01
et200sp-1 192.168.0.20 00:1b:1b:2a:10:20
et200sp-2 192.168.0.21 00:1b:1b:2a:10:21
Clignotement de et200sp-2 (00:1b:1b:2a:10:21)…
On récupère la MAC via identify_all() plutôt que de la coder en dur : c'est plus
robuste si le device a été remplacé. blink() ne modifie rien dans le
device (juste un voyant), donc c'est sûr même en production — contrairement à
set_name_of_station.
Exercice 3 — arp-scan, OUI Siemens, et portée vs DCP
Lance un arp-scan sur ton segment, repère les équipements Siemens à leur OUI, puis
explique en quoi la portée de arp-scan diffère de celle d'un DCP
Identify All. Dans quel cas un device apparaît-il en DCP mais pas en
arp-scan ?
Voir la solution
$ sudo arp-scan --interface=eth0 --localnet
192.168.0.1 00:1b:1b:2a:10:01 Siemens AG
192.168.0.20 00:1b:1b:2a:10:20 Siemens AG
192.168.0.240 00:1b:1b:2a:1f:40 Siemens AG
...
Les équipements dont la MAC commence par 00:1b:1b (ou 8c:f3:19,
ac:64:17) sont des Siemens : la colonne constructeur, tirée de la base OUI, le dit
directement.
Différence de portée :
arp-scantravaille en IP sur le subnet local : il envoie un ARP à chaque adresse du même sous-réseau que ton interface. Un device dont l'IP est dans un autre plan (par ex.10.0.0.5alors que tu es en192.168.0.71/24) ne répondra pas à ton ARP : il est invisible pourarp-scan. De même, un device neuf sans IP configurée reste muet.- Le DCP Identify All est de la couche 2 pure (multicast Ethernet,
sans IP). Il fait répondre tout device Profinet du segment, quelle que soit son
IP — voire sans IP du tout. C'est justement pour ça qu'on l'utilise pour retrouver un
device « perdu » sur un mauvais plan d'adressage : DCP le voit,
arp-scannon.
Conclusion : un device Profinet mal adressé (IP hors subnet) ou vierge
apparaît en DCP mais pas en arp-scan. Les deux sont
complémentaires : DCP pour l'identité L2 exhaustive, arp-scan pour un inventaire IP
rapide avec constructeur une fois le plan d'adressage connu.