Translation

This post is also available in Simplified Chinese.

At least the wired network on Zijingang’s Yuquan campus has IPv6. While doing a computer networking assignment, I took the opportunity to investigate IPv6 network programming.

This article is largely based on my own experience. Corrections are welcome.

Creating a Socket, Connecting to a Server, and Binding an Address

socket and AF_INET6

When calling socket(), set the first argument to AF_INET6. For example, create a TCP socket with socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP). Interestingly, the macro has different numeric values on Windows and Linux.

sockaddr_in6

Like sockaddr_in, this structure stores IPv6 address information. In addition to sin6_family, sin6_port, and sin6_addr, it has two extra members: sin6_flowinfo and sin6_scope_id. Be sure to clear the entire structure with memset before use, or a connection may fail.

sockaddr_storage

This structure is large enough for every type of sockaddr, so it can hold addresses uniformly in code that supports both IPv4 and IPv6. Except for ss_family, however, its fields always require casts. I usually store addresses in a union containing sockaddr_in, sockaddr_in6, and sockaddr_storage; this also makes them easier to inspect in a debugger.

connect

There is nothing special about connect(). Just ensure that the addrlen argument is not too short. send and recv work exactly as they do with IPv4.

IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT

These are analogous to IPv4’s INADDR_ANY and INADDR_LOOPBACK. They can be assigned directly to sin6_addr to bind to [::] and [::1], respectively.

Address Conversion

Use inet_pton() to convert a textual address into the binary representation used by sin6_addr. inet_ntop() performs the reverse conversion.

Manually filling the fields described above works well for IPv6 unicast addresses. A machine without access to the IPv6 Internet, however, can only use link-local addresses—and those work only on the same “link,” such as within the same switch. Link-local addresses use the fe80::/10 prefix followed by a 64-bit suffix.

How It Works

Entering a link-local address alone is insufficient. Unlike unicast addresses, link-local addresses have no prefix mechanism for distinguishing subnets, and the routing table contains no route for them. If a computer has multiple network interfaces, it cannot determine which interface should send the data.

ipv6(7) explains the purpose of sin6_scope_id:

sin6_scope_id is an ID depending on the scope of the address. It is new in Linux 2.4. Linux supports it only for link-local addresses, in that case sin6_scope_id contains the interface index (see netdevice(7)).

In other words, when using a link-local address, put the network interface index in sin6_scope_id. Run ip a on Linux or ipconfig /all on Windows to display all current addresses:

Linux network interfaces and their indices

The 1 and 2 before lo and ens33 are their indices. Appending %x to an address specifies its scope ID. For example, ping6 fe80::20c:29ff:fe09:31c6 reports an invalid argument, while ping6 fe80::20c:29ff:fe09:31c6%2 works. ping6 fe80::20c:29ff:fe09:31c6%ens33 works as well.

getaddrinfo

You can put an index directly into sin6_scope_id, but a scope written as an interface name requires getaddrinfo(). This function is commonly used to resolve IPv4 addresses, but it can also populate an IPv6 address:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#define addr "fe80::20c:29ff:fe09:31c6%ens33"
#define port "80"

struct addrinfo hints, *results;
struct sockaddr_storage addr_svr;

memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if (getaddrinfo(addr, port, &hint, &results) != 0) {
    puts("error in getaddrinfo");
}
else {
    memcpy(&addr_svr, results[0].ai_addr, results[0].ai_addrlen);
}

getaddrinfo() can produce a sockaddr for either a unicast address or a link-local address with a scope ID.

getnameinfo

This function performs the reverse of getaddrinfo(). Given a sockaddr_in6, it converts the address into text; for a link-local address, the result also includes its scope ID.

Listening on IPv6 and IPv4 Simultaneously

A server should support both network-layer protocols. Since accept() blocks, however, waiting for an IPv4 connection would prevent it from accepting IPv6 connections. You must either listen in multiple threads or use an I/O multiplexing mechanism.

select() is probably the oldest such mechanism. Add both the IPv4 and IPv6 sockets to the same fd_set and pass it as readfds to select(). When a connection arrives, the listening socket becomes readable; calling accept() only on a readable socket avoids blocking.

Conclusion

IPv6 is the future. When will the campus wireless network finally support it too?