This chapter describes the sockets-based communication facilities using IPv6.
Topics in this chapter include:
Introduction of the AF_INET6 socket and related address structures
Client/Server programs for connection-based and connectionless sockets
Socket options for unicasting and multicasting
Multicasting
The socket() call creates a socket in the specified domain of the specified type, as in the following example:
#include <sys/socket.h> s = socket(domain, type, protocol); |
A number of domains are available from the sys/socket.h file, including the AF_INET6 domain. Following are all of the domains that are available:
AF_UNIX | UNIX domain | |
AF_INET6 | Internet domain for IPv6 and IPv4 | |
AF_INET | Internet domain for IPv4 only | |
AF_RAW | Raw domain |
The socket types, SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW are supported by the domains AF_INET6, AF_INET, and AF_UNIX. For example, to create a stream socket in the IPv6 domain, use the following entry:
s = socket(AF_INET6, SOCK_STREAM, 0); |
The in6_addr structure, defined in netinet/in.h, stores IPv6 addresses. Also defined in netinet/in.h is the sockaddr_in6 structure, a protocol-specific socket address data structure for IPv6.
The global variable in6addr_any is defined as an in6_addr structure and initialized to the “unspecified address” in netinet/in.h. After opening an AF_INET6 socket, for the system to select the source address, this “unspecified address” can be used in the bind() call.
For example, you can use the following code to bind an AF_INET6 socket to a port number but let the system select the source address. Here the global variable in6addr_any is used.
struct sockaddr_in6 sin6; ... bzero(&sin6, sizeof(struct sockaddr_in6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(23); sin6.sin6_addr = in6addr_any; ... if(bind(s, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) ... |
You can use the symbolic constant IN6ADDR_ANY_INIT, which is also defined in netinet/in.h, to initialize an object of type struct in6_addr. The object will then contain the same address as the global variable in6addr_any. Consider the following example:
struct in6_addr anyaddr = IN6ADDR_ANY_INIT; |
This constant can be used only at declaration time. It cannot be used to assign a previously declared in6_addr structure. For example, the following code will not work:
struct sockaddr_in6 sin6; ... sin6.sin6_addr = IN6ADDR_ANY_INIT; /* will NOT compile */ |
Like the unspecified address, the IPv6 loopback address is also provided in two forms: a global variable and a symbolic constant. The global variable is defined as an in6_addr structure named in6addr_loopback. The symbolic constant is named IN6ADDR_LOOPBACK_INIT. Both are defined in netinet/in.h. Like IN6ADDR_ANY_INIT, IN6ADDR_LOOPBACK_INIT cannot be used in an assignment to a previously declared IPv6 address variable.
The following example shows the use of in6addr_loopback:
struct sockaddr_in6 sin6; ... sin6.sin6_addr = in6addr_loopback; ... if(connect(s, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) ... |
The following example shows the use of IN6ADDR_LOOPBACK_INIT:
struct in6_addr loopbackaddr = IN6ADDR_LOOPBACK_INIT; |
You can use the sockaddr_storage structure, defined in sys/socket.h, to write protocol-independent application programs. This data structure is large enough to accommodate both AF_INET and AF_INET6 protocol-specific address structures. It is also aligned at an appropriate boundary so that pointers to it can be cast as pointers to protocol specific address structures and they can be used to access the fields of those structures without alignment problems.
For example, it can be used in a server code in the following way:
... struct sockaddr_storage from; ... struct addrinfo hints, *res, *ressave; int oldsock, newsock, err_num; ... hints.ai_family = AF_UNSPEC; hints.ai_family = AI_PASSIVE | AI_ADDRCONFIG; ... err_num = getaddrinfo(NULL, argv[1], &hints, &res); ressave = res; do { /* do socket(), bind() and listen() calls here */ ... if((newsock = accept(oldsock, (struct sockaddr *)&from, sizeof(from))) == -1) { perror("accept"); exit(1); } else { doit(&from); } } ... |
For the server and client examples given in this section, if the ai_family field of the hints structure is replaced with AF_UNSPEC and if AF_INET6 is not supported, the program tries for the AF_INET family.
A server process normally opens a stream socket and listens at a particular port number for sevice requests. The client initiates a connection to the server's address and port number by calling connect(). At this point, the server wakes up and services the client request.
Example 3-1 is an example of a server program that creates an IPv6 socket and binds a name to that socket. The port number on which the socket is to be bound is provided on the command line. The program calls listen() to mark the socket as ready to accept incoming connections. In this example, the number of connections that the server can accept is set to five. Each pass of the loop accepts a new connection and creates a new socket. The server reads and displays the messages from the socket and closes it.
The command-line format for the server program is as follows:
program_name port_number |
Example 3-1. Connection-based Server
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #define MAX_CONN 5 main(argc, argv) int argc; char *argv[]; { struct addrinfo hints, *res, *ressave; struct sockaddr_storage from; int fromlen; int err_num; int oldsock, newsock; int readval; char buf[1024]; int conn_num = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; hints.ai_socktype = SOCK_STREAM; err_num = getaddrinfo(NULL, argv[1], &hints, &res); if(err_num) { fprintf(stderr, "getaddrinfo: %s", gai_strerror(err_num)); exit(1); } ressave = res; do { oldsock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(oldsock < 0) continue; if(bind(oldsock, res->ai_addr, res->ai_addrlen) < 0) { perror("bind"); close(oldsock); continue; } if(listen(oldsock, 5) < 0) { perror("listen"); close(oldsock); continue; } do { if((newsock = accept(oldsock, (struct sockaddr *)&from, &fromlen)) < 0) { perror("accept"); close(oldsock); } else { do { memset(&buf, 0, sizeof(buf)); if((readval = read(newsock, buf, 1024)) == -1) perror("read"); if(readval == 0) printf("Ending connection\n"); else printf("---> %s\n", buf); } while (readval > 0); conn_num++; } close(newsock); } while (conn_num < MAX_CONN); /* break after establishing the required no. of connections */ close(oldsock); break; } while ((res = res->ai_next) != NULL); if(!res) { fprintf(stderr, "bind/listen/accept failed for all addresses\n"); freeaddrinfo(ressave); exit(1); } freeaddrinfo(ressave); } /* end of main */ |
Example 3-2 is an example of a client program that creates an AF_INET6 stream socket and calls connect() with the socket address for connection. If the request is accepted, the connection is complete and the program can send data. The port number provided on the command line should match the port number provided on the command line for the server program.
The command-line format for this program is as follows:
program_name server_name port_number |
Example 3-2. Connection-based Client
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #define DATA "Data received through connection-based socket from client" main(argc, argv) int argc; char *argv[]; { struct addrinfo hints, *res, *ressave; int s, err_num; bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; err_num = getaddrinfo(argv[1], argv[2], &hints, &res); if(err_num) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err_num)); exit(1); } ressave = res; do { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) continue; if(connect(s, res->ai_addr, res->ai_addrlen) < 0) { perror("connect"); close(s); } else break; } while ((res = res->ai_next) != NULL); if (!res) { fprintf(stderr, "socket/connect failed for all addresses\n"); freeaddrinfo(ressave); exit(1); } if (write(s, DATA, sizeof DATA) == -1) perror("write"); close(s); freeaddrinfo(ressave); } /* end of main */ |
With datagram sockets, data can be exchanged without requiring connection establishment. The sendto() call sends data on an unconnected datagram socket. You can use recvfrom() to receive data on a datagram socket.
Example 3-3 is an example program that creates a datagram socket, binds a name to it, then reads from the socket.
The command-line format for this server program is as follows:
program_name port_number |
Example 3-3. Connectionless Server
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> main(argc, argv) int argc; char *argv[]; { struct addrinfo hints, *res, *ressave; int s, err_num; char buf[1024]; bzero(&hints, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; hints.ai_socktype = SOCK_DGRAM; err_num = getaddrinfo(NULL, argv[1], &hints, &res); if(err_num) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err_num)); exit(1); } ressave = res; do { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(s < 0) continue; if(bind (s, res->ai_addr, res->ai_addrlen) == -1) { perror("bind"); close(s); continue; } if(read(s, buf, 1024) == -1) { perror("read"); close(s); continue; } else { printf("---> %s\n", buf); close(s); break; } } while ((res = res->ai_next) != NULL); if(!res){ fprintf(stderr, "socket/bind/read failed for all addresses\n"); freeaddrinfo(ressave); exit(1); } freeaddrinfo(ressave); } /* end of main */ |
Example 3-4 is an example of a program that opens an AF_INET6 datagram socket and sends a datagram to the server whose name is obtained from the command line argument. The port number provided on the command line should match the port number that is provided on the command line for the server program.
The command line format for this program is as follows:
program_name server_name port_number |
Example 3-4. Connectionless Client
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #define DATA "Data received through connection-less socket from client" main(argc, argv) int argc; char *argv[]; { struct addrinfo hints, *res, *ressave; int s, err_num; bzero(&hints, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; hints.ai_socktype = SOCK_DGRAM; err_num = getaddrinfo(argv[1], argv[2], &hints, &res); if(err_num) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err_num)); exit(1); } ressave = res; do { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(s < 0) continue; if(sendto(s, DATA, sizeof DATA, 0, res->ai_addr, res->ai_addrlen) == -1) { perror("send"); close(s); continue; } else { close(s); break; } } while ((res = res->ai_next) != NULL); if(!res) { fprintf(stderr, "socket/sendto failed for all addresses\n"); freeaddrinfo(ressave); exit(1); } freeaddrinfo(ressave); } /* end of main */ |
For IPv6, there are a number of socket options that are defined at the IPPROTO_IPV6 level. When you use these socket options, set the level parameter to IPPROTO_IPV6. You can obtain these socket options and the declaration for IPPROTO_IPV6 by including the header netinet/in.h.
Use the socket option IPV6_UNICAST_HOPS at the IPPROTO_IPV6 level to control the hop limit used in outgoing IPv6 unicast packets. Example 3-5 shows its usage:
Example 3-5. Using IPV6_UNICAST_HOPS
int hoplimit = 10; if(setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&hoplimit, sizeof(hoplimit)) == -1) perror("setsockopt IPV6_UNICAST_HOPS"); |
If this option is not set, the system selects a default value.
Several socket options are available for sending and receiving IPv6 multicast packets. The following sections describe those options.
The following socket options at the IPPROTO_IPV6 level control some of the parameters for sending IPv6 multicast packets:
IPV6_MULTICAST_IF
IPV6_MULTICAST_HOPS
IPV6_MULTICAST_LOOP
The IPV6_MULTICAST_IF socket option sets the interface to use for outgoing multicast packets. If the interface index is specified as 0, the system selects the interface to use. Here the argument type is unsigned int. In the following example, ifindex is the interface index for the desired outgoing interface.
unsigned int ifindex; ifindex = if_nametoindex("ef0"); setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex,sizeof(ifindex)); |
The IPV6_MULTICAST_HOPS socket option sets the hop limit to use for outgoing multicast packets. If it is not set, the default is 1. Here the argument type is int.
If a multicast datagram is sent to a group to which the sending host itself belongs (on the outgoing interface) and theIPV6_MULTICAST_LOOP option is set to 1, a copy of the datagram is looped back for local delivery. If this option is set to 0, a copy is not looped back. If this option is not set, the default is 1. This socket option in IPv6 is similar to IP_MULTICAST_LOOP in IPv4. The argument type is unsigned int. In the following example, loop is 0 to disable loopback and 1 to enable loopback.
unsigned int loop; setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)); |
The following socket options control the reception of IPv6 multicast packets:
IPV6_JOIN_GROUP
IPV6_LEAVE_GROUP
The IPV6_JOIN_GROUP socket option allows you to join a multicast group on a specified local interface. The ipv6_mreq structure, defined in netinet/in.h, is to be used for this purpose, as shown in the following example. If ipv6mr_interface is set to 0, the kernel chooses the local interface.
struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; /* IPv6 multicast address of group */ unsigned int ipv6mr_interface; /* interface index */ }; |
The IPV6_LEAVE_GROUP socket option allows you to leave a multicast group on a specified interface. If the ipv6mr_interface field of the ipv6_mreq structure is set to 0, the system chooses a multicast group membership to drop by matching the multicast address.
The IPV6_V6ONLY socket option restricts AF_INET6 sockets to IPv6 communication only. Normally, AF_INET6 sockets can be used for both IPv4 and IPv6 communications. But if an application wishes to restrict the use of AF_INET6 sockets to IPv6 communications only, this socket option can be used, as in the following example:
int on = 1; setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on)); |
IP multicasting is supported both on AF_INET6 and AF_INET sockets for connectionless protocols, that is, for sockets of types other than SOCK_STREAM, and only on subnetworks for which the interface driver supports multicasting. The following sections describe sending and receiving IPv6 multicast datagrams.
![]() | Note: Broadcasting is not supported in IPv6. It is supported only in IPv4. |
To send an IPv6 multicast datagram, specify an IP multicast address in the range ff00::0/8 as the destination address in a sendto() call.
The definitions required for the multicast-related socket options are found in netinet/in.h.
By default, IPv6 multicast datagrams are sent with a hop limit of 1, which prevents it from being forwarded beyond a single subnetwork. The socket option IPV6_MULTICAST_HOPS allows the hop limit for subsequent multicast datagrams to be set to any value from 0 to 255, to control the scope of the multicasts, as in the following example:
int hops; setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,sizeof(hops)); |
IPv6 multicast datagrams with a hop limit of 0 are not transmitted on any subnet but can be delivered locally if the following conditions exist:
The sending node belongs to the destination group.
Multicast loopback on the sending socket is enabled.
Multicast datagrams with a hop limit greater than 1 might be delivered to more than one subnetwork if there is at least one multicast router attached to the first-hop subnetwork.
The IPv6 multicast addresses contain scope information encoded in the first part of the address. The scopes are defined in netinet/in.h.
Before a node can receive IP multicast datagrams for a given multicast address, it must become a member of the associated IP multicast group. A process can ask the node to join an IPv6 multicast group by using the IPV6_JOIN_GROUP socket option, as follows:
struct ipv6_mreq mreq; setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)); |
Each membership is associated with a single interface and it is possible to join the same group on more than one interface. If ipv6mr_interface is 0, the default multicast interface is chosen for the membership. Otherwise, one of the node's interface indices that is specified as ipv6mr_interface is chosen for the multicast membership.
To leave a group, use the following code:
struct ipv6_mreq mreq; setsockopt(s, IPPROTO_IPV6, IP_LEAVE_GROUP, &mreq, sizeof(mreq)); |
The mreq argument contains the same values as those used to add the membership. The socket drops associated memberships when the socket is closed or the process holding the socket is killed. However, more than one socket may claim a membership in a particular group, and the node will remain a member of that group until the last claim is dropped.