Messaging over IPv6 Destination Options by Thomas Graf July 6th, 2003 The initial idea was to make something very stupid using IPv6. My first thought was IRC over IPv6 extension headers using a special Next Header value. I had to discard the idea because RFC 1883 says: "If, as a result of processing a header, a node is required to proceed to the next header but the Next Header value in the current header is unrecognized by the node, it should discard the packet and send an ICMP Parameter Problem message to the source of the packet, ..." An existing extension header must do it then, I thought. I found the Destination Options header having no use yet. So make it have one... THEORY ====== +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Header | Hdr Ext Len | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | . . . Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ That is how an IPv6 extension header looks like. The first 16-bits of each extension header are reserved for the Next Header type that follows, and 8-bits for the header length. The options data can have variable length but must be TLV encoded and aligned to a multiple of 8 octets. TLV encoding: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - | Option Type | Opt Data Len | Option Data +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - The highest-order 2 bits of the Option Type specifiy the action that must be taken if the option type is not recognized. 00 - skip over this option and continue processing the header. 01 - discard the packet. So, all we have to do is: generate a destination options extension header, TLV encode our message, set the highest-order 2 bits of the option type to 00 and choose an option type value not taken yet. THE API ======= The API used below is fully documented in RFC 2292 - Advanced Sockets API for IPv6. I tested the code using the implementation of the USAGI project. Note: The code was shortened and the error handling was omitted intentionally for understanding and simplicity reasons. Prepare the socket ------------------ sock = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6); Create an IPv6 raw-socket for ICMPv6 traffic. struct icmp6_filter filter; int on = 1; ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) setsockopt(fd, IPPROTO_IPV6, IPV6_DSTOPTS, &on, sizeof(on)) In order to receive messages from other users the socket has to be told to hand over the destination options as cmsghdr. Further, to save ourself a bit work we filter the traffic to only pass through ICMPv6 Echo-Reply packets. Building the Destination Options header --------------------------------------- static struct cmsghdr * make_exthdr(const char *msg, int *mclen) { struct cmsghdr *chdr; int len, mlen = strlen(msg); len = inet6_option_space(mlen); inet6_option_space returns the number of bytes required to hold an option when it is stored as ancillary data, including the cmsghdr structure at the beginning, and any padding at the end (to make its size a multiple of 8 bytes). inet6_option_init((void *) chdr, &chdr, IPV6_DSTOPTS); This initializes the cmsghdr structure, sets the cmsg_level to SOL_IPV6, the cmsg_type to IPV6_DSTOPTS and sets cmsg_len to the minimal required padding. char buf[mlen + 2]; buf[0] = 0x00 | 0x17; /* skip this option | type == 0x17 */ buf[1] = mlen; memcpy(&buf[2], msg, mlen); inet6_option_append(chdr, buf, 1, 0); Creates a buffer (C99), sets the option type to 0x17, and copies the message into the buffer. The buffer is then appended to the cmsghdr structure initialized before. inet6_option_append will recalculate cmsg_len itself. *mclen = chdr->cmsg_len; return chdr; } Assign the calculated cmsg_len to the int pointer and return our cmsghdr holding the destination options exension header. Building the ICMPv6 Echo-Reply and send it out ---------------------------------------------- struct iovec iov[1]; struct icmp6_hdr *hdr = calloc(1, sizeof(struct icmp6_hdr) + 42); hdr->icmp6_type = 129; iov[0].iov_base = hdr; iov[0].iov_len = sizeof(struct icmp6_hdr) + 42; Creates a buffer big enough to hold the ICMPv6 header and 42 bytes of payload, zeros out the buffer, sets the ICMP type to 129 (Echo-Reply), and assigns the buffer to an iovec structure. struct msghdr mhdr; memset((void *) &mhdr, 0, sizeof(mhdr)); mhdr.msg_name = (caddr_t) addr; mhdr.msg_namelen = sizeof(struct sockaddr_in6); mhdr.msg_iov = iov; mhdr.msg_iovlen = 1; Initializes a msghdr structure and assigns the destination address and the iovec structure to it. Note: The port-field (sin6_port) in the sockaddr_in6 structure is used as Next Header value and thus must be set to 58 (ICMPv6). mhdr.msg_control = (caddr_t) make_exthdr(msg, &mhdr.msg_controllen); Calls the function we defined before, assigns the newly created cmsghdr containing the destination option to the msg_control field which is used to transmit ancillary data, and sets msg_controllen to cmsg_len. sendmsg(sock, &mhdr, 0); This finally sends the packet out over the socket. Receiving messages ------------------ Use select(2) or poll(2) to watch the socket and use the following code when there is data to read: struct msghdr mhdr = {0}; struct cmsghdr *cmsg; char buf[CMSG_SPACE(1024)]; mhdr.msg_control = buf; mhdr.msg_controllen = sizeof(buf); Allocates a buffer for the incoming ancillary data objects and assigns it to the msghdr structure. recvmsg(sock, &mhdr, 0); if (mhdr.msg_controllen > 0) cmsg = CMSG_FIRSTHDR(&mhdr); Receives the message and checks if there are ancillary data included. CMSG_FIRSTHDR returns the first cmsghdr structure. JOE 6 PACK ========== Joe 6 Pack (j6p) is my attempt to implement this idea. The full source can be found at http://trash.net/~reeler/j6p.tar.bz2. You need the ip6_exthdrs.diff patch to make it work on linux 2.5. The patch is against 2.5.74 in the USAGI tree (Sun, 06 Jul 2003 03:04:00 +0200). Example Session --------------- Alice and Bob are living on the same network and they want to have a chat together so they decide to run joe 6 pack in multicast mode. Alice's screen: # j6p -M -n alice Multicast Mode: ff02::1 hi bob, how are you doing? hi bob, how are you doing? hi alice # Bob's screen: # j6p -M -n bob Multicast Mode: ff02::1 hi bob, how are you doing? hi alice hi alice # Traffic tcpdump dumps the packet of the first message as follows: fe80::240:63ff:fec0:9e8d > ip6-allnodes: DSTOPT (opt_type 0x17: len=35) \ (pad1)icmp6: echo reply [hlim 1] (len 90) Let's take a closer look at the payload of the IPv6 packet: [RAW] Size: 90 bytes Data: [RAW] 3a 04 17 23 3c 61 6c 69 63 65 3e 20 68 69 20 62 6f 62 :..# hi bob [RAW] 2c 20 68 6f 77 20 61 72 65 20 79 6f 75 20 64 6f 69 6e , how are you doin [RAW] 67 3f 0a 00 81 00 7d 80 00 00 00 00 00 00 00 00 00 00 g?....}........... [RAW] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .................. [RAW] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .................. First octect specifies the Next Header Value, ICMPv6 (0x3a = 58) in this case. Second octect specifies the length of the extension header. Next octect is the destination option type followed by the option length. Octets 5 to 39 contain the payload including the message. 40th octect is padding for alignment reasons. Octets 41 to 90 contain the ICMPv6 Echo-Reply part. References - RFC 1883 - Internet Protocol, Version 6 (IPv6) ftp://ftp.rfc-editor.org/in-notes/rfc1883.txt - RFC 2292 - Advanced Sockets API for IPv6 ftp://ftp.rfc-editor.org/in-notes/rfc2292.txt - USAGI Project http://www.linux-ipv6.org/ - Joe 6 Pack http://trash.net/~reeler/j6p.tar.bz2