diff --git a/ipv6/control.go b/ipv6/control.go new file mode 100644 index 00000000..be06dd67 --- /dev/null +++ b/ipv6/control.go @@ -0,0 +1,84 @@ +// Copyright 2013 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 ipv6 + +import ( + "errors" + "fmt" + "net" + "sync" +) + +var ( + errNotSupported = errors.New("not supported") + errMissingAddress = errors.New("missing address") + errInvalidConnType = errors.New("invalid conn type") + errNoSuchInterface = errors.New("no such interface") +) + +// References: +// +// RFC 2292 Advanced Sockets API for IPv6 +// http://tools.ietf.org/html/rfc2292 +// RFC 2460 Internet Protocol, Version 6 (IPv6) Specification +// http://tools.ietf.org/html/rfc2460 +// RFC 3493 Basic Socket Interface Extensions for IPv6 +// http://tools.ietf.org/html/rfc3493.html +// RFC 3542 Advanced Sockets Application Program Interface (API) for IPv6 +// http://tools.ietf.org/html/rfc3542 +// +// Note that RFC 3542 obsoltes RFC 2292 but OS X Snow Leopard and the +// former still support RFC 2292 only. Please be aware that almost +// all protocol implementations prohibit using a combination of RFC +// 2292 and RFC 3542 for some practical reasons. + +type rawOpt struct { + sync.Mutex + cflags ControlFlags +} + +func (c *rawOpt) set(f ControlFlags) { c.cflags |= f } +func (c *rawOpt) clear(f ControlFlags) { c.cflags &^= f } +func (c *rawOpt) isset(f ControlFlags) bool { return c.cflags&f != 0 } + +// A ControlFlags reprensents per packet basis IP-level socket option +// control flags. +type ControlFlags uint + +const ( + FlagTrafficClass ControlFlags = 1 << iota // pass the traffic class on the received packet + FlagHopLimit // pass the hop limit 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 + FlagPathMTU // pass the path MTU on the received packet path +) + +// A ControlMessage represents per packet basis IP-level socket +// options. +type ControlMessage struct { + // Receiving socket options: SetControlMessage allows to + // receive the options from the protocol stack using ReadFrom + // method of PacketConn. + // + // Specifying socket options: ControlMessage for WriteTo + // method of PacketConn allows to send the options to the + // protocol stack. + // + TrafficClass int // traffic class, must be 1 <= value <= 255 when specifying + HopLimit int // hop limit, must be 1 <= value <= 255 when specifying + Src net.IP // source address, specifying only + Dst net.IP // destination address, receiving only + IfIndex int // interface index, must be 1 <= value when specifying + NextHop net.IP // next hop address, specifying only + MTU int // path MTU, receiving only +} + +func (cm *ControlMessage) String() string { + if cm == nil { + return "" + } + return fmt.Sprintf("tclass: %#x, hoplim: %v, src: %v, dst: %v, ifindex: %v, nexthop: %v, mtu: %v", cm.TrafficClass, cm.HopLimit, cm.Src, cm.Dst, cm.IfIndex, cm.NextHop, cm.MTU) +} diff --git a/ipv6/control_rfc2292_darwin.go b/ipv6/control_rfc2292_darwin.go new file mode 100644 index 00000000..f09ad7b6 --- /dev/null +++ b/ipv6/control_rfc2292_darwin.go @@ -0,0 +1,151 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "os" + "syscall" + "unsafe" +) + +const pktinfo = FlagDst | FlagInterface + +func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error { + opt.Lock() + defer opt.Unlock() + if cf&FlagHopLimit != 0 { + if err := setIPv6ReceiveHopLimit(fd, on); err != nil { + return err + } + if on { + opt.set(FlagHopLimit) + } else { + opt.clear(FlagHopLimit) + } + } + if cf&pktinfo != 0 { + if err := setIPv6ReceivePacketInfo(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() + l, off := 0, 0 + if opt.isset(FlagHopLimit) { + l += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if l > 0 { + oob = make([]byte, l) + if opt.isset(FlagHopLimit) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_2292HOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + off += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_2292PKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + } + return +} + +func parseControlMessage(b []byte) (*ControlMessage, error) { + if len(b) == 0 { + return nil, nil + } + cmsgs, err := syscall.ParseSocketControlMessage(b) + if err != nil { + return nil, os.NewSyscallError("parse socket control message", err) + } + cm := &ControlMessage{} + for _, m := range cmsgs { + if m.Header.Level != ianaProtocolIPv6 { + continue + } + switch m.Header.Type { + case syscall.IPV6_2292HOPLIMIT: + cm.HopLimit = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0]))) + case syscall.IPV6_2292PKTINFO: + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&m.Data[0])) + cm.IfIndex = int(pi.Ifindex) + cm.Dst = pi.Addr[:] + } + } + return cm, nil +} + +func marshalControlMessage(cm *ControlMessage) (oob []byte) { + if cm == nil { + return + } + l, off := 0, 0 + if cm.HopLimit > 0 { + l += syscall.CmsgSpace(4) + } + pion := false + if cm.Src.To4() == nil && cm.Src.To16() != nil || cm.IfIndex != 0 { + pion = true + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + l += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + if l > 0 { + oob = make([]byte, l) + if cm.HopLimit > 0 { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_2292HOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + data := oob[off+syscall.CmsgLen(0):] + *(*byte)(unsafe.Pointer(&data[:1][0])) = byte(cm.HopLimit) + off += syscall.CmsgSpace(4) + } + if pion { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_2292PKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + if ip := cm.Src.To16(); ip != nil && ip.To4() == nil { + copy(pi.Addr[:], ip) + } + if cm.IfIndex != 0 { + pi.Ifindex = uint32(cm.IfIndex) + } + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_2292NEXTHOP + m.SetLen(syscall.CmsgLen(syscall.SizeofSockaddrInet6)) + sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + sa.Len = syscall.SizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], cm.NextHop) + off += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + } + return +} diff --git a/ipv6/control_rfc3542_bsd.go b/ipv6/control_rfc3542_bsd.go new file mode 100644 index 00000000..0b42c0b7 --- /dev/null +++ b/ipv6/control_rfc3542_bsd.go @@ -0,0 +1,213 @@ +// Copyright 2013 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 freebsd netbsd openbsd + +package ipv6 + +import ( + "net" + "os" + "syscall" + "unsafe" +) + +const pktinfo = FlagDst | FlagInterface + +func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error { + opt.Lock() + defer opt.Unlock() + if cf&FlagTrafficClass != 0 { + if err := setIPv6ReceiveTrafficClass(fd, on); err != nil { + return err + } + if on { + opt.set(FlagTrafficClass) + } else { + opt.clear(FlagTrafficClass) + } + } + if cf&FlagHopLimit != 0 { + if err := setIPv6ReceiveHopLimit(fd, on); err != nil { + return err + } + if on { + opt.set(FlagHopLimit) + } else { + opt.clear(FlagHopLimit) + } + } + if cf&pktinfo != 0 { + if err := setIPv6ReceivePacketInfo(fd, on); err != nil { + return err + } + if on { + opt.set(cf & pktinfo) + } else { + opt.clear(cf & pktinfo) + } + } + if cf&FlagPathMTU != 0 { + if err := setIPv6ReceivePathMTU(fd, on); err != nil { + return err + } + if on { + opt.set(FlagPathMTU) + } else { + opt.clear(FlagPathMTU) + } + } + return nil +} + +func newControlMessage(opt *rawOpt) (oob []byte) { + opt.Lock() + defer opt.Unlock() + l, off := 0, 0 + if opt.isset(FlagTrafficClass) { + l += syscall.CmsgSpace(4) + } + if opt.isset(FlagHopLimit) { + l += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if opt.isset(FlagPathMTU) { + l += syscall.CmsgSpace(syscall.SizeofIPv6MTUInfo) + } + if l > 0 { + oob = make([]byte, l) + if opt.isset(FlagTrafficClass) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVTCLASS + m.SetLen(syscall.CmsgLen(4)) + off += syscall.CmsgSpace(4) + } + if opt.isset(FlagHopLimit) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVHOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + off += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVPKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if opt.isset(FlagPathMTU) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVPATHMTU + m.SetLen(syscall.CmsgLen(syscall.SizeofIPv6MTUInfo)) + off += syscall.CmsgSpace(syscall.SizeofIPv6MTUInfo) + } + } + return +} + +func parseControlMessage(b []byte) (*ControlMessage, error) { + if len(b) == 0 { + return nil, nil + } + cmsgs, err := syscall.ParseSocketControlMessage(b) + if err != nil { + return nil, os.NewSyscallError("parse socket control message", err) + } + cm := &ControlMessage{} + for _, m := range cmsgs { + if m.Header.Level != ianaProtocolIPv6 { + continue + } + switch m.Header.Type { + case syscall.IPV6_TCLASS: + cm.TrafficClass = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0]))) + case syscall.IPV6_HOPLIMIT: + cm.HopLimit = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0]))) + case syscall.IPV6_PKTINFO: + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&m.Data[0])) + cm.Dst = pi.Addr[:] + cm.IfIndex = int(pi.Ifindex) + case syscall.IPV6_PATHMTU: + mi := (*syscall.IPv6MTUInfo)(unsafe.Pointer(&m.Data[0])) + cm.Dst = mi.Addr.Addr[:] + cm.IfIndex = int(mi.Addr.Scope_id) + cm.MTU = int(mi.Mtu) + } + } + return cm, nil +} + +func marshalControlMessage(cm *ControlMessage) (oob []byte) { + if cm == nil { + return + } + l, off := 0, 0 + if cm.TrafficClass > 0 { + l += syscall.CmsgSpace(4) + } + if cm.HopLimit > 0 { + l += syscall.CmsgSpace(4) + } + pion := false + if cm.Src.To4() == nil && cm.Src.To16() != nil || cm.IfIndex != 0 { + pion = true + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + l += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + if l > 0 { + oob = make([]byte, l) + if cm.TrafficClass > 0 { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_TCLASS + m.SetLen(syscall.CmsgLen(4)) + data := oob[off+syscall.CmsgLen(0):] + *(*byte)(unsafe.Pointer(&data[:1][0])) = byte(cm.TrafficClass) + off += syscall.CmsgSpace(4) + } + if cm.HopLimit > 0 { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_HOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + data := oob[off+syscall.CmsgLen(0):] + *(*byte)(unsafe.Pointer(&data[:1][0])) = byte(cm.HopLimit) + off += syscall.CmsgSpace(4) + } + if pion { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_PKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + if ip := cm.Src.To16(); ip != nil && ip.To4() == nil { + copy(pi.Addr[:], ip) + } + if cm.IfIndex != 0 { + pi.Ifindex = uint32(cm.IfIndex) + } + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_NEXTHOP + m.SetLen(syscall.CmsgLen(syscall.SizeofSockaddrInet6)) + sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + sa.Len = syscall.SizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], cm.NextHop) + sa.Scope_id = uint32(cm.IfIndex) + off += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + } + return +} diff --git a/ipv6/control_rfc3542_linux.go b/ipv6/control_rfc3542_linux.go new file mode 100644 index 00000000..3e73f3b1 --- /dev/null +++ b/ipv6/control_rfc3542_linux.go @@ -0,0 +1,217 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "os" + "syscall" + "unsafe" +) + +const ( + // See /usr/include/linux/in6.h. + syscall_IPV6_RECVPATHMTU = syscall.IPV6_DSTOPTS + 1 + iota + syscall_IPV6_PATHMTU + syscall_IPV6_DONTFRAG +) + +const pktinfo = FlagDst | FlagInterface + +func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error { + opt.Lock() + defer opt.Unlock() + if cf&FlagTrafficClass != 0 { + if err := setIPv6ReceiveTrafficClass(fd, on); err != nil { + return err + } + if on { + opt.set(FlagTrafficClass) + } else { + opt.clear(FlagTrafficClass) + } + } + if cf&FlagHopLimit != 0 { + if err := setIPv6ReceiveHopLimit(fd, on); err != nil { + return err + } + if on { + opt.set(FlagHopLimit) + } else { + opt.clear(FlagHopLimit) + } + } + if cf&pktinfo != 0 { + if err := setIPv6ReceivePacketInfo(fd, on); err != nil { + return err + } + if on { + opt.set(cf & pktinfo) + } else { + opt.clear(cf & pktinfo) + } + } + if cf&FlagPathMTU != 0 { + if err := setIPv6ReceivePathMTU(fd, on); err != nil { + return err + } + if on { + opt.set(FlagPathMTU) + } else { + opt.clear(FlagPathMTU) + } + } + return nil +} + +func newControlMessage(opt *rawOpt) (oob []byte) { + opt.Lock() + defer opt.Unlock() + l, off := 0, 0 + if opt.isset(FlagTrafficClass) { + l += syscall.CmsgSpace(4) + } + if opt.isset(FlagHopLimit) { + l += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if opt.isset(FlagPathMTU) { + l += syscall.CmsgSpace(syscall.SizeofIPv6MTUInfo) + } + if l > 0 { + oob = make([]byte, l) + if opt.isset(FlagTrafficClass) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVTCLASS + m.SetLen(syscall.CmsgLen(4)) + off += syscall.CmsgSpace(4) + } + if opt.isset(FlagHopLimit) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVHOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + off += syscall.CmsgSpace(4) + } + if opt.isset(pktinfo) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_RECVPKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if opt.isset(FlagPathMTU) { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall_IPV6_RECVPATHMTU + m.SetLen(syscall.CmsgLen(syscall.SizeofIPv6MTUInfo)) + off += syscall.CmsgSpace(syscall.SizeofIPv6MTUInfo) + } + } + return +} + +func parseControlMessage(b []byte) (*ControlMessage, error) { + if len(b) == 0 { + return nil, nil + } + cmsgs, err := syscall.ParseSocketControlMessage(b) + if err != nil { + return nil, os.NewSyscallError("parse socket control message", err) + } + cm := &ControlMessage{} + for _, m := range cmsgs { + if m.Header.Level != ianaProtocolIPv6 { + continue + } + switch m.Header.Type { + case syscall.IPV6_TCLASS: + cm.TrafficClass = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0]))) + case syscall.IPV6_HOPLIMIT: + cm.HopLimit = int(*(*byte)(unsafe.Pointer(&m.Data[:1][0]))) + case syscall.IPV6_PKTINFO: + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&m.Data[0])) + cm.Dst = pi.Addr[:] + cm.IfIndex = int(pi.Ifindex) + case syscall_IPV6_PATHMTU: + mi := (*syscall.IPv6MTUInfo)(unsafe.Pointer(&m.Data[0])) + cm.Dst = mi.Addr.Addr[:] + cm.IfIndex = int(mi.Addr.Scope_id) + cm.MTU = int(mi.Mtu) + } + } + return cm, nil +} + +func marshalControlMessage(cm *ControlMessage) (oob []byte) { + if cm == nil { + return + } + l, off := 0, 0 + if cm.TrafficClass > 0 { + l += syscall.CmsgSpace(4) + } + if cm.HopLimit > 0 { + l += syscall.CmsgSpace(4) + } + pion := false + if cm.Src.To4() == nil && cm.Src.To16() != nil || cm.IfIndex != 0 { + pion = true + l += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + l += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + if l > 0 { + oob = make([]byte, l) + if cm.TrafficClass > 0 { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_TCLASS + m.SetLen(syscall.CmsgLen(4)) + data := oob[off+syscall.CmsgLen(0):] + *(*byte)(unsafe.Pointer(&data[:1][0])) = byte(cm.TrafficClass) + off += syscall.CmsgSpace(4) + } + if cm.HopLimit > 0 { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_HOPLIMIT + m.SetLen(syscall.CmsgLen(4)) + data := oob[off+syscall.CmsgLen(0):] + *(*byte)(unsafe.Pointer(&data[:1][0])) = byte(cm.HopLimit) + off += syscall.CmsgSpace(4) + } + if pion { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_PKTINFO + m.SetLen(syscall.CmsgLen(syscall.SizeofInet6Pktinfo)) + pi := (*syscall.Inet6Pktinfo)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + if ip := cm.Src.To16(); ip != nil && ip.To4() == nil { + copy(pi.Addr[:], ip) + } + if cm.IfIndex != 0 { + pi.Ifindex = uint32(cm.IfIndex) + } + off += syscall.CmsgSpace(syscall.SizeofInet6Pktinfo) + } + if len(cm.NextHop) == net.IPv6len { + m := (*syscall.Cmsghdr)(unsafe.Pointer(&oob[off])) + m.Level = ianaProtocolIPv6 + m.Type = syscall.IPV6_NEXTHOP + m.SetLen(syscall.CmsgLen(syscall.SizeofSockaddrInet6)) + sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&oob[off+syscall.CmsgLen(0)])) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], cm.NextHop) + sa.Scope_id = uint32(cm.IfIndex) + off += syscall.CmsgSpace(syscall.SizeofSockaddrInet6) + } + } + return +} diff --git a/ipv6/control_rfc3542_plan9.go b/ipv6/control_rfc3542_plan9.go new file mode 100644 index 00000000..052b229f --- /dev/null +++ b/ipv6/control_rfc3542_plan9.go @@ -0,0 +1,27 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +func newControlMessage(opt *rawOpt) (oob []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) (oob []byte) { + // TODO(mikio): Implement this + return nil +} diff --git a/ipv6/control_rfc3542_windows.go b/ipv6/control_rfc3542_windows.go new file mode 100644 index 00000000..b4d53fb6 --- /dev/null +++ b/ipv6/control_rfc3542_windows.go @@ -0,0 +1,27 @@ +// Copyright 2013 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 ipv6 + +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) (oob []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) (oob []byte) { + // TODO(mikio): Implement this + return nil +} diff --git a/ipv6/control_test.go b/ipv6/control_test.go new file mode 100644 index 00000000..db66f8e1 --- /dev/null +++ b/ipv6/control_test.go @@ -0,0 +1,32 @@ +// Copyright 2013 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 ipv6 + +import ( + "sync" + "testing" +) + +func TestControlFlags(t *testing.T) { + tf := FlagInterface | FlagPathMTU + opt := rawOpt{cflags: tf | FlagHopLimit} + + type ffn func(ControlFlags) + var wg sync.WaitGroup + for _, fn := range []ffn{opt.set, opt.clear, opt.clear} { + wg.Add(1) + go func(fn ffn) { + defer wg.Done() + opt.Lock() + defer opt.Unlock() + fn(tf) + }(fn) + } + wg.Wait() + + if opt.isset(tf) { + t.Fatalf("got %#x; expected %#x", opt.cflags, FlagHopLimit) + } +} diff --git a/ipv6/dgramopt_plan9.go b/ipv6/dgramopt_plan9.go new file mode 100644 index 00000000..4c26be25 --- /dev/null +++ b/ipv6/dgramopt_plan9.go @@ -0,0 +1,96 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "syscall" +) + +// MulticastHopLimit returns the hop limit field value for outgoing +// multicast packets. +func (c *dgramOpt) MulticastHopLimit() (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EPLAN9 +} + +// SetMulticastHopLimit sets the hop limit field value for future +// outgoing multicast packets. +func (c *dgramOpt) SetMulticastHopLimit(hoplim int) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// MulticastInterface returns the default interface for multicast +// packet transmissions. +func (c *dgramOpt) MulticastInterface() (*net.Interface, error) { + // TODO(mikio): Implement this + return nil, syscall.EPLAN9 +} + +// SetMulticastInterface sets the default interface for future +// multicast packet transmissions. +func (c *dgramOpt) SetMulticastInterface(ifi *net.Interface) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// MulticastLoopback reports whether transmitted multicast packets +// should be copied and send back to the originator. +func (c *dgramOpt) MulticastLoopback() (bool, error) { + // TODO(mikio): Implement this + return false, syscall.EPLAN9 +} + +// SetMulticastLoopback sets whether transmitted multicast packets +// should be copied and send back to the originator. +func (c *dgramOpt) SetMulticastLoopback(on bool) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// 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 { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// LeaveGroup leaves the group address group on the interface ifi. +func (c *dgramOpt) LeaveGroup(ifi *net.Interface, group net.Addr) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// Checksum reports whether the kernel will compute, store or verify a +// checksum for both incoming and outgoing packets. If on is true, it +// returns an offset in bytes into the data of where the checksum +// field is located. +func (c *dgramOpt) Checksum() (on bool, offset int, err error) { + // TODO(mikio): Implement this + return false, 0, syscall.EPLAN9 +} + +// SetChecksum enables the kernel checksum processing. If on is ture, +// the offset should be an offset in bytes into the data of where the +// checksum field is located. +func (c *dgramOpt) SetChecksum(on bool, offset int) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// ICMPFilter returns an ICMP filter. +func (c *dgramOpt) ICMPFilter() (*ICMPFilter, error) { + // TODO(mikio): Implement this + return nil, syscall.EPLAN9 +} + +// SetICMPFilter deploys the ICMP filter. +func (c *dgramOpt) SetICMPFilter(f *ICMPFilter) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} diff --git a/ipv6/dgramopt_posix.go b/ipv6/dgramopt_posix.go new file mode 100644 index 00000000..c52f48da --- /dev/null +++ b/ipv6/dgramopt_posix.go @@ -0,0 +1,178 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "syscall" +) + +// MulticastHopLimit returns the hop limit field value for outgoing +// multicast packets. +func (c *dgramOpt) MulticastHopLimit() (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return 0, err + } + return ipv6MulticastHopLimit(fd) +} + +// SetMulticastHopLimit sets the hop limit field value for future +// outgoing multicast packets. +func (c *dgramOpt) SetMulticastHopLimit(hoplim int) error { + if !c.ok() { + return syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return err + } + return setIPv6MulticastHopLimit(fd, hoplim) +} + +// 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 ipv6MulticastInterface(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 setIPv6MulticastInterface(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 ipv6MulticastLoopback(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 setIPv6MulticastLoopback(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 := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + return joinIPv6Group(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 := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + return leaveIPv6Group(fd, ifi, grp) +} + +// Checksum reports whether the kernel will compute, store or verify a +// checksum for both incoming and outgoing packets. If on is true, it +// returns an offset in bytes into the data of where the checksum +// field is located. +func (c *dgramOpt) Checksum() (on bool, offset int, err error) { + if !c.ok() { + return false, 0, syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return false, 0, err + } + return ipv6Checksum(fd) +} + +// SetChecksum enables the kernel checksum processing. If on is ture, +// the offset should be an offset in bytes into the data of where the +// checksum field is located. +func (c *dgramOpt) SetChecksum(on bool, offset int) error { + if !c.ok() { + return syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return err + } + return setIPv6Checksum(fd, on, offset) +} + +// ICMPFilter returns an ICMP filter. +func (c *dgramOpt) ICMPFilter() (*ICMPFilter, error) { + if !c.ok() { + return nil, syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return nil, err + } + return ipv6ICMPFilter(fd) +} + +// SetICMPFilter deploys the ICMP filter. +func (c *dgramOpt) SetICMPFilter(f *ICMPFilter) error { + if !c.ok() { + return syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return err + } + return setIPv6ICMPFilter(fd, f) +} diff --git a/ipv6/doc.go b/ipv6/doc.go new file mode 100644 index 00000000..30201f8f --- /dev/null +++ b/ipv6/doc.go @@ -0,0 +1,193 @@ +// Copyright 2013 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 ipv6 implements IP-level socket options for the Internet +// Protocol version 6. +// +// The package provides IP-level socket options that allow +// manipulation of IPv6 facilities. The IPv6 and socket options for +// IPv6 are defined in RFC 2460, RFC 3493 and RFC 3542. +// +// +// Unicasting +// +// The options for unicasting are available for net.TCPConn, +// net.UDPConn and net.IPConn which are created as network connections +// that use the IPv6 transport. When a single TCP connection carrying +// a data flow of multiple packets needs to indicate the flow is +// important, ipv6.Conn is used to set the traffic class field on the +// IPv6 header for each packet. +// +// ln, err := net.Listen("tcp6", "[::]: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. +// +// if err := ipv6.NewConn(c).SetTrafficClass(DiffServAF11); err != nil { +// // error handling +// } +// if _, err := c.Write(data); 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 +// IPv6 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.ParseIP("ff02::114") +// +// First, an application listens to an appropriate address with an +// appropriate service port. +// +// c, err := net.ListenPacket("udp6", "[::]: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 IPv6 and Ethernet. +// +// p := ipv6.NewPacketConn(c) +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: group}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en1, &net.UDPAddr{IP: group}); 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 ipv6.PacketConn is used to enable control +// message transmissons. +// +// if err := p.SetControlMessage(ipv6.FlagDst, true); 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, rcm, src, err := p.ReadFrom(b) +// if err != nil { +// // error handling +// } +// if rcm.Dst.IsMulticast() { +// if rcm.Dst.Equal(group) +// // joined group, do something +// } else { +// // unknown group, discard +// continue +// } +// } +// +// The application can also send both unicast and multicast packets. +// +// p.SetTrafficClass(DiffServCS0) +// p.SetHopLimit(16) +// if _, err := p.WriteTo(data[:n], nil, src); err != nil { +// // error handling +// } +// dst := &net.UDPAddr{IP: group, Port: 1024} +// wcm := ipv6.ControlMessage{TrafficClass: DiffServCS7, HopLimit: 1} +// for _, ifi := range []*net.Interface{en0, en1} { +// wcm.IfIndex = ifi.Index +// if _, err := p.WriteTo(data[:n], &wcm, dst); 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("udp6", "[::]:1024") +// if err != nil { +// // error handling +// } +// defer c.Close() +// p := ipv6.NewPacketConn(c) +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::1:114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::2:114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en1, &net.UDPAddr{IP: net.ParseIP("ff02::2:114")}); 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("udp6", "[ff02::]:1024") +// if err != nil { +// // error handling +// } +// defer c1.Close() +// c2, err := net.ListenPacket("udp6", "[ff02::]:1024") +// if err != nil { +// // error handling +// } +// defer c2.Close() +// p1 := ipv6.NewPacketConn(c1) +// if err := p1.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// p2 := ipv6.NewPacketConn(c2) +// if err := p2.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// +// Also it is possible for the application to leave or rejoin a +// multicast group on the network interface. +// +// if err := p.LeaveGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff01::114")}); err != nil { +// // error handling +// } +package ipv6 diff --git a/ipv6/endpoint.go b/ipv6/endpoint.go new file mode 100644 index 00000000..aa19587d --- /dev/null +++ b/ipv6/endpoint.go @@ -0,0 +1,83 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "syscall" +) + +// A Conn represents a network endpoint that uses IPv6 transport. +// It allows to set basic IP-level socket options such as traffic +// class and hop limit. +type Conn struct { + genericOpt +} + +type genericOpt struct { + net.Conn +} + +func (c *genericOpt) ok() bool { return c != nil && c.Conn != nil } + +// PathMTU returns a path MTU value for the destination associated +// with the endpoint. +func (c *Conn) PathMTU() (int, error) { + if !c.genericOpt.ok() { + return 0, syscall.EINVAL + } + fd, err := c.genericOpt.sysfd() + if err != nil { + return 0, err + } + return ipv6PathMTU(fd) +} + +// NewConn returns a new Conn. +func NewConn(c net.Conn) *Conn { + return &Conn{ + genericOpt: genericOpt{Conn: c}, + } +} + +// A PacketConn represents a packet network endpoint that uses IPv6 +// transport. It is used to control several IP-level socket options +// including IPv6 header manipulation. It also provides datagram +// based network I/O methods specific to the IPv6 and higher layer +// protocols such as OSPF, GRE, and UDP. +type PacketConn struct { + genericOpt + dgramOpt + payloadHandler +} + +type dgramOpt struct { + net.PacketConn +} + +func (c *dgramOpt) ok() bool { return c != nil && c.PacketConn != nil } + +// SetControlMessage allows to receive the per packet basis 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) +} + +// NewPacketConn returns a new PacketConn using c as its underlying +// transport. +func NewPacketConn(c net.PacketConn) *PacketConn { + return &PacketConn{ + genericOpt: genericOpt{Conn: c.(net.Conn)}, + dgramOpt: dgramOpt{PacketConn: c}, + payloadHandler: payloadHandler{PacketConn: c}, + } +} diff --git a/ipv6/gen.go b/ipv6/gen.go new file mode 100644 index 00000000..de529ade --- /dev/null +++ b/ipv6/gen.go @@ -0,0 +1,237 @@ +// Copyright 2013 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 ignore + +// This program generates internet protocol constatns and tables by +// reading IANA protocol registries. +// +// Usage: +// go run gen.go > iana.go +package main + +import ( + "bytes" + "encoding/xml" + "fmt" + "go/format" + "io" + "net/http" + "os" + "strconv" + "strings" +) + +var registries = []struct { + url string + parse func(io.Writer, io.Reader) error +}{ + { + "http://www.iana.org/assignments/icmpv6-parameters", + parseICMPv6Parameters, + }, + { + "http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml", + parseProtocolNumbers, + }, +} + +func main() { + var bb bytes.Buffer + fmt.Fprintf(&bb, "// go run gen.go\n") + fmt.Fprintf(&bb, "// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n") + fmt.Fprintf(&bb, "package ipv6\n\n") + for _, r := range registries { + resp, err := http.Get(r.url) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + fmt.Fprintf(os.Stderr, "got HTTP status code %v for %v\n", resp.StatusCode, r.url) + os.Exit(1) + } + if err := r.parse(&bb, resp.Body); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Fprintf(&bb, "\n") + } + b, err := format.Source(bb.Bytes()) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Stdout.Write(b) +} + +func parseICMPv6Parameters(w io.Writer, r io.Reader) error { + dec := xml.NewDecoder(r) + var icp icmpv6Parameters + if err := dec.Decode(&icp); err != nil { + return err + } + prs := icp.escape(1) + fmt.Fprintf(w, "// %s, Updated: %s\n", icp.Title, icp.Updated) + fmt.Fprintf(w, "const (\n") + for _, pr := range prs { + if pr.Name == "" { + continue + } + fmt.Fprintf(w, "ICMPType%s ICMPType = %d", pr.Name, pr.Value) + fmt.Fprintf(w, "// %s\n", pr.OrigName) + } + fmt.Fprintf(w, ")\n\n") + fmt.Fprintf(w, "// %s, Updated: %s\n", icp.Title, icp.Updated) + fmt.Fprintf(w, "var icmpTypes = map[ICMPType]string{\n") + for _, pr := range prs { + if pr.Name == "" { + continue + } + fmt.Fprintf(w, "%d: %q,\n", pr.Value, strings.ToLower(pr.OrigName)) + } + fmt.Fprintf(w, "}\n") + return nil +} + +type icmpv6Parameters struct { + XMLName xml.Name `xml:"registry"` + Title string `xml:"title"` + Updated string `xml:"updated"` + Registries []icmpv6ParamRegistry `xml:"registry"` +} + +type icmpv6ParamRegistry struct { + Title string `xml:"title"` + Records []icmpv6ParamRecord `xml:"record"` +} + +type icmpv6ParamRecord struct { + Value string `xml:"value"` + Name string `xml:"name"` +} + +type canonICMPv6ParamRecord struct { + OrigName string + Name string + Value int +} + +func (icp *icmpv6Parameters) escape(id int) []canonICMPv6ParamRecord { + prs := make([]canonICMPv6ParamRecord, len(icp.Registries[id].Records)) + sr := strings.NewReplacer( + "Messages", "", + "Message", "", + "ICMP", "", + "+", "P", + "-", "", + "/", "", + ".", "", + " ", "", + ) + for i, pr := range icp.Registries[id].Records { + if strings.Contains(pr.Name, "Reserved") || + strings.Contains(pr.Name, "Unassigned") || + strings.Contains(pr.Name, "Deprecated") || + strings.Contains(pr.Name, "Experiment") || + strings.Contains(pr.Name, "experiment") { + continue + } + ss := strings.Split(pr.Name, "\n") + if len(ss) > 1 { + prs[i].Name = strings.Join(ss, " ") + } else { + prs[i].Name = ss[0] + } + s := strings.TrimSpace(prs[i].Name) + prs[i].OrigName = s + prs[i].Name = sr.Replace(s) + prs[i].Value, _ = strconv.Atoi(pr.Value) + } + return prs +} + +func parseProtocolNumbers(w io.Writer, r io.Reader) error { + dec := xml.NewDecoder(r) + var pn protocolNumbers + if err := dec.Decode(&pn); err != nil { + return err + } + prs := pn.escape() + fmt.Fprintf(w, "// %s, Updated: %s\n", pn.Title, pn.Updated) + fmt.Fprintf(w, "const (\n") + for _, pr := range prs { + if pr.Name == "" { + continue + } + fmt.Fprintf(w, "ianaProtocol%s = %d", pr.Name, pr.Value) + s := pr.Descr + if s == "" { + s = pr.OrigName + } + fmt.Fprintf(w, "// %s\n", s) + } + fmt.Fprintf(w, ")\n") + return nil +} + +type protocolNumbers struct { + XMLName xml.Name `xml:"registry"` + Title string `xml:"title"` + Updated string `xml:"updated"` + RegTitle string `xml:"registry>title"` + Note string `xml:"registry>note"` + Records []protocolRecord `xml:"registry>record"` +} + +type protocolRecord struct { + Value string `xml:"value"` + Name string `xml:"name"` + Descr string `xml:"description"` +} + +type canonProtocolRecord struct { + OrigName string + Name string + Descr string + Value int +} + +func (pn *protocolNumbers) escape() []canonProtocolRecord { + prs := make([]canonProtocolRecord, len(pn.Records)) + sr := strings.NewReplacer( + "-in-", "in", + "-within-", "within", + "-over-", "over", + "+", "P", + "-", "", + "/", "", + ".", "", + " ", "", + ) + for i, pr := range pn.Records { + prs[i].OrigName = pr.Name + s := strings.TrimSpace(pr.Name) + switch pr.Name { + case "ISIS over IPv4": + prs[i].Name = "ISIS" + case "manet": + prs[i].Name = "MANET" + default: + prs[i].Name = sr.Replace(s) + } + ss := strings.Split(pr.Descr, "\n") + for i := range ss { + ss[i] = strings.TrimSpace(ss[i]) + } + if len(ss) > 1 { + prs[i].Descr = strings.Join(ss, " ") + } else { + prs[i].Descr = ss[0] + } + prs[i].Value, _ = strconv.Atoi(pr.Value) + } + return prs +} diff --git a/ipv6/genericopt_plan9.go b/ipv6/genericopt_plan9.go new file mode 100644 index 00000000..6108443f --- /dev/null +++ b/ipv6/genericopt_plan9.go @@ -0,0 +1,34 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +// TrafficClass returns the traffic class field value for outgoing +// packets. +func (c *genericOpt) TrafficClass() (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EPLAN9 +} + +// SetTrafficClass sets the traffic class field value for future +// outgoing packets. +func (c *genericOpt) SetTrafficClass(tclass int) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} + +// HopLimit returns the hop limit field value for outgoing packets. +func (c *genericOpt) HopLimit() (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EPLAN9 +} + +// SetHopLimit sets the hop limit field value for future outgoing +// packets. +func (c *genericOpt) SetHopLimit(hoplim int) error { + // TODO(mikio): Implement this + return syscall.EPLAN9 +} diff --git a/ipv6/genericopt_posix.go b/ipv6/genericopt_posix.go new file mode 100644 index 00000000..a3a9af9a --- /dev/null +++ b/ipv6/genericopt_posix.go @@ -0,0 +1,60 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +// TrafficClass returns the traffic class field value for outgoing +// packets. +func (c *genericOpt) TrafficClass() (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return 0, err + } + return ipv6TrafficClass(fd) +} + +// SetTrafficClass sets the traffic class field value for future +// outgoing packets. +func (c *genericOpt) SetTrafficClass(tclass int) error { + if !c.ok() { + return syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return err + } + return setIPv6TrafficClass(fd, tclass) +} + +// HopLimit returns the hop limit field value for outgoing packets. +func (c *genericOpt) HopLimit() (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return 0, err + } + return ipv6HopLimit(fd) +} + +// SetHopLimit sets the hop limit field value for future outgoing +// packets. +func (c *genericOpt) SetHopLimit(hoplim int) error { + if !c.ok() { + return syscall.EINVAL + } + fd, err := c.sysfd() + if err != nil { + return err + } + return setIPv6HopLimit(fd, hoplim) +} diff --git a/ipv6/gentest.go b/ipv6/gentest.go new file mode 100644 index 00000000..fddbfd1e --- /dev/null +++ b/ipv6/gentest.go @@ -0,0 +1,196 @@ +// Copyright 2013 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 ignore + +// This program generates internet protocol constants by reading IANA +// protocol registries. +// +// Usage: +// go run gentest.go > iana_test.go +package main + +import ( + "bytes" + "encoding/xml" + "fmt" + "go/format" + "io" + "net/http" + "os" + "strconv" + "strings" +) + +var registries = []struct { + url string + parse func(io.Writer, io.Reader) error +}{ + { + "http://www.iana.org/assignments/dscp-registry/dscp-registry.xml", + parseDSCPRegistry, + }, + { + "http://www.iana.org/assignments/ipv4-tos-byte/ipv4-tos-byte.xml", + parseTOSTCByte, + }, +} + +func main() { + var bb bytes.Buffer + fmt.Fprintf(&bb, "// go run gentv.go\n") + fmt.Fprintf(&bb, "// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n") + fmt.Fprintf(&bb, "package ipv6_test\n\n") + for _, r := range registries { + resp, err := http.Get(r.url) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + fmt.Fprintf(os.Stderr, "got HTTP status code %v for %v\n", resp.StatusCode, r.url) + os.Exit(1) + } + if err := r.parse(&bb, resp.Body); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Fprintf(&bb, "\n") + } + b, err := format.Source(bb.Bytes()) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Stdout.Write(b) +} + +func parseDSCPRegistry(w io.Writer, r io.Reader) error { + dec := xml.NewDecoder(r) + var dr dscpRegistry + if err := dec.Decode(&dr); err != nil { + return err + } + drs := dr.escape() + fmt.Fprintf(w, "// %s, Updated: %s\n", dr.Title, dr.Updated) + fmt.Fprintf(w, "const (\n") + for _, dr := range drs { + fmt.Fprintf(w, "DiffServ%s = %#x", dr.Name, dr.Value) + fmt.Fprintf(w, "// %s\n", dr.OrigName) + } + fmt.Fprintf(w, ")\n") + return nil +} + +type dscpRegistry struct { + XMLName xml.Name `xml:"registry"` + Title string `xml:"title"` + Updated string `xml:"updated"` + Note string `xml:"note"` + RegTitle string `xml:"registry>title"` + PoolRecords []dscpRecord `xml:"registry>record"` + Records []dscpRecord `xml:"registry>registry>record"` +} + +type dscpRecord struct { + Name string `xml:"name"` + Space string `xml:"space"` +} + +type canonDSCPRecord struct { + OrigName string + Name string + Value int +} + +func (drr *dscpRegistry) escape() []canonDSCPRecord { + drs := make([]canonDSCPRecord, len(drr.Records)) + sr := strings.NewReplacer( + "+", "", + "-", "", + "/", "", + ".", "", + " ", "", + ) + for i, dr := range drr.Records { + s := strings.TrimSpace(dr.Name) + drs[i].OrigName = s + drs[i].Name = sr.Replace(s) + n, err := strconv.ParseUint(dr.Space, 2, 8) + if err != nil { + continue + } + drs[i].Value = int(n) << 2 + } + return drs +} + +func parseTOSTCByte(w io.Writer, r io.Reader) error { + dec := xml.NewDecoder(r) + var ttb tosTCByte + if err := dec.Decode(&ttb); err != nil { + return err + } + trs := ttb.escape() + fmt.Fprintf(w, "// %s, Updated: %s\n", ttb.Title, ttb.Updated) + fmt.Fprintf(w, "const (\n") + for _, tr := range trs { + fmt.Fprintf(w, "%s = %#x", tr.Keyword, tr.Value) + fmt.Fprintf(w, "// %s\n", tr.OrigKeyword) + } + fmt.Fprintf(w, ")\n") + return nil +} + +type tosTCByte struct { + XMLName xml.Name `xml:"registry"` + Title string `xml:"title"` + Updated string `xml:"updated"` + Note string `xml:"note"` + RegTitle string `xml:"registry>title"` + Records []tosTCByteRecord `xml:"registry>record"` +} + +type tosTCByteRecord struct { + Binary string `xml:"binary"` + Keyword string `xml:"keyword"` +} + +type canonTOSTCByteRecord struct { + OrigKeyword string + Keyword string + Value int +} + +func (ttb *tosTCByte) escape() []canonTOSTCByteRecord { + trs := make([]canonTOSTCByteRecord, len(ttb.Records)) + sr := strings.NewReplacer( + "Capable", "", + "(", "", + ")", "", + "+", "", + "-", "", + "/", "", + ".", "", + " ", "", + ) + for i, tr := range ttb.Records { + s := strings.TrimSpace(tr.Keyword) + trs[i].OrigKeyword = s + ss := strings.Split(s, " ") + if len(ss) > 1 { + trs[i].Keyword = strings.Join(ss[1:], " ") + } else { + trs[i].Keyword = ss[0] + } + trs[i].Keyword = sr.Replace(trs[i].Keyword) + n, err := strconv.ParseUint(tr.Binary, 2, 8) + if err != nil { + continue + } + trs[i].Value = int(n) + } + return trs +} diff --git a/ipv6/helper.go b/ipv6/helper.go new file mode 100644 index 00000000..ec87a0f7 --- /dev/null +++ b/ipv6/helper.go @@ -0,0 +1,28 @@ +// Copyright 2013 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 ipv6 + +import "net" + +func boolint(b bool) int { + if b { + return 1 + } + return 0 +} + +func netAddrToIP16(a net.Addr) net.IP { + switch v := a.(type) { + case *net.UDPAddr: + if ip := v.IP.To16(); ip != nil && ip.To4() == nil { + return ip + } + case *net.IPAddr: + if ip := v.IP.To16(); ip != nil && ip.To4() == nil { + return ip + } + } + return nil +} diff --git a/ipv6/helper_plan9.go b/ipv6/helper_plan9.go new file mode 100644 index 00000000..9f12a3a1 --- /dev/null +++ b/ipv6/helper_plan9.go @@ -0,0 +1,22 @@ +// Copyright 2013 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 ipv6 + +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 +} diff --git a/ipv6/helper_unix.go b/ipv6/helper_unix.go new file mode 100644 index 00000000..6ad8db45 --- /dev/null +++ b/ipv6/helper_unix.go @@ -0,0 +1,46 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "reflect" +) + +func (c *genericOpt) sysfd() (int, error) { + switch p := c.Conn.(type) { + case *net.TCPConn, *net.UDPConn, *net.IPConn: + return sysfd(p) + } + return 0, errInvalidConnType +} + +func (c *dgramOpt) sysfd() (int, error) { + switch p := c.PacketConn.(type) { + case *net.UDPConn, *net.IPConn: + return sysfd(p.(net.Conn)) + } + return 0, errInvalidConnType +} + +func (c *payloadHandler) sysfd() (int, error) { + return sysfd(c.PacketConn.(net.Conn)) +} + +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/ipv6/helper_windows.go b/ipv6/helper_windows.go new file mode 100644 index 00000000..28c401b5 --- /dev/null +++ b/ipv6/helper_windows.go @@ -0,0 +1,45 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "reflect" + "syscall" +) + +func (c *genericOpt) sysfd() (syscall.Handle, error) { + switch p := c.Conn.(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.PacketConn.(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.PacketConn.(net.Conn)) +} + +func sysfd(c net.Conn) (syscall.Handle, error) { + cv := reflect.ValueOf(c) + switch ce := cv.Elem(); ce.Kind() { + case reflect.Struct: + netfd := ce.FieldByName("conn").FieldByName("fd") + switch fe := netfd.Elem(); fe.Kind() { + case reflect.Struct: + fd := fe.FieldByName("sysfd") + return syscall.Handle(fd.Uint()), nil + } + } + return syscall.InvalidHandle, errInvalidConnType +} diff --git a/ipv6/iana.go b/ipv6/iana.go new file mode 100644 index 00000000..c888cf25 --- /dev/null +++ b/ipv6/iana.go @@ -0,0 +1,224 @@ +// go run gen.go +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT + +package ipv6 + +// Internet Control Message Protocol version 6 (ICMPv6) Parameters, Updated: 2012-11-12 +const ( + ICMPTypeDestinationUnreachable ICMPType = 1 // Destination Unreachable + ICMPTypePacketTooBig ICMPType = 2 // Packet Too Big + ICMPTypeTimeExceeded ICMPType = 3 // Time Exceeded + ICMPTypeParameterProblem ICMPType = 4 // Parameter Problem + ICMPTypeEchoRequest ICMPType = 128 // Echo Request + ICMPTypeEchoReply ICMPType = 129 // Echo Reply + ICMPTypeMulticastListenerQuery ICMPType = 130 // Multicast Listener Query + ICMPTypeMulticastListenerReport ICMPType = 131 // Multicast Listener Report + ICMPTypeMulticastListenerDone ICMPType = 132 // Multicast Listener Done + ICMPTypeRouterSolicitation ICMPType = 133 // Router Solicitation + ICMPTypeRouterAdvertisement ICMPType = 134 // Router Advertisement + ICMPTypeNeighborSolicitation ICMPType = 135 // Neighbor Solicitation + ICMPTypeNeighborAdvertisement ICMPType = 136 // Neighbor Advertisement + ICMPTypeRedirect ICMPType = 137 // Redirect Message + ICMPTypeRouterRenumbering ICMPType = 138 // Router Renumbering + ICMPTypeNodeInformationQuery ICMPType = 139 // ICMP Node Information Query + ICMPTypeNodeInformationResponse ICMPType = 140 // ICMP Node Information Response + ICMPTypeInverseNeighborDiscoverySolicitation ICMPType = 141 // Inverse Neighbor Discovery Solicitation Message + ICMPTypeInverseNeighborDiscoveryAdvertisement ICMPType = 142 // Inverse Neighbor Discovery Advertisement Message + ICMPTypeVersion2MulticastListenerReport ICMPType = 143 // Version 2 Multicast Listener Report + ICMPTypeHomeAgentAddressDiscoveryRequest ICMPType = 144 // Home Agent Address Discovery Request Message + ICMPTypeHomeAgentAddressDiscoveryReply ICMPType = 145 // Home Agent Address Discovery Reply Message + ICMPTypeMobilePrefixSolicitation ICMPType = 146 // Mobile Prefix Solicitation + ICMPTypeMobilePrefixAdvertisement ICMPType = 147 // Mobile Prefix Advertisement + ICMPTypeCertificationPathSolicitation ICMPType = 148 // Certification Path Solicitation Message + ICMPTypeCertificationPathAdvertisement ICMPType = 149 // Certification Path Advertisement Message + ICMPTypeMulticastRouterAdvertisement ICMPType = 151 // Multicast Router Advertisement + ICMPTypeMulticastRouterSolicitation ICMPType = 152 // Multicast Router Solicitation + ICMPTypeMulticastRouterTermination ICMPType = 153 // Multicast Router Termination + ICMPTypeFMIPv6 ICMPType = 154 // FMIPv6 Messages + ICMPTypeRPLControl ICMPType = 155 // RPL Control Message + ICMPTypeILNPv6LocatorUpdate ICMPType = 156 // ILNPv6 Locator Update Message + ICMPTypeDuplicateAddressRequest ICMPType = 157 // Duplicate Address Request + ICMPTypeDuplicateAddressConfirmation ICMPType = 158 // Duplicate Address Confirmation +) + +// Internet Control Message Protocol version 6 (ICMPv6) Parameters, Updated: 2012-11-12 +var icmpTypes = map[ICMPType]string{ + 1: "destination unreachable", + 2: "packet too big", + 3: "time exceeded", + 4: "parameter problem", + 128: "echo request", + 129: "echo reply", + 130: "multicast listener query", + 131: "multicast listener report", + 132: "multicast listener done", + 133: "router solicitation", + 134: "router advertisement", + 135: "neighbor solicitation", + 136: "neighbor advertisement", + 137: "redirect message", + 138: "router renumbering", + 139: "icmp node information query", + 140: "icmp node information response", + 141: "inverse neighbor discovery solicitation message", + 142: "inverse neighbor discovery advertisement message", + 143: "version 2 multicast listener report", + 144: "home agent address discovery request message", + 145: "home agent address discovery reply message", + 146: "mobile prefix solicitation", + 147: "mobile prefix advertisement", + 148: "certification path solicitation message", + 149: "certification path advertisement message", + 151: "multicast router advertisement", + 152: "multicast router solicitation", + 153: "multicast router termination", + 154: "fmipv6 messages", + 155: "rpl control message", + 156: "ilnpv6 locator update message", + 157: "duplicate address request", + 158: "duplicate address confirmation", +} + +// Protocol Numbers, Updated: 2013-02-17 +const ( + ianaProtocolHOPOPT = 0 // IPv6 Hop-by-Hop Option + ianaProtocolICMP = 1 // Internet Control Message + ianaProtocolIGMP = 2 // Internet Group Management + ianaProtocolGGP = 3 // Gateway-to-Gateway + ianaProtocolIPv4 = 4 // IPv4 encapsulation + ianaProtocolST = 5 // Stream + ianaProtocolTCP = 6 // Transmission Control + ianaProtocolCBT = 7 // CBT + ianaProtocolEGP = 8 // Exterior Gateway Protocol + ianaProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP) + ianaProtocolBBNRCCMON = 10 // BBN RCC Monitoring + ianaProtocolNVPII = 11 // Network Voice Protocol + ianaProtocolPUP = 12 // PUP + ianaProtocolARGUS = 13 // ARGUS + ianaProtocolEMCON = 14 // EMCON + ianaProtocolXNET = 15 // Cross Net Debugger + ianaProtocolCHAOS = 16 // Chaos + ianaProtocolUDP = 17 // User Datagram + ianaProtocolMUX = 18 // Multiplexing + ianaProtocolDCNMEAS = 19 // DCN Measurement Subsystems + ianaProtocolHMP = 20 // Host Monitoring + ianaProtocolPRM = 21 // Packet Radio Measurement + ianaProtocolXNSIDP = 22 // XEROX NS IDP + ianaProtocolTRUNK1 = 23 // Trunk-1 + ianaProtocolTRUNK2 = 24 // Trunk-2 + ianaProtocolLEAF1 = 25 // Leaf-1 + ianaProtocolLEAF2 = 26 // Leaf-2 + ianaProtocolRDP = 27 // Reliable Data Protocol + ianaProtocolIRTP = 28 // Internet Reliable Transaction + ianaProtocolISOTP4 = 29 // ISO Transport Protocol Class 4 + ianaProtocolNETBLT = 30 // Bulk Data Transfer Protocol + ianaProtocolMFENSP = 31 // MFE Network Services Protocol + ianaProtocolMERITINP = 32 // MERIT Internodal Protocol + ianaProtocolDCCP = 33 // Datagram Congestion Control Protocol + ianaProtocol3PC = 34 // Third Party Connect Protocol + ianaProtocolIDPR = 35 // Inter-Domain Policy Routing Protocol + ianaProtocolXTP = 36 // XTP + ianaProtocolDDP = 37 // Datagram Delivery Protocol + ianaProtocolIDPRCMTP = 38 // IDPR Control Message Transport Proto + ianaProtocolTPPP = 39 // TP++ Transport Protocol + ianaProtocolIL = 40 // IL Transport Protocol + ianaProtocolIPv6 = 41 // IPv6 encapsulation + ianaProtocolSDRP = 42 // Source Demand Routing Protocol + ianaProtocolIPv6Route = 43 // Routing Header for IPv6 + ianaProtocolIPv6Frag = 44 // Fragment Header for IPv6 + ianaProtocolIDRP = 45 // Inter-Domain Routing Protocol + ianaProtocolRSVP = 46 // Reservation Protocol + ianaProtocolGRE = 47 // Generic Routing Encapsulation + ianaProtocolDSR = 48 // Dynamic Source Routing Protocol + ianaProtocolBNA = 49 // BNA + ianaProtocolESP = 50 // Encap Security Payload + ianaProtocolAH = 51 // Authentication Header + ianaProtocolINLSP = 52 // Integrated Net Layer Security TUBA + ianaProtocolSWIPE = 53 // IP with Encryption + ianaProtocolNARP = 54 // NBMA Address Resolution Protocol + ianaProtocolMOBILE = 55 // IP Mobility + ianaProtocolTLSP = 56 // Transport Layer Security Protocol using Kryptonet key management + ianaProtocolSKIP = 57 // SKIP + ianaProtocolIPv6ICMP = 58 // ICMP for IPv6 + ianaProtocolIPv6NoNxt = 59 // No Next Header for IPv6 + ianaProtocolIPv6Opts = 60 // Destination Options for IPv6 + ianaProtocolCFTP = 62 // CFTP + ianaProtocolSATEXPAK = 64 // SATNET and Backroom EXPAK + ianaProtocolKRYPTOLAN = 65 // Kryptolan + ianaProtocolRVD = 66 // MIT Remote Virtual Disk Protocol + ianaProtocolIPPC = 67 // Internet Pluribus Packet Core + ianaProtocolSATMON = 69 // SATNET Monitoring + ianaProtocolVISA = 70 // VISA Protocol + ianaProtocolIPCV = 71 // Internet Packet Core Utility + ianaProtocolCPNX = 72 // Computer Protocol Network Executive + ianaProtocolCPHB = 73 // Computer Protocol Heart Beat + ianaProtocolWSN = 74 // Wang Span Network + ianaProtocolPVP = 75 // Packet Video Protocol + ianaProtocolBRSATMON = 76 // Backroom SATNET Monitoring + ianaProtocolSUNND = 77 // SUN ND PROTOCOL-Temporary + ianaProtocolWBMON = 78 // WIDEBAND Monitoring + ianaProtocolWBEXPAK = 79 // WIDEBAND EXPAK + ianaProtocolISOIP = 80 // ISO Internet Protocol + ianaProtocolVMTP = 81 // VMTP + ianaProtocolSECUREVMTP = 82 // SECURE-VMTP + ianaProtocolVINES = 83 // VINES + ianaProtocolTTP = 84 // TTP + ianaProtocolIPTM = 84 // Protocol Internet Protocol Traffic Manager + ianaProtocolNSFNETIGP = 85 // NSFNET-IGP + ianaProtocolDGP = 86 // Dissimilar Gateway Protocol + ianaProtocolTCF = 87 // TCF + ianaProtocolEIGRP = 88 // EIGRP + ianaProtocolOSPFIGP = 89 // OSPFIGP + ianaProtocolSpriteRPC = 90 // Sprite RPC Protocol + ianaProtocolLARP = 91 // Locus Address Resolution Protocol + ianaProtocolMTP = 92 // Multicast Transport Protocol + ianaProtocolAX25 = 93 // AX.25 Frames + ianaProtocolIPIP = 94 // IP-within-IP Encapsulation Protocol + ianaProtocolMICP = 95 // Mobile Internetworking Control Pro. + ianaProtocolSCCSP = 96 // Semaphore Communications Sec. Pro. + ianaProtocolETHERIP = 97 // Ethernet-within-IP Encapsulation + ianaProtocolENCAP = 98 // Encapsulation Header + ianaProtocolGMTP = 100 // GMTP + ianaProtocolIFMP = 101 // Ipsilon Flow Management Protocol + ianaProtocolPNNI = 102 // PNNI over IP + ianaProtocolPIM = 103 // Protocol Independent Multicast + ianaProtocolARIS = 104 // ARIS + ianaProtocolSCPS = 105 // SCPS + ianaProtocolQNX = 106 // QNX + ianaProtocolAN = 107 // Active Networks + ianaProtocolIPComp = 108 // IP Payload Compression Protocol + ianaProtocolSNP = 109 // Sitara Networks Protocol + ianaProtocolCompaqPeer = 110 // Compaq Peer Protocol + ianaProtocolIPXinIP = 111 // IPX in IP + ianaProtocolVRRP = 112 // Virtual Router Redundancy Protocol + ianaProtocolPGM = 113 // PGM Reliable Transport Protocol + ianaProtocolL2TP = 115 // Layer Two Tunneling Protocol + ianaProtocolDDX = 116 // D-II Data Exchange (DDX) + ianaProtocolIATP = 117 // Interactive Agent Transfer Protocol + ianaProtocolSTP = 118 // Schedule Transfer Protocol + ianaProtocolSRP = 119 // SpectraLink Radio Protocol + ianaProtocolUTI = 120 // UTI + ianaProtocolSMP = 121 // Simple Message Protocol + ianaProtocolSM = 122 // SM + ianaProtocolPTP = 123 // Performance Transparency Protocol + ianaProtocolISIS = 124 // ISIS over IPv4 + ianaProtocolFIRE = 125 // FIRE + ianaProtocolCRTP = 126 // Combat Radio Transport Protocol + ianaProtocolCRUDP = 127 // Combat Radio User Datagram + ianaProtocolSSCOPMCE = 128 // SSCOPMCE + ianaProtocolIPLT = 129 // IPLT + ianaProtocolSPS = 130 // Secure Packet Shield + ianaProtocolPIPE = 131 // Private IP Encapsulation within IP + ianaProtocolSCTP = 132 // Stream Control Transmission Protocol + ianaProtocolFC = 133 // Fibre Channel + ianaProtocolRSVPE2EIGNORE = 134 // RSVP-E2E-IGNORE + ianaProtocolMobilityHeader = 135 // Mobility Header + ianaProtocolUDPLite = 136 // UDPLite + ianaProtocolMPLSinIP = 137 // MPLS-in-IP + ianaProtocolMANET = 138 // MANET Protocols + ianaProtocolHIP = 139 // Host Identity Protocol + ianaProtocolShim6 = 140 // Shim6 Protocol + ianaProtocolWESP = 141 // Wrapped Encapsulating Security Payload + ianaProtocolROHC = 142 // Robust Header Compression + ianaProtocolReserved = 255 // Reserved +) diff --git a/ipv6/iana_test.go b/ipv6/iana_test.go new file mode 100644 index 00000000..7b6bb85e --- /dev/null +++ b/ipv6/iana_test.go @@ -0,0 +1,38 @@ +// go run gentv.go +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT + +package ipv6_test + +// Differentiated Services Field Codepoints, Updated: 2010-05-11 +const ( + DiffServCS0 = 0x0 // CS0 + DiffServCS1 = 0x20 // CS1 + DiffServCS2 = 0x40 // CS2 + DiffServCS3 = 0x60 // CS3 + DiffServCS4 = 0x80 // CS4 + DiffServCS5 = 0xa0 // CS5 + DiffServCS6 = 0xc0 // CS6 + DiffServCS7 = 0xe0 // CS7 + DiffServAF11 = 0x28 // AF11 + DiffServAF12 = 0x30 // AF12 + DiffServAF13 = 0x38 // AF13 + DiffServAF21 = 0x48 // AF21 + DiffServAF22 = 0x50 // AF22 + DiffServAF23 = 0x58 // AF23 + DiffServAF31 = 0x68 // AF31 + DiffServAF32 = 0x70 // AF32 + DiffServAF33 = 0x78 // AF33 + DiffServAF41 = 0x88 // AF41 + DiffServAF42 = 0x90 // AF42 + DiffServAF43 = 0x98 // AF43 + DiffServEFPHB = 0xb8 // EF PHB + DiffServVOICEADMIT = 0xb0 // VOICE-ADMIT +) + +// IPv4 TOS Byte and IPv6 Traffic Class Octet, Updated: 2001-09-06 +const ( + NotECNTransport = 0x0 // Not-ECT (Not ECN-Capable Transport) + ECNTransport1 = 0x1 // ECT(1) (ECN-Capable Transport(1)) + ECNTransport0 = 0x2 // ECT(0) (ECN-Capable Transport(0)) + CongestionExperienced = 0x3 // CE (Congestion Experienced) +) diff --git a/ipv6/icmp.go b/ipv6/icmp.go new file mode 100644 index 00000000..9fb6a483 --- /dev/null +++ b/ipv6/icmp.go @@ -0,0 +1,46 @@ +// Copyright 2013 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 ipv6 + +import "sync" + +// An ICMPType represents a type of ICMP message. +type ICMPType int + +func (typ ICMPType) String() string { + s, ok := icmpTypes[typ] + if !ok { + return "" + } + return s +} + +// An ICMPFilter represents an ICMP message filter for incoming +// packets. +type ICMPFilter struct { + mu sync.RWMutex + rawICMPFilter +} + +// Set sets the ICMP type and filter action to the filter. +func (f *ICMPFilter) Set(typ ICMPType, block bool) { + f.mu.Lock() + defer f.mu.Unlock() + f.set(typ, block) +} + +// SetAll sets the filter action to the filter. +func (f *ICMPFilter) SetAll(block bool) { + f.mu.Lock() + defer f.mu.Unlock() + f.setAll(block) +} + +// WillBlock reports whether the ICMP type will be blocked. +func (f *ICMPFilter) WillBlock(typ ICMPType) bool { + f.mu.RLock() + defer f.mu.RUnlock() + return f.willBlock(typ) +} diff --git a/ipv6/icmp_bsd.go b/ipv6/icmp_bsd.go new file mode 100644 index 00000000..20960799 --- /dev/null +++ b/ipv6/icmp_bsd.go @@ -0,0 +1,35 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +type rawICMPFilter struct { + syscall.ICMPv6Filter +} + +func (f *rawICMPFilter) set(typ ICMPType, block bool) { + if block { + f.Filt[typ>>5] &^= 1 << (uint32(typ) & 31) + } else { + f.Filt[typ>>5] |= 1 << (uint32(typ) & 31) + } +} + +func (f *rawICMPFilter) setAll(block bool) { + for i := 0; i < len(f.Filt); i++ { + if block { + f.Filt[i] = 0 + } else { + f.Filt[i] = 1<<32 - 1 + } + } +} + +func (f *rawICMPFilter) willBlock(typ ICMPType) bool { + return f.Filt[typ>>5]&(1<<(uint32(typ)&31)) == 0 +} diff --git a/ipv6/icmp_linux.go b/ipv6/icmp_linux.go new file mode 100644 index 00000000..f60372b8 --- /dev/null +++ b/ipv6/icmp_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +type rawICMPFilter struct { + syscall.ICMPv6Filter +} + +func (f *rawICMPFilter) set(typ ICMPType, block bool) { + if block { + f.Data[typ>>5] |= 1 << (uint32(typ) & 31) + } else { + f.Data[typ>>5] &^= 1 << (uint32(typ) & 31) + } +} + +func (f *rawICMPFilter) setAll(block bool) { + for i := 0; i < len(f.Data); i++ { + if block { + f.Data[i] = 1<<32 - 1 + } else { + f.Data[i] = 0 + } + } +} + +func (f *rawICMPFilter) willBlock(typ ICMPType) bool { + return f.Data[typ>>5]&(1<<(uint32(typ)&31)) != 0 +} diff --git a/ipv6/icmp_plan9.go b/ipv6/icmp_plan9.go new file mode 100644 index 00000000..b97c8280 --- /dev/null +++ b/ipv6/icmp_plan9.go @@ -0,0 +1,22 @@ +// Copyright 2013 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 ipv6 + +type rawICMPFilter struct { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) set(typ ICMPType, block bool) { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) setAll(block bool) { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) willBlock(typ ICMPType) bool { + // TODO(mikio): Implement this + return false +} diff --git a/ipv6/icmp_test.go b/ipv6/icmp_test.go new file mode 100644 index 00000000..97278294 --- /dev/null +++ b/ipv6/icmp_test.go @@ -0,0 +1,81 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "reflect" + "runtime" + "sync" + "testing" +) + +func TestICMPFilter(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + var f ipv6.ICMPFilter + for _, toggle := range []bool{false, true} { + f.SetAll(toggle) + var wg sync.WaitGroup + for _, typ := range []ipv6.ICMPType{ + ipv6.ICMPTypeDestinationUnreachable, + ipv6.ICMPTypeEchoReply, + ipv6.ICMPTypeNeighborSolicitation, + ipv6.ICMPTypeDuplicateAddressConfirmation, + } { + wg.Add(1) + go func(typ ipv6.ICMPType) { + defer wg.Done() + f.Set(typ, false) + if f.WillBlock(typ) { + t.Errorf("ipv6.ICMPFilter.Set(%v, false) failed", typ) + } + f.Set(typ, true) + if !f.WillBlock(typ) { + t.Errorf("ipv6.ICMPFilter.Set(%v, true) failed", typ) + } + }(typ) + } + wg.Wait() + } +} + +func TestSetICMPFilter(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + + c, err := net.ListenPacket("ip6:ipv6-icmp", "::1") + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + p := ipv6.NewPacketConn(c) + + var f ipv6.ICMPFilter + f.SetAll(true) + f.Set(ipv6.ICMPTypeEchoRequest, false) + f.Set(ipv6.ICMPTypeEchoReply, false) + if err := p.SetICMPFilter(&f); err != nil { + t.Fatalf("ipv6.PacketConn.SetICMPFilter failed: %v", err) + } + kf, err := p.ICMPFilter() + if err != nil { + t.Fatalf("ipv6.PacketConn.ICMPFilter failed: %v", err) + } + if !reflect.DeepEqual(kf, &f) { + t.Fatalf("got unexpected filter %#v; expected %#v", kf, f) + } +} diff --git a/ipv6/icmp_windows.go b/ipv6/icmp_windows.go new file mode 100644 index 00000000..b97c8280 --- /dev/null +++ b/ipv6/icmp_windows.go @@ -0,0 +1,22 @@ +// Copyright 2013 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 ipv6 + +type rawICMPFilter struct { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) set(typ ICMPType, block bool) { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) setAll(block bool) { + // TODO(mikio): Implement this +} + +func (f *rawICMPFilter) willBlock(typ ICMPType) bool { + // TODO(mikio): Implement this + return false +} diff --git a/ipv6/mockicmp_test.go b/ipv6/mockicmp_test.go new file mode 100644 index 00000000..13ee03db --- /dev/null +++ b/ipv6/mockicmp_test.go @@ -0,0 +1,112 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "errors" +) + +// icmpMessage represents an ICMP message. +type icmpMessage struct { + Type ipv6.ICMPType // type + Code int // code + Checksum int // checksum + Body icmpMessageBody // body +} + +// icmpMessageBody represents an ICMP message body. +type icmpMessageBody interface { + Len() int + Marshal() ([]byte, error) +} + +// Marshal returns the binary enconding of the ICMP echo request or +// reply message m. +func (m *icmpMessage) Marshal() ([]byte, error) { + b := []byte{byte(m.Type), byte(m.Code), 0, 0} + if m.Body != nil && m.Body.Len() != 0 { + mb, err := m.Body.Marshal() + if err != nil { + return nil, err + } + b = append(b, mb...) + } + switch m.Type { + case ipv6.ICMPTypeEchoRequest, ipv6.ICMPTypeEchoReply: + return b, nil + } + csumcv := len(b) - 1 // checksum coverage + s := uint32(0) + for i := 0; i < csumcv; i += 2 { + s += uint32(b[i+1])<<8 | uint32(b[i]) + } + if csumcv&1 == 0 { + s += uint32(b[csumcv]) + } + s = s>>16 + s&0xffff + s = s + s>>16 + // Place checksum back in header; using ^= avoids the + // assumption the checksum bytes are zero. + b[2] ^= byte(^s & 0xff) + b[3] ^= byte(^s >> 8) + return b, nil +} + +// parseICMPMessage parses b as an ICMP message. +func parseICMPMessage(b []byte) (*icmpMessage, error) { + msglen := len(b) + if msglen < 4 { + return nil, errors.New("message too short") + } + m := &icmpMessage{Type: ipv6.ICMPType(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])} + if msglen > 4 { + var err error + switch m.Type { + case ipv6.ICMPTypeEchoRequest, ipv6.ICMPTypeEchoReply: + m.Body, err = parseICMPEcho(b[4:]) + if err != nil { + return nil, err + } + } + } + return m, nil +} + +// imcpEcho represenets an ICMP echo request or reply message body. +type icmpEcho struct { + ID int // identifier + Seq int // sequence number + Data []byte // data +} + +func (p *icmpEcho) Len() int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal returns the binary enconding of the ICMP echo request or +// reply message body p. +func (p *icmpEcho) Marshal() ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + b[0], b[1] = byte(p.ID>>8), byte(p.ID&0xff) + b[2], b[3] = byte(p.Seq>>8), byte(p.Seq&0xff) + copy(b[4:], p.Data) + return b, nil +} + +// parseICMPEcho parses b as an ICMP echo request or reply message +// body. +func parseICMPEcho(b []byte) (*icmpEcho, error) { + bodylen := len(b) + p := &icmpEcho{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])} + if bodylen > 4 { + p.Data = make([]byte, bodylen-4) + copy(p.Data, b[4:]) + } + return p, nil +} diff --git a/ipv6/mocktransponder_test.go b/ipv6/mocktransponder_test.go new file mode 100644 index 00000000..2da54641 --- /dev/null +++ b/ipv6/mocktransponder_test.go @@ -0,0 +1,110 @@ +// Copyright 2013 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 ipv6_test + +import ( + "net" + "testing" +) + +func isLinkLocalUnicast(ip net.IP) bool { + return ip.To4() == nil && ip.To16() != nil && ip.IsLinkLocalUnicast() +} + +func loopbackInterface() *net.Interface { + ift, err := net.Interfaces() + if err != nil { + return nil + } + for _, ifi := range ift { + if ifi.Flags&net.FlagLoopback == 0 || ifi.Flags&net.FlagUp == 0 { + continue + } + ifat, err := ifi.Addrs() + if err != nil { + continue + } + for _, ifa := range ifat { + switch ifa := ifa.(type) { + case *net.IPAddr: + if isLinkLocalUnicast(ifa.IP) { + return &ifi + } + case *net.IPNet: + if isLinkLocalUnicast(ifa.IP) { + return &ifi + } + } + } + } + return nil +} + +func isMulticastAvailable(ifi *net.Interface) (net.IP, bool) { + if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 { + return nil, false + } + ifat, err := ifi.Addrs() + if err != nil { + return nil, false + } + for _, ifa := range ifat { + switch ifa := ifa.(type) { + case *net.IPAddr: + if isLinkLocalUnicast(ifa.IP) { + return ifa.IP, true + } + case *net.IPNet: + if isLinkLocalUnicast(ifa.IP) { + return ifa.IP, true + } + } + } + return nil, false +} + +func connector(t *testing.T, network, addr string, done chan<- bool) { + defer func() { done <- true }() + + c, err := net.Dial(network, addr) + if err != nil { + t.Errorf("net.Dial failed: %v", err) + return + } + c.Close() +} + +func acceptor(t *testing.T, ln net.Listener, done chan<- bool) { + defer func() { done <- true }() + + c, err := ln.Accept() + if err != nil { + t.Errorf("net.Listener.Accept failed: %v", err) + return + } + c.Close() +} + +func transponder(t *testing.T, ln net.Listener, done chan<- bool) { + defer func() { done <- true }() + + c, err := ln.Accept() + if err != nil { + t.Errorf("net.Listener.Accept failed: %v", err) + return + } + defer c.Close() + + b := make([]byte, 128) + n, err := c.Read(b) + if err != nil { + t.Errorf("net.Conn.Read failed: %v", err) + return + } + if _, err := c.Write(b[:n]); err != nil { + t.Errorf("net.Conn.Write failed: %v", err) + return + } +} diff --git a/ipv6/multicast_test.go b/ipv6/multicast_test.go new file mode 100644 index 00000000..395f8f58 --- /dev/null +++ b/ipv6/multicast_test.go @@ -0,0 +1,154 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "runtime" + "testing" +) + +func TestPacketConnReadWriteMulticastUDP(t *testing.T) { + switch runtime.GOOS { + case "freebsd": // due to a bug on loopback marking + t.Skipf("not supported on %q", runtime.GOOS) + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + ifi := loopbackInterface() + if ifi == nil { + t.Skipf("not available on %q", runtime.GOOS) + } + + c, err := net.ListenPacket("udp6", "[ff02::114]:0") // see RFC 4727 + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + _, port, err := net.SplitHostPort(c.LocalAddr().String()) + if err != nil { + t.Fatalf("net.SplitHostPort failed: %v", err) + } + dst, err := net.ResolveUDPAddr("udp6", "[ff02::114]:"+port) // see RFC 4727 + if err != nil { + t.Fatalf("net.ResolveUDPAddr failed: %v", err) + } + + p := ipv6.NewPacketConn(c) + if err := p.JoinGroup(ifi, dst); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup on %v failed: %v", ifi, err) + } + if err := p.SetMulticastInterface(ifi); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastInterface failed: %v", err) + } + if err := p.SetMulticastLoopback(true); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastLoopback failed: %v", err) + } + + cm := ipv6.ControlMessage{ + TrafficClass: DiffServAF11 | CongestionExperienced, + IfIndex: ifi.Index, + } + cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagInterface | ipv6.FlagPathMTU + + for i, toggle := range []bool{true, false, true} { + if err := p.SetControlMessage(cf, toggle); err != nil { + t.Fatalf("ipv6.PacketConn.SetControlMessage failed: %v", err) + } + cm.HopLimit = i + 1 + if _, err := p.WriteTo([]byte("HELLO-R-U-THERE"), &cm, dst); err != nil { + t.Fatalf("ipv6.PacketConn.WriteTo failed: %v", err) + } + b := make([]byte, 128) + if _, cm, _, err := p.ReadFrom(b); err != nil { + t.Fatalf("ipv6.PacketConn.ReadFrom failed: %v", err) + } else { + t.Logf("rcvd cmsg: %v", cm) + } + } +} + +func TestPacketConnReadWriteMulticastICMP(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + ifi := loopbackInterface() + if ifi == nil { + t.Skipf("not available on %q", runtime.GOOS) + } + + c, err := net.ListenPacket("ip6:ipv6-icmp", "::") + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + dst, err := net.ResolveIPAddr("ip6", "ff02::114") // see RFC 4727 + if err != nil { + t.Fatalf("net.ResolveIPAddr failed: %v", err) + } + + p := ipv6.NewPacketConn(c) + if err := p.JoinGroup(ifi, dst); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup on %v failed: %v", ifi, err) + } + if err := p.SetMulticastInterface(ifi); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastInterface failed: %v", err) + } + if err := p.SetMulticastLoopback(true); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastLoopback failed: %v", err) + } + + cm := ipv6.ControlMessage{ + TrafficClass: DiffServAF11 | CongestionExperienced, + IfIndex: ifi.Index, + } + cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagInterface | ipv6.FlagPathMTU + + var f ipv6.ICMPFilter + f.SetAll(true) + f.Set(ipv6.ICMPTypeEchoReply, false) + if err := p.SetICMPFilter(&f); err != nil { + t.Fatalf("ipv6.PacketConn.SetICMPFilter failed: %v", err) + } + + for i, toggle := range []bool{true, false, true} { + wb, err := (&icmpMessage{ + Type: ipv6.ICMPTypeEchoRequest, Code: 0, + Body: &icmpEcho{ + ID: os.Getpid() & 0xffff, Seq: i + 1, + Data: []byte("HELLO-R-U-THERE"), + }, + }).Marshal() + if err != nil { + t.Fatalf("icmpMessage.Marshal failed: %v", err) + } + if err := p.SetControlMessage(cf, toggle); err != nil { + t.Fatalf("ipv6.PacketConn.SetControlMessage failed: %v", err) + } + cm.HopLimit = i + 1 + if _, err := p.WriteTo(wb, &cm, dst); err != nil { + t.Fatalf("ipv6.PacketConn.WriteTo failed: %v", err) + } + b := make([]byte, 128) + if n, cm, _, err := p.ReadFrom(b); err != nil { + t.Fatalf("ipv6.PacketConn.ReadFrom failed: %v", err) + } else { + t.Logf("rcvd cmsg: %v", cm) + if m, err := parseICMPMessage(b[:n]); err != nil { + t.Fatalf("parseICMPMessage failed: %v", err) + } else if m.Type != ipv6.ICMPTypeEchoReply || m.Code != 0 { + t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv6.ICMPTypeEchoReply, 0) + } + } + } +} diff --git a/ipv6/multicastlistener_test.go b/ipv6/multicastlistener_test.go new file mode 100644 index 00000000..61cf3575 --- /dev/null +++ b/ipv6/multicastlistener_test.go @@ -0,0 +1,185 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "fmt" + "net" + "os" + "runtime" + "testing" +) + +var udpMultipleGroupListenerTests = []net.Addr{ + &net.UDPAddr{IP: net.ParseIP("ff02::114")}, // see RFC 4727 + &net.UDPAddr{IP: net.ParseIP("ff02::1:114")}, + &net.UDPAddr{IP: net.ParseIP("ff02::2:114")}, +} + +func TestUDPSinglePacketConnWithMultipleGroupListeners(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, gaddr := range udpMultipleGroupListenerTests { + c, err := net.ListenPacket("udp6", "[::]:0") // wildcard address with non-reusable port + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + p := ipv6.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 := isMulticastAvailable(&ifi); !ok { + continue + } + if err := p.JoinGroup(&ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup %v on %v failed: %v", gaddr, ifi, err) + } + mift = append(mift, &ift[i]) + } + for _, ifi := range mift { + if err := p.LeaveGroup(ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.LeaveGroup %v on %v failed: %v", gaddr, ifi, err) + } + } + } +} + +func TestUDPMultipleConnWithMultipleGroupListeners(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, gaddr := range udpMultipleGroupListenerTests { + c1, err := net.ListenPacket("udp6", "[ff02::]:1024") // wildcard address with reusable port + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c1.Close() + + c2, err := net.ListenPacket("udp6", "[ff02::]:1024") // wildcard address with reusable port + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c2.Close() + + var ps [2]*ipv6.PacketConn + ps[0] = ipv6.NewPacketConn(c1) + ps[1] = ipv6.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 := isMulticastAvailable(&ifi); !ok { + continue + } + for _, p := range ps { + if err := p.JoinGroup(&ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup %v on %v failed: %v", gaddr, ifi, err) + } + } + mift = append(mift, &ift[i]) + } + for _, ifi := range mift { + for _, p := range ps { + if err := p.LeaveGroup(ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.LeaveGroup %v on %v failed: %v", gaddr, ifi, err) + } + } + } + } +} + +func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + gaddr := &net.IPAddr{IP: net.ParseIP("ff02::114")} // see RFC 4727 + type ml struct { + c *ipv6.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 := isMulticastAvailable(&ifi) + if !ok { + continue + } + c, err := net.ListenPacket("udp6", fmt.Sprintf("[%s%%%s]:1024", ip.String(), ifi.Name)) // unicast address with non-reusable port + if err != nil { + t.Fatalf("net.ListenPacket with %v failed: %v", ip, err) + } + defer c.Close() + p := ipv6.NewPacketConn(c) + if err := p.JoinGroup(&ifi, gaddr); err != nil { + t.Fatalf("ipv6.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("ipv6.PacketConn.LeaveGroup on %v failed: %v", m.ifi, err) + } + } +} + +func TestIPSinglePacketConnWithSingleGroupListener(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + + c, err := net.ListenPacket("ip6:ipv6-icmp", "::") + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + p := ipv6.NewPacketConn(c) + gaddr := &net.IPAddr{IP: net.ParseIP("ff02::114")} // 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 := isMulticastAvailable(&ifi); !ok { + continue + } + if err := p.JoinGroup(&ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup on %v failed: %v", ifi, err) + } + mift = append(mift, &ift[i]) + } + for _, ifi := range mift { + if err := p.LeaveGroup(ifi, gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.LeaveGroup on %v failed: %v", ifi, err) + } + } +} diff --git a/ipv6/multicastsockopt_test.go b/ipv6/multicastsockopt_test.go new file mode 100644 index 00000000..21fc32cc --- /dev/null +++ b/ipv6/multicastsockopt_test.go @@ -0,0 +1,73 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "runtime" + "testing" +) + +var packetConnMulticastSocketOptionTests = []struct { + net, proto, addr string + gaddr net.Addr +}{ + {"udp6", "", "[ff02::]:0", &net.UDPAddr{IP: net.ParseIP("ff02::114")}}, // see RFC 4727 + {"ip6", ":ipv6-icmp", "::", &net.IPAddr{IP: net.ParseIP("ff02::114")}}, // see RFC 4727 +} + +func TestPacketConnMulticastSocketOptions(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + ifi := loopbackInterface() + if ifi == nil { + t.Skipf("not available on %q", runtime.GOOS) + } + + for _, tt := range packetConnMulticastSocketOptionTests { + if tt.net == "ip6" && os.Getuid() != 0 { + t.Skip("must be root") + } + c, err := net.ListenPacket(tt.net+tt.proto, tt.addr) + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + p := ipv6.NewPacketConn(c) + + hoplim := 255 + if err := p.SetMulticastHopLimit(hoplim); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastHopLimit failed: %v", err) + } + if v, err := p.MulticastHopLimit(); err != nil { + t.Fatalf("ipv6.PacketConn.MulticastHopLimit failed: %v", err) + } else if v != hoplim { + t.Fatalf("got unexpected multicast hop limit %v; expected %v", v, hoplim) + } + + for _, toggle := range []bool{true, false} { + if err := p.SetMulticastLoopback(toggle); err != nil { + t.Fatalf("ipv6.PacketConn.SetMulticastLoopback failed: %v", err) + } + if v, err := p.MulticastLoopback(); err != nil { + t.Fatalf("ipv6.PacketConn.MulticastLoopback failed: %v", err) + } else if v != toggle { + t.Fatalf("got unexpected multicast loopback %v; expected %v", v, toggle) + } + } + + if err := p.JoinGroup(ifi, tt.gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.JoinGroup(%v, %v) failed: %v", ifi, tt.gaddr, err) + } + if err := p.LeaveGroup(ifi, tt.gaddr); err != nil { + t.Fatalf("ipv6.PacketConn.LeaveGroup(%v, %v) failed: %v", ifi, tt.gaddr, err) + } + } +} diff --git a/ipv6/payload.go b/ipv6/payload.go new file mode 100644 index 00000000..529b20bc --- /dev/null +++ b/ipv6/payload.go @@ -0,0 +1,15 @@ +// Copyright 2013 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 ipv6 + +import "net" + +// A payloadHandler represents the IPv6 datagram payload handler. +type payloadHandler struct { + net.PacketConn + rawOpt +} + +func (c *payloadHandler) ok() bool { return c != nil && c.PacketConn != nil } diff --git a/ipv6/payload_cmsg.go b/ipv6/payload_cmsg.go new file mode 100644 index 00000000..658467a6 --- /dev/null +++ b/ipv6/payload_cmsg.go @@ -0,0 +1,70 @@ +// Copyright 2013 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 !plan9,!windows + +package ipv6 + +import ( + "net" + "syscall" +) + +// ReadFrom reads a payload of the received IPv6 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) ReadFrom(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 c := c.PacketConn.(type) { + case *net.UDPConn: + if n, oobn, _, src, err = c.ReadMsgUDP(b, oob); err != nil { + return 0, nil, nil, err + } + case *net.IPConn: + if n, oobn, _, src, err = c.ReadMsgIP(b, oob); err != nil { + return 0, nil, nil, err + } + default: + return 0, nil, nil, errInvalidConnType + } + if cm, err = parseControlMessage(oob[:oobn]); err != nil { + return 0, nil, nil, err + } + if cm != nil { + cm.Src = netAddrToIP16(src) + } + return +} + +// WriteTo writes a payload of the IPv6 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 IPv6 header fields and the datagram path to be specified. The +// cm may be nil if control of the outgoing datagram is not required. +func (c *payloadHandler) WriteTo(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 c := c.PacketConn.(type) { + case *net.UDPConn: + n, _, err = c.WriteMsgUDP(b, oob, dst.(*net.UDPAddr)) + case *net.IPConn: + n, _, err = c.WriteMsgIP(b, oob, dst.(*net.IPAddr)) + default: + return 0, errInvalidConnType + } + if err != nil { + return 0, err + } + return +} diff --git a/ipv6/payload_noncmsg.go b/ipv6/payload_noncmsg.go new file mode 100644 index 00000000..dd0630a9 --- /dev/null +++ b/ipv6/payload_noncmsg.go @@ -0,0 +1,41 @@ +// Copyright 2013 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 plan9 windows + +package ipv6 + +import ( + "net" + "syscall" +) + +// ReadFrom reads a payload of the received IPv6 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) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.Addr, err error) { + if !c.ok() { + return 0, nil, nil, syscall.EINVAL + } + if n, src, err = c.PacketConn.ReadFrom(b); err != nil { + return 0, nil, nil, err + } + return +} + +// WriteTo writes a payload of the IPv6 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 IPv6 header fields and the datagram path to be specified. The +// cm may be nil if control of the outgoing datagram is not required. +func (c *payloadHandler) WriteTo(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error) { + if !c.ok() { + return 0, syscall.EINVAL + } + if dst == nil { + return 0, errMissingAddress + } + return c.PacketConn.WriteTo(b, dst) +} diff --git a/ipv6/sockopt_rfc2292_darwin.go b/ipv6/sockopt_rfc2292_darwin.go new file mode 100644 index 00000000..f21802c8 --- /dev/null +++ b/ipv6/sockopt_rfc2292_darwin.go @@ -0,0 +1,66 @@ +// Copyright 2013 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 ipv6 + +import ( + "os" + "syscall" +) + +func ipv6ReceiveTrafficClass(fd int) (bool, error) { + return false, errNotSupported +} + +func setIPv6ReceiveTrafficClass(fd int, v bool) error { + return errNotSupported +} + +func ipv6ReceiveHopLimit(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_2292HOPLIMIT) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceiveHopLimit(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_2292HOPLIMIT, boolint(v))) +} + +func ipv6ReceivePacketInfo(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_2292PKTINFO) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceivePacketInfo(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_2292PKTINFO, boolint(v))) +} + +func ipv6PathMTU(fd int) (int, error) { + return 0, errNotSupported +} + +func ipv6ReceivePathMTU(fd int) (bool, error) { + return false, errNotSupported +} + +func setIPv6ReceivePathMTU(fd int, v bool) error { + return errNotSupported +} + +func ipv6ICMPFilter(fd int) (*ICMPFilter, error) { + v, err := syscall.GetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMP6_FILTER) + if err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + return &ICMPFilter{rawICMPFilter: rawICMPFilter{*v}}, nil +} + +func setIPv6ICMPFilter(fd int, f *ICMPFilter) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMP6_FILTER, &f.rawICMPFilter.ICMPv6Filter)) +} diff --git a/ipv6/sockopt_rfc3493_bsd.go b/ipv6/sockopt_rfc3493_bsd.go new file mode 100644 index 00000000..aa5e6e3a --- /dev/null +++ b/ipv6/sockopt_rfc3493_bsd.go @@ -0,0 +1,19 @@ +// Copyright 2013 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 ipv6 + +import ( + "os" + "syscall" +) + +func setIPv6Checksum(fd int, on bool, offset int) error { + if !on { + offset = -1 + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_CHECKSUM, offset)) +} diff --git a/ipv6/sockopt_rfc3493_linux.go b/ipv6/sockopt_rfc3493_linux.go new file mode 100644 index 00000000..b2557e86 --- /dev/null +++ b/ipv6/sockopt_rfc3493_linux.go @@ -0,0 +1,17 @@ +// Copyright 2013 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 ipv6 + +import ( + "os" + "syscall" +) + +func setIPv6Checksum(fd int, on bool, offset int) error { + if !on { + offset = -1 + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolReserved, syscall.IPV6_CHECKSUM, offset)) +} diff --git a/ipv6/sockopt_rfc3493_unix.go b/ipv6/sockopt_rfc3493_unix.go new file mode 100644 index 00000000..b055cf04 --- /dev/null +++ b/ipv6/sockopt_rfc3493_unix.go @@ -0,0 +1,114 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "os" + "syscall" +) + +func ipv6TrafficClass(fd int) (int, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_TCLASS) + if err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return v, nil +} + +func setIPv6TrafficClass(fd, v int) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_TCLASS, v)) +} + +func ipv6HopLimit(fd int) (int, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_UNICAST_HOPS) + if err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return v, nil +} + +func setIPv6HopLimit(fd, v int) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_UNICAST_HOPS, v)) +} + +func ipv6Checksum(fd int) (bool, int, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_CHECKSUM) + if err != nil { + return false, 0, os.NewSyscallError("getsockopt", err) + } + on := true + if v == -1 { + on = false + } + return on, v, nil +} + +func ipv6MulticastHopLimit(fd int) (int, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_HOPS) + if err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return v, nil +} + +func setIPv6MulticastHopLimit(fd, v int) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_HOPS, v)) +} + +func ipv6MulticastInterface(fd int) (*net.Interface, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_IF) + if err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + if v == 0 { + return nil, nil + } + ifi, err := net.InterfaceByIndex(v) + if err != nil { + return nil, err + } + return ifi, nil +} + +func setIPv6MulticastInterface(fd int, ifi *net.Interface) error { + var v int + if ifi != nil { + v = ifi.Index + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_IF, v)) +} + +func ipv6MulticastLoopback(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_LOOP) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6MulticastLoopback(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_MULTICAST_LOOP, boolint(v))) +} + +func joinIPv6Group(fd int, ifi *net.Interface, grp net.IP) error { + mreq := syscall.IPv6Mreq{} + copy(mreq.Multiaddr[:], grp) + if ifi != nil { + mreq.Interface = uint32(ifi.Index) + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptIPv6Mreq(fd, ianaProtocolIPv6, syscall.IPV6_JOIN_GROUP, &mreq)) +} + +func leaveIPv6Group(fd int, ifi *net.Interface, grp net.IP) error { + mreq := syscall.IPv6Mreq{} + copy(mreq.Multiaddr[:], grp) + if ifi != nil { + mreq.Interface = uint32(ifi.Index) + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptIPv6Mreq(fd, ianaProtocolIPv6, syscall.IPV6_LEAVE_GROUP, &mreq)) +} diff --git a/ipv6/sockopt_rfc3493_windows.go b/ipv6/sockopt_rfc3493_windows.go new file mode 100644 index 00000000..3ffb86de --- /dev/null +++ b/ipv6/sockopt_rfc3493_windows.go @@ -0,0 +1,116 @@ +// Copyright 2013 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 ipv6 + +import ( + "net" + "os" + "syscall" + "unsafe" +) + +func ipv6TrafficClass(fd syscall.Handle) (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EWINDOWS +} + +func setIPv6TrafficClass(fd syscall.Handle, v int) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} + +func ipv6HopLimit(fd syscall.Handle) (int, error) { + var v int32 + l := int32(4) + if err := syscall.Getsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_UNICAST_HOPS), (*byte)(unsafe.Pointer(&v)), &l); err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return int(v), nil +} + +func setIPv6HopLimit(fd syscall.Handle, v int) error { + vv := int32(v) + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_UNICAST_HOPS), (*byte)(unsafe.Pointer(&vv)), 4)) +} + +func ipv6Checksum(fd syscall.Handle) (bool, int, error) { + // TODO(mikio): Implement this + return false, 0, syscall.EWINDOWS +} + +func ipv6MulticastHopLimit(fd syscall.Handle) (int, error) { + var v int32 + l := int32(4) + if err := syscall.Getsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_HOPS), (*byte)(unsafe.Pointer(&v)), &l); err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return int(v), nil +} + +func setIPv6MulticastHopLimit(fd syscall.Handle, v int) error { + vv := int32(v) + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_HOPS), (*byte)(unsafe.Pointer(&vv)), 4)) +} + +func ipv6MulticastInterface(fd syscall.Handle) (*net.Interface, error) { + var v int32 + l := int32(4) + if err := syscall.Getsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_IF), (*byte)(unsafe.Pointer(&v)), &l); err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + if v == 0 { + return nil, nil + } + ifi, err := net.InterfaceByIndex(int(v)) + if err != nil { + return nil, err + } + return ifi, nil +} + +func setIPv6MulticastInterface(fd syscall.Handle, ifi *net.Interface) error { + var v int32 + if ifi != nil { + v = int32(ifi.Index) + } + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_IF), (*byte)(unsafe.Pointer(&v)), 4)) +} + +func ipv6MulticastLoopback(fd syscall.Handle) (bool, error) { + var v int32 + l := int32(4) + if err := syscall.Getsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_LOOP), (*byte)(unsafe.Pointer(&v)), &l); err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6MulticastLoopback(fd syscall.Handle, v bool) error { + vv := int32(boolint(v)) + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_MULTICAST_LOOP), (*byte)(unsafe.Pointer(&vv)), 4)) +} + +func joinIPv6Group(fd syscall.Handle, ifi *net.Interface, grp net.IP) error { + mreq := syscall.IPv6Mreq{} + copy(mreq.Multiaddr[:], grp) + if ifi != nil { + mreq.Interface = uint32(ifi.Index) + } + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_JOIN_GROUP), (*byte)(unsafe.Pointer(&mreq)), int32(unsafe.Sizeof(mreq)))) +} + +func leaveIPv6Group(fd syscall.Handle, ifi *net.Interface, grp net.IP) error { + mreq := syscall.IPv6Mreq{} + copy(mreq.Multiaddr[:], grp) + if ifi != nil { + mreq.Interface = uint32(ifi.Index) + } + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd, int32(ianaProtocolIPv6), int32(syscall.IPV6_LEAVE_GROUP), (*byte)(unsafe.Pointer(&mreq)), int32(unsafe.Sizeof(mreq)))) +} + +func setIPv6Checksum(fd syscall.Handle, on bool, offset int) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} diff --git a/ipv6/sockopt_rfc3542_bsd.go b/ipv6/sockopt_rfc3542_bsd.go new file mode 100644 index 00000000..61073b12 --- /dev/null +++ b/ipv6/sockopt_rfc3542_bsd.go @@ -0,0 +1,44 @@ +// Copyright 2013 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 freebsd netbsd openbsd + +package ipv6 + +import ( + "os" + "syscall" +) + +func ipv6PathMTU(fd int) (int, error) { + v, err := syscall.GetsockoptIPv6MTUInfo(fd, ianaProtocolIPv6, syscall.IPV6_PATHMTU) + if err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return int(v.Mtu), nil +} + +func ipv6ReceivePathMTU(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVPATHMTU) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceivePathMTU(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVPATHMTU, boolint(v))) +} + +func ipv6ICMPFilter(fd int) (*ICMPFilter, error) { + v, err := syscall.GetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMP6_FILTER) + if err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + return &ICMPFilter{rawICMPFilter: rawICMPFilter{*v}}, nil +} + +func setIPv6ICMPFilter(fd int, f *ICMPFilter) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMP6_FILTER, &f.rawICMPFilter.ICMPv6Filter)) +} diff --git a/ipv6/sockopt_rfc3542_linux.go b/ipv6/sockopt_rfc3542_linux.go new file mode 100644 index 00000000..9cea683f --- /dev/null +++ b/ipv6/sockopt_rfc3542_linux.go @@ -0,0 +1,42 @@ +// Copyright 2013 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 ipv6 + +import ( + "os" + "syscall" +) + +func ipv6PathMTU(fd int) (int, error) { + v, err := syscall.GetsockoptIPv6MTUInfo(fd, ianaProtocolIPv6, syscall_IPV6_PATHMTU) + if err != nil { + return 0, os.NewSyscallError("getsockopt", err) + } + return int(v.Mtu), nil +} + +func ipv6ReceivePathMTU(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall_IPV6_RECVPATHMTU) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceivePathMTU(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall_IPV6_RECVPATHMTU, boolint(v))) +} + +func ipv6ICMPFilter(fd int) (*ICMPFilter, error) { + v, err := syscall.GetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMPV6_FILTER) + if err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + return &ICMPFilter{rawICMPFilter: rawICMPFilter{*v}}, nil +} + +func setIPv6ICMPFilter(fd int, f *ICMPFilter) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptICMPv6Filter(fd, ianaProtocolIPv6ICMP, syscall.ICMPV6_FILTER, &f.rawICMPFilter.ICMPv6Filter)) +} diff --git a/ipv6/sockopt_rfc3542_plan9.go b/ipv6/sockopt_rfc3542_plan9.go new file mode 100644 index 00000000..53a59f44 --- /dev/null +++ b/ipv6/sockopt_rfc3542_plan9.go @@ -0,0 +1,12 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +func ipv6PathMTU(fd int) (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EPLAN9 +} diff --git a/ipv6/sockopt_rfc3542_unix.go b/ipv6/sockopt_rfc3542_unix.go new file mode 100644 index 00000000..463f426b --- /dev/null +++ b/ipv6/sockopt_rfc3542_unix.go @@ -0,0 +1,48 @@ +// Copyright 2013 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 freebsd linux netbsd openbsd + +package ipv6 + +import ( + "os" + "syscall" +) + +func ipv6ReceiveTrafficClass(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVTCLASS) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceiveTrafficClass(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVTCLASS, boolint(v))) +} + +func ipv6ReceiveHopLimit(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVHOPLIMIT) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceiveHopLimit(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVHOPLIMIT, boolint(v))) +} + +func ipv6ReceivePacketInfo(fd int) (bool, error) { + v, err := syscall.GetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVPKTINFO) + if err != nil { + return false, os.NewSyscallError("getsockopt", err) + } + return v == 1, nil +} + +func setIPv6ReceivePacketInfo(fd int, v bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, ianaProtocolIPv6, syscall.IPV6_RECVPKTINFO, boolint(v))) +} diff --git a/ipv6/sockopt_rfc3542_windows.go b/ipv6/sockopt_rfc3542_windows.go new file mode 100644 index 00000000..2bc22d9a --- /dev/null +++ b/ipv6/sockopt_rfc3542_windows.go @@ -0,0 +1,62 @@ +// Copyright 2013 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 ipv6 + +import "syscall" + +func ipv6ReceiveTrafficClass(fd syscall.Handle) (bool, error) { + // TODO(mikio): Implement this + return false, syscall.EWINDOWS +} + +func setIPv6ReceiveTrafficClass(fd syscall.Handle, v bool) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} + +func ipv6ReceiveHopLimit(fd syscall.Handle) (bool, error) { + // TODO(mikio): Implement this + return false, syscall.EWINDOWS +} + +func setIPv6ReceiveHopLimit(fd syscall.Handle, v bool) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} + +func ipv6ReceivePacketInfo(fd syscall.Handle) (bool, error) { + // TODO(mikio): Implement this + return false, syscall.EWINDOWS +} + +func setIPv6ReceivePacketInfo(fd syscall.Handle, v bool) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} + +func ipv6PathMTU(fd syscall.Handle) (int, error) { + // TODO(mikio): Implement this + return 0, syscall.EWINDOWS +} + +func ipv6ReceivePathMTU(fd syscall.Handle) (bool, error) { + // TODO(mikio): Implement this + return false, syscall.EWINDOWS +} + +func setIPv6ReceivePathMTU(fd syscall.Handle, v bool) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} + +func ipv6ICMPFilter(fd syscall.Handle) (*ICMPFilter, error) { + // TODO(mikio): Implement this + return nil, syscall.EWINDOWS +} + +func setIPv6ICMPFilter(fd syscall.Handle, f *ICMPFilter) error { + // TODO(mikio): Implement this + return syscall.EWINDOWS +} diff --git a/ipv6/sockopt_test.go b/ipv6/sockopt_test.go new file mode 100644 index 00000000..95b5fad1 --- /dev/null +++ b/ipv6/sockopt_test.go @@ -0,0 +1,118 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "runtime" + "testing" +) + +var condFatalf = func() func(*testing.T, string, ...interface{}) { + // A few APIs are not implemented yet on some platforms. + switch runtime.GOOS { + case "darwin", "plan9", "windows": + return (*testing.T).Logf + } + return (*testing.T).Fatalf +}() + +func TestConnInitiatorPathMTU(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + ln, err := net.Listen("tcp6", "[::1]:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer ln.Close() + + done := make(chan bool) + go acceptor(t, ln, done) + + c, err := net.Dial("tcp6", ln.Addr().String()) + if err != nil { + t.Fatalf("net.Dial failed: %v", err) + } + defer c.Close() + + if pmtu, err := ipv6.NewConn(c).PathMTU(); err != nil { + condFatalf(t, "ipv6.Conn.PathMTU failed: %v", err) + } else { + t.Logf("path mtu for %v: %v", c.RemoteAddr(), pmtu) + } + + <-done +} + +func TestConnResponderPathMTU(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + ln, err := net.Listen("tcp6", "[::1]:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer ln.Close() + + done := make(chan bool) + go connector(t, "tcp6", ln.Addr().String(), done) + + c, err := ln.Accept() + if err != nil { + t.Fatalf("net.Accept failed: %v", err) + } + defer c.Close() + + if pmtu, err := ipv6.NewConn(c).PathMTU(); err != nil { + condFatalf(t, "ipv6.Conn.PathMTU failed: %v", err) + } else { + t.Logf("path mtu for %v: %v", c.RemoteAddr(), pmtu) + } + + <-done +} + +func TestPacketConnChecksum(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + + c, err := net.ListenPacket("ip6:89", "::") // OSPF for IPv6 + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + p := ipv6.NewPacketConn(c) + offset := 12 // see RFC 5340 + + for _, toggle := range []bool{false, true} { + if err := p.SetChecksum(toggle, offset); err != nil { + if toggle { + t.Fatalf("ipv6.PacketConn.SetChecksum(%v, %v) failed: %v", toggle, offset, err) + } else { + // Some platforms never allow to disable the kernel + // checksum processing. + t.Logf("ipv6.PacketConn.SetChecksum(%v, %v) failed: %v", toggle, offset, err) + } + } + if on, offset, err := p.Checksum(); err != nil { + t.Fatalf("ipv6.PacketConn.Checksum failed: %v", err) + } else { + t.Logf("kernel checksum processing enabled=%v, offset=%v", on, offset) + } + } +} diff --git a/ipv6/unicast_test.go b/ipv6/unicast_test.go new file mode 100644 index 00000000..063dc4e3 --- /dev/null +++ b/ipv6/unicast_test.go @@ -0,0 +1,195 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "runtime" + "testing" +) + +func benchmarkUDPListener() (net.PacketConn, net.Addr, error) { + c, err := net.ListenPacket("udp6", "[::1]:0") + if err != nil { + return nil, nil, err + } + dst, err := net.ResolveUDPAddr("udp6", c.LocalAddr().String()) + if err != nil { + c.Close() + return nil, nil, err + } + return c, dst, nil +} + +func BenchmarkReadWriteNetUDP(b *testing.B) { + c, dst, err := benchmarkUDPListener() + if err != nil { + b.Fatalf("benchmarkUDPListener failed: %v", err) + } + defer c.Close() + + for i := 0; i < b.N; i++ { + benchmarkReadWriteNetUDP(b, c, dst) + } +} + +func benchmarkReadWriteNetUDP(b *testing.B, c net.PacketConn, dst net.Addr) { + if _, err := c.WriteTo([]byte("HELLO-R-U-THERE"), dst); err != nil { + b.Fatalf("net.PacketConn.WriteTo failed: %v", err) + } + rb := make([]byte, 128) + if _, _, err := c.ReadFrom(rb); err != nil { + b.Fatalf("net.PacketConn.ReadFrom failed: %v", err) + } +} + +func BenchmarkReadWriteIPv6UDP(b *testing.B) { + c, dst, err := benchmarkUDPListener() + if err != nil { + b.Fatalf("benchmarkUDPListener failed: %v", err) + } + defer c.Close() + + p := ipv6.NewPacketConn(c) + cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagInterface | ipv6.FlagPathMTU + if err := p.SetControlMessage(cf, true); err != nil { + b.Fatalf("ipv6.PacketConn.SetControlMessage failed: %v", err) + } + ifi := loopbackInterface() + + for i := 0; i < b.N; i++ { + benchmarkReadWriteIPv6UDP(b, p, dst, ifi) + } +} + +func benchmarkReadWriteIPv6UDP(b *testing.B, p *ipv6.PacketConn, dst net.Addr, ifi *net.Interface) { + cm := ipv6.ControlMessage{ + TrafficClass: DiffServAF11 | CongestionExperienced, + HopLimit: 1, + } + if ifi != nil { + cm.IfIndex = ifi.Index + } + if _, err := p.WriteTo([]byte("HELLO-R-U-THERE"), &cm, dst); err != nil { + b.Fatalf("ipv6.PacketConn.WriteTo failed: %v", err) + } + rb := make([]byte, 128) + if _, _, _, err := p.ReadFrom(rb); err != nil { + b.Fatalf("ipv6.PacketConn.ReadFrom failed: %v", err) + } +} + +func TestPacketConnReadWriteUnicastUDP(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + c, err := net.ListenPacket("udp6", "[::1]:0") + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + dst, err := net.ResolveUDPAddr("udp6", c.LocalAddr().String()) + if err != nil { + t.Fatalf("net.ResolveUDPAddr failed: %v", err) + } + + p := ipv6.NewPacketConn(c) + cm := ipv6.ControlMessage{ + TrafficClass: DiffServAF11 | CongestionExperienced, + } + cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagInterface | ipv6.FlagPathMTU + ifi := loopbackInterface() + if ifi != nil { + cm.IfIndex = ifi.Index + } + + for i, toggle := range []bool{true, false, true} { + if err := p.SetControlMessage(cf, toggle); err != nil { + t.Fatalf("ipv6.PacketConn.SetControlMessage failed: %v", err) + } + cm.HopLimit = i + 1 + if _, err := p.WriteTo([]byte("HELLO-R-U-THERE"), &cm, dst); err != nil { + t.Fatalf("ipv6.PacketConn.WriteTo failed: %v", err) + } + b := make([]byte, 128) + if _, cm, _, err := p.ReadFrom(b); err != nil { + t.Fatalf("ipv6.PacketConn.ReadFrom failed: %v", err) + } else { + t.Logf("rcvd cmsg: %v", cm) + } + } +} + +func TestPacketConnReadWriteUnicastICMP(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + + c, err := net.ListenPacket("ip6:ipv6-icmp", "::1") + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + defer c.Close() + + dst, err := net.ResolveIPAddr("ip6", "::1") + if err != nil { + t.Fatalf("net.ResolveIPAddr failed: %v", err) + } + + p := ipv6.NewPacketConn(c) + cm := ipv6.ControlMessage{TrafficClass: DiffServAF11 | CongestionExperienced} + cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagInterface | ipv6.FlagPathMTU + ifi := loopbackInterface() + if ifi != nil { + cm.IfIndex = ifi.Index + } + + var f ipv6.ICMPFilter + f.SetAll(true) + f.Set(ipv6.ICMPTypeEchoReply, false) + if err := p.SetICMPFilter(&f); err != nil { + t.Fatalf("ipv6.PacketConn.SetICMPFilter failed: %v", err) + } + + for i, toggle := range []bool{true, false, true} { + wb, err := (&icmpMessage{ + Type: ipv6.ICMPTypeEchoRequest, Code: 0, + Body: &icmpEcho{ + ID: os.Getpid() & 0xffff, Seq: i + 1, + Data: []byte("HELLO-R-U-THERE"), + }, + }).Marshal() + if err != nil { + t.Fatalf("icmpMessage.Marshal failed: %v", err) + } + if err := p.SetControlMessage(cf, toggle); err != nil { + t.Fatalf("ipv6.PacketConn.SetControlMessage failed: %v", err) + } + cm.HopLimit = i + 1 + if _, err := p.WriteTo(wb, &cm, dst); err != nil { + t.Fatalf("ipv6.PacketConn.WriteTo failed: %v", err) + } + b := make([]byte, 128) + if n, cm, _, err := p.ReadFrom(b); err != nil { + t.Fatalf("ipv6.PacketConn.ReadFrom failed: %v", err) + } else { + t.Logf("rcvd cmsg: %v", cm) + if m, err := parseICMPMessage(b[:n]); err != nil { + t.Fatalf("parseICMPMessage failed: %v", err) + } else if m.Type != ipv6.ICMPTypeEchoReply || m.Code != 0 { + t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv6.ICMPTypeEchoReply, 0) + } + } + } +} diff --git a/ipv6/unicastsockopt_test.go b/ipv6/unicastsockopt_test.go new file mode 100644 index 00000000..a2e5bc54 --- /dev/null +++ b/ipv6/unicastsockopt_test.go @@ -0,0 +1,95 @@ +// Copyright 2013 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 ipv6_test + +import ( + "code.google.com/p/go.net/ipv6" + "net" + "os" + "runtime" + "testing" +) + +func TestConnUnicastSocketOptions(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + ln, err := net.Listen("tcp6", "[::1]:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer ln.Close() + + done := make(chan bool) + go acceptor(t, ln, done) + + c, err := net.Dial("tcp6", ln.Addr().String()) + if err != nil { + t.Fatalf("net.Dial failed: %v", err) + } + defer c.Close() + + testUnicastSocketOptions(t, ipv6.NewConn(c)) + + <-done +} + +var packetConnUnicastSocketOptionTests = []struct { + net, proto, addr string +}{ + {"udp6", "", "[::1]:0"}, + {"ip6", ":ipv6-icmp", "::1"}, +} + +func TestPacketConnUnicastSocketOptions(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, tt := range packetConnUnicastSocketOptionTests { + if tt.net == "ip6" && os.Getuid() != 0 { + t.Skip("must be root") + } + c, err := net.ListenPacket(tt.net+tt.proto, tt.addr) + if err != nil { + t.Fatalf("net.ListenPacket(%q, %q) failed: %v", tt.net+tt.proto, tt.addr, err) + } + defer c.Close() + + testUnicastSocketOptions(t, ipv6.NewPacketConn(c)) + } +} + +type testIPv6UnicastConn interface { + TrafficClass() (int, error) + SetTrafficClass(int) error + HopLimit() (int, error) + SetHopLimit(int) error +} + +func testUnicastSocketOptions(t *testing.T, c testIPv6UnicastConn) { + tclass := DiffServCS0 | NotECNTransport + if err := c.SetTrafficClass(tclass); err != nil { + t.Fatalf("ipv6.Conn.SetTrafficClass failed: %v", err) + } + if v, err := c.TrafficClass(); err != nil { + t.Fatalf("ipv6.Conn.TrafficClass failed: %v", err) + } else if v != tclass { + t.Fatalf("got unexpected traffic class %v; expected %v", v, tclass) + } + + hoplim := 255 + if err := c.SetHopLimit(hoplim); err != nil { + t.Fatalf("ipv6.Conn.SetHopLimit failed: %v", err) + } + if v, err := c.HopLimit(); err != nil { + t.Fatalf("ipv6.Conn.HopLimit failed: %v", err) + } else if v != hoplim { + t.Fatalf("got unexpected hop limit %v; expected %v", v, hoplim) + } +}