3
votes

I'm trying to make a server to listen on both IPv4 and IPv6 in dual stack mode.

I want the same port number for both IPv4 and IPv6 servers, and I want it to be on a random selection of port (using port "0")

when I bind, each server get different port, and I want it to be the same.

so I thought it should be done by the getaddrinfo function.

But when I give it the "0" port it stays "0" in the addrinfo results, what cause each bind to give me different port number.

My question: Is there a way to tell the getaddrinfo to select a single free port which is free on all interfaces, then bind the given address to all interfaces?

if there isn't, is there other way to find a free port number? (without binding and stop when fail)

please refer to the following code:

EDIT: now the code can fully compiled on VS 10.

#ifdef WIN32
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#define closesocket close
#endif
#include <iostream>
#include <vector>  
#include <stdio.h>
#include <string>

int GetAddressFamily()
{
    return AF_UNSPEC;
}

std::string ipaddress(addrinfo* info)
{
    std::string retval;   
    char temp[260];
    socklen_t addrlen = (socklen_t)info->ai_addrlen;
    int res = ::getnameinfo(info->ai_addr, addrlen, temp, 256, NULL, 0, NI_NUMERICHOST);
    if(res){
        std::cout<<gai_strerrorA(res)<<std::endl;
    }else{
        retval = temp;
    }
    return retval;
}

int getport(addrinfo* info)
{
    int retval=0;

    if (info->ai_family == AF_INET) {
        retval = htons(((struct sockaddr_in*)(info->ai_addr))->sin_port);
    }else{
        retval = htons(((struct sockaddr_in6*)(info->ai_addr))->sin6_port);
    }
    return retval;
}

int main()
{
    char *hostName = NULL; //GetHostName();
    int portNum = 0;
#ifdef WIN32
    WSADATA w;
    if(0 != WSAStartup(MAKEWORD(2, 2), &w))
    {
       std::cerr<<" WSAStartup() failed \n";
       return -1;
    } 
#endif
    addrinfo hints,*results,*tmp;
    memset(&hints,0,sizeof(hints));
    hints.ai_family = GetAddressFamily();
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_NUMERICSERV;
    if(hostName){
        hints.ai_flags |= AI_CANONNAME; 
        //AI_CANONNAME - fills ai_cannonname in address.
    }else{
        hints.ai_flags |= AI_PASSIVE;
        //AI_PASSIVE - give ADDR_ANY and IN6ADDR_ANY_INIT when hostName is NULL 
    }
    char portbuff[40];
    sprintf(portbuff,"%u",portNum);

    int res = ::getaddrinfo(hostName, portbuff,&hints, &results); 
    if(res){
        std::cerr<<gai_strerrorA(res)<<std::endl;
    }else{
        std::vector<int> sockets;
        for(tmp = results; tmp ; tmp=tmp->ai_next){
            std::cout<<ipaddress(tmp).c_str()<<" : "<<port(tmp)<<std::endl;
            int s = socket(tmp->ai_family,tmp->ai_socktype,tmp->ai_protocol);
            if(s != -1){
                res = bind(s, tmp->ai_addr, (int)tmp->ai_addrlen);
                if(res != -1){
                    sockaddr_storage addr;
                    socklen_t len =sizeof(addr);
                    int res = getsockname(s, (struct sockaddr *)&addr, &len);
                    std::cout<<"Bound to port: ";
                    if(addr.ss_family == AF_INET){
                        std::cout<<htons(((sockaddr_in*)&addr)->sin_port)<<std::endl;
                    }else{
                        std::cout<<htons(((sockaddr_in6*)&addr)->sin6_port)<<std::endl;
                    }
                    sockets.push_back(s);
                }
            }
        }
        for(int i=0;i<sockets.size();i++){
            closesocket(sockets[i]);
        }
    }
    ::freeaddrinfo(results);
    return 0;
}

EDIT2: My solution for now:

I added the following function to be called after first successful bind, and will set the given port to addrinfo list:

void setport(addrinfo* info,int port)
{
   for(addrinfo* tmp = info; tmp ; tmp=tmp->ai_next){
   if (tmp->ai_family == AF_INET) {
     ((struct sockaddr_in*)(tmp->ai_addr))->sin_port = htons(port);
   }else{
     ((struct sockaddr_in6*)(tmp->ai_addr))->sin6_port = htons(port);
   }
}

It should be called after successful bind:

port = getport(result)
//...after bind:   
if(port == 0) {
   port = printed value after succesful bind
   setport(result, port)
}
1
bind() to a zero port and zero address already does exactly what you want. Your edited-in code just solves the problem of how to discover the system-assigned port number.user207421
I wan't the same port for all. my example shows how each bind gives different port. and I have at least 2 bindes, 1 for IPv6 and 1 for IPv4, If I give my PC name I have more... the edit fixed it, according to the first bind. But I don't think it is safe because I don't know for sure if the first port I've got (IPv6) is also free on the last (IPv4)SHR
Which operating system are you intending this code to run on?Michael Hampton
it should be a cross platform. I test the sample code, here, on WIN 7 64 bit. but I don't want a solution to only 1 OS...SHR

1 Answers

2
votes

I think your best option is to use an IPv6 socket with IPV6_V6ONLY disabled. Then that single socket will acccept both IPv6 and IPv4 connections. IPv4 clients will have the address set to the mapped address ::ffff:a.b.c.d.

There's not really any reason to use getaddrinfo here, because you're not looking anything up. Something like this should do it (untested):

int s = socket(AF_INET6, SOCK_STREAM, 0);
if (s < 0)
    throw std::system_error(errno, generic_category());

const int off = 0;
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) {
    close(s);
    throw std::system_error(errno, generic_category());
}

struct sockaddr_in6 addr{};
socklen_t alen = sizeof(addr);
if (bind(s, static_cast<struct sockaddr*>(&addr), alen)) {
    close(s);
    throw std::system_error(errno, generic_category());
}

getsockname(s, static_cast<struct sockaddr*>(&addr), &alen);
int port = ntohs(addr.sin6_port);

PS. It's a good idea to always set the IPV6_V6ONLY option to whichever value you wish, because default varies between OS.