go.net/ipv4: fix sprious lookahead on IPConn-based PacketConn

Also improves test coverage for both payload and datagram I/O.

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7304091
This commit is contained in:
Mikio Hara
2013-02-16 13:02:07 +09:00
parent b38392a462
commit 6b91bf25ed
7 changed files with 297 additions and 143 deletions

View File

@@ -44,27 +44,24 @@ var (
}
// TODO(mikio): Add platform dependent wire header formats when
// we support new platforms.
testHeader = &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TOS: 1,
TotalLen: 0xbeef,
ID: 0xcafe,
FragOff: 1500,
TTL: 255,
Protocol: 1,
Checksum: 0xdead,
Src: net.IPv4(172, 16, 254, 254),
Dst: net.IPv4(192, 168, 0, 1),
}
)
func testHeader() *ipv4.Header {
h := &ipv4.Header{}
h.Version = ipv4.Version
h.Len = ipv4.HeaderLen
h.TOS = 1
h.TotalLen = 0xbeef
h.ID = 0xcafe
h.FragOff = 1500
h.TTL = 255
h.Protocol = 1
h.Checksum = 0xdead
h.Src = net.IPv4(172, 16, 254, 254)
h.Dst = net.IPv4(192, 168, 0, 1)
return h
}
func TestMarshalHeader(t *testing.T) {
th := testHeader()
b, err := th.Marshal()
b, err := testHeader.Marshal()
if err != nil {
t.Fatalf("ipv4.Header.Marshal failed: %v", err)
}
@@ -92,8 +89,7 @@ func TestParseHeader(t *testing.T) {
if err != nil {
t.Fatalf("ipv4.ParseHeader failed: %v", err)
}
th := testHeader()
if !reflect.DeepEqual(h, th) {
t.Fatalf("ipv4.ParseHeader failed: %#v not equal %#v", h, th)
if !reflect.DeepEqual(h, testHeader) {
t.Fatalf("ipv4.ParseHeader failed: %#v not equal %#v", h, testHeader)
}
}

View File

@@ -5,43 +5,117 @@
package ipv4_test
import (
"bytes"
"errors"
"flag"
)
var testExternal = flag.Bool("external", true, "allow use of external networks during long test")
func newICMPEchoRequest(id, seqnum, msglen int, filler []byte) []byte {
b := newICMPInfoMessage(id, seqnum, msglen, filler)
b[0] = 8
// calculate ICMP checksum
cklen := len(b)
s := uint32(0)
for i := 0; i < cklen-1; i += 2 {
s += uint32(b[i+1])<<8 | uint32(b[i])
}
if cklen&1 == 1 {
s += uint32(b[cklen-1])
}
s = (s >> 16) + (s & 0xffff)
s = s + (s >> 16)
// place checksum back in header; using ^= avoids the
// assumption the checksum bytes are zero
b[2] ^= byte(^s & 0xff)
b[3] ^= byte(^s >> 8)
return b
const (
icmpv4EchoRequest = 8
icmpv4EchoReply = 0
icmpv6EchoRequest = 128
icmpv6EchoReply = 129
)
// icmpMessage represents an ICMP message.
type icmpMessage struct {
Type int // type
Code int // code
Checksum int // checksum
Body icmpMessageBody // body
}
func newICMPInfoMessage(id, seqnum, msglen int, filler []byte) []byte {
b := make([]byte, msglen)
copy(b[8:], bytes.Repeat(filler, (msglen-8)/len(filler)+1))
b[0] = 0 // type
b[1] = 0 // code
b[2] = 0 // checksum
b[3] = 0 // checksum
b[4] = byte(id >> 8) // identifier
b[5] = byte(id & 0xff) // identifier
b[6] = byte(seqnum >> 8) // sequence number
b[7] = byte(seqnum & 0xff) // sequence number
return b
// icmpMessageBody represents an ICMP message body.
type icmpMessageBody interface {
Len() int
Marshal() ([]byte, error)
}
// Marshal returns the binary enconding of the ICMP echo request or
// reply message m.
func (m *icmpMessage) Marshal() ([]byte, error) {
b := []byte{byte(m.Type), byte(m.Code), 0, 0}
if m.Body != nil && m.Body.Len() != 0 {
mb, err := m.Body.Marshal()
if err != nil {
return nil, err
}
b = append(b, mb...)
}
switch m.Type {
case icmpv6EchoRequest, icmpv6EchoReply:
return b, nil
}
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[2] ^= byte(^s & 0xff)
b[3] ^= byte(^s >> 8)
return b, nil
}
// parseICMPMessage parses b as an ICMP message.
func parseICMPMessage(b []byte) (*icmpMessage, error) {
msglen := len(b)
if msglen < 4 {
return nil, errors.New("message too short")
}
m := &icmpMessage{Type: int(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
if msglen > 4 {
var err error
switch m.Type {
case icmpv4EchoRequest, icmpv4EchoReply, icmpv6EchoRequest, icmpv6EchoReply:
m.Body, err = parseICMPEcho(b[4:])
if err != nil {
return nil, err
}
}
}
return m, nil
}
// imcpEcho represenets an ICMP echo request or reply message body.
type icmpEcho struct {
ID int // identifier
Seq int // sequence number
Data []byte // data
}
func (p *icmpEcho) Len() int {
if p == nil {
return 0
}
return 4 + len(p.Data)
}
// Marshal returns the binary enconding of the ICMP echo request or
// reply message body p.
func (p *icmpEcho) Marshal() ([]byte, error) {
b := make([]byte, 4+len(p.Data))
b[0], b[1] = byte(p.ID>>8), byte(p.ID&0xff)
b[2], b[3] = byte(p.Seq>>8), byte(p.Seq&0xff)
copy(b[4:], p.Data)
return b, nil
}
// parseICMPEcho parses b as an ICMP echo request or reply message
// body.
func parseICMPEcho(b []byte) (*icmpEcho, error) {
bodylen := len(b)
p := &icmpEcho{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
}

View File

@@ -13,77 +13,70 @@ import (
"time"
)
// runPayloadTransponder transmits IPv4 datagram payloads to the
// writeThenReadPayload transmits IPv4 datagram payloads to the
// loopback address or interface and captures the loopback'd datagram
// payloads.
func runPayloadTransponder(t *testing.T, c *ipv4.PacketConn, wb []byte, dst net.Addr) {
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
func writeThenReadPayload(t *testing.T, i int, c *ipv4.PacketConn, wb []byte, dst net.Addr) []byte {
rb := make([]byte, 1500)
for i, toggle := range []bool{true, false, true} {
if err := c.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err)
}
c.SetTOS(i + 1)
var ip net.IP
switch v := dst.(type) {
case *net.UDPAddr:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip.IsMulticast() {
c.SetMulticastTTL(i + 1)
} else {
c.SetTTL(i + 1)
}
c.SetDeadline(time.Now().Add(100 * time.Millisecond))
if _, err := c.WriteTo(wb, nil, dst); err != nil {
t.Fatalf("ipv4.PacketConn.WriteTo failed: %v", err)
}
_, cm, _, err := c.ReadFrom(rb)
if err != nil {
t.Fatalf("ipv4.PacketConn.ReadFrom failed: %v", err)
}
t.Logf("rcvd cmsg: %v", cm)
c.SetTOS(i + 1)
var ip net.IP
switch v := dst.(type) {
case *net.UDPAddr:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip.IsMulticast() {
c.SetMulticastTTL(i + 1)
} else {
c.SetTTL(i + 1)
}
c.SetDeadline(time.Now().Add(100 * time.Millisecond))
if _, err := c.WriteTo(wb, nil, dst); err != nil {
t.Fatalf("ipv4.PacketConn.WriteTo failed: %v", err)
}
n, cm, _, err := c.ReadFrom(rb)
if err != nil {
t.Fatalf("ipv4.PacketConn.ReadFrom failed: %v", err)
}
t.Logf("rcvd cmsg: %v", cm)
return rb[:n]
}
// runDatagramTransponder transmits ICMP for IPv4 datagrams to the
// writeThenReadDatagram transmits ICMP for IPv4 datagrams to the
// loopback address or interface and captures the response datagrams
// from the protocol stack within the kernel.
func runDatagramTransponder(t *testing.T, c *ipv4.RawConn, wb []byte, src, dst net.Addr) {
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
func writeThenReadDatagram(t *testing.T, i int, c *ipv4.RawConn, wb []byte, src, dst net.Addr) []byte {
rb := make([]byte, ipv4.HeaderLen+len(wb))
for i, toggle := range []bool{true, false, true} {
if err := c.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.RawConn.SetControlMessage failed: %v", err)
}
wh := &ipv4.Header{}
wh.Version = ipv4.Version
wh.Len = ipv4.HeaderLen
wh.TOS = i + 1
wh.TotalLen = ipv4.HeaderLen + len(wb)
wh.TTL = i + 1
wh.Protocol = 1
if src != nil {
wh.Src = src.(*net.IPAddr).IP
}
if dst != nil {
wh.Dst = dst.(*net.IPAddr).IP
}
c.SetDeadline(time.Now().Add(100 * time.Millisecond))
if err := c.WriteTo(wh, wb, nil); err != nil {
t.Fatalf("ipv4.RawConn.WriteTo failed: %v", err)
}
rh, _, cm, err := c.ReadFrom(rb)
if err != nil {
t.Fatalf("ipv4.RawConn.ReadFrom failed: %v", err)
}
t.Logf("rcvd cmsg: %v", cm.String())
t.Logf("rcvd hdr: %v", rh.String())
wh := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TOS: i + 1,
TotalLen: ipv4.HeaderLen + len(wb),
TTL: i + 1,
Protocol: 1,
}
if src != nil {
wh.Src = src.(*net.IPAddr).IP
}
if dst != nil {
wh.Dst = dst.(*net.IPAddr).IP
}
c.SetDeadline(time.Now().Add(100 * time.Millisecond))
if err := c.WriteTo(wh, wb, nil); err != nil {
t.Fatalf("ipv4.RawConn.WriteTo failed: %v", err)
}
rh, b, cm, err := c.ReadFrom(rb)
if err != nil {
t.Fatalf("ipv4.RawConn.ReadFrom failed: %v", err)
}
t.Logf("rcvd cmsg: %v", cm.String())
t.Logf("rcvd hdr: %v", rh.String())
return b
}
// LoopbackInterface returns a logical network interface for loopback
// tests.
func loopbackInterface() *net.Interface {
ift, err := net.Interfaces()
if err != nil {
@@ -97,12 +90,13 @@ func loopbackInterface() *net.Interface {
return nil
}
func isGoodForMulticast(ifi *net.Interface) (net.IP, bool) {
if ifi.Flags&net.FlagUp == 0 {
// isMulticastAvailable returns true if ifi is a multicast access
// enabled network interface. It also returns a unicast IPv4 address
// that can be used for listening on ifi.
func isMulticastAvailable(ifi *net.Interface) (net.IP, bool) {
if ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
return nil, false
}
// We need a unicast IPv4 address that can be used to specify
// the IPv4 multicast interface.
ifat, err := ifi.Addrs()
if err != nil {
return nil, false
@@ -126,8 +120,5 @@ func isGoodForMulticast(ifi *net.Interface) (net.IP, bool) {
}
break
}
if ip == nil {
return nil, false
}
return ip, true
}

View File

@@ -45,7 +45,13 @@ func TestReadWriteMulticastIPPayloadUDP(t *testing.T) {
if err := p.SetMulticastLoopback(true); err != nil {
t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err)
}
runPayloadTransponder(t, p, []byte("HELLO-R-U-THERE"), dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
if err := p.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err)
}
writeThenReadPayload(t, i, p, []byte("HELLO-R-U-THERE"), dst)
}
}
func TestReadWriteMulticastIPPayloadICMP(t *testing.T) {
@@ -81,9 +87,30 @@ func TestReadWriteMulticastIPPayloadICMP(t *testing.T) {
if err := p.SetMulticastInterface(ifi); err != nil {
t.Fatalf("ipv4.PacketConn.SetMulticastInterface failed: %v", err)
}
id := os.Getpid() & 0xffff
pld := newICMPEchoRequest(id, 1, 128, []byte("HELLO-R-U-THERE"))
runPayloadTransponder(t, p, pld, dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
wb, err := (&icmpMessage{
Type: icmpv4EchoRequest, Code: 0,
Body: &icmpEcho{
ID: os.Getpid() & 0xffff, Seq: i + 1,
Data: []byte("HELLO-R-U-THERE"),
},
}).Marshal()
if err != nil {
t.Fatalf("icmpMessage.Marshal failed: %v", err)
}
if err := p.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err)
}
rb := writeThenReadPayload(t, i, p, wb, dst)
m, err := parseICMPMessage(rb)
if err != nil {
t.Fatalf("parseICMPMessage failed: %v", err)
}
if m.Type != icmpv4EchoReply || m.Code != 0 {
t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, icmpv4EchoReply, 0)
}
}
}
func TestReadWriteMulticastIPDatagram(t *testing.T) {
@@ -122,7 +149,28 @@ func TestReadWriteMulticastIPDatagram(t *testing.T) {
if err := r.SetMulticastInterface(ifi); err != nil {
t.Fatalf("ipv4.PacketConn.SetMulticastInterface failed: %v", err)
}
id := os.Getpid() & 0xffff
pld := newICMPEchoRequest(id, 1, 128, []byte("HELLO-R-U-THERE"))
runDatagramTransponder(t, r, pld, nil, dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
wb, err := (&icmpMessage{
Type: icmpv4EchoRequest, Code: 0,
Body: &icmpEcho{
ID: os.Getpid() & 0xffff, Seq: i + 1,
Data: []byte("HELLO-R-U-THERE"),
},
}).Marshal()
if err != nil {
t.Fatalf("icmpMessage.Marshal failed: %v", err)
}
if err := r.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.RawConn.SetControlMessage failed: %v", err)
}
rb := writeThenReadDatagram(t, i, r, wb, nil, dst)
m, err := parseICMPMessage(rb)
if err != nil {
t.Fatalf("parseICMPMessage failed: %v", err)
}
if m.Type != icmpv4EchoReply || m.Code != 0 {
t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, icmpv4EchoReply, 0)
}
}
}

View File

@@ -43,7 +43,7 @@ func TestUDPSingleConnWithMultipleGroupListeners(t *testing.T) {
t.Fatalf("net.Interfaces failed: %v", err)
}
for i, ifi := range ift {
if _, ok := isGoodForMulticast(&ifi); !ok {
if _, ok := isMulticastAvailable(&ifi); !ok {
continue
}
if err := p.JoinGroup(&ifi, tt.gaddr); err != nil {
@@ -90,7 +90,7 @@ func TestUDPMultipleConnWithMultipleGroupListeners(t *testing.T) {
t.Fatalf("net.Interfaces failed: %v", err)
}
for i, ifi := range ift {
if _, ok := isGoodForMulticast(&ifi); !ok {
if _, ok := isMulticastAvailable(&ifi); !ok {
continue
}
for _, p := range ps {
@@ -139,7 +139,7 @@ func TestIPSingleConnWithSingleGroupListener(t *testing.T) {
t.Fatalf("net.Interfaces failed: %v", err)
}
for i, ifi := range ift {
if _, ok := isGoodForMulticast(&ifi); !ok {
if _, ok := isMulticastAvailable(&ifi); !ok {
continue
}
if err := r.JoinGroup(&ifi, gaddr); err != nil {
@@ -172,7 +172,7 @@ func TestUDPPerInterfaceSingleConnWithSingleGroupListener(t *testing.T) {
t.Fatalf("net.Interfaces failed: %v", err)
}
for i, ifi := range ift {
ip, ok := isGoodForMulticast(&ifi)
ip, ok := isMulticastAvailable(&ifi)
if !ok {
continue
}
@@ -217,7 +217,7 @@ func TestIPPerInterfaceSingleConnWithSingleGroupListener(t *testing.T) {
t.Fatalf("net.Interfaces failed: %v", err)
}
for i, ifi := range ift {
ip, ok := isGoodForMulticast(&ifi)
ip, ok := isMulticastAvailable(&ifi)
if !ok {
continue
}

View File

@@ -33,11 +33,11 @@ func (c *payloadHandler) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.
return 0, nil, nil, err
}
case *net.IPConn:
nb := make([]byte, len(b)+maxHeaderLen)
nb := make([]byte, maxHeaderLen+len(b))
if n, oobn, _, src, err = rd.ReadMsgIP(nb, oob); err != nil {
return 0, nil, nil, err
}
hdrlen := (int(b[0]) & 0x0f) << 2
hdrlen := int(nb[0]&0x0f) << 2
copy(b, nb[hdrlen:])
n -= hdrlen
default:

View File

@@ -24,9 +24,14 @@ func TestReadWriteUnicastIPPayloadUDP(t *testing.T) {
if err != nil {
t.Fatalf("net.ResolveUDPAddr failed: %v", err)
}
p := ipv4.NewPacketConn(c)
runPayloadTransponder(t, p, []byte("HELLO-R-U-THERE"), dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
if err := p.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err)
}
writeThenReadPayload(t, i, p, []byte("HELLO-R-U-THERE"), dst)
}
}
func TestReadWriteUnicastIPPayloadICMP(t *testing.T) {
@@ -45,11 +50,31 @@ func TestReadWriteUnicastIPPayloadICMP(t *testing.T) {
if err != nil {
t.Fatalf("ResolveIPAddr failed: %v", err)
}
p := ipv4.NewPacketConn(c)
id := os.Getpid() & 0xffff
pld := newICMPEchoRequest(id, 1, 128, []byte("HELLO-R-U-THERE"))
runPayloadTransponder(t, p, pld, dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
wb, err := (&icmpMessage{
Type: icmpv4EchoRequest, Code: 0,
Body: &icmpEcho{
ID: os.Getpid() & 0xffff, Seq: i + 1,
Data: []byte("HELLO-R-U-THERE"),
},
}).Marshal()
if err != nil {
t.Fatalf("icmpMessage.Marshal failed: %v", err)
}
if err := p.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err)
}
rb := writeThenReadPayload(t, i, p, wb, dst)
m, err := parseICMPMessage(rb)
if err != nil {
t.Fatalf("parseICMPMessage failed: %v", err)
}
if m.Type != icmpv4EchoReply || m.Code != 0 {
t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, icmpv4EchoReply, 0)
}
}
}
func TestReadWriteUnicastIPDatagram(t *testing.T) {
@@ -68,12 +93,32 @@ func TestReadWriteUnicastIPDatagram(t *testing.T) {
if err != nil {
t.Fatalf("ResolveIPAddr failed: %v", err)
}
r, err := ipv4.NewRawConn(c)
if err != nil {
t.Fatalf("ipv4.NewRawConn failed: %v", err)
}
id := os.Getpid() & 0xffff
pld := newICMPEchoRequest(id, 1, 128, []byte("HELLO-R-U-THERE"))
runDatagramTransponder(t, r, pld, nil, dst)
cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
for i, toggle := range []bool{true, false, true} {
wb, err := (&icmpMessage{
Type: icmpv4EchoRequest, Code: 0,
Body: &icmpEcho{
ID: os.Getpid() & 0xffff, Seq: i + 1,
Data: []byte("HELLO-R-U-THERE"),
},
}).Marshal()
if err != nil {
t.Fatalf("icmpMessage.Marshal failed: %v", err)
}
if err := r.SetControlMessage(cf, toggle); err != nil {
t.Fatalf("ipv4.RawConn.SetControlMessage failed: %v", err)
}
rb := writeThenReadDatagram(t, i, r, wb, nil, dst)
m, err := parseICMPMessage(rb)
if err != nil {
t.Fatalf("parseICMPMessage failed: %v", err)
}
if m.Type != icmpv4EchoReply || m.Code != 0 {
t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, icmpv4EchoReply, 0)
}
}
}