x/net/internal/icmp: add support for error message
This CL introduces few ICMP error message body types such as
DstUnreach, PacketTooBig, TimeExceeded or ParamProb.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/183850043
diff --git a/internal/icmp/dstunreach.go b/internal/icmp/dstunreach.go
new file mode 100644
index 0000000..8241017
--- /dev/null
+++ b/internal/icmp/dstunreach.go
@@ -0,0 +1,41 @@
+// Copyright 2014 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
+
+// A DstUnreach represents an ICMP destination unreachable message
+// body.
+type DstUnreach struct {
+ Data []byte // data
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *DstUnreach) Len() int {
+ if p == nil {
+ return 0
+ }
+ return 4 + len(p.Data)
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *DstUnreach) Marshal() ([]byte, error) {
+ b := make([]byte, 4+len(p.Data))
+ copy(b[4:], p.Data)
+ return b, nil
+}
+
+// parseDstUnreach parses b as an ICMP destination unreachable message
+// body.
+func parseDstUnreach(b []byte) (MessageBody, error) {
+ bodyLen := len(b)
+ if bodyLen < 4 {
+ return nil, errMessageTooShort
+ }
+ p := &DstUnreach{}
+ if bodyLen > 4 {
+ p.Data = make([]byte, bodyLen-4)
+ copy(p.Data, b[4:])
+ }
+ return p, nil
+}
diff --git a/internal/icmp/echo.go b/internal/icmp/echo.go
index b203d08..dfac9cb 100644
--- a/internal/icmp/echo.go
+++ b/internal/icmp/echo.go
@@ -4,9 +4,7 @@
package icmp
-import "errors"
-
-// An Echo represenets an ICMP echo request or reply message body.
+// An Echo represents an ICMP echo request or reply message body.
type Echo struct {
ID int // identifier
Seq int // sequence number
@@ -31,10 +29,10 @@
}
// parseEcho parses b as an ICMP echo request or reply message body.
-func parseEcho(b []byte) (*Echo, error) {
+func parseEcho(b []byte) (MessageBody, error) {
bodyLen := len(b)
if bodyLen < 4 {
- return nil, errors.New("message too short")
+ return nil, errMessageTooShort
}
p := &Echo{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])}
if bodyLen > 4 {
diff --git a/internal/icmp/message.go b/internal/icmp/message.go
index 3d7a418..2023dbe 100644
--- a/internal/icmp/message.go
+++ b/internal/icmp/message.go
@@ -9,12 +9,20 @@
import (
"errors"
"net"
+ "syscall"
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
+var (
+ errMessageTooShort = errors.New("message too short")
+ errHeaderTooShort = errors.New("header too short")
+ errBufferTooShort = errors.New("buffer too short")
+ errOpNoSupport = errors.New("operation not supported")
+)
+
// A Type represents an ICMP message type.
type Type interface {
Protocol() int
@@ -45,7 +53,7 @@
case ipv6.ICMPType:
mtype = int(typ)
default:
- return nil, errors.New("invalid argument")
+ return nil, syscall.EINVAL
}
b := []byte{byte(mtype), byte(m.Code), 0, 0}
if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil {
@@ -82,39 +90,46 @@
return b[len(psh):], nil
}
-// ParseMessage parses b as an ICMP message. Proto must be
-// iana.ProtocolICMP or iana.ProtocolIPv6ICMP.
+var parseFns = map[Type]func([]byte) (MessageBody, error){
+ ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
+ ipv4.ICMPTypeTimeExceeded: parseTimeExceeded,
+ ipv4.ICMPTypeParameterProblem: parseParamProb,
+
+ ipv4.ICMPTypeEcho: parseEcho,
+ ipv4.ICMPTypeEchoReply: parseEcho,
+
+ ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
+ ipv6.ICMPTypePacketTooBig: parsePacketTooBig,
+ ipv6.ICMPTypeTimeExceeded: parseTimeExceeded,
+ ipv6.ICMPTypeParameterProblem: parseParamProb,
+
+ ipv6.ICMPTypeEchoRequest: parseEcho,
+ ipv6.ICMPTypeEchoReply: parseEcho,
+}
+
+// ParseMessage parses b as an ICMP message.
+// Proto must be either the ICMPv4 or ICMPv6 protocol number.
func ParseMessage(proto int, b []byte) (*Message, error) {
if len(b) < 4 {
- return nil, errors.New("message too short")
+ return nil, errMessageTooShort
}
var err error
+ m := &Message{Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
switch proto {
case iana.ProtocolICMP:
- m := &Message{Type: ipv4.ICMPType(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
- switch m.Type {
- case ipv4.ICMPTypeEcho, ipv4.ICMPTypeEchoReply:
- m.Body, err = parseEcho(b[4:])
- if err != nil {
- return nil, err
- }
- default:
- m.Body = &DefaultMessageBody{Data: b[4:]}
- }
- return m, nil
+ m.Type = ipv4.ICMPType(b[0])
case iana.ProtocolIPv6ICMP:
- m := &Message{Type: ipv6.ICMPType(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
- switch m.Type {
- case ipv6.ICMPTypeEchoRequest, ipv6.ICMPTypeEchoReply:
- m.Body, err = parseEcho(b[4:])
- if err != nil {
- return nil, err
- }
- default:
- m.Body = &DefaultMessageBody{Data: b[4:]}
- }
- return m, nil
+ m.Type = ipv6.ICMPType(b[0])
default:
- return nil, errors.New("unknown protocol")
+ return nil, syscall.EINVAL
}
+ if fn, ok := parseFns[m.Type]; !ok {
+ m.Body, err = parseDefaultMessageBody(b[4:])
+ } else {
+ m.Body, err = fn(b[4:])
+ }
+ if err != nil {
+ return nil, err
+ }
+ return m, nil
}
diff --git a/internal/icmp/message_test.go b/internal/icmp/message_test.go
index 4b37120..cd643e2 100644
--- a/internal/icmp/message_test.go
+++ b/internal/icmp/message_test.go
@@ -17,6 +17,25 @@
var marshalAndParseMessageForIPv4Tests = []icmp.Message{
{
+ Type: ipv4.ICMPTypeDestinationUnreachable, Code: 15,
+ Body: &icmp.DstUnreach{
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
+ Type: ipv4.ICMPTypeTimeExceeded, Code: 1,
+ Body: &icmp.TimeExceeded{
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
+ Type: ipv4.ICMPTypeParameterProblem, Code: 2,
+ Body: &icmp.ParamProb{
+ Pointer: 8,
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: 1, Seq: 2,
@@ -52,6 +71,32 @@
var marshalAndParseMessageForIPv6Tests = []icmp.Message{
{
+ Type: ipv6.ICMPTypeDestinationUnreachable, Code: 6,
+ Body: &icmp.DstUnreach{
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
+ Type: ipv6.ICMPTypePacketTooBig, Code: 0,
+ Body: &icmp.PacketTooBig{
+ MTU: 1<<16 - 1,
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
+ Type: ipv6.ICMPTypeTimeExceeded, Code: 1,
+ Body: &icmp.TimeExceeded{
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
+ Type: ipv6.ICMPTypeParameterProblem, Code: 2,
+ Body: &icmp.ParamProb{
+ Pointer: 8,
+ Data: []byte("ERROR-INVOKING-PACKET"),
+ },
+ },
+ {
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: 1, Seq: 2,
diff --git a/internal/icmp/messagebody.go b/internal/icmp/messagebody.go
index 1994257..f653ab6 100644
--- a/internal/icmp/messagebody.go
+++ b/internal/icmp/messagebody.go
@@ -30,3 +30,10 @@
func (p *DefaultMessageBody) Marshal() ([]byte, error) {
return p.Data, nil
}
+
+// parseDefaultMessageBody parses b as an ICMP message body.
+func parseDefaultMessageBody(b []byte) (MessageBody, error) {
+ p := &DefaultMessageBody{Data: make([]byte, len(b))}
+ copy(p.Data, b)
+ return p, nil
+}
diff --git a/internal/icmp/packettoobig.go b/internal/icmp/packettoobig.go
new file mode 100644
index 0000000..fc10192
--- /dev/null
+++ b/internal/icmp/packettoobig.go
@@ -0,0 +1,41 @@
+// Copyright 2014 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
+
+// A PacketTooBig represents an ICMP packet too big message body.
+type PacketTooBig struct {
+ MTU int // maximum transmission unit of the nexthop link
+ Data []byte // data
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *PacketTooBig) Len() int {
+ if p == nil {
+ return 0
+ }
+ return 4 + len(p.Data)
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *PacketTooBig) Marshal() ([]byte, error) {
+ b := make([]byte, 4+len(p.Data))
+ b[0], b[1], b[2], b[3] = byte(p.MTU>>24), byte(p.MTU>>16), byte(p.MTU>>8), byte(p.MTU)
+ copy(b[4:], p.Data)
+ return b, nil
+}
+
+// parsePacketTooBig parses b as an ICMP packet too big message body.
+func parsePacketTooBig(b []byte) (MessageBody, error) {
+ bodyLen := len(b)
+ if bodyLen < 4 {
+ return nil, errMessageTooShort
+ }
+ p := &PacketTooBig{MTU: int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])}
+ if bodyLen > 4 {
+ p.Data = make([]byte, bodyLen-4)
+ copy(p.Data, b[4:])
+ }
+ return p, nil
+}
diff --git a/internal/icmp/paramprob.go b/internal/icmp/paramprob.go
new file mode 100644
index 0000000..be32ce0
--- /dev/null
+++ b/internal/icmp/paramprob.go
@@ -0,0 +1,41 @@
+// Copyright 2014 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
+
+// A ParamProb represents an ICMP parameter problem message body.
+type ParamProb struct {
+ Pointer uintptr // offset within the data where the error was detected
+ Data []byte // data
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *ParamProb) Len() int {
+ if p == nil {
+ return 0
+ }
+ return 4 + len(p.Data)
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *ParamProb) Marshal() ([]byte, error) {
+ b := make([]byte, 4+len(p.Data))
+ b[0], b[1], b[2], b[3] = byte(p.Pointer>>24), byte(p.Pointer>>16), byte(p.Pointer>>8), byte(p.Pointer)
+ copy(b[4:], p.Data)
+ return b, nil
+}
+
+// parseParamProb parses b as an ICMP parameter problem message body.
+func parseParamProb(b []byte) (MessageBody, error) {
+ bodyLen := len(b)
+ if bodyLen < 4 {
+ return nil, errMessageTooShort
+ }
+ p := &ParamProb{Pointer: uintptr(b[0])<<24 | uintptr(b[1])<<16 | uintptr(b[2])<<8 | uintptr(b[3])}
+ if bodyLen > 4 {
+ p.Data = make([]byte, bodyLen-4)
+ copy(p.Data, b[4:])
+ }
+ return p, nil
+}
diff --git a/internal/icmp/timeexceeded.go b/internal/icmp/timeexceeded.go
new file mode 100644
index 0000000..993a150
--- /dev/null
+++ b/internal/icmp/timeexceeded.go
@@ -0,0 +1,39 @@
+// Copyright 2014 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
+
+// A TimeExceeded represents an ICMP time exceeded message body.
+type TimeExceeded struct {
+ Data []byte // data
+}
+
+// Len implements the Len method of MessageBody interface.
+func (p *TimeExceeded) Len() int {
+ if p == nil {
+ return 0
+ }
+ return 4 + len(p.Data)
+}
+
+// Marshal implements the Marshal method of MessageBody interface.
+func (p *TimeExceeded) Marshal() ([]byte, error) {
+ b := make([]byte, 4+len(p.Data))
+ copy(b[4:], p.Data)
+ return b, nil
+}
+
+// parseTimeExceeded parses b as an ICMP time exceeded message body.
+func parseTimeExceeded(b []byte) (MessageBody, error) {
+ bodyLen := len(b)
+ if bodyLen < 4 {
+ return nil, errMessageTooShort
+ }
+ p := &TimeExceeded{}
+ if bodyLen > 4 {
+ p.Data = make([]byte, bodyLen-4)
+ copy(p.Data, b[4:])
+ }
+ return p, nil
+}