web-dev-qa-db-fra.com

Comment obtenir l'adresse IP de sockaddr

Je veux essayer d'obtenir l'adresse IP d'un client après avoir appelé accept. C'est ce que j'ai jusqu'à présent, mais je finis par obtenir un numéro long qui n'est clairement pas une adresse IP. Qu'est-ce qui pourrait mal?

int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);

sockaddr_in client;
client.sin_family = AF_INET;
socklen_t c_len = sizeof(client);

int acc_tcp_sock = accept(tcp_sock, (sockaddr*)&client, &c_len);
cout << "Connected to: " << client.sin_addr.s_addr << endl;
25
adhanlon

Ce long numéro est l'adresse IP, sous forme d'entier (une adresse IP n'est qu'un entier, après tout; il est juste plus facile à utiliser lorsque les gens séparer les octets et le mettre en notation par points).

Vous pouvez utiliser inet_ntoa pour convertir la valeur entière en notation par points standard.

29
James McNellis

Vu de http://beej.us/guide/bgnet/examples/client.c :

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

// [...]

struct addrinfo *p;
char s[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);

Il utilise inet_ntop, qui est préférable à inet_ntoa (non thread-safe) car il gère IPv4 et IPv6 (AF_INET et AF_INET6) et devrait être thread-safe je pense.

30
ofavre

ce que vous obtenez est la représentation entière brute de 32 bits de l'adresse IP. pour obtenir la chaîne familière séparée par des points, utilisez la fonction:

 char * inet_ntoa(struct in_addr addr);

qui convertira l'entier en une chaîne statique.

6
Alon

Ce qui suit est tiré de l'exemple https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c

              struct sockaddr in_addr;
              socklen_t in_len;
              int infd;
              char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

              in_len = sizeof in_addr;
              infd = accept (sfd, &in_addr, &in_len);
              if (infd == -1)
                {
                  if ((errno == EAGAIN) ||
                      (errno == EWOULDBLOCK))
                    {
                      /* We have processed all incoming
                         connections. */
                      break;
                    }
                  else
                    {
                      perror ("accept");
                      break;
                    }
                }

                 s = getnameinfo (&in_addr, in_len,
                 hbuf, sizeof hbuf,
                 sbuf, sizeof sbuf,
                 NI_NUMERICHOST | NI_NUMERICSERV);
                 if (s == 0){
                     printf("Accepted connection on descriptor %d "
                         "(Host=%s, port=%s)\n", infd, hbuf, sbuf);
                 }
3
enthusiasticgeek

Vous pouvez utiliser la fonction getnameinfo disponible pour Linux et Windows. Depuis les pages de manuel Linux https://linux.die.net/man/3/getnameinfo :

getnameinfo - traduction d'adresse à nom de manière indépendante du protocole

Exemple pour C/C++ (Linux):

#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>

char Host[NI_MAXHOST]; // to store resulting string for ip
if (getnameinfo((sockaddr*)&client, c_len,
                  Host, NI_MAXHOST, // to try to get domain name don't put NI_NUMERICHOST flag
                  NULL, 0,          // use char serv[NI_MAXSERV] if you need port number
                  NI_NUMERICHOST    // | NI_NUMERICSERV
               ) != 0) {
   //handle errors
} else {
    printf("Connected to: %s\n", Host);     
}

Ceci est un nettoyage de la réponse @enthusiasticgeek. Sa réponse a un exemple de travail avec accepter l'appel.

0
littlewhywhat

Bien que ce soient de bonnes réponses, ils ont cassé ma compilation avec -pedantic -std=c99 -Werror.

De prise homme 7

Pour permettre à tout type d'adresse de socket d'être transmis aux interfaces dans l'API de sockets, le type struct sockaddr est défini. Le but de ce type est purement de permettre le transtypage de types d'adresse de socket spécifiques au domaine en un type "générique", afin d'éviter les avertissements du compilateur concernant les incompatibilités de type dans les appels à l'API sockets.

Pour obtenir toutes les données pertinentes dans cette page, à partir de glibc-2.17 (RHEL7) je vois

/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];       /* Address data.  */
  };

où SOCKADDR_COMMON est un uint16_t. La taille totale est donc de 16B.

IP (protocole Internet) Domaine spécifique, de man 7 ip:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

Premier essai

inet_ntoa( ((struct sockaddr_in) peer_addr).sin_addr )

Problème

error: conversion to non-scalar type requested

Deuxième essai

 inet_ntoa( ((struct sockaddr_in *) &peer_addr)->sin_addr ) ));

Problème

error: dereferencing type-punned pointer might break strict-aliasing rules [-Werror=strict-aliasing]

Troisième essai: inet_pton, plus moderne de toute façon, thread-safe, prend vide *

char peer_addr_str[ INET_ADDRSTRLEN ];
inet_ntop( AF_INET, &peer_addr, peer_addr_str, INET_ADDRSTRLEN );

ok, ça marche. La chaîne décimale et points lisible par l'homme est en peer_addr_str.

0
FDS