diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..5cfe148d03 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# On macOS, docker does not support IPv6. + +FROM alpine + +RUN apk add go fuse bash + +RUN go install github.com/anacrolix/godo@v1 +RUN echo "$HOME" +ENV PATH="/root/go/bin:$PATH" + +WORKDIR /src + +COPY . . + +ARG GOCACHE=/root/.cache/go-build +ARG GOMODCACHE=/root/go/pkg/mod + +RUN --mount=type=cache,target=$GOCACHE \ + --mount=type=cache,target=$GOMODCACHE \ + go test -failfast ./... + +# Can't use fuse inside Docker? Asks for modprobe fuse. + +# RUN --mount=type=cache,target=$GOCACHE \ +# --mount=type=cache,target=$GOMODCACHE \ +# ./fs/test.sh diff --git a/client-nowasm_test.go b/client-nowasm_test.go index 9b93139da5..08ed80ced4 100644 --- a/client-nowasm_test.go +++ b/client-nowasm_test.go @@ -15,6 +15,7 @@ import ( ) func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) { + c := qt.New(t) cfg := TestingConfig(t) pc, err := storage.NewBoltPieceCompletion(cfg.DataDir) require.NoError(t, err) @@ -22,7 +23,7 @@ func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) { defer ci.Close() cfg.DefaultStorage = ci cl, err := NewClient(cfg) - require.NoError(t, err) + c.Assert(err, qt.IsNil, qt.Commentf("%#v", err)) cl.Close() // And again, https://github.com/anacrolix/torrent/issues/158 cl, err = NewClient(cfg) diff --git a/client.go b/client.go index 7aab0402a7..7e3fa0f134 100644 --- a/client.go +++ b/client.go @@ -256,10 +256,21 @@ func NewClient(cfg *ClientConfig) (cl *Client, err error) { } } - sockets, err := listenAll(cl.listenNetworks(), cl.config.ListenHost, cl.config.ListenPort, cl.firewallCallback, cl.logger) + builtinListenNetworks := cl.listenNetworks() + sockets, err := listenAll( + builtinListenNetworks, + cl.config.ListenHost, + cl.config.ListenPort, + cl.firewallCallback, + cl.logger, + ) if err != nil { return } + if len(sockets) == 0 && len(builtinListenNetworks) != 0 { + err = fmt.Errorf("no sockets created for networks %v", builtinListenNetworks) + return + } // Check for panics. cl.LocalPort() diff --git a/client_test.go b/client_test.go index d2a88e9e7a..5463d41c3f 100644 --- a/client_test.go +++ b/client_test.go @@ -339,6 +339,7 @@ func TestTorrentDroppedDuringResponsiveRead(t *testing.T) { } func TestDhtInheritBlocklist(t *testing.T) { + c := qt.New(t) ipl := iplist.New(nil) require.NotNil(t, ipl) cfg := TestingConfig(t) @@ -353,7 +354,7 @@ func TestDhtInheritBlocklist(t *testing.T) { assert.Equal(t, ipl, s.(AnacrolixDhtServerWrapper).Server.IPBlocklist()) numServers++ }) - assert.EqualValues(t, 2, numServers) + c.Assert(numServers, qt.Not(qt.Equals), 0) } // Check that stuff is merged in subsequent AddTorrentSpec for the same diff --git a/network_test.go b/network_test.go index a1fd8806c9..691d6725b8 100644 --- a/network_test.go +++ b/network_test.go @@ -16,6 +16,9 @@ func testListenerNetwork( expectedNet, givenNet, addr string, validIp4 bool, ) { l, err := listenFunc(givenNet, addr) + if isUnsupportedNetworkError(err) { + return + } require.NoError(t, err) defer l.Close() assert.EqualValues(t, expectedNet, l.Addr().Network()) @@ -49,10 +52,13 @@ func testAcceptedConnAddr( require.NoError(t, err) defer c.Close() assert.EqualValues(t, network, c.RemoteAddr().Network()) - assert.Equal(t, valid4, missinggo.AddrIP(c.RemoteAddr()).To4() != nil) + assert.Equal(t, valid4, missinggo.AddrIP(c.RemoteAddr()).To4() == nil) } -func listenClosure(rawListenFunc func(string, string) (net.Listener, error), network, addr string) func() (net.Listener, error) { +func listenClosure( + rawListenFunc func(string, string) (net.Listener, error), + network, addr string, +) func() (net.Listener, error) { return func() (net.Listener, error) { return rawListenFunc(network, addr) } @@ -76,6 +82,6 @@ func TestListenLocalhostNetwork(t *testing.T) { "tcp", false, dialClosure(net.Dial, "tcp"), - listenClosure(net.Listen, "tcp6", "localhost:0"), + listenClosure(net.Listen, "tcp4", "localhost:0"), ) } diff --git a/socket.go b/socket.go index 2d4ea863ac..0f45dcbae7 100644 --- a/socket.go +++ b/socket.go @@ -2,14 +2,17 @@ package torrent import ( "context" + "errors" + "fmt" + g "github.com/anacrolix/generics" "net" + "os" "strconv" "syscall" "github.com/anacrolix/log" "github.com/anacrolix/missinggo/perf" "github.com/anacrolix/missinggo/v2" - "github.com/pkg/errors" ) type Listener interface { @@ -111,7 +114,13 @@ type tcpSocket struct { NetworkDialer } -func listenAll(networks []network, getHost func(string) string, port int, f firewallCallback, logger log.Logger) ([]socket, error) { +func listenAll( + networks []network, + getHost func(string) string, + port int, + f firewallCallback, + logger log.Logger, +) ([]socket, error) { if len(networks) == 0 { return nil, nil } @@ -132,13 +141,27 @@ type networkAndHost struct { Host string } -func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback, logger log.Logger) (ss []socket, retry bool, err error) { - ss = make([]socket, 1, len(nahs)) - portStr := strconv.FormatInt(int64(port), 10) - ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), f, logger) - if err != nil { - return nil, false, errors.Wrap(err, "first listen") +func isUnsupportedNetworkError(err error) bool { + var sysErr *os.SyscallError + //spewCfg := spew.NewDefaultConfig() + //spewCfg.ContinueOnMethod = true + //spewCfg.Dump(err) + if !errors.As(err, &sysErr) { + return false } + //spewCfg.Dump(sysErr) + //spewCfg.Dump(sysErr.Err.Error()) + // This might only be Linux specific. + return sysErr.Syscall == "bind" && sysErr.Err.Error() == "cannot assign requested address" +} + +func listenAllRetry( + nahs []networkAndHost, + port int, + f firewallCallback, + logger log.Logger, +) (ss []socket, retry bool, err error) { + // Close all sockets on error or retry. defer func() { if err != nil || retry { for _, s := range ss { @@ -147,15 +170,27 @@ func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback, logger ss = nil } }() - portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10) - for _, nah := range nahs[1:] { - s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger) + g.MakeSliceWithCap(&ss, len(nahs)) + portStr := strconv.FormatInt(int64(port), 10) + for _, nah := range nahs { + var s socket + s, err = listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger) if err != nil { - return ss, - missinggo.IsAddrInUse(err) && port == 0, - errors.Wrap(err, "subsequent listen") + if isUnsupportedNetworkError(err) { + err = nil + continue + } + if len(ss) == 0 { + // First relative to a possibly dynamic port (0). + err = fmt.Errorf("first listen: %w", err) + } else { + err = fmt.Errorf("subsequent listen: %w", err) + } + retry = missinggo.IsAddrInUse(err) && port == 0 + return } ss = append(ss, s) + portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10) } return } diff --git a/test/issue377_test.go b/test/issue377_test.go index 5b0e659fee..c5bfae2c38 100644 --- a/test/issue377_test.go +++ b/test/issue377_test.go @@ -21,7 +21,7 @@ import ( func justOneNetwork(cc *torrent.ClientConfig) { cc.DisableTCP = true - cc.DisableIPv4 = true + cc.DisableIPv6 = true } func TestReceiveChunkStorageFailureSeederFastExtensionDisabled(t *testing.T) { diff --git a/tracker/udp/udp_test.go b/tracker/udp/udp_test.go index 64aeb80ff7..378351cd81 100644 --- a/tracker/udp/udp_test.go +++ b/tracker/udp/udp_test.go @@ -95,7 +95,10 @@ func TestConnClientLogDispatchUnknownTransactionId(t *testing.T) { c.Assert(err, qt.IsNil) defer pc.Close() ccAddr := *cc.LocalAddr().(*net.UDPAddr) - ccAddr.IP = net.IPv6loopback + ipAddrs, err := net.DefaultResolver.LookupIPAddr(context.Background(), "localhost") + c.Assert(err, qt.IsNil) + ccAddr.IP = ipAddrs[0].IP + ccAddr.Zone = ipAddrs[0].Zone _, err = pc.WriteTo(make([]byte, 30), &ccAddr) c.Assert(err, qt.IsNil) }