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
+}