mirror of https://github.com/libp2p/go-libp2p.git
vyzo
6 years ago
3 changed files with 282 additions and 0 deletions
@ -0,0 +1,252 @@ |
|||
package relay |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"sync" |
|||
"time" |
|||
|
|||
basic "github.com/libp2p/go-libp2p/p2p/host/basic" |
|||
|
|||
autonat "github.com/libp2p/go-libp2p-autonat" |
|||
discovery "github.com/libp2p/go-libp2p-discovery" |
|||
host "github.com/libp2p/go-libp2p-host" |
|||
inet "github.com/libp2p/go-libp2p-net" |
|||
peer "github.com/libp2p/go-libp2p-peer" |
|||
pstore "github.com/libp2p/go-libp2p-peerstore" |
|||
ma "github.com/multiformats/go-multiaddr" |
|||
manet "github.com/multiformats/go-multiaddr-net" |
|||
) |
|||
|
|||
var DesiredRelays = 3 |
|||
|
|||
// AutoRelayHost is a Host that uses relays for connectivity when a NAT is detected.
|
|||
type AutoRelayHost struct { |
|||
*basic.BasicHost |
|||
discover discovery.Discoverer |
|||
autonat autonat.AutoNAT |
|||
addrsF basic.AddrsFactory |
|||
|
|||
disconnect chan struct{} |
|||
|
|||
mx sync.Mutex |
|||
relays map[peer.ID]pstore.PeerInfo |
|||
addrs []ma.Multiaddr |
|||
} |
|||
|
|||
func NewAutoRelayHost(ctx context.Context, bhost *basic.BasicHost, discover discovery.Discoverer) *AutoRelayHost { |
|||
autonat := autonat.NewAutoNAT(ctx, bhost) |
|||
h := &AutoRelayHost{ |
|||
BasicHost: bhost, |
|||
discover: discover, |
|||
autonat: autonat, |
|||
addrsF: bhost.AddrsFactory, |
|||
relays: make(map[peer.ID]pstore.PeerInfo), |
|||
disconnect: make(chan struct{}, 1), |
|||
} |
|||
bhost.AddrsFactory = h.hostAddrs |
|||
bhost.Network().Notify(h) |
|||
go h.background(ctx) |
|||
return h |
|||
} |
|||
|
|||
func (h *AutoRelayHost) hostAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { |
|||
h.mx.Lock() |
|||
defer h.mx.Unlock() |
|||
if h.addrs != nil && h.autonat.Status() == autonat.NATStatusPrivate { |
|||
return h.addrs |
|||
} else { |
|||
return h.addrsF(addrs) |
|||
} |
|||
} |
|||
|
|||
func (h *AutoRelayHost) background(ctx context.Context) { |
|||
select { |
|||
case <-time.After(autonat.AutoNATBootDelay + 30*time.Second): |
|||
case <-ctx.Done(): |
|||
return |
|||
} |
|||
|
|||
for { |
|||
wait := autonat.AutoNATRefreshInterval |
|||
switch h.autonat.Status() { |
|||
case autonat.NATStatusUnknown: |
|||
wait = autonat.AutoNATRetryInterval |
|||
case autonat.NATStatusPublic: |
|||
case autonat.NATStatusPrivate: |
|||
h.findRelays(ctx) |
|||
} |
|||
|
|||
select { |
|||
case <-h.disconnect: |
|||
// invalidate addrs
|
|||
h.mx.Lock() |
|||
h.addrs = nil |
|||
h.mx.Unlock() |
|||
case <-time.After(wait): |
|||
case <-ctx.Done(): |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (h *AutoRelayHost) findRelays(ctx context.Context) { |
|||
h.mx.Lock() |
|||
if len(h.relays) >= DesiredRelays { |
|||
h.mx.Unlock() |
|||
return |
|||
} |
|||
need := DesiredRelays - len(h.relays) |
|||
h.mx.Unlock() |
|||
|
|||
limit := 20 |
|||
for ; need > limit; limit *= 2 { |
|||
} |
|||
|
|||
dctx, cancel := context.WithTimeout(ctx, 60*time.Second) |
|||
pis, err := discovery.FindPeers(dctx, h.discover, "/libp2p/relay", limit) |
|||
cancel() |
|||
if err != nil { |
|||
log.Debugf("error discovering relays: %s", err.Error()) |
|||
return |
|||
} |
|||
|
|||
// TODO better relay selection strategy; this just selects random relays
|
|||
// but we should probably use ping latency as the selection metric
|
|||
shuffleRelays(pis) |
|||
|
|||
update := 0 |
|||
|
|||
for _, pi := range pis { |
|||
h.mx.Lock() |
|||
if _, ok := h.relays[pi.ID]; ok { |
|||
h.mx.Unlock() |
|||
continue |
|||
} |
|||
h.mx.Unlock() |
|||
|
|||
cctx, cancel := context.WithTimeout(ctx, 60*time.Second) |
|||
err = h.Connect(cctx, pi) |
|||
cancel() |
|||
if err != nil { |
|||
log.Debugf("error connecting to relay %s: %s", pi.ID, err.Error()) |
|||
continue |
|||
} |
|||
|
|||
log.Debugf("connected to relay %s", pi.ID) |
|||
h.mx.Lock() |
|||
h.relays[pi.ID] = pi |
|||
h.mx.Unlock() |
|||
|
|||
update++ |
|||
need-- |
|||
if need == 0 { |
|||
break |
|||
} |
|||
} |
|||
|
|||
if update > 0 || h.addrs == nil { |
|||
h.updateAddrs() |
|||
} |
|||
} |
|||
|
|||
func (h *AutoRelayHost) updateAddrs() { |
|||
h.doUpdateAddrs() |
|||
h.PushIdentify() |
|||
} |
|||
|
|||
func (h *AutoRelayHost) doUpdateAddrs() { |
|||
h.mx.Lock() |
|||
defer h.mx.Unlock() |
|||
|
|||
addrs := h.addrsF(h.AllAddrs()) |
|||
raddrs := make([]ma.Multiaddr, 0, len(addrs)+len(h.relays)) |
|||
|
|||
// remove our public addresses from the list and replace them by just the public IP
|
|||
for _, addr := range addrs { |
|||
if manet.IsPublicAddr(addr) { |
|||
ip, err := addr.ValueForProtocol(ma.P_IP4) |
|||
if err == nil { |
|||
pub, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s", ip)) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
if !containsAddr(raddrs, pub) { |
|||
raddrs = append(raddrs, pub) |
|||
} |
|||
continue |
|||
} |
|||
|
|||
ip, err = addr.ValueForProtocol(ma.P_IP6) |
|||
if err == nil { |
|||
pub, err := ma.NewMultiaddr(fmt.Sprintf("/ip6/%s", ip)) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
if !containsAddr(raddrs, pub) { |
|||
raddrs = append(raddrs, pub) |
|||
} |
|||
continue |
|||
} |
|||
} else { |
|||
raddrs = append(raddrs, addr) |
|||
} |
|||
} |
|||
|
|||
circuit, err := ma.NewMultiaddr("/p2p-circuit") |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
for _, pi := range h.relays { |
|||
for _, addr := range pi.Addrs { |
|||
if !manet.IsPrivateAddr(addr) { |
|||
pub := addr.Encapsulate(circuit) |
|||
raddrs = append(raddrs, pub) |
|||
} |
|||
} |
|||
} |
|||
|
|||
h.addrs = raddrs |
|||
} |
|||
|
|||
func shuffleRelays(pis []pstore.PeerInfo) { |
|||
for i := range pis { |
|||
j := rand.Intn(i + 1) |
|||
pis[i], pis[j] = pis[j], pis[i] |
|||
} |
|||
} |
|||
|
|||
func containsAddr(lst []ma.Multiaddr, addr ma.Multiaddr) bool { |
|||
for _, xaddr := range lst { |
|||
if xaddr.Equal(addr) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
// notify
|
|||
func (h *AutoRelayHost) Listen(inet.Network, ma.Multiaddr) {} |
|||
func (h *AutoRelayHost) ListenClose(inet.Network, ma.Multiaddr) {} |
|||
func (h *AutoRelayHost) Connected(inet.Network, inet.Conn) {} |
|||
|
|||
func (h *AutoRelayHost) Disconnected(_ inet.Network, c inet.Conn) { |
|||
p := c.RemotePeer() |
|||
h.mx.Lock() |
|||
defer h.mx.Unlock() |
|||
if _, ok := h.relays[p]; ok { |
|||
delete(h.relays, p) |
|||
select { |
|||
case h.disconnect <- struct{}{}: |
|||
default: |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (h *AutoRelayHost) OpenedStream(inet.Network, inet.Stream) {} |
|||
func (h *AutoRelayHost) ClosedStream(inet.Network, inet.Stream) {} |
|||
|
|||
var _ host.Host = (*AutoRelayHost)(nil) |
@ -0,0 +1,7 @@ |
|||
package relay |
|||
|
|||
import ( |
|||
logging "github.com/ipfs/go-log" |
|||
) |
|||
|
|||
var log = logging.Logger("relay") |
@ -0,0 +1,23 @@ |
|||
package relay |
|||
|
|||
import ( |
|||
"context" |
|||
|
|||
discovery "github.com/libp2p/go-libp2p-discovery" |
|||
host "github.com/libp2p/go-libp2p-host" |
|||
) |
|||
|
|||
// RelayHost is a Host that provides Relay services.
|
|||
type RelayHost struct { |
|||
host.Host |
|||
advertise discovery.Advertiser |
|||
} |
|||
|
|||
// New constructs a new RelayHost
|
|||
func NewRelayHost(ctx context.Context, host host.Host, advertise discovery.Advertiser) *RelayHost { |
|||
h := &RelayHost{Host: host, advertise: advertise} |
|||
discovery.Advertise(ctx, advertise, "/libp2p/relay") |
|||
return h |
|||
} |
|||
|
|||
var _ host.Host = (*RelayHost)(nil) |
Loading…
Reference in new issue