Last update: 24-Mar-2019
Author: R. Koucha

Is inet_ntoa() reentrant ?



Introduction

This paper focuses on the Linux GLIBC service inet_ntoa() which translates an internet address into ASCII. This service is widely used in network applications but it must be used with care...

Recap

The GLIBC service inet_ntoa() function converts the Internet host address in given in network byte order to a string in standard numbers-and-dots notation. The prototype of this service is:

char *inet_ntoa(struct in_addr in);

Limitation

The manual adds the following interesting information: "The string is returned in a statically allocated buffer, which subsequent calls will overwrite".

This means that the following code is wrong:

char *p1, *p2;

p1 = inet_ntoa(ip1);
p2 = inet_ntoa(ip2);

because p1 points on the statically allocated buffer into which is stored the translation of ip1 but the second call modifies this buffer to put the translation of ip2. So, this results in the fact that both p1 and p2 point on the translation of ip2 (i.e. the translation of ip1 is lost !).

So, a fix for the previous code consists to copy the result of the service into a local buffer:

// XXX.XXX.XXX.XXX\0 = 1- bytes
char p1[16], p2[16];

strcpy(p1, inet_ntoa(ip1));
strcpy(p2, inet_ntoa(ip2));

In a mono-threaded process, the previous code works fine. But what about the multi-threaded environment ? The manual does not give any tips about it.

Multi-threaded environment

At first sight, the previous code snippet does not work in a multi-threaded environment as a strcpy() call is not atomic. While running the strcpy() service, the running thread can be preempted by another thread running inet_ntoa(). So, the static buffer of inet_ntoa() call may be corrupted.

When we look at the source of inet_ntoa() in the GLIBC 2.9, we get the following:

/* The interface of this function is completely stupid, it requires a
   static buffer.  We relax this a bit in that we allow one buffer for
   each thread.  */
static __thread char buffer[18];


char *
inet_ntoa (struct in_addr in)
{
  unsigned char *bytes = (unsigned char *) ∈
  __snprintf (buffer, sizeof (buffer), "%d.%d.%d.%d",
          bytes[0], bytes[1], bytes[2], bytes[3]);

  return buffer;
}


The directive __thread tells the linker to allocate one buffer per thread (i.e. Thread Local Storage) as explained here. So, this proves that inet_ntoa() is MT-safe.

Enhanced version of inet_ntoa()


Some operating systems like BSD or AIX offer a full reentrant and MT-safe version of inet_ntoa(). But this is not available under the GLIBC. So, here is an implementation of this new function which would be called inet_ntoa_r():

int inet_ntoa_r(
                  struct in_addr  in,
                  char           *s,
                  unsigned int    slen
                 )
{
unsigned char *bytes = (unsigned char *)&(in.s_addr);

  if (slen < 16 || !s)
  {
    errno = EINVAL;
    return -1;  
  }

  snprintf(s, slen, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]);

  return 0;
}

New standard service

Actually, inet_ntoa() is now deprecated and should be replaced by the full reetrant and MT-safe inet_ntop() call:

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

This  function converts the network address structure src in the af address family into a character string. The resulting string is copied to the buffer pointed to by dst, which must be a non-NULL pointer.  The caller specifies the number of bytes available in this buffer in the argument size.
inet_ntop() extends the inet_ntoa() function to support multiple address families. The  following address families are currently supported:

About the author

The author is an engineer in computer sciences located in France. He can be contacted here.