Commit a21e453f authored by Mikio Hara's avatar Mikio Hara

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
parent 119dea2c
// 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
}
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
package icmp package icmp
import "errors" // An Echo represents an ICMP echo request or reply message body.
// An Echo represenets an ICMP echo request or reply message body.
type Echo struct { type Echo struct {
ID int // identifier ID int // identifier
Seq int // sequence number Seq int // sequence number
...@@ -31,10 +29,10 @@ func (p *Echo) Marshal() ([]byte, error) { ...@@ -31,10 +29,10 @@ func (p *Echo) Marshal() ([]byte, error) {
} }
// parseEcho parses b as an ICMP echo request or reply message body. // 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) bodyLen := len(b)
if bodyLen < 4 { 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])} p := &Echo{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])}
if bodyLen > 4 { if bodyLen > 4 {
......
...@@ -9,12 +9,20 @@ package icmp ...@@ -9,12 +9,20 @@ package icmp
import ( import (
"errors" "errors"
"net" "net"
"syscall"
"golang.org/x/net/internal/iana" "golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/ipv6" "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. // A Type represents an ICMP message type.
type Type interface { type Type interface {
Protocol() int Protocol() int
...@@ -45,7 +53,7 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) { ...@@ -45,7 +53,7 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) {
case ipv6.ICMPType: case ipv6.ICMPType:
mtype = int(typ) mtype = int(typ)
default: default:
return nil, errors.New("invalid argument") return nil, syscall.EINVAL
} }
b := []byte{byte(mtype), byte(m.Code), 0, 0} b := []byte{byte(mtype), byte(m.Code), 0, 0}
if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil { if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil {
...@@ -82,39 +90,46 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) { ...@@ -82,39 +90,46 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) {
return b[len(psh):], nil return b[len(psh):], nil
} }
// ParseMessage parses b as an ICMP message. Proto must be var parseFns = map[Type]func([]byte) (MessageBody, error){
// iana.ProtocolICMP or iana.ProtocolIPv6ICMP. 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) { func ParseMessage(proto int, b []byte) (*Message, error) {
if len(b) < 4 { if len(b) < 4 {
return nil, errors.New("message too short") return nil, errMessageTooShort
} }
var err error var err error
m := &Message{Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
switch proto { switch proto {
case iana.ProtocolICMP: case iana.ProtocolICMP:
m := &Message{Type: ipv4.ICMPType(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])} m.Type = ipv4.ICMPType(b[0])
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
case iana.ProtocolIPv6ICMP: case iana.ProtocolIPv6ICMP:
m := &Message{Type: ipv6.ICMPType(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])} m.Type = ipv6.ICMPType(b[0])
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
default: 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
} }
...@@ -16,6 +16,25 @@ import ( ...@@ -16,6 +16,25 @@ import (
) )
var marshalAndParseMessageForIPv4Tests = []icmp.Message{ 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, Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{ Body: &icmp.Echo{
...@@ -51,6 +70,32 @@ func TestMarshalAndParseMessageForIPv4(t *testing.T) { ...@@ -51,6 +70,32 @@ func TestMarshalAndParseMessageForIPv4(t *testing.T) {
} }
var marshalAndParseMessageForIPv6Tests = []icmp.Message{ 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, Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{ Body: &icmp.Echo{
......
...@@ -30,3 +30,10 @@ func (p *DefaultMessageBody) Len() int { ...@@ -30,3 +30,10 @@ func (p *DefaultMessageBody) Len() int {
func (p *DefaultMessageBody) Marshal() ([]byte, error) { func (p *DefaultMessageBody) Marshal() ([]byte, error) {
return p.Data, nil 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
}
// 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
}
// 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
}
// 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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment