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:
- AF_INET: src points to a struct in_addr (in network byte order) which is converted to an IPv4 network address in the dotted-decimal format, "ddd.ddd.ddd.ddd". The buffer dst must be at least INET_ADDRSTRLEN bytes long.
- AF_INET6: src
points to a struct in6_addr
(in network byte order) which is converted to a representation of this
address in the most appropriate IPv6 network address format for this
address. The buffer dst
must be at least INET6_ADDRSTRLEN
bytes long.