0

The following c function I wrote returns a file descriptor that accepts IPv4 connections but not IPv6. Could someone help me figure out what went wrong? I suspect I did not use getaddrinfo() correctly.

Open file descriptor that listens to connections.

/*
 * open_listenfd - open and return a listening socket on port
 *     Returns -1 and sets errno on Unix error.
 */
int open_listenfd(int port)
{
    const char* hostname=0;
    // Converts port to string
    char* pName = malloc(numPlaces(port) + 1);
    sprintf(pName, "%d", port);
    const char* portname= pName;

    struct addrinfo hints;
    memset(&hints,0,sizeof(hints));
    hints.ai_family=AF_UNSPEC;
    hints.ai_socktype=SOCK_STREAM;
    hints.ai_protocol= 0;
    hints.ai_flags=AI_PASSIVE|AI_ADDRCONFIG;
    struct addrinfo* res=0;
    int err=getaddrinfo(hostname,portname,&hints,&res);
    free(pName);
    if (err!=0) {
            return -1;
    }

    int listenfd, optval=1;
    struct sockaddr_in serveraddr;

    /* Create a socket descriptor */
    if ((listenfd = socket(res->ai_family,res->ai_socktype, res->ai_protocol)) < 0)
        return -1;

    /* Eliminates "Address already in use" error from bind. */
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
               (const void *)&optval , sizeof(int)) < 0)
        return -1;

    /* Listenfd will be an endpoint for all requests to port
       on any IP address for this host */
    bzero((char *) &serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = /*AF_INET;*/ AF_UNSPEC;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons((unsigned short)port);
    if (bind(listenfd, res->ai_addr, res->ai_addrlen/*(SA *)&serveraddr, sizeof(serveraddr)*/) < 0)
    return -1;
    freeaddrinfo(res);

    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, 1024) < 0)
        return -1;
    return listenfd;
}
MLD_Saturn
  • 87
  • 1
  • 1
  • 7

1 Answers1

3

If you want your server to listen on IPV4 and IPV6 addresses you need to could set up two socket to listen on.

getaddrinfo() might return information for more then one internet address for given host and/or service.

The member ai_family of the hints structure passed specifies which address family the caller is interest in. If AF_UNSPEC is specified IPV4 and IPV6 addresses might be returned.

To find out if there are such addresses available you might like to mod you code like so:

int open_listenfd(int port, int * pfdSocketIpV4, int * pfdSocketIpV6)
{
  ...

  struct addrinfo hints = {0};
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE|AI_ADDRCONFIG;
  hints.ai_next = NULL;

  struct addrinfo * res = NULL;

  int err=getaddrinfo(hostname, portname, &hints, &res);
  free(pName);
  if (err) 
  {
    return -1;
  }

  struct addrinfo * pAddrInfoIpV4 = NULL;
  struct addrinfo * pAddrInfoIpV6 = NULL;

  {
    struct addrinfo * pAddrInfo = res;

    /* Loop over all address infos found until a IPV4 and a IPV6 address is found. */
    while (pAddrInfo)
    {
      if (!pAddrInfoIpV4 && AF_INET == pAddrInfo->ai_family)
      {
        pAddrInfoIpV4 = pAddrInfo; /* Take first IPV4 address available */
      }
      else if (!pAddrInfoIpV6 && AF_INET6 == pAddrInfo->ai_family)
      {
        pAddrInfoIpV6 = pAddrInfo; /* Take first IPV6 address available */
      }
      else
      {
        break; /* Already got an IPV4 and IPV6 address, so skip the rest */
      }

      pAddrInfo= pAddrInfo->ai_next; /* Get next address info, if any */
    }
  }

  if (pAddrInfoIpV4)
  {
    ... /* create, bind and make IPV4 socket listen */
    int fdSocketIpV4 = socket(pAddrInfoIpV4->ai_family,...

    *pfdSocketIpV4 = fdSocketIpV4; 
  }

  if (pAddrInfoIpV6)
  {
    /* create, bind and make IPV6 socket listen */
    int fdSocketIpV6 = socket(pAddrInfoIpV6->ai_family,...

    *pfdSocketIpV6 = fdSocketIpV6; 
  }

  freeaddrinfo(res);

  ...

Then call it like so:

...
int fdSocketIpV4 = -1;    
int fdSocketIpV6 = -1;

if (0 > open_listenfd(port, &fdSocketIpV4, &fdSocketIpV6))
{
  printf("Error executing 'open_listenfd()'\n");
}
else 
{
  ... /* go for accepting connectings on 'fdSocketIpV4' and 'fdSocketIpV6' */

Update:

As commented by Per Johansson an alternative approach would be to set up a dual stack socket, supporting both, Ipv4 and Ipv6, as mentioned by this answer: How to support both IPv4 and IPv6 connections

Community
  • 1
  • 1
alk
  • 69,737
  • 10
  • 105
  • 255
  • 1
    @MLD_Saturn: You are welcome. If you liked the answer you are free to upvote it. If it solved your problem you also may mark is as accepted by clickling the check mark. – alk Dec 14 '12 at 07:57
  • 1
    It's not really true that you must set up two sockets. There's also the option to use an IPv6 socket marked as accepting both. – Per Johansson Dec 15 '12 at 18:30
  • @glglgl Hrm, I know different OSes have different defaults for the `V6ONLY` flag, but I haven't heard of any not supporting it. It's in the basic API rfc... Edit: alright, that link mentions Windows XP. – Per Johansson Dec 17 '12 at 18:57
  • @PerJohansson Right. That's why I recommend creating a socket for each of the results of `getaddrinfo(..., AI_PASSIVE)` and switching them to `V6ONLY` (if applicable) and serving them via `select()` or something like that. – glglgl Dec 17 '12 at 22:11