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