Skip to content

Commit

Permalink
reuse flag with comment
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasen committed Dec 3, 2015
1 parent aef42dd commit 2e5a4c7
Show file tree
Hide file tree
Showing 20 changed files with 1,585 additions and 1 deletion.
10 changes: 9 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"testing"
"time"

"github.com/jbenet/go-reuseport"
"github.com/xindong/frontd/aes256cbc"
"github.com/xindong/frontd/reuse"
)

var (
Expand All @@ -26,11 +26,19 @@ var (
)

var (
// use -reuse with go test enable SO_REUSEPORT
// go test -parallel 6553 -benchtime 60s -bench BenchmarkEchoParallel -reuse
// but it seems will not working with single backend addr because of
// http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t
reuseTest = flag.Bool("reuse", false, "test reuseport dialer")
)

func TestMain(m *testing.M) {
flag.Parse()
if *reuseTest {
fmt.Println("testing SO_REUSEPORT")
}

// start echo server
go servEcho()

Expand Down
10 changes: 10 additions & 0 deletions reuse/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
language: go

go:
- 1.3
- 1.4
- release
- tip

script:
- go test -race -cpu=5 -v ./...
13 changes: 13 additions & 0 deletions reuse/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
42 changes: 42 additions & 0 deletions reuse/README.md
Original file line number Diff line number Diff line change
@@ -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`.
20 changes: 20 additions & 0 deletions reuse/addr.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
91 changes: 91 additions & 0 deletions reuse/available_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// +build darwin freebsd dragonfly netbsd openbsd linux

package reuseport

import (
"sync"
"sync/atomic"
"syscall"
"time"
)

// checker is a struct to gather the availability check fields + funcs.
// we use atomic ints because this is potentially a really hot function call.
type checkerT struct {
avail int32 // atomic int managed by set/isAvailable()
check int32 // atomic int managed by has/checked()
mu sync.Mutex // synchonizes the actual check
}

// the static location of the vars.
var checker checkerT

func (c *checkerT) isAvailable() bool {
return atomic.LoadInt32(&c.avail) != 0
}

func (c *checkerT) setIsAvailable(b bool) {
if b {
atomic.StoreInt32(&c.avail, 1)
} else {
atomic.StoreInt32(&c.avail, 0)
}
}

func (c *checkerT) hasChecked() bool {
return atomic.LoadInt32(&c.check) != 0
}

func (c *checkerT) setHasChecked(b bool) {
if b {
atomic.StoreInt32(&c.check, 1)
} else {
atomic.StoreInt32(&c.check, 0)
}
}

// Available returns whether or not SO_REUSEPORT is available in the OS.
// It does so by attepting to open a tcp listener, setting the option, and
// checking ENOPROTOOPT on error. After checking, the decision is cached
// for the rest of the process run.
func available() bool {
if checker.hasChecked() {
return checker.isAvailable()
}

// synchronize, only one should check
checker.mu.Lock()
defer checker.mu.Unlock()

// we blocked. someone may have been gotten this.
if checker.hasChecked() {
return checker.isAvailable()
}

// there may be fluke reasons to fail to add a listener.
// so we give it 5 shots. if not, give up and call it not avail.
for i := 0; i < 5; i++ {
// try to listen at tcp port 0.
l, err := listenStream("tcp", "127.0.0.1:0")
if err == nil {
// no error? available.
checker.setIsAvailable(true)
checker.setHasChecked(true)
l.Close() // Go back to the Shadow!
return true
}

if errno, ok := err.(syscall.Errno); ok {
if errno == syscall.ENOPROTOOPT {
break // :( that's all folks.
}
}

// not an errno? or not ENOPROTOOPT? retry.
<-time.After(20 * time.Millisecond) // wait a bit
}

checker.setIsAvailable(false)
checker.setHasChecked(true)
return false
}
10 changes: 10 additions & 0 deletions reuse/const_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build darwin freebsd dragonfly netbsd openbsd

package reuseport

import (
"syscall"
)

var soReusePort = syscall.SO_REUSEPORT
var soReuseAddr = syscall.SO_REUSEADDR
10 changes: 10 additions & 0 deletions reuse/const_linux.go
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 2e5a4c7

Please sign in to comment.