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
diff --git a/ipv4/header_test.go b/ipv4/header_test.go
index 0ac02ed..cc2fd8e 100644
--- a/ipv4/header_test.go
+++ b/ipv4/header_test.go
@@ -44,27 +44,24 @@
 	}
 	// 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 @@
 	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)
 	}
 }
diff --git a/ipv4/mockicmp_test.go b/ipv4/mockicmp_test.go
index 5bf8920..7b9ee06 100644
--- a/ipv4/mockicmp_test.go
+++ b/ipv4/mockicmp_test.go
@@ -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
 }
diff --git a/ipv4/mocktransponder_test.go b/ipv4/mocktransponder_test.go
index ef682bd..357721d 100644
--- a/ipv4/mocktransponder_test.go
+++ b/ipv4/mocktransponder_test.go
@@ -13,77 +13,70 @@
 	"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 @@
 	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 @@
 		}
 		break
 	}
-	if ip == nil {
-		return nil, false
-	}
 	return ip, true
 }
diff --git a/ipv4/multicast_test.go b/ipv4/multicast_test.go
index 01cc2a1..a5c470d 100644
--- a/ipv4/multicast_test.go
+++ b/ipv4/multicast_test.go
@@ -45,7 +45,13 @@
 	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 @@
 	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 @@
 	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)
+		}
+	}
 }
diff --git a/ipv4/multicastlistener_test.go b/ipv4/multicastlistener_test.go
index 4199c05..2ece9a7 100644
--- a/ipv4/multicastlistener_test.go
+++ b/ipv4/multicastlistener_test.go
@@ -43,7 +43,7 @@
 			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 @@
 			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 @@
 		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 @@
 		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 @@
 		t.Fatalf("net.Interfaces failed: %v", err)
 	}
 	for i, ifi := range ift {
-		ip, ok := isGoodForMulticast(&ifi)
+		ip, ok := isMulticastAvailable(&ifi)
 		if !ok {
 			continue
 		}
diff --git a/ipv4/payload.go b/ipv4/payload.go
index 9b3c2c9..7580d78 100644
--- a/ipv4/payload.go
+++ b/ipv4/payload.go
@@ -33,11 +33,11 @@
 			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:
diff --git a/ipv4/unicast_test.go b/ipv4/unicast_test.go
index 3b905a0..bddfb97 100644
--- a/ipv4/unicast_test.go
+++ b/ipv4/unicast_test.go
@@ -24,9 +24,14 @@
 	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 @@
 	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 @@
 	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)
+		}
+	}
 }