icmp: add extensions for interface and next-hop identification

This change implements ICMP extensions for interface and next-hop
identification which are used for route trace applications as described
in RFC 5837.

Change-Id: I2435109b5e766e743b894b0280a537324975489d
Reviewed-on: https://go-review.googlesource.com/3112
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/icmp/extension.go b/icmp/extension.go
index 37af0e1..720e167 100644
--- a/icmp/extension.go
+++ b/icmp/extension.go
@@ -74,6 +74,12 @@
 				return nil, -1, err
 			}
 			exts = append(exts, ext)
+		case classInterfaceInfo:
+			ext, err := parseInterfaceInfo(b[:ol])
+			if err != nil {
+				return nil, -1, err
+			}
+			exts = append(exts, ext)
 		}
 		b = b[ol:]
 	}
diff --git a/icmp/extension_test.go b/icmp/extension_test.go
index 6ed9e70..488c36b 100644
--- a/icmp/extension_test.go
+++ b/icmp/extension_test.go
@@ -5,6 +5,7 @@
 package icmp
 
 import (
+	"net"
 	"reflect"
 	"testing"
 
@@ -90,6 +91,78 @@
 			},
 		},
 	},
+	// Interface information with no attribute
+	{
+		proto: iana.ProtocolICMP,
+		hdr: []byte{
+			0x20, 0x00, 0x00, 0x00,
+		},
+		obj: []byte{
+			0x00, 0x04, 0x02, 0x00,
+		},
+		exts: []Extension{
+			&InterfaceInfo{
+				Class: classInterfaceInfo,
+			},
+		},
+	},
+	// Interface information with ifIndex and name
+	{
+		proto: iana.ProtocolICMP,
+		hdr: []byte{
+			0x20, 0x00, 0x00, 0x00,
+		},
+		obj: []byte{
+			0x00, 0x10, 0x02, 0x0a,
+			0x00, 0x00, 0x00, 0x10,
+			0x08, byte('e'), byte('n'), byte('1'),
+			byte('0'), byte('1'), 0x00, 0x00,
+		},
+		exts: []Extension{
+			&InterfaceInfo{
+				Class: classInterfaceInfo,
+				Type:  0x0a,
+				Interface: &net.Interface{
+					Index: 16,
+					Name:  "en101",
+				},
+			},
+		},
+	},
+	// Interface information with ifIndex, IPAddr, name and MTU
+	{
+		proto: iana.ProtocolIPv6ICMP,
+		hdr: []byte{
+			0x20, 0x00, 0x00, 0x00,
+		},
+		obj: []byte{
+			0x00, 0x28, 0x02, 0x0f,
+			0x00, 0x00, 0x00, 0x0f,
+			0x00, 0x02, 0x00, 0x00,
+			0xfe, 0x80, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x01,
+			0x08, byte('e'), byte('n'), byte('1'),
+			byte('0'), byte('1'), 0x00, 0x00,
+			0x00, 0x00, 0x20, 0x00,
+		},
+		exts: []Extension{
+			&InterfaceInfo{
+				Class: classInterfaceInfo,
+				Type:  0x0f,
+				Interface: &net.Interface{
+					Index: 15,
+					Name:  "en101",
+					MTU:   8192,
+				},
+				Addr: &net.IPAddr{
+					IP:   net.ParseIP("fe80::1"),
+					Zone: "en101",
+				},
+			},
+		},
+	},
 }
 
 func TestMarshalAndParseExtension(t *testing.T) {
@@ -104,6 +177,12 @@
 					t.Errorf("#%v/%v: %v", i, j, err)
 					continue
 				}
+			case *InterfaceInfo:
+				b, err = ext.Marshal(tt.proto)
+				if err != nil {
+					t.Errorf("#%v/%v: %v", i, j, err)
+					continue
+				}
 			}
 			if !reflect.DeepEqual(b, tt.obj) {
 				t.Errorf("#%v/%v: got %#v; want %#v", i, j, b, tt.obj)
@@ -149,6 +228,9 @@
 					case *MPLSLabelStack:
 						want := tt.exts[j].(*MPLSLabelStack)
 						t.Errorf("#%v/%v: got %#v; want %#v", i, j, ext, want)
+					case *InterfaceInfo:
+						want := tt.exts[j].(*InterfaceInfo)
+						t.Errorf("#%v/%v: got %#v; want %#v", i, j, ext, want)
 					}
 				}
 				continue
diff --git a/icmp/interface.go b/icmp/interface.go
new file mode 100644
index 0000000..806b26b
--- /dev/null
+++ b/icmp/interface.go
@@ -0,0 +1,232 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package icmp
+
+import (
+	"net"
+	"strings"
+
+	"golang.org/x/net/internal/iana"
+)
+
+const (
+	classInterfaceInfo = 2
+
+	afiIPv4 = 1
+	afiIPv6 = 2
+)
+
+const (
+	attrMTU = 1 << iota
+	attrName
+	attrIPAddr
+	attrIfIndex
+)
+
+// An InterfaceInfo represents interface and next-hop identification.
+type InterfaceInfo struct {
+	Class     int // extension object class number
+	Type      int // extension object sub-type
+	Interface *net.Interface
+	Addr      *net.IPAddr
+}
+
+func (ifi *InterfaceInfo) nameLen() int {
+	if len(ifi.Interface.Name) > 63 {
+		return 64
+	}
+	l := 1 + len(ifi.Interface.Name)
+	return (l + 3) &^ 3
+}
+
+func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) {
+	l = 4
+	if ifi.Interface != nil && ifi.Interface.Index > 0 {
+		attrs |= attrIfIndex
+		l += 4
+		if len(ifi.Interface.Name) > 0 {
+			attrs |= attrName
+			l += ifi.nameLen()
+		}
+		if ifi.Interface.MTU > 0 {
+			attrs |= attrMTU
+			l += 4
+		}
+	}
+	if ifi.Addr != nil {
+		switch proto {
+		case iana.ProtocolICMP:
+			if ifi.Addr.IP.To4() != nil {
+				attrs |= attrIPAddr
+				l += 4 + net.IPv4len
+			}
+		case iana.ProtocolIPv6ICMP:
+			if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
+				attrs |= attrIPAddr
+				l += 4 + net.IPv6len
+			}
+		}
+	}
+	return
+}
+
+// Len implements the Len method of Extension interface.
+func (ifi *InterfaceInfo) Len(proto int) int {
+	_, l := ifi.attrsAndLen(proto)
+	return l
+}
+
+// Marshal implements the Marshal method of Extension interface.
+func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) {
+	attrs, l := ifi.attrsAndLen(proto)
+	b := make([]byte, l)
+	if err := ifi.marshal(proto, b, attrs, l); err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error {
+	b[0], b[1] = byte(l>>8), byte(l)
+	b[2], b[3] = classInterfaceInfo, byte(ifi.Type)
+	for b = b[4:]; len(b) > 0 && attrs != 0; {
+		switch {
+		case attrs&attrIfIndex != 0:
+			b = ifi.marshalIfIndex(proto, b)
+			attrs &^= attrIfIndex
+		case attrs&attrIPAddr != 0:
+			b = ifi.marshalIPAddr(proto, b)
+			attrs &^= attrIPAddr
+		case attrs&attrName != 0:
+			b = ifi.marshalName(proto, b)
+			attrs &^= attrName
+		case attrs&attrMTU != 0:
+			b = ifi.marshalMTU(proto, b)
+			attrs &^= attrMTU
+		}
+	}
+	return nil
+}
+
+func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte {
+	b[0], b[1], b[2], b[3] = byte(ifi.Interface.Index>>24), byte(ifi.Interface.Index>>16), byte(ifi.Interface.Index>>8), byte(ifi.Interface.Index)
+	return b[4:]
+}
+
+func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) {
+	if len(b) < 4 {
+		return nil, errMessageTooShort
+	}
+	ifi.Interface.Index = int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
+	return b[4:], nil
+}
+
+func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte {
+	switch proto {
+	case iana.ProtocolICMP:
+		b[0], b[1] = byte(afiIPv4>>8), byte(afiIPv4)
+		copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4())
+		b = b[4+net.IPv4len:]
+	case iana.ProtocolIPv6ICMP:
+		b[0], b[1] = byte(afiIPv6>>8), byte(afiIPv6)
+		copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16())
+		b = b[4+net.IPv6len:]
+	}
+	return b
+}
+
+func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) {
+	if len(b) < 4 {
+		return nil, errMessageTooShort
+	}
+	afi := int(b[0])<<8 | int(b[1])
+	b = b[4:]
+	switch afi {
+	case afiIPv4:
+		if len(b) < net.IPv4len {
+			return nil, errMessageTooShort
+		}
+		ifi.Addr.IP = make(net.IP, net.IPv4len)
+		copy(ifi.Addr.IP, b[:net.IPv4len])
+		b = b[net.IPv4len:]
+	case afiIPv6:
+		if len(b) < net.IPv6len {
+			return nil, errMessageTooShort
+		}
+		ifi.Addr.IP = make(net.IP, net.IPv6len)
+		copy(ifi.Addr.IP, b[:net.IPv6len])
+		b = b[net.IPv6len:]
+	}
+	return b, nil
+}
+
+func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte {
+	l := byte(ifi.nameLen())
+	b[0] = l
+	copy(b[1:], []byte(ifi.Interface.Name))
+	return b[l:]
+}
+
+func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) {
+	if 4 > len(b) || len(b) < int(b[0]) {
+		return nil, errMessageTooShort
+	}
+	l := int(b[0])
+	var name [63]byte
+	copy(name[:], b[1:l])
+	ifi.Interface.Name = strings.Trim(string(name[:]), "\000")
+	return b[l:], nil
+}
+
+func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte {
+	b[0], b[1], b[2], b[3] = byte(ifi.Interface.MTU>>24), byte(ifi.Interface.MTU>>16), byte(ifi.Interface.MTU>>8), byte(ifi.Interface.MTU)
+	return b[4:]
+}
+
+func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) {
+	if len(b) < 4 {
+		return nil, errMessageTooShort
+	}
+	ifi.Interface.MTU = int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
+	return b[4:], nil
+}
+
+func parseInterfaceInfo(b []byte) (Extension, error) {
+	ifi := &InterfaceInfo{
+		Class: int(b[2]),
+		Type:  int(b[3]),
+	}
+	if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 {
+		ifi.Interface = &net.Interface{}
+	}
+	if ifi.Type&attrIPAddr != 0 {
+		ifi.Addr = &net.IPAddr{}
+	}
+	attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU)
+	for b = b[4:]; len(b) > 0 && attrs != 0; {
+		var err error
+		switch {
+		case attrs&attrIfIndex != 0:
+			b, err = ifi.parseIfIndex(b)
+			attrs &^= attrIfIndex
+		case attrs&attrIPAddr != 0:
+			b, err = ifi.parseIPAddr(b)
+			attrs &^= attrIPAddr
+		case attrs&attrName != 0:
+			b, err = ifi.parseName(b)
+			attrs &^= attrName
+		case attrs&attrMTU != 0:
+			b, err = ifi.parseMTU(b)
+			attrs &^= attrMTU
+		}
+		if err != nil {
+			return nil, err
+		}
+	}
+	if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
+		ifi.Addr.Zone = ifi.Interface.Name
+	}
+	return ifi, nil
+}
diff --git a/icmp/message.go b/icmp/message.go
index 13b57f7..654c519 100644
--- a/icmp/message.go
+++ b/icmp/message.go
@@ -9,6 +9,8 @@
 // ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443.
 // Multi-part message support for ICMP is defined in RFC 4884.
 // ICMP extensions for MPLS are defined in RFC 4950.
+// ICMP extensions for interface and next-hop identification are
+// defined in RFC 5837.
 package icmp // import "golang.org/x/net/icmp"
 
 import (
diff --git a/icmp/multipart.go b/icmp/multipart.go
index 3f89a76..54ac8bc 100644
--- a/icmp/multipart.go
+++ b/icmp/multipart.go
@@ -65,6 +65,12 @@
 					return nil, err
 				}
 				off += ext.Len(proto)
+			case *InterfaceInfo:
+				attrs, l := ext.attrsAndLen(proto)
+				if err := ext.marshal(proto, b[off:], attrs, l); err != nil {
+					return nil, err
+				}
+				off += ext.Len(proto)
 			}
 		}
 		s := checksum(b[dataLen+4:])
diff --git a/icmp/multipart_test.go b/icmp/multipart_test.go
index 505db86..9248e47 100644
--- a/icmp/multipart_test.go
+++ b/icmp/multipart_test.go
@@ -34,6 +34,18 @@
 						},
 					},
 				},
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x0f,
+					Interface: &net.Interface{
+						Index: 15,
+						Name:  "en101",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP: net.IPv4(192, 168, 0, 1).To4(),
+					},
+				},
 			},
 		},
 	},
@@ -42,6 +54,18 @@
 		Body: &icmp.TimeExceeded{
 			Data: []byte("ERROR-INVOKING-PACKET"),
 			Extensions: []icmp.Extension{
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x0f,
+					Interface: &net.Interface{
+						Index: 15,
+						Name:  "en101",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP: net.IPv4(192, 168, 0, 1).To4(),
+					},
+				},
 				&icmp.MPLSLabelStack{
 					Class: 1,
 					Type:  1,
@@ -75,6 +99,30 @@
 						},
 					},
 				},
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x0f,
+					Interface: &net.Interface{
+						Index: 15,
+						Name:  "en101",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP: net.IPv4(192, 168, 0, 1).To4(),
+					},
+				},
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x2f,
+					Interface: &net.Interface{
+						Index: 16,
+						Name:  "en102",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP: net.IPv4(192, 168, 0, 2).To4(),
+					},
+				},
 			},
 		},
 	},
@@ -100,7 +148,7 @@
 		case ipv4.ICMPTypeDestinationUnreachable:
 			got, want := m.Body.(*icmp.DstUnreach), tt.Body.(*icmp.DstUnreach)
 			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
-				t.Errorf("#%v: got %#v; want %#v", i, got.Extensions, want.Extensions)
+				t.Error(dumpExtensions(i, got.Extensions, want.Extensions))
 			}
 			if len(got.Data) != 128 {
 				t.Errorf("#%v: got %v; want 128", i, len(got.Data))
@@ -143,6 +191,19 @@
 						},
 					},
 				},
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x0f,
+					Interface: &net.Interface{
+						Index: 15,
+						Name:  "en101",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP:   net.ParseIP("fe80::1"),
+						Zone: "en101",
+					},
+				},
 			},
 		},
 	},
@@ -151,6 +212,19 @@
 		Body: &icmp.TimeExceeded{
 			Data: []byte("ERROR-INVOKING-PACKET"),
 			Extensions: []icmp.Extension{
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x0f,
+					Interface: &net.Interface{
+						Index: 15,
+						Name:  "en101",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP:   net.ParseIP("fe80::1"),
+						Zone: "en101",
+					},
+				},
 				&icmp.MPLSLabelStack{
 					Class: 1,
 					Type:  1,
@@ -163,6 +237,19 @@
 						},
 					},
 				},
+				&icmp.InterfaceInfo{
+					Class: 2,
+					Type:  0x2f,
+					Interface: &net.Interface{
+						Index: 16,
+						Name:  "en102",
+						MTU:   8192,
+					},
+					Addr: &net.IPAddr{
+						IP:   net.ParseIP("fe80::1"),
+						Zone: "en102",
+					},
+				},
 			},
 		},
 	},
@@ -217,6 +304,11 @@
 			if !reflect.DeepEqual(got, want) {
 				s += fmt.Sprintf("#%v/%v: got %#v; want %#v\n", i, j, got, want)
 			}
+		case *icmp.InterfaceInfo:
+			want := wantExts[j].(*icmp.InterfaceInfo)
+			if !reflect.DeepEqual(got, want) {
+				s += fmt.Sprintf("#%v/%v: got %#v, %#v, %#v; want %#v, %#v, %#v\n", i, j, got, got.Interface, got.Addr, want, want.Interface, want.Addr)
+			}
 		}
 	}
 	return s[:len(s)-1]