github.com/anacrolix/torrent@v1.61.0/socket.go (about)

     1  package torrent
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"strconv"
    10  	"syscall"
    11  
    12  	g "github.com/anacrolix/generics"
    13  	"github.com/anacrolix/log"
    14  	"github.com/anacrolix/missinggo/v2"
    15  )
    16  
    17  type Listener interface {
    18  	// Accept waits for and returns the next connection to the listener.
    19  	Accept() (net.Conn, error)
    20  
    21  	// Addr returns the listener's network address.
    22  	Addr() net.Addr
    23  }
    24  
    25  type socket interface {
    26  	Listener
    27  	Dialer
    28  	Close() error
    29  }
    30  
    31  func listen(n network, addr string, f firewallCallback, logger log.Logger) (socket, error) {
    32  	switch {
    33  	case n.Tcp:
    34  		return listenTcp(n.String(), addr)
    35  	case n.Udp:
    36  		return listenUtp(n.String(), addr, f, logger)
    37  	default:
    38  		panic(n)
    39  	}
    40  }
    41  
    42  // Dialing TCP from a local port limits us to a single outgoing TCP connection to each remote
    43  // client. Instead, this should be a last resort if we need to use holepunching, and only then to
    44  // connect to other clients that actually try to holepunch TCP.
    45  const dialTcpFromListenPort = false
    46  
    47  var SocketIPTypeOfService = 0
    48  
    49  var tcpListenConfig = net.ListenConfig{
    50  	Control: func(network, address string, c syscall.RawConn) (err error) {
    51  		controlErr := c.Control(func(fd uintptr) {
    52  			if dialTcpFromListenPort {
    53  				err = setReusePortSockOpts(fd)
    54  			}
    55  			if err == nil && SocketIPTypeOfService != 0 {
    56  				err = setSockIPTOS(fd, SocketIPTypeOfService)
    57  			}
    58  		})
    59  		if err != nil {
    60  			return
    61  		}
    62  		err = controlErr
    63  		return
    64  	},
    65  	// BitTorrent connections manage their own keep-alives.
    66  	KeepAlive: -1,
    67  }
    68  
    69  func listenTcp(network, address string) (s socket, err error) {
    70  	l, err := tcpListenConfig.Listen(context.Background(), network, address)
    71  	if err != nil {
    72  		return
    73  	}
    74  	netDialer := net.Dialer{
    75  		// We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction ourselves,
    76  		// although it's probably not triggered as I think the network is already constrained to
    77  		// tcp4 or tcp6 at this point.
    78  		FallbackDelay: -1,
    79  		// BitTorrent connections manage their own keepalives.
    80  		KeepAlive: tcpListenConfig.KeepAlive,
    81  		Control: func(network, address string, c syscall.RawConn) (err error) {
    82  			controlErr := c.Control(func(fd uintptr) {
    83  				err = setSockNoLinger(fd)
    84  				if err != nil {
    85  					// Failing to disable linger is undesirable, but not fatal.
    86  					log.Levelf(log.Debug, "error setting linger socket option on tcp socket: %v", err)
    87  					err = nil
    88  				}
    89  				// This is no longer required I think, see
    90  				// https://github.com/anacrolix/torrent/discussions/856. I added this originally to
    91  				// allow dialling out from the client's listen port, but that doesn't really work. I
    92  				// think Linux older than ~2013 doesn't support SO_REUSEPORT.
    93  				if dialTcpFromListenPort {
    94  					err = setReusePortSockOpts(fd)
    95  				}
    96  				if err == nil && SocketIPTypeOfService != 0 {
    97  					err = setSockIPTOS(fd, SocketIPTypeOfService)
    98  				}
    99  			})
   100  			if err == nil {
   101  				err = controlErr
   102  			}
   103  			return
   104  		},
   105  	}
   106  	if dialTcpFromListenPort {
   107  		netDialer.LocalAddr = l.Addr()
   108  	}
   109  	s = tcpSocket{
   110  		Listener: l,
   111  		NetworkDialer: NetworkDialer{
   112  			Network: network,
   113  			Dialer:  &netDialer,
   114  		},
   115  	}
   116  	return
   117  }
   118  
   119  type tcpSocket struct {
   120  	net.Listener
   121  	NetworkDialer
   122  }
   123  
   124  func listenAll(
   125  	networks []network,
   126  	getHost func(string) string,
   127  	port int,
   128  	f firewallCallback,
   129  	logger log.Logger,
   130  ) ([]socket, error) {
   131  	if len(networks) == 0 {
   132  		return nil, nil
   133  	}
   134  	var nahs []networkAndHost
   135  	for _, n := range networks {
   136  		nahs = append(nahs, networkAndHost{n, getHost(n.String())})
   137  	}
   138  	for {
   139  		ss, retry, err := listenAllRetry(nahs, port, f, logger)
   140  		if !retry {
   141  			return ss, err
   142  		}
   143  	}
   144  }
   145  
   146  type networkAndHost struct {
   147  	Network network
   148  	Host    string
   149  }
   150  
   151  func isUnsupportedNetworkError(err error) bool {
   152  	var sysErr *os.SyscallError
   153  	//spewCfg := spew.NewDefaultConfig()
   154  	//spewCfg.ContinueOnMethod = true
   155  	//spewCfg.Dump(err)
   156  	if !errors.As(err, &sysErr) {
   157  		return false
   158  	}
   159  	//spewCfg.Dump(sysErr)
   160  	//spewCfg.Dump(sysErr.Err.Error())
   161  	// This might only be Linux specific.
   162  	return sysErr.Syscall == "bind" && sysErr.Err.Error() == "cannot assign requested address"
   163  }
   164  
   165  func listenAllRetry(
   166  	nahs []networkAndHost,
   167  	port int,
   168  	f firewallCallback,
   169  	logger log.Logger,
   170  ) (ss []socket, retry bool, err error) {
   171  	// Close all sockets on error or retry.
   172  	defer func() {
   173  		if err != nil || retry {
   174  			for _, s := range ss {
   175  				s.Close()
   176  			}
   177  			ss = nil
   178  		}
   179  	}()
   180  	g.MakeSliceWithCap(&ss, len(nahs))
   181  	portStr := strconv.FormatInt(int64(port), 10)
   182  	for _, nah := range nahs {
   183  		var s socket
   184  		s, err = listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger)
   185  		if err != nil {
   186  			if isUnsupportedNetworkError(err) {
   187  				err = nil
   188  				continue
   189  			}
   190  			if len(ss) == 0 {
   191  				// First relative to a possibly dynamic port (0).
   192  				err = fmt.Errorf("first listen: %w", err)
   193  			} else {
   194  				err = fmt.Errorf("subsequent listen: %w", err)
   195  			}
   196  			retry = missinggo.IsAddrInUse(err) && port == 0
   197  			return
   198  		}
   199  		ss = append(ss, s)
   200  		portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10)
   201  	}
   202  	return
   203  }
   204  
   205  // This isn't aliased from go-libutp since that assumes CGO.
   206  type firewallCallback func(net.Addr) bool
   207  
   208  func listenUtp(network, addr string, fc firewallCallback, logger log.Logger) (socket, error) {
   209  	us, err := NewUtpSocket(network, addr, fc, logger)
   210  	return utpSocketSocket{us, network}, err
   211  }
   212  
   213  // utpSocket wrapper, additionally wrapped for the torrent package's socket interface.
   214  type utpSocketSocket struct {
   215  	utpSocket
   216  	network string
   217  }
   218  
   219  func (me utpSocketSocket) DialerNetwork() string {
   220  	return me.network
   221  }
   222  
   223  func (me utpSocketSocket) Dial(ctx context.Context, addr string) (conn net.Conn, err error) {
   224  	return me.utpSocket.DialContext(ctx, me.network, addr)
   225  }