This repository has been archived by the owner on May 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from libp2p/implementation
NAT Auto Discovery
- Loading branch information
Showing
15 changed files
with
2,172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
os: | ||
- linux | ||
|
||
sudo: false | ||
|
||
language: go | ||
|
||
go: | ||
- 1.9.x | ||
|
||
install: | ||
- make deps | ||
|
||
script: | ||
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh) | ||
|
||
cache: | ||
directories: | ||
- $GOPATH/src/gx | ||
|
||
notifications: | ||
email: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
gx: | ||
go get -u github.com/whyrusleeping/gx | ||
go get -u github.com/whyrusleeping/gx-go | ||
|
||
deps: gx | ||
gx --verbose install --global | ||
gx-go rewrite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# go-libp2p-discovery | ||
|
||
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) | ||
[![](https://img.shields.io/badge/project-libp2p-blue.svg?style=flat-square)](http://libp2p.io/) | ||
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) | ||
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) | ||
|
||
> Ambient NAT discovery | ||
This package provides an ambient NAT autodiscovery service. | ||
It allows peers to figure out their NAT dialability situation by using test dial backs through peers providing the AutoNAT service. | ||
|
||
## Documenation | ||
|
||
See https://godoc.org/github.com/libp2p/go-libp2p-discovery. | ||
|
||
## Contribute | ||
|
||
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/go-libp2p-discovery/issues)! | ||
|
||
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package autonat | ||
|
||
import ( | ||
"net" | ||
|
||
ma "github.com/multiformats/go-multiaddr" | ||
) | ||
|
||
var private4, private6 []*net.IPNet | ||
var privateCIDR4 = []string{ | ||
// localhost | ||
"127.0.0.0/8", | ||
// private networks | ||
"10.0.0.0/8", | ||
"100.64.0.0/10", | ||
"172.16.0.0/12", | ||
"192.168.0.0/16", | ||
// link local | ||
"169.254.0.0/16", | ||
} | ||
var privateCIDR6 = []string{ | ||
// localhost | ||
"::1/128", | ||
// ULA reserved | ||
"fc00::/7", | ||
// link local | ||
"fe80::/10", | ||
} | ||
|
||
func init() { | ||
private4 = parsePrivateCIDR(privateCIDR4) | ||
private6 = parsePrivateCIDR(privateCIDR6) | ||
} | ||
|
||
func parsePrivateCIDR(cidrs []string) []*net.IPNet { | ||
ipnets := make([]*net.IPNet, len(cidrs)) | ||
for i, cidr := range cidrs { | ||
_, ipnet, err := net.ParseCIDR(cidr) | ||
if err != nil { | ||
panic(err) | ||
} | ||
ipnets[i] = ipnet | ||
} | ||
return ipnets | ||
} | ||
|
||
func isPublicAddr(a ma.Multiaddr) bool { | ||
ip, err := a.ValueForProtocol(ma.P_IP4) | ||
if err == nil { | ||
return !inAddrRange(ip, private4) | ||
} | ||
|
||
ip, err = a.ValueForProtocol(ma.P_IP6) | ||
if err == nil { | ||
return !inAddrRange(ip, private6) | ||
} | ||
|
||
return false | ||
} | ||
|
||
func inAddrRange(s string, ipnets []*net.IPNet) bool { | ||
ip := net.ParseIP(s) | ||
for _, ipnet := range ipnets { | ||
if ipnet.Contains(ip) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package autonat | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"math/rand" | ||
"sync" | ||
"time" | ||
|
||
host "github.com/libp2p/go-libp2p-host" | ||
inet "github.com/libp2p/go-libp2p-net" | ||
peer "github.com/libp2p/go-libp2p-peer" | ||
ma "github.com/multiformats/go-multiaddr" | ||
) | ||
|
||
// NATStatus is the state of NAT as detected by the ambient service. | ||
type NATStatus int | ||
|
||
const ( | ||
// NAT status is unknown; this means that the ambient service has not been | ||
// able to decide the presence of NAT in the most recent attempt to test | ||
// dial through known autonat peers. initial state. | ||
NATStatusUnknown NATStatus = iota | ||
// NAT status is publicly dialable | ||
NATStatusPublic | ||
// NAT status is private network | ||
NATStatusPrivate | ||
) | ||
|
||
var ( | ||
AutoNATBootDelay = 15 * time.Second | ||
AutoNATRetryInterval = 90 * time.Second | ||
AutoNATRefreshInterval = 15 * time.Minute | ||
AutoNATRequestTimeout = 60 * time.Second | ||
) | ||
|
||
// AutoNAT is the interface for ambient NAT autodiscovery | ||
type AutoNAT interface { | ||
// Status returns the current NAT status | ||
Status() NATStatus | ||
// PublicAddr returns the public dial address when NAT status is public and an | ||
// error otherwise | ||
PublicAddr() (ma.Multiaddr, error) | ||
} | ||
|
||
// AmbientAutoNAT is the implementation of ambient NAT autodiscovery | ||
type AmbientAutoNAT struct { | ||
ctx context.Context | ||
host host.Host | ||
|
||
mx sync.Mutex | ||
peers map[peer.ID]struct{} | ||
status NATStatus | ||
addr ma.Multiaddr | ||
// Reflects the confidence on of the NATStatus being private, as a single | ||
// dialback may fail for reasons unrelated to NAT. | ||
// If it is <3, then multiple autoNAT peers may be contacted for dialback | ||
// If only a single autoNAT peer is known, then the confidence increases | ||
// for each failure until it reaches 3. | ||
confidence int | ||
} | ||
|
||
// NewAutoNAT creates a new ambient NAT autodiscovery instance attached to a host | ||
func NewAutoNAT(ctx context.Context, h host.Host) AutoNAT { | ||
as := &AmbientAutoNAT{ | ||
ctx: ctx, | ||
host: h, | ||
peers: make(map[peer.ID]struct{}), | ||
status: NATStatusUnknown, | ||
} | ||
|
||
h.Network().Notify(as) | ||
go as.background() | ||
|
||
return as | ||
} | ||
|
||
func (as *AmbientAutoNAT) Status() NATStatus { | ||
return as.status | ||
} | ||
|
||
func (as *AmbientAutoNAT) PublicAddr() (ma.Multiaddr, error) { | ||
as.mx.Lock() | ||
defer as.mx.Unlock() | ||
|
||
if as.status != NATStatusPublic { | ||
return nil, errors.New("NAT Status is not public") | ||
} | ||
|
||
return as.addr, nil | ||
} | ||
|
||
func (as *AmbientAutoNAT) background() { | ||
// wait a bit for the node to come online and establish some connections | ||
// before starting autodetection | ||
select { | ||
case <-time.After(AutoNATBootDelay): | ||
case <-as.ctx.Done(): | ||
return | ||
} | ||
|
||
for { | ||
as.autodetect() | ||
|
||
delay := AutoNATRefreshInterval | ||
if as.status == NATStatusUnknown { | ||
delay = AutoNATRetryInterval | ||
} | ||
|
||
select { | ||
case <-time.After(delay): | ||
case <-as.ctx.Done(): | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (as *AmbientAutoNAT) autodetect() { | ||
peers := as.getPeers() | ||
|
||
if len(peers) == 0 { | ||
log.Debugf("skipping NAT auto detection; no autonat peers") | ||
return | ||
} | ||
|
||
cli := NewAutoNATClient(as.host) | ||
failures := 0 | ||
|
||
for _, p := range peers { | ||
ctx, cancel := context.WithTimeout(as.ctx, AutoNATRequestTimeout) | ||
a, err := cli.DialBack(ctx, p) | ||
cancel() | ||
|
||
switch { | ||
case err == nil: | ||
log.Debugf("NAT status is public; address through %s: %s", p.Pretty(), a.String()) | ||
as.mx.Lock() | ||
as.addr = a | ||
as.status = NATStatusPublic | ||
as.confidence = 0 | ||
as.mx.Unlock() | ||
return | ||
|
||
case IsDialError(err): | ||
log.Debugf("dial error through %s: %s", p.Pretty(), err.Error()) | ||
failures++ | ||
if failures >= 3 || as.confidence >= 3 { // 3 times is enemy action | ||
log.Debugf("NAT status is private") | ||
as.mx.Lock() | ||
as.status = NATStatusPrivate | ||
as.confidence = 3 | ||
as.mx.Unlock() | ||
return | ||
} | ||
|
||
default: | ||
log.Debugf("Error dialing through %s: %s", p.Pretty(), err.Error()) | ||
} | ||
} | ||
|
||
as.mx.Lock() | ||
if failures > 0 { | ||
as.status = NATStatusPrivate | ||
as.confidence++ | ||
log.Debugf("NAT status is private") | ||
} else { | ||
as.status = NATStatusUnknown | ||
as.confidence = 0 | ||
log.Debugf("NAT status is unknown") | ||
} | ||
as.mx.Unlock() | ||
} | ||
|
||
func (as *AmbientAutoNAT) getPeers() []peer.ID { | ||
as.mx.Lock() | ||
defer as.mx.Unlock() | ||
|
||
if len(as.peers) == 0 { | ||
return nil | ||
} | ||
|
||
var connected, others []peer.ID | ||
|
||
for p := range as.peers { | ||
if as.host.Network().Connectedness(p) == inet.Connected { | ||
connected = append(connected, p) | ||
} else { | ||
others = append(others, p) | ||
} | ||
} | ||
|
||
shufflePeers(connected) | ||
|
||
if len(connected) < 3 { | ||
shufflePeers(others) | ||
return append(connected, others...) | ||
} else { | ||
return connected | ||
} | ||
} | ||
|
||
func shufflePeers(peers []peer.ID) { | ||
for i := range peers { | ||
j := rand.Intn(i + 1) | ||
peers[i], peers[j] = peers[j], peers[i] | ||
} | ||
} |
Oops, something went wrong.