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 }