1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package icmp provides basic functions for the manipulation of
6// messages used in the Internet Control Message Protocols,
7// ICMPv4 and ICMPv6.
8//
9// ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443.
10// Multi-part message support for ICMP is defined in RFC 4884.
11// ICMP extensions for MPLS are defined in RFC 4950.
12// ICMP extensions for interface and next-hop identification are
13// defined in RFC 5837.
14// PROBE: A utility for probing interfaces is defined in RFC 8335.
15package icmp // import "golang.org/x/net/icmp"
16
17import (
18	"encoding/binary"
19	"errors"
20	"net"
21
22	"golang.org/x/net/internal/iana"
23	"golang.org/x/net/ipv4"
24	"golang.org/x/net/ipv6"
25)
26
27// BUG(mikio): This package is not implemented on AIX, JS, NaCl and
28// Plan 9.
29
30var (
31	errInvalidConn      = errors.New("invalid connection")
32	errInvalidProtocol  = errors.New("invalid protocol")
33	errMessageTooShort  = errors.New("message too short")
34	errHeaderTooShort   = errors.New("header too short")
35	errBufferTooShort   = errors.New("buffer too short")
36	errOpNoSupport      = errors.New("operation not supported")
37	errNoExtension      = errors.New("no extension")
38	errInvalidExtension = errors.New("invalid extension")
39)
40
41func checksum(b []byte) uint16 {
42	csumcv := len(b) - 1 // checksum coverage
43	s := uint32(0)
44	for i := 0; i < csumcv; i += 2 {
45		s += uint32(b[i+1])<<8 | uint32(b[i])
46	}
47	if csumcv&1 == 0 {
48		s += uint32(b[csumcv])
49	}
50	s = s>>16 + s&0xffff
51	s = s + s>>16
52	return ^uint16(s)
53}
54
55// A Type represents an ICMP message type.
56type Type interface {
57	Protocol() int
58}
59
60// A Message represents an ICMP message.
61type Message struct {
62	Type     Type        // type, either ipv4.ICMPType or ipv6.ICMPType
63	Code     int         // code
64	Checksum int         // checksum
65	Body     MessageBody // body
66}
67
68// Marshal returns the binary encoding of the ICMP message m.
69//
70// For an ICMPv4 message, the returned message always contains the
71// calculated checksum field.
72//
73// For an ICMPv6 message, the returned message contains the calculated
74// checksum field when psh is not nil, otherwise the kernel will
75// compute the checksum field during the message transmission.
76// When psh is not nil, it must be the pseudo header for IPv6.
77func (m *Message) Marshal(psh []byte) ([]byte, error) {
78	var mtype int
79	switch typ := m.Type.(type) {
80	case ipv4.ICMPType:
81		mtype = int(typ)
82	case ipv6.ICMPType:
83		mtype = int(typ)
84	default:
85		return nil, errInvalidProtocol
86	}
87	b := []byte{byte(mtype), byte(m.Code), 0, 0}
88	if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil {
89		b = append(psh, b...)
90	}
91	if m.Body != nil && m.Body.Len(m.Type.Protocol()) != 0 {
92		mb, err := m.Body.Marshal(m.Type.Protocol())
93		if err != nil {
94			return nil, err
95		}
96		b = append(b, mb...)
97	}
98	if m.Type.Protocol() == iana.ProtocolIPv6ICMP {
99		if psh == nil { // cannot calculate checksum here
100			return b, nil
101		}
102		off, l := 2*net.IPv6len, len(b)-len(psh)
103		binary.BigEndian.PutUint32(b[off:off+4], uint32(l))
104	}
105	s := checksum(b)
106	// Place checksum back in header; using ^= avoids the
107	// assumption the checksum bytes are zero.
108	b[len(psh)+2] ^= byte(s)
109	b[len(psh)+3] ^= byte(s >> 8)
110	return b[len(psh):], nil
111}
112
113var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){
114	ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
115	ipv4.ICMPTypeTimeExceeded:           parseTimeExceeded,
116	ipv4.ICMPTypeParameterProblem:       parseParamProb,
117
118	ipv4.ICMPTypeEcho:                parseEcho,
119	ipv4.ICMPTypeEchoReply:           parseEcho,
120	ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
121	ipv4.ICMPTypeExtendedEchoReply:   parseExtendedEchoReply,
122
123	ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
124	ipv6.ICMPTypePacketTooBig:           parsePacketTooBig,
125	ipv6.ICMPTypeTimeExceeded:           parseTimeExceeded,
126	ipv6.ICMPTypeParameterProblem:       parseParamProb,
127
128	ipv6.ICMPTypeEchoRequest:         parseEcho,
129	ipv6.ICMPTypeEchoReply:           parseEcho,
130	ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
131	ipv6.ICMPTypeExtendedEchoReply:   parseExtendedEchoReply,
132}
133
134// ParseMessage parses b as an ICMP message.
135// The provided proto must be either the ICMPv4 or ICMPv6 protocol
136// number.
137func ParseMessage(proto int, b []byte) (*Message, error) {
138	if len(b) < 4 {
139		return nil, errMessageTooShort
140	}
141	var err error
142	m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))}
143	switch proto {
144	case iana.ProtocolICMP:
145		m.Type = ipv4.ICMPType(b[0])
146	case iana.ProtocolIPv6ICMP:
147		m.Type = ipv6.ICMPType(b[0])
148	default:
149		return nil, errInvalidProtocol
150	}
151	if fn, ok := parseFns[m.Type]; !ok {
152		m.Body, err = parseDefaultMessageBody(proto, b[4:])
153	} else {
154		m.Body, err = fn(proto, m.Type, b[4:])
155	}
156	if err != nil {
157		return nil, err
158	}
159	return m, nil
160}
161