Skip to content

Commit 6078986

Browse files
committed
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 <[email protected]>
1 parent 92b859f commit 6078986

13 files changed

+632
-98
lines changed

icmp/diag_test.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ func TestDiag(t *testing.T) {
7171
})
7272
t.Run("Ping/Privileged", func(t *testing.T) {
7373
if m, ok := nettest.SupportsRawIPSocket(); !ok {
74-
t.Log(m)
75-
return
74+
t.Skip(m)
7675
}
7776
for i, dt := range []diagTest{
7877
{
@@ -102,6 +101,50 @@ func TestDiag(t *testing.T) {
102101
}
103102
}
104103
})
104+
t.Run("Probe/Privileged", func(t *testing.T) {
105+
if m, ok := nettest.SupportsRawIPSocket(); !ok {
106+
t.Skip(m)
107+
}
108+
for i, dt := range []diagTest{
109+
{
110+
"ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
111+
icmp.Message{
112+
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
113+
Body: &icmp.ExtendedEchoRequest{
114+
ID: os.Getpid() & 0xffff,
115+
Local: true,
116+
Extensions: []icmp.Extension{
117+
&icmp.InterfaceIdent{
118+
Class: 3, Type: 1,
119+
Name: "doesnotexist",
120+
},
121+
},
122+
},
123+
},
124+
},
125+
126+
{
127+
"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
128+
icmp.Message{
129+
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
130+
Body: &icmp.ExtendedEchoRequest{
131+
ID: os.Getpid() & 0xffff,
132+
Local: true,
133+
Extensions: []icmp.Extension{
134+
&icmp.InterfaceIdent{
135+
Class: 3, Type: 1,
136+
Name: "doesnotexist",
137+
},
138+
},
139+
},
140+
},
141+
},
142+
} {
143+
if err := doDiag(dt, i); err != nil {
144+
t.Error(err)
145+
}
146+
}
147+
})
105148
}
106149

107150
func doDiag(dt diagTest, seq int) error {
@@ -124,6 +167,7 @@ func doDiag(dt diagTest, seq int) error {
124167
f.Accept(ipv6.ICMPTypeTimeExceeded)
125168
f.Accept(ipv6.ICMPTypeParameterProblem)
126169
f.Accept(ipv6.ICMPTypeEchoReply)
170+
f.Accept(ipv6.ICMPTypeExtendedEchoReply)
127171
if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
128172
return err
129173
}
@@ -132,6 +176,8 @@ func doDiag(dt diagTest, seq int) error {
132176
switch m := dt.m.Body.(type) {
133177
case *icmp.Echo:
134178
m.Seq = 1 << uint(seq)
179+
case *icmp.ExtendedEchoRequest:
180+
m.Seq = 1 << uint(seq)
135181
}
136182
wb, err := dt.m.Marshal(nil)
137183
if err != nil {
@@ -159,9 +205,13 @@ func doDiag(dt diagTest, seq int) error {
159205
case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
160206
fallthrough
161207
case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
208+
fallthrough
209+
case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
210+
fallthrough
211+
case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
162212
return nil
163213
default:
164-
return fmt.Errorf("got %+v from %v; want echo reply", rm, peer)
214+
return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
165215
}
166216
}
167217

icmp/dstunreach.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,24 @@ func (p *DstUnreach) Len(proto int) int {
1616
if p == nil {
1717
return 0
1818
}
19-
l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
19+
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
2020
return 4 + l
2121
}
2222

2323
// Marshal implements the Marshal method of MessageBody interface.
2424
func (p *DstUnreach) Marshal(proto int) ([]byte, error) {
25-
return marshalMultipartMessageBody(proto, p.Data, p.Extensions)
25+
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
2626
}
2727

2828
// parseDstUnreach parses b as an ICMP destination unreachable message
2929
// body.
30-
func parseDstUnreach(proto int, b []byte) (MessageBody, error) {
30+
func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) {
3131
if len(b) < 4 {
3232
return nil, errMessageTooShort
3333
}
3434
p := &DstUnreach{}
3535
var err error
36-
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
36+
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
3737
if err != nil {
3838
return nil, err
3939
}

icmp/echo.go

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (p *Echo) Marshal(proto int) ([]byte, error) {
3131
}
3232

3333
// parseEcho parses b as an ICMP echo request or reply message body.
34-
func parseEcho(proto int, b []byte) (MessageBody, error) {
34+
func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) {
3535
bodyLen := len(b)
3636
if bodyLen < 4 {
3737
return nil, errMessageTooShort
@@ -43,3 +43,115 @@ func parseEcho(proto int, b []byte) (MessageBody, error) {
4343
}
4444
return p, nil
4545
}
46+
47+
// An ExtendedEchoRequest represents an ICMP extended echo request
48+
// message body.
49+
type ExtendedEchoRequest struct {
50+
ID int // identifier
51+
Seq int // sequence number
52+
Local bool // must be true when identifying by name or index
53+
Extensions []Extension // extensions
54+
}
55+
56+
// Len implements the Len method of MessageBody interface.
57+
func (p *ExtendedEchoRequest) Len(proto int) int {
58+
if p == nil {
59+
return 0
60+
}
61+
l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions)
62+
return 4 + l
63+
}
64+
65+
// Marshal implements the Marshal method of MessageBody interface.
66+
func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) {
67+
b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions)
68+
if err != nil {
69+
return nil, err
70+
}
71+
bb := make([]byte, 4)
72+
binary.BigEndian.PutUint16(bb[:2], uint16(p.ID))
73+
bb[2] = byte(p.Seq)
74+
if p.Local {
75+
bb[3] |= 0x01
76+
}
77+
bb = append(bb, b...)
78+
return bb, nil
79+
}
80+
81+
// parseExtendedEchoRequest parses b as an ICMP extended echo request
82+
// message body.
83+
func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) {
84+
if len(b) < 4+4 {
85+
return nil, errMessageTooShort
86+
}
87+
p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])}
88+
if b[3]&0x01 != 0 {
89+
p.Local = true
90+
}
91+
var err error
92+
_, p.Extensions, err = parseMultipartMessageBody(proto, typ, b[4:])
93+
if err != nil {
94+
return nil, err
95+
}
96+
return p, nil
97+
}
98+
99+
// An ExtendedEchoReply represents an ICMP extended echo reply message
100+
// body.
101+
type ExtendedEchoReply struct {
102+
ID int // identifier
103+
Seq int // sequence number
104+
State int // 3-bit state working together with Message.Code
105+
Active bool // probed interface is active
106+
IPv4 bool // probed interface runs IPv4
107+
IPv6 bool // probed interface runs IPv6
108+
}
109+
110+
// Len implements the Len method of MessageBody interface.
111+
func (p *ExtendedEchoReply) Len(proto int) int {
112+
if p == nil {
113+
return 0
114+
}
115+
return 4
116+
}
117+
118+
// Marshal implements the Marshal method of MessageBody interface.
119+
func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) {
120+
b := make([]byte, 4)
121+
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
122+
b[2] = byte(p.Seq)
123+
b[3] = byte(p.State<<5) & 0xe0
124+
if p.Active {
125+
b[3] |= 0x04
126+
}
127+
if p.IPv4 {
128+
b[3] |= 0x02
129+
}
130+
if p.IPv6 {
131+
b[3] |= 0x01
132+
}
133+
return b, nil
134+
}
135+
136+
// parseExtendedEchoReply parses b as an ICMP extended echo reply
137+
// message body.
138+
func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) {
139+
if len(b) < 4 {
140+
return nil, errMessageTooShort
141+
}
142+
p := &ExtendedEchoReply{
143+
ID: int(binary.BigEndian.Uint16(b[:2])),
144+
Seq: int(b[2]),
145+
State: int(b[3]) >> 5,
146+
}
147+
if b[3]&0x04 != 0 {
148+
p.Active = true
149+
}
150+
if b[3]&0x02 != 0 {
151+
p.IPv4 = true
152+
}
153+
if b[3]&0x01 != 0 {
154+
p.IPv6 = true
155+
}
156+
return p, nil
157+
}

icmp/extension.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
package icmp
66

7-
import "encoding/binary"
7+
import (
8+
"encoding/binary"
9+
10+
"golang.org/x/net/ipv4"
11+
"golang.org/x/net/ipv6"
12+
)
813

914
// An Extension represents an ICMP extension.
1015
type Extension interface {
@@ -38,7 +43,7 @@ func validExtensionHeader(b []byte) bool {
3843
// It will return a list of ICMP extensions and an adjusted length
3944
// attribute that represents the length of the padded original
4045
// datagram field. Otherwise, it returns an error.
41-
func parseExtensions(b []byte, l int) ([]Extension, int, error) {
46+
func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) {
4247
// Still a lot of non-RFC 4884 compliant implementations are
4348
// out there. Set the length attribute l to 128 when it looks
4449
// inappropriate for backwards compatibility.
@@ -48,20 +53,28 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
4853
// header.
4954
//
5055
// See RFC 4884 for further information.
51-
if 128 > l || l+8 > len(b) {
52-
l = 128
53-
}
54-
if l+8 > len(b) {
55-
return nil, -1, errNoExtension
56-
}
57-
if !validExtensionHeader(b[l:]) {
58-
if l == 128 {
56+
switch typ {
57+
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
58+
if len(b) < 8 || !validExtensionHeader(b) {
5959
return nil, -1, errNoExtension
6060
}
61-
l = 128
62-
if !validExtensionHeader(b[l:]) {
61+
l = 0
62+
default:
63+
if 128 > l || l+8 > len(b) {
64+
l = 128
65+
}
66+
if l+8 > len(b) {
6367
return nil, -1, errNoExtension
6468
}
69+
if !validExtensionHeader(b[l:]) {
70+
if l == 128 {
71+
return nil, -1, errNoExtension
72+
}
73+
l = 128
74+
if !validExtensionHeader(b[l:]) {
75+
return nil, -1, errNoExtension
76+
}
77+
}
6578
}
6679
var exts []Extension
6780
for b = b[l+4:]; len(b) >= 4; {
@@ -82,6 +95,12 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
8295
return nil, -1, err
8396
}
8497
exts = append(exts, ext)
98+
case classInterfaceIdent:
99+
ext, err := parseInterfaceIdent(b[:ol])
100+
if err != nil {
101+
return nil, -1, err
102+
}
103+
exts = append(exts, ext)
85104
}
86105
b = b[ol:]
87106
}

0 commit comments

Comments
 (0)