diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 577a44ffe63..c7f985d88f7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -143,7 +143,7 @@ }, { "ImportPath": "github.com/jbenet/go-multiaddr-net", - "Rev": "04044c2289504304472715d827a8f564fa3759a8" + "Rev": "eae26b653a87d02193bb261ae5c5e1f39cc033d6" }, { "ImportPath": "github.com/jbenet/go-multihash", @@ -158,6 +158,14 @@ "ImportPath": "github.com/jbenet/go-random", "Rev": "c50d0ce235bacde481d3e885b5dfd562c75c981a" }, + { + "ImportPath": "github.com/jbenet/go-reuseport", + "Rev": "1e1968c4744fef51234e83f015aa0187b4bd796b" + }, + { + "ImportPath": "github.com/jbenet/go-sockaddr/net", + "Rev": "da304f94eea1af8ba8d1faf184623e1f9d9777dc" + }, { "ImportPath": "github.com/jbenet/go-temp-err-catcher", "Rev": "c531232018e678b2a702cfb86b5c3f68d1c8beb8" diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go index 2fec9663f78..dd8cf68fa94 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go @@ -236,13 +236,17 @@ func Listen(laddr ma.Multiaddr) (Listener, error) { return nil, err } - // we need to fetch the new multiaddr from the listener, as it - // may have resolved to some other value. - nladdr, err := FromNetAddr(nl.Addr()) + // we want to fetch the new multiaddr from the listener, as it may + // have resolved to some other value. WrapNetListener does it for us. + return WrapNetListener(nl) +} + +// WrapNetListener wraps a net.Listener with a manet.Listener. +func WrapNetListener(nl net.Listener) (Listener, error) { + laddr, err := FromNetAddr(nl.Addr()) if err != nil { return nil, err } - laddr = nladdr return &maListener{ Listener: nl, diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/.travis.yml b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/.travis.yml new file mode 100644 index 00000000000..7669438ed9a --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.2 + - 1.3 + - 1.4 + - release + - tip + +script: + - go test -v ./... diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/LICENSE b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/LICENSE new file mode 100644 index 00000000000..0d760cbb4d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Conformal Systems LLC. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/README.md b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/README.md new file mode 100644 index 00000000000..f373e593545 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/README.md @@ -0,0 +1,42 @@ +# go-reuseport + +[![travisbadge](https://travis-ci.org/jbenet/go-reuseport.svg)](https://travis-ci.org/jbenet/go-reuseport) + +This package enables listening and dialing from _the same_ TCP or UDP port. +This means that the following sockopts are set: + +``` +SO_REUSEADDR +SO_REUSEPORT +``` + +- godoc: https://godoc.org/github.com/jbenet/go-reuseport + +This is a simple package to get around the problem of reusing addresses. +The go `net` package (to my knowledge) does not allow setting socket options. +This is particularly problematic when attempting to do TCP NAT holepunching, +which requires a process to both Listen and Dial on the same TCP port. +This package makes this possible for me. It is a pretty narrow use case, but +perhaps this package can grow to be more general over time. + +## Examples + + +```Go +// listen on the same port. oh yeah. +l1, _ := reuse.Listen("tcp", "127.0.0.1:1234") +l2, _ := reuse.Listen("tcp", "127.0.0.1:1234") +``` + +```Go +// dial from the same port. oh yeah. +l1, _ := reuse.Listen("tcp", "127.0.0.1:1234") +l2, _ := reuse.Listen("tcp", "127.0.0.1:1235") +c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235") +``` + +**Note: cant dial self because tcp/ip stacks use 4-tuples to identify connections, and doing so would clash.** + +## Tested + +Tested on `darwin` and `linux`. diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/addr.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/addr.go new file mode 100644 index 00000000000..cfffc7c8c88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/addr.go @@ -0,0 +1,20 @@ +package reuseport + +import ( + "net" +) + +func ResolveAddr(network, address string) (net.Addr, error) { + switch network { + default: + return nil, net.UnknownNetworkError(network) + case "ip", "ip4", "ip6": + return net.ResolveIPAddr(network, address) + case "tcp", "tcp4", "tcp6": + return net.ResolveTCPAddr(network, address) + case "udp", "udp4", "udp6": + return net.ResolveUDPAddr(network, address) + case "unix", "unixgram", "unixpacket": + return net.ResolveUnixAddr(network, address) + } +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_bsd.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_bsd.go new file mode 100644 index 00000000000..480ddbbb841 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_bsd.go @@ -0,0 +1,10 @@ +// +build darwin freebsd dragonfly netbsd openbsd + +package reuseport + +import ( + "syscall" +) + +var soReusePort = syscall.SO_REUSEPORT +var soReuseAddr = syscall.SO_REUSEADDR diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_linux.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_linux.go new file mode 100644 index 00000000000..93ddfd90e8a --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/const_linux.go @@ -0,0 +1,10 @@ +// +build linux + +package reuseport + +import ( + "syscall" +) + +var soReusePort = 15 // this is not defined in unix go pkg. +var soReuseAddr = syscall.SO_REUSEADDR diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_unix.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_unix.go new file mode 100644 index 00000000000..ed8415abd85 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_unix.go @@ -0,0 +1,353 @@ +// +build darwin freebsd dragonfly netbsd openbsd linux + +package reuseport + +import ( + "net" + "os" + "strconv" + "syscall" + "time" + + sockaddrnet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net" +) + +const ( + filePrefix = "port." +) + +// Wrapper around the socket system call that marks the returned file +// descriptor as nonblocking and close-on-exec. +func socket(family, socktype, protocol int) (fd int, err error) { + syscall.ForkLock.RLock() + fd, err = syscall.Socket(family, socktype, protocol) + if err == nil { + syscall.CloseOnExec(fd) + } + syscall.ForkLock.RUnlock() + + if err != nil { + return -1, err + } + + // set non-blocking until after connect, because we cant poll using runtime :( + // if err = syscall.SetNonblock(fd, true); err != nil { + // syscall.Close(fd) + // return -1, err + // } + + if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, soReuseAddr, 1); err != nil { + // fmt.Println("reuse addr failed") + syscall.Close(fd) + return -1, err + } + + if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, soReusePort, 1); err != nil { + // fmt.Println("reuse port failed") + syscall.Close(fd) + return -1, err + } + + // set setLinger to 5 as reusing exact same (srcip:srcport, dstip:dstport) + // will otherwise fail on connect. + if err = setLinger(fd, 5); err != nil { + // fmt.Println("linger failed") + syscall.Close(fd) + return -1, err + } + + return fd, nil +} + +func dial(dialer net.Dialer, netw, addr string) (c net.Conn, err error) { + var ( + fd int + lfamily int + rfamily int + socktype int + lprotocol int + rprotocol int + file *os.File + remoteSockaddr syscall.Sockaddr + localSockaddr syscall.Sockaddr + ) + + netAddr, err := ResolveAddr(netw, addr) + if err != nil { + // fmt.Println("resolve addr failed") + return nil, err + } + + switch netAddr.(type) { + case *net.TCPAddr, *net.UDPAddr: + default: + return nil, ErrUnsupportedProtocol + } + + localSockaddr = sockaddrnet.NetAddrToSockaddr(dialer.LocalAddr) + remoteSockaddr = sockaddrnet.NetAddrToSockaddr(netAddr) + + rfamily = sockaddrnet.NetAddrAF(netAddr) + rprotocol = sockaddrnet.NetAddrIPPROTO(netAddr) + socktype = sockaddrnet.NetAddrSOCK(netAddr) + + if dialer.LocalAddr != nil { + switch dialer.LocalAddr.(type) { + case *net.TCPAddr, *net.UDPAddr: + default: + return nil, ErrUnsupportedProtocol + } + + // check family and protocols match. + lfamily = sockaddrnet.NetAddrAF(dialer.LocalAddr) + lprotocol = sockaddrnet.NetAddrIPPROTO(dialer.LocalAddr) + if lfamily != rfamily && lprotocol != rfamily { + return nil, &net.AddrError{Err: "unexpected address type", Addr: netAddr.String()} + } + } + + // look at dialTCP in http://golang.org/src/net/tcpsock_posix.go .... ! + // here we just try again 3 times. + for i := 0; i < 3; i++ { + if fd, err = socket(rfamily, socktype, rprotocol); err != nil { + return nil, err + } + + if err = syscall.Bind(fd, localSockaddr); err != nil { + // fmt.Println("bind failed") + syscall.Close(fd) + return nil, err + } + if err = connect(fd, remoteSockaddr); err != nil { + syscall.Close(fd) + // fmt.Println("connect failed", localSockaddr, err) + continue // try again. + } + + break + } + if err != nil { + return nil, err + } + + if rprotocol == syscall.IPPROTO_TCP { + // by default golang/net sets TCP no delay to true. + if err = setNoDelay(fd, true); err != nil { + // fmt.Println("set no delay failed") + syscall.Close(fd) + return nil, err + } + } + + if err = syscall.SetNonblock(fd, true); err != nil { + syscall.Close(fd) + return nil, err + } + + switch socktype { + case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET: + + // File Name get be nil + file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid())) + if c, err = net.FileConn(file); err != nil { + // fmt.Println("fileconn failed") + syscall.Close(fd) + return nil, err + } + + case syscall.SOCK_DGRAM: + + // File Name get be nil + file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid())) + if c, err = net.FileConn(file); err != nil { + // fmt.Println("fileconn failed") + syscall.Close(fd) + return nil, err + } + } + + if err = file.Close(); err != nil { + // fmt.Println("file close failed") + syscall.Close(fd) + return nil, err + } + + return c, err +} + +func listen(netw, addr string) (fd int, err error) { + var ( + family int + socktype int + protocol int + sockaddr syscall.Sockaddr + ) + + netAddr, err := ResolveAddr(netw, addr) + if err != nil { + // fmt.Println("resolve addr failed") + return -1, err + } + + switch netAddr.(type) { + case *net.TCPAddr, *net.UDPAddr: + default: + return -1, ErrUnsupportedProtocol + } + + family = sockaddrnet.NetAddrAF(netAddr) + protocol = sockaddrnet.NetAddrIPPROTO(netAddr) + sockaddr = sockaddrnet.NetAddrToSockaddr(netAddr) + socktype = sockaddrnet.NetAddrSOCK(netAddr) + + if fd, err = socket(family, socktype, protocol); err != nil { + return -1, err + } + + if err = syscall.Bind(fd, sockaddr); err != nil { + // fmt.Println("bind failed") + syscall.Close(fd) + return -1, err + } + + if protocol == syscall.IPPROTO_TCP { + // by default golang/net sets TCP no delay to true. + if err = setNoDelay(fd, true); err != nil { + // fmt.Println("set no delay failed") + syscall.Close(fd) + return -1, err + } + } + + if err = syscall.SetNonblock(fd, true); err != nil { + syscall.Close(fd) + return -1, err + } + + return fd, nil +} + +func listenStream(netw, addr string) (l net.Listener, err error) { + var ( + file *os.File + ) + + fd, err := listen(netw, addr) + if err != nil { + return nil, err + } + + // Set backlog size to the maximum + if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil { + // fmt.Println("listen failed") + syscall.Close(fd) + return nil, err + } + + file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid())) + if l, err = net.FileListener(file); err != nil { + // fmt.Println("filelistener failed") + syscall.Close(fd) + return nil, err + } + + if err = file.Close(); err != nil { + // fmt.Println("file close failed") + syscall.Close(fd) + return nil, err + } + + return l, err +} + +func listenPacket(netw, addr string) (p net.PacketConn, err error) { + var ( + file *os.File + ) + + fd, err := listen(netw, addr) + if err != nil { + return nil, err + } + + file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid())) + if p, err = net.FilePacketConn(file); err != nil { + // fmt.Println("filelistener failed") + syscall.Close(fd) + return nil, err + } + + if err = file.Close(); err != nil { + // fmt.Println("file close failed") + syscall.Close(fd) + return nil, err + } + + return p, err +} + +func listenUDP(netw, addr string) (c net.Conn, err error) { + var ( + file *os.File + ) + + fd, err := listen(netw, addr) + if err != nil { + return nil, err + } + + file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid())) + if c, err = net.FileConn(file); err != nil { + // fmt.Println("filelistener failed") + syscall.Close(fd) + return nil, err + } + + if err = file.Close(); err != nil { + // fmt.Println("file close failed") + syscall.Close(fd) + return nil, err + } + + return c, err +} + +// this is close to the connect() function inside stdlib/net +func connect(fd int, ra syscall.Sockaddr) error { + switch err := syscall.Connect(fd, ra); err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + case nil, syscall.EISCONN: + return nil + default: + return err + } + + var err error + start := time.Now() + for { + // if err := fd.pd.WaitWrite(); err != nil { + // return err + // } + // i'd use the above fd.pd.WaitWrite to poll io correctly, just like net sockets... + // but of course, it uses fucking runtime_* functions that _cannot_ be used by + // non-go-stdlib source... seriously guys, what kind of bullshit is that!? + // we're relegated to using syscall.Select (what nightmare that is) or using + // a simple but totally bogus time-based wait. garbage. + var nerr int + nerr, err = syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ERROR) + if err != nil { + return err + } + switch err = syscall.Errno(nerr); err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + if time.Now().Sub(start) > time.Second { + return err + } + <-time.After(20 * time.Microsecond) + case syscall.Errno(0), syscall.EISCONN: + return nil + default: + return err + } + } +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_windows.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_windows.go new file mode 100644 index 00000000000..10fe4520d15 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_windows.go @@ -0,0 +1,15 @@ +package reuseport + +import ( + "net" +) + +// TODO. for now, just pass it over to net.Listen/net.Dial + +func listen(network, address string) (net.Listener, error) { + return net.Listen(network, address) +} + +func dial(dialer net.Dialer, network, address string) (net.Conn, error) { + return dialer.Dial(network, address) +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/interface.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/interface.go new file mode 100644 index 00000000000..dae96c574d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/interface.go @@ -0,0 +1,91 @@ +// Package reuseport provides Listen and Dial functions that set socket options +// in order to be able to reuse ports. You should only use this package if you +// know what SO_REUSEADDR and SO_REUSEPORT are. +// +// For example: +// +// // listen on the same port. oh yeah. +// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234") +// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234") +// +// // dial from the same port. oh yeah. +// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234") +// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235") +// c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235") +// +// Note: cant dial self because tcp/ip stacks use 4-tuples to identify connections, +// and doing so would clash. +package reuseport + +import ( + "errors" + "net" + "time" +) + +// ErrUnsuportedProtocol signals that the protocol is not currently +// supported by this package. This package currently only supports TCP. +var ErrUnsupportedProtocol = errors.New("protocol not yet supported") + +// ErrReuseFailed is returned if a reuse attempt was unsuccessful. +var ErrReuseFailed = errors.New("reuse failed") + +// Listen listens at the given network and address. see net.Listen +// Returns a net.Listener created from a file discriptor for a socket +// with SO_REUSEPORT and SO_REUSEADDR option set. +func Listen(network, address string) (net.Listener, error) { + return listenStream(network, address) +} + +// ListenPacket listens at the given network and address. see net.ListenPacket +// Returns a net.Listener created from a file discriptor for a socket +// with SO_REUSEPORT and SO_REUSEADDR option set. +func ListenPacket(network, address string) (net.PacketConn, error) { + return listenPacket(network, address) +} + +// Dial dials the given network and address. see net.Dialer.Dial +// Returns a net.Conn created from a file discriptor for a socket +// with SO_REUSEPORT and SO_REUSEADDR option set. +func Dial(network, laddr, raddr string) (net.Conn, error) { + + var d Dialer + if laddr != "" { + netladdr, err := ResolveAddr(network, laddr) + if err != nil { + return nil, err + } + d.D.LocalAddr = netladdr + } + + return d.Dial(network, raddr) +} + +// Dialer is used to specify the Dial options, much like net.Dialer. +// We simply wrap a net.Dialer. +type Dialer struct { + D net.Dialer +} + +// Dial dials the given network and address. see net.Dialer.Dial +// Returns a net.Conn created from a file discriptor for a socket +// with SO_REUSEPORT and SO_REUSEADDR option set. +func (d *Dialer) Dial(network, address string) (net.Conn, error) { + c, err := dial(d.D, network, address) + if err != nil { + return nil, err + } + + // there's a rare case where dial returns successfully but for some reason the + // RemoteAddr is not yet set. We wait here a while until it is, and if too long + // passes, we fail. This is horrendous. + for start := time.Now(); c.RemoteAddr() == nil; { + if time.Now().Sub(start) > (time.Millisecond * 500) { + c.Close() + return nil, ErrReuseFailed + } + + <-time.After(20 * time.Microsecond) + } + return c, nil +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/opts_posix.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/opts_posix.go new file mode 100644 index 00000000000..de122f46daa --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/opts_posix.go @@ -0,0 +1,35 @@ +// Copyright 2009 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 dragonfly freebsd linux netbsd openbsd solaris windows + +package reuseport + +import ( + "os" + "syscall" +) + +func boolint(b bool) int { + if b { + return 1 + } + return 0 +} + +func setNoDelay(fd int, noDelay bool) error { + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, boolint(noDelay))) +} + +func setLinger(fd int, sec int) error { + var l syscall.Linger + if sec >= 0 { + l.Onoff = 1 + l.Linger = int32(sec) + } else { + l.Onoff = 0 + l.Linger = 0 + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptLinger(fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &l)) +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/reuse_test.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/reuse_test.go new file mode 100644 index 00000000000..0dc77abba7c --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/reuse_test.go @@ -0,0 +1,374 @@ +package reuseport + +import ( + "bytes" + "io" + "net" + "os" + "strings" + "testing" +) + +func echo(c net.Conn) { + io.Copy(c, c) + c.Close() +} + +func packetEcho(c net.PacketConn) { + buf := make([]byte, 65536) + for { + n, addr, err := c.ReadFrom(buf) + if err != nil { + return + } + if _, err := c.WriteTo(buf[:n], addr); err != nil { + return + } + } +} + +func acceptAndEcho(l net.Listener) { + for { + c, err := l.Accept() + if err != nil { + return + } + go echo(c) + } +} + +func CI() bool { + return os.Getenv("TRAVIS") == "true" +} + +func TestStreamListenSamePort(t *testing.T) { + + // any ports + any := [][]string{ + []string{"tcp", "0.0.0.0:0"}, + []string{"tcp4", "0.0.0.0:0"}, + []string{"tcp6", "[::]:0"}, + + []string{"tcp", "127.0.0.1:0"}, + []string{"tcp", "[::1]:0"}, + []string{"tcp4", "127.0.0.1:0"}, + []string{"tcp6", "[::1]:0"}, + } + + // specific ports. off in CI + specific := [][]string{ + []string{"tcp", "127.0.0.1:5556"}, + []string{"tcp", "[::1]:5557"}, + []string{"tcp4", "127.0.0.1:5558"}, + []string{"tcp6", "[::1]:5559"}, + } + + testCases := any + if !CI() { + testCases = append(testCases, specific...) + } + + for _, tcase := range testCases { + network := tcase[0] + addr := tcase[1] + t.Log("testing", network, addr) + + l1, err := Listen(network, addr) + if err != nil { + t.Fatal(err) + continue + } + defer l1.Close() + t.Log("listening", l1.Addr()) + + l2, err := Listen(l1.Addr().Network(), l1.Addr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer l2.Close() + t.Log("listening", l2.Addr()) + + l3, err := Listen(l2.Addr().Network(), l2.Addr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer l3.Close() + t.Log("listening", l3.Addr()) + + if l1.Addr().String() != l2.Addr().String() { + t.Fatal("addrs should match", l1.Addr(), l2.Addr()) + } + + if l1.Addr().String() != l3.Addr().String() { + t.Fatal("addrs should match", l1.Addr(), l3.Addr()) + } + } +} + +func TestPacketListenSamePort(t *testing.T) { + + // any ports + any := [][]string{ + []string{"udp", "0.0.0.0:0"}, + []string{"udp4", "0.0.0.0:0"}, + []string{"udp6", "[::]:0"}, + + []string{"udp", "127.0.0.1:0"}, + []string{"udp", "[::1]:0"}, + []string{"udp4", "127.0.0.1:0"}, + []string{"udp6", "[::1]:0"}, + } + + // specific ports. off in CI + specific := [][]string{ + []string{"udp", "127.0.0.1:5560"}, + []string{"udp", "[::1]:5561"}, + []string{"udp4", "127.0.0.1:5562"}, + []string{"udp6", "[::1]:5563"}, + } + + testCases := any + if !CI() { + testCases = append(testCases, specific...) + } + + for _, tcase := range testCases { + network := tcase[0] + addr := tcase[1] + t.Log("testing", network, addr) + + l1, err := ListenPacket(network, addr) + if err != nil { + t.Fatal(err) + continue + } + defer l1.Close() + t.Log("listening", l1.LocalAddr()) + + l2, err := ListenPacket(l1.LocalAddr().Network(), l1.LocalAddr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer l2.Close() + t.Log("listening", l2.LocalAddr()) + + l3, err := ListenPacket(l2.LocalAddr().Network(), l2.LocalAddr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer l3.Close() + t.Log("listening", l3.LocalAddr()) + + if l1.LocalAddr().String() != l2.LocalAddr().String() { + t.Fatal("addrs should match", l1.LocalAddr(), l2.LocalAddr()) + } + + if l1.LocalAddr().String() != l3.LocalAddr().String() { + t.Fatal("addrs should match", l1.LocalAddr(), l3.LocalAddr()) + } + } +} + +func TestStreamListenDialSamePort(t *testing.T) { + + any := [][]string{ + []string{"tcp", "0.0.0.0:0", "0.0.0.0:0"}, + []string{"tcp4", "0.0.0.0:0", "0.0.0.0:0"}, + []string{"tcp6", "[::]:0", "[::]:0"}, + + []string{"tcp", "127.0.0.1:0", "127.0.0.1:0"}, + []string{"tcp4", "127.0.0.1:0", "127.0.0.1:0"}, + []string{"tcp6", "[::1]:0", "[::1]:0"}, + } + + specific := [][]string{ + []string{"tcp", "127.0.0.1:0", "127.0.0.1:5571"}, + []string{"tcp4", "127.0.0.1:0", "127.0.0.1:5573"}, + []string{"tcp6", "[::1]:0", "[::1]:5574"}, + []string{"tcp", "127.0.0.1:5570", "127.0.0.1:0"}, + []string{"tcp4", "127.0.0.1:5572", "127.0.0.1:0"}, + []string{"tcp6", "[::1]:5573", "[::1]:0"}, + } + + testCases := any + if !CI() { + testCases = append(testCases, specific...) + } + + for _, tcase := range testCases { + t.Log("testing", tcase) + network := tcase[0] + addr1 := tcase[1] + addr2 := tcase[2] + + l1, err := Listen(network, addr1) + if err != nil { + t.Fatal(err) + continue + } + defer l1.Close() + t.Log("listening", l1.Addr()) + + l2, err := Listen(network, addr2) + if err != nil { + t.Fatal(err) + continue + } + defer l2.Close() + t.Log("listening", l2.Addr()) + + go acceptAndEcho(l1) + go acceptAndEcho(l2) + + c1, err := Dial(network, l1.Addr().String(), l2.Addr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer c1.Close() + t.Log("dialed", c1, c1.LocalAddr(), c1.RemoteAddr()) + + if getPort(l1.Addr()) != getPort(c1.LocalAddr()) { + t.Fatal("addrs should match", l1.Addr(), c1.LocalAddr()) + } + + if getPort(l2.Addr()) != getPort(c1.RemoteAddr()) { + t.Fatal("addrs should match", l2.Addr(), c1.RemoteAddr()) + } + + hello1 := []byte("hello world") + hello2 := make([]byte, len(hello1)) + if _, err := c1.Write(hello1); err != nil { + t.Fatal(err) + continue + } + + if _, err := c1.Read(hello2); err != nil { + t.Fatal(err) + continue + } + + if !bytes.Equal(hello1, hello2) { + t.Fatal("echo failed", string(hello1), "!=", string(hello2)) + } + t.Log("echoed", string(hello2)) + c1.Close() + } +} + +func TestPacketListenDialSamePort(t *testing.T) { + + any := [][]string{ + []string{"udp", "0.0.0.0:0", "0.0.0.0:0"}, + []string{"udp4", "0.0.0.0:0", "0.0.0.0:0"}, + []string{"udp6", "[::]:0", "[::]:0"}, + + []string{"udp", "127.0.0.1:0", "127.0.0.1:0"}, + []string{"udp4", "127.0.0.1:0", "127.0.0.1:0"}, + []string{"udp6", "[::1]:0", "[::1]:0"}, + } + + specific := [][]string{ + []string{"udp", "127.0.0.1:5670", "127.0.0.1:5671"}, + []string{"udp4", "127.0.0.1:5672", "127.0.0.1:5673"}, + []string{"udp6", "[::1]:5673", "[::1]:5674"}, + } + + testCases := any + if !CI() { + testCases = append(testCases, specific...) + } + + for _, tcase := range testCases { + t.Log("testing", tcase) + network := tcase[0] + addr1 := tcase[1] + addr2 := tcase[2] + + l1, err := ListenPacket(network, addr1) + if err != nil { + t.Fatal(err) + continue + } + defer l1.Close() + t.Log("listening", l1.LocalAddr()) + + l2, err := ListenPacket(network, addr2) + if err != nil { + t.Fatal(err) + continue + } + defer l2.Close() + t.Log("listening", l2.LocalAddr()) + + go packetEcho(l1) + go packetEcho(l2) + + c1, err := Dial(network, l1.LocalAddr().String(), l2.LocalAddr().String()) + if err != nil { + t.Fatal(err) + continue + } + defer c1.Close() + t.Log("dialed", c1.LocalAddr(), c1.RemoteAddr()) + + if getPort(l1.LocalAddr()) != getPort(c1.LocalAddr()) { + t.Fatal("addrs should match", l1.LocalAddr(), c1.LocalAddr()) + } + + if getPort(l2.LocalAddr()) != getPort(c1.RemoteAddr()) { + t.Fatal("addrs should match", l2.LocalAddr(), c1.RemoteAddr()) + } + + hello1 := []byte("hello world") + hello2 := make([]byte, len(hello1)) + if _, err := c1.Write(hello1); err != nil { + t.Fatal(err) + continue + } + + if _, err := c1.Read(hello2); err != nil { + t.Fatal(err) + continue + } + + if !bytes.Equal(hello1, hello2) { + t.Fatal("echo failed", string(hello1), "!=", string(hello2)) + } + t.Log("echoed", string(hello2)) + } +} + +func TestUnixNotSupported(t *testing.T) { + + testCases := [][]string{ + []string{"unix", "/tmp/foo"}, + } + + for _, tcase := range testCases { + network := tcase[0] + addr := tcase[1] + t.Log("testing", network, addr) + + _, err := Listen(network, addr) + if err == nil { + t.Fatal("unix supported") + continue + } + } +} + +func getPort(a net.Addr) string { + if a == nil { + return "" + } + s := strings.Split(a.String(), ":") + if len(s) > 1 { + return s[1] + } + return "" +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/.gitignore b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/.gitignore new file mode 100644 index 00000000000..9daeafb9864 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/.gitignore @@ -0,0 +1 @@ +test diff --git a/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/main.go b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/main.go new file mode 100644 index 00000000000..2bec1b2ded9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-reuseport/test/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" + + reuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport" + resolve "github.com/jbenet/go-net-resolve-addr" +) + +func main() { + + l1, err := reuse.Listen("tcp", "0.0.0.0:11111") + maybeDie(err) + fmt.Printf("listening on %s\n", l1.Addr()) + + l2, err := reuse.Listen("tcp", "0.0.0.0:22222") + maybeDie(err) + fmt.Printf("listening on %s\n", l2.Addr()) + + a1, err := resolve.ResolveAddr("dial", "tcp", "127.0.0.1:11111") + maybeDie(err) + + a3, err := resolve.ResolveAddr("dial", "tcp", "127.0.0.1:33333") + maybeDie(err) + + d1 := reuse.Dialer{net.Dialer{LocalAddr: a1}} + d2 := reuse.Dialer{net.Dialer{LocalAddr: a3}} + + go func() { + l2to1foo, err := l2.Accept() + maybeDie(err) + fmt.Printf("%s accepted conn from %s\n", addrStr(l2.Addr()), addrStr(l2to1foo.RemoteAddr())) + + fmt.Println("safe") + + l1to2bar, err := l1.Accept() + maybeDie(err) + fmt.Printf("%s accepted conn from %s\n", addrStr(l1.Addr()), addrStr(l1to2bar.RemoteAddr())) + + io.Copy(l1to2bar, l2to1foo) + }() + + d1to2foo, err := d1.Dial("tcp4", "127.0.0.1:22222") + maybeDie(err) + fmt.Printf("dialing from %s to %s\n", d1.D.LocalAddr, "127.0.0.1:22222") + + d2to1bar, err := d2.Dial("tcp4", "127.0.0.1:11111") + maybeDie(err) + fmt.Printf("dialing from %s to %s\n", d2.D.LocalAddr, "127.0.0.1:11111") + + go io.Copy(d1to2foo, os.Stdin) + io.Copy(os.Stdout, d2to1bar) +} + +func die(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(-1) +} + +func maybeDie(err error) { + if err != nil { + die(err) + } +} + +func addrStr(a net.Addr) string { + return fmt.Sprintf("%s/%s", a.Network(), a) +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net.go b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net.go new file mode 100644 index 00000000000..9299d08ff3a --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net.go @@ -0,0 +1,341 @@ +// package sockaddrnet provides conversions between net.Addr and syscall.Sockaddr +package sockaddrnet + +import ( + "net" + "syscall" +) + +// NetAddrAF returns the syscall AF_* type for a given net.Addr +// returns AF_UNSPEC if unknown +func NetAddrAF(addr net.Addr) int { + switch addr := addr.(type) { + case *net.IPAddr: + return IPAF(addr.IP) + + case *net.TCPAddr: + return IPAF(addr.IP) + + case *net.UDPAddr: + return IPAF(addr.IP) + + case *net.UnixAddr: + return AF_UNIX + + default: + return AF_UNSPEC + } +} + +// IPAF returns the syscall AF_* type for a given IP address +// returns AF_UNSPEC if unknown +func IPAF(ip net.IP) int { + switch { + case ip.To4() != nil: + return AF_INET + + case ip.To16() != nil: + return AF_INET6 + + default: + return AF_UNSPEC + } +} + +// NetAddrIPPROTO returns the syscall IPPROTO_* type for a given net.Addr +// returns -1 if protocol unknown +func NetAddrIPPROTO(addr net.Addr) int { + switch addr := addr.(type) { + case *net.IPAddr: + switch { + default: + return IPPROTO_IP + + case addr.IP.To4() != nil: + return IPPROTO_IPV4 + + case addr.IP.To16() != nil: + return IPPROTO_IPV6 + } + + case *net.TCPAddr: + return IPPROTO_TCP + + case *net.UDPAddr: + return IPPROTO_UDP + + default: + return -1 + } +} + +// NetAddrSOCK returns the syscall SOCK_* type for a given net.Addr +// returns 0 if type unknown +func NetAddrSOCK(addr net.Addr) int { + switch addr := addr.(type) { + case *net.IPAddr: + return SOCK_DGRAM + case *net.TCPAddr: + return SOCK_STREAM + case *net.UDPAddr: + return SOCK_DGRAM + case *net.UnixAddr: + switch addr.Net { + default: + return 0 + case "unix": + return SOCK_STREAM + case "unixgram": + return SOCK_DGRAM + case "unixpacket": + return SOCK_SEQPACKET + } + default: + return 0 + } +} + +// NetAddrToSockaddr converts a net.Addr to a syscall.Sockaddr. +// Returns nil if the input is invalid or conversion is not possible. +func NetAddrToSockaddr(addr net.Addr) syscall.Sockaddr { + switch addr := addr.(type) { + case *net.IPAddr: + return IPAddrToSockaddr(addr) + case *net.TCPAddr: + return TCPAddrToSockaddr(addr) + case *net.UDPAddr: + return UDPAddrToSockaddr(addr) + case *net.UnixAddr: + sa, _ := UnixAddrToSockaddr(addr) + return sa + default: + return nil + } +} + +// IPAndZoneToSockaddr converts a net.IP (with optional IPv6 Zone) to a syscall.Sockaddr +// Returns nil if conversion fails. +func IPAndZoneToSockaddr(ip net.IP, zone string) syscall.Sockaddr { + switch { + case len(ip) < net.IPv4len: // default to IPv4 + buf := [4]byte{0, 0, 0, 0} + return &syscall.SockaddrInet4{Addr: buf} + + case ip.To4() != nil: + var buf [4]byte + copy(buf[:], ip[12:16]) // last 4 bytes + return &syscall.SockaddrInet4{Addr: buf} + + case ip.To16() != nil: + var buf [16]byte + copy(buf[:], ip) + return &syscall.SockaddrInet6{Addr: buf, ZoneId: uint32(IP6ZoneToInt(zone))} + } + panic("should be unreachable") +} + +// IPAddrToSockaddr converts a net.IPAddr to a syscall.Sockaddr. +// Returns nil if conversion fails. +func IPAddrToSockaddr(addr *net.IPAddr) syscall.Sockaddr { + return IPAndZoneToSockaddr(addr.IP, addr.Zone) +} + +// TCPAddrToSockaddr converts a net.TCPAddr to a syscall.Sockaddr. +// Returns nil if conversion fails. +func TCPAddrToSockaddr(addr *net.TCPAddr) syscall.Sockaddr { + sa := IPAndZoneToSockaddr(addr.IP, addr.Zone) + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + sa.Port = addr.Port + return sa + case *syscall.SockaddrInet6: + sa.Port = addr.Port + return sa + default: + return nil + } +} + +// UDPAddrToSockaddr converts a net.UDPAddr to a syscall.Sockaddr. +// Returns nil if conversion fails. +func UDPAddrToSockaddr(addr *net.UDPAddr) syscall.Sockaddr { + sa := IPAndZoneToSockaddr(addr.IP, addr.Zone) + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + sa.Port = addr.Port + return sa + case *syscall.SockaddrInet6: + sa.Port = addr.Port + return sa + default: + return nil + } +} + +// UnixAddrToSockaddr converts a net.UnixAddr to a syscall.Sockaddr, and returns +// the type (syscall.SOCK_STREAM, syscall.SOCK_DGRAM, syscall.SOCK_SEQPACKET) +// Returns (nil, 0) if conversion fails. +func UnixAddrToSockaddr(addr *net.UnixAddr) (syscall.Sockaddr, int) { + t := 0 + switch addr.Net { + case "unix": + t = syscall.SOCK_STREAM + case "unixgram": + t = syscall.SOCK_DGRAM + case "unixpacket": + t = syscall.SOCK_SEQPACKET + default: + return nil, 0 + } + return &syscall.SockaddrUnix{Name: addr.Name}, t +} + +// IPAndZoneToSockaddr converts a net.IP (with optional IPv6 Zone) to a syscall.Sockaddr +// Returns nil if conversion fails. +func SockaddrToIPAndZone(sa syscall.Sockaddr) (net.IP, string) { + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + ip := make([]byte, 16) + copy(ip[12:16], sa.Addr[:]) + return ip, "" + + case *syscall.SockaddrInet6: + ip := make([]byte, 16) + copy(ip, sa.Addr[:]) + return ip, IP6ZoneToString(int(sa.ZoneId)) + } + return nil, "" +} + +// SockaddrToIPAddr converts a syscall.Sockaddr to a net.IPAddr +// Returns nil if conversion fails. +func SockaddrToIPAddr(sa syscall.Sockaddr) *net.IPAddr { + ip, zone := SockaddrToIPAndZone(sa) + switch sa.(type) { + case *syscall.SockaddrInet4: + return &net.IPAddr{IP: ip} + case *syscall.SockaddrInet6: + return &net.IPAddr{IP: ip, Zone: zone} + } + return nil +} + +// SockaddrToTCPAddr converts a syscall.Sockaddr to a net.TCPAddr +// Returns nil if conversion fails. +func SockaddrToTCPAddr(sa syscall.Sockaddr) *net.TCPAddr { + ip, zone := SockaddrToIPAndZone(sa) + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + return &net.TCPAddr{IP: ip, Port: sa.Port} + case *syscall.SockaddrInet6: + return &net.TCPAddr{IP: ip, Port: sa.Port, Zone: zone} + } + return nil +} + +// SockaddrToUDPAddr converts a syscall.Sockaddr to a net.UDPAddr +// Returns nil if conversion fails. +func SockaddrToUDPAddr(sa syscall.Sockaddr) *net.UDPAddr { + ip, zone := SockaddrToIPAndZone(sa) + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + return &net.UDPAddr{IP: ip, Port: sa.Port} + case *syscall.SockaddrInet6: + return &net.UDPAddr{IP: ip, Port: sa.Port, Zone: zone} + } + return nil +} + +// from: go/src/pkg/net/unixsock_posix.go + +// SockaddrToUnixAddr converts a syscall.Sockaddr to a net.UnixAddr +// Returns nil if conversion fails. +func SockaddrToUnixAddr(sa syscall.Sockaddr) *net.UnixAddr { + if s, ok := sa.(*syscall.SockaddrUnix); ok { + return &net.UnixAddr{Name: s.Name, Net: "unix"} + } + return nil +} + +// SockaddrToUnixgramAddr converts a syscall.Sockaddr to a net.UnixAddr +// Returns nil if conversion fails. +func SockaddrToUnixgramAddr(sa syscall.Sockaddr) *net.UnixAddr { + if s, ok := sa.(*syscall.SockaddrUnix); ok { + return &net.UnixAddr{Name: s.Name, Net: "unixgram"} + } + return nil +} + +// SockaddrToUnixpacketAddr converts a syscall.Sockaddr to a net.UnixAddr +// Returns nil if conversion fails. +func SockaddrToUnixpacketAddr(sa syscall.Sockaddr) *net.UnixAddr { + if s, ok := sa.(*syscall.SockaddrUnix); ok { + return &net.UnixAddr{Name: s.Name, Net: "unixpacket"} + } + return nil +} + +// from: go/src/pkg/net/ipsock.go + +// IP6ZoneToString converts an IP6 Zone syscall int to a net string +// returns "" if zone is 0 +func IP6ZoneToString(zone int) string { + if zone == 0 { + return "" + } + if ifi, err := net.InterfaceByIndex(zone); err == nil { + return ifi.Name + } + return itod(uint(zone)) +} + +// IP6ZoneToInt converts an IP6 Zone net string to a syscall int +// returns 0 if zone is "" +func IP6ZoneToInt(zone string) int { + if zone == "" { + return 0 + } + if ifi, err := net.InterfaceByName(zone); err == nil { + return ifi.Index + } + n, _, _ := dtoi(zone, 0) + return n +} + +// from: go/src/pkg/net/parse.go + +// Convert i to decimal string. +func itod(i uint) string { + if i == 0 { + return "0" + } + + // Assemble decimal in reverse order. + var b [32]byte + bp := len(b) + for ; i > 0; i /= 10 { + bp-- + b[bp] = byte(i%10) + '0' + } + + return string(b[bp:]) +} + +// Bigger than we need, not too big to worry about overflow +const big = 0xFFFFFF + +// Decimal to integer starting at &s[i0]. +// Returns number, new offset, success. +func dtoi(s string, i0 int) (n int, i int, ok bool) { + n = 0 + for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return 0, i, false + } + } + if i == i0 { + return 0, i, false + } + return n, i, true +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_bsd.go b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_bsd.go new file mode 100644 index 00000000000..a38c2bd4848 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_bsd.go @@ -0,0 +1,24 @@ +// +build darwin dragonfly freebsd netbsd openbsd + +package sockaddrnet + +import ( + "syscall" +) + +const ( + AF_INET = syscall.AF_INET + AF_INET6 = syscall.AF_INET6 + AF_UNIX = syscall.AF_UNIX + AF_UNSPEC = syscall.AF_UNSPEC + + IPPROTO_IP = syscall.IPPROTO_IP + IPPROTO_IPV4 = syscall.IPPROTO_IPV4 + IPPROTO_IPV6 = syscall.IPPROTO_IPV6 + IPPROTO_TCP = syscall.IPPROTO_TCP + IPPROTO_UDP = syscall.IPPROTO_UDP + + SOCK_DGRAM = syscall.SOCK_DGRAM + SOCK_STREAM = syscall.SOCK_STREAM + SOCK_SEQPACKET = syscall.SOCK_SEQPACKET +) diff --git a/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_linux.go b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_linux.go new file mode 100644 index 00000000000..ddc4ec45a5b --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_linux.go @@ -0,0 +1,22 @@ +package sockaddrnet + +import ( + "syscall" +) + +const ( + AF_INET = syscall.AF_INET + AF_INET6 = syscall.AF_INET6 + AF_UNIX = syscall.AF_UNIX + AF_UNSPEC = syscall.AF_UNSPEC + + IPPROTO_IP = syscall.IPPROTO_IP + IPPROTO_IPV4 = syscall.IPPROTO_IPIP + IPPROTO_IPV6 = syscall.IPPROTO_IPV6 + IPPROTO_TCP = syscall.IPPROTO_TCP + IPPROTO_UDP = syscall.IPPROTO_UDP + + SOCK_DGRAM = syscall.SOCK_DGRAM + SOCK_STREAM = syscall.SOCK_STREAM + SOCK_SEQPACKET = syscall.SOCK_SEQPACKET +) diff --git a/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_windows.go b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_windows.go new file mode 100644 index 00000000000..6f54bfffc69 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net/net_windows.go @@ -0,0 +1,22 @@ +package sockaddrnet + +import ( + "syscall" +) + +const ( + AF_INET = syscall.AF_INET + AF_INET6 = syscall.AF_INET6 + AF_UNIX = syscall.AF_UNIX + AF_UNSPEC = syscall.AF_UNSPEC + + IPPROTO_IP = syscall.IPPROTO_IP + IPPROTO_IPV4 = syscall.IPPROTO_IPV4 + IPPROTO_IPV6 = syscall.IPPROTO_IPV6 + IPPROTO_TCP = syscall.IPPROTO_TCP + IPPROTO_UDP = syscall.IPPROTO_UDP + + SOCK_DGRAM = syscall.SOCK_DGRAM + SOCK_STREAM = syscall.SOCK_STREAM + SOCK_SEQPACKET = syscall.SOCK_SEQPACKET +) diff --git a/p2p/net/conn/dial.go b/p2p/net/conn/dial.go index a9df7d3c4db..fdebbc24bd8 100644 --- a/p2p/net/conn/dial.go +++ b/p2p/net/conn/dial.go @@ -2,12 +2,17 @@ package conn import ( "fmt" + "math/rand" + "net" "strings" + "syscall" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" + reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport" + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr" peer "github.com/jbenet/go-ipfs/p2p/peer" debugerror "github.com/jbenet/go-ipfs/util/debugerror" ) @@ -22,32 +27,7 @@ func (d *Dialer) String() string { // Example: d.DialAddr(ctx, peer.Addresses()[0], peer) func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (Conn, error) { - _, _, err := manet.DialArgs(raddr) - if err != nil { - return nil, err - } - - if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") { - return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr) - } - - if len(d.LocalAddrs) > 0 { - laddrs := manet.AddrMatch(raddr, d.LocalAddrs) - if len(laddrs) < 1 { - return nil, debugerror.Errorf("No local address matches %s %s", raddr, d.LocalAddrs) - } - - // TODO pick with a good heuristic - // we use a random one for now to prevent bad addresses from making nodes unreachable - // with a random selection, multiple tries may work. - // laddr := laddrs[rand.Intn(len(laddrs))] - - // TODO: try to get reusing addr/ports to work. - // d.Dialer.LocalAddr = laddr - } - - log.Debugf("%s dialing %s %s", d.LocalPeer, remote, raddr) - maconn, err := d.Dialer.Dial(raddr) + maconn, err := d.rawConnDial(ctx, raddr, remote) if err != nil { return nil, err } @@ -92,6 +72,128 @@ func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) ( return connOut, errOut } +// rawConnDial dials the underlying net.Conn + manet.Conns +func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (manet.Conn, error) { + + // before doing anything, check we're going to be able to dial. + // we may not support the given address. + if _, _, err := manet.DialArgs(raddr); err != nil { + return nil, err + } + + if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") { + return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr) + } + + // get local addr to use. + laddr := pickLocalAddr(d.LocalAddrs, raddr) + log.Debugf("%s dialing %s -- %s --> %s", d.LocalPeer, remote, laddr, raddr) + + if laddr != nil { + // dial using reuseport.Dialer, because we're probably reusing addrs. + // this is optimistic, as the reuseDial may fail to bind the port. + if nconn, retry, reuseErr := d.reuseDial(laddr, raddr); reuseErr == nil { + // if it worked, wrap the raw net.Conn with our manet.Conn + log.Debugf("%s reuse worked! %s %s %s", d.LocalPeer, laddr, nconn.RemoteAddr(), nconn) + return manet.WrapNetConn(nconn) + } else if !retry { + // reuseDial is sure this is a legitimate dial failure, not a reuseport failure. + return nil, reuseErr + } else { + // this is a failure to reuse port. log it. + log.Debugf("%s port reuse failed: %s --> %s -- %s", d.LocalPeer, laddr, raddr, reuseErr) + } + } + + // no local addr, or reuseport failed. just dial straight with a new port. + return d.Dialer.Dial(raddr) +} + +func (d *Dialer) reuseDial(laddr, raddr ma.Multiaddr) (conn net.Conn, retry bool, err error) { + if laddr == nil { + // if we're given no local address no sense in using reuseport to dial, dial out as usual. + return nil, true, reuseport.ErrReuseFailed + } + + // give reuse.Dialer the manet.Dialer's Dialer. + // (wow, Dialer should've so been an interface...) + rd := reuseport.Dialer{d.Dialer.Dialer} + + // get the local net.Addr manually + rd.D.LocalAddr, err = manet.ToNetAddr(laddr) + if err != nil { + return nil, true, err // something wrong with laddr. retry without. + } + + // get the raddr dial args for rd.dial + network, netraddr, err := manet.DialArgs(raddr) + if err != nil { + return nil, true, err // something wrong with laddr. retry without. + } + + // rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set. + conn, err = rd.Dial(network, netraddr) + return conn, reuseErrShouldRetry(err), err // hey! it worked! +} + +// reuseErrShouldRetry diagnoses whether to retry after a reuse error. +// if we failed to bind, we should retry. if bind worked and this is a +// real dial error (remote end didnt answer) then we should not retry. +func reuseErrShouldRetry(err error) bool { + if err == nil { + return false // hey, it worked! no need to retry. + } + + errno, ok := err.(syscall.Errno) + if !ok { // not an errno? who knows what this is. retry. + return true + } + + switch errno { + case syscall.EADDRINUSE, syscall.EADDRNOTAVAIL: + return true // failure to bind. retry. + case syscall.ECONNREFUSED: + return false // real dial error + default: + return true // optimistically default to retry. + } +} + +func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiaddr) { + if len(laddrs) < 1 { + return nil + } + + // make sure that we ONLY use local addrs that match the remote addr. + laddrs = manet.AddrMatch(raddr, laddrs) + if len(laddrs) < 1 { + return nil + } + + // make sure that we ONLY use local addrs that CAN dial the remote addr. + // filter out all the local addrs that aren't capable + raddrIPLayer := ma.Split(raddr)[0] + raddrIsLoopback := manet.IsIPLoopback(raddrIPLayer) + raddrIsLinkLocal := manet.IsIP6LinkLocal(raddrIPLayer) + laddrs = addrutil.FilterAddrs(laddrs, func(a ma.Multiaddr) bool { + laddrIPLayer := ma.Split(a)[0] + laddrIsLoopback := manet.IsIPLoopback(laddrIPLayer) + laddrIsLinkLocal := manet.IsIP6LinkLocal(laddrIPLayer) + if laddrIsLoopback { // our loopback addrs can only dial loopbacks. + return raddrIsLoopback + } + if laddrIsLinkLocal { + return raddrIsLinkLocal // out linklocal addrs can only dial link locals. + } + return true + }) + + // TODO pick with a good heuristic + // we use a random one for now to prevent bad addresses from making nodes unreachable + // with a random selection, multiple tries may work. + return laddrs[rand.Intn(len(laddrs))] +} + // MultiaddrProtocolsMatch returns whether two multiaddrs match in protocol stacks. func MultiaddrProtocolsMatch(a, b ma.Multiaddr) bool { ap := a.Protocols() diff --git a/p2p/net/conn/listen.go b/p2p/net/conn/listen.go index 32011651e1e..05d3c229e1b 100644 --- a/p2p/net/conn/listen.go +++ b/p2p/net/conn/listen.go @@ -9,7 +9,9 @@ import ( ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" + reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport" tec "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher" + ic "github.com/jbenet/go-ipfs/p2p/crypto" peer "github.com/jbenet/go-ipfs/p2p/peer" ) @@ -123,11 +125,23 @@ func (l *listener) Loggable() map[string]interface{} { // Listen listens on the particular multiaddr, with given peer and peerstore. func Listen(ctx context.Context, addr ma.Multiaddr, local peer.ID, sk ic.PrivKey) (Listener, error) { - ml, err := manet.Listen(addr) + network, naddr, err := manet.DialArgs(addr) + if err != nil { + return nil, err + } + + // _ := reuseport.Listen + // ml, err := manet.Listen(addr) + nl, err := reuseport.Listen(network, naddr) if err != nil { return nil, fmt.Errorf("Failed to listen on %s: %s", addr, err) } + ml, err := manet.WrapNetListener(nl) + if err != nil { + return nil, err + } + l := &listener{ Listener: ml, local: local, diff --git a/p2p/net/swarm/swarm_dial.go b/p2p/net/swarm/swarm_dial.go index 4528c2c4d91..c6160b4762b 100644 --- a/p2p/net/swarm/swarm_dial.go +++ b/p2p/net/swarm/swarm_dial.go @@ -251,8 +251,10 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) { log.Warning("Dial not given PrivateKey, so WILL NOT SECURE conn.") } - // get our own addrs - localAddrs := s.peers.Addresses(s.local) + // get our own addrs. try dialing out from our listener addresses (reusing ports) + // Note that using our peerstore's addresses here is incorrect, as that would + // include observed addresses. TODO: make peerstore's address book smarter. + localAddrs := s.ListenAddresses() if len(localAddrs) == 0 { log.Debug("Dialing out with no local addresses.") } diff --git a/p2p/protocol/identify/id.go b/p2p/protocol/identify/id.go index 6065f0eec42..3a0012287e9 100644 --- a/p2p/protocol/identify/id.go +++ b/p2p/protocol/identify/id.go @@ -163,7 +163,9 @@ func (ids *IDService) consumeMessage(mes *pb.Identify, c inet.Conn) { p := c.RemotePeer() // mes.Protocols + // mes.ObservedAddr + ids.consumeObservedAddress(mes.GetObservedAddr(), c) // mes.ListenAddrs laddrs := mes.GetListenAddrs() @@ -208,3 +210,44 @@ func (ids *IDService) IdentifyWait(c inet.Conn) <-chan struct{} { close(ch) return ch } + +func (ids *IDService) consumeObservedAddress(observed []byte, c inet.Conn) { + if observed == nil { + return + } + + maddr, err := ma.NewMultiaddrBytes(observed) + if err != nil { + log.Debugf("error parsing received observed addr for %s: %s", c, err) + return + } + + // we should only use ObservedAddr when our connection's LocalAddr is one + // of our ListenAddrs. If we Dial out using an ephemeral addr, knowing that + // address's external mapping is not very useful because the port will not be + // the same as the listen addr. + ifaceaddrs, err := ids.Host.Network().InterfaceListenAddresses() + if err != nil { + log.Infof("failed to get interface listen addrs", err) + return + } + + log.Debugf("identify identifying observed multiaddr: %s %s", c.LocalMultiaddr(), ifaceaddrs) + if !addrInAddrs(c.LocalMultiaddr(), ifaceaddrs) { + // not in our list + return + } + + // ok! we have the observed version of one of our ListenAddresses! + log.Debugf("added own observed listen addr: %s --> %s", c.LocalMultiaddr(), maddr) + ids.Host.Peerstore().AddAddress(ids.Host.ID(), maddr) +} + +func addrInAddrs(a ma.Multiaddr, as []ma.Multiaddr) bool { + for _, b := range as { + if a.Equal(b) { + return true + } + } + return false +} diff --git a/p2p/test/reconnects/reconnect_test.go b/p2p/test/reconnects/reconnect_test.go index 9dcd0ed1b7c..373fc219ba5 100644 --- a/p2p/test/reconnects/reconnect_test.go +++ b/p2p/test/reconnects/reconnect_test.go @@ -111,7 +111,7 @@ func TestReconnect2(t *testing.T) { h1.SetStreamHandler(protocol.TestingID, EchoStreamHandler) h2.SetStreamHandler(protocol.TestingID, EchoStreamHandler) - rounds := 10 + rounds := 8 if testing.Short() { rounds = 4 } @@ -137,9 +137,9 @@ func TestReconnect5(t *testing.T) { h4.SetStreamHandler(protocol.TestingID, EchoStreamHandler) h5.SetStreamHandler(protocol.TestingID, EchoStreamHandler) - rounds := 10 + rounds := 4 if testing.Short() { - rounds = 4 + rounds = 2 } for i := 0; i < rounds; i++ { log.Debugf("TestReconnect: %d/%d\n", i, rounds)