From: Matt Joiner <anacrolix@gmail.com>
Date: Thu, 22 Feb 2024 03:30:21 +0000 (+1100)
Subject: Support operating when IPv6 isn't available
X-Git-Tag: v1.55.0~20
X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=2cf5c7ec511c333b6ea06898c9d09006240cac97;p=btrtrc.git

Support operating when IPv6 isn't available
---

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..5cfe148d
--- /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 9b93139d..08ed80ce 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 7aab0402..7e3fa0f1 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 d2a88e9e..5463d41c 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 a1fd8806..691d6725 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 2d4ea863..0f45dcba 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 5b0e659f..c5bfae2c 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 64aeb80f..378351cd 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)
 }