icmp: add support for extended echo request and echo reply messages

This change implements support for extended echo request and reply
messages, and interface identification object that can be used to test
the status of a probed interface via a proxy interface from a probing
interface as defined in RFC 8335.

It's package user's responsibility to make a complete RFC-compliant
PROBE initiator implementation using ipv4, ipv6 and icmp packages of
x/net repository.

Fixes golang/go#24440.

Change-Id: I87ab8a7581c4d41a0c579805b0e3043e48ac85f0
Reviewed-on: https://go-review.googlesource.com/63999
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/icmp/diag_test.go b/icmp/diag_test.go
index 6a87a60..2ecd465 100644
--- a/icmp/diag_test.go
+++ b/icmp/diag_test.go
@@ -71,8 +71,7 @@
 	})
 	t.Run("Ping/Privileged", func(t *testing.T) {
 		if m, ok := nettest.SupportsRawIPSocket(); !ok {
-			t.Log(m)
-			return
+			t.Skip(m)
 		}
 		for i, dt := range []diagTest{
 			{
@@ -102,6 +101,50 @@
 			}
 		}
 	})
+	t.Run("Probe/Privileged", func(t *testing.T) {
+		if m, ok := nettest.SupportsRawIPSocket(); !ok {
+			t.Skip(m)
+		}
+		for i, dt := range []diagTest{
+			{
+				"ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
+				icmp.Message{
+					Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
+					Body: &icmp.ExtendedEchoRequest{
+						ID:    os.Getpid() & 0xffff,
+						Local: true,
+						Extensions: []icmp.Extension{
+							&icmp.InterfaceIdent{
+								Class: 3, Type: 1,
+								Name: "doesnotexist",
+							},
+						},
+					},
+				},
+			},
+
+			{
+				"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
+				icmp.Message{
+					Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
+					Body: &icmp.ExtendedEchoRequest{
+						ID:    os.Getpid() & 0xffff,
+						Local: true,
+						Extensions: []icmp.Extension{
+							&icmp.InterfaceIdent{
+								Class: 3, Type: 1,
+								Name: "doesnotexist",
+							},
+						},
+					},
+				},
+			},
+		} {
+			if err := doDiag(dt, i); err != nil {
+				t.Error(err)
+			}
+		}
+	})
 }
 
 func doDiag(dt diagTest, seq int) error {
@@ -124,6 +167,7 @@
 		f.Accept(ipv6.ICMPTypeTimeExceeded)
 		f.Accept(ipv6.ICMPTypeParameterProblem)
 		f.Accept(ipv6.ICMPTypeEchoReply)
+		f.Accept(ipv6.ICMPTypeExtendedEchoReply)
 		if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
 			return err
 		}
@@ -132,6 +176,8 @@
 	switch m := dt.m.Body.(type) {
 	case *icmp.Echo:
 		m.Seq = 1 << uint(seq)
+	case *icmp.ExtendedEchoRequest:
+		m.Seq = 1 << uint(seq)
 	}
 	wb, err := dt.m.Marshal(nil)
 	if err != nil {
@@ -159,9 +205,13 @@
 	case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
 		fallthrough
 	case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
+		fallthrough
+	case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
+		fallthrough
+	case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
 		return nil
 	default:
-		return fmt.Errorf("got %+v from %v; want echo reply", rm, peer)
+		return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
 	}
 }
 
diff --git a/icmp/dstunreach.go b/icmp/dstunreach.go
index 75db991..7464bf7 100644
--- a/icmp/dstunreach.go
+++ b/icmp/dstunreach.go
@@ -16,24 +16,24 @@
 	if p == nil {
 		return 0
 	}
-	l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
+	l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
 	return 4 + l
 }
 
 // Marshal implements the Marshal method of MessageBody interface.
 func (p *DstUnreach) Marshal(proto int) ([]byte, error) {
-	return marshalMultipartMessageBody(proto, p.Data, p.Extensions)
+	return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
 }
 
 // parseDstUnreach parses b as an ICMP destination unreachable message
 // body.
-func parseDstUnreach(proto int, b []byte) (MessageBody, error) {
+func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) {
 	if len(b) < 4 {
 		return nil, errMessageTooShort
 	}
 	p := &DstUnreach{}
 	var err error
-	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
+	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
 	if err != nil {
 		return nil, err
 	}
diff --git a/icmp/echo.go b/icmp/echo.go
index e6f15ef..c611f65 100644
--- a/icmp/echo.go
+++ b/icmp/echo.go
@@ -31,7 +31,7 @@
 }
 
 // parseEcho parses b as an ICMP echo request or reply message body.
-func parseEcho(proto int, b []byte) (MessageBody, error) {
+func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) {
 	bodyLen := len(b)
 	if bodyLen < 4 {
 		return nil, errMessageTooShort
@@ -43,3 +43,115 @@
 	}
 	return p, nil
 }
+
+// An ExtendedEchoRequest represents an ICMP extended echo request
+// message body.
+type ExtendedEchoRequest struct {
+	ID         int         // identifier
+	Seq        int         // sequence number
+	Local      bool        // must be true when identifying by name or index
+	Extensions []Extension // extensions
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *ExtendedEchoRequest) Len(proto int) int {
+	if p == nil {
+		return 0
+	}
+	l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions)
+	return 4 + l
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) {
+	b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions)
+	if err != nil {
+		return nil, err
+	}
+	bb := make([]byte, 4)
+	binary.BigEndian.PutUint16(bb[:2], uint16(p.ID))
+	bb[2] = byte(p.Seq)
+	if p.Local {
+		bb[3] |= 0x01
+	}
+	bb = append(bb, b...)
+	return bb, nil
+}
+
+// parseExtendedEchoRequest parses b as an ICMP extended echo request
+// message body.
+func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) {
+	if len(b) < 4+4 {
+		return nil, errMessageTooShort
+	}
+	p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])}
+	if b[3]&0x01 != 0 {
+		p.Local = true
+	}
+	var err error
+	_, p.Extensions, err = parseMultipartMessageBody(proto, typ, b[4:])
+	if err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+// An ExtendedEchoReply represents an ICMP extended echo reply message
+// body.
+type ExtendedEchoReply struct {
+	ID     int  // identifier
+	Seq    int  // sequence number
+	State  int  // 3-bit state working together with Message.Code
+	Active bool // probed interface is active
+	IPv4   bool // probed interface runs IPv4
+	IPv6   bool // probed interface runs IPv6
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *ExtendedEchoReply) Len(proto int) int {
+	if p == nil {
+		return 0
+	}
+	return 4
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) {
+	b := make([]byte, 4)
+	binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
+	b[2] = byte(p.Seq)
+	b[3] = byte(p.State<<5) & 0xe0
+	if p.Active {
+		b[3] |= 0x04
+	}
+	if p.IPv4 {
+		b[3] |= 0x02
+	}
+	if p.IPv6 {
+		b[3] |= 0x01
+	}
+	return b, nil
+}
+
+// parseExtendedEchoReply parses b as an ICMP extended echo reply
+// message body.
+func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) {
+	if len(b) < 4 {
+		return nil, errMessageTooShort
+	}
+	p := &ExtendedEchoReply{
+		ID:    int(binary.BigEndian.Uint16(b[:2])),
+		Seq:   int(b[2]),
+		State: int(b[3]) >> 5,
+	}
+	if b[3]&0x04 != 0 {
+		p.Active = true
+	}
+	if b[3]&0x02 != 0 {
+		p.IPv4 = true
+	}
+	if b[3]&0x01 != 0 {
+		p.IPv6 = true
+	}
+	return p, nil
+}
diff --git a/icmp/extension.go b/icmp/extension.go
index 402a751..2005068 100644
--- a/icmp/extension.go
+++ b/icmp/extension.go
@@ -4,7 +4,12 @@
 
 package icmp
 
-import "encoding/binary"
+import (
+	"encoding/binary"
+
+	"golang.org/x/net/ipv4"
+	"golang.org/x/net/ipv6"
+)
 
 // An Extension represents an ICMP extension.
 type Extension interface {
@@ -38,7 +43,7 @@
 // It will return a list of ICMP extensions and an adjusted length
 // attribute that represents the length of the padded original
 // datagram field. Otherwise, it returns an error.
-func parseExtensions(b []byte, l int) ([]Extension, int, error) {
+func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) {
 	// Still a lot of non-RFC 4884 compliant implementations are
 	// out there. Set the length attribute l to 128 when it looks
 	// inappropriate for backwards compatibility.
@@ -48,20 +53,28 @@
 	// header.
 	//
 	// See RFC 4884 for further information.
-	if 128 > l || l+8 > len(b) {
-		l = 128
-	}
-	if l+8 > len(b) {
-		return nil, -1, errNoExtension
-	}
-	if !validExtensionHeader(b[l:]) {
-		if l == 128 {
+	switch typ {
+	case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
+		if len(b) < 8 || !validExtensionHeader(b) {
 			return nil, -1, errNoExtension
 		}
-		l = 128
-		if !validExtensionHeader(b[l:]) {
+		l = 0
+	default:
+		if 128 > l || l+8 > len(b) {
+			l = 128
+		}
+		if l+8 > len(b) {
 			return nil, -1, errNoExtension
 		}
+		if !validExtensionHeader(b[l:]) {
+			if l == 128 {
+				return nil, -1, errNoExtension
+			}
+			l = 128
+			if !validExtensionHeader(b[l:]) {
+				return nil, -1, errNoExtension
+			}
+		}
 	}
 	var exts []Extension
 	for b = b[l+4:]; len(b) >= 4; {
@@ -82,6 +95,12 @@
 				return nil, -1, err
 			}
 			exts = append(exts, ext)
+		case classInterfaceIdent:
+			ext, err := parseInterfaceIdent(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 193859e..a7669da 100644
--- a/icmp/extension_test.go
+++ b/icmp/extension_test.go
@@ -11,10 +11,12 @@
 	"testing"
 
 	"golang.org/x/net/internal/iana"
+	"golang.org/x/net/ipv4"
+	"golang.org/x/net/ipv6"
 )
 
 func TestMarshalAndParseExtension(t *testing.T) {
-	fn := func(t *testing.T, proto int, hdr, obj []byte, te Extension) error {
+	fn := func(t *testing.T, proto int, typ Type, hdr, obj []byte, te Extension) error {
 		b, err := te.Marshal(proto)
 		if err != nil {
 			return err
@@ -22,39 +24,53 @@
 		if !reflect.DeepEqual(b, obj) {
 			return fmt.Errorf("got %#v; want %#v", b, obj)
 		}
-		for i, wire := range []struct {
-			data     []byte // original datagram
-			inlattr  int    // length of padded original datagram, a hint
-			outlattr int    // length of padded original datagram, a want
-			err      error
-		}{
-			{nil, 0, -1, errNoExtension},
-			{make([]byte, 127), 128, -1, errNoExtension},
-
-			{make([]byte, 128), 127, -1, errNoExtension},
-			{make([]byte, 128), 128, -1, errNoExtension},
-			{make([]byte, 128), 129, -1, errNoExtension},
-
-			{append(make([]byte, 128), append(hdr, obj...)...), 127, 128, nil},
-			{append(make([]byte, 128), append(hdr, obj...)...), 128, 128, nil},
-			{append(make([]byte, 128), append(hdr, obj...)...), 129, 128, nil},
-
-			{append(make([]byte, 512), append(hdr, obj...)...), 511, -1, errNoExtension},
-			{append(make([]byte, 512), append(hdr, obj...)...), 512, 512, nil},
-			{append(make([]byte, 512), append(hdr, obj...)...), 513, -1, errNoExtension},
-		} {
-			exts, l, err := parseExtensions(wire.data, wire.inlattr)
-			if err != wire.err {
-				return fmt.Errorf("#%d: got %v; want %v", i, err, wire.err)
+		switch typ {
+		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
+			exts, l, err := parseExtensions(typ, append(hdr, obj...), 0)
+			if err != nil {
+				return err
 			}
-			if wire.err != nil {
-				continue
-			}
-			if l != wire.outlattr {
-				return fmt.Errorf("#%d: got %d; want %d", i, l, wire.outlattr)
+			if l != 0 {
+				return fmt.Errorf("got %d; want 0", l)
 			}
 			if !reflect.DeepEqual(exts, []Extension{te}) {
-				return fmt.Errorf("#%d: got %#v; want %#v", i, exts[0], te)
+				return fmt.Errorf("got %#v; want %#v", exts[0], te)
+			}
+		default:
+			for i, wire := range []struct {
+				data     []byte // original datagram
+				inlattr  int    // length of padded original datagram, a hint
+				outlattr int    // length of padded original datagram, a want
+				err      error
+			}{
+				{nil, 0, -1, errNoExtension},
+				{make([]byte, 127), 128, -1, errNoExtension},
+
+				{make([]byte, 128), 127, -1, errNoExtension},
+				{make([]byte, 128), 128, -1, errNoExtension},
+				{make([]byte, 128), 129, -1, errNoExtension},
+
+				{append(make([]byte, 128), append(hdr, obj...)...), 127, 128, nil},
+				{append(make([]byte, 128), append(hdr, obj...)...), 128, 128, nil},
+				{append(make([]byte, 128), append(hdr, obj...)...), 129, 128, nil},
+
+				{append(make([]byte, 512), append(hdr, obj...)...), 511, -1, errNoExtension},
+				{append(make([]byte, 512), append(hdr, obj...)...), 512, 512, nil},
+				{append(make([]byte, 512), append(hdr, obj...)...), 513, -1, errNoExtension},
+			} {
+				exts, l, err := parseExtensions(typ, wire.data, wire.inlattr)
+				if err != wire.err {
+					return fmt.Errorf("#%d: got %v; want %v", i, err, wire.err)
+				}
+				if wire.err != nil {
+					continue
+				}
+				if l != wire.outlattr {
+					return fmt.Errorf("#%d: got %d; want %d", i, l, wire.outlattr)
+				}
+				if !reflect.DeepEqual(exts, []Extension{te}) {
+					return fmt.Errorf("#%d: got %#v; want %#v", i, exts[0], te)
+				}
 			}
 		}
 		return nil
@@ -63,6 +79,7 @@
 	t.Run("MPLSLabelStack", func(t *testing.T) {
 		for _, et := range []struct {
 			proto int
+			typ   Type
 			hdr   []byte
 			obj   []byte
 			ext   Extension
@@ -70,6 +87,7 @@
 			// MPLS label stack with no label
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -84,6 +102,7 @@
 			// MPLS label stack with a single label
 			{
 				proto: iana.ProtocolIPv6ICMP,
+				typ:   ipv6.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -107,6 +126,7 @@
 			// MPLS label stack with multiple labels
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -135,7 +155,7 @@
 				},
 			},
 		} {
-			if err := fn(t, et.proto, et.hdr, et.obj, et.ext); err != nil {
+			if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
 				t.Error(err)
 			}
 		}
@@ -143,6 +163,7 @@
 	t.Run("InterfaceInfo", func(t *testing.T) {
 		for _, et := range []struct {
 			proto int
+			typ   Type
 			hdr   []byte
 			obj   []byte
 			ext   Extension
@@ -150,6 +171,7 @@
 			// Interface information with no attribute
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -163,6 +185,7 @@
 			// Interface information with ifIndex and name
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -184,6 +207,7 @@
 			// Interface information with ifIndex, IPAddr, name and MTU
 			{
 				proto: iana.ProtocolIPv6ICMP,
+				typ:   ipv6.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -214,7 +238,77 @@
 				},
 			},
 		} {
-			if err := fn(t, et.proto, et.hdr, et.obj, et.ext); err != nil {
+			if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
+				t.Error(err)
+			}
+		}
+	})
+	t.Run("InterfaceIdent", func(t *testing.T) {
+		for _, et := range []struct {
+			proto int
+			typ   Type
+			hdr   []byte
+			obj   []byte
+			ext   Extension
+		}{
+			// Interface identification by name
+			{
+				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeExtendedEchoRequest,
+				hdr: []byte{
+					0x20, 0x00, 0x00, 0x00,
+				},
+				obj: []byte{
+					0x00, 0x0c, 0x03, 0x01,
+					byte('e'), byte('n'), byte('1'), byte('0'),
+					byte('1'), 0x00, 0x00, 0x00,
+				},
+				ext: &InterfaceIdent{
+					Class: classInterfaceIdent,
+					Type:  typeInterfaceByName,
+					Name:  "en101",
+				},
+			},
+			// Interface identification by index
+			{
+				proto: iana.ProtocolIPv6ICMP,
+				typ:   ipv6.ICMPTypeExtendedEchoRequest,
+				hdr: []byte{
+					0x20, 0x00, 0x00, 0x00,
+				},
+				obj: []byte{
+					0x00, 0x0c, 0x03, 0x02,
+					0x00, 0x00, 0x00, 0x00,
+					0x00, 0x00, 0x03, 0x8f,
+				},
+				ext: &InterfaceIdent{
+					Class: classInterfaceIdent,
+					Type:  typeInterfaceByIndex,
+					Index: 911,
+				},
+			},
+			// Interface identification by address
+			{
+				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeExtendedEchoRequest,
+				hdr: []byte{
+					0x20, 0x00, 0x00, 0x00,
+				},
+				obj: []byte{
+					0x00, 0x10, 0x03, 0x03,
+					byte(iana.AddrFamily48bitMAC >> 8), byte(iana.AddrFamily48bitMAC & 0x0f), 0x06, 0x00,
+					0x01, 0x23, 0x45, 0x67,
+					0x89, 0xab, 0x00, 0x00,
+				},
+				ext: &InterfaceIdent{
+					Class: classInterfaceIdent,
+					Type:  typeInterfaceByAddress,
+					AFI:   iana.AddrFamily48bitMAC,
+					Addr:  []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab},
+				},
+			},
+		} {
+			if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
 				t.Error(err)
 			}
 		}
diff --git a/icmp/interface.go b/icmp/interface.go
index 78b5b98..617f757 100644
--- a/icmp/interface.go
+++ b/icmp/interface.go
@@ -14,9 +14,6 @@
 
 const (
 	classInterfaceInfo = 2
-
-	afiIPv4 = 1
-	afiIPv6 = 2
 )
 
 const (
@@ -127,11 +124,11 @@
 func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte {
 	switch proto {
 	case iana.ProtocolICMP:
-		binary.BigEndian.PutUint16(b[:2], uint16(afiIPv4))
+		binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4))
 		copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4())
 		b = b[4+net.IPv4len:]
 	case iana.ProtocolIPv6ICMP:
-		binary.BigEndian.PutUint16(b[:2], uint16(afiIPv6))
+		binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6))
 		copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16())
 		b = b[4+net.IPv6len:]
 	}
@@ -145,14 +142,14 @@
 	afi := int(binary.BigEndian.Uint16(b[:2]))
 	b = b[4:]
 	switch afi {
-	case afiIPv4:
+	case iana.AddrFamilyIPv4:
 		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:
+	case iana.AddrFamilyIPv6:
 		if len(b) < net.IPv6len {
 			return nil, errMessageTooShort
 		}
@@ -234,3 +231,92 @@
 	}
 	return ifi, nil
 }
+
+const (
+	classInterfaceIdent    = 3
+	typeInterfaceByName    = 1
+	typeInterfaceByIndex   = 2
+	typeInterfaceByAddress = 3
+)
+
+// An InterfaceIdent represents interface identification.
+type InterfaceIdent struct {
+	Class int    // extension object class number
+	Type  int    // extension object sub-type
+	Name  string // interface name
+	Index int    // interface index
+	AFI   int    // address family identifier; see address family numbers in IANA registry
+	Addr  []byte // address
+}
+
+// Len implements the Len method of Extension interface.
+func (ifi *InterfaceIdent) Len(_ int) int {
+	switch ifi.Type {
+	case typeInterfaceByName:
+		l := len(ifi.Name)
+		if l > 255 {
+			l = 255
+		}
+		return 4 + (l+3)&^3
+	case typeInterfaceByIndex:
+		return 4 + 8
+	case typeInterfaceByAddress:
+		return 4 + 4 + (len(ifi.Addr)+3)&^3
+	default:
+		return 4
+	}
+}
+
+// Marshal implements the Marshal method of Extension interface.
+func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) {
+	b := make([]byte, ifi.Len(proto))
+	if err := ifi.marshal(proto, b); err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func (ifi *InterfaceIdent) marshal(proto int, b []byte) error {
+	l := ifi.Len(proto)
+	binary.BigEndian.PutUint16(b[:2], uint16(l))
+	b[2], b[3] = classInterfaceIdent, byte(ifi.Type)
+	switch ifi.Type {
+	case typeInterfaceByName:
+		copy(b[4:], ifi.Name)
+	case typeInterfaceByIndex:
+		binary.BigEndian.PutUint64(b[4:4+8], uint64(ifi.Index))
+	case typeInterfaceByAddress:
+		binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI))
+		b[4+2] = byte(len(ifi.Addr))
+		copy(b[4+4:], ifi.Addr)
+	}
+	return nil
+}
+
+func parseInterfaceIdent(b []byte) (Extension, error) {
+	ifi := &InterfaceIdent{
+		Class: int(b[2]),
+		Type:  int(b[3]),
+	}
+	switch ifi.Type {
+	case typeInterfaceByName:
+		ifi.Name = strings.Trim(string(b[4:]), string(0))
+	case typeInterfaceByIndex:
+		if len(b[4:]) < 8 {
+			return nil, errInvalidExtension
+		}
+		ifi.Index = int(binary.BigEndian.Uint64(b[4 : 4+8]))
+	case typeInterfaceByAddress:
+		if len(b[4:]) < 4 {
+			return nil, errInvalidExtension
+		}
+		ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2]))
+		l := int(b[4+2])
+		if len(b[4+4:]) < l {
+			return nil, errInvalidExtension
+		}
+		ifi.Addr = make([]byte, l)
+		copy(ifi.Addr, b[4+4:])
+	}
+	return ifi, nil
+}
diff --git a/icmp/message.go b/icmp/message.go
index 81140b0..46fe95a 100644
--- a/icmp/message.go
+++ b/icmp/message.go
@@ -11,6 +11,7 @@
 // ICMP extensions for MPLS are defined in RFC 4950.
 // ICMP extensions for interface and next-hop identification are
 // defined in RFC 5837.
+// PROBE: A utility for probing interfaces is defined in RFC 8335.
 package icmp // import "golang.org/x/net/icmp"
 
 import (
@@ -107,21 +108,25 @@
 	return b[len(psh):], nil
 }
 
-var parseFns = map[Type]func(int, []byte) (MessageBody, error){
+var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){
 	ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
 	ipv4.ICMPTypeTimeExceeded:           parseTimeExceeded,
 	ipv4.ICMPTypeParameterProblem:       parseParamProb,
 
-	ipv4.ICMPTypeEcho:      parseEcho,
-	ipv4.ICMPTypeEchoReply: parseEcho,
+	ipv4.ICMPTypeEcho:                parseEcho,
+	ipv4.ICMPTypeEchoReply:           parseEcho,
+	ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
+	ipv4.ICMPTypeExtendedEchoReply:   parseExtendedEchoReply,
 
 	ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
 	ipv6.ICMPTypePacketTooBig:           parsePacketTooBig,
 	ipv6.ICMPTypeTimeExceeded:           parseTimeExceeded,
 	ipv6.ICMPTypeParameterProblem:       parseParamProb,
 
-	ipv6.ICMPTypeEchoRequest: parseEcho,
-	ipv6.ICMPTypeEchoReply:   parseEcho,
+	ipv6.ICMPTypeEchoRequest:         parseEcho,
+	ipv6.ICMPTypeEchoReply:           parseEcho,
+	ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
+	ipv6.ICMPTypeExtendedEchoReply:   parseExtendedEchoReply,
 }
 
 // ParseMessage parses b as an ICMP message.
@@ -143,7 +148,7 @@
 	if fn, ok := parseFns[m.Type]; !ok {
 		m.Body, err = parseDefaultMessageBody(proto, b[4:])
 	} else {
-		m.Body, err = fn(proto, b[4:])
+		m.Body, err = fn(proto, m.Type, b[4:])
 	}
 	if err != nil {
 		return nil, err
diff --git a/icmp/message_test.go b/icmp/message_test.go
index 95f6c37..c278b8b 100644
--- a/icmp/message_test.go
+++ b/icmp/message_test.go
@@ -77,6 +77,18 @@
 					},
 				},
 				{
+					Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
+					Body: &icmp.ExtendedEchoRequest{
+						ID: 1, Seq: 2,
+					},
+				},
+				{
+					Type: ipv4.ICMPTypeExtendedEchoReply, Code: 0,
+					Body: &icmp.ExtendedEchoReply{
+						State: 4 /* Delay */, Active: true, IPv4: true,
+					},
+				},
+				{
 					Type: ipv4.ICMPTypePhoturis,
 					Body: &icmp.DefaultMessageBody{
 						Data: []byte{0x80, 0x40, 0x20, 0x10},
@@ -121,6 +133,18 @@
 					},
 				},
 				{
+					Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
+					Body: &icmp.ExtendedEchoRequest{
+						ID: 1, Seq: 2,
+					},
+				},
+				{
+					Type: ipv6.ICMPTypeExtendedEchoReply, Code: 0,
+					Body: &icmp.ExtendedEchoReply{
+						State: 5 /* Probe */, Active: true, IPv6: true,
+					},
+				},
+				{
 					Type: ipv6.ICMPTypeDuplicateAddressConfirmation,
 					Body: &icmp.DefaultMessageBody{
 						Data: []byte{0x80, 0x40, 0x20, 0x10},
diff --git a/icmp/multipart.go b/icmp/multipart.go
index f271356..9ebbbaf 100644
--- a/icmp/multipart.go
+++ b/icmp/multipart.go
@@ -10,12 +10,14 @@
 // exts as extensions, and returns a required length for message body
 // and a required length for a padded original datagram in wire
 // format.
-func multipartMessageBodyDataLen(proto int, b []byte, exts []Extension) (bodyLen, dataLen int) {
+func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) {
 	for _, ext := range exts {
 		bodyLen += ext.Len(proto)
 	}
 	if bodyLen > 0 {
-		dataLen = multipartMessageOrigDatagramLen(proto, b)
+		if withOrigDgram {
+			dataLen = multipartMessageOrigDatagramLen(proto, b)
+		}
 		bodyLen += 4 // length of extension header
 	} else {
 		dataLen = len(b)
@@ -50,8 +52,8 @@
 // marshalMultipartMessageBody takes data as an original datagram and
 // exts as extesnsions, and returns a binary encoding of message body.
 // It can be used for non-multipart message bodies when exts is nil.
-func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]byte, error) {
-	bodyLen, dataLen := multipartMessageBodyDataLen(proto, data, exts)
+func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) {
+	bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts)
 	b := make([]byte, 4+bodyLen)
 	copy(b[4:], data)
 	off := dataLen + 4
@@ -71,16 +73,23 @@
 					return nil, err
 				}
 				off += ext.Len(proto)
+			case *InterfaceIdent:
+				if err := ext.marshal(proto, b[off:]); err != nil {
+					return nil, err
+				}
+				off += ext.Len(proto)
 			}
 		}
 		s := checksum(b[dataLen+4:])
 		b[dataLen+4+2] ^= byte(s)
 		b[dataLen+4+3] ^= byte(s >> 8)
-		switch proto {
-		case iana.ProtocolICMP:
-			b[1] = byte(dataLen / 4)
-		case iana.ProtocolIPv6ICMP:
-			b[0] = byte(dataLen / 8)
+		if withOrigDgram {
+			switch proto {
+			case iana.ProtocolICMP:
+				b[1] = byte(dataLen / 4)
+			case iana.ProtocolIPv6ICMP:
+				b[0] = byte(dataLen / 8)
+			}
 		}
 	}
 	return b, nil
@@ -88,7 +97,7 @@
 
 // parseMultipartMessageBody parses b as either a non-multipart
 // message body or a multipart message body.
-func parseMultipartMessageBody(proto int, b []byte) ([]byte, []Extension, error) {
+func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) {
 	var l int
 	switch proto {
 	case iana.ProtocolICMP:
@@ -99,11 +108,14 @@
 	if len(b) == 4 {
 		return nil, nil, nil
 	}
-	exts, l, err := parseExtensions(b[4:], l)
+	exts, l, err := parseExtensions(typ, b[4:], l)
 	if err != nil {
 		l = len(b) - 4
 	}
-	data := make([]byte, l)
-	copy(data, b[4:])
+	var data []byte
+	if l > 0 {
+		data = make([]byte, l)
+		copy(data, b[4:])
+	}
 	return data, exts, nil
 }
diff --git a/icmp/multipart_test.go b/icmp/multipart_test.go
index 611fecf..7440882 100644
--- a/icmp/multipart_test.go
+++ b/icmp/multipart_test.go
@@ -23,17 +23,21 @@
 		if err != nil {
 			return err
 		}
-		switch proto {
-		case iana.ProtocolICMP:
-			if b[5] != 32 {
-				return fmt.Errorf("got %d; want 32", b[5])
-			}
-		case iana.ProtocolIPv6ICMP:
-			if b[4] != 16 {
-				return fmt.Errorf("got %d; want 16", b[4])
-			}
+		switch tm.Type {
+		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
 		default:
-			return fmt.Errorf("unknown protocol: %d", proto)
+			switch proto {
+			case iana.ProtocolICMP:
+				if b[5] != 32 {
+					return fmt.Errorf("got %d; want 32", b[5])
+				}
+			case iana.ProtocolIPv6ICMP:
+				if b[4] != 16 {
+					return fmt.Errorf("got %d; want 16", b[4])
+				}
+			default:
+				return fmt.Errorf("unknown protocol: %d", proto)
+			}
 		}
 		m, err := icmp.ParseMessage(proto, b)
 		if err != nil {
@@ -43,6 +47,11 @@
 			return fmt.Errorf("got %v; want %v", m, &tm)
 		}
 		switch m.Type {
+		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
+			got, want := m.Body.(*icmp.ExtendedEchoRequest), tm.Body.(*icmp.ExtendedEchoRequest)
+			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
+				return errors.New(dumpExtensions(got.Extensions, want.Extensions))
+			}
 		case ipv4.ICMPTypeDestinationUnreachable:
 			got, want := m.Body.(*icmp.DstUnreach), tm.Body.(*icmp.DstUnreach)
 			if !reflect.DeepEqual(got.Extensions, want.Extensions) {
@@ -200,6 +209,51 @@
 					},
 				},
 			},
+			{
+				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2, Local: true,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  1,
+							Name:  "en101",
+						},
+					},
+				},
+			},
+			{
+				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2, Local: true,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  2,
+							Index: 911,
+						},
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  1,
+							Name:  "en101",
+						},
+					},
+				},
+			},
+			{
+				Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  3,
+							AFI:   iana.AddrFamily48bitMAC,
+							Addr:  []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab},
+						},
+					},
+				},
+			},
 		} {
 			if err := fn(t, iana.ProtocolICMP, tm); err != nil {
 				t.Errorf("#%d: %v", i, err)
@@ -287,6 +341,51 @@
 					},
 				},
 			},
+			{
+				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2, Local: true,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  1,
+							Name:  "en101",
+						},
+					},
+				},
+			},
+			{
+				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2, Local: true,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  1,
+							Name:  "en101",
+						},
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  2,
+							Index: 911,
+						},
+					},
+				},
+			},
+			{
+				Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
+				Body: &icmp.ExtendedEchoRequest{
+					ID: 1, Seq: 2,
+					Extensions: []icmp.Extension{
+						&icmp.InterfaceIdent{
+							Class: 3,
+							Type:  3,
+							AFI:   iana.AddrFamilyIPv4,
+							Addr:  []byte{192, 0, 2, 1},
+						},
+					},
+				},
+			},
 		} {
 			if err := fn(t, iana.ProtocolIPv6ICMP, tm); err != nil {
 				t.Errorf("#%d: %v", i, err)
@@ -309,6 +408,11 @@
 			if !reflect.DeepEqual(got, want) {
 				s += fmt.Sprintf("#%d: got %#v, %#v, %#v; want %#v, %#v, %#v\n", i, got, got.Interface, got.Addr, want, want.Interface, want.Addr)
 			}
+		case *icmp.InterfaceIdent:
+			want := wantExts[i].(*icmp.InterfaceIdent)
+			if !reflect.DeepEqual(got, want) {
+				s += fmt.Sprintf("#%d: got %#v; want %#v\n", i, got, want)
+			}
 		}
 	}
 	if len(s) == 0 {
@@ -435,6 +539,34 @@
 			},
 			4 + 4 + 4 + 0 + 136, // [length, unused], extension header, object header, object payload and original datagram
 		},
+
+		{
+			iana.ProtocolICMP,
+			&icmp.ExtendedEchoRequest{},
+			4, // [id, seq, l-bit]
+		},
+		{
+			iana.ProtocolICMP,
+			&icmp.ExtendedEchoRequest{
+				Extensions: []icmp.Extension{
+					&icmp.InterfaceIdent{},
+				},
+			},
+			4 + 4 + 4, // [id, seq, l-bit], extension header, object header
+		},
+		{
+			iana.ProtocolIPv6ICMP,
+			&icmp.ExtendedEchoRequest{
+				Extensions: []icmp.Extension{
+					&icmp.InterfaceIdent{
+						Type: 3,
+						AFI:  iana.AddrFamilyNSAP,
+						Addr: []byte{0x49, 0x00, 0x01, 0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0x00},
+					},
+				},
+			},
+			4 + 4 + 4 + 16, // [id, seq, l-bit], extension header, object header, object payload
+		},
 	} {
 		if out := tt.in.Len(tt.proto); out != tt.out {
 			t.Errorf("#%d: got %d; want %d", i, out, tt.out)
diff --git a/icmp/packettoobig.go b/icmp/packettoobig.go
index a1c9df7..afbf24f 100644
--- a/icmp/packettoobig.go
+++ b/icmp/packettoobig.go
@@ -29,7 +29,7 @@
 }
 
 // parsePacketTooBig parses b as an ICMP packet too big message body.
-func parsePacketTooBig(proto int, b []byte) (MessageBody, error) {
+func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) {
 	bodyLen := len(b)
 	if bodyLen < 4 {
 		return nil, errMessageTooShort
diff --git a/icmp/paramprob.go b/icmp/paramprob.go
index 0a2548d..8587255 100644
--- a/icmp/paramprob.go
+++ b/icmp/paramprob.go
@@ -21,7 +21,7 @@
 	if p == nil {
 		return 0
 	}
-	l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
+	l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
 	return 4 + l
 }
 
@@ -33,7 +33,7 @@
 		copy(b[4:], p.Data)
 		return b, nil
 	}
-	b, err := marshalMultipartMessageBody(proto, p.Data, p.Extensions)
+	b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
 	if err != nil {
 		return nil, err
 	}
@@ -42,7 +42,7 @@
 }
 
 // parseParamProb parses b as an ICMP parameter problem message body.
-func parseParamProb(proto int, b []byte) (MessageBody, error) {
+func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) {
 	if len(b) < 4 {
 		return nil, errMessageTooShort
 	}
@@ -55,7 +55,7 @@
 	}
 	p.Pointer = uintptr(b[0])
 	var err error
-	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
+	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
 	if err != nil {
 		return nil, err
 	}
diff --git a/icmp/timeexceeded.go b/icmp/timeexceeded.go
index 344e158..14e9e23 100644
--- a/icmp/timeexceeded.go
+++ b/icmp/timeexceeded.go
@@ -15,23 +15,23 @@
 	if p == nil {
 		return 0
 	}
-	l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
+	l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
 	return 4 + l
 }
 
 // Marshal implements the Marshal method of MessageBody interface.
 func (p *TimeExceeded) Marshal(proto int) ([]byte, error) {
-	return marshalMultipartMessageBody(proto, p.Data, p.Extensions)
+	return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
 }
 
 // parseTimeExceeded parses b as an ICMP time exceeded message body.
-func parseTimeExceeded(proto int, b []byte) (MessageBody, error) {
+func parseTimeExceeded(proto int, typ Type, b []byte) (MessageBody, error) {
 	if len(b) < 4 {
 		return nil, errMessageTooShort
 	}
 	p := &TimeExceeded{}
 	var err error
-	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
+	p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
 	if err != nil {
 		return nil, err
 	}