Introduction

In this tutorial, we discuss and experiment a few IPv6 related topics.

IPv6 Support on Linux

To get a sense of IPv6 support on Linux systems, we can use `sysctl, e.g.,

brooklyn@flatbush:~$ sudo sysctl net.ipv6.conf
brooklyn@flatbush:~$ sudo sysctl net.ipv6.conf | grep disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.enp0s10.disable_ipv6 = 0
net.ipv6.conf.enp0s16.disable_ipv6 = 0
net.ipv6.conf.enp0s17.disable_ipv6 = 0
net.ipv6.conf.enp0s3.disable_ipv6 = 0
net.ipv6.conf.enp0s8.disable_ipv6 = 0
net.ipv6.conf.enp0s9.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0

IPv6 Forwarding

For IPv6, we can selectively turn on IP forwarding for network interfaces. In the following example, we turn IPv6 forwarding on for all network interfaces on virtual machine flatbush.

brooklyn@flatbush:~$ sudo sysctl -w net.ipv6.conf.all.forwarding=1

You can add this option to /etc/sysctl.conf, which allows this to survive a reboot.

IPv6 Auto Configuration

IPv6 supports stateless configuraion, i.e., IPv6 can assign addresses to a NIC without external support like a DHCP server.

EUI-64 Interface ID

IPv6 can assign an address to a NIC based on its EUI-64 interface ID that is globally unique. The following article explains it well.

Addresses assigned following this scheme have a privacy concern since EUI-64 is globally unique, from which we can identify and trace a user. This concern materializes when the host is mobile, i.e., a laptop computer or a phone since a laptop computer or a phone likely joins networks out of your administrative domain. IPv6 supports to assign a temporary address and exposes only the temporary address other than the IPv6 EUI-64 derived address.

In Linux we can check whether the temporary addressing is turned on, e.g.,

brooklyn@eastny:~$ sudo sysctl net.ipv6.conf | grep use_tempaddr                [sudo] password for brooklyn:
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0
net.ipv6.conf.enp0s10.use_tempaddr = 0
net.ipv6.conf.enp0s16.use_tempaddr = 0
net.ipv6.conf.enp0s3.use_tempaddr = 0
net.ipv6.conf.enp0s8.use_tempaddr = 0
net.ipv6.conf.enp0s9.use_tempaddr = 0
net.ipv6.conf.lo.use_tempaddr = -1
brooklyn@eastny:~$

To turn it on for a NIC, you use also sysctl, e.g.,

sudo sysctl -w net.ipv6.conf.enp0s10.use_tempaddr=2

You can add this option to /etc/sysctl.conf, which allows this to survive a reboot.

IPv6 Neighbor Discovery

IPv6 packs a few services that IPv4 provides via a few protocols in a protocol called the IPv6 Neighbor Discovery Protocol. One of the service it provides is the address resolution. To understand how IPv6 does address resolution, we do these experiments.

Solicited-Node Multicast Addresses

In ScaPy, do these,

>>> inet_ntop(socket.AF_INET6, in6_getnsma(inet_pton(socket.AF_INET6, 'fc00::1:1
...::2')))
>>> help(neighsol)
>>> packet = neighsol('fc00::1:1:2', 'fc00::1:1:1', 'enp0s9', timeout=5)
>>> pkt.summary()

To which address was this packet sent? How about the following?

>>> src6 = 'fc00::1:1:1'
>>> nexthop6 = 'fc00::1:1:2'
>>> iface = 'enp0s9'
>>> nsma = in6_getnsma(inet_pton(socket.AF_INET6, nexthop6))
>>>  d = inet_ntop(socket.AF_INET6,nsma)
>>> print(d)
ff02::1:ff01:2
>>> dm = in6_getnsmac(nsma)
>>> print(dm)
33:33:ff:01:00:02
>>> packet = Ether(dst=dm) / IPv6(dst=d, src=src6, hlim=255)
>>> packet = packet / ICMPv6ND_NS(tgt=nexthop6)
>>> packet = pacekt / ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface))
>>> packet = packet / ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface))
>>> packet.display()
...
...
...
>>> res = srp1(packet, type=ETH_P_IPV6, iface=iface, timeout=1, verbose=0)
>>> print(res)
b"\x08\x00'\x08\r\xa1\x08\x00'\xcbg\x1d\x86\xdd`\x00\x00\x00\x00 :\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x02\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x01\x88\x00\n\xad\xe0\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x02\x02\x01\x08\x00'\xcbg\x1d"
>>> res.summary()
'Ether / IPv6 / ICMPv6ND_NA / ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address 08:00:27:cb:67:1d'

To capture the packets originated in the above, we do,

packets = sniff(prn=lambda p: p.summary(), filter='icmp6')

IPv6 Multicasting

IPv6 does not have broadcast address, and rely on multicasting. But why?

IPv6 Fragmentation and Resassembly

IPv6 only permits fragmentation at the source nodes, but by any routers along the path. Can you carry out an experiment and capture IPv6 fragments?

Appendix I provides some resources to aid this experiment.

Exercises and Exploration

Complete the experiments in the above. Have fun.

Appendix I. Sending Larger IPv6 Packet

ScaPy as of the latest version of 2.4.4 does not appear to be able to send larger IPv6 packets. The following two programs sends large IPv6 packets, one runs directly on top of the IPv6 protocol (ipv6send.py), and the other is on the UDP protocol that in turns runs on the IPv6 protocol (upd6send.py).

ipv6send.py

import socket
import string
import sys

def print_bytes(octets):
    for b in octets:
        print(' ', hex(b), end = '')
    print()

def prepare_data(size):
    nb = 1000
    data = ""
    for i in range(int(size/nb)):
        data = data + string.ascii_uppercase[i]*nb
    rest = size - int(size/nb)*nb
    data = data + string.ascii_uppercase[int(size/nb)]*rest

    return data

def build_ipv6_header(src, dest, size):
    # first 32 bits 
    #   version (4 bits), traffic class (8 bits), flow label (20 bits)
    #   version has to be 6, choose the rest two fields 0
    ver = 6 << 28 # 32 - 4 = 28
    tc  = 0 << 20 # 32 - 4 - 8 = 20
    fl  = 0
    row1 = (ver | tc | fl).to_bytes(4, byteorder='big')
    #print_bytes(row1)

    # second 32 bits
    #   payload length (16 bits), next header (8 bits), hop limits (8 bits)
    le = size << 16    # 32 - 16 = 16
    nh = 0x90 << 8 # 32 - 16 - 8 = 8
    hl = 64
    row2 = (le | nh | hl).to_bytes(4, byteorder='big')
    #print_bytes(row2)
    
    row3 = socket.inet_pton(socket.AF_INET6, src)
    #print_bytes(row3)

    row4 = socket.inet_pton(socket.AF_INET6, dest)
    #print_bytes(row4)

    ipv6hdr = b"".join([row1, row2, row3, row4])
    #print_bytes(ipv4hdr)

    return ipv6hdr;
    

def transmit(src, dest, size):
    data = prepare_data(size)
    sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_RAW\
        , proto=socket.IPPROTO_IPV6)

    ipv6hdr = build_ipv6_header(src, dest, size)
    
    nb = sock.sendto(b"".join([ipv6hdr, data.encode()]), (dest, 0))
    print("Sent an IPv6 packet of " + str(nb) + " bytes")
    
    sock.close()
    
def usage(prog):
    print("Usage: " + prog + " source desintation payload_length")

def main(argv):
    if len(sys.argv) < 4:
        usage(argv[0])
        sys.exit(1)
    transmit(argv[1], argv[2], int(argv[3]))

if __name__ == "__main__":
    main(sys.argv)

udp6send.py

import socket
import string
import sys

def prepare_data(size):
    nb = 1000
    data = ""
    for i in range(int(size/nb)):
        data = data + string.ascii_uppercase[i]*nb
    rest = size - int(size/nb)*nb
    data = data + string.ascii_uppercase[int(size/nb)]*rest

    return data

def transmit(host, port, size):
    data = prepare_data(size)
    sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM\
        , proto=socket.IPPROTO_UDP, fileno=None)

    sock.sendto(data.encode(), (host, port))
    
    sock.close()
    
def usage(prog):
    print("Usage: " + prog + " host port payload_length")

def main(argv):
    if len(sys.argv) < 4:
        usage(argv[0])
        sys.exit(1)
    transmit(argv[1], int(argv[2]), int(argv[3]))

if __name__ == "__main__":
    main(sys.argv)