c++socketsudpgetaddrinfo

getaddrinfo, bind, localhost and INADDR_ANY


I'm learning socket programing and pretty new to this... My question is, I want to write a UDP server that is able to receive packets from both remote server and local host via a OS-assigned socket.

I know I should somehow pass INADDR_ANY to bind() to let it accept packet from both. and I guess I need to pass char *service=NULL to getaddrinfo so that it will assign a port. However, I don't know if so what to do with the node parameter of getaddrinfo.

According to getaddrino man page, node and service cannot both be NULL, yet the INADDR_ANY will only be set in the returned socket address when (1) hints.ai_flags=AI_PASSIVE and (2) node is set to NULL. I'm confused with this confliction.. And since "have an OS-assigned port" is the assignment requirement, I can't change the service I guess.

I read that INADDR_ANY is basically 0.0.0.0, does that mean I can just pass this string as node?

Any comment is welcomed!


Solution

  • It is not getaddrinfo() that assigns a random available port. It is bind() that does so when port 0 is requested.

    The getaddrinfo() man page is correct. The node and service parameters cannot both be NULL at the same time. You can set node to NULL to get INADDR_ANY - or - you can set service to NULL to get port 0. But, to get both, you will have to either:

    Any of those combinations will cause getaddrinfo() to return a pointer to a sockaddr_in that has its sin_addr set to INADDR_ANY and its sin_port set to 0.

    You can then bind() using that sockaddr_in, and bind() will choose a random available port, which you can retrieve using getsockname() afterwards.

    int server_fd = -1;
    
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    
    struct addrinfo *result;
    if( getaddrinfo(NULL, "0", &hints, &result) != 0 )
    {
        // error handling...
    }
    else if( (server_fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol)) < 0 )
    {
        // error handling...
        freeaddrinfo(result);
    }
    else if( bind(server_fd, result->ai_addr, result->ai_addrlen) < 0 )
    {
        // error handling...
        close(server_fd);
        freeaddrinfo(result);
    }
    else
    {
        freeaddrinfo(result);
    
        // use server_fd as needed...
    
        struct sockaddr_in bound_addr;
        socklen_t addrlen = sizeof(bound_addr); 
      
        if( getsockname(server_fd, (struct sockaddr*)&bound_addr, &addrlen) < 0 )
        {
            // error handling...
        } 
        else
        {
            // use ntohs(bound_addr.sin_port) as needed...
        }
    
        ...
    
        close(server_fd);
    }
    

    Or, since you know exactly what you want to bind() to, you can simply ignore getaddrinfo() and populate a sockaddr_in manually:

    int server_fd;
    
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = 0;
    
    if( (server_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 )
    {
        // error handling...
    }
    else if( bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0 )
    {
        // error handling...
        close(server_fd);
    }
    else
    {
        // use server_fd as needed...
    
        struct sockaddr_in bound_addr;
        socklen_t addrlen = sizeof(bound_addr); 
      
        if( getsockname(server_fd, (struct sockaddr*)&bound_addr, &addrlen) < 0 )
        {
            // error handling...
        } 
        else
        {
            // use ntohs(bound_addr.sin_port) as needed...
        }
    
        ...
    
        close(server_fd);
    }