// Copyright 2012 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 provides basic functions for the manipulation of // messages used in the Internet Control Message Protocols, // ICMPv4 and ICMPv6. // // ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443. // Multi-part message support for ICMP is defined in RFC 4884. // ICMP extensions for MPLS are defined in RFC 4950. // ICMP extensions for interface and next-hop identification are // defined in RFC 5837. // PROBE: A utility for probing interfaces is defined in RFC 8335. package icmp // import "golang.org/x/net/icmp" import ( "encoding/binary" "errors" "net" "golang.org/x/net/internal/iana" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) // BUG(mikio): This package is not implemented on JS, NaCl and Plan 9. var ( errInvalidConn = errors.New("invalid connection") errInvalidProtocol = errors.New("invalid protocol") errMessageTooShort = errors.New("message too short") errHeaderTooShort = errors.New("header too short") errBufferTooShort = errors.New("buffer too short") errOpNoSupport = errors.New("operation not supported") errNoExtension = errors.New("no extension") errInvalidExtension = errors.New("invalid extension") ) func checksum(b []byte) uint16 { csumcv := len(b) - 1 // checksum coverage s := uint32(0) for i := 0; i < csumcv; i += 2 { s += uint32(b[i+1])<<8 | uint32(b[i]) } if csumcv&1 == 0 { s += uint32(b[csumcv]) } s = s>>16 + s&0xffff s = s + s>>16 return ^uint16(s) } // A Type represents an ICMP message type. type Type interface { Protocol() int } // A Message represents an ICMP message. type Message struct { Type Type // type, either ipv4.ICMPType or ipv6.ICMPType Code int // code Checksum int // checksum Body MessageBody // body } // Marshal returns the binary encoding of the ICMP message m. // // For an ICMPv4 message, the returned message always contains the // calculated checksum field. // // For an ICMPv6 message, the returned message contains the calculated // checksum field when psh is not nil, otherwise the kernel will // compute the checksum field during the message transmission. // When psh is not nil, it must be the pseudo header for IPv6. func (m *Message) Marshal(psh []byte) ([]byte, error) { var mtype int switch typ := m.Type.(type) { case ipv4.ICMPType: mtype = int(typ) case ipv6.ICMPType: mtype = int(typ) default: return nil, errInvalidProtocol } b := []byte{byte(mtype), byte(m.Code), 0, 0} if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil { b = append(psh, b...) } if m.Body != nil && m.Body.Len(m.Type.Protocol()) != 0 { mb, err := m.Body.Marshal(m.Type.Protocol()) if err != nil { return nil, err } b = append(b, mb...) } if m.Type.Protocol() == iana.ProtocolIPv6ICMP { if psh == nil { // cannot calculate checksum here return b, nil } off, l := 2*net.IPv6len, len(b)-len(psh) binary.BigEndian.PutUint32(b[off:off+4], uint32(l)) } s := checksum(b) // Place checksum back in header; using ^= avoids the // assumption the checksum bytes are zero. b[len(psh)+2] ^= byte(s) b[len(psh)+3] ^= byte(s >> 8) return b[len(psh):], nil } var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){ ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach, ipv4.ICMPTypeTimeExceeded: parseTimeExceeded, ipv4.ICMPTypeParameterProblem: parseParamProb, ipv4.ICMPTypeEcho: parseEcho, ipv4.ICMPTypeEchoReply: parseEcho, ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach, ipv6.ICMPTypePacketTooBig: parsePacketTooBig, ipv6.ICMPTypeTimeExceeded: parseTimeExceeded, ipv6.ICMPTypeParameterProblem: parseParamProb, ipv6.ICMPTypeEchoRequest: parseEcho, ipv6.ICMPTypeEchoReply: parseEcho, ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, } // ParseMessage parses b as an ICMP message. // The provided proto must be either the ICMPv4 or ICMPv6 protocol // number. func ParseMessage(proto int, b []byte) (*Message, error) { if len(b) < 4 { return nil, errMessageTooShort } var err error m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))} switch proto { case iana.ProtocolICMP: m.Type = ipv4.ICMPType(b[0]) case iana.ProtocolIPv6ICMP: m.Type = ipv6.ICMPType(b[0]) default: return nil, errInvalidProtocol } if fn, ok := parseFns[m.Type]; !ok { m.Body, err = parseDefaultMessageBody(proto, b[4:]) } else { m.Body, err = fn(proto, m.Type, b[4:]) } if err != nil { return nil, err } return m, nil }