diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 483902b..5d014bd 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -4,219 +4,203 @@ package yggdrasil // 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" -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 + peerlladdr net.IP + mylladdr net.IP mymac macAddress - mylladdr ipv6Address } -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 +// 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 - copy(i.mymac[:], []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) - copy(i.mylladdr[:], []byte{ + + // 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}) + 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 { - i.tun.core.log.Printf("ICMPv6 error: %v", err) return } - if response != nil { - i.tun.iface.Write(response) - } + + // Write the packet to TUN/TAP + i.tun.iface.Write(response) } func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { - // Set up - in := (*icmpv6Frame)(unsafe.Pointer(&datain[0])) - // Store the peer MAC address - copy(i.peermac[:6], in.ether.source[:6]) + copy(i.peermac[:6], datain[6:12]) // Ignore non-IPv6 frames - if binary.BigEndian.Uint16(in.ether.ethertype[:]) != uint16(0x86DD) { + if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { return nil, nil } - // Create the response buffer - dataout := make([]byte, ETHER+IPV6+32) - out := (*icmpv6Frame)(unsafe.Pointer(&dataout[0])) - - // Populate the response ethernet headers - copy(out.ether.destination[:], in.ether.destination[:]) - copy(out.ether.source[:], i.mymac[:]) - binary.BigEndian.PutUint16(out.ether.ethertype[:], uint16(0x86DD)) - // Hand over to parse_packet_tun to interpret the IPv6 packet - ipv6packet, err := i.parse_packet_tun(datain) - if err != nil { - return nil, nil - } - - // Copy the returned packet to our response ethernet frame - if ipv6packet != nil { - copy(dataout[ETHER:ETHER+IPV6], ipv6packet) - return dataout, nil - } - - // At this point there is no response to send back - return nil, nil -} - -func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { - // Set up - dataout := make([]byte, IPV6+32) - out := (*icmpv6Packet)(unsafe.Pointer(&dataout[0])) - in := (*icmpv6Packet)(unsafe.Pointer(&datain[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 nil, nil - } - - // 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 - err := i.calculate_checksum(dataout) + ipv6packet, err := i.parse_packet_tun(datain[ETHER:]) if err != nil { return nil, err } - // Return the response packet + // 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) calculate_checksum(dataout []byte) (error) { - // 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:]) +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 err + return nil, err } - // And copy the payload - payload, err := icmpv6.Marshal(ps) - if err != nil { - return err + // Check if the packet is IPv6 + if ipv6Header.Version != ipv6.Version { + return nil, err } - copy(dataout[IPV6:], payload) - // Return nil if successful - return nil + // 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(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) handle_ndp(in *icmpv6Payload, out *icmpv6Payload) { +func (i *icmpv6) create_icmpv6(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.targetaddress[0] != 0xFD { - return + if in[8] != 0xFD { + return nil, nil } - // Update the ICMPv6 headers - out.icmpv6.messagetype = uint8(136) - out.icmpv6.code = uint8(0) + // 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]) - // 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)) + // Send it back + return body, nil } diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index dc2c673..ea2b589 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -80,11 +80,7 @@ func (tun *tunDevice) read() error { b := make([]byte, n) copy(b, buf) // tun.icmpv6.recv <- b - if tun.iface.IsTAP() { - go tun.icmpv6.parse_packet_tap(b) - } else { - go tun.icmpv6.parse_packet_tun(b) - } + go tun.icmpv6.parse_packet(b) } packet := append(util_getBytes(), buf[o:n]...) tun.send <- packet