github.com/anacrolix/torrent@v1.61.0/ut-holepunching_test.go (about)

     1  package torrent
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"net"
    10  	"os"
    11  	"sync"
    12  	"testing"
    13  	"testing/iotest"
    14  	"time"
    15  
    16  	"github.com/anacrolix/log"
    17  	"github.com/anacrolix/missinggo/v2/iter"
    18  	qt "github.com/go-quicktest/qt"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"golang.org/x/time/rate"
    22  
    23  	"github.com/anacrolix/torrent/internal/testutil"
    24  )
    25  
    26  // Check that after completing leeching, a leecher transitions to a seeding
    27  // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
    28  func TestHolepunchConnect(t *testing.T) {
    29  	greetingTempDir, mi := testutil.GreetingTestTorrent()
    30  	defer os.RemoveAll(greetingTempDir)
    31  
    32  	cfg := TestingConfig(t)
    33  	cfg.Seed = true
    34  	cfg.MaxAllocPeerRequestDataPerConn = 4
    35  	cfg.DataDir = greetingTempDir
    36  	cfg.DisablePEX = true
    37  	cfg.Debug = true
    38  	cfg.AcceptPeerConnections = false
    39  	// Listening, even without accepting, still means the leecher-leecher completes the dial to the
    40  	// seeder, and so it won't attempt to holepunch.
    41  	cfg.DisableTCP = true
    42  	// Ensure that responding to holepunch connects don't wait around for the dial limit. We also
    43  	// have to allow the initial connection to the leecher though, so it can rendezvous for us.
    44  	cfg.DialRateLimiter = rate.NewLimiter(0, 1)
    45  	cfg.Logger = cfg.Logger.WithContextText("seeder")
    46  	seeder, err := NewClient(cfg)
    47  	require.NoError(t, err)
    48  	defer seeder.Close()
    49  	defer testutil.ExportStatusWriter(seeder, "s", t)()
    50  	seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
    51  	require.NoError(t, err)
    52  	assert.True(t, ok)
    53  	seederTorrent.VerifyData()
    54  
    55  	cfg = TestingConfig(t)
    56  	cfg.Seed = true
    57  	cfg.DataDir = t.TempDir()
    58  	cfg.AlwaysWantConns = true
    59  	cfg.Logger = cfg.Logger.WithContextText("leecher")
    60  	// This way the leecher leecher will still try to use this peer as a relay, but won't be told
    61  	// about the seeder via PEX.
    62  	//cfg.DisablePEX = true
    63  	cfg.Debug = true
    64  	leecher, err := NewClient(cfg)
    65  	require.NoError(t, err)
    66  	defer leecher.Close()
    67  	defer testutil.ExportStatusWriter(leecher, "l", t)()
    68  
    69  	cfg = TestingConfig(t)
    70  	cfg.Seed = false
    71  	cfg.DataDir = t.TempDir()
    72  	cfg.MaxAllocPeerRequestDataPerConn = 4
    73  	cfg.Debug = true
    74  	cfg.NominalDialTimeout = time.Second
    75  	cfg.Logger = cfg.Logger.WithContextText("leecher-leecher")
    76  	//cfg.DisableUTP = true
    77  	leecherLeecher, _ := NewClient(cfg)
    78  	require.NoError(t, err)
    79  	defer leecherLeecher.Close()
    80  	defer testutil.ExportStatusWriter(leecherLeecher, "ll", t)()
    81  	leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
    82  		ret = TorrentSpecFromMetaInfo(mi)
    83  		ret.ChunkSize = 2
    84  		return
    85  	}())
    86  	_ = leecherGreeting
    87  	require.NoError(t, err)
    88  	assert.True(t, ok)
    89  	llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
    90  		ret = TorrentSpecFromMetaInfo(mi)
    91  		ret.ChunkSize = 3
    92  		return
    93  	}())
    94  	require.NoError(t, err)
    95  	assert.True(t, ok)
    96  
    97  	var wg sync.WaitGroup
    98  	wg.Add(1)
    99  	go func() {
   100  		defer wg.Done()
   101  		r := llg.NewReader()
   102  		defer r.Close()
   103  		qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents))))
   104  	}()
   105  	go seederTorrent.AddClientPeer(leecher)
   106  	waitForConns(seederTorrent)
   107  	go llg.AddClientPeer(leecher)
   108  	waitForConns(llg)
   109  	time.Sleep(time.Second)
   110  	llg.cl.lock()
   111  	targetAddr := seeder.ListenAddrs()[0]
   112  	log.Printf("trying to initiate to %v", targetAddr)
   113  	initiateConn(outgoingConnOpts{
   114  		peerInfo: PeerInfo{
   115  			Addr: targetAddr,
   116  		},
   117  		t:                       llg,
   118  		requireRendezvous:       true,
   119  		skipHolepunchRendezvous: false,
   120  		HeaderObfuscationPolicy: llg.cl.config.HeaderObfuscationPolicy,
   121  	}, true)
   122  	llg.cl.unlock()
   123  	wg.Wait()
   124  
   125  	qt.Check(t, qt.Not(qt.HasLen(seeder.dialedSuccessfullyAfterHolepunchConnect, 0)))
   126  	qt.Check(t, qt.Not(qt.HasLen(leecherLeecher.probablyOnlyConnectedDueToHolepunch, 0)))
   127  
   128  	llClientStats := leecherLeecher.Stats()
   129  	qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersUndialableWithoutHolepunch, 0)))
   130  	qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersUndialableWithoutHolepunchDialedAfterHolepunchConnect, 0)))
   131  	qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersProbablyOnlyConnectedDueToHolepunch, 0)))
   132  }
   133  
   134  func waitForConns(t *Torrent) {
   135  	t.cl.lock()
   136  	defer t.cl.unlock()
   137  	for {
   138  		for range t.conns {
   139  			return
   140  		}
   141  		t.cl.event.Wait()
   142  	}
   143  }
   144  
   145  // Show that dialling TCP will complete before the other side accepts.
   146  func TestDialTcpNotAccepting(t *testing.T) {
   147  	l, err := net.Listen("tcp", "localhost:0")
   148  	qt.Check(t, qt.IsNil(err))
   149  	defer l.Close()
   150  	dialedConn, err := net.Dial("tcp", l.Addr().String())
   151  	qt.Assert(t, qt.IsNil(err))
   152  	dialedConn.Close()
   153  }
   154  
   155  func TestTcpSimultaneousOpen(t *testing.T) {
   156  	const network = "tcp"
   157  	ctx := context.Background()
   158  	makeDialer := func(localPort int, remoteAddr string) func() (net.Conn, error) {
   159  		dialer := net.Dialer{
   160  			LocalAddr: &net.TCPAddr{
   161  				//IP:   net.IPv6loopback,
   162  				Port: localPort,
   163  			},
   164  		}
   165  		return func() (net.Conn, error) {
   166  			return dialer.DialContext(ctx, network, remoteAddr)
   167  		}
   168  	}
   169  	// I really hate doing this in unit tests, but we would need to pick apart Dialer to get
   170  	// perfectly synchronized simultaneous dials.
   171  	for range iter.N(10) {
   172  		first, second := randPortPair()
   173  		t.Logf("ports are %v and %v", first, second)
   174  		err := testSimultaneousOpen(
   175  			t.Cleanup,
   176  			makeDialer(first, fmt.Sprintf("localhost:%d", second)),
   177  			makeDialer(second, fmt.Sprintf("localhost:%d", first)),
   178  		)
   179  		if err == nil {
   180  			return
   181  		}
   182  		// This proves that the connections are not the same.
   183  		if errors.Is(err, errMsgNotReceived) {
   184  			t.Fatal(err)
   185  		}
   186  		// Could be a timing issue, so try again.
   187  		t.Log(err)
   188  	}
   189  	// If we weren't able to get a simultaneous dial to occur, then we can't call it a failure.
   190  	t.Skip("couldn't synchronize dials")
   191  }
   192  
   193  func randIntInRange(low, high int) int {
   194  	return rand.Intn(high-low+1) + low
   195  }
   196  
   197  func randDynamicPort() int {
   198  	return randIntInRange(49152, 65535)
   199  }
   200  
   201  func randPortPair() (first int, second int) {
   202  	first = randDynamicPort()
   203  	for {
   204  		second = randDynamicPort()
   205  		if second != first {
   206  			return
   207  		}
   208  	}
   209  }
   210  
   211  func writeMsg(conn net.Conn) {
   212  	conn.Write([]byte(defaultMsg))
   213  	// Writing must be closed so the reader will get EOF and stop reading.
   214  	conn.Close()
   215  }
   216  
   217  func readMsg(conn net.Conn) error {
   218  	msgBytes, err := io.ReadAll(conn)
   219  	if err != nil {
   220  		return err
   221  	}
   222  	msgStr := string(msgBytes)
   223  	if msgStr != defaultMsg {
   224  		return fmt.Errorf("read %q", msgStr)
   225  	}
   226  	return nil
   227  }
   228  
   229  var errMsgNotReceived = errors.New("msg not received in time")
   230  
   231  // Runs two dialers simultaneously, then sends a message on one connection and check it reads from
   232  // the other, thereby showing that both dials obtained endpoints to the same connection.
   233  func testSimultaneousOpen(
   234  	cleanup func(func()),
   235  	firstDialer, secondDialer func() (net.Conn, error),
   236  ) error {
   237  	errs := make(chan error)
   238  	var dialsDone sync.WaitGroup
   239  	const numDials = 2
   240  	dialsDone.Add(numDials)
   241  	signal := make(chan struct{})
   242  	var dialersDone sync.WaitGroup
   243  	dialersDone.Add(numDials)
   244  	doDial := func(
   245  		dialer func() (net.Conn, error),
   246  		onSignal func(net.Conn),
   247  	) {
   248  		defer dialersDone.Done()
   249  		conn, err := dialer()
   250  		dialsDone.Done()
   251  		errs <- err
   252  		if err != nil {
   253  			return
   254  		}
   255  		cleanup(func() {
   256  			conn.Close()
   257  		})
   258  		<-signal
   259  		onSignal(conn)
   260  		//if err == nil {
   261  		//	conn.Close()
   262  		//}
   263  	}
   264  	go doDial(
   265  		firstDialer,
   266  		func(conn net.Conn) {
   267  			writeMsg(conn)
   268  			errs <- nil
   269  		},
   270  	)
   271  	go doDial(
   272  		secondDialer,
   273  		func(conn net.Conn) {
   274  			gotMsg := make(chan error, 1)
   275  			go func() {
   276  				gotMsg <- readMsg(conn)
   277  			}()
   278  			select {
   279  			case err := <-gotMsg:
   280  				errs <- err
   281  			case <-time.After(time.Second):
   282  				errs <- errMsgNotReceived
   283  			}
   284  		},
   285  	)
   286  	dialsDone.Wait()
   287  	for range iter.N(numDials) {
   288  		err := <-errs
   289  		if err != nil {
   290  			return err
   291  		}
   292  	}
   293  	close(signal)
   294  	for range iter.N(numDials) {
   295  		err := <-errs
   296  		if err != nil {
   297  			return err
   298  		}
   299  	}
   300  	dialersDone.Wait()
   301  	return nil
   302  }
   303  
   304  const defaultMsg = "hello"
   305  
   306  // Show that uTP doesn't implement simultaneous open. When two sockets dial each other, they both
   307  // get separate connections. This means that holepunch connect may result in an accept (and dial)
   308  // for one or both peers involved.
   309  func TestUtpSimultaneousOpen(t *testing.T) {
   310  	t.Parallel()
   311  	const network = "udp"
   312  	ctx := context.Background()
   313  	newUtpSocket := func(addr string) utpSocket {
   314  		socket, err := NewUtpSocket(
   315  			network,
   316  			addr,
   317  			func(net.Addr) bool {
   318  				return false
   319  			},
   320  			log.Default,
   321  		)
   322  		qt.Assert(t, qt.IsNil(err))
   323  		return socket
   324  	}
   325  	first := newUtpSocket("localhost:0")
   326  	defer first.Close()
   327  	second := newUtpSocket("localhost:0")
   328  	defer second.Close()
   329  	getDial := func(sock utpSocket, addr string) func() (net.Conn, error) {
   330  		return func() (net.Conn, error) {
   331  			return sock.DialContext(ctx, network, addr)
   332  		}
   333  	}
   334  	t.Logf("first addr is %v. second addr is %v", first.Addr().String(), second.Addr().String())
   335  	for range iter.N(10) {
   336  		err := testSimultaneousOpen(
   337  			t.Cleanup,
   338  			getDial(first, second.Addr().String()),
   339  			getDial(second, first.Addr().String()),
   340  		)
   341  		if err == nil {
   342  			t.Fatal("expected utp to fail simultaneous open")
   343  		}
   344  		if errors.Is(err, errMsgNotReceived) {
   345  			return
   346  		}
   347  		skipGoUtpDialIssue(t, err)
   348  		t.Log(err)
   349  		time.Sleep(time.Second)
   350  	}
   351  	t.FailNow()
   352  }
   353  
   354  func writeAndReadMsg(r, w net.Conn) error {
   355  	go writeMsg(w)
   356  	return readMsg(r)
   357  }
   358  
   359  func skipGoUtpDialIssue(t *testing.T, err error) {
   360  	if err.Error() == "timed out waiting for ack" {
   361  		t.Skip("anacrolix go utp implementation has issues. Use anacrolix/go-libutp by enabling CGO.")
   362  	}
   363  }
   364  
   365  // Show that dialling one socket and accepting from the other results in them having ends of the
   366  // same connection.
   367  func TestUtpDirectDialMsg(t *testing.T) {
   368  	t.Parallel()
   369  	const network = "udp4"
   370  	ctx := context.Background()
   371  	newUtpSocket := func(addr string) utpSocket {
   372  		socket, err := NewUtpSocket(network, addr, func(net.Addr) bool {
   373  			return false
   374  		}, log.Default)
   375  		qt.Assert(t, qt.IsNil(err))
   376  		return socket
   377  	}
   378  	for range iter.N(10) {
   379  		err := func() error {
   380  			first := newUtpSocket("localhost:0")
   381  			defer first.Close()
   382  			second := newUtpSocket("localhost:0")
   383  			defer second.Close()
   384  			writer, err := first.DialContext(ctx, network, second.Addr().String())
   385  			if err != nil {
   386  				return err
   387  			}
   388  			defer writer.Close()
   389  			reader, err := second.Accept()
   390  			qt.Assert(t, qt.IsNil(err))
   391  			defer reader.Close()
   392  			return writeAndReadMsg(reader, writer)
   393  		}()
   394  		if err == nil {
   395  			return
   396  		}
   397  		skipGoUtpDialIssue(t, err)
   398  		t.Log(err)
   399  		time.Sleep(time.Second)
   400  	}
   401  	t.FailNow()
   402  }