Est-ce que sin_addr.s_addr = INADDR_ANY; besoin de htonl du tout?

Je suis tombé sur deux fils:

Socket avec recv-timeout: Quel est le problème avec ce code?

Lecture / écriture sur un socket en utilisant un stream FILE en c

l’un utilise htonl et l’autre non.

Qui a raison?

Etant donné que d’autres constantes telles que INADDR_LOOPBACK sont dans l’ordre des octets hôte, je soumets que htonl devrait être appliqué à toutes les constantes de cette famille, y compris INADDR_ANY .

(Remarque: j’ai écrit cette réponse pendant la rédaction de @Mat; sa réponse indique également qu’il est préférable d’être cohérent et d’utiliser toujours htonl .)

Raisonnement

Si vous écrivez comme ceci, cela représente un risque pour les futurs responsables de la maintenance de votre code:

 if (some_condition) sa.s_addr = htonl(INADDR_LOOPBACK); else sa.s_addr = INADDR_ANY; 

Si je htonl revue ce code, je me demanderais immédiatement pourquoi une des constantes a htonl appliquée et l’autre pas. Et je le signalerais comme un bogue, que je sache ou non que INADDR_ANY toujours la valeur 0, la conversion n’est donc pas un problème.

Le code que vous écrivez ne concerne pas seulement le comportement correct de l’exécution, il doit également être évident lorsque c’est possible et facile à croire qu’il est correct. Pour cette raison, vous ne devez pas supprimer le htonl INADDR_ANY . Les trois raisons pour lesquelles je n’ai pas utilisé htonl sont les suivantes:

  1. Cela peut choquer les programmeurs de sockets expérimentés d’utiliser htonl car ils sauront que cela n’a aucun effet (car ils connaissent la valeur de la constante par cœur).
  2. Il faut moins de frappe pour l’omettre.
  3. Une optimisation “de performance” bidon (clairement ce ne sera pas grave).

INADDR_ANY est “n’importe quelle adresse” dans IPV4. Cette adresse est 0.0.0.0 en notation pointée, donc 0x000000 en hexadécimal pour toute finalité. Le faire passer par htonl n’a aucun effet.

Maintenant, si vous souhaitez vous interroger sur les autres constantes de macro, consultez INADDR_LOOPBACK si elle est définie sur votre plate-forme. Il y a des chances que ce soit une macro comme celle-ci:

 #define INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */ 

(de linux/in.h , définition équivalente dans winsock.h ).

Donc, pour INADDR_LOOPBACK , un htonl est nécessaire.

Par htonl cohérence, il pourrait donc être préférable d’utiliser htonl dans tous les cas.

Ni l’un ni l’autre n’est correct , dans la mesure où INADDR_ANY et htonl sont obsolètes et conduisent à un code complexe et laid qui ne fonctionne qu’avec IPv4. Passez à l’utilisation de getaddrinfo pour tous vos besoins en matière de création d’adresses de socket:

 struct addrinfo *ai, hints = { .ai_flags = AI_PASSIVE|AI_ADDRCONFIG }; getaddrinfo(0, "1234", &hints, &ai); 

Remplacez "1234" par votre numéro de port ou nom de service.

J’allais append ceci comme commentaire, mais ça a pris un peu de temps …

Je pense que les réponses et le commentaire ici htonl() que htonl() doit être utilisé sur ces constantes (bien que le fait d’appeler INADDR_ANY et INADDR_NONE équivaut à un non-fonctionnement). Le problème que je vois en ce qui concerne la confusion est qu’il n’est pas explicitement appelé dans la documentation – veuillez me corriger si je l’ai simplement manqué, mais je ne l’ai pas vu dans les pages de manuel, ni dans l’en-tête d’inclusion où cela est explicitement indiqué. indique que les définitions pour INADDR_* sont dans l’ordre de l’hôte. Encore une fois, ce n’est pas un gros problème pour INADDR_ANY , INADDR_NONE et INADDR_BROADCAST , mais c’est important pour INADDR_LOOPBACK .

Maintenant, j’ai beaucoup travaillé sur les sockets de bas niveau en C, mais l’adresse de bouclage est rarement utilisée, voire jamais, dans mon code. Bien que ce sujet date de plus d’un an, ce problème même est survenu aujourd’hui, c’est parce que j’ai supposé à tort que les adresses définies dans l’en-tête d’inclusion sont dans l’ordre du réseau. Je ne sais pas trop pourquoi j’ai eu cette idée – probablement parce que la structure in_addr doit avoir l’adresse dans l’ordre du réseau, inet_aton et inet_addr renvoient leurs valeurs dans l’ordre du réseau. Mon hypothèse logique était donc que ces constantes seraient utilisables telles inet_addr . Jeter ensemble un rapide 5-liner pour tester cette théorie m’a montré le contraire. Si l’un des pouvoirs en place voyait cela, je suggérerais explicitement d’indiquer que les valeurs sont, en fait, dans l’ordre de l’hôte, et non dans l’ordre du réseau, et que htonl() devrait leur être appliqué. Par souci de cohérence, je suggérerais également, comme d’autres l’ont déjà fait ici, que htonl() soit utilisé pour toutes les valeurs INADDR_* , même s’il ne fait rien à la valeur.

Stevens utilise htonl(INADDR_ANY) dans le livre UNIX Network Programming (ma copie date de 1990).

La version actuelle de FreeBSD définit 12 constantes netinet/in.h dans netinet/in.h ; 9 sur 12 nécessitent htonl() pour fonctionner correctement. (Les 9 INADDR_LOOPBACK sont INADDR_LOOPBACK et 8 autres adresses de groupe de multidiffusion, telles que INADDR_ALLHOSTS_GROUP et INADDR_ALLMDNS_GROUP .)

En pratique, que vous INADDR_ANY ou htonl(INADDR_ANY) , cela ne fait aucune différence, à part le risque de htonl() performance possible de htonl() . Et même cet impact sur les performances peut ne pas exister – avec mon gcc 4.2.1 64 bits, l’activation de tout niveau d’optimisation semble activer la conversion htonl() moment de la htonl() des constantes.

En théorie, il serait possible pour un implémenteur de redéfinir INADDR_ANY en une valeur où htonl() fait réellement quelque chose, mais un tel changement htonl() dizaines de milliers de codes existants et ne survivrait pas dans le “monde réel”. … Il existe trop de code qui dépend explicitement ou implicitement de la INADDR_ANY d’ INADDR_ANY sous la forme d’une sorte d’entier de valeur zéro. Stevens n’avait probablement pas l’intention de INADDR_ANY supposer que INADDR_ANY était toujours égal à zéro lorsqu’il écrivait:

 cli_addr.sin_addr.s_addr = htonl(INADDR_ANY); cli_addr.sin_port = htons(0); 

Lors de l’atsortingbution d’une adresse locale au client à l’aide de bind , nous définissons l’adresse Internet sur INADDR_ANY et le port Internet 16 bits sur zéro.

Résumons-le un peu, car aucune des réponses précédentes ne semble être à jour et je ne suis peut-être pas la dernière personne à consulter cette page de questions. Il y a eu des opinions pour et contre l’utilisation de htonl autour d’INADDR_ANY, constante ou négative.

De nos jours (et cela fait un certain temps déjà que les bibliothèques système sont prêtes pour IPv6, nous utilisons IPv4 ainsi que IPv6. La situation avec IPv6 est beaucoup plus facile car les structures de données et les constantes ne souffrent pas de l’ordre des octets. On utiliserait ‘in6addr_any’ ainsi que ‘in6addr_loopback’ (les deux types struct in6_addr) et les deux sont des objects constants dans l’ordre des octets du réseau.

Découvrez pourquoi IPv6 ne souffre pas du même problème (si les adresses IPv4 étaient définies comme des tableaux à quatre octets, elles ne le seraient pas non plus):

 struct in_addr { uint32_t s_addr; /* address in network byte order */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ }; 

Pour IPv4, il serait intéressant d’avoir également les constantes ‘inaddr_any’ et ‘inaddr_loopback’ en tant que ‘struct in_addr’ (afin qu’elles puissent également être comparées avec memcmp ou copiées avec memcpy). En effet, il pourrait être intéressant de les créer dans votre programme car ils ne sont pas fournis par glibc et d’autres bibliothèques:

 const struct in_addr inaddr_loopback = { htonl(INADDR_LOOPBACK) }; 

Avec glibc, cela ne fonctionne que pour moi dans une fonction (et je ne peux pas la rendre static ), car htonl n’est pas une macro, mais une fonction ordinaire.

Le problème est que la glibc (contrairement à ce qui a été affirmé dans d’autres réponses) ne fournit pas htonl en tant que macro, mais plutôt en tant que fonction. Par conséquent, vous devriez:

 static const struct in_addr inaddr_any = { 0 }; #if BYTE_ORDER == BIG_ENDIAN static const struct in_addr inaddr_loopback = { 0x7f000001 }; #elif BYTE_ORDER == LITTLE_ENDIAN static const struct in_addr inaddr_loopback = { 0x0100007f }; #else #error Neither big endian nor little endian #endif 

Ce serait un très bon ajout aux en-têtes et vous pourrez ensuite travailler avec les constantes IPv4 aussi facilement que vous le pouvez avec IPv6.

Mais pour implémenter cela, je devais utiliser certaines constantes pour initialiser cela. Quand je connais exactement les octets respectifs, je n’ai besoin d’ aucune constante. De même que certaines personnes prétendent que htonl() est redondant pour une constante évaluée à zéro, tout le monde peut prétendre que la constante elle-même est redondante. Et il aurait raison.

Dans le code, je préfère être explicite qu’implicite. Par conséquent, si ces constantes (telles que INADDR_ANY, INADDR_ALL, INADDR_LOOPBACK) sont toutes systématiquement dans l’ordre des octets de l’hôte, il est alors correct que si vous les traitez comme cela. Voir par exemple (lorsque vous n’utilisez pas la constante ci-dessus):

 struct in_addr address4 = { htonl(use_loopback ? INADDR_LOOPBACK : INADDR_ANY }; 

Bien sûr, vous pouvez dire que vous n’avez pas besoin d’appeler htonl pour INADDR_ANY et donc vous pouvez:

 struct in_addr address4 = { use_loopback ? htonl(INADDR_LOOPBACK) : INADDR_ANY }; 

Mais alors, en ignorant l’ordre des octets de la constante parce que c’est de toute façon zéro, alors je ne vois pas beaucoup de logique dans l’utilisation de la constante. Et la même chose s’applique à INADDR_ALL, car il est également facile de taper 0xffffffff;

Une autre façon de contourner ce problème consiste à éviter de définir ces valeurs directement:

 struct in_addr address4; inet_pton(AF_INET, "127.0.0.1", &address4); 

Cela ajoute un peu de traitement inutile, mais il n’ya pas de problème d’ordre des octets et il en va pratiquement de même pour IPv4 et IPv6 (il suffit de changer la chaîne d’adresse).

Mais la question est pourquoi faites-vous cela du tout. Si vous voulez vous connect() à IPv4 localhost (mais parfois à IPv6 localhost, ou à n’importe quel nom d’hôte), getaddrinfo () (mentionné dans l’une des réponses) est bien meilleur pour cela, car:

  1. Il s’agit d’une fonction utilisée pour traduire tout nom d’hôte / service / famille / socktype / protocole en une liste d’enregistrements struct addrinfo correspondants.

  2. Chaque struct addrinfo inclut un pointeur polymorphe vers struct sockaddr que vous pouvez directement utiliser avec connect() . Par conséquent, vous n’avez pas besoin de vous soucier de la construction de struct sockaddr_in , du transtypage (via un pointeur) de struct sockaddr , etc.

    struct addrinfo * ai, hints = {.ai_family = AF_INET}; getaddrinfo (0, “1234”, & astuces, & ai);

    enregistrement qui inclut à son tour des pointeurs polymorphes de struct sockaddr dont vous avez besoin pour l’appel connect() .

Donc, la conclusion est:

1) L’API standard ne parvient pas à fournir des constantes struct in_addr directement utilisables (au lieu de cela, elle fournit des constantes d’entiers non signés plutôt inutiles dans l’ordre des hôtes).

 struct addrinfo *ai, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP }; int error; error = getaddrinfo(NULL, 80, &hints, &ai); if (error) ... for (item = result; item; item = item->ai_next) { sock = socket(item->ai_family, item->ai_socktype, item->ai_protocol); if (sock == -1) continue; if (connect(sock, item->ai_addr, item->ai_addrlen) != -1) { fprintf(stderr, "Connected successfully."); break; } close(sock); } 

Lorsque vous êtes certain que votre requête est suffisamment sélective pour ne renvoyer qu’un résultat, vous pouvez (en omettant le traitement des erreurs pour plus de concision):

 struct *result, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP }; getaddrinfo(NULL, 80, &hints, &ai); sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); connect(sock, result->ai_addr, result->ai_addrlen); 

Si vous craignez que getaddrinfo() ne soit pas aussi lent que l’utilisation des constantes, la bibliothèque système est le meilleur endroit pour y remédier. Une bonne implémentation renverrait simplement l’adresse de bouclage demandée lorsque le service est nul et que hints.ai_family est défini.

Je n’aime généralement pas répondre lorsqu’il existe déjà une réponse “décente”. Dans ce cas, je vais faire une exception parce que les informations que j’ai ajoutées à ces réponses sont mal interprétées.

INADDR_ANY est défini comme une adresse IPv4 INADDR_ANY tous les bits nuls, 0.0.0.0 ou 0x00000000 . Appeler htonl() sur cette valeur donnera la même valeur, zéro. Par conséquent, appeler htonl() sur cette valeur constante n’est pas techniquement nécessaire.

INADDR_ALL est défini comme une adresse IPv4 d’un seul bit, 255.255.255.255 ou 0xFFFFFFFF . L’appel de htonl() avec INADDR_ALL renverra INADDR_ALL . Encore une fois, appeler htonl() n’est pas techniquement nécessaire.

Une autre constante définie dans les fichiers d’en-tête est INADDR_LOOPBACK , définie comme 127.0.0.1 ou 0x7F000001 . Cette adresse est donnée dans l’ordre des octets du réseau et ne peut pas être transmise à l’interface de socket sans htonl() . Vous devez utiliser htonl() avec cette constante.

Certains suggéreraient que la cohérence et la lisibilité du code exigent que les programmeurs utilisent htonl() pour toute constante nommée INADDR_* – car elle est requirejse pour certaines d’entre elles. Ces affiches sont fausses.

Un exemple donné dans ce fil est:

 if (some_condition) sa.s_addr = htonl(INADDR_LOOPBACK); else sa.s_addr = INADDR_ANY; 

Citant “John Zwinck”:

“Si j’examinais ce code, je me demanderais immédiatement pourquoi une des constantes s’est appliquée à l’autre, pas à l’autre. Et je le signale comme un bogue, que je sache ou non que INADDR_ANY est toujours 0 donc le convertir n’est pas une opération. Et je pense (et j’espère) que beaucoup d’autres mainteneurs feraient de même. ”

Si je recevais un tel rapport de bogue, je le jetterais immédiatement. Ce processus me ferait gagner beaucoup de temps, car il INADDR_ANY des rapports de bogues émanant de personnes ne sachant pas que INADDR_ANY est toujours INADDR_ANY 0. (Suggérer que connaître les valeurs de INADDR_ANY et al. Viole en quelque sorte l’encapsulation non-starter – les mêmes numéros sont utilisés dans la sortie netcat et dans le kernel. Les programmeurs doivent connaître les valeurs numériques actuelles. Les personnes qui ne le savent pas ne manquent pas de connaissances internes , mais de connaissances de base du domaine. )

En réalité, si un programmeur gère le code de sockets et qu’il ne connaît pas les modèles de bits de INADDR_ANY et INADDR_ALL, vous avez déjà des problèmes. Envelopper 0 dans une macro qui renvoie 0 est le type de mentalité qui est esclave de la cohérence dépourvue de sens et qui ne respecte pas la connaissance du domaine.

Conserver le code des sockets va au-delà de la compréhension C. Si vous ne comprenez pas la différence entre INADDR_LOOPBACK et INADDR_ANY à un niveau compatible avec la sortie netstat , alors vous êtes dangereux dans ce code et vous ne devriez pas le changer.

Les arguments de l’homme de paille proposés par Zwinck concernant l’utilisation inutile de htonl() :

  1. Cela peut choquer les programmeurs de sockets expérimentés d’utiliser htonl car ils sauront que cela n’a aucun effet (car ils connaissent la valeur de la constante par cœur).

Ceci est un argument de paille parce que nous avons une représentation selon laquelle des programmeurs de sockets expérimentés connaissent la valeur de INADDR_ANY par cœur. Cela revient à écrire que seul un programmeur C expérimenté connaît la valeur de NULL par cœur. Ecrire “par cœur” donne l’impression que le nombre est un peu difficile à mémoriser, peut-être quelques chiffres, comme 127.0.0.1 . Mais non, nous discutons de manière hyperbolique de la difficulté de mémoriser les modèles nommés “tous les bits nuls” et “tous un bits”.

Considérant que ces valeurs numériques apparaissent dans le résultat, par exemple, de netstat et d’autres utilitaires système, et considérant également que certaines de ces valeurs apparaissent dans les en-têtes IP, il n’existe pas de programmeur de sockets compétent qui ne connaisse pas ces valeurs, que par coeur ou par cerveau. En fait, tenter de programmer des sockets sans connaître ces bases peut être dangereux pour la disponibilité du réseau.

  1. Il faut moins de dactylographie pour l’omettre.

Cet argument est censé être absurde et méprisant, il n’a donc pas besoin d’être beaucoup réfuté.

  1. Une optimisation “de performance” bidon (clairement ce ne sera pas grave).

Il est difficile de savoir d’où vient cet argument. Ce pourrait être une tentative de fournir des arguments à l’apparence stupide à l’opposition. Dans tous les cas, le fait de ne pas utiliser la macro htonl() n’a aucune htonl() sur les performances lorsque vous fournissez une constante et utilisez un compilateur C typique: les expressions de constante sont réduites à une constante dans les deux cas.


Une raison de ne pas utiliser htonl() avec INADDR_ANY est que le programmeur de sockets le plus expérimenté sait qu’il n’est pas nécessaire. De plus, les programmeurs qui ne le savent pas doivent apprendre. L’utilisation de htonl() aucun “coût” supplémentaire, le problème réside dans le coût d’établissement d’une norme de codage qui favorise la méconnaissance de valeurs aussi importantes.

Par définition, l’encapsulation favorise l’ignorance. Cette ignorance même est l’avantage habituel de l’utilisation d’une interface encapsulée – la connaissance est coûteuse et finie, donc l’encapsulation est généralement bonne. La question devient alors: quels efforts de programmation sont optimisés via l’encapsulation? Existe-t-il des tâches de programmation qui sont gérées par l’encapsulation?

Il n’est pas techniquement incorrect d’utiliser htonl() , car cela n’a aucun effet sur cette valeur. Cependant, les arguments que vous devriez utiliser peuvent être trompeurs.

Il y a ceux qui diraient qu’une meilleure situation serait une situation dans laquelle le développeur n’aurait pas besoin de savoir que INADDR_ANY est un zéros, etc. Ce pays d’ignorance est pire, pas meilleur. Considérez que ces “valeurs magiques” sont utilisées dans diverses interfaces avec TCP / IP. Par exemple, lors de la configuration d’Apache, si vous souhaitez écouter uniquement IPv4 (et non IPv6), vous devez spécifier:

 Listen 0.0.0.0:80 

J’ai rencontré des programmeurs qui ont fourni l’adresse IP locale par erreur à la place de INADDR_ANY (0.0.0.0) ci-dessus. Ces programmeurs ne savent pas ce qu’est INADDR_ANY et ils l’enveloppent probablement dans htonl() tant qu’ils y sont. C’est le pays de l’abstention, de la pensée et de l’encapsulation.

Les notions d ‘”encapsulation” et d’ “abstraction” ont été largement acceptées et trop largement appliquées, mais elles ne s’appliquent pas toujours. Dans le domaine de l’adressage IPv4, il n’est pas approprié de traiter ces valeurs constantes comme “abstraites” – elles sont converties directement en bits sur le fil.


Mon argument est le suivant: il n’ya pas d’utilisation “correcte” de INADDR_ANY avec htonl() – les deux sont équivalents. Je ne recommanderais pas d’adopter une exigence selon laquelle la valeur doit être utilisée d’une manière particulière, car la famille de constantes INADDR_X ne comporte que quatre membres, et un seul d’entre eux, INADDR_LOOPBACK a une valeur différente en fonction de l’ordre des octets. Il est préférable de simplement savoir ce fait que d’établir une norme d’utilisation des valeurs qui ferme les yeux sur les motifs binarys des valeurs.

Dans de nombreuses autres API, il est utile pour les programmeurs de continuer sans connaître la valeur numérique ou les modèles de bits des constantes utilisées par les API. Dans le cas de l’API Sockets, ces modèles de bits et valeurs sont utilisés en entrée et affichés de manière omniprésente. Il vaut mieux connaître ces valeurs numériquement que de passer du temps à réfléchir à l’utilisation de htonl() .

Lors de la programmation en C, en particulier, la plupart des “utilisations” de l’API sockets impliquent de récupérer le code source d’une autre personne et de l’adapter. C’est une autre raison pour laquelle il est si important de savoir ce qu’est INADDR_ANY avant de toucher une ligne qui l’utilise.