1

I'm writing a UDP socket program which is provided an address from the command line.

To make sending and writing easier, I'm using getaddrinfo to convert the address to a sockaddr struct: either sockaddr_in or sockaddr_in6. Now I understand that I should use a union of sockaddrs:

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

As I understand they can't be pointers to avoid hiding strict aliasing problems. I'm having trouble seamlessly putting the information from getaddrinfo's addrinfo into this 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;

Also, copying the memory:

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

Causes a segfault too.

How exactly should I do this? I tried a dozen different ways and I just can't figure it out. How do I put an address from addrinfo to this union? Do I use sockaddr_storage or the sockaddr_ins?

EDIT: Editing for clarity and additional code information.

m_highlanderish
  • 499
  • 1
  • 6
  • 16

4 Answers4

1

You need to de-reference the pointer.

addr->s4 = *(sockaddr_in*) serv->ai_addr;
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
1

I think you're not getting getaddrinfo right.

About the third argument, :

const struct addrinfo *hints

The hints argument points to an addrinfo structure that specifies criteria for selecting the socket address structures returned in the list pointed to by res. If hints is not NULL it points to an addrinfo structure whose ai_family, ai_socktype, and ai_protocol specify criteria that limit the set of socket addresses returned by getaddrinfo()[...]

For example, you can ask for IPv4 address family only, and/or for datagram sockets only (which could be fine given your attempt to use UDP). Basically, you provide an addrinfo instance, set the fields of interest, then pass a pointer to it to the function, as its third argument:

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);

In this example, the function could return not just one, but a whole list of address structures:

The getaddrinfo() function allocates and initializes a linked list of addrinfo structures, one for each network address that matches node and service, subject to any restrictions imposed by hints, and returns a pointer to the start of the list in res. The items in the linked list are linked by the ai_next field.

So you have to loop through the function result this way:

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

I strongly suggest to carefully read the documentation at the link I provided. There is also a long and detailed example which could alone solve your issues.

p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
  • I understand how getaddrinfo works and what it returns. I am specifically asking how to properly fill address_t with the first answer from addrinfo. I read through all the Beejs Guide to Network Programming and a dozen other SO posts and while some mention the union none of them give an explicit example. So far [this](https://stackoverflow.com/questions/47846444/sockaddr-union-and-getaddrinfo/47846998#47846998) and [that](https://stackoverflow.com/questions/47846444/sockaddr-union-and-getaddrinfo/47846998#47846998) answer don't work as they give segfaults. – m_highlanderish Dec 16 '17 at 22:07
  • I really don't understand why you have to use that union. Even in beejs examples, the structures returned by getaddrinfo are directly used in socket/connect/bind and freeaddrinfo is called right after. No reason to copy and paste sockaddr struct around. – p-a-o-l-o Dec 16 '17 at 22:34
  • Because I have a UDP server and client programs and the server has multiple clients to which he constantly sends information. Since I'm doing it single-threadedly I have to store the information because I send more packets to them than I receive. Hence, the need for storing the addresses. I also think it's a glaring omission from Beejs guide. Not a single proper example of even using sockaddr_storage. – m_highlanderish Dec 16 '17 at 23:00
  • If I got you right, you don't need getaddrinfo at all, but this: http://www.beej.us/guide/bgnet/output/html/multipage/getpeernameman.html – p-a-o-l-o Dec 16 '17 at 23:54
1

sockaddr_storage is large enough to hold any sockaddr_... type, thus making address_t just as large. So, I would just memcpy() the entire serv->ai_addr in one operation and get rid of the switch, eg:

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);
} 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

Coming bit late to the party but I was also interested in using a union for the different socket address structures and found this question. I wasn't able to understand what's going wrong with addressing, type cast and segfaults because of missing a complete example, in particular what are address_sr and s_port.

So I used Remy Lebeau's suggestion here to code a copy and paste example to examine the situation. I found that with the union there is no type cast needed. To show how to access the members of the address_t addr union without segfaults I just output them to std::cout. Here is the example:

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

The output of the program on my test machine is:

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

I compiled it with:

g++ -std=c++11 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated example.cpp
Ingo
  • 588
  • 9
  • 21