Sockaddr union и getaddrinfo()

Я пишу программу сокета UDP, которой предоставляется адрес из командной строки.

Чтобы упростить отправку и запись, я использую getaddrinfo для преобразования адреса в структуру sockaddr: либо sockaddr_in, либо sockaddr_in6. Теперь я понимаю, что я должен использовать союз sockaddrs:

typedef union address
{
    struct sockaddr s;
    struct sockaddr_in s4;
    struct sockaddr_in6 s6;
    struct sockaddr_storage ss;
} address_t;

Как я понимаю, они не могут быть указателями, чтобы не скрывать строгие проблемы с алиасами. У меня возникли проблемы с беспрепятственным помещением информации из addrinfo из getaddrinfo в этот address_t:

struct addrinfo hint, *serv = NULL;
address_t addr;

hint.ai_family = AF_UNSPEC;
hint.ai_flags = 0;
hint.ai_socktype = SOCK_DGRAM;
hint.ai_protocol = IPPROTO_UDP;

ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &serv);
//address_sr and s_port are strings with the address and port

switch (serv->ai_addr) {
    case AF_INET: {
        addr->s4 = * (sockaddr_in*) serv->ai_addr;
        //Here I want to fill the address_t struct with information
        //This line causes a segfault 
    }
    break;
    case AF_INET6: {
        addr->s6 = * (sockaddr_in6*) serv->ai_addr;
        //Conversion here
    }
    break;

Также копируем память:

    memcpy(&addr, serv->ai_addr, serv->ai_addrlen);

Вызывает и segfault.

Как именно я должен это сделать? Я пробовал дюжину разных способов, и я просто не могу понять это. Как мне добавить адрес от addrinfo в этот союз? Я использую sockaddr_storage или sockaddr_ins?

РЕДАКТИРОВАТЬ: редактирование для ясности и дополнительной информации кода.

4 ответа

Я думаю, что вы не правильно понимаете getaddrinfo.

О третьем аргументе:

const struct addrinfo *hints

Аргумент hints указывает на структуру addrinfo, которая задает критерии для выбора структур адреса сокета, возвращаемых в списке, на который указывает res. Если hints не равен NULL, это указывает на структуру addrinfo, чьи ai_family, ai_socktype и ai_protocol определяют критерии, которые ограничивают набор адресов сокетов, возвращаемых getaddrinfo()[...]

Например, вы можете запросить только семейство адресов IPv4 и / или только сокеты датаграмм (что может быть хорошо, если вы попытаетесь использовать UDP). По сути, вы предоставляете экземпляр addrinfo, устанавливаете поля интереса, а затем передаете указатель на него функции в качестве третьего аргумента:

struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */

ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &serv);

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

Функция getaddrinfo () выделяет и инициализирует связанный список структур addrinfo, по одному для каждого сетевого адреса, который соответствует узлу и услуге, с учетом любых ограничений, наложенных подсказками, и возвращает указатель на начало списка в res. Элементы в связанном списке связаны полем ai_next.

Таким образом, вы должны просмотреть результат функции следующим образом:

for (rp = serv; rp != NULL; rp = rp->ai_next)

Я настоятельно рекомендую внимательно прочитать документацию по указанной мной ссылке. Существует также длинный и подробный пример, который может решить ваши проблемы в одиночку.

sockaddr_storage достаточно велик, чтобы вместить любой sockaddr_... типа, таким образом, делая address_t такой же большой. Итак, я бы просто memcpy() целиком serv->ai_addr в одну операцию и избавиться от switchНапример:

struct addrinfo hints = {};
struct addrinfo *addrs, *serv;
address_t addr;

hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
... 

ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &addrs);
if (ret == 0)
{
    for (serv = addrs; serv != NULL; serv = serv->ai_next)
    {
        memcpy(&addr, serv->ai_addr, serv->ai_addrlen);
        ...
    }
    freeaddrinfo(addrs);
} 

Вам нужно отменить ссылку на указатель.

addr->s4 = *(sockaddr_in*) serv->ai_addr;

Я немного опоздал на вечеринку, но меня также интересовало использование объединения для различных структур адресов сокетов, и я нашел этот вопрос. Я не смог понять, что не так с адресацией, приведением типов и ошибками сегментирования, потому что пропустил полный пример, в частности, что такоеaddress_srиs_port.

Поэтому я использовал здесь предложение Реми Лебо , чтобы закодировать пример копирования и вставки, чтобы изучить ситуацию. Я обнаружил, что при объединении приведение типов не требуется. Чтобы показать, как получить доступ к членамaddress_t addrUnion без ошибок Segfault, я просто вывожу их в std::cout. Вот пример:

      #include <netdb.h>
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

typedef union {
    struct sockaddr s;
    struct sockaddr_in s4;
    struct sockaddr_in6 s6;
    struct sockaddr_storage ss;
} address_t;

struct addrinfo hint{};
struct addrinfo *addrs, *serv;
address_t addr;

std::string address_sr{"example.com"}; // Resolves real IP addresses by DNS
std::string s_port{"https"};


int main() {
    hint.ai_family = AF_UNSPEC;
    hint.ai_socktype = SOCK_DGRAM;
    // hint.ai_protocol = IPPROTO_UDP; // Already given by SOCK_DGRAM

    int ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &addrs);
    if (ret != 0) {
        std::cerr << "ERROR! " << gai_strerror(ret) << ".\n";
        return 1;
    }

    for (serv = addrs; serv != NULL; serv = serv->ai_next) {
        memcpy(&addr, serv->ai_addr, serv->ai_addrlen);

        char addr_buf[INET6_ADDRSTRLEN]{};
        switch (addr.ss.ss_family) {
        case AF_INET: {
            const char* ret = inet_ntop(AF_INET, &addr.s4.sin_addr, addr_buf,
                                        sizeof(addr_buf));
            if (ret == nullptr) {
                std::cerr << "ERROR! " << std::strerror(errno) << ".\n";
                return 3;
            }
            std::cout << address_sr << " AF_INET  = " << addr_buf << ":"
                      << ntohs(addr.s4.sin_port) << "\n";
        } break;

        case AF_INET6: {
            const char* ret = inet_ntop(AF_INET6, &addr.s6.sin6_addr, addr_buf,
                                        sizeof(addr_buf));
            if (ret == nullptr) {
                std::cerr << "ERROR! " << std::strerror(errno) << ".\n";
                return 1;
            }
            std::cout << address_sr << " AF_INET6 = [" << addr_buf << "]:"
                      << ntohs(addr.s6.sin6_port) << "\n";
        } break;

        default:
            std::cerr << "ERROR! invalid address family " << addr.ss.ss_family << ".\n";
            return 1;
        } // switch
    } // for

    freeaddrinfo(addrs);
    return 0;
}

Вывод программы на моей тестовой машине:

      example.com AF_INET6 = [2606:2800:220:1:248:1893:25c8:1946]:443
example.com AF_INET  = 93.184.216.34:443

Я скомпилировал его с помощью:

      g++ -std=c++11 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated example.cpp
Другие вопросы по тегам