diff --git a/ipv4/control.go b/ipv4/control.go new file mode 100644 index 00000000..621c7ac8 --- /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 "" + } + 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 00000000..63cd6c6b --- /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 00000000..16f53d07 --- /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 00000000..df9d50bf --- /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 00000000..7b1d604f --- /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 00000000..b211d914 --- /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 00000000..0ba81c20 --- /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 00000000..bfc60446 --- /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 00000000..6e598b47 --- /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 00000000..9f4644e6 --- /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 00000000..2dff61ee --- /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 00000000..f1eab2dc --- /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 00000000..ab17da7d --- /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 "" + } + 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 00000000..0ac02ed3 --- /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 00000000..2cc56bd4 --- /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 00000000..2bc9c717 --- /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 00000000..0adb3e1a --- /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 00000000..4eab7756 --- /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 00000000..0733e3ec --- /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 00000000..5bf89209 --- /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 00000000..99759ef1 --- /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 00000000..01cc2a13 --- /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 00000000..4199c056 --- /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 00000000..3f227399 --- /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 00000000..3847a9e5 --- /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 = +// TOS = +// TotalLen = +// ID = platform sets an appropriate value if ID is zero +// FragOff = +// TTL = +// Protocol = +// Checksum = platform sets an appropriate value if Checksum is zero +// Src = platform sets an appropriate value if Src is nil +// Dst = +// 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 00000000..bfcf7513 --- /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 00000000..46ab31cb --- /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 00000000..6bcd66f7 --- /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 00000000..8c0ca066 --- /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 00000000..0816a70c --- /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 00000000..ef000ecc --- /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 00000000..b1e4edf9 --- /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 00000000..3b905a04 --- /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 00000000..6316a2a6 --- /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 +}