Razonamiento detrás de los sockets C sockaddr y sockaddr_storage

6 minutos de lectura

Estoy viendo funciones como connect() y bind() en sockets C y observe que llevan un puntero a un sockaddr estructura He estado leyendo y para hacer su aplicación AF-Independiente, es útil usar el sockaddr_storage puntero de estructura y convertirlo en un sockaddr puntero debido a todo el espacio adicional que tiene para direcciones más grandes.

Lo que me pregunto es cómo funciona como connect() y bind() que pide un sockaddr puntero accede a los datos desde un puntero que apunta a una estructura más grande que la que espera. Claro, le pasa el tamaño de la estructura que le está proporcionando, pero ¿cuál es la sintaxis real que usan las funciones para obtener la dirección IP de los punteros a estructuras más grandes que ha enviado a struct *sockaddr?

Probablemente sea porque vengo de los lenguajes OOP, pero parece un truco y un poco desordenado.

avatar de usuario
el prole

Funciones que esperan un puntero para struct sockaddr probablemente encasille el puntero al que los envía sockaddr cuando les envías un puntero a struct sockaddr_storage. De esa forma, acceden a él como si fuera un struct sockaddr.

struct sockaddr_storage está diseñado para encajar tanto en un struct sockaddr_in y struct sockaddr_in6

Tu no creas el tuyo struct sockaddrnormalmente creas un struct sockaddr_in o un struct sockaddr_in6 dependiendo de la versión de IP que estés usando. Para evitar tratar de saber qué versión de IP usará, puede usar un struct sockaddr_storage que puede contener cualquiera. Esto a su vez se encasillará en struct sockaddr por las funciones connect(), bind(), etc. y se accede de esa manera.

Puede ver todas estas estructuras a continuación (el relleno es específico de la implementación, con fines de alineación):

struct sockaddr {
   unsigned short    sa_family;    // address family, AF_xxx
   char              sa_data[14];  // 14 bytes of protocol address
};


struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};


struct sockaddr_in6 {
    u_int16_t       sin6_family;   // address family, AF_INET6
    u_int16_t       sin6_port;     // port number, Network Byte Order
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

Entonces, como puede ver, si la función espera una dirección IPv4, solo leerá los primeros 4 bytes (porque asume que la estructura es del tipo struct sockaddr. De lo contrario, leerá los 16 bytes completos para IPv6).

  • Así que supongamos que tengo un puntero a un struct sockaddr_storage llamó sas y todos los campos de la estructura han sido debidamente rellenados con la dirección y la familia de direcciones. Ahora realizo esto: struct sockaddr *s = (struct sockaddr*)sas. ¿Cómo obtendría la dirección de s ¿ahora?

    –Matt Vaughan

    15 de abril de 2013 a las 8:36


  • no usarías un struct sockaddr directamente. Lo que harías es encasillarlo de nuevo a sockaddr_storage o sockaddr_in y luego leerlo. Como puedes ver en mi publicación. sockaddr tiene suficiente espacio para una dirección IPv4 o v6. Habiendo dicho eso, no sé por qué querrías encasillar a struct sockaddr para cualquier otra cosa que no sean los parámetros cuyas funciones lo requieren. struct sockaddr no está diseñado para ‘uso del programador’.

    – el prole

    15 de abril de 2013 a las 8:43


  • Estoy leyendo un libro que dice que sockaddr no es lo suficientemente grande para contener sockaddr_in6.

    –Matt Vaughan

    15 de abril de 2013 a las 9:08

  • Bueno, el tamaño de sockaddr es de 16, mientras que el tamaño de sockaddr_storage es de 128 bytes. Dado que la dirección IPv6 es de 16 bytes, es difícil ver cómo sockaddr acomodaría la dirección IPv6.

    – flautista de Hamelín

    8 dic 2013 a las 20:38

  • Si alguien está confundido por sa_len / sin_len: consulte stackoverflow.com/a/23627149/2013911 (en resumen: estos atributos son específicos de la implementación => no portátiles)

    – Niklas Peter

    30 de enero de 2016 a las 11:24

avatar de usuario
alexis wilke

En C++, las clases con al menos una función virtual reciben una ETIQUETA. Esa etiqueta te permite dynamic_cast<>() a cualquiera de las clases de las que deriva su clase y viceversa. El TAG es lo que permite dynamic_cast<>() trabajar. Más o menos, esto puede ser un número o una cadena…

En C estamos limitados a las estructuras. Sin embargo, a las estructuras también se les puede asignar una ETIQUETA. De hecho, si miras todas las estructuras que el prole publicado en su respuesta, notará que todos comienzan con 2 bytes (un corto sin firmar) que representa lo que llamamos la familia de la dirección. Esto define exactamente qué es la estructura y, por lo tanto, su tamaño, campos, etc.

Por lo tanto, puedes hacer algo como esto:

int bind(int fd, struct sockaddr *in, socklen_t len)
{
  switch(in->sa_family)
  {
  case AF_INET:
    if(len < sizeof(struct sockaddr_in))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in *p = (struct sockaddr_in *) in;
      ...
    }
    break;

  case AF_INET6:
    if(len < sizeof(struct sockaddr_in6))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in6 *p = (struct sockaddr_in6 *) in;
      ...
    }
    break;

  [...other cases...]

  default:
    errno = EINVAL; // family not supported
    return -1;

  }
}

Como puede ver, la función puede comprobar la len parámetro para asegurarse de que la longitud es suficiente para adaptarse a la estructura esperada y, por lo tanto, pueden reinterpret_cast<>() (como se llamaría en C++) su puntero. Si los datos son correctos en la estructura depende de la persona que llama. No hay muchas opciones en ese extremo. Se espera que estas funciones verifiquen todo tipo de cosas antes de usar los datos y devolver -1 y errno siempre que se encuentre un problema.

Entonces, en efecto, usted tiene un struct sockaddr_in o struct sockaddr_in6 que tú (reinterpretas) echas a un struct sockaddr y el bind() (y otras) lanzan ese puntero de vuelta a un struct sockaddr_in o struct sockaddr_in6 después de que comprobaron el sa_family miembro y verificó el tamaño.

  • Aunque esta no es una pregunta de c ++, tal vez podría aclarar que no todas las clases de c ++ tienen una ETIQUETA, sino solo las que tienen al menos una función virtual.

    – Mike MB

    23 de junio de 2016 a las 22:51

  • @MikeMB El OP escribió: “Probablemente sea porque vengo de lenguajes orientados a objetos”lo que estoy pensando significa que quiere entender cómo funciona C en comparación con lo que aprendió antes.

    – Alexis Wilke

    24 de junio de 2016 a las 18:23

  • Lo siento, probablemente debería haberme expresado más claramente: no he tenido ningún problema con que hagas referencia a c++ en absoluto. Solo quería asegurarme de que…a pesar de que no es una pregunta dirigida a los programadores de c++ – las partes que hacer referir a c++ son lo más precisos posible (en caso de que un principiante de c++ tropiece con él, como lo hice yo),

    – Mike MB

    24/06/2016 a las 22:44

¿Ha sido útil esta solución?