Improve icmpv6.go

- Now doesn't use unsafe
- Much cleaner
- Doesn't run in a goroutine perpetually
- Has a function to create ICMPv6 packets
This commit is contained in:
Neil Alexander 2018-02-14 11:21:23 +00:00
parent 6571a8c300
commit d78e0f7067
2 changed files with 140 additions and 160 deletions

View File

@ -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 {
// 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)
// 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 err
}
copy(dataout[IPV6:], payload)
// Return nil if successful
return nil
return nil, err
}
func (i *icmpv6) handle_ndp(in *icmpv6Payload, out *icmpv6Payload) {
// 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) 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
}

View File

@ -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