1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package icmp_test
6
7import (
8	"errors"
9	"fmt"
10	"net"
11	"reflect"
12	"testing"
13
14	"golang.org/x/net/icmp"
15	"golang.org/x/net/internal/iana"
16	"golang.org/x/net/ipv4"
17	"golang.org/x/net/ipv6"
18)
19
20func TestMarshalAndParseMultipartMessage(t *testing.T) {
21	fn := func(t *testing.T, proto int, tm icmp.Message) error {
22		b, err := tm.Marshal(nil)
23		if err != nil {
24			return err
25		}
26		switch tm.Type {
27		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
28		default:
29			switch proto {
30			case iana.ProtocolICMP:
31				if b[5] != 32 {
32					return fmt.Errorf("got %d; want 32", b[5])
33				}
34			case iana.ProtocolIPv6ICMP:
35				if b[4] != 16 {
36					return fmt.Errorf("got %d; want 16", b[4])
37				}
38			default:
39				return fmt.Errorf("unknown protocol: %d", proto)
40			}
41		}
42		m, err := icmp.ParseMessage(proto, b)
43		if err != nil {
44			return err
45		}
46		if m.Type != tm.Type || m.Code != tm.Code {
47			return fmt.Errorf("got %v; want %v", m, &tm)
48		}
49		switch m.Type {
50		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
51			got, want := m.Body.(*icmp.ExtendedEchoRequest), tm.Body.(*icmp.ExtendedEchoRequest)
52			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
53				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
54			}
55		case ipv4.ICMPTypeDestinationUnreachable:
56			got, want := m.Body.(*icmp.DstUnreach), tm.Body.(*icmp.DstUnreach)
57			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
58				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
59			}
60			if len(got.Data) != 128 {
61				return fmt.Errorf("got %d; want 128", len(got.Data))
62			}
63		case ipv4.ICMPTypeTimeExceeded:
64			got, want := m.Body.(*icmp.TimeExceeded), tm.Body.(*icmp.TimeExceeded)
65			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
66				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
67			}
68			if len(got.Data) != 128 {
69				return fmt.Errorf("got %d; want 128", len(got.Data))
70			}
71		case ipv4.ICMPTypeParameterProblem:
72			got, want := m.Body.(*icmp.ParamProb), tm.Body.(*icmp.ParamProb)
73			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
74				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
75			}
76			if len(got.Data) != 128 {
77				return fmt.Errorf("got %d; want 128", len(got.Data))
78			}
79		case ipv6.ICMPTypeDestinationUnreachable:
80			got, want := m.Body.(*icmp.DstUnreach), tm.Body.(*icmp.DstUnreach)
81			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
82				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
83			}
84			if len(got.Data) != 128 {
85				return fmt.Errorf("got %d; want 128", len(got.Data))
86			}
87		case ipv6.ICMPTypeTimeExceeded:
88			got, want := m.Body.(*icmp.TimeExceeded), tm.Body.(*icmp.TimeExceeded)
89			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
90				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
91			}
92			if len(got.Data) != 128 {
93				return fmt.Errorf("got %d; want 128", len(got.Data))
94			}
95		default:
96			return fmt.Errorf("unknown message type: %v", m.Type)
97		}
98		return nil
99	}
100
101	t.Run("IPv4", func(t *testing.T) {
102		for i, tm := range []icmp.Message{
103			{
104				Type: ipv4.ICMPTypeDestinationUnreachable, Code: 15,
105				Body: &icmp.DstUnreach{
106					Data: []byte("ERROR-INVOKING-PACKET"),
107					Extensions: []icmp.Extension{
108						&icmp.MPLSLabelStack{
109							Class: 1,
110							Type:  1,
111							Labels: []icmp.MPLSLabel{
112								{
113									Label: 16014,
114									TC:    0x4,
115									S:     true,
116									TTL:   255,
117								},
118							},
119						},
120						&icmp.InterfaceInfo{
121							Class: 2,
122							Type:  0x0f,
123							Interface: &net.Interface{
124								Index: 15,
125								Name:  "en101",
126								MTU:   8192,
127							},
128							Addr: &net.IPAddr{
129								IP: net.IPv4(192, 168, 0, 1).To4(),
130							},
131						},
132					},
133				},
134			},
135			{
136				Type: ipv4.ICMPTypeTimeExceeded, Code: 1,
137				Body: &icmp.TimeExceeded{
138					Data: []byte("ERROR-INVOKING-PACKET"),
139					Extensions: []icmp.Extension{
140						&icmp.InterfaceInfo{
141							Class: 2,
142							Type:  0x0f,
143							Interface: &net.Interface{
144								Index: 15,
145								Name:  "en101",
146								MTU:   8192,
147							},
148							Addr: &net.IPAddr{
149								IP: net.IPv4(192, 168, 0, 1).To4(),
150							},
151						},
152						&icmp.MPLSLabelStack{
153							Class: 1,
154							Type:  1,
155							Labels: []icmp.MPLSLabel{
156								{
157									Label: 16014,
158									TC:    0x4,
159									S:     true,
160									TTL:   255,
161								},
162							},
163						},
164					},
165				},
166			},
167			{
168				Type: ipv4.ICMPTypeParameterProblem, Code: 2,
169				Body: &icmp.ParamProb{
170					Pointer: 8,
171					Data:    []byte("ERROR-INVOKING-PACKET"),
172					Extensions: []icmp.Extension{
173						&icmp.MPLSLabelStack{
174							Class: 1,
175							Type:  1,
176							Labels: []icmp.MPLSLabel{
177								{
178									Label: 16014,
179									TC:    0x4,
180									S:     true,
181									TTL:   255,
182								},
183							},
184						},
185						&icmp.InterfaceInfo{
186							Class: 2,
187							Type:  0x0f,
188							Interface: &net.Interface{
189								Index: 15,
190								Name:  "en101",
191								MTU:   8192,
192							},
193							Addr: &net.IPAddr{
194								IP: net.IPv4(192, 168, 0, 1).To4(),
195							},
196						},
197						&icmp.InterfaceInfo{
198							Class: 2,
199							Type:  0x2f,
200							Interface: &net.Interface{
201								Index: 16,
202								Name:  "en102",
203								MTU:   8192,
204							},
205							Addr: &net.IPAddr{
206								IP: net.IPv4(192, 168, 0, 2).To4(),
207							},
208						},
209					},
210				},
211			},
212			{
213				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
214				Body: &icmp.ExtendedEchoRequest{
215					ID: 1, Seq: 2, Local: true,
216					Extensions: []icmp.Extension{
217						&icmp.InterfaceIdent{
218							Class: 3,
219							Type:  1,
220							Name:  "en101",
221						},
222					},
223				},
224			},
225			{
226				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
227				Body: &icmp.ExtendedEchoRequest{
228					ID: 1, Seq: 2, Local: true,
229					Extensions: []icmp.Extension{
230						&icmp.InterfaceIdent{
231							Class: 3,
232							Type:  2,
233							Index: 911,
234						},
235					},
236				},
237			},
238			{
239				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
240				Body: &icmp.ExtendedEchoRequest{
241					ID: 1, Seq: 2,
242					Extensions: []icmp.Extension{
243						&icmp.InterfaceIdent{
244							Class: 3,
245							Type:  3,
246							AFI:   iana.AddrFamily48bitMAC,
247							Addr:  []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab},
248						},
249					},
250				},
251			},
252		} {
253			if err := fn(t, iana.ProtocolICMP, tm); err != nil {
254				t.Errorf("#%d: %v", i, err)
255			}
256		}
257	})
258	t.Run("IPv6", func(t *testing.T) {
259		for i, tm := range []icmp.Message{
260			{
261				Type: ipv6.ICMPTypeDestinationUnreachable, Code: 6,
262				Body: &icmp.DstUnreach{
263					Data: []byte("ERROR-INVOKING-PACKET"),
264					Extensions: []icmp.Extension{
265						&icmp.MPLSLabelStack{
266							Class: 1,
267							Type:  1,
268							Labels: []icmp.MPLSLabel{
269								{
270									Label: 16014,
271									TC:    0x4,
272									S:     true,
273									TTL:   255,
274								},
275							},
276						},
277						&icmp.InterfaceInfo{
278							Class: 2,
279							Type:  0x0f,
280							Interface: &net.Interface{
281								Index: 15,
282								Name:  "en101",
283								MTU:   8192,
284							},
285							Addr: &net.IPAddr{
286								IP:   net.ParseIP("fe80::1"),
287								Zone: "en101",
288							},
289						},
290					},
291				},
292			},
293			{
294				Type: ipv6.ICMPTypeTimeExceeded, Code: 1,
295				Body: &icmp.TimeExceeded{
296					Data: []byte("ERROR-INVOKING-PACKET"),
297					Extensions: []icmp.Extension{
298						&icmp.InterfaceInfo{
299							Class: 2,
300							Type:  0x0f,
301							Interface: &net.Interface{
302								Index: 15,
303								Name:  "en101",
304								MTU:   8192,
305							},
306							Addr: &net.IPAddr{
307								IP:   net.ParseIP("fe80::1"),
308								Zone: "en101",
309							},
310						},
311						&icmp.MPLSLabelStack{
312							Class: 1,
313							Type:  1,
314							Labels: []icmp.MPLSLabel{
315								{
316									Label: 16014,
317									TC:    0x4,
318									S:     true,
319									TTL:   255,
320								},
321							},
322						},
323						&icmp.InterfaceInfo{
324							Class: 2,
325							Type:  0x2f,
326							Interface: &net.Interface{
327								Index: 16,
328								Name:  "en102",
329								MTU:   8192,
330							},
331							Addr: &net.IPAddr{
332								IP:   net.ParseIP("fe80::1"),
333								Zone: "en102",
334							},
335						},
336					},
337				},
338			},
339			{
340				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
341				Body: &icmp.ExtendedEchoRequest{
342					ID: 1, Seq: 2, Local: true,
343					Extensions: []icmp.Extension{
344						&icmp.InterfaceIdent{
345							Class: 3,
346							Type:  1,
347							Name:  "en101",
348						},
349					},
350				},
351			},
352			{
353				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
354				Body: &icmp.ExtendedEchoRequest{
355					ID: 1, Seq: 2, Local: true,
356					Extensions: []icmp.Extension{
357						&icmp.InterfaceIdent{
358							Class: 3,
359							Type:  2,
360							Index: 911,
361						},
362					},
363				},
364			},
365			{
366				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
367				Body: &icmp.ExtendedEchoRequest{
368					ID: 1, Seq: 2,
369					Extensions: []icmp.Extension{
370						&icmp.InterfaceIdent{
371							Class: 3,
372							Type:  3,
373							AFI:   iana.AddrFamilyIPv4,
374							Addr:  []byte{192, 0, 2, 1},
375						},
376					},
377				},
378			},
379		} {
380			if err := fn(t, iana.ProtocolIPv6ICMP, tm); err != nil {
381				t.Errorf("#%d: %v", i, err)
382			}
383		}
384	})
385}
386
387func dumpExtensions(gotExts, wantExts []icmp.Extension) string {
388	var s string
389	for i, got := range gotExts {
390		switch got := got.(type) {
391		case *icmp.MPLSLabelStack:
392			want := wantExts[i].(*icmp.MPLSLabelStack)
393			if !reflect.DeepEqual(got, want) {
394				s += fmt.Sprintf("#%d: got %#v; want %#v\n", i, got, want)
395			}
396		case *icmp.InterfaceInfo:
397			want := wantExts[i].(*icmp.InterfaceInfo)
398			if !reflect.DeepEqual(got, want) {
399				s += fmt.Sprintf("#%d: got %#v, %#v, %#v; want %#v, %#v, %#v\n", i, got, got.Interface, got.Addr, want, want.Interface, want.Addr)
400			}
401		case *icmp.InterfaceIdent:
402			want := wantExts[i].(*icmp.InterfaceIdent)
403			if !reflect.DeepEqual(got, want) {
404				s += fmt.Sprintf("#%d: got %#v; want %#v\n", i, got, want)
405			}
406		case *icmp.RawExtension:
407			s += fmt.Sprintf("#%d: raw extension\n", i)
408		}
409	}
410	if len(s) == 0 {
411		s += "empty extension"
412	}
413	return s[:len(s)-1]
414}
415
416func TestMultipartMessageBodyLen(t *testing.T) {
417	for i, tt := range []struct {
418		proto int
419		in    icmp.MessageBody
420		out   int
421	}{
422		{
423			iana.ProtocolICMP,
424			&icmp.DstUnreach{
425				Data: make([]byte, ipv4.HeaderLen),
426			},
427			4 + ipv4.HeaderLen, // unused and original datagram
428		},
429		{
430			iana.ProtocolICMP,
431			&icmp.TimeExceeded{
432				Data: make([]byte, ipv4.HeaderLen),
433			},
434			4 + ipv4.HeaderLen, // unused and original datagram
435		},
436		{
437			iana.ProtocolICMP,
438			&icmp.ParamProb{
439				Data: make([]byte, ipv4.HeaderLen),
440			},
441			4 + ipv4.HeaderLen, // [pointer, unused] and original datagram
442		},
443
444		{
445			iana.ProtocolICMP,
446			&icmp.ParamProb{
447				Data: make([]byte, ipv4.HeaderLen),
448				Extensions: []icmp.Extension{
449					&icmp.MPLSLabelStack{},
450				},
451			},
452			4 + 4 + 4 + 0 + 128, // [pointer, length, unused], extension header, object header, object payload, original datagram
453		},
454		{
455			iana.ProtocolICMP,
456			&icmp.ParamProb{
457				Data: make([]byte, 128),
458				Extensions: []icmp.Extension{
459					&icmp.MPLSLabelStack{},
460				},
461			},
462			4 + 4 + 4 + 0 + 128, // [pointer, length, unused], extension header, object header, object payload and original datagram
463		},
464		{
465			iana.ProtocolICMP,
466			&icmp.ParamProb{
467				Data: make([]byte, 129),
468				Extensions: []icmp.Extension{
469					&icmp.MPLSLabelStack{},
470				},
471			},
472			4 + 4 + 4 + 0 + 132, // [pointer, length, unused], extension header, object header, object payload and original datagram
473		},
474
475		{
476			iana.ProtocolIPv6ICMP,
477			&icmp.DstUnreach{
478				Data: make([]byte, ipv6.HeaderLen),
479			},
480			4 + ipv6.HeaderLen, // unused and original datagram
481		},
482		{
483			iana.ProtocolIPv6ICMP,
484			&icmp.PacketTooBig{
485				Data: make([]byte, ipv6.HeaderLen),
486			},
487			4 + ipv6.HeaderLen, // mtu and original datagram
488		},
489		{
490			iana.ProtocolIPv6ICMP,
491			&icmp.TimeExceeded{
492				Data: make([]byte, ipv6.HeaderLen),
493			},
494			4 + ipv6.HeaderLen, // unused and original datagram
495		},
496		{
497			iana.ProtocolIPv6ICMP,
498			&icmp.ParamProb{
499				Data: make([]byte, ipv6.HeaderLen),
500			},
501			4 + ipv6.HeaderLen, // pointer and original datagram
502		},
503
504		{
505			iana.ProtocolIPv6ICMP,
506			&icmp.DstUnreach{
507				Data: make([]byte, 127),
508				Extensions: []icmp.Extension{
509					&icmp.MPLSLabelStack{},
510				},
511			},
512			4 + 4 + 4 + 0 + 128, // [length, unused], extension header, object header, object payload and original datagram
513		},
514		{
515			iana.ProtocolIPv6ICMP,
516			&icmp.DstUnreach{
517				Data: make([]byte, 128),
518				Extensions: []icmp.Extension{
519					&icmp.MPLSLabelStack{},
520				},
521			},
522			4 + 4 + 4 + 0 + 128, // [length, unused], extension header, object header, object payload and original datagram
523		},
524		{
525			iana.ProtocolIPv6ICMP,
526			&icmp.DstUnreach{
527				Data: make([]byte, 129),
528				Extensions: []icmp.Extension{
529					&icmp.MPLSLabelStack{},
530				},
531			},
532			4 + 4 + 4 + 0 + 136, // [length, unused], extension header, object header, object payload and original datagram
533		},
534
535		{
536			iana.ProtocolICMP,
537			&icmp.ExtendedEchoRequest{},
538			4, // [id, seq, l-bit]
539		},
540		{
541			iana.ProtocolICMP,
542			&icmp.ExtendedEchoRequest{
543				Extensions: []icmp.Extension{
544					&icmp.InterfaceIdent{},
545				},
546			},
547			4 + 4 + 4, // [id, seq, l-bit], extension header, object header
548		},
549		{
550			iana.ProtocolIPv6ICMP,
551			&icmp.ExtendedEchoRequest{
552				Extensions: []icmp.Extension{
553					&icmp.InterfaceIdent{
554						Type: 3,
555						AFI:  iana.AddrFamilyNSAP,
556						Addr: []byte{0x49, 0x00, 0x01, 0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0x00},
557					},
558				},
559			},
560			4 + 4 + 4 + 16, // [id, seq, l-bit], extension header, object header, object payload
561		},
562	} {
563		if out := tt.in.Len(tt.proto); out != tt.out {
564			t.Errorf("#%d: got %d; want %d", i, out, tt.out)
565		}
566	}
567}
568