]> Sergey Matveev's repositories - btrtrc.git/blob - socket.go
Dial TCP with the listener's local addr
[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 var tcpListenConfig = net.ListenConfig{
41         Control: func(network, address string, c syscall.RawConn) (err error) {
42                 controlErr := c.Control(func(fd uintptr) {
43                         err = setReusePortSockOpts(fd)
44                 })
45                 if err != nil {
46                         return
47                 }
48                 err = controlErr
49                 return
50         },
51         // BitTorrent connections manage their own keep-alives.
52         KeepAlive: -1,
53 }
54
55 func listenTcp(network, address string) (s socket, err error) {
56         l, err := tcpListenConfig.Listen(context.Background(), network, address)
57         return tcpSocket{
58                 Listener: l,
59                 NetworkDialer: NetworkDialer{
60                         Network: network,
61                         Dialer: &net.Dialer{
62                                 // My hope is that dialling out from a consistent port will improve the
63                                 // hole-punching behaviour. It might also prevent duplicate connections to the same
64                                 // peer if the peer does the same thing. There should probably be a fallback dialer
65                                 // if we fail to configure the socket to reuse ports for whatever reason.
66                                 LocalAddr: l.Addr(),
67                                 // We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction
68                                 // ourselves, although it's probably not triggered as I think the network is already
69                                 // constrained to tcp4 or tcp6 at this point.
70                                 FallbackDelay: -1,
71                                 // BitTorrent connections manage their own keep-alives.
72                                 KeepAlive: tcpListenConfig.KeepAlive,
73                                 Control: func(network, address string, c syscall.RawConn) (err error) {
74                                         controlErr := c.Control(func(fd uintptr) {
75                                                 err = setSockNoLinger(fd)
76                                                 if err != nil {
77                                                         // Failing to disable linger is undesirable, but not fatal.
78                                                         log.Printf("error setting linger socket option on tcp socket: %v", err)
79                                                 }
80                                                 err = setReusePortSockOpts(fd)
81                                         })
82                                         if err == nil {
83                                                 err = controlErr
84                                         }
85                                         return
86                                 },
87                         },
88                 },
89         }, err
90 }
91
92 type tcpSocket struct {
93         net.Listener
94         NetworkDialer
95 }
96
97 func listenAll(networks []network, getHost func(string) string, port int, f firewallCallback, logger log.Logger) ([]socket, error) {
98         if len(networks) == 0 {
99                 return nil, nil
100         }
101         var nahs []networkAndHost
102         for _, n := range networks {
103                 nahs = append(nahs, networkAndHost{n, getHost(n.String())})
104         }
105         for {
106                 ss, retry, err := listenAllRetry(nahs, port, f, logger)
107                 if !retry {
108                         return ss, err
109                 }
110         }
111 }
112
113 type networkAndHost struct {
114         Network network
115         Host    string
116 }
117
118 func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback, logger log.Logger) (ss []socket, retry bool, err error) {
119         ss = make([]socket, 1, len(nahs))
120         portStr := strconv.FormatInt(int64(port), 10)
121         ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), f, logger)
122         if err != nil {
123                 return nil, false, errors.Wrap(err, "first listen")
124         }
125         defer func() {
126                 if err != nil || retry {
127                         for _, s := range ss {
128                                 s.Close()
129                         }
130                         ss = nil
131                 }
132         }()
133         portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10)
134         for _, nah := range nahs[1:] {
135                 s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger)
136                 if err != nil {
137                         return ss,
138                                 missinggo.IsAddrInUse(err) && port == 0,
139                                 errors.Wrap(err, "subsequent listen")
140                 }
141                 ss = append(ss, s)
142         }
143         return
144 }
145
146 // This isn't aliased from go-libutp since that assumes CGO.
147 type firewallCallback func(net.Addr) bool
148
149 func listenUtp(network, addr string, fc firewallCallback, logger log.Logger) (socket, error) {
150         us, err := NewUtpSocket(network, addr, fc, logger)
151         return utpSocketSocket{us, network}, err
152 }
153
154 // utpSocket wrapper, additionally wrapped for the torrent package's socket interface.
155 type utpSocketSocket struct {
156         utpSocket
157         network string
158 }
159
160 func (me utpSocketSocket) DialerNetwork() string {
161         return me.network
162 }
163
164 func (me utpSocketSocket) Dial(ctx context.Context, addr string) (conn net.Conn, err error) {
165         defer perf.ScopeTimerErr(&err)()
166         return me.utpSocket.DialContext(ctx, me.network, addr)
167 }