Mike Higginbottom

A Digital Garden - frequent growth but a bit weedy in the back corner

Free Your addrinfo List With This One Weird Trick

I was recently digging into getaddrinfo() and as part of that I created a little sandbox to play wth the code. getaddrinfo() returns a list of struct addrinfo which represent the various potential sockets you could connect to. In a real-world program you would essentially pick one of these, connect to it, have a bit of a chit chat and then disconnect. Finally, you need to call freeaddrinfo() to get glibc to free up the memory it allocated to these structures. But for the purposes of this sandbox I just wanted to call it, look at the returned list and then clean up. Like so:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>

int main (int argc, char **argv) {
  char *node = "google.com";
  char *service = "80";

  struct addrinfo hints;
  memset(&hints, 0, sizeof hints);

  struct addrinfo *res;

  setvbuf(stdout, NULL, _IONBF, 0);

  int ret = getaddrinfo(node, service, &hints, &res);
  printf("getaddrinfo() returned: %d\n", ret);
  if (ret != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
    return ret;
  }
  printf("Returned struct addrinfo instances for node: %s and service: %s:\n\n", node, service);
  struct addrinfo *ai;
  for (ai = res; ai != NULL; ai = ai->ai_next) {
    // printAddrInfo(ai);
  }
  freeaddrinfo(res);
 
  return 0;
}

If you want to build this and have a play with it using GDB for example, you’ll need to build your own version of glibc and possibly implement some version of printAddrInfo() to dump the returned structs to the screen. I might throw all this up online in another article and a GitHub repo at some point. But… not today’s problem.

Being a nosey little sod, I wondered how these two functions behave if an error occurs while building and populating this list. More specifically, do you need to call freeaddrinfo() if the call to getaddrinfo() fails for some reason? You know, cos either: possible memory leak or possible double free. Both of which are no bueno. So I went down the rabbit hole. And part way down I ended up looking at the source for freeaddrinfo(). The signature for this is void freeaddrinfo(struct addrinfo *ai) and the definition of struct addrinfo looks like this:

struct addrinfo {
  int              ai_flags;
  int              ai_family;
  int              ai_socktype;
  int              ai_protocol;
  socklen_t        ai_addrlen;
  struct sockaddr *ai_addr;
  char            *ai_canonname;
  struct addrinfo *ai_next;
};

My expectation was that freeaddrinfo() would step through the input list, grab a copy of ai_next, free up the memory allocated to the members ai_addr, ai_canonname and the struct itself, then step on to do the same to the stored next list item. But the code looks like this:

void freeaddrinfo (struct addrinfo *ai)
{
  struct addrinfo *p;

  while (ai != NULL)
    {
      p = ai;
      ai = ai->ai_next;
      free (p->ai_canonname);
      free (p);
    }
}

Waidda minit… Where’s the call to free ai_addr? Now obviously (well probably) this is not going to be a bug. Bitter and long experience has taught me that pretty much every time I identify a cast iron definite bug, the reality is I’m just demonstrating idiocy. Again.

First things first then, let’s make sure it’s not a bug. I ran it through Valgrind which reported no memory leaks. As expected, looks like I’m being an idiot. Next things next, in order to work out if everything that’s being allocated is being freed you need to work out what’s being allocated. And a quick search in glibc/src/nss/getaddrinfo.c finds a call to malloc() that indicates the allocation is happening in generate_addrinfo() at line 1087:

static int
generate_addrinfo (const struct addrinfo *req, struct gaih_result *res,
           const struct gaih_servtuple *st, struct addrinfo **pai,
           unsigned int *naddrs)
{
  size_t socklen;
  sa_family_t family;

  /* Buffer is the size of an unformatted IPv6 address in printable format.  */
  for (struct gaih_addrtuple *at = res->at; at != NULL; at = at->next)
    {
      family = at->family;
      if (family == AF_INET6)
    {
      socklen = sizeof (struct sockaddr_in6);

      /* If we looked up IPv4 mapped address discard them here if
         the caller isn't interested in all address and we have
         found at least one IPv6 address.  */
      if (res->got_ipv6
          && (req->ai_flags & (AI_V4MAPPED|AI_ALL)) == AI_V4MAPPED
          && IN6_IS_ADDR_V4MAPPED (at->addr))
        continue;
    }
      else
    socklen = sizeof (struct sockaddr_in);

      for (int i = 0; st[i].set; i++)
    {
      struct addrinfo *ai;
      ai = *pai = malloc (sizeof (struct addrinfo) + socklen);
      if (ai == NULL)
        return -EAI_MEMORY;

      ai->ai_flags = req->ai_flags;
      ai->ai_family = family;
      ai->ai_socktype = st[i].socktype;
      ai->ai_protocol = st[i].protocol;
      ai->ai_addrlen = socklen;
      ai->ai_addr = (void *) (ai + 1);

      /* We only add the canonical name once.  */
      ai->ai_canonname = res->canon;
      res->canon = NULL;

#ifdef _HAVE_SA_LEN
      ai->ai_addr->sa_len = socklen;
#endif /* _HAVE_SA_LEN */
      ai->ai_addr->sa_family = family;

      /* In case of an allocation error the list must be NULL
         terminated.  */
      ai->ai_next = NULL;

      if (family == AF_INET6)
        {
          struct sockaddr_in6 *sin6p = (struct sockaddr_in6 *) ai->ai_addr;
          sin6p->sin6_port = st[i].port;
          sin6p->sin6_flowinfo = 0;
          memcpy (&sin6p->sin6_addr, at->addr, sizeof (struct in6_addr));
          sin6p->sin6_scope_id = at->scopeid;
        }
      else
        {
          struct sockaddr_in *sinp = (struct sockaddr_in *) ai->ai_addr;
          sinp->sin_port = st[i].port;
          memcpy (&sinp->sin_addr, at->addr, sizeof (struct in_addr));
          memset (sinp->sin_zero, '\0', sizeof (sinp->sin_zero));
        }

      pai = &(ai->ai_next);
    }

      ++*naddrs;
    }
  return 0;
}