Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[LONG] Quelles requêtes je peux interrompre ?

4 réponses
Avatar
Olivier Miakinen
Bonjour,

Ma question du mois dernier n'ayant eu aucune réponse, je me permets
de poser celle-ci dans les groupes de gourous Linux et Windows, au
cas où s'y trouveraient des spécialistes ne lisant pas forcément
fr.comp.reseaux.ip ; mais le suivi est vers fr.comp.reseaux.ip seul.

Voilà mon problème. J'ai un programme qui fait des requêtes UDP.
Plus précisément c'est du SNMP, mais ça n'a pas d'importance pour
ma question. Contrairement à TCP qui est un protocole connecté,
on peut envoyer des paquets UDP alors que le destinataire n'est
pas accessible, et l'expéditeur n'est même pas mis au courant.

La seule solution pour éviter d'attendre trop longtemps, c'est de
se mettre à l'écoute des paquets ICMP 'UNREACHABLE' et d'essayer
de reconnaître dedans les caractéristiques du paquet UDP qu'on
avait envoyé : l'adresse de destination bien sûr, le protocole
(UDP), le port (généralement 161 pour SNMP), et si on est chanceux
un identifiant unique de la requête SNMP.

Pour info, voici les codes ICMP UNREACH définis pour IPv4 :
<http://tools.ietf.org/html/rfc1812#section-5.2.7.1>.

Ma question est la suivante : en fonction du code ICMP UNREACH
reçu, quelles sont les requêtes UDP que je peux annuler avec
assez peu de risques de voir arriver plus tard une réponse UDP ?

Par exemple, si je reçois un code 0 (network unreachable) ou 1 (host
unreachable), je peux annuler en confiance toutes les requêtes vers
cette adresse. Idem si je reçois un code 2 (protocol unreachable)
pourvu qu'il porte sur un paquet de protocole UDP. Pour le code 3
(port unreachable) je dois en outre vérifier le numéro de port, en
l'occurrence celui de SNMP.

Inversement, si je reçois un code 4 (fragmentation needed DF set)
ou 5 (source route failed), je ne peux pas annuler d'autres requêtes
vers cette adresse car ça peut être dû à un autre programme ayant
envoyé un paquet fixant le flag 'don't fragment' ou définissant une
'source route'.

Pour les codes 6 à 10, outre que les 6, 8, 9 et 10 sont obsolètes
et ne devraient plus être émis, je pense que je peux annuler toutes
les requêtes vers l'adresse en question.

Et donc je me pose surtout la question pour les codes 11, 12, 13,
14 et 15. Les deux premiers parlent de « type of service (TOS) »,
le suivant de filtrage, les deux derniers de « precedence ».

***********
* En bref *
**********************************************************************
Si je reçois un ICMP UNREACH avec l'un des codes 11, 12, 13, 14 ou 15,
est-ce que je peux annuler toutes mes requêtes UDP en attente vers
l'adresse indiquée ?
**********************************************************************

D'avance merci pour vos réponses, en vous remerciant déjà de m'avoir
lu jusqu'au bout !

Cordialement,
--
Olivier Miakinen

4 réponses

Avatar
Nicolas George
Olivier Miakinen , dans le message <la8pkg$632$,
a écrit :
La seule solution pour éviter d'attendre trop longtemps, c'est de
se mettre à l'écoute des paquets ICMP 'UNREACHABLE' et d'essayer
de reconnaître dedans les caractéristiques du paquet UDP qu'on
avait envoyé : l'adresse de destination bien sûr, le protocole
(UDP), le port (généralement 161 pour SNMP), et si on est chanceux
un identifiant unique de la requête SNMP.



Normalement, tu as à ta disposition dans le paquet ICMP le quadruplet
(adresse source, port source, adresse destination, port destination) qui
identifie de manière unique l'application émettrice, à moins d'avoir
réutilisé un port source trop rapidement ou d'autoriser le partage de port.

Si je reçois un ICMP UNREACH avec l'un des codes 11, 12, 13, 14 ou 15,
est-ce que je peux annuler toutes mes requêtes UDP en attente vers
l'adresse indiquée ?



As-tu regardé ce que font les noyaux Unix ?

Dans l'API des sockets habituelle, si tu as fixé une adresse destination
avec connect() au lieu de la spécifier / recevoir à chaque fois avec
sendto() recvfrom(), alors quand une erreur est renvoyée pour cette
pseudo-connexion, la prochaine opération (lecture ou écriture) retournera
une erreur.

Tu peux probablement imiter l'algorithme utilisé par Linux ou un BSD pour
décider si une telle erreur est retournée.
Avatar
Olivier Miakinen
Bonjour Nicolas,

Le 04/01/2014 12:10, Nicolas George m'a répondu :

La seule solution pour éviter d'attendre trop longtemps, c'est de
se mettre à l'écoute des paquets ICMP 'UNREACHABLE' et d'essayer
de reconnaître dedans les caractéristiques du paquet UDP qu'on
avait envoyé : l'adresse de destination bien sûr, le protocole
(UDP), le port (généralement 161 pour SNMP), et si on est chanceux
un identifiant unique de la requête SNMP.



Normalement, tu as à ta disposition dans le paquet ICMP le quadruplet
(adresse source, port source, adresse destination, port destination) qui
identifie de manière unique l'application émettrice,



Oui, même si les ports source et destination ne sont pas aussi faciles à
trouver dans le cas d'IPv6 que pour IPv4 (oui, car j'ai aussi le cas de
ICMPv6 dont je n'avais pas encore parlé).

à moins d'avoir
réutilisé un port source trop rapidement ou d'autoriser le partage de port.



Je dois vérifier, mais je pense que le port source a des chances d'être
le même pour toutes les requêtes, du moins un port pour les requêtes en
IPv4 et un autre pour celles en IPv6. Mais peut-être change-t-il selon
l'adresse source, laquelle changera selon l'interface utilisée.

Cela dit, je n'y avais pas pensé, mais c'est vrai que vérifier le port
source doit me permettre de distinguer les paquets ICMP en réponse aux
requêtes que je fais moi-même de ceux en réponse d'autres requêtes. Je
te remercie déjà pour cette idée.

Si je reçois un ICMP UNREACH avec l'un des codes 11, 12, 13, 14 ou 15,
est-ce que je peux annuler toutes mes requêtes UDP en attente vers
l'adresse indiquée ?



As-tu regardé ce que font les noyaux Unix ?



Non, parce que j'étais à mille lieues d'imaginer que cela pouvait
m'être utile avant de lire ce qui suit :

Dans l'API des sockets habituelle, si tu as fixé une adresse destination
avec connect() au lieu de la spécifier / recevoir à chaque fois avec
sendto() recvfrom(), alors quand une erreur est renvoyée pour cette
pseudo-connexion, la prochaine opération (lecture ou écriture) retournera
une erreur.



J'ignorais complètement que l'on pouvait faire un connect() en UDP, et
encore plus que le noyau pouvait du coup gérer une pseudo-connexion
et tenir compte des paquets ICMP !

Tu peux probablement imiter l'algorithme utilisé par Linux ou un BSD pour
décider si une telle erreur est retournée.



Je vais regarder, mais je crains que ce code ne fasse de son mieux pour
identifier *la* pseudo-connexion UDP qui a généré l'erreur, alors que
j'espérais pouvoir généraliser à d'autres envois UDP présents ou à venir
vers la même adresse IP.

Cordialement,
--
Olivier Miakinen
Avatar
Olivier Miakinen
Le 04/01/2014 16:49, je répondais à Nicolas George :

Tu peux probablement imiter l'algorithme utilisé par Linux ou un BSD pour
décider si une telle erreur est retournée.



Je vais regarder, mais je crains que ce code ne fasse de son mieux pour
identifier *la* pseudo-connexion UDP qui a généré l'erreur, alors que
j'espérais pouvoir généraliser à d'autres envois UDP présents ou à venir
vers la même adresse IP.



Je viens de regarder rapidement dans linux-3.13-rc6/net/ipv4/udp.c et il
semble que ce soit bien ce que je pensais.

En gros : la fonction __udp4_lib_err() récupère dans un 'socket buffer'
(sk_buff) non seulement les adresses et ports source et destination,
mais aussi le ifindex (information dont je ne dispose pas). Muni de
ces infos il appelle __udp4_lib_lookup() qui retourne un pointeur sur
une seule structure sock, et dans le cas d'un ICMP_DEST_UNREACH le seul
traitement spécifique est lorsque code vaut ICMP_FRAG_NEEDED ; tous les
autres ont en gros le même code.

Ah si, selon le code l'erreur est considérée ou non comme 'fatal' ou
'harderr'. Voici celles qui ne sont pas fatales :

.errno = ENETUNREACH, /* ICMP_NET_UNREACH = 0 */
.errno = EHOSTUNREACH, /* ICMP_HOST_UNREACH = 1 */
.errno = EMSGSIZE, /* ICMP_FRAG_NEEDED = 4 */
.errno = EOPNOTSUPP, /* ICMP_SR_FAILED = 5 */
.errno = ENETUNREACH, /* ICMP_NET_UNR_TOS = 11 */
.errno = EHOSTUNREACH, /* ICMP_HOST_UNR_TOS = 12 */

Et celles qui le sont :

.errno = ENOPROTOOPT /* ICMP_PROT_UNREACH = 2 */,
.errno = ECONNREFUSED, /* ICMP_PORT_UNREACH = 3 */
.errno = ENETUNREACH, /* ICMP_NET_UNKNOWN = 6 */
.errno = EHOSTDOWN, /* ICMP_HOST_UNKNOWN = 7 */
.errno = ENONET, /* ICMP_HOST_ISOLATED = 8 */
.errno = ENETUNREACH, /* ICMP_NET_ANO = 9 */
.errno = EHOSTUNREACH, /* ICMP_HOST_ANO = 10 */
.errno = EHOSTUNREACH, /* ICMP_PKT_FILTERED = 13 */
.errno = EHOSTUNREACH, /* ICMP_PREC_VIOLATION = 14 */
.errno = EHOSTUNREACH, /* ICMP_PREC_CUTOFF = 15 */

Mais je n'y trouve pas la réponse à ma question, à savoir si je peux
ou non interrompre des requêtes dont je ne suis pas 100 % certain
qu'elles soient à l'origine de l'erreur.

Cordialement,
--
Olivier Miakinen
Avatar
Olivier Miakinen
Le 04/01/2014 16:49, je répondais à Nicolas George :

Normalement, tu as à ta disposition dans le paquet ICMP le quadruplet
(adresse source, port source, adresse destination, port destination) qui
identifie de manière unique l'application émettrice, à moins d'avoir
réutilisé un port source trop rapidement ou d'autoriser le partage de port.



Je dois vérifier, mais je pense que le port source a des chances d'être
le même pour toutes les requêtes, du moins un port pour les requêtes en
IPv4 et un autre pour celles en IPv6. Mais peut-être change-t-il selon
l'adresse source, laquelle changera selon l'interface utilisée.

Cela dit, je n'y avais pas pensé, mais c'est vrai que vérifier le port
source doit me permettre de distinguer les paquets ICMP en réponse aux
requêtes que je fais moi-même de ceux en réponse d'autres requêtes. Je
te remercie déjà pour cette idée.



Finalement, c'est bien la solution que j'ai retenue. En dehors de
quelques codes ICMP UNREACH assez sûrs (essentiellement 0 et 1 pour
ICMP v4 et 0 pour ICMP v6), je ne prends en compte que les messages
portant sur des paquets UDP dont le port source est celui que j'ai
utilisé. Résultat, ça fonctionne parfaitement.

Encore merci pour m'y avoir fait penser !

Cordialement,
--
Olivier Miakinen