diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go new file mode 100644 index 00000000..f96a9c9a --- /dev/null +++ b/src/yggdrasil/icmpv6.go @@ -0,0 +1,226 @@ +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 "net" +import "golang.org/x/net/ipv6" +import "golang.org/x/net/icmp" +import "encoding/binary" + +type macAddress [6]byte + +const ETHER = 14 + +type icmpv6 struct { + tun *tunDevice + peermac macAddress + peerlladdr net.IP + mylladdr net.IP + mymac macAddress +} + +// Marshal returns the binary encoding of h. +func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { + b := make([]byte, 40) + b[0] |= byte(h.Version) << 4 + b[0] |= byte(h.TrafficClass) >> 4 + b[1] |= byte(h.TrafficClass) << 4 + b[1] |= byte(h.FlowLabel >> 16) + b[2] = byte(h.FlowLabel >> 8) + b[3] = byte(h.FlowLabel) + binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen)) + b[6] = byte(h.NextHeader) + b[7] = byte(h.HopLimit) + copy(b[8:24], h.Src) + copy(b[24:40], h.Dst) + return b, nil +} + +func (i *icmpv6) init(t *tunDevice) { + i.tun = t + + // Our MAC address and link-local address + copy(i.mymac[:], []byte{ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) + i.mylladdr = net.IP{ + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} +} + +func (i *icmpv6) parse_packet(datain []byte) { + var response []byte + var err error + + // Parse the frame/packet + if i.tun.iface.IsTAP() { + response, err = i.parse_packet_tap(datain) + } else { + response, err = i.parse_packet_tun(datain) + } + + if err != nil { + return + } + + // Write the packet to TUN/TAP + i.tun.iface.Write(response) +} + +func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { + // Store the peer MAC address + copy(i.peermac[:6], datain[6:12]) + + // Ignore non-IPv6 frames + if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { + return nil, nil + } + + // Hand over to parse_packet_tun to interpret the IPv6 packet + ipv6packet, err := i.parse_packet_tun(datain[ETHER:]) + if err != nil { + return nil, err + } + + // Create the response buffer + dataout := make([]byte, ETHER+ipv6.HeaderLen+32) + + // Populate the response ethernet headers + copy(dataout[:6], datain[6:12]) + copy(dataout[6:12], i.mymac[:]) + binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD)) + + // Copy the returned packet to our response ethernet frame + copy(dataout[ETHER:], ipv6packet) + return dataout, nil +} + +func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { + // Parse the IPv6 packet headers + ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) + if err != nil { + return nil, err + } + + // Check if the packet is IPv6 + if ipv6Header.Version != ipv6.Version { + return nil, err + } + + // Check if the packet is ICMPv6 + if ipv6Header.NextHeader != 58 { + return nil, err + } + + // Store the peer link local address, it will come in useful later + copy(i.peerlladdr[:], ipv6Header.Src[:]) + + // Parse the ICMPv6 message contents + icmpv6Header, err := icmp.ParseMessage(58, datain[ipv6.HeaderLen:]) + if err != nil { + return nil, err + } + + // Check for a supported message type + switch icmpv6Header.Type { + case ipv6.ICMPTypeNeighborSolicitation: + { + response, err := i.handle_ndp(datain[ipv6.HeaderLen:]) + if err == nil { + // Create our ICMPv6 response + responsePacket, err := i.create_icmpv6_tun(ipv6Header.Src, ipv6.ICMPTypeNeighborAdvertisement, 0, response) + if err != nil { + return nil, err + } + + // Fix the checksum because I don't even know why, net/icmp is stupid + responsePacket[17] ^= 0x4 + + // Send it back + return responsePacket, nil + } else { + return nil, err + } + } + } + + return nil, nil +} + +func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, mtype ipv6.ICMPType, mcode int, mbody []byte) ([]byte, error) { + // Pass through to create_icmpv6_tun + ipv6packet, err := i.create_icmpv6_tun(dst, mtype, mcode, mbody) + if err != nil { + return nil, err + } + + // Create the response buffer + dataout := make([]byte, ETHER+ipv6.HeaderLen+len(mbody)) + + // Populate the response ethernet headers + copy(dataout[:6], dstmac[:6]) + copy(dataout[6:12], i.mymac[:]) + binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD)) + + // Copy the returned packet to our response ethernet frame + copy(dataout[ETHER:], ipv6packet) + return dataout, nil +} + +func (i *icmpv6) create_icmpv6_tun(dst net.IP, mtype ipv6.ICMPType, mcode int, mbody []byte) ([]byte, error) { + // Create the IPv6 header + ipv6Header := ipv6.Header{ + Version: ipv6.Version, + NextHeader: 58, + PayloadLen: len(mbody), + HopLimit: 255, + Src: i.mylladdr, + Dst: dst, + } + + // Create the ICMPv6 message + icmpMessage := icmp.Message{ + Type: mtype, + Code: mcode, + Body: &icmp.DefaultMessageBody{Data: mbody}, + } + + // Convert the IPv6 header into []byte + ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header) + if err != nil { + return nil, err + } + + // Convert the ICMPv6 message into []byte + icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(ipv6Header.Dst, ipv6Header.Src)) + if err != nil { + return nil, err + } + + // Construct the packet + responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen) + copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf) + copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf) + + // Send it back + return responsePacket, nil +} + +func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) { + // Ignore NDP requests for anything outside of fd00::/8 + if in[8] != 0xFD { + return nil, nil + } + + // Create our NDP message body response + body := make([]byte, 32) + binary.BigEndian.PutUint32(body[:4], uint32(0x20000000)) + copy(body[4:20], in[8:24]) // Target address + body[20] = uint8(2) + body[21] = uint8(1) + copy(body[22:28], i.mymac[:6]) + + // Send it back + return body, nil +} 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..ea2b5893 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,13 @@ 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 + go tun.icmpv6.parse_packet(b) + } packet := append(util_getBytes(), buf[o:n]...) tun.send <- packet }