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 addr
Union без ошибок 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