diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 0491f880..957b192e 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -13,6 +13,7 @@ import ( "encoding/binary" "errors" "net" + "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" @@ -23,11 +24,17 @@ type macAddress [6]byte const len_ETHER = 14 type icmpv6 struct { - tun *tunDevice - peermac macAddress - peerlladdr net.IP - mylladdr net.IP - mymac macAddress + tun *tunDevice + mylladdr net.IP + mymac macAddress + peermacs map[address]neighbor +} + +type neighbor struct { + mac macAddress + learned bool + lastadvertisement time.Time + lastsolicitation time.Time } // Marshal returns the binary encoding of h. @@ -52,13 +59,16 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *icmpv6) init(t *tunDevice) { i.tun = t + i.peermacs = make(map[address]neighbor) // Our MAC address and link-local address - copy(i.mymac[:], []byte{ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) + i.mymac = macAddress{ + 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} + copy(i.mymac[:], i.tun.core.router.addr[:]) + copy(i.mylladdr[9:], i.tun.core.router.addr[1:]) } // Parses an incoming ICMPv6 packet. The packet provided may be either an @@ -73,7 +83,7 @@ func (i *icmpv6) parse_packet(datain []byte) { if i.tun.iface.IsTAP() { response, err = i.parse_packet_tap(datain) } else { - response, err = i.parse_packet_tun(datain) + response, err = i.parse_packet_tun(datain, nil) } if err != nil { @@ -89,16 +99,14 @@ func (i *icmpv6) parse_packet(datain []byte) { // 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) { - // 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[len_ETHER:]) + mac := datain[6:12] + ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac) if err != nil { return nil, err } @@ -120,7 +128,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { // 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, datamac *[]byte) ([]byte, error) { // Parse the IPv6 packet headers ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) if err != nil { @@ -137,9 +145,6 @@ func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { 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 { @@ -149,24 +154,35 @@ func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { // 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, i.mylladdr, - ipv6.ICMPTypeNeighborAdvertisement, 0, - &icmp.DefaultMessageBody{Data: response}) - if err != nil { - return nil, err - } - - // Send it back - return responsePacket, nil - } else { + response, err := i.handle_ndp(datain[ipv6.HeaderLen:]) + if err == nil { + // Create our ICMPv6 response + responsePacket, err := i.create_icmpv6_tun( + ipv6Header.Src, i.mylladdr, + ipv6.ICMPTypeNeighborAdvertisement, 0, + &icmp.DefaultMessageBody{Data: response}) + if err != nil { return nil, err } + + // Send it back + return responsePacket, nil + } else { + return nil, err } + case ipv6.ICMPTypeNeighborAdvertisement: + if datamac != nil { + var addr address + var mac macAddress + copy(addr[:], ipv6Header.Src[:]) + copy(mac[:], (*datamac)[:]) + neighbor := i.peermacs[addr] + neighbor.mac = mac + neighbor.learned = true + neighbor.lastadvertisement = time.Now() + i.peermacs[addr] = neighbor + } + return nil, errors.New("No response needed") } return nil, errors.New("ICMPv6 type not matched") @@ -238,6 +254,42 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, return responsePacket, nil } +func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) { + // Create the ND payload + var payload [28]byte + copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) + copy(payload[4:20], dst[:]) + copy(payload[20:22], []byte{0x01, 0x01}) + copy(payload[22:28], i.mymac[:6]) + + // Create the ICMPv6 solicited-node address + var dstaddr address + copy(dstaddr[:13], []byte{ + 0xFF, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xFF}) + copy(dstaddr[13:], dst[13:16]) + + // Create the multicast MAC + var dstmac macAddress + copy(dstmac[:2], []byte{0x33, 0x33}) + copy(dstmac[2:6], dstaddr[12:16]) + + // Create the ND request + requestPacket, err := i.create_icmpv6_tap( + dstmac, dstaddr[:], i.mylladdr, + ipv6.ICMPTypeNeighborSolicitation, 0, + &icmp.DefaultMessageBody{Data: payload[:]}) + if err != nil { + return nil, err + } + neighbor := i.peermacs[dstaddr] + neighbor.lastsolicitation = time.Now() + i.peermacs[dstaddr] = neighbor + + return requestPacket, 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 diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 1987c2d2..447ba617 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -3,6 +3,9 @@ package yggdrasil // This manages the tun driver to send/recv packets to/from applications import ( + "bytes" + "errors" + "time" "yggdrasil/defaults" "github.com/songgao/packets/ethernet" @@ -48,6 +51,21 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) } go func() { panic(tun.read()) }() go func() { panic(tun.write()) }() + go func() { + for { + if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { + break + } + request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + time.Sleep(time.Second) + } + }() return nil } @@ -61,16 +79,74 @@ func (tun *tunDevice) write() error { continue } if tun.iface.IsTAP() { - var frame ethernet.Frame - frame.Prepare( - 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[tun_ETHER_HEADER_LENGTH:], data[:]) - if _, err := tun.iface.Write(frame); err != nil { - panic(err) + var destAddr address + if data[0]&0xf0 == 0x60 { + if len(data) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } + copy(destAddr[:16], data[24:]) + } else if data[0]&0xf0 == 0x40 { + if len(data) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } + copy(destAddr[:4], data[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(destAddr address) { + neigh, known := tun.icmpv6.peermacs[destAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.create_ndp_tap(destAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[destAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if data[0]&0xf0 == 0x40 { + destAddr = tun.core.router.addr + } else if data[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) { + destAddr = tun.core.router.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(destAddr) + } else { + sendndp(tun.core.router.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case data[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case data[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(data)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) + if _, err := tun.iface.Write(frame); err != nil { + panic(err) + } } } else { if _, err := tun.iface.Write(data); err != nil {