go.net/ipv4: new package

Package ipv4 implements IP-level socket options for the Internet
Protocol version 4. It also provides raw IP socket access methods
including IPv4 header manipulation.

Fixes golang/go#3684.
Fixes golang/go#3820.

This CL requires CL 6426047;
net: add read, write message methods to IPConn, UDPConn

R=rsc, dave, alex.brainman
CC=gobot, golang-dev
https://golang.org/cl/6482044
diff --git a/ipv4/control.go b/ipv4/control.go
new file mode 100644
index 0000000..621c7ac
--- /dev/null
+++ b/ipv4/control.go
@@ -0,0 +1,47 @@
+// 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 ipv4
+
+import (
+	"fmt"
+	"net"
+	"sync"
+)
+
+type rawOpt struct {
+	mu     sync.Mutex
+	cflags ControlFlags
+}
+
+func (o *rawOpt) lock()                     { o.mu.Lock() }
+func (o *rawOpt) unlock()                   { o.mu.Unlock() }
+func (o *rawOpt) set(f ControlFlags)        { o.cflags |= f }
+func (o *rawOpt) clear(f ControlFlags)      { o.cflags ^= f }
+func (o *rawOpt) isset(f ControlFlags) bool { return o.cflags&f != 0 }
+
+type ControlFlags uint
+
+const (
+	FlagTTL       ControlFlags = 1 << iota // pass the TTL on the received packet
+	FlagSrc                                // pass the source address on the received packet
+	FlagDst                                // pass the destination address on the received packet
+	FlagInterface                          // pass the interface index on the received packet or outgoing packet
+)
+
+// A ControlMessage represents control information that contains per
+// packet IP-level option data.
+type ControlMessage struct {
+	TTL     int    // time-to-live
+	Src     net.IP // source address
+	Dst     net.IP // destination address
+	IfIndex int    // interface index
+}
+
+func (cm *ControlMessage) String() string {
+	if cm == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("ttl: %v, src: %v, dst: %v, ifindex: %v", cm.TTL, cm.Src, cm.Dst, cm.IfIndex)
+}
diff --git a/ipv4/control_bsd.go b/ipv4/control_bsd.go
new file mode 100644
index 0000000..63cd6c6
--- /dev/null
+++ b/ipv4/control_bsd.go
@@ -0,0 +1,112 @@
+// 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.
+
+// +build darwin freebsd netbsd openbsd
+
+package ipv4
+
+import (
+	"net"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error {
+	opt.lock()
+	defer opt.unlock()
+	if cf&FlagTTL != 0 {
+		if err := setIPv4ReceiveTTL(fd, on); err != nil {
+			return err
+		}
+		if on {
+			opt.set(FlagTTL)
+		} else {
+			opt.clear(FlagTTL)
+		}
+	}
+	if cf&FlagDst != 0 {
+		if err := setIPv4ReceiveDestinationAddress(fd, on); err != nil {
+			return err
+		}
+		if on {
+			opt.set(FlagDst)
+		} else {
+			opt.clear(FlagDst)
+		}
+	}
+	if cf&FlagInterface != 0 {
+		if err := setIPv4ReceiveInterface(fd, on); err != nil {
+			return err
+		}
+		if on {
+			opt.set(FlagInterface)
+		} else {
+			opt.clear(FlagInterface)
+		}
+	}
+	return nil
+}
+
+func newControlMessage(opt *rawOpt) (oob []byte) {
+	opt.lock()
+	defer opt.unlock()
+	if opt.isset(FlagTTL) {
+		b := make([]byte, syscall.CmsgSpace(1))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_RECVTTL
+		cmsg.SetLen(syscall.CmsgLen(1))
+		oob = append(oob, b...)
+	}
+	if opt.isset(FlagDst) {
+		b := make([]byte, syscall.CmsgSpace(net.IPv4len))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_RECVDSTADDR
+		cmsg.SetLen(syscall.CmsgLen(net.IPv4len))
+		oob = append(oob, b...)
+	}
+	if opt.isset(FlagInterface) {
+		b := make([]byte, syscall.CmsgSpace(syscall.SizeofSockaddrDatalink))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_RECVIF
+		cmsg.SetLen(syscall.CmsgLen(syscall.SizeofSockaddrDatalink))
+		oob = append(oob, b...)
+	}
+	return
+}
+
+func parseControlMessage(b []byte) (*ControlMessage, error) {
+	cmsgs, err := syscall.ParseSocketControlMessage(b)
+	if err != nil {
+		return nil, os.NewSyscallError("parse socket control message", err)
+	}
+	if len(b) == 0 {
+		return nil, nil
+	}
+	cm := &ControlMessage{}
+	for _, m := range cmsgs {
+		if m.Header.Level != syscall.IPPROTO_IP {
+			continue
+		}
+		switch m.Header.Type {
+		case syscall.IP_RECVTTL:
+			cm.TTL = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0])))
+		case syscall.IP_RECVDSTADDR:
+			v := m.Data[:4]
+			cm.Dst = net.IPv4(v[0], v[1], v[2], v[3])
+		case syscall.IP_RECVIF:
+			sadl := (*syscall.SockaddrDatalink)(unsafe.Pointer(&m.Data[0]))
+			cm.IfIndex = int(sadl.Index)
+		}
+	}
+	return cm, nil
+}
+
+func marshalControlMessage(cm *ControlMessage) []byte {
+	// TODO(mikio): Implement IP_PKTINFO stuff when OS X 10.8 comes
+	return nil
+}
diff --git a/ipv4/control_linux.go b/ipv4/control_linux.go
new file mode 100644
index 0000000..16f53d0
--- /dev/null
+++ b/ipv4/control_linux.go
@@ -0,0 +1,116 @@
+// 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 ipv4
+
+import (
+	"net"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+// Linux provides a convenient path control option IP_PKTINFO that
+// contains IP_SENDSRCADDR, IP_RECVDSTADDR, IP_RECVIF and IP_SENDIF.
+const pktinfo = FlagSrc | FlagDst | FlagInterface
+
+func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error {
+	opt.lock()
+	defer opt.unlock()
+	if cf&FlagTTL != 0 {
+		if err := setIPv4ReceiveTTL(fd, on); err != nil {
+			return err
+		}
+		if on {
+			opt.set(FlagTTL)
+		} else {
+			opt.clear(FlagTTL)
+		}
+	}
+	if cf&pktinfo != 0 {
+		if err := setIPv4PacketInfo(fd, on); err != nil {
+			return err
+		}
+		if on {
+			opt.set(cf & pktinfo)
+		} else {
+			opt.clear(cf & pktinfo)
+		}
+	}
+	return nil
+}
+
+func newControlMessage(opt *rawOpt) (oob []byte) {
+	opt.lock()
+	defer opt.unlock()
+	if opt.isset(FlagTTL) {
+		b := make([]byte, syscall.CmsgSpace(1))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_RECVTTL
+		cmsg.SetLen(syscall.CmsgLen(1))
+		oob = append(oob, b...)
+	}
+	if opt.isset(pktinfo) {
+		b := make([]byte, syscall.CmsgSpace(syscall.SizeofInet4Pktinfo))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_PKTINFO
+		cmsg.SetLen(syscall.CmsgLen(syscall.SizeofInet4Pktinfo))
+		oob = append(oob, b...)
+	}
+	return
+}
+
+func parseControlMessage(b []byte) (*ControlMessage, error) {
+	cmsgs, err := syscall.ParseSocketControlMessage(b)
+	if err != nil {
+		return nil, os.NewSyscallError("parse socket control message", err)
+	}
+	if len(b) == 0 {
+		return nil, nil
+	}
+	cm := &ControlMessage{}
+	for _, m := range cmsgs {
+		if m.Header.Level != syscall.IPPROTO_IP {
+			continue
+		}
+		switch m.Header.Type {
+		case syscall.IP_TTL:
+			cm.TTL = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0])))
+		case syscall.IP_PKTINFO:
+			pi := (*syscall.Inet4Pktinfo)(unsafe.Pointer(&m.Data[0]))
+			cm.IfIndex = int(pi.Ifindex)
+			cm.Dst = net.IPv4(pi.Addr[0], pi.Addr[1], pi.Addr[2], pi.Addr[3])
+		}
+	}
+	return cm, nil
+}
+
+func marshalControlMessage(cm *ControlMessage) (oob []byte) {
+	if cm == nil {
+		return
+	}
+	pi := &syscall.Inet4Pktinfo{}
+	pion := false
+	if ip := cm.Src.To4(); ip != nil {
+		copy(pi.Spec_dst[:], ip[0:net.IPv4len])
+		pion = true
+	}
+	if cm.IfIndex != 0 {
+		pi.Ifindex = int32(cm.IfIndex)
+		pion = true
+	}
+	if pion {
+		b := make([]byte, syscall.CmsgSpace(syscall.SizeofInet4Pktinfo))
+		cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
+		cmsg.Level = syscall.IPPROTO_IP
+		cmsg.Type = syscall.IP_PKTINFO
+		cmsg.SetLen(syscall.CmsgLen(syscall.SizeofInet4Pktinfo))
+		data := b[syscall.CmsgLen(0):]
+		copy(data[0:syscall.SizeofInet4Pktinfo], (*[syscall.SizeofInet4Pktinfo]byte)(unsafe.Pointer(pi))[:syscall.SizeofInet4Pktinfo])
+		oob = append(oob, b...)
+	}
+	return
+}
diff --git a/ipv4/control_plan9.go b/ipv4/control_plan9.go
new file mode 100644
index 0000000..df9d50b
--- /dev/null
+++ b/ipv4/control_plan9.go
@@ -0,0 +1,29 @@
+// 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 ipv4
+
+import (
+	"syscall"
+)
+
+func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func newControlMessage(opt *rawOpt) []byte {
+	// TODO(mikio): Implement this
+	return nil
+}
+
+func parseControlMessage(b []byte) (*ControlMessage, error) {
+	// TODO(mikio): Implement this
+	return nil, syscall.EPLAN9
+}
+
+func marshalControlMessage(cm *ControlMessage) []byte {
+	// TODO(mikio): Implement this
+	return nil
+}
diff --git a/ipv4/control_windows.go b/ipv4/control_windows.go
new file mode 100644
index 0000000..7b1d604
--- /dev/null
+++ b/ipv4/control_windows.go
@@ -0,0 +1,29 @@
+// 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 ipv4
+
+import (
+	"syscall"
+)
+
+func setControlMessage(fd syscall.Handle, opt *rawOpt, cf ControlFlags, on bool) error {
+	// TODO(mikio): Implement this
+	return syscall.EWINDOWS
+}
+
+func newControlMessage(opt *rawOpt) []byte {
+	// TODO(mikio): Implement this
+	return nil
+}
+
+func parseControlMessage(b []byte) (*ControlMessage, error) {
+	// TODO(mikio): Implement this
+	return nil, syscall.EWINDOWS
+}
+
+func marshalControlMessage(cm *ControlMessage) []byte {
+	// TODO(mikio): Implement this
+	return nil
+}
diff --git a/ipv4/dgramopt_plan9.go b/ipv4/dgramopt_plan9.go
new file mode 100644
index 0000000..b211d91
--- /dev/null
+++ b/ipv4/dgramopt_plan9.go
@@ -0,0 +1,50 @@
+// 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 ipv4
+
+import (
+	"net"
+	"syscall"
+)
+
+func (c *dgramOpt) MulticastTTL() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *dgramOpt) SetMulticastTTL(ttl int) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func (c *dgramOpt) MulticastInterface() (*net.Interface, error) {
+	// TODO(mikio): Implement this
+	return nil, syscall.EPLAN9
+}
+
+func (c *dgramOpt) SetMulticastInterface(ifi *net.Interface) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func (c *dgramOpt) MulticastLoopback() (bool, error) {
+	// TODO(mikio): Implement this
+	return false, syscall.EPLAN9
+}
+
+func (c *dgramOpt) SetMulticastLoopback(on bool) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func (c *dgramOpt) JoinGroup(ifi *net.Interface, grp net.Addr) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func (c *dgramOpt) LeaveGroup(ifi *net.Interface, grp net.Addr) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
diff --git a/ipv4/dgramopt_posix.go b/ipv4/dgramopt_posix.go
new file mode 100644
index 0000000..0ba81c2
--- /dev/null
+++ b/ipv4/dgramopt_posix.go
@@ -0,0 +1,125 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd windows
+
+package ipv4
+
+import (
+	"net"
+	"syscall"
+)
+
+// MulticastTTL returns the time-to-live field value for outgoing
+// multicast packets.
+func (c *dgramOpt) MulticastTTL() (int, error) {
+	if !c.ok() {
+		return 0, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return 0, err
+	}
+	return ipv4MulticastTTL(fd)
+}
+
+// SetMulticastTTL sets the time-to-live field value for future
+// outgoing multicast packets.
+func (c *dgramOpt) SetMulticastTTL(ttl int) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setIPv4MulticastTTL(fd, ttl)
+}
+
+// MulticastInterface returns the default interface for multicast
+// packet transmissions.
+func (c *dgramOpt) MulticastInterface() (*net.Interface, error) {
+	if !c.ok() {
+		return nil, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return nil, err
+	}
+	return ipv4MulticastInterface(fd)
+}
+
+// SetMulticastInterface sets the default interface for future
+// multicast packet transmissions.
+func (c *dgramOpt) SetMulticastInterface(ifi *net.Interface) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setIPv4MulticastInterface(fd, ifi)
+}
+
+// MulticastLoopback reports whether transmitted multicast packets
+// should be copied and send back to the originator.
+func (c *dgramOpt) MulticastLoopback() (bool, error) {
+	if !c.ok() {
+		return false, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return false, err
+	}
+	return ipv4MulticastLoopback(fd)
+}
+
+// SetMulticastLoopback sets whether transmitted multicast packets
+// should be copied and send back to the originator.
+func (c *dgramOpt) SetMulticastLoopback(on bool) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setIPv4MulticastLoopback(fd, on)
+}
+
+// JoinGroup joins the group address group on the interface ifi.
+// It uses the system assigned multicast interface when ifi is nil,
+// although this is not recommended because the assignment depends on
+// platforms and sometimes it might require routing configuration.
+func (c *dgramOpt) JoinGroup(ifi *net.Interface, group net.Addr) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	grp := netAddrToIP4(group)
+	if grp == nil {
+		return errMissingAddress
+	}
+	return joinIPv4Group(fd, ifi, grp)
+}
+
+// LeaveGroup leaves the group address group on the interface ifi.
+func (c *dgramOpt) LeaveGroup(ifi *net.Interface, group net.Addr) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	grp := netAddrToIP4(group)
+	if grp == nil {
+		return errMissingAddress
+	}
+	return leaveIPv4Group(fd, ifi, grp)
+}
diff --git a/ipv4/doc.go b/ipv4/doc.go
new file mode 100644
index 0000000..bfc6044
--- /dev/null
+++ b/ipv4/doc.go
@@ -0,0 +1,213 @@
+// 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 ipv4 implements IP-level socket options for the Internet
+// Protocol version 4.
+//
+// The package provides IP-level socket options that allow
+// manipulation of IPv4 facilities.  The IPv4 and basic host
+// requirements for IPv4 are defined in RFC 791, 1112 and 1122.  A
+// series of RFCs 2474, 2475, 2597, 2598 and 3168 describe how to use
+// the type-of-service field in a DiffServ, differentiated services
+// environment.
+//
+//
+// Unicasting
+//
+// The options for unicasting are available for net.TCPConn,
+// net.UDPConn and net.IPConn which are created as network connections
+// that use the IPv4 transport.  When a single TCP connection carrying
+// a data flow of multiple packets needs to indicate the flow is
+// important, ipv4.Conn is used to set the type-of-service field on
+// the IPv4 header for each packet.
+//
+//	ln, err := net.Listen("tcp4", "0.0.0.0:1024") 
+//	if err != nil {
+//		// error handling
+//	}
+//	defer ln.Close()
+//	for {
+//		c, err := ln.Accept()
+//		if err != nil {
+//			// error handling
+//		}
+//		go func(c net.Conn) {
+//			defer c.Close()
+//
+// The outgoing packets will be labeled DiffServ assured forwarding
+// class 1 low drop precedence, as known as AF11 packets.
+//
+//			err := ipv4.NewConn(c).SetTOS(ipv4.DSCP_AF11)
+//			if err != nil {
+//				// error handling
+//			}
+//			_, err = c.Write(data)
+//			if err != nil {
+//				// error handling
+//			}
+//		}(c)
+//	}
+//
+//
+// Multicasting
+//
+// The options for multicasting are available for net.UDPConn and
+// net.IPconn which are created as network connections that use the
+// IPv4 transport.  A few network facilities must be prepared before
+// you begin multicasting, at a minimum joining network interfaces and
+// group addresses.
+//
+//	en0, err := net.InterfaceByName("en0")
+//	if err != nil {
+//		// error handling
+//	}
+//	en1, err := net.InterfaceByIndex(911)
+//	if err != nil {
+//		// error handling
+//	}
+//	group := net.IPv4(224, 0, 0, 250)
+//
+// First, an application listens to an appropriate address with an
+// appropriate service port.
+//
+//	c, err := net.ListenPacket("udp4", "0.0.0.0:1024")
+//	if err != nil {
+//		// error handling
+//	}
+//	defer c.Close()
+//
+// Second, the application joins groups, starts listening to the
+// group addresses on the specified network interfaces.  Note that
+// the service port for transport layer protocol does not matter with
+// this operation as joining groups affects only network and link
+// layer protocols, such as IPv4 and Ethernet.
+//
+//	p := ipv4.NewPacketConn(c)
+//	err = p.JoinGroup(en0, &net.UDPAddr{IP: group})
+//	if err != nil {
+//		// error handling
+//	}
+//	err = p.JoinGroup(en1, &net.UDPAddr{IP: group})
+//	if err != nil {
+//		// error handling
+//	}
+//
+// The application might set per packet control message transmissions
+// between the protocol stack within the kernel.  When the application
+// needs a destination address on an incoming packet,
+// SetControlMessage of ipv4.PacketConn is used to enable control
+// message transmissons.
+//
+//	err = p.SetControlMessage(ipv4.FlagDst, true)
+//	if err != nil {
+//		// error handling
+//	}
+//
+// The application could identify whether the received packets are
+// of interest by using the control message that contains the
+// destination address of the received packet.
+//
+//	b := make([]byte, 1500)
+//	for {
+//		n, cm, src, err := p.Read(b)
+//		if err != nil {
+//			// error handling
+//		}
+//		if cm.Dst.IsMulticast() {
+//			if cm.Dst.Equal(group)
+//				// joined group, do something
+//			} else {
+//				// unknown group, discard
+//				continue
+//			}
+//		}
+//
+// The application can also send both unicast and multicast packets.
+//
+//		p.SetTOS(ipv4.DSCP_CS0)
+//		p.SetTTL(16)
+//		_, err = p.Write(data, nil, src)
+//		if err != nil {
+//			// error handling
+//		}
+//		dst := &net.UDPAddr{IP: group, Port: 1024}
+//		for _, ifi := range []*net.Interface{en0, en1} {
+//			err := p.SetMulticastInterface(ifi)
+//			if err != nil {
+//				// error handling
+//			}
+//			p.SetMulticastTTL(2)
+//			_, err = p.Write(data, nil, dst)
+//			if err != nil {
+//				// error handling
+//			}
+//		}
+//	}
+//
+//
+// More multicasting
+//
+// An application that uses PacketConn or RawConn might join the
+// multiple group addresses.  For example, a UDP listener with port
+// 1024 might join two different groups across over two different
+// network interfaces by using:
+//
+//	c, err := net.ListenPacket("udp4", "0.0.0.0:1024")
+//	if err != nil {
+//		// error handling
+//	}
+//	defer c.Close()
+//	p := ipv4.NewPacketConn(c)
+//	err = p.JoinGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 248)})
+//	if err != nil {
+//		// error handling
+//	}
+//	err = p.JoinGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 249)})
+//	if err != nil {
+//		// error handling
+//	}
+//	err = p.JoinGroup(en1, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 249)})
+//	if err != nil {
+//		// error handling
+//	}
+//
+// It is possible for multiple UDP listeners that listen on the same
+// UDP port to join the same group address.  The net package will
+// provide a socket that listens to a wildcard address with reusable
+// UDP port when an appropriate multicast address prefix is passed to
+// the net.ListenPacket or net.ListenUDP.
+//
+//	c1, err := net.ListenPacket("udp4", "224.0.0.0:1024")
+//	if err != nil {
+//		// error handling
+//	}
+//	defer c1.Close()
+//	c2, err := net.ListenPacket("udp4", "224.0.0.0:1024")
+//	if err != nil {
+//		// error handling
+//	}
+//	defer c2.Close()
+//	p1 := ipv4.NewPacketConn(c1)
+//	err = p1.JoinGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 248)})
+//	if err != nil {
+//		// error handling
+//	}
+//	p2 := ipv4.NewPacketConn(c2)
+//	err = p2.JoinGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 248)})
+//	if err != nil {
+//		// error handling
+//	}
+//
+// Also it is possible for the application to leave or rejoin a
+// multicast group on the network interface.
+//
+//	err = p.LeaveGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 248)})
+//	if err != nil {
+//		// error handling
+//	}
+//	err = p.JoinGroup(en0, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 250)})
+//	if err != nil {
+//		// error handling
+//	}
+package ipv4
diff --git a/ipv4/endpoint.go b/ipv4/endpoint.go
new file mode 100644
index 0000000..6e598b4
--- /dev/null
+++ b/ipv4/endpoint.go
@@ -0,0 +1,181 @@
+// 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 ipv4
+
+import (
+	"net"
+	"syscall"
+	"time"
+)
+
+// A Conn represents a network endpoint that uses the IPv4 transport.
+// It is used to control basic IP-level socket options such as TOS and
+// TTL.
+type Conn struct {
+	genericOpt
+}
+
+type genericOpt struct {
+	c net.Conn
+}
+
+func (c *genericOpt) ok() bool { return c != nil && c.c != nil }
+
+// NewConn returns a new Conn.
+func NewConn(c net.Conn) *Conn {
+	return &Conn{
+		genericOpt: genericOpt{c},
+	}
+}
+
+// A PacketConn represents a packet network endpoint that uses the
+// IPv4 transport.  It is used to control several IP-level socket
+// options including multicasting.  It also provides datagram based
+// network I/O methods specific to the IPv4 and higher layer protocols
+// such as UDP.
+type PacketConn struct {
+	genericOpt
+	dgramOpt
+	payloadHandler
+}
+
+type dgramOpt struct {
+	c net.PacketConn
+}
+
+func (c *dgramOpt) ok() bool { return c != nil && c.c != nil }
+
+// SetControlMessage sets the per packet IP-level socket options.
+func (c *PacketConn) SetControlMessage(cf ControlFlags, on bool) error {
+	if !c.payloadHandler.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.payloadHandler.sysfd()
+	if err != nil {
+		return err
+	}
+	return setControlMessage(fd, &c.payloadHandler.rawOpt, cf, on)
+}
+
+// SetDeadline sets the read and write deadlines associated with the
+// endpoint.
+func (c *PacketConn) SetDeadline(t time.Time) error {
+	if !c.payloadHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.payloadHandler.c.SetDeadline(t)
+}
+
+// SetReadDeadline sets the read deadline associated with the
+// endpoint.
+func (c *PacketConn) SetReadDeadline(t time.Time) error {
+	if !c.payloadHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.payloadHandler.c.SetReadDeadline(t)
+}
+
+// SetWriteDeadline sets the write deadline associated with the
+// endpoint.
+func (c *PacketConn) SetWriteDeadline(t time.Time) error {
+	if !c.payloadHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.payloadHandler.c.SetWriteDeadline(t)
+}
+
+// Close closes the endpoint.
+func (c *PacketConn) Close() error {
+	if !c.payloadHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.payloadHandler.c.Close()
+}
+
+// NewPacketConn returns a new PacketConn using c as its underlying
+// transport.
+func NewPacketConn(c net.PacketConn) *PacketConn {
+	return &PacketConn{
+		genericOpt:     genericOpt{c.(net.Conn)},
+		dgramOpt:       dgramOpt{c},
+		payloadHandler: payloadHandler{c: c},
+	}
+}
+
+// A RawConn represents a packet network endpoint that uses the IPv4
+// transport.  It is used to control several IP-level socket options
+// including IPv4 header manipulation.  It also provides datagram
+// based network I/O methods specific to the IPv4 and higher layer
+// protocols that handle IPv4 datagram directly such as OSPF, GRE.
+type RawConn struct {
+	genericOpt
+	dgramOpt
+	packetHandler
+}
+
+// SetControlMessage sets the per packet IP-level socket options.
+func (c *RawConn) SetControlMessage(cf ControlFlags, on bool) error {
+	if !c.packetHandler.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.packetHandler.sysfd()
+	if err != nil {
+		return err
+	}
+	return setControlMessage(fd, &c.packetHandler.rawOpt, cf, on)
+}
+
+// SetDeadline sets the read and write deadlines associated with the
+// endpoint.
+func (c *RawConn) SetDeadline(t time.Time) error {
+	if !c.packetHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.packetHandler.c.SetDeadline(t)
+}
+
+// SetReadDeadline sets the read deadline associated with the
+// endpoint.
+func (c *RawConn) SetReadDeadline(t time.Time) error {
+	if !c.packetHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.packetHandler.c.SetReadDeadline(t)
+}
+
+// SetWriteDeadline sets the write deadline associated with the
+// endpoint.
+func (c *RawConn) SetWriteDeadline(t time.Time) error {
+	if !c.packetHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.packetHandler.c.SetWriteDeadline(t)
+}
+
+// Close closes the endpoint.
+func (c *RawConn) Close() error {
+	if !c.packetHandler.ok() {
+		return syscall.EINVAL
+	}
+	return c.packetHandler.c.Close()
+}
+
+// NewRawConn returns a new RawConn using c as it sunderlying
+// transport.
+func NewRawConn(c net.PacketConn) (*RawConn, error) {
+	r := &RawConn{
+		genericOpt:    genericOpt{c.(net.Conn)},
+		dgramOpt:      dgramOpt{c},
+		packetHandler: packetHandler{c: c.(*net.IPConn)},
+	}
+	fd, err := r.packetHandler.sysfd()
+	if err != nil {
+		return nil, err
+	}
+	if err := setIPv4HeaderPrepend(fd, true); err != nil {
+		return nil, err
+	}
+	return r, nil
+}
diff --git a/ipv4/example_test.go b/ipv4/example_test.go
new file mode 100644
index 0000000..9f4644e
--- /dev/null
+++ b/ipv4/example_test.go
@@ -0,0 +1,252 @@
+// 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 ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"log"
+	"net"
+)
+
+func ExampleUnicastTCPListener() {
+	ln, err := net.Listen("tcp4", "0.0.0.0:1024")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer ln.Close()
+	for {
+		c, err := ln.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		go func(c net.Conn) {
+			defer c.Close()
+			err := ipv4.NewConn(c).SetTOS(ipv4.DSCP_AF11)
+			if err != nil {
+				log.Fatal(err)
+			}
+			_, err = c.Write([]byte("HELLO-R-U-THERE-ACK"))
+			if err != nil {
+				log.Fatal(err)
+			}
+		}(c)
+	}
+}
+
+func ExampleMulticastUDPListener() {
+	en0, err := net.InterfaceByName("en0")
+	if err != nil {
+		log.Fatal(err)
+	}
+	en1, err := net.InterfaceByIndex(911)
+	if err != nil {
+		log.Fatal(err)
+	}
+	group := net.IPv4(224, 0, 0, 250)
+
+	c, err := net.ListenPacket("udp4", "0.0.0.0:1024")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer c.Close()
+
+	p := ipv4.NewPacketConn(c)
+	err = p.JoinGroup(en0, &net.UDPAddr{IP: group})
+	if err != nil {
+		log.Fatal(err)
+	}
+	err = p.JoinGroup(en1, &net.UDPAddr{IP: group})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	err = p.SetControlMessage(ipv4.FlagDst, true)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	b := make([]byte, 1500)
+	for {
+		n, cm, src, err := p.Read(b)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if cm.Dst.IsMulticast() {
+			if cm.Dst.Equal(group) {
+				// joined group, do something
+			} else {
+				// unknown group, discard
+				continue
+			}
+		}
+		p.SetTOS(ipv4.DSCP_CS7)
+		p.SetTTL(16)
+		_, err = p.Write(b[:n], nil, src)
+		if err != nil {
+			log.Fatal(err)
+		}
+		dst := &net.UDPAddr{IP: group, Port: 1024}
+		for _, ifi := range []*net.Interface{en0, en1} {
+			err := p.SetMulticastInterface(ifi)
+			if err != nil {
+				log.Fatal(err)
+			}
+			p.SetMulticastTTL(2)
+			_, err = p.Write(b[:n], nil, dst)
+			if err != nil {
+				log.Fatal(err)
+			}
+		}
+	}
+
+	err = p.LeaveGroup(en1, &net.UDPAddr{IP: group})
+	if err != nil {
+		log.Fatal(err)
+	}
+	newgroup := net.IPv4(224, 0, 0, 249)
+	err = p.JoinGroup(en1, &net.UDPAddr{IP: newgroup})
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+type OSPFHeader struct {
+	Version  byte
+	Type     byte
+	Len      uint16
+	RouterID uint32
+	AreaID   uint32
+	Checksum uint16
+}
+
+const (
+	OSPFHeaderLen      = 14
+	OSPFHelloHeaderLen = 20
+	OSPF_VERSION       = 2
+	OSPF_TYPE_HELLO    = iota + 1
+	OSPF_TYPE_DB_DESCRIPTION
+	OSPF_TYPE_LS_REQUEST
+	OSPF_TYPE_LS_UPDATE
+	OSPF_TYPE_LS_ACK
+)
+
+var (
+	AllSPFRouters = net.IPv4(224, 0, 0, 5)
+	AllDRouters   = net.IPv4(224, 0, 0, 6)
+)
+
+func ExampleIPOSPFListener() {
+	var ifs []*net.Interface
+	en0, err := net.InterfaceByName("en0")
+	if err != nil {
+		log.Fatal(err)
+	}
+	ifs = append(ifs, en0)
+	en1, err := net.InterfaceByIndex(911)
+	if err != nil {
+		log.Fatal(err)
+	}
+	ifs = append(ifs, en1)
+
+	c, err := net.ListenPacket("ip4:89", "0.0.0.0")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer c.Close()
+
+	r, err := ipv4.NewRawConn(c)
+	if err != nil {
+		log.Fatal(err)
+	}
+	for _, ifi := range ifs {
+		err := r.JoinGroup(ifi, &net.IPAddr{IP: AllSPFRouters})
+		if err != nil {
+			log.Fatal(err)
+		}
+		err = r.JoinGroup(ifi, &net.IPAddr{IP: AllDRouters})
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	err = r.SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true)
+	if err != nil {
+		log.Fatal(err)
+	}
+	r.SetTOS(ipv4.DSCP_CS6)
+
+	parseOSPFHeader := func(b []byte) *OSPFHeader {
+		if len(b) < OSPFHeaderLen {
+			return nil
+		}
+		return &OSPFHeader{
+			Version:  b[0],
+			Type:     b[1],
+			Len:      uint16(b[2])<<8 | uint16(b[3]),
+			RouterID: uint32(b[4])<<24 | uint32(b[5])<<16 | uint32(b[6])<<8 | uint32(b[7]),
+			AreaID:   uint32(b[8])<<24 | uint32(b[9])<<16 | uint32(b[10])<<8 | uint32(b[11]),
+			Checksum: uint16(b[12])<<8 | uint16(b[13]),
+		}
+	}
+
+	b := make([]byte, 1500)
+	for {
+		iph, p, _, err := r.Read(b)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if iph.Version != ipv4.Version {
+			continue
+		}
+		if iph.Dst.IsMulticast() {
+			if !iph.Dst.Equal(AllSPFRouters) && !iph.Dst.Equal(AllDRouters) {
+				continue
+			}
+		}
+		ospfh := parseOSPFHeader(p)
+		if ospfh == nil {
+			continue
+		}
+		if ospfh.Version != OSPF_VERSION {
+			continue
+		}
+		switch ospfh.Type {
+		case OSPF_TYPE_HELLO:
+		case OSPF_TYPE_DB_DESCRIPTION:
+		case OSPF_TYPE_LS_REQUEST:
+		case OSPF_TYPE_LS_UPDATE:
+		case OSPF_TYPE_LS_ACK:
+		}
+	}
+}
+
+func ExampleWriteIPOSPFHello(c *ipv4.RawConn, ifs []*net.Interface) {
+	hello := make([]byte, OSPFHelloHeaderLen)
+
+	ospf := make([]byte, OSPFHeaderLen)
+	ospf[0] = OSPF_VERSION
+	ospf[1] = OSPF_TYPE_HELLO
+	ospf = append(ospf, hello...)
+
+	iph := &ipv4.Header{}
+	iph.Version = ipv4.Version
+	iph.Len = ipv4.HeaderLen
+	iph.TOS = ipv4.DSCP_CS6
+	iph.TotalLen = ipv4.HeaderLen + len(ospf)
+	iph.TTL = 1
+	iph.Protocol = 89
+	iph.Dst = AllSPFRouters
+
+	for _, ifi := range ifs {
+		err := c.SetMulticastInterface(ifi)
+		if err != nil {
+			return
+		}
+		err = c.Write(iph, ospf, nil)
+		if err != nil {
+			return
+		}
+	}
+}
diff --git a/ipv4/genericopt_plan9.go b/ipv4/genericopt_plan9.go
new file mode 100644
index 0000000..2dff61e
--- /dev/null
+++ b/ipv4/genericopt_plan9.go
@@ -0,0 +1,29 @@
+// 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 ipv4
+
+import (
+	"syscall"
+)
+
+func (c *genericOpt) TOS() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *genericOpt) SetTOS(tos int) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
+
+func (c *genericOpt) TTL() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *genericOpt) SetTTL(ttl int) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
diff --git a/ipv4/genericopt_posix.go b/ipv4/genericopt_posix.go
new file mode 100644
index 0000000..f1eab2d
--- /dev/null
+++ b/ipv4/genericopt_posix.go
@@ -0,0 +1,61 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd windows
+
+package ipv4
+
+import (
+	"syscall"
+)
+
+// TOS returns the type-of-service field value for outgoing packets.
+func (c *genericOpt) TOS() (int, error) {
+	if !c.ok() {
+		return 0, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return 0, err
+	}
+	return ipv4TOS(fd)
+}
+
+// SetTOS sets the type-of-service field value for future outgoing
+// packets.
+func (c *genericOpt) SetTOS(tos int) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setIPv4TOS(fd, tos)
+}
+
+// TTL returns the time-to-live field value for outgoing packets.
+func (c *genericOpt) TTL() (int, error) {
+	if !c.ok() {
+		return 0, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return 0, err
+	}
+	return ipv4TTL(fd)
+}
+
+// SetTTL sets the time-to-live field value for future outgoing
+// packets.
+func (c *genericOpt) SetTTL(ttl int) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setIPv4TTL(fd, ttl)
+}
diff --git a/ipv4/header.go b/ipv4/header.go
new file mode 100644
index 0000000..ab17da7
--- /dev/null
+++ b/ipv4/header.go
@@ -0,0 +1,194 @@
+// 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 ipv4
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"runtime"
+	"syscall"
+	"unsafe"
+)
+
+var (
+	errMissingAddress  = errors.New("missing address")
+	errMissingHeader   = errors.New("missing header")
+	errHeaderTooShort  = errors.New("header too short")
+	errBufferTooShort  = errors.New("buffer too short")
+	errInvalidConnType = errors.New("invalid conn type")
+)
+
+// References:
+//
+// RFC  791  Internet Protocol
+//	http://tools.ietf.org/html/rfc791
+// RFC 1112  Host Extensions for IP Multicasting
+//	http://tools.ietf.org/html/rfc1112
+// RFC 1122  Requirements for Internet Hosts
+//	http://tools.ietf.org/html/rfc1122
+// RFC 2474  Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers
+//	http://tools.ietf.org/html/rfc2474
+// RFC 2475  An Architecture for Differentiated Services
+//	http://tools.ietf.org/html/rfc2475
+// RFC 2597  Assured Forwarding PHB Group
+//	http://tools.ietf.org/html/rfc2597
+// RFC 2598  An Expedited Forwarding PHB
+//	http://tools.ietf.org/html/rfc2598
+// RFC 3168  The Addition of Explicit Congestion Notification (ECN) to IP
+//	http://tools.ietf.org/html/rfc3168
+// RFC 3260  New Terminology and Clarifications for Diffserv
+//	http://tools.ietf.org/html/rfc3260
+
+const (
+	Version      = 4  // protocol version
+	HeaderLen    = 20 // header length without extension headers
+	maxHeaderLen = 60 // sensible default, revisit if later RFCs define new usage of version and header length fields
+)
+
+const (
+	// DiffServ class selector codepoints in RFC 2474.
+	DSCP_CS0 = 0x00 // best effort
+	DSCP_CS1 = 0x20 // class 1
+	DSCP_CS2 = 0x40 // class 2
+	DSCP_CS3 = 0x60 // class 3
+	DSCP_CS4 = 0x80 // class 4
+	DSCP_CS5 = 0xa0 // expedited forwarding
+	DSCP_CS6 = 0xc0 // subsume deprecated IP precedence, internet control (routing information update)
+	DSCP_CS7 = 0xe0 // subsume deprecated IP precedence, network control (link, neighbor liveliness check)
+
+	// DiffServ assured forwarding codepoints in RFC 2474, 2475, 2597 and 3260.
+	DSCP_AF11 = 0x28 // class 1 low drop precedence
+	DSCP_AF12 = 0x30 // class 1 medium drop precedence
+	DSCP_AF13 = 0x38 // class 1 high drop precedence
+	DSCP_AF21 = 0x48 // class 2 low drop precedence
+	DSCP_AF22 = 0x50 // class 2 medium drop precedence
+	DSCP_AF23 = 0x58 // class 2 high drop precedence
+	DSCP_AF31 = 0x68 // class 3 low drop precedence
+	DSCP_AF32 = 0x70 // class 3 medium drop precedence
+	DSCP_AF33 = 0x78 // class 3 high drop precedence
+	DSCP_AF41 = 0x88 // class 4 low drop precedence
+	DSCP_AF42 = 0x90 // class 4 medium drop precedence
+	DSCP_AF43 = 0x98 // class 4 high drop precedence
+	DSCP_EF   = 0xb8 // expedited forwarding
+
+	// ECN codepoints in RFC 3168.
+	ECN_NOTECT = 0x00 // not ECN-capable transport
+	ECN_ECT1   = 0x01 // ECN-capable transport, ECT(1)
+	ECN_ECT0   = 0x02 // ECN-capable transport, ECT(0)
+	ECN_CE     = 0x03 // congestion experienced
+)
+
+type headerField int
+
+const (
+	posTOS      headerField = 1  // type-of-service
+	posTotalLen             = 2  // packet total length
+	posID                   = 4  // identification
+	posFragOff              = 6  // fragment offset
+	posTTL                  = 8  // time-to-live
+	posProtocol             = 9  // next protocol
+	posChecksum             = 10 // checksum
+	posSrc                  = 12 // source address
+	posDst                  = 16 // destination address
+)
+
+// A Header represents an IPv4 header.
+type Header struct {
+	Version  int    // protocol version
+	Len      int    // header length
+	TOS      int    // type-of-service
+	TotalLen int    // packet total length
+	ID       int    // identification
+	FragOff  int    // fragment offset
+	TTL      int    // time-to-live
+	Protocol int    // next protocol
+	Checksum int    // checksum
+	Src      net.IP // source address
+	Dst      net.IP // destination address
+	Options  []byte // options, extension headers
+}
+
+func (h *Header) String() string {
+	if h == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("ver: %v, hdrlen: %v, tos: %#x, totallen: %v, id: %#x, fragoff: %#x, ttl: %v, proto: %v, cksum: %#x, src: %v, dst: %v", h.Version, h.Len, h.TOS, h.TotalLen, h.ID, h.FragOff, h.TTL, h.Protocol, h.Checksum, h.Src, h.Dst)
+}
+
+// Please refer to the online manual; IP(4) on Darwin, FreeBSD and
+// OpenBSD.  IP(7) on Linux.
+var supportsNewIPInput = runtime.GOOS == "linux" || runtime.GOOS == "openbsd"
+
+// Marshal returns the binary encoding of the IPv4 header h.
+func (h *Header) Marshal() ([]byte, error) {
+	if h == nil {
+		return nil, syscall.EINVAL
+	}
+	if h.Len < HeaderLen {
+		return nil, errHeaderTooShort
+	}
+	hdrlen := HeaderLen + len(h.Options)
+	b := make([]byte, hdrlen)
+	b[0] = byte(Version<<4 | (hdrlen >> 2 & 0x0f))
+	b[posTOS] = byte(h.TOS)
+	if supportsNewIPInput {
+		b[posTotalLen], b[posTotalLen+1] = byte(h.TotalLen>>8), byte(h.TotalLen)
+		b[posFragOff], b[posFragOff+1] = byte(h.FragOff>>8), byte(h.FragOff)
+	} else {
+		*(*uint16)(unsafe.Pointer(&b[posTotalLen : posTotalLen+1][0])) = uint16(h.TotalLen)
+		*(*uint16)(unsafe.Pointer(&b[posFragOff : posFragOff+1][0])) = uint16(h.FragOff)
+	}
+	b[posID], b[posID+1] = byte(h.ID>>8), byte(h.ID)
+	b[posTTL] = byte(h.TTL)
+	b[posProtocol] = byte(h.Protocol)
+	b[posChecksum], b[posChecksum+1] = byte(h.Checksum>>8), byte(h.Checksum)
+	if ip := h.Src.To4(); ip != nil {
+		copy(b[posSrc:posSrc+net.IPv4len], ip[0:net.IPv4len])
+	}
+	if ip := h.Dst.To4(); ip != nil {
+		copy(b[posDst:posDst+net.IPv4len], ip[0:net.IPv4len])
+	} else {
+		return nil, errMissingAddress
+	}
+	if len(h.Options) > 0 {
+		copy(b[HeaderLen:], h.Options)
+	}
+	return b, nil
+}
+
+// ParseHeader parses b as an IPv4 header.
+func ParseHeader(b []byte) (*Header, error) {
+	if len(b) < HeaderLen {
+		return nil, errHeaderTooShort
+	}
+	hdrlen := (int(b[0]) & 0x0f) << 2
+	if hdrlen > len(b) {
+		return nil, errBufferTooShort
+	}
+	h := &Header{}
+	h.Version = int(b[0] >> 4)
+	h.Len = hdrlen
+	h.TOS = int(b[posTOS])
+	if supportsNewIPInput {
+		h.TotalLen = int(b[posTotalLen])<<8 | int(b[posTotalLen+1])
+		h.FragOff = int(b[posFragOff])<<8 | int(b[posFragOff+1])
+	} else {
+		h.TotalLen = int(*(*uint16)(unsafe.Pointer(&b[posTotalLen : posTotalLen+1][0])))
+		h.TotalLen += hdrlen
+		h.FragOff = int(*(*uint16)(unsafe.Pointer(&b[posFragOff : posFragOff+1][0])))
+	}
+	h.ID = int(b[posID])<<8 | int(b[posID+1])
+	h.TTL = int(b[posTTL])
+	h.Protocol = int(b[posProtocol])
+	h.Checksum = int(b[posChecksum])<<8 | int(b[posChecksum+1])
+	h.Src = net.IPv4(b[posSrc], b[posSrc+1], b[posSrc+2], b[posSrc+3])
+	h.Dst = net.IPv4(b[posDst], b[posDst+1], b[posDst+2], b[posDst+3])
+	if hdrlen-HeaderLen > 0 {
+		h.Options = make([]byte, hdrlen-HeaderLen)
+		copy(h.Options, b[HeaderLen:])
+	}
+	return h, nil
+}
diff --git a/ipv4/header_test.go b/ipv4/header_test.go
new file mode 100644
index 0000000..0ac02ed
--- /dev/null
+++ b/ipv4/header_test.go
@@ -0,0 +1,99 @@
+// 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 ipv4_test
+
+import (
+	"bytes"
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"reflect"
+	"runtime"
+	"testing"
+)
+
+var (
+	wireHeaderFromKernel = [ipv4.HeaderLen]byte{
+		0x45, 0x01, 0xbe, 0xef,
+		0xca, 0xfe, 0x05, 0xdc,
+		0xff, 0x01, 0xde, 0xad,
+		172, 16, 254, 254,
+		192, 168, 0, 1,
+	}
+	wireHeaderToKernel = [ipv4.HeaderLen]byte{
+		0x45, 0x01, 0xbe, 0xef,
+		0xca, 0xfe, 0x05, 0xdc,
+		0xff, 0x01, 0xde, 0xad,
+		172, 16, 254, 254,
+		192, 168, 0, 1,
+	}
+	wireHeaderFromTradBSDKernel = [ipv4.HeaderLen]byte{
+		0x45, 0x01, 0xdb, 0xbe,
+		0xca, 0xfe, 0xdc, 0x05,
+		0xff, 0x01, 0xde, 0xad,
+		172, 16, 254, 254,
+		192, 168, 0, 1,
+	}
+	wireHeaderToTradBSDKernel = [ipv4.HeaderLen]byte{
+		0x45, 0x01, 0xef, 0xbe,
+		0xca, 0xfe, 0xdc, 0x05,
+		0xff, 0x01, 0xde, 0xad,
+		172, 16, 254, 254,
+		192, 168, 0, 1,
+	}
+	// TODO(mikio): Add platform dependent wire header formats when
+	// we support new platforms.
+)
+
+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()
+	if err != nil {
+		t.Fatalf("ipv4.Header.Marshal failed: %v", err)
+	}
+	var wh []byte
+	switch runtime.GOOS {
+	case "linux", "openbsd":
+		wh = wireHeaderToKernel[:]
+	default:
+		wh = wireHeaderToTradBSDKernel[:]
+	}
+	if !bytes.Equal(b, wh) {
+		t.Fatalf("ipv4.Header.Marshal failed: %#v not equal %#v", b, wh)
+	}
+}
+
+func TestParseHeader(t *testing.T) {
+	var wh []byte
+	switch runtime.GOOS {
+	case "linux", "openbsd":
+		wh = wireHeaderFromKernel[:]
+	default:
+		wh = wireHeaderFromTradBSDKernel[:]
+	}
+	h, err := ipv4.ParseHeader(wh)
+	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)
+	}
+}
diff --git a/ipv4/helper.go b/ipv4/helper.go
new file mode 100644
index 0000000..2cc56bd
--- /dev/null
+++ b/ipv4/helper.go
@@ -0,0 +1,85 @@
+// 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 ipv4
+
+import (
+	"errors"
+	"net"
+)
+
+var (
+	errNoSuchInterface          = errors.New("no such interface")
+	errNoSuchMulticastInterface = errors.New("no such multicast interface")
+)
+
+func boolint(b bool) int {
+	if b {
+		return 1
+	}
+	return 0
+}
+
+func netAddrToIP4(a net.Addr) net.IP {
+	switch v := a.(type) {
+	case *net.UDPAddr:
+		if ip := v.IP.To4(); ip != nil {
+			return ip
+		}
+	case *net.IPAddr:
+		if ip := v.IP.To4(); ip != nil {
+			return ip
+		}
+	}
+	return nil
+}
+
+func netIP4ToInterface(ip net.IP) (*net.Interface, error) {
+	ift, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	for _, ifi := range ift {
+		ifat, err := ifi.Addrs()
+		if err != nil {
+			return nil, err
+		}
+		for _, ifa := range ifat {
+			switch v := ifa.(type) {
+			case *net.IPAddr:
+				if ip.Equal(v.IP) {
+					return &ifi, nil
+				}
+			case *net.IPNet:
+				if ip.Equal(v.IP) {
+					return &ifi, nil
+				}
+			}
+		}
+	}
+	return nil, errNoSuchInterface
+}
+
+func netInterfaceToIP4(ifi *net.Interface) (net.IP, error) {
+	if ifi == nil {
+		return net.IPv4zero, nil
+	}
+	ifat, err := ifi.Addrs()
+	if err != nil {
+		return nil, err
+	}
+	for _, ifa := range ifat {
+		switch v := ifa.(type) {
+		case *net.IPAddr:
+			if v.IP.To4() != nil {
+				return v.IP, nil
+			}
+		case *net.IPNet:
+			if v.IP.To4() != nil {
+				return v.IP, nil
+			}
+		}
+	}
+	return nil, errNoSuchInterface
+}
diff --git a/ipv4/helper_plan9.go b/ipv4/helper_plan9.go
new file mode 100644
index 0000000..2bc9c71
--- /dev/null
+++ b/ipv4/helper_plan9.go
@@ -0,0 +1,29 @@
+// 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 ipv4
+
+import (
+	"syscall"
+)
+
+func (c *genericOpt) sysfd() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *dgramOpt) sysfd() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *payloadHandler) sysfd() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
+
+func (c *packetHandler) sysfd() (int, error) {
+	// TODO(mikio): Implement this
+	return 0, syscall.EPLAN9
+}
diff --git a/ipv4/helper_posix.go b/ipv4/helper_posix.go
new file mode 100644
index 0000000..0adb3e1
--- /dev/null
+++ b/ipv4/helper_posix.go
@@ -0,0 +1,42 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd windows
+
+package ipv4
+
+import (
+	"bytes"
+	"net"
+	"syscall"
+)
+
+func setSyscallIPMreq(mreq *syscall.IPMreq, ifi *net.Interface) error {
+	if ifi == nil {
+		return nil
+	}
+	ifat, err := ifi.Addrs()
+	if err != nil {
+		return err
+	}
+	for _, ifa := range ifat {
+		switch v := ifa.(type) {
+		case *net.IPAddr:
+			if a := v.IP.To4(); a != nil {
+				copy(mreq.Interface[:], a)
+				goto done
+			}
+		case *net.IPNet:
+			if a := v.IP.To4(); a != nil {
+				copy(mreq.Interface[:], a)
+				goto done
+			}
+		}
+	}
+done:
+	if bytes.Equal(mreq.Multiaddr[:], net.IPv4zero.To4()) {
+		return errNoSuchMulticastInterface
+	}
+	return nil
+}
diff --git a/ipv4/helper_unix.go b/ipv4/helper_unix.go
new file mode 100644
index 0000000..4eab775
--- /dev/null
+++ b/ipv4/helper_unix.go
@@ -0,0 +1,50 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4
+
+import (
+	"net"
+	"reflect"
+)
+
+func (c *genericOpt) sysfd() (int, error) {
+	switch p := c.c.(type) {
+	case *net.TCPConn, *net.UDPConn, *net.IPConn:
+		return sysfd(p)
+	}
+	return 0, errInvalidConnType
+}
+
+func (c *dgramOpt) sysfd() (int, error) {
+	switch p := c.c.(type) {
+	case *net.UDPConn, *net.IPConn:
+		return sysfd(p.(net.Conn))
+	}
+	return 0, errInvalidConnType
+}
+
+func (c *payloadHandler) sysfd() (int, error) {
+	return sysfd(c.c.(net.Conn))
+}
+
+func (c *packetHandler) sysfd() (int, error) {
+	return sysfd(c.c)
+}
+
+func sysfd(c net.Conn) (int, error) {
+	cv := reflect.ValueOf(c)
+	switch ce := cv.Elem(); ce.Kind() {
+	case reflect.Struct:
+		nfd := ce.FieldByName("conn").FieldByName("fd")
+		switch fe := nfd.Elem(); fe.Kind() {
+		case reflect.Struct:
+			fd := fe.FieldByName("sysfd")
+			return int(fd.Int()), nil
+		}
+	}
+	return 0, errInvalidConnType
+}
diff --git a/ipv4/helper_windows.go b/ipv4/helper_windows.go
new file mode 100644
index 0000000..0733e3e
--- /dev/null
+++ b/ipv4/helper_windows.go
@@ -0,0 +1,49 @@
+// 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 ipv4
+
+import (
+	"net"
+	"reflect"
+	"syscall"
+)
+
+func (c *genericOpt) sysfd() (syscall.Handle, error) {
+	switch p := c.c.(type) {
+	case *net.TCPConn, *net.UDPConn, *net.IPConn:
+		return sysfd(p)
+	}
+	return syscall.InvalidHandle, errInvalidConnType
+}
+
+func (c *dgramOpt) sysfd() (syscall.Handle, error) {
+	switch p := c.c.(type) {
+	case *net.UDPConn, *net.IPConn:
+		return sysfd(p.(net.Conn))
+	}
+	return syscall.InvalidHandle, errInvalidConnType
+}
+
+func (c *payloadHandler) sysfd() (syscall.Handle, error) {
+	return sysfd(c.c.(net.Conn))
+}
+
+func (c *packetHandler) sysfd() (syscall.Handle, error) {
+	return sysfd(c.c)
+}
+
+func sysfd(c net.Conn) (syscall.Handle, error) {
+	cv := reflect.ValueOf(c)
+	switch ce := cv.Elem(); ce.Kind() {
+	case reflect.Struct:
+		fd := ce.FieldByName("conn").FieldByName("fd")
+		switch fe := fd.Elem(); fe.Kind() {
+		case reflect.Struct:
+			sysfd := fe.FieldByName("sysfd")
+			return syscall.Handle(sysfd.Uint()), nil
+		}
+	}
+	return syscall.InvalidHandle, errInvalidConnType
+}
diff --git a/ipv4/mockicmp_test.go b/ipv4/mockicmp_test.go
new file mode 100644
index 0000000..5bf8920
--- /dev/null
+++ b/ipv4/mockicmp_test.go
@@ -0,0 +1,47 @@
+// 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 ipv4_test
+
+import (
+	"bytes"
+	"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
+}
+
+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
+}
diff --git a/ipv4/mocktransponder_test.go b/ipv4/mocktransponder_test.go
new file mode 100644
index 0000000..99759ef
--- /dev/null
+++ b/ipv4/mocktransponder_test.go
@@ -0,0 +1,133 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"testing"
+	"time"
+)
+
+// runPayloadTransponder 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
+	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.Write(wb, nil, dst); err != nil {
+			t.Fatalf("ipv4.PacketConn.Write failed: %v", err)
+		}
+		_, cm, _, err := c.Read(rb)
+		if err != nil {
+			t.Fatalf("ipv4.PacketConn.Read failed: %v", err)
+		}
+		t.Logf("rcvd cmsg: %v", cm)
+	}
+}
+
+// runDatagramTransponder 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
+	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.Write(wh, wb, nil); err != nil {
+			t.Fatalf("ipv4.RawConn.Write failed: %v", err)
+		}
+		rh, _, cm, err := c.Read(rb)
+		if err != nil {
+			t.Fatalf("ipv4.RawConn.Read failed: %v", err)
+		}
+		t.Logf("rcvd cmsg: %v", cm.String())
+		t.Logf("rcvd hdr: %v", rh.String())
+	}
+}
+
+func loopbackInterface() *net.Interface {
+	ift, err := net.Interfaces()
+	if err != nil {
+		return nil
+	}
+	for _, ifi := range ift {
+		if ifi.Flags&net.FlagLoopback != 0 {
+			return &ifi
+		}
+	}
+	return nil
+}
+
+func isGoodForMulticast(ifi *net.Interface) (net.IP, bool) {
+	if ifi.Flags&net.FlagUp == 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
+	}
+	if len(ifat) == 0 {
+		return nil, false
+	}
+	var ip net.IP
+	for _, ifa := range ifat {
+		switch v := ifa.(type) {
+		case *net.IPAddr:
+			ip = v.IP
+		case *net.IPNet:
+			ip = v.IP
+		default:
+			continue
+		}
+		if ip.To4() == nil {
+			ip = nil
+			continue
+		}
+		break
+	}
+	if ip == nil {
+		return nil, false
+	}
+	return ip, true
+}
diff --git a/ipv4/multicast_test.go b/ipv4/multicast_test.go
new file mode 100644
index 0000000..01cc2a1
--- /dev/null
+++ b/ipv4/multicast_test.go
@@ -0,0 +1,128 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"os"
+	"testing"
+)
+
+func TestReadWriteMulticastIPPayloadUDP(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+
+	c, err := net.ListenPacket("udp4", "224.0.0.0:1024") // see RFC 4727
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	ifi := loopbackInterface()
+	if ifi == nil {
+		t.Logf("skipping test; an appropriate interface not found")
+		return
+	}
+	dst, err := net.ResolveUDPAddr("udp4", "224.0.0.254:1024") // see RFC 4727
+	if err != nil {
+		t.Fatalf("net.ResolveUDPAddr failed: %v", err)
+	}
+
+	p := ipv4.NewPacketConn(c)
+	if err := p.JoinGroup(ifi, dst); err != nil {
+		t.Fatalf("ipv4.PacketConn.JoinGroup on %v failed: %v", ifi, err)
+	}
+	if err := p.SetMulticastInterface(ifi); err != nil {
+		t.Fatalf("ipv4.PacketConn.SetMulticastInterface failed: %v", err)
+	}
+	if err := p.SetMulticastLoopback(true); err != nil {
+		t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err)
+	}
+	runPayloadTransponder(t, p, []byte("HELLO-R-U-THERE"), dst)
+}
+
+func TestReadWriteMulticastIPPayloadICMP(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	ifi := loopbackInterface()
+	if ifi == nil {
+		t.Logf("skipping test; an appropriate interface not found")
+		return
+	}
+	dst, err := net.ResolveIPAddr("ip4", "224.0.0.254") // see RFC 4727
+	if err != nil {
+		t.Fatalf("net.ResolveIPAddr failed: %v", err)
+	}
+
+	p := ipv4.NewPacketConn(c)
+	if err := p.JoinGroup(ifi, dst); err != nil {
+		t.Fatalf("ipv4.PacketConn.JoinGroup on %v failed: %v", ifi, err)
+	}
+	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)
+}
+
+func TestReadWriteMulticastIPDatagram(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	ifi := loopbackInterface()
+	if ifi == nil {
+		t.Logf("skipping test; an appropriate interface not found")
+		return
+	}
+	dst, err := net.ResolveIPAddr("ip4", "224.0.0.254") // see RFC 4727
+	if err != nil {
+		t.Fatalf("ResolveIPAddr failed: %v", err)
+	}
+
+	r, err := ipv4.NewRawConn(c)
+	if err != nil {
+		t.Fatalf("ipv4.NewRawConn failed: %v", err)
+	}
+	if err := r.JoinGroup(ifi, dst); err != nil {
+		t.Fatalf("ipv4.RawConn.JoinGroup on %v failed: %v", ifi, err)
+	}
+	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)
+}
diff --git a/ipv4/multicastlistener_test.go b/ipv4/multicastlistener_test.go
new file mode 100644
index 0000000..4199c05
--- /dev/null
+++ b/ipv4/multicastlistener_test.go
@@ -0,0 +1,244 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"os"
+	"testing"
+)
+
+var udpMultipleGroupListenerTests = []struct {
+	gaddr *net.UDPAddr
+}{
+	{&net.UDPAddr{IP: net.IPv4(224, 0, 0, 249)}}, // see RFC 4727
+	{&net.UDPAddr{IP: net.IPv4(224, 0, 0, 250)}}, // see RFC 4727
+	{&net.UDPAddr{IP: net.IPv4(224, 0, 0, 254)}}, // see RFC 4727
+}
+
+func TestUDPSingleConnWithMultipleGroupListeners(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+
+	for _, tt := range udpMultipleGroupListenerTests {
+		// listen to a wildcard address with no reusable port
+		c, err := net.ListenPacket("udp4", "0.0.0.0:0")
+		if err != nil {
+			t.Fatalf("net.ListenPacket failed: %v", err)
+		}
+		defer c.Close()
+
+		p := ipv4.NewPacketConn(c)
+
+		var mift []*net.Interface
+		ift, err := net.Interfaces()
+		if err != nil {
+			t.Fatalf("net.Interfaces failed: %v", err)
+		}
+		for i, ifi := range ift {
+			if _, ok := isGoodForMulticast(&ifi); !ok {
+				continue
+			}
+			if err := p.JoinGroup(&ifi, tt.gaddr); err != nil {
+				t.Fatalf("ipv4.PacketConn.JoinGroup %v on %v failed: %v", tt.gaddr, ifi, err)
+			}
+			mift = append(mift, &ift[i])
+		}
+		for _, ifi := range mift {
+			if err := p.LeaveGroup(ifi, tt.gaddr); err != nil {
+				t.Fatalf("ipv4.PacketConn.LeaveGroup %v on %v failed: %v", tt.gaddr, ifi, err)
+			}
+		}
+	}
+}
+
+func TestUDPMultipleConnWithMultipleGroupListeners(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+
+	for _, tt := range udpMultipleGroupListenerTests {
+		// listen to a group address, actually a wildcard address
+		// with reusable port
+		c1, err := net.ListenPacket("udp4", "224.0.0.0:1024") // see RFC 4727
+		if err != nil {
+			t.Fatalf("net.ListenPacket failed: %v", err)
+		}
+		defer c1.Close()
+
+		c2, err := net.ListenPacket("udp4", "224.0.0.0:1024") // see RFC 4727
+		if err != nil {
+			t.Fatalf("net.ListenPacket failed: %v", err)
+		}
+		defer c2.Close()
+
+		var ps [2]*ipv4.PacketConn
+		ps[0] = ipv4.NewPacketConn(c1)
+		ps[1] = ipv4.NewPacketConn(c2)
+
+		var mift []*net.Interface
+		ift, err := net.Interfaces()
+		if err != nil {
+			t.Fatalf("net.Interfaces failed: %v", err)
+		}
+		for i, ifi := range ift {
+			if _, ok := isGoodForMulticast(&ifi); !ok {
+				continue
+			}
+			for _, p := range ps {
+				if err := p.JoinGroup(&ifi, tt.gaddr); err != nil {
+					t.Fatalf("ipv4.PacketConn.JoinGroup %v on %v failed: %v", tt.gaddr, ifi, err)
+				}
+			}
+			mift = append(mift, &ift[i])
+		}
+		for _, ifi := range mift {
+			for _, p := range ps {
+				if err := p.LeaveGroup(ifi, tt.gaddr); err != nil {
+					t.Fatalf("ipv4.PacketConn.LeaveGroup %v on %v failed: %v", tt.gaddr, ifi, err)
+				}
+			}
+		}
+	}
+}
+
+func TestIPSingleConnWithSingleGroupListener(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	// listen to a wildcard address
+	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	r, err := ipv4.NewRawConn(c)
+	if err != nil {
+		t.Fatalf("ipv4.RawConn failed: %v", err)
+	}
+
+	gaddr := &net.IPAddr{IP: net.IPv4(224, 0, 0, 254)} // see RFC 4727
+	var mift []*net.Interface
+	ift, err := net.Interfaces()
+	if err != nil {
+		t.Fatalf("net.Interfaces failed: %v", err)
+	}
+	for i, ifi := range ift {
+		if _, ok := isGoodForMulticast(&ifi); !ok {
+			continue
+		}
+		if err := r.JoinGroup(&ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.RawConn.JoinGroup on %v failed: %v", ifi, err)
+		}
+		mift = append(mift, &ift[i])
+	}
+	for _, ifi := range mift {
+		if err := r.LeaveGroup(ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.RawConn.LeaveGroup on %v failed: %v", ifi, err)
+		}
+	}
+}
+
+func TestUDPPerInterfaceSingleConnWithSingleGroupListener(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+
+	gaddr := &net.IPAddr{IP: net.IPv4(224, 0, 0, 254)} // see RFC 4727
+	type ml struct {
+		c   *ipv4.PacketConn
+		ifi *net.Interface
+	}
+	var mlt []*ml
+
+	ift, err := net.Interfaces()
+	if err != nil {
+		t.Fatalf("net.Interfaces failed: %v", err)
+	}
+	for i, ifi := range ift {
+		ip, ok := isGoodForMulticast(&ifi)
+		if !ok {
+			continue
+		}
+		// listen to a unicast interface address
+		c, err := net.ListenPacket("udp4", ip.String()+":"+"1024") // see RFC 4727
+		if err != nil {
+			t.Fatalf("net.ListenPacket with %v failed: %v", ip, err)
+		}
+		defer c.Close()
+		p := ipv4.NewPacketConn(c)
+		if err := p.JoinGroup(&ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.PacketConn.JoinGroup on %v failed: %v", ifi, err)
+		}
+		mlt = append(mlt, &ml{p, &ift[i]})
+	}
+	for _, m := range mlt {
+		if err := m.c.LeaveGroup(m.ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.PacketConn.LeaveGroup on %v failed: %v", m.ifi, err)
+		}
+	}
+}
+
+func TestIPPerInterfaceSingleConnWithSingleGroupListener(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	gaddr := &net.IPAddr{IP: net.IPv4(224, 0, 0, 254)} // see RFC 4727
+	type ml struct {
+		c   *ipv4.RawConn
+		ifi *net.Interface
+	}
+	var mlt []*ml
+
+	ift, err := net.Interfaces()
+	if err != nil {
+		t.Fatalf("net.Interfaces failed: %v", err)
+	}
+	for i, ifi := range ift {
+		ip, ok := isGoodForMulticast(&ifi)
+		if !ok {
+			continue
+		}
+		// listen to a unicast interface address
+		c, err := net.ListenPacket("ip4:253", ip.String()) // see RFC 4727
+		if err != nil {
+			t.Fatalf("net.ListenPacket with %v failed: %v", ip, err)
+		}
+		defer c.Close()
+		r, err := ipv4.NewRawConn(c)
+		if err != nil {
+			t.Fatalf("ipv4.NewRawConn failed: %v", err)
+		}
+		if err := r.JoinGroup(&ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.RawConn.JoinGroup on %v failed: %v", ifi, err)
+		}
+		mlt = append(mlt, &ml{r, &ift[i]})
+	}
+	for _, m := range mlt {
+		if err := m.c.LeaveGroup(m.ifi, gaddr); err != nil {
+			t.Fatalf("ipv4.RawConn.LeaveGroup on %v failed: %v", m.ifi, err)
+		}
+	}
+}
diff --git a/ipv4/multicastsockopt_test.go b/ipv4/multicastsockopt_test.go
new file mode 100644
index 0000000..3f22739
--- /dev/null
+++ b/ipv4/multicastsockopt_test.go
@@ -0,0 +1,129 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd windows
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"os"
+	"runtime"
+	"testing"
+)
+
+type testMulticastConn interface {
+	testUnicastConn
+	MulticastTTL() (int, error)
+	SetMulticastTTL(ttl int) error
+	MulticastLoopback() (bool, error)
+	SetMulticastLoopback(bool) error
+	JoinGroup(*net.Interface, net.Addr) error
+	LeaveGroup(*net.Interface, net.Addr) error
+}
+
+type multicastSockoptTest struct {
+	tos    int
+	ttl    int
+	mcttl  int
+	mcloop bool
+	gaddr  net.IP
+}
+
+var multicastSockoptTests = []multicastSockoptTest{
+	{ipv4.DSCP_CS0 | ipv4.ECN_NOTECT, 127, 128, false, net.IPv4(224, 0, 0, 249)}, // see RFC 4727
+	{ipv4.DSCP_AF11 | ipv4.ECN_NOTECT, 255, 254, true, net.IPv4(224, 0, 0, 250)}, // see RFC 4727
+}
+
+func TestUDPMulticastSockopt(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+
+	for _, tt := range multicastSockoptTests {
+		c, err := net.ListenPacket("udp4", "0.0.0.0:0")
+		if err != nil {
+			t.Fatalf("net.ListenPacket failed: %v", err)
+		}
+		defer c.Close()
+
+		p := ipv4.NewPacketConn(c)
+		testMulticastSockopt(t, tt, p, &net.UDPAddr{IP: tt.gaddr})
+	}
+}
+
+func TestIPMulticastSockopt(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Logf("skipping test to avoid external network")
+		return
+	}
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	for _, tt := range multicastSockoptTests {
+		c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+		if err != nil {
+			t.Fatalf("net.ListenPacket failed: %v", err)
+		}
+		defer c.Close()
+
+		r, _ := ipv4.NewRawConn(c)
+		testMulticastSockopt(t, tt, r, &net.IPAddr{IP: tt.gaddr})
+	}
+}
+
+func testMulticastSockopt(t *testing.T, tt multicastSockoptTest, c testMulticastConn, gaddr net.Addr) {
+	switch runtime.GOOS {
+	case "windows":
+		// IP_TOS option is supported on Windows 8 and beyond.
+		t.Logf("skipping IP_TOS test on %q", runtime.GOOS)
+	default:
+		if err := c.SetTOS(tt.tos); err != nil {
+			t.Fatalf("ipv4.PacketConn.SetTOS failed: %v", err)
+		}
+		if v, err := c.TOS(); err != nil {
+			t.Fatalf("ipv4.PacketConn.TOS failed: %v", err)
+		} else if v != tt.tos {
+			t.Fatalf("Got unexpected TOS value %v; expected %v", v, tt.tos)
+		}
+	}
+
+	if err := c.SetTTL(tt.ttl); err != nil {
+		t.Fatalf("ipv4.PacketConn.SetTTL failed: %v", err)
+	}
+	if v, err := c.TTL(); err != nil {
+		t.Fatalf("ipv4.PacketConn.TTL failed: %v", err)
+	} else if v != tt.ttl {
+		t.Fatalf("Got unexpected TTL value %v; expected %v", v, tt.ttl)
+	}
+
+	if err := c.SetMulticastTTL(tt.mcttl); err != nil {
+		t.Fatalf("ipv4.PacketConn.SetMulticastTTL failed: %v", err)
+	}
+	if v, err := c.MulticastTTL(); err != nil {
+		t.Fatalf("ipv4.PacketConn.MulticastTTL failed: %v", err)
+	} else if v != tt.mcttl {
+		t.Fatalf("Got unexpected MulticastTTL value %v; expected %v", v, tt.mcttl)
+	}
+
+	if err := c.SetMulticastLoopback(tt.mcloop); err != nil {
+		t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err)
+	}
+	if v, err := c.MulticastLoopback(); err != nil {
+		t.Fatalf("ipv4.PacketConn.MulticastLoopback failed: %v", err)
+	} else if v != tt.mcloop {
+		t.Fatalf("Got unexpected MulticastLoopback value %v; expected %v", v, tt.mcloop)
+	}
+
+	if err := c.JoinGroup(nil, gaddr); err != nil {
+		t.Fatalf("ipv4.PacketConn.JoinGroup(%v) failed: %v", gaddr, err)
+	}
+	if err := c.LeaveGroup(nil, gaddr); err != nil {
+		t.Fatalf("ipv4.PacketConn.LeaveGroup(%v) failed: %v", gaddr, err)
+	}
+}
diff --git a/ipv4/packet.go b/ipv4/packet.go
new file mode 100644
index 0000000..3847a9e
--- /dev/null
+++ b/ipv4/packet.go
@@ -0,0 +1,97 @@
+// 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 ipv4
+
+import (
+	"net"
+	"syscall"
+)
+
+// A packetHandler represents the IPv4 datagram handler.
+type packetHandler struct {
+	c *net.IPConn
+	rawOpt
+}
+
+func (c *packetHandler) ok() bool { return c != nil && c.c != nil }
+
+// Read reads an IPv4 datagram from the endpoint c, copying the
+// datagram into b.  It returns the received datagram as the IPv4
+// header h, the payload p and the control message cm.
+func (c *packetHandler) Read(b []byte) (h *Header, p []byte, cm *ControlMessage, err error) {
+	if !c.ok() {
+		return nil, nil, nil, syscall.EINVAL
+	}
+	oob := newControlMessage(&c.rawOpt)
+	n, oobn, _, src, err := c.c.ReadMsgIP(b, oob)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	var hs []byte
+	if hs, p, err = slicePacket(b[:n]); err != nil {
+		return nil, nil, nil, err
+	}
+	if h, err = ParseHeader(hs); err != nil {
+		return nil, nil, nil, err
+	}
+	if cm, err = parseControlMessage(oob[:oobn]); err != nil {
+		return nil, nil, nil, err
+	}
+	if src != nil && cm != nil {
+		cm.Src = src.IP
+	}
+	return
+}
+
+func slicePacket(b []byte) (h, p []byte, err error) {
+	if len(b) < HeaderLen {
+		return nil, nil, errHeaderTooShort
+	}
+	hdrlen := (int(b[0]) & 0x0f) << 2
+	return b[:hdrlen], b[hdrlen:], nil
+}
+
+// Write writes an IPv4 datagram through the endpoint c, copying the
+// datagram from the IPv4 header h and the payload p.  The control
+// message cm allows the datagram path and the outgoing interface to be
+// specified.  Currently only Linux supports this.  The cm may be nil
+// if control of the outgoing datagram is not required.
+//
+// The IPv4 header h must contain appropriate fields that include:
+//
+//	Version       = ipv4.Version
+//	Len           = <must be specified>
+//	TOS           = <must be specified>
+//	TotalLen      = <must be specified>
+//	ID            = platform sets an appropriate value if ID is zero
+//	FragOff       = <must be specified>
+//	TTL           = <must be specified>
+//	Protocol      = <must be specified>
+//	Checksum      = platform sets an appropriate value if Checksum is zero
+//	Src           = platform sets an appropriate value if Src is nil
+//	Dst           = <must be specified>
+//	h.Options     = optional
+func (c *packetHandler) Write(h *Header, p []byte, cm *ControlMessage) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	oob := marshalControlMessage(cm)
+	wh, err := h.Marshal()
+	if err != nil {
+		return err
+	}
+	dst := &net.IPAddr{}
+	if cm != nil {
+		if ip := cm.Dst.To4(); ip != nil {
+			dst.IP = ip
+		}
+	}
+	if dst.IP == nil {
+		dst.IP = h.Dst
+	}
+	wh = append(wh, p...)
+	_, _, err = c.c.WriteMsgIP(wh, oob, dst)
+	return err
+}
diff --git a/ipv4/payload.go b/ipv4/payload.go
new file mode 100644
index 0000000..bfcf751
--- /dev/null
+++ b/ipv4/payload.go
@@ -0,0 +1,81 @@
+// 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 ipv4
+
+import (
+	"net"
+	"syscall"
+)
+
+// A payloadHandler represents the IPv4 datagram payload handler.
+type payloadHandler struct {
+	c net.PacketConn
+	rawOpt
+}
+
+func (c *payloadHandler) ok() bool { return c != nil && c.c != nil }
+
+// Read reads a payload of the received IPv4 datagram, from the
+// endpoint c, copying the payload into b.  It returns the number of
+// bytes copied into b, the control message cm and the source address
+// src of the received datagram.
+func (c *payloadHandler) Read(b []byte) (n int, cm *ControlMessage, src net.Addr, err error) {
+	if !c.ok() {
+		return 0, nil, nil, syscall.EINVAL
+	}
+	oob := newControlMessage(&c.rawOpt)
+	var oobn int
+	switch rd := c.c.(type) {
+	case *net.UDPConn:
+		if n, oobn, _, src, err = rd.ReadMsgUDP(b, oob); err != nil {
+			return 0, nil, nil, err
+		}
+	case *net.IPConn:
+		nb := make([]byte, len(b)+maxHeaderLen)
+		if n, oobn, _, src, err = rd.ReadMsgIP(nb, oob); err != nil {
+			return 0, nil, nil, err
+		}
+		hdrlen := (int(b[0]) & 0x0f) << 2
+		copy(b, nb[hdrlen:])
+		n -= hdrlen
+	default:
+		return 0, nil, nil, errInvalidConnType
+	}
+	if cm, err = parseControlMessage(oob[:oobn]); err != nil {
+		return 0, nil, nil, err
+	}
+	if cm != nil {
+		cm.Src = netAddrToIP4(src)
+	}
+	return
+}
+
+// Write writes a payload of the IPv4 datagram, to the destination
+// address dst through the endpoint c, copying the payload from b.
+// It returns the number of bytes written.  The control message cm
+// allows the datagram path and the outgoing interface to be
+// specified.  Currently only Linux supports this.  The cm may be nil
+// if control of the outgoing datagram is not required.
+func (c *payloadHandler) Write(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error) {
+	if !c.ok() {
+		return 0, syscall.EINVAL
+	}
+	oob := marshalControlMessage(cm)
+	if dst == nil {
+		return 0, errMissingAddress
+	}
+	switch wr := c.c.(type) {
+	case *net.UDPConn:
+		n, _, err = wr.WriteMsgUDP(b, oob, dst.(*net.UDPAddr))
+	case *net.IPConn:
+		n, _, err = wr.WriteMsgIP(b, oob, dst.(*net.IPAddr))
+	default:
+		return 0, errInvalidConnType
+	}
+	if err != nil {
+		return 0, err
+	}
+	return
+}
diff --git a/ipv4/sockopt_bsd.go b/ipv4/sockopt_bsd.go
new file mode 100644
index 0000000..46ab31c
--- /dev/null
+++ b/ipv4/sockopt_bsd.go
@@ -0,0 +1,123 @@
+// 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.
+
+// +build darwin freebsd netbsd openbsd
+
+package ipv4
+
+import (
+	"net"
+	"os"
+	"syscall"
+)
+
+func ipv4MulticastTTL(fd int) (int, error) {
+	v, err := syscall.GetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return int(v), nil
+}
+
+func setIPv4MulticastTTL(fd int, v int) error {
+	err := syscall.SetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL, byte(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4ReceiveDestinationAddress(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVDSTADDR)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4ReceiveDestinationAddress(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVDSTADDR, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4ReceiveInterface(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVIF)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4ReceiveInterface(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVIF, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastInterface(fd int) (*net.Interface, error) {
+	a, err := syscall.GetsockoptInet4Addr(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF)
+	if err != nil {
+		return nil, os.NewSyscallError("getsockopt", err)
+	}
+	return netIP4ToInterface(net.IPv4(a[0], a[1], a[2], a[3]))
+}
+
+func setIPv4MulticastInterface(fd int, ifi *net.Interface) error {
+	ip, err := netInterfaceToIP4(ifi)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	var a [4]byte
+	copy(a[:], ip.To4())
+	err = syscall.SetsockoptInet4Addr(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, a)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastLoopback(fd int) (bool, error) {
+	v, err := syscall.GetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4MulticastLoopback(fd int, v bool) error {
+	err := syscall.SetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, byte(boolint(v)))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func joinIPv4Group(fd int, ifi *net.Interface, grp net.IP) error {
+	mreq := &syscall.IPMreq{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if err := setSyscallIPMreq(mreq, ifi); err != nil {
+		return err
+	}
+	err := syscall.SetsockoptIPMreq(fd, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func leaveIPv4Group(fd int, ifi *net.Interface, grp net.IP) error {
+	mreq := &syscall.IPMreq{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if err := setSyscallIPMreq(mreq, ifi); err != nil {
+		return err
+	}
+	err := syscall.SetsockoptIPMreq(fd, syscall.IPPROTO_IP, syscall.IP_DROP_MEMBERSHIP, mreq)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
diff --git a/ipv4/sockopt_freebsd.go b/ipv4/sockopt_freebsd.go
new file mode 100644
index 0000000..6bcd66f
--- /dev/null
+++ b/ipv4/sockopt_freebsd.go
@@ -0,0 +1,26 @@
+// 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 ipv4
+
+import (
+	"os"
+	"syscall"
+)
+
+func ipv4SendSourceAddress(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_SENDSRCADDR)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4SendSourceAddress(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_SENDSRCADDR, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
diff --git a/ipv4/sockopt_linux.go b/ipv4/sockopt_linux.go
new file mode 100644
index 0000000..8c0ca06
--- /dev/null
+++ b/ipv4/sockopt_linux.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 ipv4
+
+import (
+	"net"
+	"os"
+	"syscall"
+)
+
+func ipv4ReceiveTOS(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVTOS)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4ReceiveTOS(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVTOS, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastTTL(fd int) (int, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return v, nil
+}
+
+func setIPv4MulticastTTL(fd int, v int) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL, v)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4PacketInfo(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_PKTINFO)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4PacketInfo(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_PKTINFO, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastInterface(fd int) (*net.Interface, error) {
+	mreqn, err := syscall.GetsockoptIPMreqn(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF)
+	if err != nil {
+		return nil, os.NewSyscallError("getsockopt", err)
+	}
+	if int(mreqn.Ifindex) == 0 {
+		return nil, nil
+	}
+	return net.InterfaceByIndex(int(mreqn.Ifindex))
+}
+
+func setIPv4MulticastInterface(fd int, ifi *net.Interface) error {
+	mreqn := &syscall.IPMreqn{}
+	if ifi != nil {
+		mreqn.Ifindex = int32(ifi.Index)
+	}
+	err := syscall.SetsockoptIPMreqn(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, mreqn)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastLoopback(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4MulticastLoopback(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func joinIPv4Group(fd int, ifi *net.Interface, grp net.IP) error {
+	mreqn := &syscall.IPMreqn{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if ifi != nil {
+		mreqn.Ifindex = int32(ifi.Index)
+	}
+	err := syscall.SetsockoptIPMreqn(fd, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreqn)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func leaveIPv4Group(fd int, ifi *net.Interface, grp net.IP) error {
+	mreqn := &syscall.IPMreqn{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if ifi != nil {
+		mreqn.Ifindex = int32(ifi.Index)
+	}
+	err := syscall.SetsockoptIPMreqn(fd, syscall.IPPROTO_IP, syscall.IP_DROP_MEMBERSHIP, mreqn)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
diff --git a/ipv4/sockopt_plan9.go b/ipv4/sockopt_plan9.go
new file mode 100644
index 0000000..0816a70
--- /dev/null
+++ b/ipv4/sockopt_plan9.go
@@ -0,0 +1,19 @@
+// 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 ipv4
+
+import (
+	"syscall"
+)
+
+func ipv4HeaderPrepend(fd int) (bool, error) {
+	// TODO(mikio): Implement this
+	return false, syscall.EPLAN9
+}
+
+func setIPv4HeaderPrepend(fd int, v bool) error {
+	// TODO(mikio): Implement this
+	return syscall.EPLAN9
+}
diff --git a/ipv4/sockopt_unix.go b/ipv4/sockopt_unix.go
new file mode 100644
index 0000000..ef000ec
--- /dev/null
+++ b/ipv4/sockopt_unix.go
@@ -0,0 +1,76 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4
+
+import (
+	"os"
+	"syscall"
+)
+
+func ipv4TOS(fd int) (int, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return v, nil
+}
+
+func setIPv4TOS(fd int, v int) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS, v)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4TTL(fd int) (int, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return v, nil
+}
+
+func setIPv4TTL(fd int, v int) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TTL, v)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4ReceiveTTL(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVTTL)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4ReceiveTTL(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVTTL, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4HeaderPrepend(fd int) (bool, error) {
+	v, err := syscall.GetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4HeaderPrepend(fd int, v bool) error {
+	err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, boolint(v))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
diff --git a/ipv4/sockopt_windows.go b/ipv4/sockopt_windows.go
new file mode 100644
index 0000000..b1e4edf
--- /dev/null
+++ b/ipv4/sockopt_windows.go
@@ -0,0 +1,179 @@
+// 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 ipv4
+
+import (
+	"net"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+// Please refer to the online manual;
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms738586(v=vs.85).aspx
+
+func ipv4TOS(fd syscall.Handle) (int, error) {
+	var v int32
+	l := int32(4)
+	err := syscall.Getsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_TOS), (*byte)(unsafe.Pointer(&v)), &l)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return int(v), nil
+}
+
+func setIPv4TOS(fd syscall.Handle, v int) error {
+	vv := int32(v)
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_TOS), (*byte)(unsafe.Pointer(&vv)), 4)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4TTL(fd syscall.Handle) (int, error) {
+	var v int32
+	l := int32(4)
+	err := syscall.Getsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_TTL), (*byte)(unsafe.Pointer(&v)), &l)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return int(v), nil
+}
+
+func setIPv4TTL(fd syscall.Handle, v int) error {
+	vv := int32(v)
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_TTL), (*byte)(unsafe.Pointer(&vv)), 4)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastTTL(fd syscall.Handle) (int, error) {
+	var v int32
+	l := int32(4)
+	err := syscall.Getsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_TTL), (*byte)(unsafe.Pointer(&v)), &l)
+	if err != nil {
+		return 0, os.NewSyscallError("getsockopt", err)
+	}
+	return int(v), nil
+}
+
+func setIPv4MulticastTTL(fd syscall.Handle, v int) error {
+	vv := int32(v)
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_TTL), (*byte)(unsafe.Pointer(&vv)), 4)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4ReceiveTTL(fd syscall.Handle) (bool, error) {
+	// NOTE: Not supported yet on any Windows
+	return false, syscall.EWINDOWS
+}
+
+func setIPv4ReceiveTTL(fd syscall.Handle, v bool) error {
+	// NOTE: Not supported yet on any Windows
+	return syscall.EWINDOWS
+}
+
+func ipv4ReceiveDestinationAddress(fd syscall.Handle) (bool, error) {
+	// TODO(mikio): Implement this for XP and beyond
+	return false, syscall.EWINDOWS
+}
+
+func setIPv4ReceiveDestinationAddress(fd syscall.Handle, v bool) error {
+	// TODO(mikio): Implement this for XP and beyond
+	return syscall.EWINDOWS
+}
+
+func ipv4HeaderPrepend(fd syscall.Handle) (bool, error) {
+	// TODO(mikio): Implement this for XP and beyond
+	return false, syscall.EWINDOWS
+}
+
+func setIPv4HeaderPrepend(fd syscall.Handle, v bool) error {
+	// TODO(mikio): Implement this for XP and beyond
+	return syscall.EWINDOWS
+}
+
+func ipv4ReceiveInterface(fd syscall.Handle) (bool, error) {
+	// TODO(mikio): Implement this for Vista and beyond
+	return false, syscall.EWINDOWS
+}
+
+func setIPv4ReceiveInterface(fd syscall.Handle, v bool) error {
+	// TODO(mikio): Implement this for Vista and beyond
+	return syscall.EWINDOWS
+}
+
+func ipv4MulticastInterface(fd syscall.Handle) (*net.Interface, error) {
+	var a [4]byte
+	l := int32(4)
+	err := syscall.Getsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_IF), (*byte)(unsafe.Pointer(&a[0])), &l)
+	if err != nil {
+		return nil, os.NewSyscallError("getsockopt", err)
+	}
+	return netIP4ToInterface(net.IPv4(a[0], a[1], a[2], a[3]))
+}
+
+func setIPv4MulticastInterface(fd syscall.Handle, ifi *net.Interface) error {
+	ip, err := netInterfaceToIP4(ifi)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	var a [4]byte
+	copy(a[:], ip.To4())
+	err = syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_IF), (*byte)(unsafe.Pointer(&a[0])), 4)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func ipv4MulticastLoopback(fd syscall.Handle) (bool, error) {
+	var v int32
+	l := int32(4)
+	err := syscall.Getsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_LOOP), (*byte)(unsafe.Pointer(&v)), &l)
+	if err != nil {
+		return false, os.NewSyscallError("getsockopt", err)
+	}
+	return v == 1, nil
+}
+
+func setIPv4MulticastLoopback(fd syscall.Handle, v bool) error {
+	vv := int32(boolint(v))
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_MULTICAST_LOOP), (*byte)(unsafe.Pointer(&vv)), 4)
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func joinIPv4Group(fd syscall.Handle, ifi *net.Interface, grp net.IP) error {
+	mreq := &syscall.IPMreq{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if err := setSyscallIPMreq(mreq, ifi); err != nil {
+		return err
+	}
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_ADD_MEMBERSHIP), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq)))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
+
+func leaveIPv4Group(fd syscall.Handle, ifi *net.Interface, grp net.IP) error {
+	mreq := &syscall.IPMreq{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}}
+	if err := setSyscallIPMreq(mreq, ifi); err != nil {
+		return err
+	}
+	err := syscall.Setsockopt(fd, int32(syscall.IPPROTO_IP), int32(syscall.IP_DROP_MEMBERSHIP), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq)))
+	if err != nil {
+		return os.NewSyscallError("setsockopt", err)
+	}
+	return nil
+}
diff --git a/ipv4/unicast_test.go b/ipv4/unicast_test.go
new file mode 100644
index 0000000..3b905a0
--- /dev/null
+++ b/ipv4/unicast_test.go
@@ -0,0 +1,79 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"net"
+	"os"
+	"testing"
+)
+
+func TestReadWriteUnicastIPPayloadUDP(t *testing.T) {
+	c, err := net.ListenPacket("udp4", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	dst, err := net.ResolveUDPAddr("udp4", c.LocalAddr().String())
+	if err != nil {
+		t.Fatalf("net.ResolveUDPAddr failed: %v", err)
+	}
+
+	p := ipv4.NewPacketConn(c)
+	runPayloadTransponder(t, p, []byte("HELLO-R-U-THERE"), dst)
+}
+
+func TestReadWriteUnicastIPPayloadICMP(t *testing.T) {
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	dst, err := net.ResolveIPAddr("ip4", "127.0.0.1")
+	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)
+}
+
+func TestReadWriteUnicastIPDatagram(t *testing.T) {
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
+	if err != nil {
+		t.Fatalf("net.ListenPacket failed: %v", err)
+	}
+	defer c.Close()
+
+	dst, err := net.ResolveIPAddr("ip4", "127.0.0.1")
+	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)
+}
diff --git a/ipv4/unicastsockopt_test.go b/ipv4/unicastsockopt_test.go
new file mode 100644
index 0000000..6316a2a
--- /dev/null
+++ b/ipv4/unicastsockopt_test.go
@@ -0,0 +1,146 @@
+// 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.
+
+// +build darwin freebsd linux netbsd openbsd windows
+
+package ipv4_test
+
+import (
+	"code.google.com/p/go.net/ipv4"
+	"errors"
+	"net"
+	"os"
+	"runtime"
+	"testing"
+)
+
+type testUnicastConn interface {
+	TOS() (int, error)
+	SetTOS(int) error
+	TTL() (int, error)
+	SetTTL(int) error
+}
+
+type unicastSockoptTest struct {
+	tos int
+	ttl int
+}
+
+var unicastSockoptTests = []unicastSockoptTest{
+	{ipv4.DSCP_CS0 | ipv4.ECN_NOTECT, 127},
+	{ipv4.DSCP_AF11 | ipv4.ECN_NOTECT, 255},
+}
+
+func TestTCPUnicastSockopt(t *testing.T) {
+	for _, tt := range unicastSockoptTests {
+		listener := make(chan net.Listener)
+		go tcpListener(t, "127.0.0.1:0", listener)
+		ln := <-listener
+		if ln == nil {
+			return
+		}
+		defer ln.Close()
+		c, err := net.Dial("tcp4", ln.Addr().String())
+		if err != nil {
+			t.Errorf("net.Dial failed: %v", err)
+			return
+		}
+		defer c.Close()
+
+		cc := ipv4.NewConn(c)
+		if err := testUnicastSockopt(t, tt, cc); err != nil {
+			break
+		}
+	}
+}
+
+func tcpListener(t *testing.T, addr string, listener chan<- net.Listener) {
+	ln, err := net.Listen("tcp4", addr)
+	if err != nil {
+		t.Errorf("net.Listen failed: %v", err)
+		listener <- nil
+		return
+	}
+	listener <- ln
+	c, err := ln.Accept()
+	if err != nil {
+		return
+	}
+	c.Close()
+}
+
+func TestUDPUnicastSockopt(t *testing.T) {
+	for _, tt := range unicastSockoptTests {
+		c, err := net.ListenPacket("udp4", "127.0.0.1:0")
+		if err != nil {
+			t.Errorf("net.ListenPacket failed: %v", err)
+			return
+		}
+		defer c.Close()
+
+		p := ipv4.NewPacketConn(c)
+		if err := testUnicastSockopt(t, tt, p); err != nil {
+			break
+		}
+	}
+}
+
+func TestIPUnicastSockopt(t *testing.T) {
+	if os.Getuid() != 0 {
+		t.Logf("skipping test; must be root")
+		return
+	}
+
+	for _, tt := range unicastSockoptTests {
+		c, err := net.ListenPacket("ip4:icmp", "127.0.0.1")
+		if err != nil {
+			t.Errorf("net.ListenPacket failed: %v", err)
+			return
+		}
+		defer c.Close()
+
+		r, err := ipv4.NewRawConn(c)
+		if err != nil {
+			t.Errorf("ipv4.NewRawConn failed: %v", err)
+			return
+		}
+		if err := testUnicastSockopt(t, tt, r); err != nil {
+			break
+		}
+	}
+}
+
+func testUnicastSockopt(t *testing.T, tt unicastSockoptTest, c testUnicastConn) error {
+	switch runtime.GOOS {
+	case "windows":
+		// IP_TOS option is supported on Windows 8 and beyond.
+		t.Logf("skipping IP_TOS test on %q", runtime.GOOS)
+	default:
+		if err := c.SetTOS(tt.tos); err != nil {
+			t.Errorf("ipv4.Conn.SetTOS failed: %v", err)
+			return err
+		}
+		if v, err := c.TOS(); err != nil {
+			t.Errorf("ipv4.Conn.TOS failed: %v", err)
+			return err
+		} else if v != tt.tos {
+			t.Errorf("Got unexpected TOS value %v; expected %v", v, tt.tos)
+			return errors.New("Got unexpected TOS value")
+		}
+	}
+
+	if err := c.SetTTL(tt.ttl); err != nil {
+		t.Errorf("ipv4.Conn.SetTTL failed: %v", err)
+		return err
+	}
+	if v, err := c.TTL(); err != nil {
+		t.Errorf("ipv4.Conn.TTL failed: %v", err)
+		return err
+	} else if v != tt.ttl {
+		t.Errorf("Got unexpected TTL value %v; expected %v", v, tt.ttl)
+		return errors.New("Got unexpected TTL value")
+	}
+
+	return nil
+}