"strconv"
"time"
+ "github.com/anacrolix/torrent/internal/panicif"
+
"github.com/anacrolix/chansync"
"github.com/anacrolix/chansync/events"
"github.com/anacrolix/dht/v2"
activeAnnounceLimiter limiter.Instance
httpClient *http.Client
+
+ undialableWithoutHolepunch map[netip.AddrPort]struct{}
+ undialableWithoutHolepunchDialAttemptedAfterHolepunchConnect map[netip.AddrPort]struct{}
+ dialableOnlyAfterHolepunch map[netip.AddrPort]struct{}
}
type ipStr string
// Returns nil connection and nil error if no connection could be established for valid reasons.
func (cl *Client) initiateRendezvousConnect(
- t *Torrent, addr PeerRemoteAddr,
+ t *Torrent, holepunchAddr netip.AddrPort,
) (ok bool, err error) {
- holepunchAddr, err := addrPortFromPeerRemoteAddr(addr)
- if err != nil {
- return
- }
cl.lock()
defer cl.unlock()
rz, err := t.startHolepunchRendezvous(holepunchAddr)
) {
t := opts.t
addr := opts.addr
- var rzOk bool
- if !opts.skipHolepunchRendezvous {
- rzOk, err = cl.initiateRendezvousConnect(t, addr)
- if err != nil {
- err = fmt.Errorf("initiating rendezvous connect: %w", err)
+ holepunchAddr, err := addrPortFromPeerRemoteAddr(addr)
+ var sentRendezvous bool
+ if err == nil {
+ if !opts.skipHolepunchRendezvous {
+ sentRendezvous, err = cl.initiateRendezvousConnect(t, holepunchAddr)
+ if err != nil {
+ err = fmt.Errorf("initiating rendezvous connect: %w", err)
+ }
}
}
- if opts.requireRendezvous && !rzOk {
+ gotHolepunchConnect := (err == nil && sentRendezvous) || opts.receivedHolepunchConnect
+ if opts.requireRendezvous && !sentRendezvous {
return nil, err
}
if err != nil {
defer cancel()
dr := cl.dialFirst(dialCtx, addr.String())
nc := dr.Conn
+ cl.lock()
+ if gotHolepunchConnect && g.MapContains(cl.undialableWithoutHolepunch, holepunchAddr) {
+ g.MakeMapIfNilAndSet(
+ &cl.undialableWithoutHolepunchDialAttemptedAfterHolepunchConnect,
+ holepunchAddr,
+ struct{}{},
+ )
+ }
+ cl.unlock()
if nc == nil {
+ if !sentRendezvous && !gotHolepunchConnect {
+ cl.lock()
+ g.MakeMapIfNilAndSet(&cl.undialableWithoutHolepunch, holepunchAddr, struct{}{})
+ cl.unlock()
+ }
if dialCtx.Err() != nil {
return nil, fmt.Errorf("dialing: %w", dialCtx.Err())
}
return nil, errors.New("dial failed")
}
+ if gotHolepunchConnect {
+ panicif.False(holepunchAddr.IsValid())
+ cl.lock()
+ if g.MapContains(cl.undialableWithoutHolepunchDialAttemptedAfterHolepunchConnect, holepunchAddr) {
+ g.MakeMapIfNilAndSet(
+ &cl.dialableOnlyAfterHolepunch,
+ holepunchAddr,
+ struct{}{},
+ )
+ }
+ cl.unlock()
+ }
addrIpPort, _ := tryIpPortFromNetAddr(addr)
c, err := cl.initiateProtocolHandshakes(
context.Background(), nc, t, obfuscatedHeader,
requireRendezvous bool
// Don't send rendezvous requests to eligible relays.
skipHolepunchRendezvous bool
+ // Outgoing connection attempt is in response to holepunch connect message.
+ receivedHolepunchConnect bool
}
// Called to dial out and run a connection. The addr we're given is already
func (cl *Client) statsLocked() (stats ClientStats) {
stats.ConnStats = cl.connStats.Copy()
stats.ActiveHalfOpenAttempts = cl.numHalfOpen
+ stats.NumPeersUndialableWithoutHolepunchDialedAfterHolepunchConnect =
+ len(cl.undialableWithoutHolepunchDialAttemptedAfterHolepunchConnect)
+ stats.NumPeersDialableOnlyAfterHolepunch =
+ len(cl.dialableOnlyAfterHolepunch)
return
}
return
}
p := t.peers.PopMax()
- t.initiateConn(p, false, false, false)
+ t.initiateConn(p, false, false, false, false)
initiated++
}
return
requireRendezvous bool,
skipHolepunchRendezvous bool,
ignoreLimits bool,
+ receivedHolepunchConnect bool,
) {
if peer.Id == t.cl.peerID {
return
t.addHalfOpen(addrStr, attemptKey)
go t.cl.outgoingConnection(
outgoingConnOpts{
- t: t,
- addr: peer.Addr,
- requireRendezvous: requireRendezvous,
- skipHolepunchRendezvous: skipHolepunchRendezvous,
+ t: t,
+ addr: peer.Addr,
+ requireRendezvous: requireRendezvous,
+ skipHolepunchRendezvous: skipHolepunchRendezvous,
+ receivedHolepunchConnect: receivedHolepunchConnect,
},
peer.Source,
peer.Trusted,
t.initiateConn(PeerInfo{
Addr: msg.AddrPort,
Source: PeerSourceUtHolepunch,
- }, false, true, true)
+ }, false, true, true, true)
}
return nil
case utHolepunch.Error:
log.Printf("trying to initiate to %v", targetAddr)
llg.initiateConn(PeerInfo{
Addr: targetAddr,
- }, true, false, false)
+ }, true, false, false, false)
llg.cl.unlock()
wg.Wait()
+ // These checks would require that the leecher leecher first attempt to connect without
+ // holepunching.
+
+ //llClientStats := leecherLeecher.Stats()
+ //c := qt.New(t)
+ //c.Check(llClientStats.NumPeersDialedRequiringHolepunch, qt.Not(qt.Equals), 0)
+ //c.Check(
+ // llClientStats.NumPeersDialedRequiringHolepunch,
+ // qt.Equals,
+ // llClientStats.NumPeersUndiableWithoutHolepunch,
+ //)
}
func waitForConns(t *Torrent) {