Commit 8916b1d9 authored by Mikio Hara's avatar Mikio Hara

go.net/internal/icmp: new package

This CL factors out ICMP utilities used by both ipv4 and ipv6 packages.

LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/143460043
parent 95f09c64
// 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
import "errors"
// An Echo represenets an ICMP echo request or reply message body.
type Echo struct {
ID int // identifier
Seq int // sequence number
Data []byte // data
}
// Len implements the Len method of MessageBody interface.
func (p *Echo) Len() int {
if p == nil {
return 0
}
return 4 + len(p.Data)
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *Echo) Marshal() ([]byte, error) {
b := make([]byte, 4+len(p.Data))
b[0], b[1] = byte(p.ID>>8), byte(p.ID)
b[2], b[3] = byte(p.Seq>>8), byte(p.Seq)
copy(b[4:], p.Data)
return b, nil
}
// parseEcho parses b as an ICMP echo request or reply message body.
func parseEcho(b []byte) (*Echo, error) {
bodyLen := len(b)
if bodyLen < 4 {
return nil, errors.New("message too short")
}
p := &Echo{ID: int(b[0])<<8 | int(b[1]), Seq: 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 2013 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
import (
"net"
"code.google.com/p/go.net/internal/iana"
)
const ipv6PseudoHeaderLen = 2*net.IPv6len + 8
// IPv6PseudoHeader returns an IPv6 pseudo header for checkusm
// calculation.
func IPv6PseudoHeader(src, dst net.IP) []byte {
b := make([]byte, ipv6PseudoHeaderLen)
copy(b[:net.IPv6len], src)
copy(b[net.IPv6len:], dst)
b[len(b)-1] = byte(iana.ProtocolIPv6ICMP)
return b
}
// 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 ICMP
// message.
package icmp
import (
"errors"
"net"
"code.google.com/p/go.net/internal/iana"
"code.google.com/p/go.net/ipv4"
"code.google.com/p/go.net/ipv6"
)
// A Type represents an ICMP message type.
type Type interface {
String() string
}
// 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 enconding of the ICMP message m.
//
// For ICMP for IPv4 message, the returned message always contains the
// calculated checksum field.
//
// For ICMP for IPv6 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
var icmpv6 bool
switch typ := m.Type.(type) {
case ipv4.ICMPType:
mtype = int(typ)
case ipv6.ICMPType:
mtype = int(typ)
icmpv6 = true
default:
return nil, errors.New("invalid argument")
}
b := []byte{byte(mtype), byte(m.Code), 0, 0}
if icmpv6 && psh != nil {
b = append(psh, b...)
}
if m.Body != nil && m.Body.Len() != 0 {
mb, err := m.Body.Marshal()
if err != nil {
return nil, err
}
b = append(b, mb...)
}
if icmpv6 {
if psh == nil { // cannot calculate checkshum here
return b, nil
}
off, l := 2*net.IPv6len, len(b)-len(psh)
b[off], b[off+1], b[off+2], b[off+3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
}
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
// 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
}
// ParseMessage parses b as an ICMP message. Proto must be
// iana.ProtocolICMP or iana.ProtocolIPv6ICMP.
func ParseMessage(proto int, b []byte) (*Message, error) {
if len(b) < 4 {
return nil, errors.New("message too short")
}
var err error
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
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
default:
return nil, errors.New("unknown protocol")
}
}
// 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_test
import (
"net"
"reflect"
"testing"
"code.google.com/p/go.net/internal/iana"
"code.google.com/p/go.net/internal/icmp"
"code.google.com/p/go.net/ipv4"
"code.google.com/p/go.net/ipv6"
)
var marshalAndParseMessageForIPv4Tests = []icmp.Message{
{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: 1, Seq: 2,
Data: []byte("HELLO-R-U-THERE"),
},
},
{
Type: ipv4.ICMPTypePhoturis,
Body: &icmp.DefaultMessageBody{
Data: []byte{0x80, 0x40, 0x20, 0x10},
},
},
}
func TestMarshalAndParseMessageForIPv4(t *testing.T) {
for _, tt := range marshalAndParseMessageForIPv4Tests {
b, err := tt.Marshal(nil)
if err != nil {
t.Fatal(err)
}
m, err := icmp.ParseMessage(iana.ProtocolICMP, b)
if err != nil {
t.Fatal(err)
}
if m.Type != tt.Type || m.Code != tt.Code {
t.Errorf("got %v; want %v", m, &tt)
}
if !reflect.DeepEqual(m.Body, tt.Body) {
t.Errorf("got %v; want %v", m.Body, tt.Body)
}
}
}
var marshalAndParseMessageForIPv6Tests = []icmp.Message{
{
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: 1, Seq: 2,
Data: []byte("HELLO-R-U-THERE"),
},
},
{
Type: ipv6.ICMPTypeDuplicateAddressConfirmation,
Body: &icmp.DefaultMessageBody{
Data: []byte{0x80, 0x40, 0x20, 0x10},
},
},
}
func TestMarshalAndParseMessageForIPv6(t *testing.T) {
pshicmp := icmp.IPv6PseudoHeader(net.ParseIP("ff02::1"), net.ParseIP("fe80::1"))
for _, tt := range marshalAndParseMessageForIPv6Tests {
for _, psh := range [][]byte{pshicmp, nil} {
b, err := tt.Marshal(psh)
if err != nil {
t.Fatal(err)
}
m, err := icmp.ParseMessage(iana.ProtocolIPv6ICMP, b)
if err != nil {
t.Fatal(err)
}
if m.Type != tt.Type || m.Code != tt.Code {
t.Errorf("got %v; want %v", m, &tt)
}
if !reflect.DeepEqual(m.Body, tt.Body) {
t.Errorf("got %v; want %v", m.Body, tt.Body)
}
}
}
}
// 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
// A MessageBody represents an ICMP message body.
type MessageBody interface {
// Len returns the length of ICMP message body.
Len() int
// Marshal returns the binary enconding of ICMP message body.
Marshal() ([]byte, error)
}
// A DefaultMessageBody represents the default message body.
type DefaultMessageBody struct {
Data []byte // data
}
// Len implements the Len method of MessageBody interface.
func (p *DefaultMessageBody) Len() int {
if p == nil {
return 0
}
return len(p.Data)
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *DefaultMessageBody) Marshal() ([]byte, error) {
return p.Data, 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