Browse Source

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>
Mikio Hara 8 years ago
parent
commit
6078986fec
13 changed files with 632 additions and 98 deletions
  1. 53 3
      icmp/diag_test.go
  2. 4 4
      icmp/dstunreach.go
  3. 113 1
      icmp/echo.go
  4. 31 12
      icmp/extension.go
  5. 127 33
      icmp/extension_test.go
  6. 93 7
      icmp/interface.go
  7. 11 6
      icmp/message.go
  8. 24 0
      icmp/message_test.go
  9. 25 13
      icmp/multipart.go
  10. 142 10
      icmp/multipart_test.go
  11. 1 1
      icmp/packettoobig.go
  12. 4 4
      icmp/paramprob.go
  13. 4 4
      icmp/timeexceeded.go

+ 53 - 3
icmp/diag_test.go

@@ -71,8 +71,7 @@ func TestDiag(t *testing.T) {
 	})
 	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 @@ func TestDiag(t *testing.T) {
 			}
 		}
 	})
+	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 @@ func doDiag(dt diagTest, seq int) error {
 		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 @@ func doDiag(dt diagTest, seq int) error {
 	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 @@ func doDiag(dt diagTest, seq int) error {
 	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)
 	}
 }
 

+ 4 - 4
icmp/dstunreach.go

@@ -16,24 +16,24 @@ func (p *DstUnreach) Len(proto int) int {
 	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
 	}

+ 113 - 1
icmp/echo.go

@@ -31,7 +31,7 @@ func (p *Echo) Marshal(proto int) ([]byte, error) {
 }
 
 // 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 @@ func parseEcho(proto int, b []byte) (MessageBody, error) {
 	}
 	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
+}

+ 31 - 12
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 @@ func validExtensionHeader(b []byte) bool {
 // 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 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
 	// 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 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
 				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:]
 	}

+ 127 - 33
icmp/extension_test.go

@@ -11,10 +11,12 @@ import (
 	"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 @@ func TestMarshalAndParseExtension(t *testing.T) {
 		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)
-			}
-			if wire.err != nil {
-				continue
+		switch typ {
+		case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
+			exts, l, err := parseExtensions(typ, append(hdr, obj...), 0)
+			if err != nil {
+				return err
 			}
-			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 @@ func TestMarshalAndParseExtension(t *testing.T) {
 	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 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// MPLS label stack with no label
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -84,6 +102,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// MPLS label stack with a single label
 			{
 				proto: iana.ProtocolIPv6ICMP,
+				typ:   ipv6.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -107,6 +126,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// MPLS label stack with multiple labels
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -135,7 +155,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
 				},
 			},
 		} {
-			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 @@ func TestMarshalAndParseExtension(t *testing.T) {
 	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 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// Interface information with no attribute
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -163,6 +185,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// Interface information with ifIndex and name
 			{
 				proto: iana.ProtocolICMP,
+				typ:   ipv4.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -184,6 +207,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
 			// Interface information with ifIndex, IPAddr, name and MTU
 			{
 				proto: iana.ProtocolIPv6ICMP,
+				typ:   ipv6.ICMPTypeDestinationUnreachable,
 				hdr: []byte{
 					0x20, 0x00, 0x00, 0x00,
 				},
@@ -214,7 +238,77 @@ func TestMarshalAndParseExtension(t *testing.T) {
 				},
 			},
 		} {
-			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)
 			}
 		}

+ 93 - 7
icmp/interface.go

@@ -14,9 +14,6 @@ import (
 
 const (
 	classInterfaceInfo = 2
-
-	afiIPv4 = 1
-	afiIPv6 = 2
 )
 
 const (
@@ -127,11 +124,11 @@ func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) {
 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 @@ func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) {
 	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 @@ func parseInterfaceInfo(b []byte) (Extension, error) {
 	}
 	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
+}

+ 11 - 6
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 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) {
 	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 @@ func ParseMessage(proto int, b []byte) (*Message, error) {
 	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

+ 24 - 0
icmp/message_test.go

@@ -76,6 +76,18 @@ func TestMarshalAndParseMessage(t *testing.T) {
 						Data: []byte("HELLO-R-U-THERE"),
 					},
 				},
+				{
+					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{
@@ -120,6 +132,18 @@ func TestMarshalAndParseMessage(t *testing.T) {
 						Data: []byte("HELLO-R-U-THERE"),
 					},
 				},
+				{
+					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{

+ 25 - 13
icmp/multipart.go

@@ -10,12 +10,14 @@ import "golang.org/x/net/internal/iana"
 // 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 @@ func multipartMessageOrigDatagramLen(proto int, b []byte) int {
 // 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 @@ func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]by
 					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 @@ func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]by
 
 // 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 @@ func parseMultipartMessageBody(proto int, b []byte) ([]byte, []Extension, error)
 	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
 }

+ 142 - 10
icmp/multipart_test.go

@@ -23,17 +23,21 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
 		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 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
 			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 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
 					},
 				},
 			},
+			{
+				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 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
 					},
 				},
 			},
+			{
+				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 @@ func dumpExtensions(gotExts, wantExts []icmp.Extension) string {
 			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 @@ func TestMultipartMessageBodyLen(t *testing.T) {
 			},
 			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)

+ 1 - 1
icmp/packettoobig.go

@@ -29,7 +29,7 @@ func (p *PacketTooBig) Marshal(proto int) ([]byte, error) {
 }
 
 // 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

+ 4 - 4
icmp/paramprob.go

@@ -21,7 +21,7 @@ func (p *ParamProb) Len(proto int) int {
 	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 @@ func (p *ParamProb) Marshal(proto int) ([]byte, error) {
 		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 @@ func (p *ParamProb) Marshal(proto int) ([]byte, error) {
 }
 
 // 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 @@ func parseParamProb(proto int, b []byte) (MessageBody, error) {
 	}
 	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
 	}

+ 4 - 4
icmp/timeexceeded.go

@@ -15,23 +15,23 @@ func (p *TimeExceeded) Len(proto int) int {
 	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
 	}