IMA3/IMA4 2019/2021 P1
Sommaire
Introduction
L'idée était d'avoir un serveur de temps pour synchroniser l'heure sur les stations de travail sans avoir à se reposer sur un serveur NTP d'Internet.
Actuellement des systèmes embarqués sont vendus sur divers sites à bas prix. Ce matériel est désigné sous une appellation à rallonge : "Network Time Server NTP Time Server for GPS Beidou GLONASS Galileo QZSS Desktop Version". Son manuel le désigne sous la référence FC-NTP-100.
A priori ce matériel arrive prêt à l'emploi mais sa configuration IP nécessite l'utilisation d'un logiciel windows donc graphique. C'est assez peu pratique quand vous le connectez sur un VLAN de serveurs sur lequel vous n'avez aucune station de travail et aucun serveur sous windows. La configuration du système embarqué s'est donc transformé en un exercice d'ingénierie inverse et de codage réseau.
Matériel
Le système embarqué est livré avec un adapteur secteur non française, de plus il est assez compliqué d'accumuler les adaptateurs dans une baie. Un boitier d'alimentation 12V a été acquis pour l'alimentation. Ce type de boitier est plutôt destiné à alimenter des caméras mais convient très bien pour le système embarqué NTP. Le boitier d'alimentation se compose d'un transformateur utilisable au travers de fusibles et de borniers. Pour le modèle acheté 4 borniers sont disponibles et se partagent les 5A de l'alimentation.
Installation
L'installation physique est assez triviale. Pour éviter de poser des éléments sur des tablettes dans la baie, l'alimentation et le système embarqué sont vissés sur le mur. Le cordon de l'alimentation a été récupéré et connecté aux borniers. Le système embarqué est relié à un commutateur via une jarretière RJ45. La LED du port du commutateur s'allume prouvant la présence d'une porteuse Ethernet. Par contre la commande ci-dessous montre qu'aucun paquet Ethernet n'est envoyé par le périphérique.
#show mac address-table interface gigabitEthernet 0/44 Mac Address Table ------------------------------------------- Vlan Mac Address Type Ports ---- ----------- -------- -----
L'antenne est positionnée près d'une fenêtre. La longueur du câble est largement suffisante pour ce faire.
Configuration
C'est au niveau de la configuration que cela se corse.
Le système embarqué sort d'usine avec comme adresse IPv4 192.168.0.100. Pour changer cette adresse, une application windows est fournie.
Pas de port série sur la version bon marché du système embarqué NTP (FC-NTP-100). Une documentation fait état d'une autre version du système embarqué avec une connexion série bizarrement accessible sur une broche DB15.
Comme décrit dans l'introduction le but est d'écrire une application Linux texte avec les mêmes fonctionnalités que l'application graphique. Pour cela le principal est d'arriver à trouver le protocole utilisé par le périphérique.
Ingénierie inverse
C'est aussi simple que de lancer l'application windows sous wine, de lancer un tcpdump dans une console et de cliquer sur tous les boutons de l'application graphique.
Il est possible de compliquer un peu et de tenter une partie de l'ingénierie inverse sans le système embarqué en ligne. Dans ce cas ajouter une entrée dans la table ARP :
# arp -s 192.168.0.100 00:11:22:33:44:55
Après ça si vous appuyez sur le bouton NET Connect puis sur le bouton Query, la console vous indiquera :
# tcpdump -vvv -n -Xe host 192.168.0.100 19:46:37.410925 24:77:03:24:8e:30 > 00:11:22:33:44:55, ethertype IPv4 (0x0800), length 49: (tos 0x0, ttl 64, id 26566, offset 0, flags [none], proto UDP (17), length 35) 192.168.0.101.58025 > 192.168.0.100.23: [udp sum ok] UDP, length 7 0x0000: 4500 0023 67c6 0000 4011 90ea c0a8 0065 E..#g...@......e 0x0010: c0a8 0064 e2a9 0017 000f 12a7 2410 2a31 ...d........$.*1 0x0020: 300d 0a
Nous savons donc que la communication avec le périphérique se fait en UDP sur le port 23. Vérifions en changeant le filtre de tcpdump et en appuyant sur le bouton Search :
# tcpdump -vvv -n -Xe port 23 20:55:37.788646 24:77:03:24:8e:30 > ff:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 49: (tos 0x0, ttl 64, id 26376, offset 0, flags [none], proto UDP (17), length 35) 192.168.0.101.35413 > 255.255.255.255.23: [udp sum ok] UDP, length 7 0x0000: 4500 0023 6708 0000 4011 52b5 c0a8 0065 E..#g...@.R....e 0x0010: ffff ffff 8a55 0017 000f 19fd 241b 2a31 .....U......$.*1 0x0020: 420d 0a
Bien vu, la recherche du périphérique, si jamais son adresse IPv4 est inconnue, se fait avec une simple diffusion UDP encore sur le port 23.
Quelques essais supplémentaires en renseignant divers champs dans l'application graphique puis en appuyant sur les boutons Set correspondants (pour le type d'adresse IP, pour l'adresse IP et pour le masque réseau) :
# tcpdump -vvv -n -Xe port 23 21:44:26.529704 24:77:03:24:8e:30 > ff:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 6680, offset 0, flags [none], proto UDP (17), length 36) 192.168.0.101.35413 > 255.255.255.255.23: [udp sum ok] UDP, length 8 0x0000: 4500 0024 1a18 0000 4011 9fa4 c0a8 0065 E..$....@......e 0x0010: ffff ffff 8a55 0017 0010 52f0 2400 002a .....U....R.$..* 0x0020: 3030 0d0a 00.. 21:44:40.843945 24:77:03:24:8e:30 > ff:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 53: (tos 0x0, ttl 64, id 7396, offset 0, flags [none], proto UDP (17), length 39) 192.168.0.101.35413 > 255.255.255.255.23: [udp sum ok] UDP, length 11 0x0000: 4500 0027 1ce4 0000 4011 9cd5 c0a8 0065 E..'....@......e 0x0010: ffff ffff 8a55 0017 0013 5703 2401 c0a8 .....U....W.$... 0x0020: 0064 2a30 440d 0a .d*0D.. 21:44:49.432484 24:77:03:24:8e:30 > ff:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 53: (tos 0x0, ttl 64, id 8621, offset 0, flags [none], proto UDP (17), length 39) 192.168.0.101.35413 > 255.255.255.255.23: [udp sum ok] UDP, length 11 0x0000: 4500 0027 21ad 0000 4011 980c c0a8 0065 E..'!...@......e 0x0010: ffff ffff 8a55 0017 0013 181e 2402 ffff .....U......$... 0x0020: fff0 2a30 440d 0a
Pour pouvoir cerner le protocole il faut obtenir une réponse du périphérique. Là pas d'autre solution que de connecter le système embarqué NTP sur le même réseau que la station de travail. Essayons de modifier le type de l'adresse IP, voila ce qui arrive sur la sortie standard de tcpdump :
# tcpdump -vvv -n -Xe port 23 22:21:39.072053 00:16:3e:aa:10:56 > d8:b0:4c:ff:01:76, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 20379, offset 0, flags [DF], proto UDP (17), length 36) 172.26.189.7.42350 > 172.26.191.100.23: [bad udp cksum 0xd4c2 -> 0x2443!] UDP, length 8 0x0000: 4500 0024 4f9b 4000 4011 168d ac1a bd07 E..$O.@.@....... 0x0010: ac1a bf64 a56e 0017 0010 d4c2 2400 002a ...d.n......$..* 0x0020: 3030 0d0a 00.. 22:21:39.097510 d8:b0:4c:ff:01:76 > 00:16:3e:aa:10:56, ethertype IPv4 (0x0800), length 47: (tos 0x0, ttl 255, id 42738, offset 0, flags [none], proto UDP (17), length 33) 172.26.191.100.23 > 172.26.189.7.42350: [udp sum ok] UDP, length 5 0x0000: 4500 0021 a6f2 0000 ff11 4038 ac1a bf64 E..!......@8...d 0x0010: ac1a bd07 0017 a56e 000d 57a0 2400 000d .......n..W.$... 0x0020: 0a
Le protocole applicatif
Regardons d'un peu plus près les données UDP envoyées par l'application. Déjà toutes ces données commencent par la valeur 0x25 soit le caractère $. L'octet suivant semble correspondre au type de données envoyées (0x00 pour le type d'adresse, 0x01 pour l'adresse IPv4, 0x02 pour le masque réseau, etc). Suivent les octets de données (1 octet pour le type, 4 octets pour l'adresse IPv4 et le masque réseau, etc). Suit un octet de séparation 0x2a soit le caractère *. Les deux derniers octets semble bien être un saut de ligne à la mode windows soit 0x0d et 0x0a (CR et LF). Restent les deux octets entre le caractère de séparation *. Sur les différents exemples ces deux octets semblent devoir être lus en ASCII et semblent être la représentation en hexadécimal d'un octet.
Après quelques essais et erreurs cet octet se révèle être une somme de contrôle basique sur les octets entre les caractères $ et * non inclus. La somme est calculée par le simple opérateur ou exclusif bit à bit. Les différents essais ont été réalisés sur l'envoi de l'adresse IPv4 en commençant par mettre tous les bits à zéro (résultat 00) puis en modifiant bit à bit le 0.0.0.0 initial.
Pour les demandes d'information le format semble être le même (voir le premier paquet des tests) mais sans octets de données et avec un code correspondant au type de l'information auquel on applique un ou bit à bit avec 0x10.
Donc pour les requêtes au périphérique le format est synthétisé dans les tableaux ci-dessous :
Modification de configuration | |||||||||
---|---|---|---|---|---|---|---|---|---|
1 octet | 1 octet | n octet(s) | 1 octet | 2 octets | 2 octets | ||||
'$' | Type de configuration | Données | '*' | Somme de contrôle | Saut de ligne | ||||
0x24 | T | D1 ... Dn | 0x2a | T ^ D1 ^ ... ^ Dn | 0x0d 0x0a |
Lecture de configuration | ||||||
---|---|---|---|---|---|---|
1 octet | 1 octet | 1 octet | 2 octets | 2 octets | ||
'$' | Type de configuration | '*' | Somme de contrôle | Saut de ligne | ||
0x24 | T | 0x10 | 0x2a | T | 0x10 | 0x0d 0x0a |
Notons que ce protocole est loin d'être optimal :
- les caractères de séparation $ et * sont loin d'être indispensables ;
- le saut de ligne final à la microsoft est totalement inutile ;
- l'envoi de la somme de contrôle en ASCII est une aberration ;
- la somme de contrôle pour la récupération d'information ne porte que sur un octet ...
Regardons les réponses du périphérique aux requêtes de l'application. En réponse à une modification le type de la configuration à modifier est retournée puis un octet à zéro. Il est possible que si un nombre non correct d'octets de données est envoyé le zéro se transforme en autre chose mais nous ne comptons pas faire d'erreur. En réponse à une lecture le type est retourné suivi des données mais cette fois pas de somme de contrôle. Probablement parce que s'il est indispensable de vérifier la configuration à appliquer, afficher une configuration erronée n'est pas trop grave.
La synthèse :
Réponse à une modification | ||||
---|---|---|---|---|
1 octet | 1 octet | 1 octet | 2 octets | |
'$' | Type de configuration | Code réponse | Saut de ligne | |
0x24 | T | R | 0x0d 0x0a |
Réponse à une lecture | |||||||
---|---|---|---|---|---|---|---|
1 octet | 1 octet | n octet(s) | 2 octets | ||||
'$' | Type de configuration | Données | Saut de ligne | ||||
0x24 | T | D1 ... Dn | 0x0d 0x0a |
Les types de configuration identifiées sont les suivantes :
Code | Type de configuration |
---|---|
0x00 | Type d'IPv4 (statique ou dynamique) |
0x01 | Adresse IPv4 |
0x02 | Masque réseau |
0x03 | Passerelle |
0x04 | Adresse Ethernet |
0x07 | Mode NTP (serveur ou diffusion) |
0x0a | Délai entre diffusion NTP |
0x0b | Recherche de périphérique |
Le dernier code ne correspond pas à une configuration mais sert pour la recherche de périphérique. Il est inclus dans le tableau pour avoir une liste complète des codes.
A noter que certains codes non listés permettent d'obtenir une réponse du périphérique mais cette réponse contient des données à 0xff que nous n'avons pas réussi à identifier.
Programmation
Une fois le protocole applicatif trouvé, l'écriture du programme est une simple formalité. Le code est donné en fin de page.
Quatre fonctions implantent très classiquement l'aspect programmation réseau UDP :
- initialisationSocketUDP initialise la socket UDP ;
- libereSocketUDP libère la réservation mémoire effectuée par la fonction précédente ;
- boucleServeurUDP gère les paquets UDP envoyé par le périphérique ;
- messageUDP permet d'envoyer des paquets UDP.
Les deux fonctions suivantes permettent la manipulation des données UDP suivant le protocole découvert dans la section précédente :
- fabricationMessage assemble un message UDP ;
- lectureMessage scanne un message UDP.
Le programme principal tente de rendre le programme le plus ergonomique possible :
- l'utilisation d'une option inconnue permet d'afficher une rapide explication des options possibles ;
- si l'adresse IPv4 du périphérique n'est pas donnée en premier argument une recherche des périphériques est effectué par diffusion UDP, si une réponse est reçue l'adresse IPv4 inclue est utilisée comme adresse du périphérique ;
- si aucune option n'est spécifiée pour modifier une configuration, toutes les configurations connues sont demandées au périphérique et affichées.
Ainsi si le programme est lancé sans aucun paramètre, une découverte de périphérique est lancée et ses configurations sont affichées.