Подтвердить что ты не робот

Рассуждение за C гнездами sockaddr и sockaddr_storage

Я рассматриваю такие функции, как connect() и bind() в C-сокетах, и замечаю, что они берут указатель на структуру sockaddr. Я читал и делал ваше приложение AF-Independent, полезно использовать указатель struct sockaddr_storage и направить его на указатель sockaddr из-за всего дополнительного пространства, которое оно имеет для больших адресов.

Мне интересно, как функции, такие как connect() и bind(), которые запрашивают указатель sockaddr, обращаются к данным из указателя, который указывает на большую структуру, чем тот, который он ожидает. Несомненно, вы передаете ему размер структуры, которую вы ему предоставляете, но каков фактический синтаксис, используемый этими функциями для получения IP-адреса от указателей на более крупные структуры, которые вы наложили на struct *sockaddr?

Вероятно, потому, что я пришел из языков ООП, но это похоже на хак и немного грязный.

4b9b3361

Ответ 1

Функции, которые ожидают указателя на struct sockaddr, вероятно, будут вызывать указатель, который вы отправляете им, в sockaddr, когда вы отправляете им указатель на struct sockaddr_storage. Таким образом, они получают доступ к нему, как если бы он был struct sockaddr.

struct sockaddr_storage предназначен для размещения как в struct sockaddr_in, так и struct sockaddr_in6

Вы не создаете свой собственный struct sockaddr, вы обычно создаете struct sockaddr_in или struct sockaddr_in6 в зависимости от того, какую версию IP вы используете. Чтобы не пытаться узнать, какую версию IP вы будете использовать, вы можете использовать struct sockaddr_storage, который может содержать. Это, в свою очередь, будет приписываться struct sockaddr функциями connect(), bind() и т.д. И доступ к ним.

Вы можете увидеть все эти структуры ниже (заполнение является специфичным для реализации, для целей выравнивания):

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];
};

Итак, как вы можете видеть, если функция ожидает адрес IPv4, она будет просто читать первые 4 байта (потому что предполагается, что struct имеет тип struct sockaddr. В противном случае он будет читать полные 16 байт для IPv6).

Ответ 2

В С++ классам с хотя бы одной виртуальной функцией задается TAG. Этот тег позволяет использовать dynamic_cast<>() для любого из классов, из которых происходит ваш класс, и наоборот. TAG позволяет dynamic_cast<>() работать. Более или менее это может быть число или строка...

В C мы ограничены структурами. Однако структурам также может быть назначена TAG. Фактически, если вы посмотрите на все структуры, которые опубликовал theprole в его ответе, вы заметите, что все они начинаются с 2 байтов (без знака), который представляет то, что мы называем семейством адреса. Это точно определяет структуру и, следовательно, ее размер, поля и т.д.

Поэтому вы можете сделать что-то вроде этого:

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;

  }
}

Как вы можете видеть, функция может проверять параметр len, чтобы убедиться, что длина достаточна для соответствия ожидаемой структуре, и поэтому они могут reinterpret_cast<>() (как это было бы вызывать в С++) ваш указатель. Правильность данных в структуре зависит от вызывающего. В этом нет большого выбора. Ожидается, что эти функции будут проверять всевозможные вещи до того, как они будут использовать данные, и возвращают -1 и errno всякий раз, когда обнаружена проблема.

Таким образом, у вас есть struct sockaddr_in или struct sockaddr_in6, которые вы (реинтерпрет) добавили в функцию struct sockaddr и bind() (и другие), чтобы вернуть этот указатель к struct sockaddr_in или struct sockaddr_in6 после проверки элемента sa_family и проверки размера.