-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't hole punch if either peer is behind a Symmetric NAT #1046
Changes from 45 commits
78ce16b
0b45239
3566bfd
29d4987
5484ccb
1537769
c2295d5
2296117
d337499
1956fea
cfef141
10adf33
ef26854
ae61f65
0b0b410
90cd9bb
2603920
e868d73
431a9a6
fa9994c
001aa52
14ef1ec
450d431
36ca4a7
1bdb86c
1d6d07f
5ecac86
d9d7e6a
dcf822c
2ee5344
46ef4d8
749086f
37141ec
5bf6a8f
837edb0
35b7b8a
9482582
eddb65c
6ec9163
d0d527c
651ae86
9b483aa
3e9dc38
ea96e13
792ff75
6efab25
00bd3a7
48350b4
9267948
b553c16
f981b25
a3fee65
9b67b75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
|
||
logging "github.com/ipfs/go-log" | ||
"github.com/jpillora/backoff" | ||
"github.com/libp2p/go-libp2p-core/event" | ||
"github.com/libp2p/go-libp2p-core/host" | ||
"github.com/libp2p/go-libp2p-core/network" | ||
"github.com/libp2p/go-libp2p-core/peer" | ||
|
@@ -49,11 +50,55 @@ func NewHolePunchService(h host.Host, ids *identify.IDService) (*HolePunchServic | |
ctx, cancel := context.WithCancel(context.Background()) | ||
hs := &HolePunchService{ctx: ctx, ctxCancel: cancel, host: h, ids: ids} | ||
|
||
sub, err := h.EventBus().Subscribe(new(event.EvtNATDeviceTypeChanged)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
h.SetStreamHandler(protocol, hs.handleNewStream) | ||
h.Network().Notify((*netNotifiee)(hs)) | ||
|
||
hs.refCount.Add(1) | ||
go hs.loop(sub) | ||
|
||
return hs, nil | ||
} | ||
|
||
func (hs *HolePunchService) loop(sub event.Subscription) { | ||
defer hs.refCount.Done() | ||
defer sub.Close() | ||
|
||
for { | ||
select { | ||
// Our local NAT device types are intialized in the peerstore when the Host is created | ||
// and updated in the peerstore by the Observed Address Manager. | ||
case _, ok := <-sub.Out(): | ||
if !ok { | ||
return | ||
} | ||
|
||
if hs.peerSupportsHolePunching(hs.host.ID(), hs.host.Addrs()) { | ||
hs.host.SetStreamHandler(protocol, hs.handleNewStream) | ||
} else { | ||
hs.host.RemoveStreamHandler(protocol) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wait, this is a BUG -- we lose the ability to dial back connect to a public node that has dialed us. |
||
} | ||
|
||
case <-hs.ctx.Done(): | ||
return | ||
} | ||
} | ||
} | ||
|
||
func hasProtoAddr(protocCode int, addrs []ma.Multiaddr) bool { | ||
for _, a := range addrs { | ||
if _, err := a.ValueForProtocol(protocCode); err == nil { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// Close closes the Hole Punch Service. | ||
func (hs *HolePunchService) Close() error { | ||
hs.closeSync.Do(func() { | ||
|
@@ -84,6 +129,12 @@ func (hs *HolePunchService) holePunch(relayConn network.Conn) { | |
} | ||
} | ||
|
||
// return if either peer does NOT support hole punching | ||
if !hs.peerSupportsHolePunching(rp, hs.host.Peerstore().Addrs(rp)) || | ||
!hs.peerSupportsHolePunching(hs.host.ID(), hs.host.Addrs()) { | ||
return | ||
} | ||
|
||
// hole punch | ||
hpCtx := network.WithUseTransient(hs.ctx, "hole-punch") | ||
sCtx := network.WithNoDial(hpCtx, "hole-punch") | ||
|
@@ -150,6 +201,30 @@ func (hs *HolePunchService) holePunch(relayConn network.Conn) { | |
} | ||
} | ||
|
||
// We can hole punch with a peer ONLY if it is NOT behind a symmetric NAT for all the transport protocol it supports. | ||
func (hs *HolePunchService) peerSupportsHolePunching(p peer.ID, addrs []ma.Multiaddr) bool { | ||
udpSupported := hasProtoAddr(ma.P_UDP, addrs) | ||
tcpSupported := hasProtoAddr(ma.P_TCP, addrs) | ||
udpNAT, _ := hs.host.Peerstore().Get(p, identify.UDPNATDeviceTypeKey) | ||
tcpNAT, _ := hs.host.Peerstore().Get(p, identify.TCPNATDeviceTypeKey) | ||
udpNatType := udpNAT.(network.NATDeviceType) | ||
tcpNATType := tcpNAT.(network.NATDeviceType) | ||
|
||
if udpSupported { | ||
if udpNatType == network.NATDeviceTypeCone || udpNatType == network.NATDeviceTypeUnknown { | ||
return true | ||
} | ||
} | ||
|
||
if tcpSupported { | ||
if tcpNATType == network.NATDeviceTypeCone || tcpNATType == network.NATDeviceTypeUnknown { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func (hs *HolePunchService) handleNewStream(s network.Stream) { | ||
log.Infof("got hole punch request from peer %s", s.Conn().RemotePeer().Pretty()) | ||
_ = s.SetDeadline(time.Now().Add(holePunchTimeout)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,6 +151,13 @@ func NewObservedAddrManager(ctx context.Context, host host.Host) (*ObservedAddrM | |
return oas, nil | ||
} | ||
|
||
func (oas *ObservedAddrManager) getNATDeviceTypes() (udp, tcp network.NATDeviceType) { | ||
oas.mu.RLock() | ||
defer oas.mu.RUnlock() | ||
|
||
return oas.currentUDPNATDeviceType, oas.currentTCPNATDeviceType | ||
} | ||
|
||
// AddrsFor return all activated observed addresses associated with the given | ||
// (resolved) listen address. | ||
func (oas *ObservedAddrManager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { | ||
|
@@ -200,6 +207,21 @@ func (oas *ObservedAddrManager) filter(observedAddrs []*observedAddr) []ma.Multi | |
} | ||
} | ||
|
||
// For certain use cases such as hole punching, it's better to advertise even unactivated observed addresses rather than none at all | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vyzo This needs your attention. I discussed this with @Stebalien and he was okay with it. |
||
// because we don't want to wait for a hole-punch till we make enough connections with other peers to discover our activated addresses. | ||
// If we have activated addresses, we will use them, otherwise, let's use whatever observed addresses we do have. | ||
if len(pmap) == 0 { | ||
for i := range observedAddrs { | ||
a := observedAddrs[i] | ||
if now.Sub(a.lastSeen) <= oas.ttl { | ||
// group addresses by their IPX/Transport Protocol(TCP or UDP) pattern. | ||
pat := a.groupKey() | ||
pmap[pat] = append(pmap[pat], a) | ||
|
||
} | ||
} | ||
} | ||
|
||
addrs := make([]ma.Multiaddr, 0, len(observedAddrs)) | ||
for pat := range pmap { | ||
s := pmap[pat] | ||
|
@@ -269,7 +291,6 @@ func (oas *ObservedAddrManager) worker(ctx context.Context) { | |
|
||
case obs := <-oas.wch: | ||
oas.maybeRecordObservation(obs.conn, obs.observed) | ||
|
||
case <-ticker.C: | ||
oas.gc() | ||
case <-oas.refreshTimer.C: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we look at the actual event? why are we ignoring it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, where do we set the support meta-variables into the peerstore?
Shouldn't we be doing it here? Or is it handled elsewhere?
I think we need a comment if this is correct as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vyzo
Our local vars are set in the peerstore in
basic_host.go
during initialisation of the Host & updated in the obs addr manager.Remote vars are set in the peerstore by Identify.
Both changes are included in this PR. I've added a comment.