Document ICMPv6 and TUN/TAP

This commit is contained in:
Neil Alexander 2018-06-12 22:45:53 +01:00
parent ad6ea59049
commit 8e2c2aa977
No known key found for this signature in database
GPG Key ID: A02A2019A2BB0944
10 changed files with 90 additions and 4 deletions

View File

@ -1,8 +1,13 @@
package yggdrasil package yggdrasil
// The NDP functions are needed when you are running with a // The ICMPv6 module implements functions to easily create ICMPv6
// TAP adapter - as the operating system expects neighbor solicitations // packets. These functions, when mixed with the built-in Go IPv6
// for on-link traffic, this goroutine provides them // and ICMP libraries, can be used to send control messages back
// to the host. Examples include:
// - NDP messages, when running in TAP mode
// - Packet Too Big messages, when packets exceed the session MTU
// - Destination Unreachable messages, when a session prohibits
// incoming traffic
import "net" import "net"
import "golang.org/x/net/ipv6" import "golang.org/x/net/ipv6"
@ -39,6 +44,9 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
return b, nil return b, nil
} }
// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
// our MAC address. ICMPv6 messages will always appear to originate from these
// addresses.
func (i *icmpv6) init(t *tunDevice) { func (i *icmpv6) init(t *tunDevice) {
i.tun = t i.tun = t
@ -50,6 +58,10 @@ func (i *icmpv6) init(t *tunDevice) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
} }
// Parses an incoming ICMPv6 packet. The packet provided may be either an
// ethernet frame containing an IP packet, or the IP packet alone. This is
// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
// TAP (layer 2) mode.
func (i *icmpv6) parse_packet(datain []byte) { func (i *icmpv6) parse_packet(datain []byte) {
var response []byte var response []byte
var err error var err error
@ -69,6 +81,10 @@ func (i *icmpv6) parse_packet(datain []byte) {
i.tun.iface.Write(response) i.tun.iface.Write(response)
} }
// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
// the IP packet to the parse_packet_tun function for further processing.
// A response buffer is also created for the response message, also complete
// with ethernet headers.
func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
// Store the peer MAC address // Store the peer MAC address
copy(i.peermac[:6], datain[6:12]) copy(i.peermac[:6], datain[6:12])
@ -97,6 +113,10 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
return dataout, nil return dataout, nil
} }
// Unwraps the IP headers of an incoming IPv6 packet and performs various
// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
// ICMPv6 message match a known expected type. The relevant handler function
// is then called and a response packet may be returned.
func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) {
// Parse the IPv6 packet headers // Parse the IPv6 packet headers
ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
@ -149,6 +169,9 @@ func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) {
return nil, errors.New("ICMPv6 type not matched") return nil, errors.New("ICMPv6 type not matched")
} }
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with ethernet and IP headers, which can be written
// directly to a TAP adapter.
func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to create_icmpv6_tun // Pass through to create_icmpv6_tun
ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody) ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody)
@ -169,6 +192,10 @@ func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mt
return dataout, nil return dataout, nil
} }
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with IP headers only, which can be written directly to
// a TUN adapter, or called directly by the create_icmpv6_tap function when
// generating a message for TAP adapters.
func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Create the ICMPv6 message // Create the ICMPv6 message
icmpMessage := icmp.Message{ icmpMessage := icmp.Message{
@ -208,6 +235,11 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType,
return responsePacket, nil return responsePacket, nil
} }
// Generates a response to an NDP discovery packet. This is effectively called
// when the host operating system generates an NDP request for any address in
// the fd00::/8 range, so that the operating system knows to route that traffic
// to the Yggdrasil TAP adapter.
// TODO: Make this respect the value of address_prefix in address.go
func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) { func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8 // Ignore NDP requests for anything outside of fd00::/8
if in[8] != 0xFD { if in[8] != 0xFD {

View File

@ -8,6 +8,7 @@ import "github.com/yggdrasil-network/water"
const tun_IPv6_HEADER_LENGTH = 40 const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14 const tun_ETHER_HEADER_LENGTH = 14
// Represents a running TUN/TAP interface.
type tunDevice struct { type tunDevice struct {
core *Core core *Core
icmpv6 icmpv6 icmpv6 icmpv6
@ -17,6 +18,9 @@ type tunDevice struct {
iface *water.Interface iface *water.Interface
} }
// Defines which parameters are expected by default for a TUN/TAP adapter on a
// specific platform. These values are populated in the relevant tun_*.go for
// the platform being targeted. They must be set.
type tunDefaultParameters struct { type tunDefaultParameters struct {
maximumIfMTU int maximumIfMTU int
defaultIfMTU int defaultIfMTU int
@ -24,6 +28,8 @@ type tunDefaultParameters struct {
defaultIfTAPMode bool defaultIfTAPMode bool
} }
// Gets the maximum supported MTU for the platform based on the defaults in
// getDefaults().
func getSupportedMTU(mtu int) int { func getSupportedMTU(mtu int) int {
if mtu > getDefaults().maximumIfMTU { if mtu > getDefaults().maximumIfMTU {
return getDefaults().maximumIfMTU return getDefaults().maximumIfMTU
@ -31,11 +37,14 @@ func getSupportedMTU(mtu int) int {
return mtu return mtu
} }
// Initialises the TUN/TAP adapter.
func (tun *tunDevice) init(core *Core) { func (tun *tunDevice) init(core *Core) {
tun.core = core tun.core = core
tun.icmpv6.init(tun) tun.icmpv6.init(tun)
} }
// Starts the setup process for the TUN/TAP adapter, and if successful, starts
// the read/write goroutines to handle packets on that interface.
func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) error {
if ifname == "none" { if ifname == "none" {
return nil return nil
@ -48,6 +57,9 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int)
return nil return nil
} }
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
// mode then additional ethernet encapsulation is added for the benefit of the
// host operating system.
func (tun *tunDevice) write() error { func (tun *tunDevice) write() error {
for { for {
data := <-tun.recv data := <-tun.recv
@ -75,6 +87,10 @@ func (tun *tunDevice) write() error {
} }
} }
// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter
// is running in TAP mode then the ethernet headers will automatically be
// processed and stripped if necessary. If an ICMPv6 packet is found, then
// the relevant helper functions in icmpv6.go are called.
func (tun *tunDevice) read() error { func (tun *tunDevice) read() error {
mtu := tun.mtu mtu := tun.mtu
if tun.iface.IsTAP() { if tun.iface.IsTAP() {
@ -109,6 +125,9 @@ func (tun *tunDevice) read() error {
} }
} }
// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
// process stops. Typically this operation will happen quickly, but on macOS
// it can block until a read operation is completed.
func (tun *tunDevice) close() error { func (tun *tunDevice) close() error {
if tun.iface == nil { if tun.iface == nil {
return nil return nil

View File

@ -70,6 +70,11 @@ type in6_ifreq_lifetime struct {
ifru_addrlifetime in6_addrlifetime ifru_addrlifetime in6_addrlifetime
} }
// Sets the IPv6 address of the utun adapter. On all BSD platforms (FreeBSD,
// OpenBSD, NetBSD) an attempt is made to set the adapter properties by using
// a system socket and making syscalls to the kernel. This is not refined though
// and often doesn't work (if at all), therefore if a call fails, it resorts
// to calling "ifconfig" instead.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config var config water.Config
if ifname[:4] == "auto" { if ifname[:4] == "auto" {

View File

@ -10,6 +10,8 @@ import "golang.org/x/sys/unix"
import water "github.com/yggdrasil-network/water" import water "github.com/yggdrasil-network/water"
// Sane defaults for the Darwin/macOS platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 65535, maximumIfMTU: 65535,
@ -19,6 +21,7 @@ func getDefaults() tunDefaultParameters {
} }
} }
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode { if iftapmode {
tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN") tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN")
@ -65,6 +68,8 @@ type ifreq struct {
ifru_mtu uint32 ifru_mtu uint32
} }
// Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using
// a system socket and making direct syscalls to the kernel.
func (tun *tunDevice) setupAddress(addr string) error { func (tun *tunDevice) setupAddress(addr string) error {
var fd int var fd int
var err error var err error

View File

@ -1,5 +1,7 @@
package yggdrasil package yggdrasil
// Sane defaults for the FreeBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 32767, maximumIfMTU: 32767,

View File

@ -1,7 +1,6 @@
package yggdrasil package yggdrasil
// The linux platform specific tun parts // The linux platform specific tun parts
// It depends on iproute2 being installed to set things on the tun device
import "errors" import "errors"
import "fmt" import "fmt"
@ -11,6 +10,8 @@ import water "github.com/yggdrasil-network/water"
import "github.com/docker/libcontainer/netlink" import "github.com/docker/libcontainer/netlink"
// Sane defaults for the Linux platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 65535, maximumIfMTU: 65535,
@ -20,6 +21,7 @@ func getDefaults() tunDefaultParameters {
} }
} }
// Configures the TAP adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config var config water.Config
if iftapmode { if iftapmode {
@ -39,6 +41,10 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
return tun.setupAddress(addr) return tun.setupAddress(addr)
} }
// Configures the TAP adapter with the correct IPv6 address and MTU. Netlink
// is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
// to exist on the system, but this will fail if Netlink is not present in the
// kernel (it nearly always is).
func (tun *tunDevice) setupAddress(addr string) error { func (tun *tunDevice) setupAddress(addr string) error {
// Set address // Set address
var netIF *net.Interface var netIF *net.Interface

View File

@ -1,5 +1,7 @@
package yggdrasil package yggdrasil
// Sane defaults for the NetBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 9000, maximumIfMTU: 9000,

View File

@ -1,5 +1,7 @@
package yggdrasil package yggdrasil
// Sane defaults for the OpenBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 16384, maximumIfMTU: 16384,

View File

@ -7,6 +7,8 @@ import water "github.com/yggdrasil-network/water"
// This is to catch unsupported platforms // This is to catch unsupported platforms
// If your platform supports tun devices, you could try configuring it manually // If your platform supports tun devices, you could try configuring it manually
// These are sane defaults for any platform that has not been matched by one of
// the other tun_*.go files.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 65535, maximumIfMTU: 65535,
@ -16,6 +18,8 @@ func getDefaults() tunDefaultParameters {
} }
} }
// Creates the TUN/TAP adapter, if supported by the Water library. Note that
// no guarantees are made at this point on an unsupported platform.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config var config water.Config
if iftapmode { if iftapmode {
@ -32,6 +36,8 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
return tun.setupAddress(addr) return tun.setupAddress(addr)
} }
// We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further.
func (tun *tunDevice) setupAddress(addr string) error { func (tun *tunDevice) setupAddress(addr string) error {
tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil return nil

View File

@ -7,6 +7,8 @@ import "fmt"
// This is to catch Windows platforms // This is to catch Windows platforms
// Sane defaults for the Windows platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters { func getDefaults() tunDefaultParameters {
return tunDefaultParameters{ return tunDefaultParameters{
maximumIfMTU: 65535, maximumIfMTU: 65535,
@ -16,6 +18,9 @@ func getDefaults() tunDefaultParameters {
} }
} }
// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows
// we don't make use of a direct operating system API to do this - we instead
// delegate the hard work to "netsh".
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode { if !iftapmode {
tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP") tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP")
@ -63,6 +68,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
return tun.setupAddress(addr) return tun.setupAddress(addr)
} }
// Sets the MTU of the TAP adapter.
func (tun *tunDevice) setupMTU(mtu int) error { func (tun *tunDevice) setupMTU(mtu int) error {
// Set MTU // Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
@ -79,6 +85,7 @@ func (tun *tunDevice) setupMTU(mtu int) error {
return nil return nil
} }
// Sets the IPv6 address of the TAP adapter.
func (tun *tunDevice) setupAddress(addr string) error { func (tun *tunDevice) setupAddress(addr string) error {
// Set address // Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",