diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go new file mode 100644 index 00000000..893cef3e --- /dev/null +++ b/src/yggdrasil/icmpv6.go @@ -0,0 +1,203 @@ +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" // TODO investigate if this can be done without resorting to unsafe + +type macAddress [6]byte +type ipv6Address [16]byte + +const ETHER = 14 +const IPV6 = 40 + +type icmpv6 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 icmpv6Payload struct { + ether etherHeader + ipv6 ipv6Header + icmpv6 icmpv6Header + flags [4]byte + targetaddress ipv6Address + optiontype byte + optionlength byte + linklayeraddress macAddress +} + +type icmpv6Packet struct { + ipv6 ipv6Header + payload icmpv6Payload +} + +type icmpv6Frame struct { + ether etherHeader + packet icmpv6Packet +} + +func (i *icmpv6) init(t *tunDevice) { + i.tun = t + i.recv = make(chan []byte) + copy(i.mymac[:], []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) + copy(i.mylladdr[:], []byte{ + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}) + go i.listen() +} + +func (i *icmpv6) listen() { + for { + datain := <-i.recv + + if i.tun.iface.IsTAP() { + // TAP mode + dataout := make([]byte, ETHER+IPV6+32) + i.read_tap(datain, dataout) + i.tun.iface.Write(dataout) + } else { + // TUN mode + dataout := make([]byte, IPV6+32) + i.read_tun(datain, dataout) + i.tun.iface.Write(dataout) + } + } +} + +func (i *icmpv6) read_tap(datain []byte, dataout []byte) { + // Set up + in := (*icmpv6Frame)(unsafe.Pointer(&datain[0])) + out := (*icmpv6Frame)(unsafe.Pointer(&dataout[0])) + + // Store the peer MAC address + copy(i.peermac[:6], in.ether.source[:6]) + + // Ignore non-IPv6 frames + if binary.BigEndian.Uint16(in.ether.ethertype[:]) != uint16(0x86DD) { + return + } + + // Populate the out ethernet headers + copy(out.ether.destination[:], in.ether.destination[:]) + copy(out.ether.source[:], i.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]) + + // Then pass the IP packet onto the next function + i.read_tun(datain[ETHER:], dataout[ETHER:]) +} + +func (i *icmpv6) read_tun(datain []byte, dataout []byte) { + // Set up + in := (*icmpv6Packet)(unsafe.Pointer(&datain[0])) + out := (*icmpv6Packet)(unsafe.Pointer(&dataout[0])) + + // Store the peer link-local address + copy(i.peerlladdr[:16], in.ipv6.source[:16]) + + // Ignore non-ICMPv6 packets + if in.ipv6.nextheader != uint8(0x3A) { + return + } + + // What is the ICMPv6 message type? + switch in.payload.icmpv6.messagetype { + case uint8(135): + i.handle_ndp(&in.payload, &out.payload) + break + } + + // Update the source and destination addresses in the IPv6 header + copy(out.ipv6.destination[:], in.ipv6.source[:]) + copy(out.ipv6.source[:], i.mylladdr[:]) + binary.BigEndian.PutUint16(out.ipv6.length[:], uint16(32)) + + // Copy the payload + copy(dataout[IPV6:], datain[IPV6:]) + + // Calculate the checksum + i.calculate_checksum(dataout) +} + +func (i *icmpv6) calculate_checksum(dataout []byte) { + // Set up + out := (*icmpv6Packet)(unsafe.Pointer(&dataout[0])) + + // 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[IPV6:]) + if err != nil { + return + } + + // And copy the payload + payload, err := icmpv6.Marshal(ps) + if err != nil { + return + } + copy(dataout[IPV6:], payload) +} + +func (i *icmpv6) handle_ndp(in *icmpv6Payload, out *icmpv6Payload) { + // Ignore NDP requests for anything outside of fd00::/8 + if in.targetaddress[0] != 0xFD { + return + } + + // 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[:], i.mymac[:]) + binary.BigEndian.PutUint32(out.flags[:], uint32(0x20000000)) +} diff --git a/src/yggdrasil/ndp.go b/src/yggdrasil/ndp.go deleted file mode 100644 index c56174b0..00000000 --- a/src/yggdrasil/ndp.go +++ /dev/null @@ -1,165 +0,0 @@ -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" // TODO investigate if this can be done without resorting to 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 871fb3c0..b1d96d99 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -17,17 +17,17 @@ type tunInterface interface { } type tunDevice struct { - core *Core - ndp ndp - send chan<- []byte - recv <-chan []byte - mtu int - iface tunInterface + core *Core + icmpv6 icmpv6 + send chan<- []byte + recv <-chan []byte + mtu int + iface tunInterface } func (tun *tunDevice) init(core *Core) { tun.core = core - tun.ndp.init(tun) + tun.icmpv6.init(tun) } func (tun *tunDevice) write() error { @@ -36,11 +36,11 @@ func (tun *tunDevice) write() error { 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 + tun.icmpv6.peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + ethernet.IPv6, // Ethertype + len(data)) // Payload length copy(frame[ETHER_HEADER_LENGTH:], data[:]) if _, err := tun.iface.Write(frame); err != nil { panic(err) @@ -68,9 +68,6 @@ func (tun *tunDevice) read() error { o := 0 if tun.iface.IsTAP() { o = ETHER_HEADER_LENGTH - 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 { @@ -78,6 +75,12 @@ func (tun *tunDevice) read() error { //panic("Should not happen in testing") continue } + if buf[o+6] == 58 { + // Found an ICMPv6 packet + b := make([]byte, n) + copy(b, buf) + tun.icmpv6.recv <- b + } packet := append(util_getBytes(), buf[o:n]...) tun.send <- packet }