diff options
author | Mikio Hara <mikioh.mikioh@gmail.com> | 2015-01-21 20:17:41 +0900 |
---|---|---|
committer | Mikio Hara <mikioh.mikioh@gmail.com> | 2015-02-06 15:44:19 +0000 |
commit | c84eff7014eba178f68bd4c05b86780efe0fbf35 (patch) | |
tree | f3d7677021a934b39e0272460d5fd0d18b36d724 | |
parent | d534621e649a469ca9f9bdb141e2dde4d764b508 (diff) | |
download | net-c84eff7014eba178f68bd4c05b86780efe0fbf35.tar.gz |
icmp: add extensions for interface and next-hop identification
This change implements ICMP extensions for interface and next-hop
identification which are used for route trace applications as described
in RFC 5837.
Change-Id: I2435109b5e766e743b894b0280a537324975489d
Reviewed-on: https://go-review.googlesource.com/3112
Reviewed-by: Ian Lance Taylor <iant@golang.org>
-rw-r--r-- | icmp/extension.go | 6 | ||||
-rw-r--r-- | icmp/extension_test.go | 82 | ||||
-rw-r--r-- | icmp/interface.go | 232 | ||||
-rw-r--r-- | icmp/message.go | 2 | ||||
-rw-r--r-- | icmp/multipart.go | 6 | ||||
-rw-r--r-- | icmp/multipart_test.go | 94 |
6 files changed, 421 insertions, 1 deletions
diff --git a/icmp/extension.go b/icmp/extension.go index 37af0e1..720e167 100644 --- a/icmp/extension.go +++ b/icmp/extension.go @@ -74,6 +74,12 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) { return nil, -1, err } exts = append(exts, ext) + case classInterfaceInfo: + ext, err := parseInterfaceInfo(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) } b = b[ol:] } diff --git a/icmp/extension_test.go b/icmp/extension_test.go index 6ed9e70..488c36b 100644 --- a/icmp/extension_test.go +++ b/icmp/extension_test.go @@ -5,6 +5,7 @@ package icmp import ( + "net" "reflect" "testing" @@ -90,6 +91,78 @@ var marshalAndParseExtensionTests = []struct { }, }, }, + // Interface information with no attribute + { + proto: iana.ProtocolICMP, + hdr: []byte{ + 0x20, 0x00, 0x00, 0x00, + }, + obj: []byte{ + 0x00, 0x04, 0x02, 0x00, + }, + exts: []Extension{ + &InterfaceInfo{ + Class: classInterfaceInfo, + }, + }, + }, + // Interface information with ifIndex and name + { + proto: iana.ProtocolICMP, + hdr: []byte{ + 0x20, 0x00, 0x00, 0x00, + }, + obj: []byte{ + 0x00, 0x10, 0x02, 0x0a, + 0x00, 0x00, 0x00, 0x10, + 0x08, byte('e'), byte('n'), byte('1'), + byte('0'), byte('1'), 0x00, 0x00, + }, + exts: []Extension{ + &InterfaceInfo{ + Class: classInterfaceInfo, + Type: 0x0a, + Interface: &net.Interface{ + Index: 16, + Name: "en101", + }, + }, + }, + }, + // Interface information with ifIndex, IPAddr, name and MTU + { + proto: iana.ProtocolIPv6ICMP, + hdr: []byte{ + 0x20, 0x00, 0x00, 0x00, + }, + obj: []byte{ + 0x00, 0x28, 0x02, 0x0f, + 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x02, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x08, byte('e'), byte('n'), byte('1'), + byte('0'), byte('1'), 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, + }, + exts: []Extension{ + &InterfaceInfo{ + Class: classInterfaceInfo, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.ParseIP("fe80::1"), + Zone: "en101", + }, + }, + }, + }, } func TestMarshalAndParseExtension(t *testing.T) { @@ -104,6 +177,12 @@ func TestMarshalAndParseExtension(t *testing.T) { t.Errorf("#%v/%v: %v", i, j, err) continue } + case *InterfaceInfo: + b, err = ext.Marshal(tt.proto) + if err != nil { + t.Errorf("#%v/%v: %v", i, j, err) + continue + } } if !reflect.DeepEqual(b, tt.obj) { t.Errorf("#%v/%v: got %#v; want %#v", i, j, b, tt.obj) @@ -149,6 +228,9 @@ func TestMarshalAndParseExtension(t *testing.T) { case *MPLSLabelStack: want := tt.exts[j].(*MPLSLabelStack) t.Errorf("#%v/%v: got %#v; want %#v", i, j, ext, want) + case *InterfaceInfo: + want := tt.exts[j].(*InterfaceInfo) + t.Errorf("#%v/%v: got %#v; want %#v", i, j, ext, want) } } continue diff --git a/icmp/interface.go b/icmp/interface.go new file mode 100644 index 0000000..806b26b --- /dev/null +++ b/icmp/interface.go @@ -0,0 +1,232 @@ +// Copyright 2015 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" + "strings" + + "golang.org/x/net/internal/iana" +) + +const ( + classInterfaceInfo = 2 + + afiIPv4 = 1 + afiIPv6 = 2 +) + +const ( + attrMTU = 1 << iota + attrName + attrIPAddr + attrIfIndex +) + +// An InterfaceInfo represents interface and next-hop identification. +type InterfaceInfo struct { + Class int // extension object class number + Type int // extension object sub-type + Interface *net.Interface + Addr *net.IPAddr +} + +func (ifi *InterfaceInfo) nameLen() int { + if len(ifi.Interface.Name) > 63 { + return 64 + } + l := 1 + len(ifi.Interface.Name) + return (l + 3) &^ 3 +} + +func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) { + l = 4 + if ifi.Interface != nil && ifi.Interface.Index > 0 { + attrs |= attrIfIndex + l += 4 + if len(ifi.Interface.Name) > 0 { + attrs |= attrName + l += ifi.nameLen() + } + if ifi.Interface.MTU > 0 { + attrs |= attrMTU + l += 4 + } + } + if ifi.Addr != nil { + switch proto { + case iana.ProtocolICMP: + if ifi.Addr.IP.To4() != nil { + attrs |= attrIPAddr + l += 4 + net.IPv4len + } + case iana.ProtocolIPv6ICMP: + if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + attrs |= attrIPAddr + l += 4 + net.IPv6len + } + } + } + return +} + +// Len implements the Len method of Extension interface. +func (ifi *InterfaceInfo) Len(proto int) int { + _, l := ifi.attrsAndLen(proto) + return l +} + +// Marshal implements the Marshal method of Extension interface. +func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) { + attrs, l := ifi.attrsAndLen(proto) + b := make([]byte, l) + if err := ifi.marshal(proto, b, attrs, l); err != nil { + return nil, err + } + return b, nil +} + +func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error { + b[0], b[1] = byte(l>>8), byte(l) + b[2], b[3] = classInterfaceInfo, byte(ifi.Type) + for b = b[4:]; len(b) > 0 && attrs != 0; { + switch { + case attrs&attrIfIndex != 0: + b = ifi.marshalIfIndex(proto, b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b = ifi.marshalIPAddr(proto, b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b = ifi.marshalName(proto, b) + attrs &^= attrName + case attrs&attrMTU != 0: + b = ifi.marshalMTU(proto, b) + attrs &^= attrMTU + } + } + return nil +} + +func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte { + b[0], b[1], b[2], b[3] = byte(ifi.Interface.Index>>24), byte(ifi.Interface.Index>>16), byte(ifi.Interface.Index>>8), byte(ifi.Interface.Index) + return b[4:] +} + +func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.Index = int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) + return b[4:], nil +} + +func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte { + switch proto { + case iana.ProtocolICMP: + b[0], b[1] = byte(afiIPv4>>8), byte(afiIPv4) + copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4()) + b = b[4+net.IPv4len:] + case iana.ProtocolIPv6ICMP: + b[0], b[1] = byte(afiIPv6>>8), byte(afiIPv6) + copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16()) + b = b[4+net.IPv6len:] + } + return b +} + +func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + afi := int(b[0])<<8 | int(b[1]) + b = b[4:] + switch afi { + case afiIPv4: + if len(b) < net.IPv4len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv4len) + copy(ifi.Addr.IP, b[:net.IPv4len]) + b = b[net.IPv4len:] + case afiIPv6: + if len(b) < net.IPv6len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv6len) + copy(ifi.Addr.IP, b[:net.IPv6len]) + b = b[net.IPv6len:] + } + return b, nil +} + +func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte { + l := byte(ifi.nameLen()) + b[0] = l + copy(b[1:], []byte(ifi.Interface.Name)) + return b[l:] +} + +func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) { + if 4 > len(b) || len(b) < int(b[0]) { + return nil, errMessageTooShort + } + l := int(b[0]) + var name [63]byte + copy(name[:], b[1:l]) + ifi.Interface.Name = strings.Trim(string(name[:]), "\000") + return b[l:], nil +} + +func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte { + b[0], b[1], b[2], b[3] = byte(ifi.Interface.MTU>>24), byte(ifi.Interface.MTU>>16), byte(ifi.Interface.MTU>>8), byte(ifi.Interface.MTU) + return b[4:] +} + +func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.MTU = int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) + return b[4:], nil +} + +func parseInterfaceInfo(b []byte) (Extension, error) { + ifi := &InterfaceInfo{ + Class: int(b[2]), + Type: int(b[3]), + } + if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 { + ifi.Interface = &net.Interface{} + } + if ifi.Type&attrIPAddr != 0 { + ifi.Addr = &net.IPAddr{} + } + attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU) + for b = b[4:]; len(b) > 0 && attrs != 0; { + var err error + switch { + case attrs&attrIfIndex != 0: + b, err = ifi.parseIfIndex(b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b, err = ifi.parseIPAddr(b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b, err = ifi.parseName(b) + attrs &^= attrName + case attrs&attrMTU != 0: + b, err = ifi.parseMTU(b) + attrs &^= attrMTU + } + if err != nil { + return nil, err + } + } + if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + ifi.Addr.Zone = ifi.Interface.Name + } + return ifi, nil +} diff --git a/icmp/message.go b/icmp/message.go index 13b57f7..654c519 100644 --- a/icmp/message.go +++ b/icmp/message.go @@ -9,6 +9,8 @@ // 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. package icmp // import "golang.org/x/net/icmp" import ( diff --git a/icmp/multipart.go b/icmp/multipart.go index 3f89a76..54ac8bc 100644 --- a/icmp/multipart.go +++ b/icmp/multipart.go @@ -65,6 +65,12 @@ func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]by return nil, err } off += ext.Len(proto) + case *InterfaceInfo: + attrs, l := ext.attrsAndLen(proto) + if err := ext.marshal(proto, b[off:], attrs, l); err != nil { + return nil, err + } + off += ext.Len(proto) } } s := checksum(b[dataLen+4:]) diff --git a/icmp/multipart_test.go b/icmp/multipart_test.go index 505db86..9248e47 100644 --- a/icmp/multipart_test.go +++ b/icmp/multipart_test.go @@ -34,6 +34,18 @@ var marshalAndParseMultipartMessageForIPv4Tests = []icmp.Message{ }, }, }, + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.IPv4(192, 168, 0, 1).To4(), + }, + }, }, }, }, @@ -42,6 +54,18 @@ var marshalAndParseMultipartMessageForIPv4Tests = []icmp.Message{ Body: &icmp.TimeExceeded{ Data: []byte("ERROR-INVOKING-PACKET"), Extensions: []icmp.Extension{ + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.IPv4(192, 168, 0, 1).To4(), + }, + }, &icmp.MPLSLabelStack{ Class: 1, Type: 1, @@ -75,6 +99,30 @@ var marshalAndParseMultipartMessageForIPv4Tests = []icmp.Message{ }, }, }, + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.IPv4(192, 168, 0, 1).To4(), + }, + }, + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x2f, + Interface: &net.Interface{ + Index: 16, + Name: "en102", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.IPv4(192, 168, 0, 2).To4(), + }, + }, }, }, }, @@ -100,7 +148,7 @@ func TestMarshalAndParseMultipartMessageForIPv4(t *testing.T) { case ipv4.ICMPTypeDestinationUnreachable: got, want := m.Body.(*icmp.DstUnreach), tt.Body.(*icmp.DstUnreach) if !reflect.DeepEqual(got.Extensions, want.Extensions) { - t.Errorf("#%v: got %#v; want %#v", i, got.Extensions, want.Extensions) + t.Error(dumpExtensions(i, got.Extensions, want.Extensions)) } if len(got.Data) != 128 { t.Errorf("#%v: got %v; want 128", i, len(got.Data)) @@ -143,6 +191,19 @@ var marshalAndParseMultipartMessageForIPv6Tests = []icmp.Message{ }, }, }, + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.ParseIP("fe80::1"), + Zone: "en101", + }, + }, }, }, }, @@ -151,6 +212,19 @@ var marshalAndParseMultipartMessageForIPv6Tests = []icmp.Message{ Body: &icmp.TimeExceeded{ Data: []byte("ERROR-INVOKING-PACKET"), Extensions: []icmp.Extension{ + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x0f, + Interface: &net.Interface{ + Index: 15, + Name: "en101", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.ParseIP("fe80::1"), + Zone: "en101", + }, + }, &icmp.MPLSLabelStack{ Class: 1, Type: 1, @@ -163,6 +237,19 @@ var marshalAndParseMultipartMessageForIPv6Tests = []icmp.Message{ }, }, }, + &icmp.InterfaceInfo{ + Class: 2, + Type: 0x2f, + Interface: &net.Interface{ + Index: 16, + Name: "en102", + MTU: 8192, + }, + Addr: &net.IPAddr{ + IP: net.ParseIP("fe80::1"), + Zone: "en102", + }, + }, }, }, }, @@ -217,6 +304,11 @@ func dumpExtensions(i int, gotExts, wantExts []icmp.Extension) string { if !reflect.DeepEqual(got, want) { s += fmt.Sprintf("#%v/%v: got %#v; want %#v\n", i, j, got, want) } + case *icmp.InterfaceInfo: + want := wantExts[j].(*icmp.InterfaceInfo) + if !reflect.DeepEqual(got, want) { + s += fmt.Sprintf("#%v/%v: got %#v, %#v, %#v; want %#v, %#v, %#v\n", i, j, got, got.Interface, got.Addr, want, want.Interface, want.Addr) + } } } return s[:len(s)-1] |