Browse Source

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
Mikio Hara 11 years ago
parent
commit
8916b1d9cd
5 changed files with 311 additions and 0 deletions
  1. 45 0
      internal/icmp/echo.go
  2. 23 0
      internal/icmp/ipv6.go
  3. 122 0
      internal/icmp/message.go
  4. 89 0
      internal/icmp/message_test.go
  5. 32 0
      internal/icmp/messagebody.go

+ 45 - 0
internal/icmp/echo.go

@@ -0,0 +1,45 @@
+// 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
+}

+ 23 - 0
internal/icmp/ipv6.go

@@ -0,0 +1,23 @@
+// 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
+}

+ 122 - 0
internal/icmp/message.go

@@ -0,0 +1,122 @@
+// 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")
+	}
+}

+ 89 - 0
internal/icmp/message_test.go

@@ -0,0 +1,89 @@
+// 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)
+			}
+		}
+	}
+}

+ 32 - 0
internal/icmp/messagebody.go

@@ -0,0 +1,32 @@
+// 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
+}