RFC 2292

Advanced Sockets API for IPv6

๐Ÿ“… published February 1998, from ietf

Status of this Memo ๐Ÿ“œ

Okay bestie, this one's an Informational doc ๐Ÿงš It gives info to the Internet community, but it doesn't set any kind of Internet standard. Anyone can pass it around freely.

Copyright (C) The Internet Society (1998). All Rights Reserved. ๐Ÿ’…

Abstract ๐Ÿฆ‹

Work is in progress on changes to the sockets API so it can support IP version 6 [RFC-2133]. These changes are for TCP and UDP-based apps and will cover most everyday end-user stuff like Telnet and FTP clients and servers, HTTP clients and servers, and the like. ๐Ÿ’–

But there's another group of apps that will also run under IPv6. We call these "advanced" applications, and right now that includes things like Ping, Traceroute, routing daemons, multicast routing daemons, router discovery daemons, and the like ๐Ÿ‘‘ What makes them "advanced" is the API feature they usually use: a raw socket to access ICMPv4, IGMPv4, or IPv4, plus some knowledge of the packet header formats those protocols use. So to keep raw-socket apps portable under IPv6, we need some standardization for the advanced API features ๐Ÿ”ฅ

There are other IPv6 features some apps will need to reach. That's interface identification (picking the outgoing interface and figuring out the incoming one) and IPv6 extension headers that [RFC-2133] doesn't cover: Hop-by-Hop options, Destination options, and the Routing header (source routing). This document gives API access to those features too โœจ

Table of Contents ๐Ÿ—‚๏ธ

 1.  Introduction ................................................3
 2.  Common Structures and Definitions ...........................5
    2.1.  The ip6_hdr Structure ..................................5
         2.1.1.  IPv6 Next Header Values .........................6
         2.1.2.  IPv6 Extension Headers ..........................6
    2.2.  The icmp6_hdr Structure ................................8
         2.2.1.  ICMPv6 Type and Code Values .....................8
         2.2.2.  ICMPv6 Neighbor Discovery Type and Code Values ..9
    2.3.  Address Testing Macros .................................12
    2.4.  Protocols File .........................................12
 3.  IPv6 Raw Sockets ............................................13
    3.1.  Checksums ..............................................14
    3.2.  ICMPv6 Type Filtering ..................................14
 4.  Ancillary Data ..............................................17
    4.1.  The msghdr Structure ...................................18
    4.2.  The cmsghdr Structure ..................................18
    4.3.  Ancillary Data Object Macros ...........................19
         4.3.1.  CMSG_FIRSTHDR ...................................20
         4.3.2.  CMSG_NXTHDR .....................................22
         4.3.3.  CMSG_DATA .......................................22
         4.3.4.  CMSG_SPACE ......................................22
         4.3.5.  CMSG_LEN ........................................22
    4.4.  Summary of Options Described Using Ancillary Data ......23
    4.5.  IPV6_PKTOPTIONS Socket Option ..........................24
         4.5.1.  TCP Sticky Options ..............................25
         4.5.2.  UDP and Raw Socket Sticky Options ...............26
 5.  Packet Information ..........................................26
    5.1.  Specifying/Receiving the Interface .....................27
    5.2.  Specifying/Receiving Source/Destination Address ........27
    5.3.  Specifying/Receiving the Hop Limit .....................28
    5.4.  Specifying the Next Hop Address ........................29
    5.5.  Additional Errors with sendmsg() .......................29
 6.  Hop-By-Hop Options ..........................................30
    6.1.  Receiving Hop-by-Hop Options ...........................31
    6.2.  Sending Hop-by-Hop Options .............................31
    6.3.  Hop-by-Hop and Destination Options Processing ..........32
         6.3.1.  inet6_option_space ..............................32
         6.3.2.  inet6_option_init ...............................32
         6.3.3.  inet6_option_append .............................33
         6.3.4.  inet6_option_alloc ..............................33
         6.3.5.  inet6_option_next ...............................34
         6.3.6.  inet6_option_find ...............................35
         6.3.7.  Options Examples ................................35
 7.  Destination Options .........................................42
    7.1.  Receiving Destination Options ..........................42
    7.2.  Sending Destination Options ............................43

 8.  Routing Header Option .......................................43
    8.1.  inet6_rthdr_space ......................................44
    8.2.  inet6_rthdr_init .......................................45
    8.3.  inet6_rthdr_add ........................................45
    8.4.  inet6_rthdr_lasthop ....................................46
    8.5.  inet6_rthdr_reverse ....................................46
    8.6.  inet6_rthdr_segments ...................................46
    8.7.  inet6_rthdr_getaddr ....................................46
    8.8.  inet6_rthdr_getflags ...................................47
    8.9.  Routing Header Example .................................47
 9.  Ordering of Ancillary Data and IPv6 Extension Headers .......53
10.  IPv6-Specific Options with IPv4-Mapped IPv6 Addresses .......54
11.  rresvport_af ................................................55
12.  Future Items ................................................55
    12.1.  Flow Labels ...........................................55
    12.2.  Path MTU Discovery and UDP ............................56
    12.3.  Neighbor Reachability and UDP .........................56
13.  Summary of New Definitions ..................................56
14.  Security Considerations .....................................59
15.  Change History ..............................................59
16.  References ..................................................65
17.  Acknowledgments .............................................65
18.  Authors' Addresses ..........................................66
19.  Full Copyright Statement ....................................67

1. Introduction โœจ

Specifications are in progress for changes to the sockets API so it can support IP version 6 [RFC-2133]. These changes are for TCP and UDP-based apps. This doc defines some of the "advanced" features of the sockets API that applications need to take advantage of extra IPv6 features ๐Ÿฆ‹

Right now, IPv4 raw sockets are pretty portable, but that's mostly because most IPv4 implementations started from a common base (the Berkeley source code), or at least from the Berkeley headers. That's how programs like Ping and Traceroute can compile with minimal effort on lots of hosts that support the sockets API ๐Ÿ’– With IPv6, though, there's no common source code base for implementors to start from, so the chance of different implementations diverging at this level is high ๐Ÿ˜ญ To avoid a total lack of portability for apps that use raw IPv6 sockets, some standardization is necessary.

There are also features from the basic IPv6 specification that [RFC-2133] doesn't address: sending and receiving Hop-by-Hop options, Destination options, and Routing headers, specifying the outgoing interface, and being told about the receiving interface ๐ŸŒธ

This document can be split into the following main sections ๐ŸŽ€

  1. Definitions of the basic constants and structures that applications need to use raw IPv6 sockets. This includes structure definitions for the IPv6 and ICMPv6 headers and all their associated constants (for example, values for the Next Header field).
  2. Some basic semantic definitions for IPv6 raw sockets. For example, a raw ICMPv4 socket requires the application to calculate and store the ICMPv4 header checksum. With IPv6, though, that would force the application to choose the source IPv6 address, because the source address is part of the pseudo header that ICMPv6 now uses for its checksum computation. So it should be defined that with a raw ICMPv6 socket, the kernel always calculates and stores the ICMPv6 header checksum ๐Ÿ”ฅ
  3. Packet information: how applications can get the received interface, destination address, and received hop limit, plus how to specify these values on a per-packet basis. There's a class of applications that need this capability, and the technique should be portable ๐Ÿ‘‘
  4. Access to the optional Hop-by-Hop, Destination, and Routing headers.
  5. Additional features needed for IPv6 application portability โœจ

The packet information and access to the extension headers (Hop-by-Hop options, Destination options, and Routing header) are specified using the "ancillary data" fields that were added to the 4.3BSD Reno sockets API in 1990. The reason is that these ancillary data fields are part of the Posix.1g standard (which should be approved in 1997), so most vendors should adopt them ๐Ÿงš

This document does not address application access to either the authentication header or the encapsulating security payload header ๐Ÿ™ˆ

All examples in this document leave out error checking for the sake of brevity and clarity ๐Ÿ“

We note that many of the functions and socket options defined in this document may have error returns that this document doesn't define. Many of these possible error returns will only be recognized as implementations progress ๐Ÿ’€

Datatypes in this document follow the Posix.1g format: intN_t means a signed integer of exactly N bits (e.g., int16_t), and uintN_t means an unsigned integer of exactly N bits (e.g., uint32_t) ๐Ÿ’…

Note that we use the (unofficial) terminology ICMPv4, IGMPv4, and ARPv4 so there's no confusion with the newer ICMPv6 protocol ๐Ÿ’œ

2. Common Structures and Definitions ๐Ÿงš

Lots of advanced applications look at fields in the IPv6 header and set and examine fields in the various ICMPv6 headers. Common structure definitions for these headers are required, along with common constant definitions for the structure members ๐Ÿซถ

Two new headers are defined: <netinet/ip6.h> and <netinet/icmp6.h> ๐ŸŒˆ

When an include file is specified, that include file is allowed to include other files that do the actual declaration or definition โœจ

2.1. The ip6_hdr Structure ๐Ÿ’–

Okay bestie, including <netinet/ip6.h> gives you this struct. Heads up, it's a brand-new header โœจ Here's the struct plus its shortcut macros:

struct ip6_hdr {
  union {
    struct ip6_hdrctl {
      uint32_t ip6_un1_flow;   /* 24 bits of flow-ID */
      uint16_t ip6_un1_plen;   /* payload length */
      uint8_t  ip6_un1_nxt;    /* next header */
      uint8_t  ip6_un1_hlim;   /* hop limit */
    } ip6_un1;
    uint8_t ip6_un2_vfc;       /* 4 bits version, 4 bits priority */
  } ip6_ctlun;
  struct in6_addr ip6_src;      /* source address */
  struct in6_addr ip6_dst;      /* destination address */
};

#define ip6_vfc   ip6_ctlun.ip6_un2_vfc
#define ip6_flow  ip6_ctlun.ip6_un1.ip6_un1_flow
#define ip6_plen  ip6_ctlun.ip6_un1.ip6_un1_plen
#define ip6_nxt   ip6_ctlun.ip6_un1.ip6_un1_nxt
#define ip6_hlim  ip6_ctlun.ip6_un1.ip6_un1_hlim
#define ip6_hops  ip6_ctlun.ip6_un1.ip6_un1_hlim

2.1.1. IPv6 Next Header Values ๐Ÿฆ‹

IPv6 adds a whole bunch of new values for the Next Header field, so slay. Including <netinet/in.h> gives you these constants:

#define IPPROTO_HOPOPTS        0 /* IPv6 Hop-by-Hop options */
#define IPPROTO_IPV6          41 /* IPv6 header */
#define IPPROTO_ROUTING       43 /* IPv6 Routing header */
#define IPPROTO_FRAGMENT      44 /* IPv6 fragmentation header */
#define IPPROTO_ESP           50 /* encapsulating security payload */
#define IPPROTO_AH            51 /* authentication header */
#define IPPROTO_ICMPV6        58 /* ICMPv6 */
#define IPPROTO_NONE          59 /* IPv6 no next header */
#define IPPROTO_DSTOPTS       60 /* IPv6 Destination options */

Quick side note ๐Ÿ™ˆ Berkeley-derived IPv4 implementations also define IPPROTO_IP as 0. That's not a problem, because IPPROTO_IP is only used with IPv4 sockets and IPPROTO_HOPOPTS is only used with IPv6 sockets. No drama ๐Ÿ’…

2.1.2. IPv6 Extension Headers ๐ŸŽ€

Six extension headers are defined for IPv6. We define structs for all of them except the Authentication header and the Encapsulating Security Payload header, both of which are out of scope for this doc. The following structs come from including <netinet/ip6.h>:

/* Hop-by-Hop options header */
/* XXX should we pad it to force alignment on an 8-byte boundary? */
struct ip6_hbh {
  uint8_t  ip6h_nxt;        /* next header */
  uint8_t  ip6h_len;        /* length in units of 8 octets */
    /* followed by options */
};

/* Destination options header */
/* XXX should we pad it to force alignment on an 8-byte boundary? */
struct ip6_dest {
  uint8_t  ip6d_nxt;        /* next header */
  uint8_t  ip6d_len;        /* length in units of 8 octets */
    /* followed by options */
};

/* Routing header */
struct ip6_rthdr {

  uint8_t  ip6r_nxt;        /* next header */
  uint8_t  ip6r_len;        /* length in units of 8 octets */
  uint8_t  ip6r_type;       /* routing type */
  uint8_t  ip6r_segleft;    /* segments left */
    /* followed by routing type specific data */
};

/* Type 0 Routing header */
struct ip6_rthdr0 {
  uint8_t  ip6r0_nxt;       /* next header */
  uint8_t  ip6r0_len;       /* length in units of 8 octets */
  uint8_t  ip6r0_type;      /* always zero */
  uint8_t  ip6r0_segleft;   /* segments left */
  uint8_t  ip6r0_reserved;  /* reserved field */
  uint8_t  ip6r0_slmap[3];  /* strict/loose bit map */
  struct in6_addr  ip6r0_addr[1];  /* up to 23 addresses */
};

/* Fragment header */
struct ip6_frag {
  uint8_t   ip6f_nxt;       /* next header */
  uint8_t   ip6f_reserved;  /* reserved field */
  uint16_t  ip6f_offlg;     /* offset, reserved, and flag */
  uint32_t  ip6f_ident;     /* identification */
};

#if     BYTE_ORDER == BIG_ENDIAN
#define IP6F_OFF_MASK       0xfff8  /* mask out offset from _offlg */
#define IP6F_RESERVED_MASK  0x0006  /* reserved bits in ip6f_offlg */
#define IP6F_MORE_FRAG      0x0001  /* more-fragments flag */
#else   /* BYTE_ORDER == LITTLE_ENDIAN */
#define IP6F_OFF_MASK       0xf8ff  /* mask out offset from _offlg */
#define IP6F_RESERVED_MASK  0x0600  /* reserved bits in ip6f_offlg */
#define IP6F_MORE_FRAG      0x0100  /* more-fragments flag */
#endif

Okay, byte order is the important bit ๐ŸŒˆ Defined constants for fields bigger than 1 byte depend on the byte ordering being used. This API assumes the fields in the protocol headers are left in network byte order, which is big-endian for the Internet protocols. If they aren't, then either these constants or the fields being tested must be converted at run-time, using something like htons() or htonl().

(Note: we show an implementation that supports both big-endian and little-endian byte ordering, assuming a hypothetical compile-time #if test to determine the byte ordering. The BYTE_ORDER constant, with values of BIG_ENDIAN and LITTLE_ENDIAN, is only there for example purposes. If an implementation runs on only one type of hardware, it only needs to define the set of constants for that hardware's byte ordering.) ๐Ÿงš

2.2. The icmp6_hdr Structure ๐ŸŒธ

The ICMPv6 header is needed by a bunch of IPv6 apps, including Ping, Traceroute, router discovery daemons, and neighbor discovery daemons. The following struct comes from including <netinet/icmp6.h>. Heads up, this is also a new header ๐Ÿ’œ

struct icmp6_hdr {
  uint8_t     icmp6_type;   /* type field */
  uint8_t     icmp6_code;   /* code field */
  uint16_t    icmp6_cksum;  /* checksum field */
  union {
    uint32_t  icmp6_un_data32[1]; /* type-specific field */
    uint16_t  icmp6_un_data16[2]; /* type-specific field */
    uint8_t   icmp6_un_data8[4];  /* type-specific field */
  } icmp6_dataun;
};

#define icmp6_data32    icmp6_dataun.icmp6_un_data32
#define icmp6_data16    icmp6_dataun.icmp6_un_data16
#define icmp6_data8     icmp6_dataun.icmp6_un_data8
#define icmp6_pptr      icmp6_data32[0]  /* parameter prob */
#define icmp6_mtu       icmp6_data32[0]  /* packet too big */
#define icmp6_id        icmp6_data16[0]  /* echo request/reply */
#define icmp6_seq       icmp6_data16[1]  /* echo request/reply */
#define icmp6_maxdelay  icmp6_data16[0]  /* mcast group membership */

2.2.1. ICMPv6 Type and Code Values โœจ

Besides the common struct for the ICMPv6 header, we also need common definitions for the ICMPv6 type and code fields. The following constants come from including <netinet/icmp6.h> too:

#define ICMP6_DST_UNREACH             1
#define ICMP6_PACKET_TOO_BIG          2
#define ICMP6_TIME_EXCEEDED           3
#define ICMP6_PARAM_PROB              4

#define ICMP6_INFOMSG_MASK  0x80    /* all informational messages */

#define ICMP6_ECHO_REQUEST          128
#define ICMP6_ECHO_REPLY            129
#define ICMP6_MEMBERSHIP_QUERY      130
#define ICMP6_MEMBERSHIP_REPORT     131
#define ICMP6_MEMBERSHIP_REDUCTION  132

#define ICMP6_DST_UNREACH_NOROUTE     0 /* no route to destination */
#define ICMP6_DST_UNREACH_ADMIN       1 /* communication with */
                                        /* destination */
                                        /* administratively */
                                        /* prohibited */
#define ICMP6_DST_UNREACH_NOTNEIGHBOR 2 /* not a neighbor */
#define ICMP6_DST_UNREACH_ADDR        3 /* address unreachable */
#define ICMP6_DST_UNREACH_NOPORT      4 /* bad port */

#define ICMP6_TIME_EXCEED_TRANSIT     0 /* Hop Limit == 0 in transit */
#define ICMP6_TIME_EXCEED_REASSEMBLY  1 /* Reassembly time out */

#define ICMP6_PARAMPROB_HEADER        0 /* erroneous header field */
#define ICMP6_PARAMPROB_NEXTHEADER    1 /* unrecognized Next Header */
#define ICMP6_PARAMPROB_OPTION        2 /* unrecognized IPv6 option */

The five ICMP message types defined by IPv6 neighbor discovery (133-137) are covered in the next section ๐Ÿฆ‹

2.2.2. ICMPv6 Neighbor Discovery Type and Code Values โœจ

okay so this bit is where <netinet/icmp6.h> gives you all the neighbor discovery goodies ๐Ÿงš literally the whole ND crew, bestie ๐Ÿ’…

#define ND_ROUTER_SOLICIT           133
#define ND_ROUTER_ADVERT            134
#define ND_NEIGHBOR_SOLICIT         135
#define ND_NEIGHBOR_ADVERT          136
#define ND_REDIRECT                 137

struct nd_router_solicit {     /* router solicitation */
  struct icmp6_hdr  nd_rs_hdr;
    /* could be followed by options */
};

#define nd_rs_type               nd_rs_hdr.icmp6_type
#define nd_rs_code               nd_rs_hdr.icmp6_code
#define nd_rs_cksum              nd_rs_hdr.icmp6_cksum
#define nd_rs_reserved           nd_rs_hdr.icmp6_data32[0]

struct nd_router_advert {      /* router advertisement */
  struct icmp6_hdr  nd_ra_hdr;
  uint32_t   nd_ra_reachable;   /* reachable time */
  uint32_t   nd_ra_retransmit;  /* retransmit timer */

    /* could be followed by options */
};

#define nd_ra_type               nd_ra_hdr.icmp6_type
#define nd_ra_code               nd_ra_hdr.icmp6_code
#define nd_ra_cksum              nd_ra_hdr.icmp6_cksum
#define nd_ra_curhoplimit        nd_ra_hdr.icmp6_data8[0]
#define nd_ra_flags_reserved     nd_ra_hdr.icmp6_data8[1]
#define ND_RA_FLAG_MANAGED       0x80
#define ND_RA_FLAG_OTHER         0x40
#define nd_ra_router_lifetime    nd_ra_hdr.icmp6_data16[1]

struct nd_neighbor_solicit {   /* neighbor solicitation */
  struct icmp6_hdr  nd_ns_hdr;
  struct in6_addr   nd_ns_target; /* target address */
    /* could be followed by options */
};

#define nd_ns_type               nd_ns_hdr.icmp6_type
#define nd_ns_code               nd_ns_hdr.icmp6_code
#define nd_ns_cksum              nd_ns_hdr.icmp6_cksum
#define nd_ns_reserved           nd_ns_hdr.icmp6_data32[0]

struct nd_neighbor_advert {    /* neighbor advertisement */
  struct icmp6_hdr  nd_na_hdr;
  struct in6_addr   nd_na_target; /* target address */
    /* could be followed by options */
};

#define nd_na_type               nd_na_hdr.icmp6_type
#define nd_na_code               nd_na_hdr.icmp6_code
#define nd_na_cksum              nd_na_hdr.icmp6_cksum
#define nd_na_flags_reserved     nd_na_hdr.icmp6_data32[0]
#if     BYTE_ORDER == BIG_ENDIAN
#define ND_NA_FLAG_ROUTER        0x80000000
#define ND_NA_FLAG_SOLICITED     0x40000000
#define ND_NA_FLAG_OVERRIDE      0x20000000
#else   /* BYTE_ORDER == LITTLE_ENDIAN */
#define ND_NA_FLAG_ROUTER        0x00000080
#define ND_NA_FLAG_SOLICITED     0x00000040
#define ND_NA_FLAG_OVERRIDE      0x00000020
#endif

struct nd_redirect {           /* redirect */
  struct icmp6_hdr  nd_rd_hdr;
  struct in6_addr   nd_rd_target; /* target address */
  struct in6_addr   nd_rd_dst;    /* destination address */
    /* could be followed by options */

};

#define nd_rd_type               nd_rd_hdr.icmp6_type
#define nd_rd_code               nd_rd_hdr.icmp6_code
#define nd_rd_cksum              nd_rd_hdr.icmp6_cksum
#define nd_rd_reserved           nd_rd_hdr.icmp6_data32[0]

struct nd_opt_hdr {           /* Neighbor discovery option header */
  uint8_t  nd_opt_type;
  uint8_t  nd_opt_len;        /* in units of 8 octets */
    /* followed by option specific data */
};

#define  ND_OPT_SOURCE_LINKADDR       1
#define  ND_OPT_TARGET_LINKADDR       2
#define  ND_OPT_PREFIX_INFORMATION    3
#define  ND_OPT_REDIRECTED_HEADER     4
#define  ND_OPT_MTU                   5

struct nd_opt_prefix_info {    /* prefix information */
  uint8_t   nd_opt_pi_type;
  uint8_t   nd_opt_pi_len;
  uint8_t   nd_opt_pi_prefix_len;
  uint8_t   nd_opt_pi_flags_reserved;
  uint32_t  nd_opt_pi_valid_time;
  uint32_t  nd_opt_pi_preferred_time;
  uint32_t  nd_opt_pi_reserved2;
  struct in6_addr  nd_opt_pi_prefix;
};

#define ND_OPT_PI_FLAG_ONLINK        0x80
#define ND_OPT_PI_FLAG_AUTO          0x40

struct nd_opt_rd_hdr {         /* redirected header */
  uint8_t   nd_opt_rh_type;
  uint8_t   nd_opt_rh_len;
  uint16_t  nd_opt_rh_reserved1;
  uint32_t  nd_opt_rh_reserved2;
    /* followed by IP header and data */
};

struct nd_opt_mtu {            /* MTU option */
  uint8_t   nd_opt_mtu_type;
  uint8_t   nd_opt_mtu_len;
  uint16_t  nd_opt_mtu_reserved;
  uint32_t  nd_opt_mtu_mtu;
};

ngl the nd_na_flags_reserved flags have the same byte ordering drama we already talked about with ip6f_offlg ๐Ÿ˜ญ so keep that in mind, it's giving dรฉjร  vu ๐Ÿ™ˆ


2.3. Address Testing Macros ๐ŸŒธ

so the basic API ([RFC-2133]) already has some macros for checking whether an IPv6 address has certain properties. this API adds more address testing macros of its own, and they come from including <netinet/in.h> ๐Ÿ’œ

int  IN6_ARE_ADDR_EQUAL(const struct in6_addr *,
                        const struct in6_addr *);

2.4. Protocols File ๐ŸŽ€

a bunch of hosts have a file called /etc/protocols that lists the names of the IP protocols along with their protocol numbers (like the value of the protocol field in the IPv4 header, so 1 for ICMP, for example). some programs call getprotobyname() to grab the protocol value, which then gets passed as the third argument to socket(). the Ping program does it like this ๐Ÿงš

struct protoent  *proto;

proto = getprotobyname("icmp");

s = socket(AF_INET, SOCK_RAW, proto->p_proto);

the new IPv6 protocols need common names in this file too, so that apps calling the getprotoXXX() functions stay portable ๐Ÿซถ

we define the following protocol names with the values shown. these are taken from ftp://ftp.isi.edu/in-notes/iana/assignments/protocol-numbers.

hopopt           0    # hop-by-hop options for ipv6
ipv6            41    # ipv6
ipv6-route      43    # routing header for ipv6
ipv6-frag       44    # fragment header for ipv6
esp             50    # encapsulating security payload for ipv6
ah              51    # authentication header for ipv6
ipv6-icmp       58    # icmp for ipv6
ipv6-nonxt      59    # no next header for ipv6
ipv6-opts       60    # destination options for ipv6

3. IPv6 Raw Sockets ๐Ÿฆ‹

okay so raw sockets skip the transport layer (TCP or UDP) entirely ๐ŸŒˆ with IPv4, they were used for ICMPv4, IGMPv4, and for reading and writing IPv4 datagrams with a protocol field the kernel doesn't process. OSPF's routing daemon is the classic example, since it uses IPv4 protocol field 89. with IPv6, raw sockets get used for ICMPv6, plus reading and writing IPv6 datagrams with a Next Header field the kernel doesn't process. examples are an OSPF-for-IPv6 routing daemon and RSVP (protocol field 46) ๐Ÿ’…

all data sent via raw sockets MUST be in network byte order, and all data received via raw sockets will be in network byte order. this is different from IPv4 raw sockets, which didn't specify a byte order and usually just used the host's byte order ๐Ÿ‘‘

another difference from IPv4 raw sockets: you can't read or write complete packets (IPv6 packets with extension headers) using the IPv6 raw sockets API ๐Ÿ˜ญ instead, ancillary data objects carry the extension headers, as described later in this document. if an app needs the complete IPv6 packet, it has to use some other technique, like the datalink interfaces BPF or DLPI โœจ

every field in the IPv6 header that an app might want to change (basically everything except the version number) can be modified for output using ancillary data and/or socket options. on input, every field in a received IPv6 header (except the version number and Next Header fields) and all extension headers are also handed to the app as ancillary data. so there's no need for anything like the IPv4 IP_HDRINCL socket option, periodt ๐Ÿ’–

when you write to a raw socket, the kernel automatically fragments the packet if it's bigger than the path MTU, and inserts the required fragmentation headers. on input, the kernel reassembles received fragments, so the raw socket reader never sees any fragment headers ๐Ÿงš

when we say "an ICMPv6 raw socket," we mean a socket created by calling the socket function with the three arguments PF_INET6, SOCK_RAW, and IPPROTO_ICMPV6 ๐ŸŒธ

most IPv4 implementations treat a raw socket made with a third socket() argument of IPPROTO_RAW (normally 255) specially. this value has no special meaning for an IPv6 raw socket, and IANA currently reserves the value 255 when it's used as a next-header field ๐Ÿ™ˆ (Note: This feature was added to IPv4 in 1988 by Van Jacobson to support traceroute, allowing a complete IP header to be passed by the application, before the IP_HDRINCL socket option was added.) ๐Ÿ“

3.1. Checksums ๐Ÿ’œ

the kernel will calculate and insert the ICMPv6 checksum for ICMPv6 raw sockets, since this checksum is mandatory โœจ

for other raw IPv6 sockets (ones created with a third argument other than IPPROTO_ICMPV6), the app must set the new IPV6_CHECKSUM socket option so the kernel can (1) compute and store a checksum for output, and (2) verify the received checksum on input, discarding the packet if the checksum is wrong ๐Ÿ”ฅ this option means apps don't have to do source address selection on the packets they send ๐Ÿซถ the checksum includes the IPv6 pseudo-header, defined in Section 8.1 of [RFC-1883]. this new socket option also specifies an integer offset into the user data where the checksum lives ๐ŸŒˆ

int  offset = 2;
setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset));

by default this socket option is disabled ๐Ÿ™ˆ setting the offset to -1 also disables it. by disabled we mean (1) the kernel won't calculate and store a checksum for outgoing packets, and (2) the kernel won't verify a checksum for received packets ๐Ÿ’€

(Note: Since the checksum is always calculated by the kernel for an ICMPv6 socket, applications are not able to generate ICMPv6 packets with incorrect checksums (presumably for testing purposes) using this API.) ๐Ÿงš

3.2. ICMPv6 Type Filtering ๐Ÿฆ‹

okay so ICMPv4 raw sockets get most of the ICMPv4 messages the kernel receives, ngl. (we say "most" and not "all" bc Berkeley-derived kernels never pass echo requests, timestamp requests, or address mask requests to a raw socket. instead the kernel handles those three entirely by itself ๐Ÿ™ˆ) but ICMPv6 is a superset of ICMPv4, and it also covers the functionality of IGMPv4 and ARPv4. so an ICMPv6 raw socket can potentially receive WAY more messages than an ICMPv4 one: ICMP messages similar to ICMPv4, plus neighbor solicitations, neighbor advertisements, and the three group membership messages. main character energy ๐Ÿ’…

most apps using an ICMPv6 raw socket only care about a small subset of the message types. moving extraneous ICMPv6 messages from the kernel up to user space can cost a significant amount of overhead ๐Ÿ˜ญ so this API includes a way to filter ICMPv6 messages by the ICMPv6 type field. ๐Ÿงš

Each ICMPv6 raw socket has an associated filter, and its datatype is defined as

struct icmp6_filter;

this structure, along with the macros and constants defined later in this section, comes from including the <netinet/icmp6.h> header. ๐ŸŽ€

the current filter gets fetched and stored using getsockopt() and setsockopt() with a level of IPPROTO_ICMPV6 and an option name of ICMP6_FILTER. โœจ

there are six macros that operate on an icmp6_filter structure:

void ICMP6_FILTER_SETPASSALL (struct icmp6_filter *);
void ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter *);

void ICMP6_FILTER_SETPASS ( int, struct icmp6_filter *);
void ICMP6_FILTER_SETBLOCK( int, struct icmp6_filter *);

int  ICMP6_FILTER_WILLPASS (int, const struct icmp6_filter *);
int  ICMP6_FILTER_WILLBLOCK(int, const struct icmp6_filter *);

the first argument to the last four macros (an integer) is an ICMPv6 message type, between 0 and 255. the pointer argument to all six macros is a pointer to a filter. the first four macros modify that filter, and the last two examine it. ๐ŸŒธ

the first two macros, SETPASSALL and SETBLOCKALL, let you say that all ICMPv6 messages get passed to the application, or that all of them get blocked from being passed to it. ๐Ÿ“

the next two, SETPASS and SETBLOCK, let you say that messages of a given ICMPv6 type should be passed to the application, or not passed (blocked). ๐Ÿ’–

the final two, WILLPASS and WILLBLOCK, return true or false depending on whether the specified message type is passed or blocked by the filter pointed to by the second argument. ๐Ÿ”ฅ

when an ICMPv6 raw socket gets created, it passes all ICMPv6 message types to the application by default. ๐ŸŒˆ

as an example, a program that only wants to receive router advertisements could do the following:

struct icmp6_filter  myfilt;

fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);

ICMP6_FILTER_SETBLOCKALL(&myfilt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &myfilt);
setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &myfilt, sizeof(myfilt));

the filter structure gets declared and then initialized to block all message types. then it gets changed to allow router advertisement messages through to the application, and the filter gets installed with setsockopt(). this is the cutest little setup ever ๐Ÿงšโœจ

the icmp6_filter structure is similar to the fd_set datatype used with the select() function in the sockets API. it's an opaque datatype, so the application shouldn't care how it's implemented. all the application does with it is allocate a variable of this type, pass a pointer to that variable to getsockopt() and setsockopt(), and operate on it using the six macros we just defined. ๐Ÿ’…

that said, it's worth showing a simple implementation of this datatype and the six macros. ๐Ÿซถ

struct icmp6_filter {
  uint32_t  icmp6_filt[8];  /* 8*32 = 256 bits */
};

#define ICMP6_FILTER_WILLPASS(type, filterp) \
    ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0)
#define ICMP6_FILTER_WILLBLOCK(type, filterp) \
    ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0)
#define ICMP6_FILTER_SETPASS(type, filterp) \
    ((((filterp)->icmp6_filt[(type) >> 5]) |=  (1 << ((type) & 31))))
#define ICMP6_FILTER_SETBLOCK(type, filterp) \
    ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))))
#define ICMP6_FILTER_SETPASSALL(filterp) \
    memset((filterp), 0xFF, sizeof(struct icmp6_filter))
#define ICMP6_FILTER_SETBLOCKALL(filterp) \
    memset((filterp), 0, sizeof(struct icmp6_filter))
(Note: These sample definitions have two limitations that an implementation may want to change. The first four macros evaluate their first argument two times. The second two macros require the inclusion of the <string.h> header for the memset() function.)

4. Ancillary Data โœจ

4.2BSD let file descriptors get transferred between separate processes across a UNIX domain socket, using the sendmsg() and recvmsg() functions. two members of the msghdr structure, msg_accrights and msg_accrightslen, were used to send and receive the descriptors. then when the OSI protocols got added to 4.3BSD Reno in 1990, the names of those two fields changed to msg_control and msg_controllen, because the OSI protocols used them for "control information". the comments in the source code call this "ancillary data" though. ๐Ÿงš

other than the OSI protocols, ancillary data has been pretty rare. in 4.4BSD, for example, the only use of ancillary data with IPv4 is returning the destination address of a received UDP datagram if the IP_RECVDSTADDR socket option is set. with Unix domain sockets, ancillary data is still used to send and receive descriptors. ๐ŸŒธ

still, the ancillary data fields of the msghdr structure are a clean way to pass information alongside the data being read or written. including the msg_control and msg_controllen members of the msghdr structure, along with the cmsghdr structure that msg_control points to, is required by the Posix.1g sockets API standard (which was supposed to be completed during 1997). ๐Ÿ’–

in this document, ancillary data is used to exchange the following optional information between the application and the kernel:

    1.  the send/receive interface and source/destination address,
    2.  the hop limit,
    3.  next hop address,
    4.  Hop-by-Hop options,
    5.  Destination options, and
    6.  Routing header.

before we get into these uses in detail, we'll review the definition of the msghdr structure itself, the cmsghdr structure that defines an ancillary data object, and some functions that operate on those objects. ๐Ÿฆ‹

4.1. The msghdr Structure ๐ŸŽ€

the msghdr structure is used by the recvmsg() and sendmsg() functions. its Posix.1g definition is:

 struct msghdr {
   void      *msg_name;        /* ptr to socket address structure */
   socklen_t  msg_namelen;     /* size of socket address structure */
   struct iovec  *msg_iov;     /* scatter/gather array */
   size_t     msg_iovlen;      /* # elements in msg_iov */
   void      *msg_control;     /* ancillary data */
   socklen_t  msg_controllen;  /* ancillary data buffer length */
   int        msg_flags;       /* flags on received message */
 };

the structure is declared by including <sys/socket.h>. ๐Ÿงš

(Note: Before Posix.1g, the two void * pointers were typically char *, and the two socklen_t members and the size_t member were typically integers. Earlier drafts of Posix.1g had the two socklen_t members as size_t, but Draft 6.6 of Posix.1g, apparently the final draft, changed these to socklen_t to simplify binary portability for 64-bit implementations and to align Posix.1g with X/Open's Networking Services, Issue 5. The change in msg_control to a void * pointer affects any code that increments this pointer.)

most Berkeley-derived implementations limit the amount of ancillary data in a call to sendmsg() to no more than 108 bytes (an mbuf). this API requires a minimum of 10240 bytes of ancillary data, but it is recommended that the amount be limited only by the buffer space reserved by the socket (which can be modified by the SO_SNDBUF socket option). (Note: This magic number 10240 was picked as a value that should always be large enough. 108 bytes is clearly too small, as the maximum size of a Type 0 Routing header is 376 bytes.) ๐Ÿ˜ญ๐Ÿ’…

4.2. The cmsghdr Structure ๐ŸŒˆ

the cmsghdr structure describes ancillary data objects transferred by recvmsg() and sendmsg(). its Posix.1g definition is:

 struct cmsghdr {
   socklen_t  cmsg_len;   /* #bytes, including this header */
   int        cmsg_level; /* originating protocol */
   int        cmsg_type;  /* protocol-specific type */
              /* followed by unsigned char cmsg_data[]; */
 };

this structure is declared by including <sys/socket.h>. ๐ŸŽ€

as shown in this definition, there normally isn't a member named cmsg_data[]. instead, the data portion is accessed using the CMSG_xxx() macros, which we'll cover shortly. still, it's common to refer to the cmsg_data[] member. ๐Ÿซถ

(Note: Before Posix.1g, the cmsg_len member was an integer, not a socklen_t. See the Note in the previous section for why socklen_t is used here.)

when ancillary data is sent or received, any number of ancillary data objects can be specified by the msg_control and msg_controllen members of the msghdr structure, because each object is preceded by a cmsghdr structure defining the object's length (the cmsg_len member). historically, Berkeley-derived implementations have passed only one object at a time, but this API allows multiple objects to be passed in a single call to sendmsg() or recvmsg(). the following example shows two ancillary data objects in a control buffer. ๐ŸŒธ

|<--------------------------- msg_controllen -------------------------->|
|                                                                       |
|<----- ancillary data object ----->|<----- ancillary data object ----->|
|<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->|
|                                   |                                   |
|<---------- cmsg_len ---------->|  |<--------- cmsg_len ----------->|  |
|<--------- CMSG_LEN() --------->|  |<-------- CMSG_LEN() ---------->|  |
|                                |  |                                |  |
+-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+
|cmsg_|cmsg_|cmsg_|XX|           |XX|cmsg_|cmsg_|cmsg_|XX|           |XX|
|len  |level|type |XX|cmsg_data[]|XX|len  |level|type |XX|cmsg_data[]|XX|
+-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+
 ^
 |
msg_control
points here

the fields shown as "XX" are possible padding, between the cmsghdr structure and the data, and between the data and the next cmsghdr structure, if the implementation requires it. ๐Ÿ’–

4.3. Ancillary Data Object Macros โœจ

okay so, to make ancillary data less painful to handle, three macros from 4.4BSD got adopted by Posix.1g: CMSG_DATA(), CMSG_NXTHDR(), and CMSG_FIRSTHDR(). ๐Ÿ’… Before we get into them, here's how they might be used with a call to recvmsg() ๐Ÿซถ

struct msghdr   msg;
struct cmsghdr  *cmsgptr;
/* fill in msg */

/* call recvmsg() */

for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
    if (cmsgptr->cmsg_level == ... && cmsgptr->cmsg_type == ... ) {
        u_char  *ptr;

        ptr = CMSG_DATA(cmsgptr);
        /* process data pointed to by ptr */
    }
}

We'll go through the three Posix.1g macros first, then the two new ones that this API adds: CMSG_SPACE() and CMSG_LEN() ๐ŸŽ€ All of these macros are defined as a result of including <sys/socket.h> โœจ

4.3.1. CMSG_FIRSTHDR

struct cmsghdr *CMSG_FIRSTHDR(const struct msghdr *mhdr);

CMSG_FIRSTHDR() gives you a pointer to the first cmsghdr structure inside the msghdr structure that mhdr points to ๐Ÿฆ‹ It returns NULL if there's no ancillary data in that msghdr structure. That happens when either msg_control is NULL, or when msg_controllen is smaller than the size of a cmsghdr structure. ๐Ÿ™ˆ

One possible implementation could be:

#define CMSG_FIRSTHDR(mhdr) \
    ( (mhdr)->msg_controllen >= sizeof(struct cmsghdr) ? \
      (struct cmsghdr *)(mhdr)->msg_control : \
      (struct cmsghdr *)NULL )

(Note: most existing implementations don't check msg_controllen and just return the value of msg_control ๐Ÿ’€ The value of msg_controllen must be tested, though! If the application asks recvmsg() to return ancillary data by pointing msg_control at its own buffer and setting msg_controllen to that buffer's length, the kernel says "no ancillary data here" by setting msg_controllen to 0 on return. It's also easier to put this check inside the macro than to make every application do it. ๐Ÿงš)

4.3.2. CMSG_NXTHDR

struct cmsghdr *CMSG_NXTHDR(const struct msghdr *mhdr,
                            const struct cmsghdr *cmsg);

CMSG_NXTHDR() returns a pointer to the cmsghdr structure that describes the next ancillary data object ๐ŸŒธ mhdr is a pointer to a msghdr structure, and cmsg is a pointer to a cmsghdr structure. If there isn't another ancillary data object, the return value is NULL.

This next bit is the new behavior for this API, and I'm obsessed with it ๐Ÿ˜ญ If the cmsg pointer is NULL, you get back a pointer to the cmsghdr structure describing the first ancillary data object. So CMSG_NXTHDR(mhdr, NULL) is equivalent to CMSG_FIRSTHDR(mhdr). If there are no ancillary data objects at all, the return value is NULL. This gives you a different way to write the processing loop from earlier:

struct msghdr  msg;
struct cmsghdr  *cmsgptr = NULL;

/* fill in msg */

/* call recvmsg() */

while ((cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) != NULL) {
    if (cmsgptr->cmsg_level == ... && cmsgptr->cmsg_type == ... ) {
        u_char  *ptr;

        ptr = CMSG_DATA(cmsgptr);
        /* process data pointed to by ptr */
    }
}

One possible implementation could be:

#define CMSG_NXTHDR(mhdr, cmsg) \
    ( ((cmsg) == NULL) ? CMSG_FIRSTHDR(mhdr) : \
      (((u_char *)(cmsg) + ALIGN((cmsg)->cmsg_len) \
                         + ALIGN(sizeof(struct cmsghdr)) > \
        (u_char *)((mhdr)->msg_control) + (mhdr)->msg_controllen) ? \
       (struct cmsghdr *)NULL : \
       (struct cmsghdr *)((u_char *)(cmsg) + ALIGN((cmsg)->cmsg_len))) )

The ALIGN() macro is implementation dependent ๐Ÿ“ It rounds its argument up to the next even multiple of whatever alignment is required (probably a multiple of 4 or 8 bytes).

4.3.3. CMSG_DATA

unsigned char *CMSG_DATA(const struct cmsghdr *cmsg);

CMSG_DATA() returns a pointer to the data that sits right after a cmsghdr structure. This is what gets called the cmsg_data[] member, even though no such member is actually defined in the structure ๐ŸŒˆ

One possible implementation could be:

#define CMSG_DATA(cmsg) ( (u_char *)(cmsg) + \
                          ALIGN(sizeof(struct cmsghdr)) )

4.3.4. CMSG_SPACE

unsigned int CMSG_SPACE(unsigned int length);

This one is new with this API ๐Ÿ‘‘ Given the length of an ancillary data object, CMSG_SPACE() returns the space the object needs, including its cmsghdr structure and any padding needed to meet alignment requirements. You can use it, for example, to allocate space dynamically for the ancillary data ๐Ÿฆ‹ This macro should not be used to initialize the cmsg_len member of a cmsghdr structure. Use the CMSG_LEN() macro for that instead โญ

One possible implementation could be:

#define CMSG_SPACE(length) ( ALIGN(sizeof(struct cmsghdr)) + \
                             ALIGN(length) )

4.3.5. CMSG_LEN

unsigned int CMSG_LEN(unsigned int length);

Also new with this API โœจ Given the length of an ancillary data object, CMSG_LEN() returns the value to store in the cmsg_len member of the cmsghdr structure, counting any padding needed for alignment ๐ŸŽ€

One possible implementation could be:

#define CMSG_LEN(length) ( ALIGN(sizeof(struct cmsghdr)) + length
)

Now, the difference between CMSG_SPACE() and CMSG_LEN() is super important, and it's also shown in the figure in Section 4.2 ๐Ÿซถ CMSG_SPACE() accounts for any padding required at the end of the ancillary data object. CMSG_LEN() gives the actual length to store in the cmsg_len member of the ancillary data object. Different jobs, different macros, no cap ๐Ÿ’…

4.4. Summary of Options Described Using Ancillary Data

There are six types of optional information in this document that get passed between the application and the kernel using ancillary data ๐ŸŒธ

    1.  the send/receive interface and source/destination address,
    2.  the hop limit,
    3.  next hop address,
    4.  Hop-by-Hop options,
    5.  Destination options, and
    6.  Routing header.

First up, to receive any of this optional information (except the next hop address, which can only be set), the application has to call setsockopt() to turn on the matching flag ๐Ÿ”ฅ

int  on = 1;

setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO,  &on, sizeof(on));
setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));
setsockopt(fd, IPPROTO_IPV6, IPV6_HOPOPTS,  &on, sizeof(on));
setsockopt(fd, IPPROTO_IPV6, IPV6_DSTOPTS,  &on, sizeof(on));
setsockopt(fd, IPPROTO_IPV6, IPV6_RTHDR,    &on, sizeof(on));

When any of these options are enabled, recvmsg() hands the matching data back as control information, as one or more ancillary data objects ๐Ÿงš

Sending is way easier ๐Ÿ™Œ There's nothing special to do for any of this optional information. The application just calls sendmsg() and includes one or more ancillary data objects as control information.

Here's a summary of the three cmsghdr fields that describe the ancillary data objects ๐ŸŒˆ

cmsg_level    cmsg_type      cmsg_data[]               #times
------------  ------------   ------------------------  ------
IPPROTO_IPV6  IPV6_PKTINFO   in6_pktinfo structure     once
IPPROTO_IPV6  IPV6_HOPLIMIT  int                       once
IPPROTO_IPV6  IPV6_NEXTHOP   socket address structure  once
IPPROTO_IPV6  IPV6_HOPOPTS   implementation dependent  mult.

IPPROTO_IPV6  IPV6_DSTOPTS   implementation dependent  mult.
IPPROTO_IPV6  IPV6_RTHDR     implementation dependent  once

The last column tells you how many times an ancillary data object of that type can show up as control information. Hop-by-Hop and Destination options can appear multiple times, and all the others can appear only one time ๐Ÿ’œ

All of these options are described in detail in the sections that follow. Every constant starting with IPV6_ is defined as a result of including the <netinet/in.h> header ๐Ÿฆ‹

(Note: we deliberately use the same constant for the cmsg_level member that's used as the second argument to getsockopt() and setsockopt(), which is called the "level." We also use the same constant for the cmsg_type member that's used as the third argument to those calls, which is called the "option name." This matches how ancillary data already works in 4.4BSD, where the destination address of an IPv4 datagram gets returned. ๐ŸŽ€)
(Note: what the implementation passes as ancillary data for the Hop-by-Hop option, the Destination option, and the Routing header option is up to the implementation. That's because the API for these features goes through a set of inet6_option_XXX() and inet6_rthdr_XXX() functions, which we define later. Those functions do two jobs: they make the interface to these features simpler (so the application doesn't need to know the intimate details of the extension header formats), and they hide the actual implementation from the application. Even so, we show some examples of these features that store the actual extension header as the ancillary data. Implementations don't need to use this technique. ๐Ÿงšโœจ)

4.5. IPV6_PKTOPTIONS Socket Option ๐ŸŽ€

okay so the summary in the last section assumed you're using a UDP socket, and with UDP it's easy bestie ๐Ÿฆ‹. You call sendmsg() and recvmsg() instead of sendto() and recvfrom(), and that's that.

But TCP apps want this stuff too, ngl ๐Ÿฅบ. A TCP client might want to specify a Routing header, and that has to happen before connect() is called. A TCP server might want to know which interface it received on after accept() returns, plus any Destination options. ๐Ÿ’…

So there's a new socket option that gives you access to all that optional info from the previous section, but without touching recvmsg() or sendmsg() โœจ. You set it like this:

setsockopt(fd, IPPROTO_IPV6, IPV6_PKTOPTIONS, &buf, len);

The fourth argument points to a buffer holding one or more ancillary data objects, and the fifth argument is the total length of all of them together. You fill that buffer exactly as if you were handing it to sendmsg() as control information, so no new brain cells required ๐Ÿง ๐Ÿ’–.

The options you set with IPV6_PKTOPTIONS are called "sticky" options, because once they're set they apply to all the packets sent on that socket ๐Ÿงท. You can call setsockopt() again to change all the sticky options, or call it with a length of 0 to remove every sticky option from the socket ๐Ÿงน.

The receiving side is getsockopt():

getsockopt(fd, IPPROTO_IPV6, IPV6_PKTOPTIONS, &buf, &len);

This gives you back a buffer with one or more ancillary data objects, covering all the optional receive info the app has previously said it wants ๐Ÿ“ฆ. The fourth argument points to the buffer that gets filled in. The fifth argument is a pointer to a value-result integer, which is a fancy way of saying: when you call it, the integer tells the kernel how big your buffer is, and when it returns, the integer tells you how many bytes actually came back ๐ŸŒธ. You process this buffer exactly as if it came back from recvmsg() as control information.

Okay, quick housekeeping ๐Ÿ™ˆ. In the rest of this doc, whenever we say "can be specified as ancillary data to sendmsg()," we really mean "can be specified as ancillary data to sendmsg(), or specified as a sticky option using setsockopt() and the IPV6_PKTOPTIONS socket option." And when we say "can be returned as ancillary data by recvmsg()," we mean "can be returned as ancillary data by recvmsg(), or returned by getsockopt() with the IPV6_PKTOPTIONS socket option." Same vibe, less typing ๐Ÿ’œ.

4.5.1. TCP Sticky Options

With getsockopt() and the IPV6_PKTOPTIONS option on a TCP socket, only the options from the most recently received segment get kept and returned to the caller, and only once the socket option has been set ๐Ÿ‘€. So TCP doesn't have to start saving copies of the options until the app says so. Lowkey efficient ๐Ÿ”ฅ.

The app is not allowed to specify ancillary data in a call to sendmsg() on a TCP socket, and none of the ancillary data described in this document ever gets returned as control information by recvmsg() on a TCP socket ๐Ÿ™…โ€โ™€๏ธ. TCP said "not me" to the whole ancillary data thing, and that's valid ๐Ÿ’€.

4.5.2. UDP and Raw Socket Sticky Options

The IPV6_PKTOPTIONS socket option can also be used with a UDP socket or a raw IPv6 socket ๐ŸŒˆ. The usual move is to set some options once, instead of passing them with every single sendmsg() call.

Unlike the TCP case, though, sticky options can be overridden on a per-packet basis. If you specify any ancillary data in a call to sendmsg() on a UDP or raw IPv6 socket, then none of the sticky options get sent with that datagram ๐Ÿฆ‹. The one-off ancillary data wins for that packet, and the sticky options sit it out.


5. Packet Information โœจ

There are four pieces of info an app can specify for an outgoing packet using ancillary data ๐Ÿ’…:

  1. the source IPv6 address,
  2. the outgoing interface index,
  3. the outgoing hop limit, and
  4. the next hop address.

Three similar pieces of info can come back for a received packet as ancillary data ๐Ÿงš:

  1. the destination IPv6 address,
  2. the arriving interface index, and
  3. the arriving hop limit.

The first two pieces of info live in an in6_pktinfo structure, which gets sent as ancillary data with sendmsg() and received as ancillary data with recvmsg(). This structure is defined when you include the <netinet/in.h> header ๐Ÿ“–. Here's the lineup:

   struct in6_pktinfo {
     struct in6_addr ipi6_addr;    /* src/dst IPv6 address */
     unsigned int    ipi6_ifindex; /* send/recv interface index */
   };

In the cmsghdr structure holding this ancillary data, the cmsg_level member is IPPROTO_IPV6, the cmsg_type member is IPV6_PKTINFO, and the first byte of cmsg_data[] is the first byte of the in6_pktinfo structure ๐ŸŽ€.

This info only gets returned as ancillary data by recvmsg() if the app has enabled the IPV6_PKTINFO socket option, like so:

int  on = 1;
setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &on, sizeof(on));

Sending this info is simple, no drama. Just specify the control information as ancillary data for sendmsg() ๐Ÿ’–.

(Note: The hop limit is not contained in the in6_pktinfo structure, and here's why ๐Ÿ“. Some UDP servers want to answer client requests by sending their reply out the same interface the request arrived on, with the reply's source IPv6 address set to the request's destination IPv6 address. To do this, the app can enable just the IPV6_PKTINFO socket option, then use the control information received from recvmsg() as the outgoing control information for sendmsg(). The app doesn't need to examine or modify the in6_pktinfo structure at all. But if the hop limit lived in that structure, the app would have to parse the received control information and change the hop limit member, since the received hop limit isn't the value you want for an outgoing packet.)

5.1. Specifying/Receiving the Interface

Interfaces on an IPv6 node are identified by a small positive integer, as described in Section 4 of [RFC-2133] ๐ŸŒ. That document also describes a function that maps an interface name to its interface index, a function that maps an interface index to its name, and a function that returns all the interface names and indexes. Notice that, from this document, no interface is ever assigned an index of 0 ๐Ÿ™…โ€โ™€๏ธ.

When you're specifying the outgoing interface, if the ipi6_ifindex value is 0, the kernel picks the outgoing interface for you ๐Ÿคทโ€โ™€๏ธ. If the app specifies an outgoing interface for a multicast packet, the interface given in the ancillary data overrides any interface set by the IPV6_MULTICAST_IF socket option (described in [RFC-2133]), but only for that one call to sendmsg() ๐Ÿฆ‹.

When the IPV6_PKTINFO socket option is enabled, the received interface index is always returned as the ipi6_ifindex member of the in6_pktinfo structure โœจ.

5.2. Specifying/Receiving Source/Destination Address

The source IPv6 address can be specified by calling bind() before each output operation ๐Ÿ’…. But supplying the source address together with the data needs less overhead (fewer system calls) and less state to store and protect in a multithreaded app, so it's the more iconic move ๐Ÿ‘‘.

When you specify the source IPv6 address as ancillary data, here's how it plays out. If the ipi6_addr member of the in6_pktinfo structure is the unspecified address (IN6ADDR_ANY_INIT), then (a) if an address is currently bound to the socket, that address is used as the source address, or (b) if no address is bound to the socket, the kernel picks the source address ๐Ÿง . If ipi6_addr is not the unspecified address, but the socket has already bound a source address, then the ipi6_addr value overrides the bound source address for that output operation only ๐Ÿ”ฅ.

The kernel must verify that the requested source address is indeed a unicast address assigned to the node ๐Ÿ›ก๏ธ.

When the in6_pktinfo structure comes back as ancillary data from recvmsg(), the ipi6_addr member contains the destination IPv6 address from the received packet ๐Ÿ’œ.

5.3. Specifying/Receiving the Hop Limit

The outgoing hop limit is normally set with either the IPV6_UNICAST_HOPS socket option or the IPV6_MULTICAST_HOPS socket option, and both are described in [RFC-2133] ๐Ÿ“. Specifying the hop limit as ancillary data lets the app override either the kernel's default or a previously set value, for a unicast or multicast destination, for one single output operation ๐Ÿ’…. Getting the received hop limit back is useful for programs like Traceroute, and for IPv6 apps that need to check that the received hop limit is 255 (for example, to confirm that the packet has not been forwarded) ๐Ÿงš.

The received hop limit is returned as ancillary data by recvmsg() only if the app has enabled the IPV6_HOPLIMIT socket option:

int  on = 1;
setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));

In the cmsghdr structure holding this ancillary data, the cmsg_level member is IPPROTO_IPV6, the cmsg_type member is IPV6_HOPLIMIT, and the first byte of cmsg_data[] is the first byte of the integer hop limit ๐ŸŒธ.

Specifying the outgoing hop limit is also easy: just specify the control information as ancillary data for sendmsg() ๐Ÿ’–. As specified in [RFC-2133], the integer hop limit value is interpreted like this:

       x < -1:        return an error of EINVAL
       x == -1:       use kernel default
       0 <= x <= 255: use x
       x >= 256:      return an error of EINVAL

5.4. Specifying the Next Hop Address

The IPV6_NEXTHOP ancillary data object specifies the next hop for the datagram, as a socket address structure ๐Ÿ›ฃ๏ธ. In the cmsghdr structure holding this ancillary data, the cmsg_level member is IPPROTO_IPV6, the cmsg_type member is IPV6_NEXTHOP, and the first byte of cmsg_data[] is the first byte of the socket address structure โœจ.

This is a privileged option ๐Ÿ‘‘. (Note: It is implementation defined, and beyond the scope of this document, to define what "privileged" means. Unix systems use this term to mean the process must have an effective user ID of 0.)

If the socket address structure contains an IPv6 address (for example, when the sin6_family member is AF_INET6), then the node identified by that address must be a neighbor of the sending host ๐Ÿก. If that address equals the destination IPv6 address of the datagram, then this is equivalent to the existing SO_DONTROUTE socket option ๐Ÿซถ.

5.5. Additional Errors with sendmsg()

With the IPV6_PKTINFO socket option, there are no additional errors possible with the call to recvmsg() ๐Ÿ™Œ. But when you specify the outgoing interface or the source address, extra errors become possible from sendmsg() ๐Ÿ˜ญ. The following are examples. Some of these may not be provided by some implementations, and some implementations may define additional errors too ๐ŸŒˆ.

   ENXIO         The interface specified by ipi6_ifindex does not exist.

   ENETDOWN      The interface specified by ipi6_ifindex is not enabled
                 for IPv6 use.

   EADDRNOTAVAIL ipi6_ifindex specifies an interface but the address
                 ipi6_addr is not available for use on that interface.

   EHOSTUNREACH  No route to the destination exists over the interface
                 specified by ifi6_ifindex.

6. Hop-By-Hop Options โœจ

okay bestie, one Hop-by-Hop options header can hold a variable number of options ๐ŸŽ€ and every single one is TLV-encoded, which means type, length, and value all lined up like a little squad ๐Ÿ’…

Right now only three Hop-by-Hop options are defined for IPv6 [RFC-1883]: Jumbo Payload, Pad1, and PadN ๐ŸŒธ. There's also a proposal floating around for a router-alert Hop-by-Hop option, so it's giving "coming soon" ๐Ÿ‘€. The Jumbo Payload option should not be passed back to an application, and an application should get an error if it tries to set it ๐Ÿ™…โ€โ™€๏ธ. The kernel handles this one entirely on its own. Datagram-based apps kinda specify it indirectly, as the size of the datagram they send, and they get it back indirectly as the length of the datagram they receive ๐Ÿ“ฆ. The two pad options are just for alignment. A sending kernel adds them automatically when needed, and the receiving kernel ignores them ๐Ÿงš. So this part of the API is really set aside for future Hop-by-Hop options that an application might need to set and receive ๐Ÿ’–

Individual Hop-by-Hop options (and Destination options, which we'll get to shortly and which are pretty similar to Hop-by-Hop ones) can have specific alignment requirements ๐Ÿ“. For example, the 4-byte Jumbo Payload length should sit on a 4-byte boundary, and IPv6 addresses are normally aligned on an 8-byte boundary ๐Ÿซถ. The requirements and terminology are covered in Section 4.2 and Appendix A of [RFC-1883]. Each option's alignment comes from two values, called x and y, written as "xn + y". This means the option must appear at an integer multiple of x bytes from the beginning of the options header (x can be 1, 2, 4, or 8), plus y bytes (y is between 0 and 7, inclusive) โœจ. Pad1 and PadN get inserted as needed to keep everything aligned. Whatever code builds either a Hop-by-Hop options header or a Destination options header needs to know the x and y values for each option ๐Ÿง 

Multiple Hop-by-Hop options can be specified by the application. Normally one ancillary data object describes all of them (since each option is already TLV-encoded), but the application can also specify multiple ancillary data objects for the Hop-by-Hop options, with each object holding one or more options ๐Ÿ“. Be careful designing the API for these, because:

  1. it may be possible for some future Hop-by-Hop options to be generated by the application and processed entirely by the application (e.g., the kernel may not know the alignment restrictions for the option),
  2. it must be possible for the kernel to insert its own Hop-by-Hop options in an outgoing packet (e.g., the Jumbo Payload option),
  3. the application can place one or more Hop-by-Hop options into a single ancillary data object,
  4. if the application specifies multiple ancillary data objects, each containing one or more Hop-by-Hop options, the kernel must combine these into a single Hop-by-Hop options header, and
  5. it must be possible for the kernel to remove some Hop-by-Hop options from a received packet before returning the remaining Hop-by-Hop options to the application. (This removal might consist of the kernel converting the option into a pad option of the same length.)

Last thing ๐Ÿ‘‘: access to some Hop-by-Hop options, or to some Destination options, might need special privilege. Normal apps (without special privilege) might be forbidden from setting certain options in outgoing packets, and might never see certain options in received packets ๐Ÿ”’

6.1. Receiving Hop-by-Hop Options

To receive Hop-by-Hop options, the application has to enable the IPV6_HOPOPTS socket option ๐ŸŽง:

int  on = 1;
setsockopt(fd, IPPROTO_IPV6, IPV6_HOPOPTS, &on, sizeof(on));

All the Hop-by-Hop options come back as one ancillary data object described by a cmsghdr structure. The cmsg_level member will be IPPROTO_IPV6 and the cmsg_type member will be IPV6_HOPOPTS. After that, these options get processed by calling the inet6_option_next() and inet6_option_find() functions, which we'll cover shortly ๐Ÿ’…

6.2. Sending Hop-by-Hop Options

To send one or more Hop-by-Hop options, the application just specifies them as ancillary data in a call to sendmsg(). No socket option needs to be set ๐Ÿš€

Normally all the Hop-by-Hop options are specified by a single ancillary data object. You can also specify multiple ancillary data objects, each containing one or more Hop-by-Hop options, and then the kernel will combine all of them into a single Hop-by-Hop extension header. But it should be more efficient to use one ancillary data object for all the Hop-by-Hop options ๐Ÿƒโ€โ™€๏ธ. The cmsg_level member is set to IPPROTO_IPV6 and the cmsg_type member is set to IPV6_HOPOPTS. The option is normally constructed using the inet6_option_init(), inet6_option_append(), and inet6_option_alloc() functions, which we'll get to shortly ๐Ÿงš

Additional errors may be possible from sendmsg() if the specified option is in error.

6.3. Hop-by-Hop and Destination Options Processing

Building and parsing Hop-by-Hop and Destination options is complicated for the reasons we covered earlier, so we define a set of functions to help the application ๐Ÿ› ๏ธ. The function prototypes for these are all in the <netinet/in.h> header.

6.3.1. inet6_option_space

int inet6_option_space(int nbytes);

This function returns the number of bytes needed to hold an option when it's stored as ancillary data. That count includes the cmsghdr structure at the beginning, plus any padding at the end (to make the size a multiple of 8 bytes) ๐Ÿ“. The argument is the size of the structure defining the option, which must include any pad bytes at the beginning (the value y in the alignment term "xn + y"), the type byte, the length byte, and the option data.

(Note: If multiple options are stored in a single ancillary data object, which is the recommended technique, this function overestimates the amount of space needed by the size of N-1 cmsghdr structures, where N is the number of options to be stored in the object. This is of little consequence, since it is assumed that most Hop-by-Hop option headers and Destination option headers carry only one option (p. 33 of [RFC-1883]).)

6.3.2. inet6_option_init

int inet6_option_init(void *bp, struct cmsghdr **cmsgp, int
type);

This function gets called once per ancillary data object that will contain either Hop-by-Hop or Destination options. It returns 0 on success or -1 on an error ๐ŸŒˆ.

bp is a pointer to previously allocated space that will hold the ancillary data object. It must be large enough to contain all the individual options that get added by later calls to inet6_option_append() and inet6_option_alloc().

cmsgp is a pointer to a pointer to a cmsghdr structure. *cmsgp is initialized by this function to point to the cmsghdr structure it constructs in the buffer pointed to by bp.

type is either IPV6_HOPOPTS or IPV6_DSTOPTS. This type gets stored in the cmsg_type member of the cmsghdr structure pointed to by *cmsgp.

6.3.3. inet6_option_append

int inet6_option_append(struct cmsghdr *cmsg, const uint8_t *typep,
                        int multx, int plusy);

This function appends a Hop-by-Hop option or a Destination option into an ancillary data object that has been initialized by inet6_option_init(). It returns 0 if it succeeds or -1 on an error ๐Ÿ’–

cmsg is a pointer to the cmsghdr structure that must have been initialized by inet6_option_init().

typep is a pointer to the 8-bit option type. It's assumed that this field is immediately followed by the 8-bit option data length field, which is then followed immediately by the option data. The caller initializes these three fields (the type-length-value, or TLV) before calling this function.

The option type must have a value from 2 to 255, inclusive. (0 and 1 are reserved for the Pad1 and PadN options, respectively.)

The option data length must have a value between 0 and 255, inclusive, and is the length of the option data that follows.

multx is the value x in the alignment term "xn + y" described earlier. It must have a value of 1, 2, 4, or 8.

plusy is the value y in the alignment term "xn + y" described earlier. It must have a value between 0 and 7, inclusive.

6.3.4. inet6_option_alloc

uint8_t *inet6_option_alloc(struct cmsghdr *cmsg, int datalen,
                            int multx, int plusy);

This function appends a Hop-by-Hop option or a Destination option into an ancillary data object that has been initialized by inet6_option_init(). On success it returns a pointer to the 8-bit option type field that starts the option, or NULL on an error ๐ŸŽฏ.

The difference between this function and inet6_option_append() is that inet6_option_append() copies the contents of a previously built option into the ancillary data object, while this function returns a pointer to the space in the data object where the option's TLV must then be built by the caller.

cmsg is a pointer to the cmsghdr structure that must have been initialized by inet6_option_init().

datalen is the value of the option data length byte for this option. This value is required as an argument so the function can determine whether padding must be appended at the end of the option. (The inet6_option_append() function doesn't need a data length argument, since the caller must already have stored the option data length.)

multx is the value x in the alignment term "xn + y" described earlier. It must have a value of 1, 2, 4, or 8.

plusy is the value y in the alignment term "xn + y" described earlier. It must have a value between 0 and 7, inclusive.

6.3.5. inet6_option_next

int inet6_option_next(const struct cmsghdr *cmsg, uint8_t
**tptrp);

This function processes the next Hop-by-Hop option or Destination option in an ancillary data object ๐Ÿ”. If another option is left to process, the return value is 0 and *tptrp points to the 8-bit option type field (which is followed by the 8-bit option data length, then the option data). If no more options are left, the return value is -1 and *tptrp is NULL. If an error occurs, the return value is -1 and *tptrp is not NULL.

cmsg is a pointer to a cmsghdr structure where cmsg_level equals IPPROTO_IPV6 and cmsg_type equals either IPV6_HOPOPTS or IPV6_DSTOPTS.

tptrp is a pointer to a pointer to an 8-bit byte. The function uses *tptrp to remember its place in the ancillary data object each time it's called. The first time this function is called for a given ancillary data object, *tptrp must be set to NULL.

Each time the function returns success, *tptrp points to the 8-bit option type field for the next option to be processed ๐Ÿซง

6.3.6. inet6_option_find ๐ŸŽฏ

int inet6_option_find(const struct cmsghdr *cmsg, uint8_t *tptrp,
                             int type);

okay so this one is basically inet6_option_next() but make it iconic ๐Ÿ’… instead of grabbing whatever option comes next, YOU get to pick the exact type you're hunting for. it's giving main character energy ๐Ÿ‘‘

cmsg is a pointer to a cmsghdr structure where cmsg_level equals IPPROTO_IPV6 and cmsg_type equals either IPV6_HOPOPTS or IPV6_DSTOPTS ๐ŸŽ€

tptrp is a pointer to a pointer to an 8-bit byte. the function uses *tptrp to remember its place in the ancillary data object every time you call it ๐Ÿง  the first time you call this for a given ancillary data object, *tptrp must be set to NULL. bestie, don't skip this step ๐Ÿ™ˆ

the search starts right after whatever *tptrp is pointing at. if it finds an option of the type you asked for, it returns 0 and *tptrp points to the 8-bit option type field of that option โœจ if it doesn't find one, the return value is -1 and *tptrp is NULL ๐Ÿ’€ and if something goes wrong, the return value is -1 but *tptrp is not NULL, so you can tell the difference between "not found" and "oops, error" ๐Ÿซถ


6.3.7. Options Examples ๐Ÿงš

okay the fun part ๐ŸŒธ we're going to build two Hop-by-Hop options in one go. first we define two options, called X and Y, taken from the example in Appendix A of [RFC-1883]. we assume that all options will have structure definitions similar to what is shown below ๐Ÿ’–

     /* option X and option Y are defined in [RFC-1883], pp. 33-34 */
#define IP6_X_OPT_TYPE       X   /* replace X with assigned value */
#define IP6_X_OPT_LEN       12
#define IP6_X_OPT_MULTX      8   /* 8n + 2 alignment */
#define IP6_X_OPT_OFFSETY    2

struct ip6_X_opt {
  uint8_t   ip6_X_opt_pad[IP6_X_OPT_OFFSETY];
  uint8_t   ip6_X_opt_type;
  uint8_t   ip6_X_opt_len;
  uint32_t  ip6_X_opt_val1;
  uint64_t  ip6_X_opt_val2;
};
#define IP6_Y_OPT_TYPE       Y   /* replace Y with assigned value */
#define IP6_Y_OPT_LEN        7
#define IP6_Y_OPT_MULTX      4   /* 4n + 3 alignment */
#define IP6_Y_OPT_OFFSETY    3

struct ip6_Y_opt {
  uint8_t   ip6_Y_opt_pad[IP6_Y_OPT_OFFSETY];
  uint8_t   ip6_Y_opt_type;
  uint8_t   ip6_Y_opt_len;
  uint8_t   ip6_Y_opt_val1;
  uint16_t  ip6_Y_opt_val2;
  uint32_t  ip6_Y_opt_val3;
};

now the code fragment that builds ONE ancillary data object holding both options ๐Ÿ“ it's giving efficiency ๐Ÿ”ฅ

struct msghdr  msg;
struct cmsghdr  *cmsgptr;
struct ip6_X_opt  optX;
struct ip6_Y_opt  optY;

msg.msg_control = malloc(inet6_option_space(sizeof(optX) +
                                            sizeof(optY)));

inet6_option_init(msg.msg_control, &cmsgptr, IPV6_HOPOPTS);

optX.ip6_X_opt_type = IP6_X_OPT_TYPE;
optX.ip6_X_opt_len  = IP6_X_OPT_LEN;
optX.ip6_X_opt_val1 = <32-bit value>;
optX.ip6_X_opt_val2 = <64-bit value>;
inet6_option_append(cmsgptr, &optX.ip6_X_opt_type,
                    IP6_X_OPT_MULTX, IP6_X_OPT_OFFSETY);

optY.ip6_Y_opt_type = IP6_Y_OPT_TYPE;
optY.ip6_Y_opt_len  = IP6_Y_OPT_LEN;
optY.ip6_Y_opt_val1 = <8-bit value>;
optY.ip6_Y_opt_val2 = <16-bit value>;
optY.ip6_Y_opt_val3 = <32-bit value>;
inet6_option_append(cmsgptr, &optY.ip6_Y_opt_type,
                    IP6_Y_OPT_MULTX, IP6_Y_OPT_OFFSETY);
msg.msg_controllen = cmsgptr->cmsg_len;

okay bestie, the inet6_option_init() call is the one that actually builds the cmsghdr inside the control buffer ๐ŸŽ€

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_len = CMSG_LEN(0) = 12                             |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_level = IPPROTO_IPV6                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_type = IPV6_HOPOPTS                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

quick vibe check on the setup ๐Ÿ’…: we're pretending we're on a 32-bit architecture where sizeof(struct cmsghdr) is 12, and everything has to line up on 4-byte boundaries. That's the ALIGN() macro from the sample CMSG_xxx() implementations, and it rounds up to a multiple of 4. no cap, the alignment math is the whole mood ๐Ÿงš

The first inet6_option_append() call adds option X ๐Ÿฆ‹. Since it's the very first option in the ancillary data object, we grab 2 bytes for the Next Header byte and the Hdr Ext Len byte. The kernel fills in Next Header later, based on whatever header comes after this one, and Hdr Ext Len gets set to 1. Those 2 bytes are the padding (IP6_X_OPT_OFFSETY) REQUIRED at the front of this option, so it's giving very organized ๐Ÿ’œ

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_len = 28                                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_level = IPPROTO_IPV6                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_type = IPV6_HOPOPTS                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |  Next Header  | Hdr Ext Len=1 | Option Type=X |Opt Data Len=12|
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         4-octet field                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                               |
     +                         8-octet field                         +
     |                                                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

the cmsg_len member of the cmsghdr structure gets bumped up by 16, which is the size of the option ๐Ÿ“ˆ

The next inet6_option_append() call tacks on option Y ๐ŸŒธ

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_len = 44                                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_level = IPPROTO_IPV6                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_type = IPV6_HOPOPTS                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |  Next Header  | Hdr Ext Len=3 | Option Type=X |Opt Data Len=12|
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         4-octet field                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                               |
     +                         8-octet field                         +
     |                                                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     | PadN Option=1 |Opt Data Len=1 |       0       | Option Type=Y |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |Opt Data Len=7 | 1-octet field |         2-octet field         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         4-octet field                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     | PadN Option=1 |Opt Data Len=2 |       0       |       0       |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

16 bytes get appended by this function, so cmsg_len becomes 44. The inet6_option_append() function notices the appended data needs 4 bytes of padding at the end, to make the size a multiple of 8, and it slaps the PadN option on before returning ๐Ÿ™ˆ The Hdr Ext Len byte goes up by 2, so it's 3 now.

Alternately, the app could build two ancillary data objects, one per option ๐Ÿคท. This will probably be less efficient than combining the two options into a single ancillary data object (like we just did). The kernel MUST combine these into a single Hop-by-Hop extension header in the final IPv6 packet ๐Ÿ‘‘

Each call to inet6_option_init() builds a new cmsghdr structure, and the final result looks like this:

     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_len = 28                                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_level = IPPROTO_IPV6                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_type = IPV6_HOPOPTS                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |  Next Header  | Hdr Ext Len=1 | Option Type=X |Opt Data Len=12|
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         4-octet field                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                               |
     +                         8-octet field                         +
     |                                                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_len = 28                                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_level = IPPROTO_IPV6                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |       cmsg_type = IPV6_HOPOPTS                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |  Next Header  | Hdr Ext Len=1 | Pad1 Option=0 | Option Type=Y |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |Opt Data Len=7 | 1-octet field |         2-octet field         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         4-octet field                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     | PadN Option=1 |Opt Data Len=2 |       0       |       0       |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

when the kernel combines these two options into a single Hop-by-Hop extension header, the first 3 bytes of the second ancillary data object (the Next Header byte, the Hdr Ext Len byte, and the Pad1 option) get merged into a PadN option that takes up 3 bytes ๐Ÿงฉ

Next up is the redo of the first example (two options in one ancillary data object), but this time with inet6_option_alloc() ๐Ÿ’–

uint8_t  *typep;
struct msghdr  msg;
struct cmsghdr  *cmsgptr;
struct ip6_X_opt  *optXp;  /* now a pointer, not a struct */
struct ip6_Y_opt  *optYp;  /* now a pointer, not a struct */

msg.msg_control = malloc(inet6_option_space(sizeof(*optXp) +
                                            sizeof(*optYp)));

inet6_option_init(msg.msg_control, &cmsgptr, IPV6_HOPOPTS);

typep = inet6_option_alloc(cmsgptr, IP6_X_OPT_LEN,
                           IP6_X_OPT_MULTX, IP6_X_OPT_OFFSETY);
optXp = (struct ip6_X_opt *) (typep - IP6_X_OPT_OFFSETY);
optXp->ip6_X_opt_type = IP6_X_OPT_TYPE;
optXp->ip6_X_opt_len  = IP6_X_OPT_LEN;
optXp->ip6_X_opt_val1 = <32-bit value>;
optXp->ip6_X_opt_val2 = <64-bit value>;

typep = inet6_option_alloc(cmsgptr, IP6_Y_OPT_LEN,
                           IP6_Y_OPT_MULTX, IP6_Y_OPT_OFFSETY);
optYp = (struct ip6_Y_opt *) (typep - IP6_Y_OPT_OFFSETY);
optYp->ip6_Y_opt_type = IP6_Y_OPT_TYPE;
optYp->ip6_Y_opt_len  = IP6_Y_OPT_LEN;
optYp->ip6_Y_opt_val1 = <8-bit value>;
optYp->ip6_Y_opt_val2 = <16-bit value>;
optYp->ip6_Y_opt_val3 = <32-bit value>;

msg.msg_controllen = cmsgptr->cmsg_len;

heads up ๐Ÿ‘€: inet6_option_alloc() hands back a pointer to the 8-bit option type field. If your program wants a pointer to the option structure that includes the front padding (like our ip6_X_opt and ip6_Y_opt definitions), the y-offset at the beginning of the structure MUST be subtracted from the returned pointer.

โš ๏ธ bestie, don't skip that subtraction. The returned pointer points at the option type field, not the start of the padding, so the struct math is off without it ๐Ÿ’€

Last bit: here's how to process Hop-by-Hop options with inet6_option_next() ๐Ÿ”ฅ

    struct msghdr   msg;
    struct cmsghdr  *cmsgptr;

    /* fill in msg */

    /* call recvmsg() */

    for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
         cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
        if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
            cmsgptr->cmsg_type == IPV6_HOPOPTS) {

            uint8_t  *tptr = NULL;

            while (inet6_option_next(cmsgptr, &tptr) == 0) {
                if (*tptr == IP6_X_OPT_TYPE) {
                    struct ip6_X_opt  *optXp;
                    optXp = (struct ip6_X_opt *) (tptr - IP6_X_OPT_OFFSETY);
                    <do whatever with> optXp->ip6_X_opt_val1;
                    <do whatever with> optXp->ip6_X_opt_val2;

                } else if (*tptr == IP6_Y_OPT_TYPE) {
                    struct ip6_Y_opt  *optYp;

                    optYp = (struct ip6_Y_opt *) (tptr - IP6_Y_OPT_OFFSETY);
                    <do whatever with> optYp->ip6_Y_opt_val1;
                    <do whatever with> optYp->ip6_Y_opt_val2;
                    <do whatever with> optYp->ip6_Y_opt_val3;
                }
            }
            if (tptr != NULL)
                <error encountered by inet6_option_next()>;
        }
    }

7. Destination Options โœจ

okay so a variable number of Destination options can show up in one or more Destination option headers ๐Ÿ’–. As defined in [RFC-1883], a Destination options header that appears before a Routing header gets processed by the first destination plus any subsequent destinations listed in the Routing header ๐Ÿฆ‹. One that appears after a Routing header is only processed by the final destination. Just like Hop-by-Hop options, each option in a Destination options header is TLV-encoded with a type, length, and value ๐ŸŽ€.

Today no Destination options are defined for IPv6 [RFC-1883], although proposals exist to use Destination options with mobility and anycasting ๐ŸŒธ.

7.1. Receiving Destination Options

To receive Destination options, the app has to turn on the IPV6_DSTOPTS socket option ๐Ÿ’…:

    int  on = 1;
    setsockopt(fd, IPPROTO_IPV6, IPV6_DSTOPTS, &on, sizeof(on));

All the Destination options before a Routing header come back as one ancillary data object described by a cmsghdr structure, and all the ones after a Routing header come back as another ancillary data object described by a cmsghdr structure โœจ. For these objects, cmsg_level will be IPPROTO_IPV6 and cmsg_type will be IPV6_HOPOPTS (yes, that's what it says ๐Ÿ™ˆ). Then you process them by calling inet6_option_next() and inet6_option_find() ๐Ÿงš.

7.2. Sending Destination Options

To send one or more Destination options, the app just specifies them as ancillary data in a call to sendmsg() ๐Ÿ’œ. No socket option needs to be set.

As described earlier, one set of Destination options can go before a Routing header, and one set can go after one. Each set can have one or more options.

Normally all the Destination options in a set go in a single ancillary data object, since each option is already TLV-encoded. Multiple ancillary data objects, each holding one or more Destination options, can also be specified. In that case the kernel combines all the Destination options in the set into a single Destination extension header. But it should be more efficient to use one ancillary data object for all the Destination options in a set ๐Ÿ”ฅ. cmsg_level is set to IPPROTO_IPV6 and cmsg_type is set to IPV6_DSTOPTS. The option is normally built using inet6_option_init(), inet6_option_append(), and inet6_option_alloc() โœจ.

Additional errors may be possible from sendmsg() if the specified option is in error.

8. Routing Header Option ๐ŸŒˆ

Source routing in IPv6 is done by specifying a Routing header as an extension header. There can be different types of Routing headers, but IPv6 currently defines only the Type 0 Routing header [RFC-1883]. This type supports up to 23 intermediate nodes. With that maximum number of intermediate nodes, plus a source and a destination, there are 24 hops, each of which is defined as a strict or loose hop.

Source routing with the IPv4 sockets API (the IP_OPTIONS socket option) requires the app to build the source route in the format that appears as the IPv4 header option. That means you need intimate knowledge of the IPv4 options format ๐Ÿ˜ญ. This IPv6 API, however, defines eight functions that the app calls to build and examine a Routing header, which is so much nicer ๐Ÿ‘‘. Four functions build a Routing header:

inet6_rthdr_space()    - return #bytes required for ancillary data
inet6_rthdr_init()     - initialize ancillary data for Routing header
inet6_rthdr_add()      - add IPv6 address & flags to Routing header
inet6_rthdr_lasthop()  - specify the flags for the final hop

Four functions deal with a returned Routing header:

inet6_rthdr_reverse()  - reverse a Routing header
inet6_rthdr_segments() - return #segments in a Routing header
inet6_rthdr_getaddr()  - fetch one address from a Routing header
inet6_rthdr_getflags() - fetch one flag from a Routing header

The function prototypes for these functions are all in the <netinet/in.h> header.

To receive a Routing header, the app must enable the IPV6_RTHDR socket option:

    int  on = 1;
    setsockopt(fd, IPPROTO_IPV6, IPV6_RTHDR, &on, sizeof(on));

To send a Routing header, the app just specifies it as ancillary data in a call to sendmsg().

A Routing header is passed between the app and the kernel as an ancillary data object. cmsg_level has a value of IPPROTO_IPV6, and cmsg_type has a value of IPV6_RTHDR. The contents of the cmsg_data[] member are implementation dependent and should not be accessed directly by the app. Instead, access them with the eight functions we're about to describe ๐Ÿ’….

The following constants are defined in the <netinet/in.h> header:

 #define IPV6_RTHDR_LOOSE     0 /* this hop need not be a neighbor */
 #define IPV6_RTHDR_STRICT    1 /* this hop must be a neighbor */

 #define IPV6_RTHDR_TYPE_0    0 /* IPv6 Routing header type 0 */

When a Routing header is specified, the destination address given to connect(), sendto(), or sendmsg() is the final destination address of the datagram. The Routing header then contains the addresses of all the intermediate nodes ๐Ÿฆ‹.

8.1. inet6_rthdr_space

    size_t inet6_rthdr_space(int type, int segments);

This function returns the number of bytes required to hold a Routing header of the specified type containing the specified number of segments (addresses). For an IPv6 Type 0 Routing header, the number of segments must be between 1 and 23, inclusive. The return value includes the size of the cmsghdr structure that precedes the Routing header, and any required padding.

If the return value is 0, then either the type of the Routing header is not supported by this implementation, or the number of segments is invalid for this type of Routing header.

(Note: This function returns the size but does not allocate the space required for the ancillary data. This lets an application allocate a larger buffer if other ancillary data objects are desired, since all the ancillary data objects must be specified to sendmsg() as a single msg_control buffer. ๐Ÿงบ)

8.2. inet6_rthdr_init

    struct cmsghdr *inet6_rthdr_init(void *bp, int type);

This function initializes the buffer pointed to by bp to contain a cmsghdr structure followed by a Routing header of the specified type. The cmsg_len member of the cmsghdr structure is initialized to the size of the structure plus the amount of space required by the Routing header. The cmsg_level and cmsg_type members are also initialized as required.

The caller must allocate the buffer, and its size can be determined by calling inet6_rthdr_space().

Upon success, the return value is the pointer to the cmsghdr structure, and this is then used as the first argument to the next two functions. Upon an error, the return value is NULL.

8.3. inet6_rthdr_add

    int inet6_rthdr_add(struct cmsghdr *cmsg,
                        const struct in6_addr *addr, unsigned int flags);

This function adds the address pointed to by addr to the end of the Routing header being constructed, and sets the type of this hop to the value of flags. For an IPv6 Type 0 Routing header, flags must be either IPV6_RTHDR_LOOSE or IPV6_RTHDR_STRICT.

If successful, the cmsg_len member of the cmsghdr structure is updated to account for the new address in the Routing header, and the return value of the function is 0. Upon an error, the return value of the function is -1.

8.4. inet6_rthdr_lasthop

    int inet6_rthdr_lasthop(struct cmsghdr *cmsg,
                            unsigned int flags);

This function specifies the Strict/Loose flag for the final hop of a Routing header. For an IPv6 Type 0 Routing header, flags must be either IPV6_RTHDR_LOOSE or IPV6_RTHDR_STRICT.

The return value of the function is 0 upon success, or -1 upon an error.

Notice that a Routing header specifying N intermediate nodes requires N+1 Strict/Loose flags. This requires N calls to inet6_rthdr_add() followed by one call to inet6_rthdr_lasthop() ๐Ÿงฎ.

8.5. inet6_rthdr_reverse

    int inet6_rthdr_reverse(const struct cmsghdr *in, struct cmsghdr *out);

This function takes a Routing header that was received as ancillary data (pointed to by the first argument) and writes a new Routing header that sends datagrams along the reverse of that route. Both arguments are allowed to point to the same buffer, which means the reversal can happen in place ๐Ÿ”„.

The return value of the function is 0 on success, or -1 upon an error.

8.6. inet6_rthdr_segments

    int inet6_rthdr_segments(const struct cmsghdr *cmsg);

This function returns the number of segments (addresses) contained in the Routing header described by cmsg. On success, the return value is between 1 and 23, inclusive. The return value of the function is -1 upon an error.

8.7. inet6_rthdr_getaddr

    struct in6_addr *inet6_rthdr_getaddr(struct cmsghdr *cmsg, int
    index);

This function returns a pointer to the IPv6 address specified by index (which must have a value between 1 and the value returned by inet6_rthdr_segments()) in the Routing header described by cmsg. An application should first call inet6_rthdr_segments() to obtain the number of segments in the Routing header ๐Ÿ”.

Upon an error, the return value of the function is NULL.

8.8. inet6_rthdr_getflags

    int inet6_rthdr_getflags(const struct cmsghdr *cmsg, int index);

This function returns the flags value specified by index (which must have a value between 0 and the value returned by inet6_rthdr_segments()) in the Routing header described by cmsg. For an IPv6 Type 0 Routing header, the return value will be either IPV6_RTHDR_LOOSE or IPV6_RTHDR_STRICT.

Upon an error, the return value of the function is -1.

(Note: Addresses are indexed starting at 1, and flags starting at 0, to maintain consistency with the terminology and figures in [RFC-1883]. ๐Ÿง )


8.9. Routing Header Example โœจ

okay bestie, let's walk through the Routing header functions with the example on p. 18 of [RFC-1883] ๐Ÿฆ‹ we've got a source S, a destination D, and three middle stops I1, I2, and I3. each hop has a Strict/Loose flag, f0, f1, f2, and f3 ๐Ÿ’…

   f0        f1        f2        f3
S -----> I1 -----> I2 -----> I3 -----> D

Here's the full picture of what the packet looks like at each stage ๐ŸŒธ

   src:    *    S         S         S         S   S
   dst:    D   I1        I2        I3         D   D
   A[1]:  I1   I2        I1        I1        I1  I1
   A[2]:  I2   I3        I3        I2        I2  I2
   A[3]:  I3    D         D         D        I3  I3
   #seg:   3    3         2         1         0   3

   check: f0        f1        f2        f3

so src and dst are the source and destination IPv6 addresses in the IPv6 header, and A[1], A[2], and A[3] are the three addresses sitting in the Routing header. #seg is the Segments Left field in that Routing header, and check tells you which bit of the Strict/Loose Bit Map (0 through 3, aka f0 through f3) each node looks at ๐Ÿงš

The six values under node S are what the application puts into the Routing header with sendmsg(). the sender's function calls look like this ๐Ÿ’–

void  *ptr;
struct msghdr  msg;
struct cmsghdr  *cmsgptr;
struct sockaddr_in6  I1, I2, I3, D;
unsigned int  f0, f1, f2, f3;

ptr = malloc(inet6_rthdr_space(IPV6_RTHDR_TYPE_0, 3));
cmsgptr = inet6_rthdr_init(ptr, IPV6_RTHDR_TYPE_0);

inet6_rthdr_add(cmsgptr, &I1.sin6_addr, f0);
inet6_rthdr_add(cmsgptr, &I2.sin6_addr, f1);
inet6_rthdr_add(cmsgptr, &I3.sin6_addr, f2);
inet6_rthdr_lasthop(cmsgptr, f3);

msg.msg_control = ptr;
msg.msg_controllen = cmsgptr->cmsg_len;

/* finish filling in msg{}, msg_name = D */
/* call sendmsg() */

we're also assuming the socket doesn't have a source address set, which is that little asterisk in the figure ๐Ÿ™ˆ

now the four columns of six values in the middle are the packet fields while it's in transit between each pair of nodes. the vibe check: before S actually sends the packet, the source address gets picked (replacing the asterisk), I1 becomes the datagram's destination, A[2] and A[3] get "shifted up", and D gets moved into A[3] ๐Ÿ”ฅ also, if f0 is IPV6_RTHDR_STRICT, then I1 must be a neighbor of S.

the columns under the destination node are what recvmsg() hands back, assuming the app has turned on both the IPV6_PKTINFO and IPV6_RTHDR socket options ๐Ÿ‘‘ the source is S (sitting in the sockaddr_in6 structure that msg_name points to), and the destination is D (returned as an ancillary data object in an in6_pktinfo structure). the ancillary data object for the Routing header holds three addresses (I1, I2, and I3) and four flags (f0, f1, f2, and f3). the number of segments is read from the Hdr Ext Len field in the Routing header, where a value of 6 means 3 addresses ๐ŸŽ€

so the receiver's calls go like this: inet6_rthdr_segments() returns 3, inet6_rthdr_getaddr(1) gives back I1, inet6_rthdr_getaddr(2) gives back I2, and inet6_rthdr_getaddr(3) gives back I3. inet6_rthdr_flags(0) returns f0, inet6_rthdr_flags(1) returns f1, inet6_rthdr_flags(2) returns f2, and inet6_rthdr_flags(3) returns f3 โœจ

if the receiving app then calls inet6_rthdr_reverse(), the three addresses flip to I3, I2, and I1, and the four Strict/Loose flags flip to f3, f2, f1, and f0 ๐Ÿฆ‹


okay now the fun part ๐Ÿง  this is what an implementation might actually store in the ancillary data object while the sending process is building the Routing header. say we're on a 32-bit architecture where sizeof(struct cmsghdr) is 12, with 4-byte boundary alignment. then calling inet6_rthdr_space(3) returns 68: 12 bytes for the cmsghdr structure, plus 56 bytes for the Routing header (8 + 3*16) ๐Ÿ’…

the call to inet6_rthdr_init() sets up the ancillary data object so it holds a Type 0 Routing header:

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_len = 20                                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_level = IPPROTO_IPV6                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_type = IPV6_RTHDR                                  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |  Next Header  | Hdr Ext Len=0 | Routing Type=0|  Seg Left=0   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |   Reserved    |             Strict/Loose Bit Map              |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

the first inet6_rthdr_add() call adds I1 to the list ๐ŸŒˆ

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_len = 36                                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_level = IPPROTO_IPV6                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_type = IPV6_RTHDR                                  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |  Next Header  | Hdr Ext Len=2 | Routing Type=0|  Seg Left=1   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |   Reserved    |X|           Strict/Loose Bit Map              |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[1] = I1                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

bit 0 of the Strict/Loose Bit Map holds f0, which we're just marking as X ๐Ÿซถ cmsg_len goes up by 16, the Hdr Ext Len field goes up by 2, and the Segments Left field goes up by 1.

the next inet6_rthdr_add() call adds I2 to the list ๐Ÿ’œ

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_len = 52                                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_level = IPPROTO_IPV6                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_type = IPV6_RTHDR                                  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |  Next Header  | Hdr Ext Len=4 | Routing Type=0|  Seg Left=2   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |   Reserved    |X|X|         Strict/Loose Bit Map              |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[1] = I1                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[2] = I2                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

the next bit of the Strict/Loose Bit Map holds f1. cmsg_len goes up by 16, the Hdr Ext Len field goes up by 2, and the Segments Left field goes up by 1 ๐Ÿ“

the last inet6_rthdr_add() call adds I3 to the list ๐Ÿงš

  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_len = 68                                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_level = IPPROTO_IPV6                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       cmsg_type = IPV6_RTHDR                                  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |  Next Header  | Hdr Ext Len=6 | Routing Type=0|  Seg Left=3   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |   Reserved    |X|X|X|       Strict/Loose Bit Map              |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[1] = I1                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[2] = I2                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                                                               |
  +                                                               +
  |                                                               |
  +                           Address[3] = I3                     +
  |                                                               |
  +                                                               +
  |                                                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

the next bit of the Strict/Loose Bit Map holds f2. cmsg_len goes up by 16, the Hdr Ext Len field goes up by 2, and the Segments Left field goes up by 1 ๐Ÿ’–

finally, inet6_rthdr_lasthop() sets the next bit of the Strict/Loose Bit Map to whatever f3 is. all the lengths stay the same, and that's the whole example, bestie ๐ŸŽ€โœจ

9. Ordering of Ancillary Data and IPv6 Extension Headers โœจ

okay so bestie, three IPv6 extension headers can be set by the app and handed back to the app through ancillary data with sendmsg() and recvmsg() ๐Ÿ’–: Hop-by-Hop options, Destination options, and the Routing header. when you pass in several ancillary data objects and they're any of these three, where they sit in the control buffer is directly tied to where they end up in the IPv6 datagram ๐ŸŽ€ so the API has some ordering rules you need to know when you use multiple objects with sendmsg() ๐Ÿ’…

same-type Hop-by-Hop options go into the Hop-by-Hop options header in the same order they show up in the control buffer ๐Ÿฆ‹ but if you give multiple Hop-by-Hop options with different option types, the kernel is allowed to shuffle them around to cut down on padding in the Hop-by-Hop options header โœจ Hop-by-Hop options can show up anywhere in the control buffer, and the kernel always gathers them up into one single Hop-by-Hop options header that sits right after the IPv6 header ๐Ÿ‘‘

Destination options follow the same vibe: (1) same-type ones keep their order, and (2) different-type ones can get reordered ๐ŸŒธ the kernel builds up to two Destination options headers, one before the Routing header and one after it ๐Ÿซถ if you specify a Routing header, every Destination option that comes before it in the control buffer lands in a Destination options header before the Routing header, and those options might get reordered too, following the two rules we just covered ๐Ÿ’œ same deal for the Destination options that come after the Routing header: they land in a Destination options header after the Routing header and can be shuffled, same two rules ๐Ÿ™ˆ

let's do an example, it'll make it click ๐Ÿงš say an app passes six ancillary data objects to sendmsg(): the first has two Hop-by-Hop options, the second has one Destination option, the third has two Destination options, the fourth has a Routing header, the fifth has a Hop-by-Hop option, and the sixth has two Destination options ๐Ÿ’€ we'll assume all the Hop-by-Hop options are different types, and so are all the Destination options. we number the options 1-9 by their order in the control buffer, and they're on the left below ๐Ÿ“

the middle shows the final arrangement of the options in the extension headers the kernel builds, and the right shows the four ancillary data objects the receiving app gets back ๐Ÿ˜ญ

       Sender's                                       Receiver's
    Ancillary Data      -->   IPv6 Extension   -->  Ancillary Data
       Objects                    Headers              Objects
    ------------------        ---------------       --------------
    HOPOPT-1,2 (first)        HOPHDR(J,7,1,2)       HOPOPT-7,1,2
    DSTOPT-3                  DSTHDR(4,5,3)         DSTOPT-4,5,3
    DSTOPT-4,5                RTHDR(6)              RTHDR-6
    RTHDR-6                   DSTHDR(8,9)           DSTOPT-8,9
    HOPOPT-7
    DSTOPT-8,9 (last)

the sender's two Hop-by-Hop objects got reordered, and so did the first two Destination objects โœจ the kernel also slips in a Jumbo Payload option (that's the J) before the sender's three Hop-by-Hop options ๐Ÿ”ฅ the first three Destination options have to be in a Destination header before the Routing header, and the last two have to be in a Destination header after it ๐Ÿ’…

last thing: if Destination options come after a Routing header in the control buffer, or if there's no Routing header at all, the kernel puts those Destination options after an authentication header and/or an encapsulating security payload header, if one's there ๐Ÿฆ‹

10. IPv6-Specific Options with IPv4-Mapped IPv6 Addresses ๐ŸŒˆ

the socket options and ancillary data stuff in this doc only applies to true IPv6 sockets ๐ŸŽ€ you can make an IPv6 socket that actually sends and receives IPv4 packets using IPv4-mapped IPv6 addresses, but how the options in this doc map onto an IPv4 datagram is totally out of scope here ๐Ÿ™ˆ

basically, if you try to set an IPv6-only option, like Hop-by-Hop options, Destination options, or a Routing header, on an IPv6 socket using IPv4-mapped IPv6 addresses, it'll probably throw an error ๐Ÿ˜ญ some implementations might still let you get at the packet info (source/destination address, send/receive interface, and hop limit) on that kind of socket though ๐Ÿงš

11. rresvport_af ๐Ÿ’œ

rresvport() gets used by rcmd(), and rcmd() gets called by a bunch of the "r" commands like rlogin ๐Ÿ“ new apps aren't being written with rcmd() anymore, but legacy apps like rlogin are still gonna use it, and they'll get ported to IPv6 ๐Ÿซถ

rresvport() makes an IPv4/TCP socket and binds a "reserved port" to it ๐ŸŒธ instead of making an IPv6 version of that function, we define a new function that takes an address family as an argument โœจ

    #include <unistd.h>

    int  rresvport_af(int *port, int family);

it acts just like the existing rresvport() function, except instead of only making an IPv4/TCP socket, it can also make an IPv6/TCP socket ๐Ÿ’– the family argument is AF_INET or AF_INET6, and there's a new error return, EAFNOSUPPORT, for when the address family isn't supported ๐Ÿ‘‘

(Note: there's not a lot of agreement on which header defines the rresvport() and rcmd() function prototypes. 4.4BSD defines them in <unistd.h>, others use <netdb.h>, and some don't define the prototypes at all. ๐Ÿ™ƒ)
(Note: we define this function only, and we don't define anything like rcmd_af() or rcmd6(). the reason is that rcmd() calls gethostbyname(), which returns the type of address (AF_INET or AF_INET6). so it should be possible to tweak rcmd() to support IPv4 or IPv6 based on the address family gethostbyname() returns ๐Ÿฆ‹)

12. Future Items ๐Ÿ”ฎ

some extra stuff might need standardizing, but nobody has come up with a concrete proposal for the API to handle these tasks yet ๐ŸŒท they might get covered in a later document ๐Ÿงš

12.1. Flow Labels ๐ŸŽ€

earlier revisions of this doc specified a set of inet6_flow_XXX() functions to assign, share, and free IPv6 flow labels ๐Ÿ’… but consensus said it was too early to specify this part of the API ๐Ÿ˜ญ

12.2. Path MTU Discovery and UDP ๐ŸŒธ

it might be nice to have a standard method for a UDP app to figure out the "maximum send transport-message size" (Section 5.1 of [RFC-1981]) to a given destination โœจ that would let the UDP app send smaller datagrams to the destination and avoid fragmentation ๐Ÿ’œ

12.3. Neighbor Reachability and UDP ๐Ÿซถ

it might also be good to have a standard method for a UDP app to tell the kernel that it's making forward progress with a given peer (Section 7.3.1 of [RFC-1970]) ๐Ÿฆ‹ that could save unneeded neighbor solicitations and neighbor advertisements ๐Ÿ™Œ

13. Summary of New Definitions ๐Ÿ’–

this list sums up the constants and structure definitions from this memo, sorted by header ๐ŸŒˆ

  <netinet/icmp6.h> ICMP6_DST_UNREACH
  <netinet/icmp6.h> ICMP6_DST_UNREACH_ADDR
  <netinet/icmp6.h> ICMP6_DST_UNREACH_ADMIN
  <netinet/icmp6.h> ICMP6_DST_UNREACH_NOPORT
  <netinet/icmp6.h> ICMP6_DST_UNREACH_NOROUTE
  <netinet/icmp6.h> ICMP6_DST_UNREACH_NOTNEIGHBOR
  <netinet/icmp6.h> ICMP6_ECHO_REPLY
  <netinet/icmp6.h> ICMP6_ECHO_REQUEST
  <netinet/icmp6.h> ICMP6_INFOMSG_MASK
  <netinet/icmp6.h> ICMP6_MEMBERSHIP_QUERY
  <netinet/icmp6.h> ICMP6_MEMBERSHIP_REDUCTION
  <netinet/icmp6.h> ICMP6_MEMBERSHIP_REPORT
  <netinet/icmp6.h> ICMP6_PACKET_TOO_BIG
  <netinet/icmp6.h> ICMP6_PARAMPROB_HEADER
  <netinet/icmp6.h> ICMP6_PARAMPROB_NEXTHEADER
  <netinet/icmp6.h> ICMP6_PARAMPROB_OPTION
  <netinet/icmp6.h> ICMP6_PARAM_PROB
  <netinet/icmp6.h> ICMP6_TIME_EXCEEDED
  <netinet/icmp6.h> ICMP6_TIME_EXCEED_REASSEMBLY
  <netinet/icmp6.h> ICMP6_TIME_EXCEED_TRANSIT
  <netinet/icmp6.h> ND_NA_FLAG_OVERRIDE
  <netinet/icmp6.h> ND_NA_FLAG_ROUTER
  <netinet/icmp6.h> ND_NA_FLAG_SOLICITED
  <netinet/icmp6.h> ND_NEIGHBOR_ADVERT
  <netinet/icmp6.h> ND_NEIGHBOR_SOLICIT
  <netinet/icmp6.h> ND_OPT_MTU
  <netinet/icmp6.h> ND_OPT_PI_FLAG_AUTO
  <netinet/icmp6.h> ND_OPT_PI_FLAG_ONLINK
  <netinet/icmp6.h> ND_OPT_PREFIX_INFORMATION
  <netinet/icmp6.h> ND_OPT_REDIRECTED_HEADER
  <netinet/icmp6.h> ND_OPT_SOURCE_LINKADDR
  <netinet/icmp6.h> ND_OPT_TARGET_LINKADDR
  <netinet/icmp6.h> ND_RA_FLAG_MANAGED
  <netinet/icmp6.h> ND_RA_FLAG_OTHER
  <netinet/icmp6.h> ND_REDIRECT
  <netinet/icmp6.h> ND_ROUTER_ADVERT
  <netinet/icmp6.h> ND_ROUTER_SOLICIT

  <netinet/icmp6.h> struct icmp6_filter{};
  <netinet/icmp6.h> struct icmp6_hdr{};
  <netinet/icmp6.h> struct nd_neighbor_advert{};
  <netinet/icmp6.h> struct nd_neighbor_solicit{};
  <netinet/icmp6.h> struct nd_opt_hdr{};
  <netinet/icmp6.h> struct nd_opt_mtu{};
  <netinet/icmp6.h> struct nd_opt_prefix_info{};
  <netinet/icmp6.h> struct nd_opt_rd_hdr{};
  <netinet/icmp6.h> struct nd_redirect{};
  <netinet/icmp6.h> struct nd_router_advert{};
  <netinet/icmp6.h> struct nd_router_solicit{};

  <netinet/in.h>    IPPROTO_AH
  <netinet/in.h>    IPPROTO_DSTOPTS
  <netinet/in.h>    IPPROTO_ESP
  <netinet/in.h>    IPPROTO_FRAGMENT
  <netinet/in.h>    IPPROTO_HOPOPTS
  <netinet/in.h>    IPPROTO_ICMPV6
  <netinet/in.h>    IPPROTO_IPV6
  <netinet/in.h>    IPPROTO_NONE
  <netinet/in.h>    IPPROTO_ROUTING
  <netinet/in.h>    IPV6_DSTOPTS
  <netinet/in.h>    IPV6_HOPLIMIT
  <netinet/in.h>    IPV6_HOPOPTS
  <netinet/in.h>    IPV6_NEXTHOP
  <netinet/in.h>    IPV6_PKTINFO
  <netinet/in.h>    IPV6_PKTOPTIONS
  <netinet/in.h>    IPV6_RTHDR
  <netinet/in.h>    IPV6_RTHDR_LOOSE
  <netinet/in.h>    IPV6_RTHDR_STRICT
  <netinet/in.h>    IPV6_RTHDR_TYPE_0
  <netinet/in.h>    struct in6_pktinfo{};

  <netinet/ip6.h>   IP6F_OFF_MASK
  <netinet/ip6.h>   IP6F_RESERVED_MASK
  <netinet/ip6.h>   IP6F_MORE_FRAG
  <netinet/ip6.h>   struct ip6_dest{};
  <netinet/ip6.h>   struct ip6_frag{};
  <netinet/ip6.h>   struct ip6_hbh{};
  <netinet/ip6.h>   struct ip6_hdr{};
  <netinet/ip6.h>   struct ip6_rthdr{};
  <netinet/ip6.h>   struct ip6_rthdr0{};

  <sys/socket.h>    struct cmsghdr{};
  <sys/socket.h>    struct msghdr{};

and this list sums up the function and macro prototypes from this memo, also sorted by header ๐Ÿง

<netinet/icmp6.h> void ICMP6_FILTER_SETBLOCK(int,
                                              struct icmp6_filter *);
<netinet/icmp6.h> void ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter *);
<netinet/icmp6.h> void ICMP6_FILTER_SETPASS(int, struct icmp6_filter *);
<netinet/icmp6.h> void ICMP6_FILTER_SETPASSALL(struct icmp6_filter *);
<netinet/icmp6.h> int  ICMP6_FILTER_WILLBLOCK(int,
                                         const struct icmp6_filter *);
<netinet/icmp6.h> int  ICMP6_FILTER_WILLPASS(int,
                                           const struct icmp6_filter *);

<netinet/in.h>    int IN6_ARE_ADDR_EQUAL(const struct in6_addr *,
                                         const struct in6_addr *);

<netinet/in.h>    uint8_t *inet6_option_alloc(struct cmsghdr *,
                                               int, int, int);
<netinet/in.h>    int inet6_option_append(struct cmsghdr *,
                                          const uint8_t *, int, int);
<netinet/in.h>    int inet6_option_find(const struct cmsghdr *,
                                        uint8_t *, int);
<netinet/in.h>    int inet6_option_init(void *, struct cmsghdr **, int);
<netinet/in.h>    int inet6_option_next(const struct cmsghdr *,
                                        uint8_t **);
<netinet/in.h>    int inet6_option_space(int);

<netinet/in.h>    int inet6_rthdr_add(struct cmsghdr *,
                                      const struct in6_addr *,
                                      unsigned int);
<netinet/in.h>    struct in6_addr inet6_rthdr_getaddr(struct cmsghdr *,
                                                      int);
<netinet/in.h>    int inet6_rthdr_getflags(const struct cmsghdr *, int);
<netinet/in.h>    struct cmsghdr *inet6_rthdr_init(void *, int);
<netinet/in.h>    int inet6_rthdr_lasthop(struct cmsghdr *,
                                                          unsigned int);
<netinet/in.h>    int inet6_rthdr_reverse(const struct cmsghdr *,
                                          struct cmsghdr *);
<netinet/in.h>    int inet6_rthdr_segments(const struct cmsghdr *);
<netinet/in.h>    size_t inet6_rthdr_space(int, int);
<sys/socket.h>    unsigned char *CMSG_DATA(const struct cmsghdr *);
<sys/socket.h>    struct cmsghdr *CMSG_FIRSTHDR(const struct msghdr *);
<sys/socket.h>    unsigned int CMSG_LEN(unsigned int);
<sys/socket.h>    struct cmsghdr *CMSG_NXTHDR(const struct msghdr *mhdr,
                                              const struct cmsghdr *);
<sys/socket.h>    unsigned int CMSG_SPACE(unsigned int);

<unistd.h>        int rresvport_af(int *, int);

14. Security Considerations ๐Ÿ”โœจ

okay bestie, security is where we get serious ๐Ÿ˜ค the setting of certain Hop-by-Hop options and Destination options may be restricted to privileged processes ๐Ÿ›ก๏ธ and similarly, some Hop-by-Hop options and Destination options may not be returned to nonprivileged applications ๐Ÿ™ˆ so not every app gets to peek at everything, periodt ๐Ÿ’…

15. Change History ๐Ÿ“œ๐ŸŒธ

okay so this is the tea on every edition and draft, the full timeline ๐Ÿซถ

Changes from the June 1997 Edition (-03 draft) ๐Ÿ“

  • Added a note that defined constants for multibyte fields are in network byte order. This affects the ip6f_offlg member of the Fragment header (Section 2.1.2) and the nd_na_flags_reserved member of the nd_neighbor_advert structure (Section 2.2.2).
  • Section 5: the ipi6_ifindex member of the in6_pktinfo structure should be "unsigned int" instead of "int", for consistency with the interface indexes in [RFC-2133].
  • Section 6.3.7: the three calls to inet6_option_space() in the examples needed to be arguments to malloc(). The final one of these was missing the "6" in the name "inet6_option_space".
  • Section 8.6: the function prototype for inet6_rthdr_segments() was missing the ending semicolon.

Changes from the March 1997 Edition (-02 draft) ๐Ÿฆ‹

  • In May 1997 Draft 6.6 of Posix 1003.1g (called Posix.1g herein) passed ballot and will be forwarded to the IEEE Standards Board later in 1997 for final approval. Some changes made for this final Posix draft are incorporated into this Internet Draft, specifically the datatypes mentioned in Section 1 (and used throughout the text), and the socklen_t datatype used in Section 4.1 and 4.2.
  • Section 1: Added the intN_t signed datatypes, changed the datatype u_intN_t to uintN_t (no underscore after the "u"), and removed the datatype u_intNm_t, as per Draft 6.6 of Posix.1g.
  • Name space issues for structure and constant names in Section 2: Many of the structure member names and constant names were changed so that the prefixes are the same. The following prefixes are used for structure members: "ip6_", "icmp6_", and "nd_". All constants have the prefixes "ICMP6_" and "ND_".
  • New definitions: Section 2.1.2: contains definitions for the IPv6 extension headers, other than AH and ESP. Section 2.2.2: contains additional structures and constants for the neighbor discovery option header and redirected header.
  • Section 2.2.2: the enum for the neighbor discovery option field was changed to be a set of #define constants.
  • Changed the word "function" to "macro" for references to all the uppercase names in Sections 2.3 (IN6_ARE_ADDR_EQUAL), 3.2 (ICMPV6_FILTER_xxx), and 4.3 (CMSG_xxx).
  • Added more protocols to the /etc/protocols file (Section 2.4) and changed the name of "icmpv6" to "ipv6-icmp".
  • Section 3: Made it more explicit that an application cannot read or write entire IPv6 packets, that all extension headers are passed as ancillary data. Added a sentence that the kernel fragments packets written to an IPv6 raw socket when necessary. Added a note that IPPROTO_RAW raw IPv6 sockets are not special.
  • Section 3.1: Explicitly stated that the checksum option applies to both outgoing packets and received packets.
  • Section 3.2: Changed the array name within the icmp6_filter structure from "data" to "icmp6_filt". Changed the prefix for the filter macros from "ICMPV6_" to "ICMP6_", for consistency with the names in Section 2.2. Changed the example from a ping program to a program that wants to receive only router advertisements.
  • Section 4.1: Changed msg_namelen and msg_controllen from size_t to the Posix.1g socklen_t datatype. Updated the Note that follows.
  • Section 4.2: Changed cmsg_len from size_t to the Posix.1g socklen_t datatype. Updated the Note that follows.
  • Section 4.4: Added a Note that the second and third arguments to getsockopt() and setsockopt() are intentionally the same as the cmsg_level and cmsg_type members.
  • Section 4.5: Reorganized the section into a description of the option, followed by the TCP semantics, and the UDP and raw socket semantics. Added a sentence on how to clear all the sticky options. Added a note that TCP need not save the options from the most recently received segment until the application says to do so. Added the statement that ancillary data is never passed with sendmsg() or recvmsg() on a TCP socket. Simplified the interaction of the sticky options with ancillary data for UDP or raw IP: none of the sticky options are sent if ancillary data is specified.
  • Final paragraph of Section 5.1: ipi6_index should be ipi6_ifindex.
  • Section 5.4: Added a note on the term "privileged".
  • Section 5.5: Noted that the errors listed are examples, and the actual errors depend on the implementation.
  • Removed Section 6 ("Flow Labels") as the consensus is that it is premature to try and specify an API for this feature. Access to the flow label field in the IPv6 header is still provided through the sin6_flowinfo member of the IPv6 socket address structure in [RFC-2133]. Added a subsection to Section 13 that this is a future item.

All remaining changes are identified by their section number in the previous draft. With the removal of Section 6, the section numbers are decremented by one.

  • Section 7.3.7: the calls to malloc() in all three examples should be calls to inet6_option_space() instead. The two calls to inet6_option_append() in the third example should be calls to inet6_option_alloc(). The two calls to CMSG_SPACE() in the first and third examples should be calls to CMSG_LEN(). The second call to CMSG_SPACE() in the second example should be a call to CMSG_LEN().
  • Section 7.3.7: All the opt_X_ and opt_Y_ structure member names were changed to be ip6_X_opt_ and ip6_Y_opt_. The two structure names ipv6_opt_X and ipv6_opt_Y were changed to ip6_X_opt and ip6_Y_opt. The constants beginning with IPV6_OPT_X_ and IPV6_OPT_Y_ were changed to begin with IP6_X_OPT_ and IP6_Y_OPT_.
  • Use the term "Routing header" throughout the draft, instead of "source routing". Changed the names of the eight inet6_srcrt_XXX() functions in Section 9 to inet6_rthdr_XXX(). Changed the name of the socket option from IPV6_SRCRT to IPV6_RTHDR, and the names of the three IPV6_SRCRT_xxx constants in Section 9 to IPV6_RTHDR_xxx.
  • Added a paragraph to Section 9 on how to receive and send a Routing header.
  • Changed inet6_rthdr_add() and inet6_rthdr_reverse() so that they return -1 upon an error, instead of an Exxx errno value.
  • In the description of inet6_rthdr_space() in Section 9.1, added the qualifier "For an IPv6 Type 0 Routing header" to the restriction of between 1 and 23 segments.
  • Refer to final function argument in Sections 9.7 and 9.8 as index, not offset.
  • Updated Section 14 with new names from Section 2.
  • Changed the References from "[n]" to "[RFC-abcd]".

Changes from the February 1997 Edition (-01 draft) ๐Ÿ’œ

  • Changed the name of the ip6hdr structure to ip6_hdr (Section 2.1) for consistency with the icmp6hdr structure. Also changed the name of the ip6hdrctl structure contained within the ip6_hdr structure to ip6_hdrctl (Section 2.1). Finally, changed the name of the icmp6hdr structure to icmp6_hdr (Section 2.2). All other occurrences of this structure name, within the Neighbor Discovery structures in Section 2.2.1, already contained the underscore.
  • The "struct nd_router_solicit" and "struct nd_router_advert" should both begin with "nd6_". (Section 2.2.2).
  • Changed the name of in6_are_addr_equal to IN6_ARE_ADDR_EQUAL (Section 2.3) for consistency with basic API address testing functions. The header defining this macro is <netinet/in.h>.
  • getprotobyname("ipv6") now returns 41, not 0 (Section 2.4).
  • The first occurrence of "struct icmpv6_filter" in Section 3.2 should be "struct icmp6_filter".
  • Changed the name of the CMSG_LENGTH() macro to CMSG_LEN() (Section 4.3.5), since LEN is used throughout the <netinet/*.h> headers.
  • Corrected the argument name for the sample implementations of the CMSG_SPACE() and CMSG_LEN() macros to be "length" (Sections 4.3.4 and 4.3.5).
  • Corrected the socket option mentioned in Section 5.1 to specify the interface for multicasting from IPV6_ADD_MEMBERSHIP to IPV6_MULTICAST_IF.
  • There were numerous errors in the previous draft that specified <netinet/ip6.h> that should have been <netinet/in.h>. These have all been corrected and the locations of all definitions is now summarized in the new Section 14 ("Summary of New Definitions").

Changes from the October 1996 Edition (-00 draft) ๐ŸŒˆ

  • Numerous rationale added using the format (Note: ...).
  • Added note that not all errors may be defined.
  • Added note about ICMPv4, IGMPv4, and ARPv4 terminology.
  • Changed the name of <netinet/ip6_icmp.h> to <netinet/icmp6.h>.
  • Changed some names in Section 2.2.1: ICMPV6_PKT_TOOBIG to ICMPV6_PACKET_TOOBIG, ICMPV6_TIME_EXCEED to ICMPV6_TIME_EXCEEDED, ICMPV6_ECHORQST to ICMPV6_ECHOREQUEST, ICMPV6_ECHORPLY to ICMPV6_ECHOREPLY, ICMPV6_PARAMPROB_HDR to ICMPV6_PARAMPROB_HEADER, ICMPV6_PARAMPROB_NXT_HDR to ICMPV6_PARAMPROB_NEXTHEADER, and ICMPV6_PARAMPROB_OPTS to ICMPV6_PARAMPROB_OPTION. ๐Ÿ’…
  • Prepend the prefix "icmp6_" to the three members of the icmp6_dataun union of the icmp6hdr structure (Section 2.2). โœจ
  • Moved the neighbor discovery definitions into the <netinet/icmp6.h> header, instead of being in their own header (Section 2.2.1). ๐ŸŽ€
  • Changed Section 2.3 ("Address Testing"). The basic macros are now in the basic API. ๐ŸŒธ
  • Added the new Section 2.4 on "Protocols File". ๐Ÿฆ‹
  • Added note to raw sockets description that something like BPF or DLPI must be used to read or write entire IPv6 packets. ๐Ÿ”ฅ
  • Corrected example of IPV6_CHECKSUM socket option (Section 3.1). Also defined value of -1 to disable. ๐Ÿ’–
  • Noted that <netinet/icmp6.h> defines all the ICMPv6 filtering constants, macros, and structures (Section 3.2). ๐Ÿงš
  • Added note on magic number 10240 for amount of ancillary data (Section 4.1). ๐Ÿ™ˆ
  • Added possible padding to picture of ancillary data (Section 4.2). ๐ŸŒˆ
  • Defined <sys/socket.h> header for CMSG_xxx() functions (Section 4.2). ๐Ÿ‘‘
  • Note that the data returned by getsockopt(IPV6_PKTOPTIONS) for a TCP socket is just from the optional headers, if present, of the most recently received segment. Also note that control information is never returned by recvmsg() for a TCP socket. ๐Ÿ˜ญ
  • Changed header for struct in6_pktinfo from <netinet.in.h> to <netinet/ip6.h> (Section 5). ๐Ÿ“
  • Removed the old Sections 5.1 and 5.2, because the interface identification functions went into the basic API. ๐Ÿซถ
  • Redid Section 5 to support the hop limit field. โญ
  • New Section 5.4 ("Next Hop Address"). ๐Ÿ’œ
  • New Section 6 ("Flow Labels"). ๐Ÿฆ‹
  • Changed all of Sections 7 and 8 dealing with Hop-by-Hop and Destination options. We now define a set of inet6_option_XXX() functions. ๐Ÿ’…
  • Changed header for IPV6_SRCRT_xxx constants from <netinet.in.h> to <netinet/ip6.h> (Section 9). ๐ŸŒธ
  • Add inet6_rthdr_lasthop() function, and fix errors in description of Routing header (Section 9). ๐Ÿ”ฅ
  • Reworded some of the Routing header descriptions to conform to the terminology in [RFC-1883]. โœจ
  • Added the example from [RFC-1883] for the Routing header (Section 9.9). ๐ŸŽ€
  • Expanded the example in Section 10 to show multiple options per ancillary data object, and to show the receiver's ancillary data objects. ๐Ÿงš
  • New Section 11 ("IPv6-Specific Options with IPv4-Mapped IPv6 Addresses"). ๐ŸŒˆ
  • New Section 12 ("rresvport_af"). ๐Ÿ‘‘
  • Redid old Section 10 ("Additional Items") into new Section 13 ("Future Items"). ๐Ÿ’–

16. References ๐Ÿ“š

  • [RFC-1883] Deering, S., and R. Hinden, "Internet Protocol, Version 6 (IPv6), Specification", RFC 1883, December 1995.
  • [RFC-2133] Gilligan, R., Thomson, S., Bound, J., and W. Stevens, "Basic Socket Interface Extensions for IPv6", RFC 2133, April 1997.
  • [RFC-1981] McCann, J., Deering, S., and J. Mogul, "Path MTU Discovery for IP version 6", RFC 1981, August 1996.
  • [RFC-1970] Narten, T., Nordmark, E., and W. Simpson, "Neighbor Discovery for IP Version 6 (IPv6)", RFC 1970, August 1996.

17. Acknowledgments ๐Ÿซถ

Matt Thomas and Jim Bound have been working on the technical details in this draft for over a year. Keith Sklower is the original implementor of ancillary data in the BSD networking code. Craig Metz provided lots of feedback, suggestions, and comments based on his implementing many of these features as the document was being written.

The following provided comments on earlier drafts: Pascal Anelli, Hamid Asayesh, Ran Atkinson, Karl Auerbach, Hamid Asayesh, Matt Crawford, Sam T. Denton, Richard Draves, Francis Dupont, Bob Gilligan, Tim Hartrick, Masaki Hirabaru, Yoshinobu Inoue, Mukesh Kacker, A. N. Kuznetsov, Pedro Marques, Jack McCann, der Mouse, John Moy, Thomas Narten, Erik Nordmark, Steve Parker, Charles Perkins, Tom Pusateri, Pedro Roque, Sameer Shah, Peter Sjodin, Stephen P. Spackman, Jinmei Tatuya, Karen Tracey, Quaizar Vohra, Carl Williams, Steve Wise, and Kazu Yamamoto.

18. Authors' Addresses ๐Ÿฆ‹

W. Richard Stevens

1202 E. Paseo del Zorro
Tucson, AZ  85718

EMail: rstevens@kohala.com

Matt Thomas

AltaVista Internet Software
LJO2-1/J8
30 Porter Rd
Littleton, MA  01460
EMail: matt.thomas@altavista-software.com

Copyright (C) The Internet Society (1998). All Rights Reserved.

This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English.

The limited permissions granted above are perpetual and will not be revoked by the Internet Society or its successors or assigns.

This document and the information contained herein is provided on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

๐Ÿ“ฆ where this page comes from