Le péché addr.s addr = INADDR ANY; besoin de htonl du tout?

31
demandé sur Community 2011-05-21 16:58:41

7 réponses

Puisque d'autres constantes comme INADDR_LOOPBACK sont dans l'ordre des octets de l'hôte, je soumets que toutes les constantes de cette famille devraient avoir htonl appliqué à eux, y compris INADDR_ANY.

(Note: j'ai écrit cette réponse pendant que @Mat était en train d'éditer; sa réponse dit maintenant aussi qu'il vaut mieux être cohérent et toujours utiliser htonl.)

Justification

C'est un danger pour les futurs responsables de votre code si vous l'écrivez comme ceci:

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

Si je révisais ce code, je le ferais demandez immédiatement pourquoi l'une des constantes a htonl appliqué et l'autre Non. Et je le signalerais comme un bug, que j'aie ou non la "connaissance interne" que INADDR_ANY est toujours 0, donc la conversion est un no-op.

Le code que vous écrivez n'est pas seulement d'avoir le comportement d'exécution correct, il devrait également être évident si possible et facile de croire qu'il est correct. Pour cette raison, vous ne devriez pas enlever le htonl autour de INADDR_ANY. Les trois raisons de ne pas utiliser htonl que je peux voir sont:

  1. cela peut offenser les programmeurs de sockets expérimentés d'utiliser htonl car ils sauront que cela ne fait rien (puisqu'ils connaissent la valeur de la constante par cœur).
  2. Il faut moins de frappe pour l'omettre.
  3. une fausse optimisation de " performance "(clairement, cela n'aura pas d'importance).
33
répondu John Zwinck 2011-05-21 14:57:26

INADDR_ANY est "n'importe quelle adresse" dans IPV4. Cette adresse est 0.0.0.0 en notation pointillée, donc 0x000000 en hexadécimal sur toute endianness. Le passage à travers htonl n'a aucun effet.

Maintenant, si vous voulez interroger sur d'autres macro-constantes, regardez - 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   */

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

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

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

16
répondu Mat 2011-05-21 13:43:49

Aucun n'est correct , en ce sens que 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 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 votre nom de service.

8
répondu R.. 2011-05-21 15:11:14

Allait ajouter ceci en tant que commentaire, mais il a été un peu long ...

Je pense qu'il est clair d'après les réponses et le commentaire ici que htonl() doit être utilisé sur ces constantes (bien que l'appeler INADDR_ANY et {[2] } équivaut à no-ops). Le problème que je vois quant à l'endroit où la confusion se pose est qu'il n'est pas explicitement appelé dans la documentation-quelqu'un me corrige si je l'ai simplement manqué, Mais je ne l'ai pas vu dans les pages de manuel, ni dans l'en-tête include où il indique explicitement que les définitions de INADDR_* sont dans l'ordre de l'hôte. Encore une fois, pas une grosse affaire pour INADDR_ANY, INADDR_NONE, et INADDR_BROADCAST, mais est significative pour INADDR_LOOPBACK.

Maintenant, j'ai fait un peu de travail de socket de bas niveau en C, mais l'adresse de bouclage est rarement, voire jamais, utilisée dans mon code. Bien que ce sujet ait plus d'un an, ce problème a juste sauté pour me mordre dans le derrière aujourd'hui, et c'est parce que je suis allé sur l'hypothèse erronée que les adresses définies dans le l'en-tête include est dans l'ordre réseau. Je ne sais pas 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 retournent leurs valeurs dans l'ordre du réseau, et donc mon hypothèse logique était que ces constantes seraient utilisables telles quelles. Jeter ensemble un 5-liner rapide pour tester cette théorie m'a montré le contraire. Si l'un des pouvoirs-qui - soit arrive à voir cela, je ferais la suggestion d'appeler explicitement que les valeurs sont, en fait, dans host l'ordre, pas l'ordre du réseau, et que htonl() devrait leur être appliqué. Par souci de cohérence, je suggère également, comme d'autres l'ont déjà fait ici, que htonl() soit utilisé pour toutes les valeurs INADDR_*, même si cela ne fait rien à la valeur.

3
répondu Will 2012-05-30 15:51:52

Stevens utilise htonl(INADDR_ANY) de manière cohérente dans le livre UNIX Network Programming (ma copie date de 1990).

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

En pratique, cela ne fait aucune différence si vous utilisez INADDR_ANY ou htonl(INADDR_ANY), autre que le succès de performance possible de htonl(). Et même cela possible le hit de performance peut ne pas exister - avec mon gcc 4.2.1 64 bits, activer n'importe quel niveau d'optimisation semble activer la conversion des constantes htonl() à la compilation.

En théorie, il serait possible pour un implémenteur de redéfinir INADDR_ANY à une valeur où htonl() fait réellement quelque chose, mais un tel changement briserait des dizaines de milliers de morceaux de code existants et ne survivrait pas dans le "monde réel"... Trop de code existe qui dépend explicitement ou implicitement de INADDR_ANY étant défini comme une sorte d'entier à valeur nulle. Stevens n'avait probablement pas l'intention pour quiconque de supposer que INADDR_ANY est toujours zéro quand il a écrit:

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

Dans l'attribution d'une adresse locale client en utilisant bind, nous définissons le Adresse Internet à INADDR_ANY et le port Internet 16 bits à zéro.

2
répondu John Brennen 2012-01-01 05:31:54

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 à voir cette page de questions. Il y a eu opinions à la fois pour et contre l'utilisation de htonl autour de la constante INADDR_ANY ou en l'évitant complètement.

De nos jours (et cela fait maintenant un certain temps), les bibliothèques système sont pour la plupart prêtes pour IPv6, donc nous utilisons IPv4 ainsi que IPv6. La situation avec IPv6 est beaucoup plus facile que 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 objets constants dans l'ordre des octets du réseau.

Voyez pourquoi IPv6 ne souffre pas du même problème (si les adresses IPv4 étaient définies comme des tableaux de quatre octets, elles ne souffriraient 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 bon d'avoir aussi 'inaddr_any' et 'inaddr_loopback' comme constantes ' struct in_addr '(afin qu'elles puissent également être comparées avec memcmp ou copié avec memcpy). En effet, il pourrait être une bonne idée de les créer dans votre programme car ils ne sont pas fournis par la glibc et d'autres bibliothèques:

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

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

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

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 pourriez alors travailler avec les constantes IPv4 aussi facilement que possible avec IPv6.

Mais pour implémenter cela, j'ai dû utiliser quelques constantes pour initialiser cela. Quand je connais exactement les octets respectifs, Je n'ai pas besoin de de constantes. Tout comme certaines personnes prétendent que htonl() est redondant pour une constante qui évalue à zéro, n'importe qui d'autre pourrait prétendre que la constante elle-même est également redondante. Et il le ferait être de droite.

Dans le code, je préfère être explicite qu'implicite. Par conséquent, si ces constantes (comme INADDR_ANY, INADDR_ALL, INADDR_LOOPBACK) sont toutes systématiquement dans l'ordre des octets de l'hôte, alors ce n'est correct que si vous les traitez comme ça. 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 {[27] }c'est zéro de toute façon, alors je ne vois pas beaucoup de logique à utiliser la constante du tout. Et la même chose s'applique à INADDR_ALL, car il est facile de taper 0xffffffff;

Une autre façon de le contourner est d'éviter de définir directement ces valeurs:

struct in_addr address4;

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

Cela ajoute un peu de traitement inutile mais il n'a pas de problèmes d'ordre des octets et c'est pratiquement la même chose pour IPv4 et IPv6 (vous changez simplement la chaîne d'adresse).

, Mais la question est: pourquoi êtes-vous le faire à tous. Si vous voulez connect() à IPv4 localhost (mais parfois à IPv6 localhost, ou juste n'importe quel nom d'hôte), getaddrinfo () (mentionné dans l'une des réponses) est beaucoup mieux pour cela, comme:

  1. C'est une fonction utilisée pour traduire n'importe quel nom d'hôte / service / famille / socktype / protocole a à une liste d'enregistrements struct addrinfo correspondants.

  2. Chaque struct addrinfo comprend un polymorphe pointeur de struct sockaddr, que vous pouvez utiliser directement avec connect(). Par conséquent, vous n'avez pas besoin de soins A propos de la construction de struct sockaddr_in, typecasting (via un pointeur) à struct sockaddr, etc.

    Struct addrinfo *ia, des notes = { .ai_family = AF_INET }; getaddrinfo(0, "1234", &astuces, &ia);

    Enregistrez qui à son tour incluent des pointeurs polymorphes struct sockaddr structures dont vous avez besoin pour l'appel connect().

Donc, la conclusion est:

1) L'API standard ne fournit pas de constantes struct in_addr directement utilisables (à la place, elle fournit des constantes entières non signées plutôt inutiles dans l'hôte de la commande).

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 sûr que votre requête est suffisamment sélective pour qu'elle ne renvoie qu'un seul résultat, vous pouvez faire (en omettant la gestion des erreurs par souci de concision) ce qui suit:

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 avez peur que getaddrinfo() soit significativement plus lent que d'utiliser les constantes, la bibliothèque système est le meilleur endroit pour résoudre ce problème. Une bonne implémentation retournerait simplement l'adresse de bouclage demandée lorsque service est null et hints.ai_family est défini.

2
répondu Pavel Šimerda 2013-10-13 16:50:30

Je n'aime généralement pas répondre quand il y a 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éfinie comme une adresse IPv4 tout-Zéro-bits, 0.0.0.0 ou 0x00000000. Appeler htonl() sur cette valeur donnera la même valeur, zéro. Par conséquent, l'appel de htonl() sur cette valeur constante n'est pas techniquement nécessaire.

INADDR_ALL est définie comme une adresse IPv4 tout-un-bits, 255.255.255.255 ou 0xFFFFFFFF. Appeler htonl() avec {[7] } 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 réseau et ne peut pas être transmise à l'interface des sockets Sans htonl(). Vous devez utiliser htonl(), avec cette constante.

Certains suggèrent que la cohérence et la lisibilité du code exigent que les programmeurs utilisent htonl() pour toute constante nommée INADDR_* - car c'est nécessaire pour certains d'entre eux. 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;
[46]}citant "John Zwinck":

"Si je révisais ce code, je me demanderais immédiatement pourquoi l'une des constantes a htonl appliqué et l'autre Non . Et je le signale comme un bug, que J'aie ou non la "connaissance interne" que INADDR_ANY est toujours 0, donc la conversion est un no-op. et je pense (et j'espère) que beaucoup d'autres mainteneurs feraient le de même."

Si je recevais un tel rapport de bogue, je le jetterais immédiatement. Ce processus me ferait gagner beaucoup de temps, en envoyant des rapports de bogues de personnes qui n'ont pas les "connaissances minimales de base" que INADDR_ANY est toujours 0. (Suggérant que connaître les valeurs de INADDR_ANY et al. en quelque sorte viole l'encapsulation ou tout ce qui est un autre non-démarreur - les mêmes nombres sont utilisés dans la sortie netcat et à l'intérieur du noyau. Les programmeurs doivent connaître les valeurs numériques réelles. Les personnes qui Je ne sais pas ne manque pas à l'intérieur connaissances, ils manquentconnaissances de base de la région.)

Vraiment, si vous avez un programmeur qui maintient le code des sockets, et que ce programmeur ne connaît pas les modèles de bits de INADDR_ANY et INADDR_ALL, vous êtes déjà en difficulté. Envelopper 0 dans une macro qui renvoie 0 est le genre de mentalité qui est esclave d'une cohérence sans signification et qui ne respecte pas la connaissance du domaine.

La maintenance du code des sockets concerne plus de comprendre 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 ne devriez pas le changer.

Arguments de Straw-man proposés par Zwinck concernant l'utilisation inutile de htonl():

  1. Il peut offenser les programmeurs de sockets expérimentés d'utiliser htonl car ils sauront qu'il ne fait rien (puisqu'ils connaissent la valeur de la constante par cœur).

C'est un argument de paille parce que nous avons une représentation que expérimentés socket programmeurs connaissent la valeur de INADDR_ANY par cœur. C'est comme écrire que seul un programmeur expérimenté C connaît la valeur de NULL par cœur. L'écriture "par cœur" donne l'impression que le nombre est léger Difficile à mémoriser, peut-être quelques chiffres, tels que 127.0.0.1. Mais non, nous discutons hyperboliquement de la difficulté de mémoriser les modèles nommés "tous les bits zéro" et " tous les bits un."

Considérant que ces valeurs numériques apparaissent dans la sortie de, par exemple, 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'y a pas de programmeur de sockets compétent qui ne connaît pas ces valeurs, que ce soit par cœur 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 frappe pour l'omettre.

Cet argument est destiné pour être absurde et dédaigneux, il n'a donc pas besoin de beaucoup de réfutation.

  1. une fausse optimisation de " performance "(clairement, cela n'aura pas d'importance).

Il est difficile de savoir d'où vient cet argument. Cela pourrait être une tentative de fournir des arguments stupides à l'opposition. Dans tous les cas, ne pas utiliser la macro htonl() ne fait aucune différence dans les performances lorsque vous fournissez une constante et utilisez un compilateur C typique-les expressions constantes sont réduites à une constante dans l'un ou l'autre 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 connaissent pas ont besoin d'apprendre. Il n'y a pas de "coût" supplémentaire avec l'utilisation de htonl(), le problème est le coût de l'établissement d'une norme de codage qui favorise l'ignorance de valeurs aussi importantes.

, Par définition, l'encapsulation favorise l'ignorance. Cette ignorance est l'avantage habituel d'utiliser un encapsulé interface-la connaissance est coûteuse et finie, donc l'encapsulation est généralement bonne. La question devient: quels efforts de programmation sont les mieux améliorés via l'encapsulation? Y a-t-il des tâches de programmation qui sont disservé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 soutiennent qu'une meilleure situation serait un dans lequel le développeur n'a pas besoin de pour savoir que INADDR_ANY est tous des zéros et ainsi de suite. Cette terre d'ignorance est pire, pas meilleure. 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 par erreur l'adresse IP locale au lieu de INADDR_ANY (0.0.0.0) ci-dessus. Ces programmeurs ne sachez ce qu'est INADDR_ANY, et ils l'enveloppent probablement dans htonl() pendant qu'ils y sont. C'est la terre de l'abstinence-penser et encapsuler.

Les idées de "l'encapsulation" et "abstraction" ont été largement acceptés 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 point est le suivant: il n'y a 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 n'a que quatre membres, et un seul d'entre eux, INADDR_LOOPBACK a une valeur différente selon l'ordre des octets. Il vaut mieux simplement connaître ce fait que d'établir une norme pour l'utilisation des valeurs qui ferme les yeux sur les modèles de bits des valeurs.

Dans de nombreuses autres API, il est utile pour les programmeurs de continuez 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 et valeurs de bits sont utilisés en entrée et affichés de manière omniprésente. Il est préférable de connaître ces valeurs numériquement que de passer du temps à penser à utiliser htonl() sur eux.

Lors de la programmation en C, en particulier, la plupart des" utilisations " de l'API sockets impliquent de saisir 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 que INADDR_ANY est avant de toucher à une ligne qui l'utilise.

0
répondu Heath Hunnicutt 2011-05-21 15:15:04