| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package torrent
- import (
- "context"
- "errors"
- "fmt"
- "net"
- "os"
- "strconv"
- "syscall"
- g "github.com/anacrolix/generics"
- "github.com/anacrolix/log"
- "github.com/anacrolix/missinggo/perf"
- "github.com/anacrolix/missinggo/v2"
- )
- type Listener interface {
- // Accept waits for and returns the next connection to the listener.
- Accept() (net.Conn, error)
- // Addr returns the listener's network address.
- Addr() net.Addr
- }
- type socket interface {
- Listener
- Dialer
- Close() error
- }
- func listen(n network, addr string, f firewallCallback, logger log.Logger) (socket, error) {
- switch {
- case n.Tcp:
- return listenTcp(n.String(), addr)
- case n.Udp:
- return listenUtp(n.String(), addr, f, logger)
- default:
- panic(n)
- }
- }
- // Dialing TCP from a local port limits us to a single outgoing TCP connection to each remote
- // client. Instead, this should be a last resort if we need to use holepunching, and only then to
- // connect to other clients that actually try to holepunch TCP.
- const dialTcpFromListenPort = false
- var tcpListenConfig = net.ListenConfig{
- Control: func(network, address string, c syscall.RawConn) (err error) {
- controlErr := c.Control(func(fd uintptr) {
- if dialTcpFromListenPort {
- err = setReusePortSockOpts(fd)
- }
- })
- if err != nil {
- return
- }
- err = controlErr
- return
- },
- // BitTorrent connections manage their own keep-alives.
- KeepAlive: -1,
- }
- func listenTcp(network, address string) (s socket, err error) {
- l, err := tcpListenConfig.Listen(context.Background(), network, address)
- if err != nil {
- return
- }
- netDialer := net.Dialer{
- // We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction ourselves,
- // although it's probably not triggered as I think the network is already constrained to
- // tcp4 or tcp6 at this point.
- FallbackDelay: -1,
- // BitTorrent connections manage their own keepalives.
- KeepAlive: tcpListenConfig.KeepAlive,
- Control: func(network, address string, c syscall.RawConn) (err error) {
- controlErr := c.Control(func(fd uintptr) {
- err = setSockNoLinger(fd)
- if err != nil {
- // Failing to disable linger is undesirable, but not fatal.
- log.Levelf(log.Debug, "error setting linger socket option on tcp socket: %v", err)
- err = nil
- }
- // This is no longer required I think, see
- // https://github.com/anacrolix/torrent/discussions/856. I added this originally to
- // allow dialling out from the client's listen port, but that doesn't really work. I
- // think Linux older than ~2013 doesn't support SO_REUSEPORT.
- if dialTcpFromListenPort {
- err = setReusePortSockOpts(fd)
- }
- })
- if err == nil {
- err = controlErr
- }
- return
- },
- }
- if dialTcpFromListenPort {
- netDialer.LocalAddr = l.Addr()
- }
- s = tcpSocket{
- Listener: l,
- NetworkDialer: NetworkDialer{
- Network: network,
- Dialer: &netDialer,
- },
- }
- return
- }
- type tcpSocket struct {
- net.Listener
- NetworkDialer
- }
- func listenAll(
- networks []network,
- getHost func(string) string,
- port int,
- f firewallCallback,
- logger log.Logger,
- ) ([]socket, error) {
- if len(networks) == 0 {
- return nil, nil
- }
- var nahs []networkAndHost
- for _, n := range networks {
- nahs = append(nahs, networkAndHost{n, getHost(n.String())})
- }
- for {
- ss, retry, err := listenAllRetry(nahs, port, f, logger)
- if !retry {
- return ss, err
- }
- }
- }
- type networkAndHost struct {
- Network network
- Host string
- }
- 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 {
- s.Close()
- }
- ss = nil
- }
- }()
- 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 {
- 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
- }
- // This isn't aliased from go-libutp since that assumes CGO.
- type firewallCallback func(net.Addr) bool
- func listenUtp(network, addr string, fc firewallCallback, logger log.Logger) (socket, error) {
- us, err := NewUtpSocket(network, addr, fc, logger)
- return utpSocketSocket{us, network}, err
- }
- // utpSocket wrapper, additionally wrapped for the torrent package's socket interface.
- type utpSocketSocket struct {
- utpSocket
- network string
- }
- func (me utpSocketSocket) DialerNetwork() string {
- return me.network
- }
- func (me utpSocketSocket) Dial(ctx context.Context, addr string) (conn net.Conn, err error) {
- defer perf.ScopeTimerErr(&err)()
- return me.utpSocket.DialContext(ctx, me.network, addr)
- }
|