IPv6 Addressing
Beginner to Intermediate
2 of 5
Why IPv6?
IPv4 provides ~4.3 billion addresses. That sounds like a lot, but with billions of smartphones, IoT devices, and servers, the world ran out of IPv4 addresses. IPv6 was designed to fix this — it uses 128-bit addresses, giving us 2128 addresses. That is approximately 340 undecillion (340 × 1036) addresses — enough for every grain of sand on Earth to have its own address, many times over.
The principles of IPv6 addressing are similar to IPv4 — there is still a network part and a host part, and there are still special addresses like loopback and wildcard. The main differences are the size (128 bits vs 32 bits) and the notation used to write addresses.
An IPv6 address is 128 bits long. It is written as eight groups of four hexadecimal digits, separated by colons:
| 128 bits total = 8 groups × 16 bits each | |||||||
| F000 16 bits |
0000 16 bits |
0000 16 bits |
0000 16 bits |
0000 16 bits |
0000 16 bits |
000A 16 bits |
0001 16 bits |
| Full: F000:0000:0000:0000:0000:0000:000A:0001 | Simplified: F000::A:1 | |||||||
Each group is written in hexadecimal. Leading zeros within a group can be dropped (so 000A becomes A). Consecutive groups of all zeros can be replaced with :: (double colon). The example above becomes F000::A:1.
Important rule: The :: shortcut can only appear once in an address. If it appeared more than once, it would be ambiguous — the reader would not know how many zero groups each :: replaces.
| Full Address | After Dropping Leading Zeros | After :: Compression |
|---|---|---|
| F000:0000:0000:0000:0000:0000:000A:0001 | F000:0:0:0:0:0:A:1 | F000::A:1 |
| 0000:0000:0000:0000:0000:0000:0000:0001 | 0:0:0:0:0:0:0:1 | ::1 (loopback) |
| 0000:0000:0000:0000:0000:0000:0000:0000 | 0:0:0:0:0:0:0:0 | :: (wildcard) |
| 2001:0DB8:0000:0000:0008:0800:200C:417A | 2001:DB8:0:0:8:800:200C:417A | 2001:DB8::8:800:200C:417A |
| Address | C Constant | IPv4 Equivalent | Meaning |
|---|---|---|---|
| ::1 | in6addr_loopback | 127.0.0.1 | Loopback — packets stay on the local host |
| :: | in6addr_any | 0.0.0.0 | Wildcard — bind to all interfaces |
::1 is 127 zero bits followed by a single 1-bit — the IPv6 loopback address. :: is all 128 bits set to zero — the IPv6 wildcard address.
During the transition from IPv4 to IPv6, many hosts still only support IPv4. IPv6 provides a special mechanism called IPv4-mapped IPv6 addresses so that IPv6 applications can communicate with IPv4-only hosts.
| 80 bits — all zeros | FFFF 16 bits |
IPv4 Address 32 bits |
|||||
| 0000 | 0000 | 0000 | 0000 | 0000 | FFFF | 204.152 | 189.116 |
| Written as: ::FFFF:204.152.189.116 | |||||||
The format is: 80 zero bits, followed by 16 bits of FFFF, followed by the 32-bit IPv4 address written in dotted-decimal notation. When writing it as a string, the IPv4 portion is kept in dotted-decimal rather than being converted to hex — this makes it easier to read.
Why Is This Useful?
An IPv6 server socket (AF_INET6) can receive connections from IPv4 clients when the OS maps the client’s IPv4 address into IPv4-mapped IPv6 format automatically. This allows a single server to handle both IPv4 and IPv6 clients without running two separate server sockets.
Creating an IPv6 TCP server uses AF_INET6 and struct sockaddr_in6:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 8080
int main() {
int server_fd;
struct sockaddr_in6 addr6;
char addr_str[INET6_ADDRSTRLEN];
/* Create an IPv6 TCP socket */
server_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Fill in IPv6 address structure */
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(PORT);
/*
* in6addr_any is the IPv6 wildcard address (::)
* Accepts connections on all interfaces for both IPv4 and IPv6
* when IPV6_V6ONLY socket option is 0 (default on Linux).
*/
addr6.sin6_addr = in6addr_any;
/* OR use loopback: addr6.sin6_addr = in6addr_loopback; */
if (bind(server_fd,
(struct sockaddr *)&addr6, sizeof(addr6)) == -1) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("IPv6 server bound to port %d\n", PORT);
listen(server_fd, 5);
/* ... accept() loop goes here ... */
close(server_fd);
return 0;
}
Address Conversion: inet_pton / inet_ntop
Unlike IPv4 where we used inet_addr(), for both IPv4 and IPv6 the modern functions are inet_pton() (text to binary) and inet_ntop() (binary to text):
#include <stdio.h>
#include <arpa/inet.h>
int main() {
struct in6_addr addr6;
char text[INET6_ADDRSTRLEN];
const char *ipv6_str = "F000::A:1";
const char *ipv4mapped = "::FFFF:204.152.189.116";
/* Convert text to binary */
if (inet_pton(AF_INET6, ipv6_str, &addr6) == 1) {
printf("Conversion OK\n");
}
/* Convert binary back to text */
if (inet_ntop(AF_INET6, &addr6, text, sizeof(text)) != NULL) {
printf("Address: %s\n", text);
}
/* Convert an IPv4-mapped address */
if (inet_pton(AF_INET6, ipv4mapped, &addr6) == 1) {
inet_ntop(AF_INET6, &addr6, text, sizeof(text));
printf("IPv4-mapped: %s\n", text);
}
return 0;
}
Key difference from IPv4: Use AF_INET for IPv4 addresses and AF_INET6 for IPv6 addresses with these functions.
2001:0DB8:0000:0000:0008:0800:200C:417A. Two compression shortcuts exist: leading zeros in a group can be dropped, and one sequence of consecutive all-zero groups can be replaced with ::.
::1 (127 zero bits followed by a single 1-bit). It is the equivalent of IPv4’s 127.0.0.1. In C, it is available as the constant in6addr_loopback for use with struct sockaddr_in6. Like IPv4 loopback, packets sent to ::1 never leave the host.
:: notation replaces one or more consecutive groups of zeros. If it appeared more than once, the address would be ambiguous — it would be impossible to determine how many zero groups each :: represents. For example, ::1::1 could mean many different addresses, so it is invalid.
FFFF, then the 32-bit IPv4 address. Written as ::FFFF:w.x.y.z. It allows IPv6-capable applications to communicate with IPv4-only hosts. On Linux, an IPv6 server socket bound to in6addr_any can automatically receive connections from IPv4 clients presented in this form.
inet_pton(AF_INET6, text_str, &in6_addr) to convert a text IPv6 address to binary (network byte order). Use inet_ntop(AF_INET6, &in6_addr, buf, buf_size) to convert binary back to text. These functions work for both IPv4 (AF_INET) and IPv6 (AF_INET6). The older inet_addr() and inet_ntoa() only work for IPv4.
struct sockaddr_in6 (defined in <netinet/in.h>). Key fields: sin6_family (set to AF_INET6), sin6_port (port in network byte order via htons()), and sin6_addr (the 128-bit address of type struct in6_addr). For wildcard, assign in6addr_any; for loopback, assign in6addr_loopback.
FE80:: are link-local addresses. This prefix-based type identification replaces the class-based (Class A/B/C) system used in early IPv4 design, providing a more flexible and extensible addressing architecture.
Continue Learning
Next: Transport Layer — Port Numbers, how TCP/UDP identify applications.
