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. 14package icmp // import "golang.org/x/net/icmp" 15 16import ( 17 "encoding/binary" 18 "errors" 19 "net" 20 "syscall" 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 NaCl and Plan 9. 28 29var ( 30 errMessageTooShort = errors.New("message too short") 31 errHeaderTooShort = errors.New("header too short") 32 errBufferTooShort = errors.New("buffer too short") 33 errOpNoSupport = errors.New("operation not supported") 34 errNoExtension = errors.New("no extension") 35 errInvalidExtension = errors.New("invalid extension") 36) 37 38func checksum(b []byte) uint16 { 39 csumcv := len(b) - 1 // checksum coverage 40 s := uint32(0) 41 for i := 0; i < csumcv; i += 2 { 42 s += uint32(b[i+1])<<8 | uint32(b[i]) 43 } 44 if csumcv&1 == 0 { 45 s += uint32(b[csumcv]) 46 } 47 s = s>>16 + s&0xffff 48 s = s + s>>16 49 return ^uint16(s) 50} 51 52// A Type represents an ICMP message type. 53type Type interface { 54 Protocol() int 55} 56 57// A Message represents an ICMP message. 58type Message struct { 59 Type Type // type, either ipv4.ICMPType or ipv6.ICMPType 60 Code int // code 61 Checksum int // checksum 62 Body MessageBody // body 63} 64 65// Marshal returns the binary encoding of the ICMP message m. 66// 67// For an ICMPv4 message, the returned message always contains the 68// calculated checksum field. 69// 70// For an ICMPv6 message, the returned message contains the calculated 71// checksum field when psh is not nil, otherwise the kernel will 72// compute the checksum field during the message transmission. 73// When psh is not nil, it must be the pseudo header for IPv6. 74func (m *Message) Marshal(psh []byte) ([]byte, error) { 75 var mtype int 76 switch typ := m.Type.(type) { 77 case ipv4.ICMPType: 78 mtype = int(typ) 79 case ipv6.ICMPType: 80 mtype = int(typ) 81 default: 82 return nil, syscall.EINVAL 83 } 84 b := []byte{byte(mtype), byte(m.Code), 0, 0} 85 if m.Type.Protocol() == iana.ProtocolIPv6ICMP && psh != nil { 86 b = append(psh, b...) 87 } 88 if m.Body != nil && m.Body.Len(m.Type.Protocol()) != 0 { 89 mb, err := m.Body.Marshal(m.Type.Protocol()) 90 if err != nil { 91 return nil, err 92 } 93 b = append(b, mb...) 94 } 95 if m.Type.Protocol() == iana.ProtocolIPv6ICMP { 96 if psh == nil { // cannot calculate checksum here 97 return b, nil 98 } 99 off, l := 2*net.IPv6len, len(b)-len(psh) 100 binary.BigEndian.PutUint32(b[off:off+4], uint32(l)) 101 } 102 s := checksum(b) 103 // Place checksum back in header; using ^= avoids the 104 // assumption the checksum bytes are zero. 105 b[len(psh)+2] ^= byte(s) 106 b[len(psh)+3] ^= byte(s >> 8) 107 return b[len(psh):], nil 108} 109 110var parseFns = map[Type]func(int, []byte) (MessageBody, error){ 111 ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach, 112 ipv4.ICMPTypeTimeExceeded: parseTimeExceeded, 113 ipv4.ICMPTypeParameterProblem: parseParamProb, 114 115 ipv4.ICMPTypeEcho: parseEcho, 116 ipv4.ICMPTypeEchoReply: parseEcho, 117 118 ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach, 119 ipv6.ICMPTypePacketTooBig: parsePacketTooBig, 120 ipv6.ICMPTypeTimeExceeded: parseTimeExceeded, 121 ipv6.ICMPTypeParameterProblem: parseParamProb, 122 123 ipv6.ICMPTypeEchoRequest: parseEcho, 124 ipv6.ICMPTypeEchoReply: parseEcho, 125} 126 127// ParseMessage parses b as an ICMP message. 128// Proto must be either the ICMPv4 or ICMPv6 protocol number. 129func ParseMessage(proto int, b []byte) (*Message, error) { 130 if len(b) < 4 { 131 return nil, errMessageTooShort 132 } 133 var err error 134 m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))} 135 switch proto { 136 case iana.ProtocolICMP: 137 m.Type = ipv4.ICMPType(b[0]) 138 case iana.ProtocolIPv6ICMP: 139 m.Type = ipv6.ICMPType(b[0]) 140 default: 141 return nil, syscall.EINVAL 142 } 143 if fn, ok := parseFns[m.Type]; !ok { 144 m.Body, err = parseDefaultMessageBody(proto, b[4:]) 145 } else { 146 m.Body, err = fn(proto, b[4:]) 147 } 148 if err != nil { 149 return nil, err 150 } 151 return m, nil 152} 153