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