1package tuntap
2
3// The ICMPv6 module implements functions to easily create ICMPv6
4// packets. These functions, when mixed with the built-in Go IPv6
5// and ICMP libraries, can be used to send control messages back
6// to the host. Examples include:
7// - NDP messages, when running in TAP mode
8// - Packet Too Big messages, when packets exceed the session MTU
9// - Destination Unreachable messages, when a session prohibits
10//   incoming traffic
11
12import (
13	"encoding/binary"
14	"errors"
15	"net"
16	"sync"
17	"time"
18
19	"golang.org/x/net/icmp"
20	"golang.org/x/net/ipv6"
21
22	"github.com/yggdrasil-network/yggdrasil-go/src/address"
23)
24
25const len_ETHER = 14
26
27type ICMPv6 struct {
28	tun           *TunAdapter
29	mylladdr      net.IP
30	mymac         net.HardwareAddr
31	peermacs      map[address.Address]neighbor
32	peermacsmutex sync.RWMutex
33}
34
35type neighbor struct {
36	mac               net.HardwareAddr
37	learned           bool
38	lastadvertisement time.Time
39	lastsolicitation  time.Time
40}
41
42// Marshal returns the binary encoding of h.
43func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
44	b := make([]byte, 40)
45	b[0] |= byte(h.Version) << 4
46	b[0] |= byte(h.TrafficClass) >> 4
47	b[1] |= byte(h.TrafficClass) << 4
48	b[1] |= byte(h.FlowLabel >> 16)
49	b[2] = byte(h.FlowLabel >> 8)
50	b[3] = byte(h.FlowLabel)
51	binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen))
52	b[6] = byte(h.NextHeader)
53	b[7] = byte(h.HopLimit)
54	copy(b[8:24], h.Src)
55	copy(b[24:40], h.Dst)
56	return b, nil
57}
58
59// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
60// our MAC address. ICMPv6 messages will always appear to originate from these
61// addresses.
62func (i *ICMPv6) Init(t *TunAdapter) {
63	i.tun = t
64	i.peermacsmutex.Lock()
65	i.peermacs = make(map[address.Address]neighbor)
66	i.peermacsmutex.Unlock()
67
68	// Our MAC address and link-local address
69	i.mymac = net.HardwareAddr{
70		0x02, 0x00, 0x00, 0x00, 0x00, 0x02}
71	i.mylladdr = net.IP{
72		0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
73		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
74	copy(i.mymac[:], i.tun.addr[:])
75	copy(i.mylladdr[9:], i.tun.addr[1:])
76}
77
78// Parses an incoming ICMPv6 packet. The packet provided may be either an
79// ethernet frame containing an IP packet, or the IP packet alone. This is
80// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
81// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6
82// module handled the packet or contains the error if not.
83func (i *ICMPv6) ParsePacket(datain []byte) error {
84	var response []byte
85	var err error
86
87	// Parse the frame/packet
88	if i.tun.IsTAP() {
89		response, err = i.UnmarshalPacketL2(datain)
90	} else {
91		response, err = i.UnmarshalPacket(datain, nil)
92	}
93
94	if err != nil {
95		return err
96	}
97
98	// Write the packet to TUN/TAP
99	i.tun.iface.Write(response)
100	return nil
101}
102
103// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
104// the IP packet to the ParsePacket function for further processing.
105// A response buffer is also created for the response message, also complete
106// with ethernet headers.
107func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) {
108	// Ignore non-IPv6 frames
109	if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) {
110		return nil, errors.New("Ignoring non-IPv6 frame")
111	}
112
113	// Hand over to ParsePacket to interpret the IPv6 packet
114	mac := datain[6:12]
115	ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac)
116	if err != nil {
117		return nil, err
118	}
119
120	// Create the response buffer
121	dataout := make([]byte, len_ETHER+ipv6.HeaderLen+32)
122
123	// Populate the response ethernet headers
124	copy(dataout[:6], datain[6:12])
125	copy(dataout[6:12], i.mymac[:])
126	binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD))
127
128	// Copy the returned packet to our response ethernet frame
129	copy(dataout[len_ETHER:], ipv6packet)
130	return dataout, nil
131}
132
133// Unwraps the IP headers of an incoming IPv6 packet and performs various
134// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
135// ICMPv6 message match a known expected type. The relevant handler function
136// is then called and a response packet may be returned.
137func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) {
138	// Parse the IPv6 packet headers
139	ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
140	if err != nil {
141		return nil, err
142	}
143
144	// Check if the packet is IPv6
145	if ipv6Header.Version != ipv6.Version {
146		return nil, errors.New("Ignoring non-IPv6 packet")
147	}
148
149	// Check if the packet is ICMPv6
150	if ipv6Header.NextHeader != 58 {
151		return nil, errors.New("Ignoring non-ICMPv6 packet")
152	}
153
154	// Parse the ICMPv6 message contents
155	icmpv6Header, err := icmp.ParseMessage(58, datain[ipv6.HeaderLen:])
156	if err != nil {
157		return nil, err
158	}
159
160	// Check for a supported message type
161	switch icmpv6Header.Type {
162	case ipv6.ICMPTypeNeighborSolicitation:
163		if !i.tun.IsTAP() {
164			return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode")
165		}
166		response, err := i.HandleNDP(datain[ipv6.HeaderLen:])
167		if err == nil {
168			// Create our ICMPv6 response
169			responsePacket, err := CreateICMPv6(
170				ipv6Header.Src, i.mylladdr,
171				ipv6.ICMPTypeNeighborAdvertisement, 0,
172				&icmp.DefaultMessageBody{Data: response})
173			if err != nil {
174				return nil, err
175			}
176			// Send it back
177			return responsePacket, nil
178		} else {
179			return nil, err
180		}
181	case ipv6.ICMPTypeNeighborAdvertisement:
182		if !i.tun.IsTAP() {
183			return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode")
184		}
185		if datamac != nil {
186			var addr address.Address
187			var target address.Address
188			mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
189			copy(addr[:], ipv6Header.Src[:])
190			copy(target[:], datain[48:64])
191			copy(mac[:], (*datamac)[:])
192			i.peermacsmutex.Lock()
193			neighbor := i.peermacs[target]
194			neighbor.mac = mac
195			neighbor.learned = true
196			neighbor.lastadvertisement = time.Now()
197			i.peermacs[target] = neighbor
198			i.peermacsmutex.Unlock()
199			i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String())
200			/*
201				i.tun.log.Debugln("Peer MAC table:")
202				i.peermacsmutex.RLock()
203				for t, n := range i.peermacs {
204					if n.learned {
205						i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String())
206					} else {
207						i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet")
208					}
209				}
210				i.peermacsmutex.RUnlock()
211			*/
212		}
213		return nil, errors.New("No response needed")
214	}
215
216	return nil, errors.New("ICMPv6 type not matched")
217}
218
219// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
220// parameters, complete with ethernet and IP headers, which can be written
221// directly to a TAP adapter.
222func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
223	// Pass through to CreateICMPv6
224	ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody)
225	if err != nil {
226		return nil, err
227	}
228
229	// Create the response buffer
230	dataout := make([]byte, len_ETHER+len(ipv6packet))
231
232	// Populate the response ethernet headers
233	copy(dataout[:6], dstmac[:6])
234	copy(dataout[6:12], i.mymac[:])
235	binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD))
236
237	// Copy the returned packet to our response ethernet frame
238	copy(dataout[len_ETHER:], ipv6packet)
239	return dataout, nil
240}
241
242// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
243// parameters, complete with IP headers only, which can be written directly to
244// a TUN adapter, or called directly by the CreateICMPv6L2 function when
245// generating a message for TAP adapters.
246func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
247	// Create the ICMPv6 message
248	icmpMessage := icmp.Message{
249		Type: mtype,
250		Code: mcode,
251		Body: mbody,
252	}
253
254	// Convert the ICMPv6 message into []byte
255	icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(src, dst))
256	if err != nil {
257		return nil, err
258	}
259
260	// Create the IPv6 header
261	ipv6Header := ipv6.Header{
262		Version:    ipv6.Version,
263		NextHeader: 58,
264		PayloadLen: len(icmpMessageBuf),
265		HopLimit:   255,
266		Src:        src,
267		Dst:        dst,
268	}
269
270	// Convert the IPv6 header into []byte
271	ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header)
272	if err != nil {
273		return nil, err
274	}
275
276	// Construct the packet
277	responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen)
278	copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf)
279	copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf)
280
281	// Send it back
282	return responsePacket, nil
283}
284
285func (i *ICMPv6) Solicit(addr address.Address) {
286	retries := 5
287	for retries > 0 {
288		retries--
289		i.peermacsmutex.RLock()
290		if n, ok := i.peermacs[addr]; ok && n.learned {
291			i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String())
292			i.peermacsmutex.RUnlock()
293			return
294		}
295		i.peermacsmutex.RUnlock()
296		i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String())
297		i.peermacsmutex.Lock()
298		if n, ok := i.peermacs[addr]; !ok {
299			i.peermacs[addr] = neighbor{
300				lastsolicitation: time.Now(),
301			}
302		} else {
303			n.lastsolicitation = time.Now()
304		}
305		i.peermacsmutex.Unlock()
306		request, err := i.createNDPL2(addr)
307		if err != nil {
308			panic(err)
309		}
310		if _, err := i.tun.iface.Write(request); err != nil {
311			panic(err)
312		}
313		i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String())
314		time.Sleep(time.Second)
315	}
316}
317
318func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) {
319	i.peermacsmutex.RLock()
320	defer i.peermacsmutex.RUnlock()
321
322	n, ok := i.peermacs[addr]
323	return n, ok
324}
325
326func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) {
327	// Create the ND payload
328	var payload [28]byte
329	copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags
330	copy(payload[4:20], dst[:])                       // Destination
331	copy(payload[20:22], []byte{0x01, 0x01})          // Type & length
332	copy(payload[22:28], i.mymac[:6])                 // Link layer address
333
334	// Create the ICMPv6 solicited-node address
335	var dstaddr address.Address
336	copy(dstaddr[:13], []byte{
337		0xFF, 0x02, 0x00, 0x00,
338		0x00, 0x00, 0x00, 0x00,
339		0x00, 0x00, 0x00, 0x01, 0xFF})
340	copy(dstaddr[13:], dst[13:16])
341
342	// Create the multicast MAC
343	dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
344	copy(dstmac[:2], []byte{0x33, 0x33})
345	copy(dstmac[2:6], dstaddr[12:16])
346
347	// Create the ND request
348	requestPacket, err := i.CreateICMPv6L2(
349		dstmac, dstaddr[:], i.mylladdr,
350		ipv6.ICMPTypeNeighborSolicitation, 0,
351		&icmp.DefaultMessageBody{Data: payload[:]})
352	if err != nil {
353		return nil, err
354	}
355
356	return requestPacket, nil
357}
358
359// Generates a response to an NDP discovery packet. This is effectively called
360// when the host operating system generates an NDP request for any address in
361// the fd00::/8 range, so that the operating system knows to route that traffic
362// to the Yggdrasil TAP adapter.
363func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) {
364	// Ignore NDP requests for anything outside of fd00::/8
365	var source address.Address
366	copy(source[:], in[8:])
367	var snet address.Subnet
368	copy(snet[:], in[8:])
369	switch {
370	case source.IsValid():
371	case snet.IsValid():
372	default:
373		return nil, errors.New("Not an NDP for 0200::/7")
374	}
375
376	// Create our NDP message body response
377	body := make([]byte, 28)
378	binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags
379	copy(body[4:20], in[8:24])                               // Target address
380	body[20] = uint8(2)                                      // Type: Target link-layer address
381	body[21] = uint8(1)                                      // Length: 1x address (8 bytes)
382	copy(body[22:28], i.mymac[:6])
383
384	// Send it back
385	return body, nil
386}
387