Сокеты iOS Поддержка IPv6

Я получаю сообщение об ошибке, когда пытаюсь подключиться к своему серверу ipv4. В настоящее время пользователи приложения ios должны ввести IP-адрес своего сервера, порт и информацию об учетной записи.

Затем приложение ios вызывает Connect для класса SocketSender (включенного в путь поиска заголовка), который, в свою очередь, вызывает функцию подключения Socket.h, а затем проверяет результаты.

Подключиться — SocketSender.cpp

bool SocketSender::Connect (const char *host, int port, CApiError &err)
{

errno = 0;
struct hostent *hostinfo;

hostinfo = gethostbyname (host);

if (!hostinfo) {
#ifdef PLATFORM_WIN32
 m_nLastErrorNo = SOCKET_ERRNO();
 err.SetSystemError(m_nLastErrorNo);
#else
/* Linux stores the gethostbyname error in h_errno.  */
m_nLastErrorNo = EINVAL; // h_errno value is incompatible with the "normal" error codes
err.SetError(FIX_SN(h_errno, hstrerror(h_errno)), CATEGORY_SYSTEM | ERR_TYPE_ERROR);
#endif
return false;
}

socket_fd = socket (AF_INET, SOCK_STREAM, 0);

 if (socket_fd == -1) {
  m_nLastErrorNo = SOCKET_ERRNO();
  err.SetSystemError(m_nLastErrorNo);
  return false;
 }

 struct sockaddr_in address;

 address.sin_family = AF_INET;
 address.sin_port = htons (port);
 address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list;

 int result;

 SetSocketOptions();

 result = connect (socket_fd, (struct sockaddr *) &address, sizeof (address));

 if (result == -1) {
 if (IS_IN_PROGRESS()) {
  fd_set f1,f2,f3;
  struct timeval tv;

  /* configure the sets  */
  FD_ZERO(&f1);
  FD_ZERO(&f2);
  FD_ZERO(&f3);
  FD_SET(socket_fd, &f2);
  FD_SET(socket_fd, &f3);

  /* we will have a timeout period */
  tv.tv_sec  = 5;
  tv.tv_usec = 0;

  int selrez = select(socket_fd + 1,&f1,&f2,&f3,&tv);

  if (selrez == -1) { // socket error
    m_nLastErrorNo = SOCKET_ERRNO();
    Disconnect(true);
    err.SetSystemError(m_nLastErrorNo);
    return false;
  }

  if (FD_ISSET(socket_fd, &f3)) { // failed to connect ..
    int sockerr = 0;
#ifdef PLATFORM_WIN32
    int sockerr_len = sizeof(sockerr);
#else
    socklen_t sockerr_len = sizeof(sockerr);
#endif
    getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
    if (sockerr != 0) {
      m_nLastErrorNo = sockerr;
    } else {
#ifdef PLATFORM_WIN32
      m_nLastErrorNo = ERROR_TIMEOUT;  // windows actually does not specify the error .. is this ok?
#else
      m_nLastErrorNo = ETIMEDOUT;
#endif
    }
    Disconnect(true);
    err.SetSystemError(m_nLastErrorNo);
    return false;
  }

  if (!FD_ISSET(socket_fd, &f2)) { // cannot read, so some (unknown) error occured (probably time-out)
    int sockerr = 0;
#ifdef PLATFORM_WIN32
    int sockerr_len = sizeof(sockerr);
#else
    socklen_t sockerr_len = sizeof(sockerr);
#endif
    getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
    if (sockerr != 0) {
      m_nLastErrorNo = sockerr;
    } else {
#ifdef PLATFORM_WIN32
      m_nLastErrorNo = ERROR_TIMEOUT;  // windows actually does not specify the error .. is this ok?
#else
      m_nLastErrorNo = ETIMEDOUT;
#endif
    }
    Disconnect(true);
    err.SetSystemError(m_nLastErrorNo);
    return false;
  }
#ifndef PLATFORM_WIN32 // FIXME: is the same needed for windows ?

  // unix always marks socket as "success", however error code has to be double-checked
  int error = 0;
  socklen_t len = sizeof(error);
  if (getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
    err.SetSystemError();
    return false;
  }
  if(error != 0) {
    m_nLastErrorNo = error;
    Disconnect(true);
    err.SetSystemError(m_nLastErrorNo);
    return false;
  }
#endif
} else {
  m_nLastErrorNo = SOCKET_ERRNO();
  Disconnect(true);
  err.SetSystemError(m_nLastErrorNo);
  return false;
}
}

m_nIP = ntohl(address.sin_addr.s_addr);

m_bServerSocket = false;
return true;
}

Это оригинальная версия, которая работала без проблем. Когда я изменил приведенное выше, чтобы использовать AF_INET6 и in_addr6->sin6_addr, я продолжал получать ошибки, и приложению не удавалось подключиться. Я пытался использовать getaddrinfo, но это все еще не соединилось.

struct addrinfo hints, *res, *res0;
int error;
const char *cause = NULL;

memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(host, "PORT", &hints, &res0);
if (error) {
    errx(1, "%s", gai_strerror(error));
    /*NOTREACHED*/
}
socket_fd = -1;
printf("IP addresses for %s:\n\n", host);
int result;
void *addr;
char *ipver;
for (res = res0; res!=NULL; res = res->ai_next) {
    socket_fd = socket(res->ai_family, res->ai_socktype,
               res->ai_protocol);
    if (socket_fd < 0) {
        cause = "socket";
        continue;
    }

    if ((result = connect(socket_fd, res->ai_addr, res->ai_addrlen)) < 0) {
        cause = "connect";
        close(socket_fd);
        socket_fd = -1;
        continue;
    }
    // get the pointer to the address itself,
    // different fields in IPv4 and IPv6:
    if (res->ai_family == AF_INET) { // IPv4
        struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
        addr = &(ipv4->sin_addr);
        ipver = "IPv4";
    } else { // IPv6
        struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
        addr = &(ipv6->sin6_addr);
        ipver = "IPv6";
    }
    SetSocketOptions();
    break;  /* okay we got one */
}

Мне нужно сделать его обратно совместимым с ipv6 и ipv4. Любая помощь будет высоко оценена, так как я застрял в тестировании на прошлой неделе. Также, если кто-нибудь знает, как отлаживать SocketSender.cpp в XCode, это очень поможет.


person 3rdeye7    schedule 13.07.2016    source источник
comment
В чем ТОЧНО у вас проблема? Вы говорите, что есть ошибки, но не говорите, что это за ошибки на самом деле или какая строка кода сообщает об ошибках. В любом случае, ваш код gethostbyname() изначально был взломан. Вы не проверяли поле hostinfo->h_addrtype, чтобы убедиться, что IP-адрес(а) (да, во множественном числе) имеют тот же тип, который вы ожидаете, и вы не перебирали весь список. gethostbyname() может возвращать либо адреса IPv4, либо IPv6, и вы не можете контролировать, какой тип отчетов. getaddrinfo() обеспечивает этот контроль, так что да, используйте его.   -  person Remy Lebeau    schedule 14.07.2016
comment
@RemyLebeau, когда я использую getaddrinfo(), перебираю список и подключаюсь, используя функцию подключения в socket.h (сетевая библиотека). Функция подключения возвращает -1, что означает, что соединение не установлено. Чтобы уточнить, я пытаюсь сделать то же самое, что и socketsender.cpp для ipv6, но он не подключается.   -  person 3rdeye7    schedule 14.07.2016
comment
Вы уже пробовали проверить errno? Он расскажет вам, почему connect() терпит неудачу. Вы пробовали регистрировать IP-адреса, о которых сообщает getaddrinfo()? Подключено ли ваше устройство к сети, которая может получить доступ к этим IP-адресам?   -  person Remy Lebeau    schedule 14.07.2016
comment
@3rdeye7: Если у вас возникли проблемы с подключением с использованием адресов, возвращенных getaddrinfo, и вы передаете литерал IPv4 и числовой порт в getaddrinfo, возможно, вы столкнулись с эта ошибка, возвращающая структуры с 0 портами; вам нужно вручную установить правильное количество портов после того, как вы получите структуры, чтобы обойти эту ошибку.   -  person user102008    schedule 14.07.2016
comment
@user102008 user102008 Большое спасибо, это было именно так, наконец-то все заработало. Не могу поверить, что я не сталкивался с этим на форуме разработчиков. Я добавил ответ для справки, если у кого-то возникают такие же/похожие проблемы.   -  person 3rdeye7    schedule 15.07.2016


Ответы (1)


Итак, после двух недель тестирования различных подходов и ознакомления с сетью (POSIX) я, наконец, заставил это работать, в основном благодаря предложению @user102008.

Это относится к приложениям клиент-сервер. Мое приложение — это клиентское приложение, которое подключается к серверу/системе IPv4 в удаленном месте. Нам еще предстоит поддерживать IPv6 для наших продуктов, которые включают клиенты (iOS, android, windows, unix) и серверы (windows и unix), но поддержка будет реализована в будущих выпусках. Причина этой поддержки заключалась исключительно в том, что Apple изменила свою среду процесса проверки Apple.

Подход, советы и проблемы

  1. Apple предоставила способ проверить совместимость IPv6 с вашим приложением. Это совместное использование вашего соединения из Ethernet с использованием NAT64/DNS64. Мне это много раз не удавалось. После изучения и сброса настроек SMC я наткнулся на эту статью и понял, что, возможно, ошибался с конфигурацией слишком много. Поэтому я перезагрузил свой SMC, перезапустил и создал узел общего доступа к Интернету. Всегда не забывайте отключать Wi-Fi, прежде чем вносить какие-либо изменения в общий доступ к Интернету.
  2. Пользователи должны были подключаться к серверу с IP-адресом IPv4. Приложение отлично работало в сети IPv4, но не работало в сети IPv6. Это произошло из-за того, что приложение не разрешало литерал IP-адреса. Сетевая библиотека, которую использует мое приложение, — это библиотека cpp, которая была включена в качестве макроса предварительной обработки. Одной из самых больших неприятностей была попытка отладки, потому что вы не можете отлаживать код времени компиляции. Итак, что я сделал, так это переместил свои файлы cpp с их заголовками в проект (к счастью, это было всего 3 файла).
  3. ВАЖНО ДЛЯ ВСЕХ, ПЕРЕДАЮЩИХ НОМЕРА ПОРТОВ. Это связано с пунктом 2 и разрешением литерала IPv4. Я использовал точную реализацию Apple в обзоре сети (листинг 10-1). Каждый раз, когда я тестировал, функция подключения возвращала -1, что означает, что она не подключалась. Благодаря тому, что @user102008 предоставил мне эту статью, я понял, что реализация getaddrinfo в Apple не работает, когда пытается передать строковый литерал для порта. Да, они запрашивают постоянный символ, даже при попытке c_str() он все равно вернет номер порта 0. По этой причине разработчик Apple, который, как я заметил, ответил и решает бесчисленные проблемы с сетью, предоставил обходной путь. Это устранило мою проблему с портом, постоянно возвращающим 0, код также опубликован ниже. Что я сделал, так это просто добавил это в свой сетевой класс (SocketSender.cpp) и вместо вызова getaddrinfo в Connect я вызвал getaddrinfo_compat. Это позволило мне отлично подключаться к сетям IPv4 и IPv6.

    static int getaddrinfo_compat(  
    const char * hostname,  
    const char * servname,  
    const struct addrinfo * hints,  
    struct addrinfo ** res  
    ) {  
       int    err;  
       int    numericPort;  
    
        // If we're given a service name and it's a numeric string, set `numericPort` to that,  
       // otherwise it ends up as 0.  
    
       numericPort = servname != NULL ? atoi(servname) : 0;  
    
       // Call `getaddrinfo` with our input parameters.  
    
       err = getaddrinfo(hostname, servname, hints, res);  
    
      // Post-process the results of `getaddrinfo` to work around   <rdar://problem/26365575>.  
    
    if ( (err == 0) && (numericPort != 0) ) {  
    for (const struct addrinfo * addr = *res; addr != NULL; addr = addr->ai_next) {  
        in_port_t *    portPtr;  
    
        switch (addr->ai_family) {  
            case AF_INET: {  
                portPtr = &((struct sockaddr_in *) addr->ai_addr)->sin_port;  
            } break;  
            case AF_INET6: {  
                portPtr = &((struct sockaddr_in6 *) addr->ai_addr)->sin6_port;  
            } break;  
            default: {  
                portPtr = NULL;  
            } break;  
        }  
        if ( (portPtr != NULL) && (*portPtr == 0) ) {  
            *portPtr = htons(numericPort);  
        }  
    }  
    }  
    return err;  
    } 
    
  4. На самом деле я сохраняю IP (address.sin_addr.s_addr) в длинном типе данных, который является частной переменной m_nIP. Проблема заключалась в том, что мне не нужен был IPv6, так как все наши группы продуктов используют IPv4. Решил это, используя код ниже.

    const uint8_t *bytes = ((const struct sockaddr_in6 *)addrPtr)->sin6_addr.s6_addr;
    bytes += 12;
    struct in_addr addr = { *(const in_addr_t *)bytes };
    m_nIP = ntohl(addr.s_addr);
    
  5. СООТВЕТСТВУЮЩИЕ РУКОВОДСТВА Руководство Beej по сетевому программированию, Введение в IPv6 на уровне пользователя, Перенос приложений на IPv6

person 3rdeye7    schedule 15.07.2016
comment
Многие мобильные сети поддерживают только IPv6. Все остальные двухуровневые. Все сети выполняют некоторую трансляцию NAT, иногда более одного раза, чтобы трафик IPv4 работал. Вы увидите меньшую задержку и лучшую производительность, если будете поддерживать IPv6 на обоих концах соединения. - person Michael Hampton; 22.07.2016