From ff55070458100dc2c1edfd6400b690a92fb0dab1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 24 Jan 2018 10:59:01 +0000 Subject: [PATCH] TAP support added - Supports Windows using OpenVPN NDIS 6 TAP driver - Supports NDP Neighbor Solicitation and Advertisements in ndp.go - Supports TAP encapsulation and decapsulation in tun.go --- src/yggdrasil/ndp.go | 165 +++++++++++++++++++++++++++++++++++ src/yggdrasil/tun.go | 35 ++++++-- src/yggdrasil/tun_other.go | 1 + src/yggdrasil/tun_windows.go | 37 ++++++++ 4 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 src/yggdrasil/ndp.go create mode 100644 src/yggdrasil/tun_windows.go diff --git a/src/yggdrasil/ndp.go b/src/yggdrasil/ndp.go new file mode 100644 index 00000000..f6b123a7 --- /dev/null +++ b/src/yggdrasil/ndp.go @@ -0,0 +1,165 @@ +package yggdrasil + +// The NDP functions are needed when you are running with a +// TAP adapter - as the operating system expects neighbor solicitations +// for on-link traffic, this goroutine provides them + +import "golang.org/x/net/icmp" +import "encoding/binary" +import "unsafe" + +type macAddress [6]byte +type ipv6Address [16]byte + +const ETHER = 14 +const IPV6 = 40 + +type ndp struct { + tun *tunDevice + peermac macAddress + peerlladdr ipv6Address + mymac macAddress + mylladdr ipv6Address + recv chan []byte +} + +type etherHeader struct { + destination macAddress + source macAddress + ethertype [2]byte +} + +type ipv6Header struct { + preamble [4]byte + length [2]byte + nextheader byte + hoplimit byte + source ipv6Address + destination ipv6Address +} + +type icmpv6Header struct { + messagetype byte + code byte + checksum uint16 +} + +type icmpv6PseudoHeader struct { + source ipv6Address + destination ipv6Address + length [4]byte + zero [3]byte + nextheader byte +} + +type icmpv6Packet struct { + ether etherHeader + ipv6 ipv6Header + icmpv6 icmpv6Header + flags [4]byte + targetaddress ipv6Address + optiontype byte + optionlength byte + linklayeraddress macAddress +} + +func (n *ndp) init(t *tunDevice) { + n.tun = t + n.recv = make(chan []byte) + copy(n.mymac[:], []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) + copy(n.mylladdr[:], []byte{ + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}) + go n.listen() +} + +func (n *ndp) listen() { + for { + // Receive from the channel and check if we're using TAP instead + // of TUN mode - NDP is only relevant for TAP + datain := <-n.recv + if !n.tun.iface.IsTAP() { + continue + } + + // Create our return frame buffer and also the unsafe pointers to + // map them to the structs + dataout := make([]byte, ETHER+IPV6+32) + in := (*icmpv6Packet)(unsafe.Pointer(&datain[0])) + out := (*icmpv6Packet)(unsafe.Pointer(&dataout[0])) + + // Store peer MAC address and link-local IP address - + // these will be used later by tun.go + copy(n.peermac[:6], in.ether.source[:6]) + copy(n.peerlladdr[:16], in.ipv6.source[:16]) + + // Ignore non-IPv6 packets + if binary.BigEndian.Uint16(in.ether.ethertype[:]) != uint16(0x86DD) { + continue + } + + // Ignore non-ICMPv6 packets + if in.ipv6.nextheader != uint8(0x3A) { + continue + } + + // Ignore non-NDP Solicitation packets + if in.icmpv6.messagetype != uint8(135) { + continue + } + + // Ignore NDP requests for anything outside of fd00::/8 + if in.targetaddress[0] != 0xFD { + continue + } + + // Populate the out ethernet headers + copy(out.ether.destination[:], in.ether.destination[:]) + copy(out.ether.source[:], n.mymac[:]) + binary.BigEndian.PutUint16(out.ether.ethertype[:], uint16(0x86DD)) + + // And for now just copy the rest of the packet we were sent + copy(dataout[ETHER:ETHER+IPV6], datain[ETHER:ETHER+IPV6]) + + // Update the source and destination addresses in the IPv6 header + copy(out.ipv6.destination[:], in.ipv6.source[:]) + copy(out.ipv6.source[:], n.mylladdr[:]) + binary.BigEndian.PutUint16(out.ipv6.length[:], uint16(32)) + + // Copy the payload + copy(dataout[ETHER+IPV6:], datain[ETHER+IPV6:]) + + // Update the ICMPv6 headers + out.icmpv6.messagetype = uint8(136) + out.icmpv6.code = uint8(0) + + // Update the ICMPv6 payload + copy(out.targetaddress[:], in.targetaddress[:]) + out.optiontype = uint8(2) + out.optionlength = uint8(1) + copy(out.linklayeraddress[:], n.mymac[:]) + binary.BigEndian.PutUint32(out.flags[:], uint32(0x20000000)) + + // Generate the pseudo-header for the checksum + ps := make([]byte, 44) + pseudo := (*icmpv6PseudoHeader)(unsafe.Pointer(&ps[0])) + copy(pseudo.destination[:], out.ipv6.destination[:]) + copy(pseudo.source[:], out.ipv6.source[:]) + binary.BigEndian.PutUint32(pseudo.length[:], uint32(binary.BigEndian.Uint16(out.ipv6.length[:]))) + pseudo.nextheader = out.ipv6.nextheader + + // Lazy-man's checksum using the icmp library + icmpv6, err := icmp.ParseMessage(0x3A, dataout[ETHER+IPV6:]) + if err != nil { + continue + } + payload, err := icmpv6.Marshal(ps) + if err != nil { + continue + } + copy(dataout[ETHER+IPV6:], payload) + + // Send the frame back to the TAP adapter + n.tun.iface.Write(dataout) + } +} diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 3a819982..d23ced7e 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -2,7 +2,7 @@ package yggdrasil // This manages the tun driver to send/recv packets to/from applications -// import water "github.com/songgao/water" +import ethernet "github.com/songgao/packets/ethernet" const IPv6_HEADER_LENGTH = 40 @@ -17,6 +17,7 @@ type tunInterface interface { type tunDevice struct { core *Core + ndp ndp send chan<- []byte recv <-chan []byte mtu int @@ -25,13 +26,28 @@ type tunDevice struct { func (tun *tunDevice) init(core *Core) { tun.core = core + tun.ndp.init(tun) } func (tun *tunDevice) write() error { for { data := <-tun.recv - if _, err := tun.iface.Write(data); err != nil { - return err + if tun.iface.IsTAP() { + var frame ethernet.Frame + frame.Prepare( + tun.ndp.peermac[:6], // Destination MAC address + tun.ndp.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + ethernet.IPv6, // Ethertype + len(data)) // Payload length + copy(frame[14:], data[:]) + if _, err := tun.iface.Write(frame); err != nil { + return err + } + } else { + if _, err := tun.iface.Write(data); err != nil { + return err + } } util_putBytes(data) } @@ -44,13 +60,20 @@ func (tun *tunDevice) read() error { if err != nil { return err } - if buf[0]&0xf0 != 0x60 || - n != 256*int(buf[4])+int(buf[5])+IPv6_HEADER_LENGTH { + o := 0 + if tun.iface.IsTAP() { + o = 14 + b := make([]byte, n) + copy(b, buf) + tun.ndp.recv <- b + } + if buf[o]&0xf0 != 0x60 || + n != 256*int(buf[o+4])+int(buf[o+5])+IPv6_HEADER_LENGTH+o { // Either not an IPv6 packet or not the complete packet for some reason //panic("Should not happen in testing") continue } - packet := append(util_getBytes(), buf[:n]...) + packet := append(util_getBytes(), buf[o:n]...) tun.send <- packet } } diff --git a/src/yggdrasil/tun_other.go b/src/yggdrasil/tun_other.go index e525827c..4f86d050 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/yggdrasil/tun_other.go @@ -1,5 +1,6 @@ // +build !linux // +build !darwin +// +build !windows package yggdrasil diff --git a/src/yggdrasil/tun_windows.go b/src/yggdrasil/tun_windows.go new file mode 100644 index 00000000..ee0134a2 --- /dev/null +++ b/src/yggdrasil/tun_windows.go @@ -0,0 +1,37 @@ +package yggdrasil + +import water "github.com/songgao/water" +import "os/exec" +import "strings" +import "fmt" + +// This is to catch Windows platforms + +func (tun *tunDevice) setup(ifname string, addr string, mtu int) error { + config := water.Config{DeviceType: water.TAP} + config.PlatformSpecificParams.ComponentID = "tap0901" + config.PlatformSpecificParams.Network = "169.254.0.1/32" + iface, err := water.New(config) + if err != nil { + panic(err) + } + tun.iface = iface + tun.mtu = mtu + return tun.setupAddress(addr) +} + +func (tun *tunDevice) setupAddress(addr string) error { + // Set address + // addr = strings.TrimRight(addr, "/8") + cmd := exec.Command("netsh", "interface", "ipv6", "set", "address", + fmt.Sprintf("interface=\"%s\"", tun.iface.Name()), + fmt.Sprintf("addr=\"%s\"", addr)) + tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + output, err := cmd.CombinedOutput() + if err != nil { + tun.core.log.Printf("Windows netsh failed: %v.", err) + tun.core.log.Println(string(output)) + return err + } + return nil +}