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
+}