mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-15 16:40:27 +00:00
237 lines
5.5 KiB
Go
237 lines
5.5 KiB
Go
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 "errors"
|
|
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
|
|
response, err := i.parse_packet_tap(datain)
|
|
if err != nil {
|
|
i.tun.core.log.Printf("Error from icmpv6.parse_packet_tap: %v", err)
|
|
continue
|
|
}
|
|
if response != nil {
|
|
i.tun.iface.Write(response)
|
|
}
|
|
} else {
|
|
// TUN mode
|
|
response, err := i.parse_packet_tun(datain)
|
|
if err != nil {
|
|
i.tun.core.log.Printf("Error from icmpv6.parse_packet_tun: %v", err)
|
|
continue
|
|
}
|
|
if response != nil {
|
|
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])
|
|
|
|
// Ignore non-IPv6 frames
|
|
if binary.BigEndian.Uint16(in.ether.ethertype[:]) != 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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return the response packet
|
|
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:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// And copy the payload
|
|
payload, err := icmpv6.Marshal(ps)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(dataout[IPV6:], payload)
|
|
|
|
// Return nil if successful
|
|
return nil
|
|
}
|
|
|
|
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))
|
|
}
|