Address Conversion
Intermediate
TLPI Ch. 59
What Is This About?
When you write a socket program, IP addresses exist in two forms:
- Binary form – what the kernel and network stack actually use (stored in
struct in_addrorstruct in6_addr) - Presentation form – what humans read, like
192.168.1.10or::1
You need to convert between these two forms constantly. The two modern functions for this are inet_pton() (presentation → network binary) and inet_ntop() (network binary → presentation). The “p” means presentation; the “n” means network.
These functions handle both IPv4 and IPv6, unlike the older inet_aton() / inet_ntoa() pair which only handled IPv4.
Key Terms
An IPv4 address like 204.152.189.116 is easy for you to type and read. But internally, the Linux kernel stores it as a 32-bit integer in network byte order (big-endian). An IPv6 address like ::1 is stored as a 128-bit value in struct in6_addr.
The three presentation formats you will encounter:
| Format | Example | IP Version |
|---|---|---|
| Dotted-decimal | 204.152.189.116 |
IPv4 |
| Colon-hex | ::1 |
IPv6 |
| IPv4-mapped IPv6 | ::FFFF:204.152.189.116 |
IPv6 (wraps IPv4) |
The flow of conversion:
| Presentation String “192.168.1.10” |
⟶ | inet_pton() | ⟶ | Binary (in_addr) 0xC0A8010A |
| Binary (in_addr) 0xC0A8010A |
⟶ | inet_ntop() | ⟶ | Presentation String “192.168.1.10” |
Both functions are declared in <arpa/inet.h>.
#include <arpa/inet.h>
/* Presentation string --> Binary */
int inet_pton(int domain, const char *src_str, void *addrptr);
/*
* domain : AF_INET (IPv4) or AF_INET6 (IPv6)
* src_str : e.g. "192.168.1.10" or "::1"
* addrptr : pointer to struct in_addr (for AF_INET)
* pointer to struct in6_addr (for AF_INET6)
*
* Returns:
* 1 -> success
* 0 -> src_str not in valid presentation format
* -1 -> error (check errno)
*/
/* Binary --> Presentation string */
const char *inet_ntop(int domain, const void *addrptr,
char *dst_str, size_t len);
/*
* domain : AF_INET or AF_INET6
* addrptr : pointer to struct in_addr or struct in6_addr
* dst_str : output buffer for the string
* len : size of dst_str buffer
*
* Returns:
* pointer to dst_str -> success
* NULL -> error (errno = ENOSPC if buffer too small)
*/
Notice that inet_pton() returns three different values: 1, 0, or -1. Always check all three. Returning 0 means the string was not a valid IP address — that is not an error in the Unix sense, errno is not set in that case.
When using inet_ntop() you must provide an output buffer. To avoid guessing the size, use the two constants defined in <netinet/in.h>:
#include <netinet/in.h>
#define INET_ADDRSTRLEN 16 /* Max IPv4 dotted-decimal string length */
/* e.g. "255.255.255.255\0" = 16 bytes */
#define INET6_ADDRSTRLEN 46 /* Max IPv6 hex string length */
/* includes colons, \0 terminator */
| Constant | Value | Covers | Use With |
|---|---|---|---|
INET_ADDRSTRLEN |
16 | IPv4 dotted-decimal + null | AF_INET |
INET6_ADDRSTRLEN |
46 | IPv6 colon-hex + null | AF_INET6 |
Always use these constants instead of hardcoding numbers like char buf[50]. It makes your code portable and self-documenting.
Example 1 – inet_pton() with IPv4
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(void)
{
struct in_addr addr; /* 32-bit IPv4 binary */
int result;
const char *ipstr = "192.168.1.10";
result = inet_pton(AF_INET, ipstr, &addr);
if (result == 1) {
/* addr.s_addr now holds the IPv4 address in network byte order */
printf("Conversion OK. Binary (hex) = 0x%08X\n",
ntohl(addr.s_addr)); /* ntohl for display only */
} else if (result == 0) {
printf("Not a valid IPv4 presentation string: %s\n", ipstr);
} else {
perror("inet_pton");
}
return 0;
}
/* Output:
* Conversion OK. Binary (hex) = 0xC0A8010A
*/
Example 2 – inet_ntop() with IPv4
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
struct in_addr addr;
char buf[INET_ADDRSTRLEN]; /* Use the constant, not a magic number */
/* Manually set binary address: 8.8.8.8 */
addr.s_addr = htonl(0x08080808);
if (inet_ntop(AF_INET, &addr, buf, sizeof(buf)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("IP address string: %s\n", buf);
/* Output: IP address string: 8.8.8.8 */
return 0;
}
Example 3 – inet_pton() and inet_ntop() with IPv6
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
struct in6_addr addr6;
char buf[INET6_ADDRSTRLEN];
const char *ipv6str = "2001:db8::1";
/* Step 1: presentation --> binary */
if (inet_pton(AF_INET6, ipv6str, &addr6) != 1) {
fprintf(stderr, "inet_pton failed\n");
return 1;
}
/* Step 2: binary --> presentation (round-trip check) */
if (inet_ntop(AF_INET6, &addr6, buf, sizeof(buf)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("Original : %s\n", ipv6str);
printf("Round-trip: %s\n", buf);
/* Note: kernel may normalize, e.g. "2001:db8::1" stays "2001:db8::1" */
return 0;
}
Example 4 – Practical logging use case
A common real-world use: you receive a connection and want to log the client’s IP without doing a DNS lookup (which is slow).
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void log_client_address(struct sockaddr_in *client_addr)
{
char ip_str[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET,
&client_addr->sin_addr,
ip_str,
sizeof(ip_str)) == NULL) {
/* Fall back gracefully */
snprintf(ip_str, sizeof(ip_str), "(unknown)");
}
printf("[LOG] Client connected from %s port %d\n",
ip_str,
ntohs(client_addr->sin_port));
}
This is exactly the pattern used in the TLPI i6d_ucase_sv.c server shown in the next part of this chapter.
| Function | IPv4 Only? | Thread-Safe? | Status |
|---|---|---|---|
inet_aton() |
Yes | Yes | Old, limited |
inet_ntoa() |
Yes | No (static buffer) | Old, avoid |
inet_pton() |
No (IPv4 + IPv6) | Yes | Modern ✔ |
inet_ntop() |
No (IPv4 + IPv6) | Yes (caller buffer) | Modern ✔ |
inet_ntoa() is particularly dangerous in multi-threaded programs because it uses an internal static buffer — two threads calling it simultaneously will corrupt each other’s result. inet_ntop() takes a caller-supplied buffer, so it is completely thread-safe.
| Situation | Use | Reason |
|---|---|---|
| Log an IP address quickly | inet_ntop() |
No DNS lookup, fast |
| No DNS PTR record exists | inet_ntop() |
getnameinfo would fail |
| Need hostname from IP | getnameinfo() |
Does reverse DNS lookup |
| Connect to a named host | getaddrinfo() |
Resolves name to binary address |
A: “p” stands for presentation (human-readable string like “192.168.1.1”) and “n” stands for network (binary form in network byte order). inet_pton converts presentation → network; inet_ntop converts network → presentation.
A:
1 → Conversion successful.
0 → The string was not a valid presentation-format address (errno is NOT set).
-1 → An error occurred (errno is set, e.g., invalid domain).
A: inet_ntoa() stores its result in a static (shared) internal buffer. If two threads call it at the same time, one will overwrite the other’s result, causing a race condition. inet_ntop() is safe because the caller provides the output buffer.
A: INET_ADDRSTRLEN = 16 (max IPv4 dotted-decimal + null terminator). INET6_ADDRSTRLEN = 46 (max IPv6 colon-hex + null terminator). They are used to correctly size the buffer passed to inet_ntop(), avoiding buffer overflows and magic numbers in code.
A: #include <arpa/inet.h> for the function declarations. #include <netinet/in.h> for struct in_addr, struct in6_addr, and the INET_ADDRSTRLEN / INET6_ADDRSTRLEN constants.
A: Use inet_ntop() when: (1) speed matters — DNS reverse lookup can take hundreds of milliseconds; (2) no DNS PTR record exists for the IP address; (3) DNS is unavailable. inet_ntop() is a pure local conversion with no network I/O.
A: For AF_INET, addrptr points to struct in_addr (contains s_addr, a 32-bit IPv4 address). For AF_INET6, it points to struct in6_addr (contains s6_addr, a 128-bit IPv6 address as a byte array).
A: inet_ntop() returns NULL and sets errno to ENOSPC. The function does not write a partial result to the buffer.
| Item | Detail |
|---|---|
| Header | <arpa/inet.h> |
| inet_pton direction | String → Binary |
| inet_ntop direction | Binary → String |
| IPv4 buffer size | INET_ADDRSTRLEN = 16 |
| IPv6 buffer size | INET6_ADDRSTRLEN = 46 |
| Domains supported | AF_INET, AF_INET6 |
| Thread-safe? | Yes (caller provides buffer) |
Next: Host & Service Name Resolution APIs
Learn why gethostbyname() is obsolete and how getaddrinfo() replaces it.
