]> Sergey Matveev's repositories - btrtrc.git/blob - socket.go
Improve error handling for listening
[btrtrc.git] / socket.go
1 package torrent
2
3 import (
4         "context"
5         "fmt"
6         "net"
7         "net/url"
8         "strconv"
9         "strings"
10
11         "github.com/anacrolix/missinggo"
12         "github.com/anacrolix/missinggo/perf"
13         "github.com/pkg/errors"
14         "golang.org/x/net/proxy"
15 )
16
17 type dialer interface {
18         dial(_ context.Context, addr string) (net.Conn, error)
19 }
20
21 type socket interface {
22         net.Listener
23         dialer
24 }
25
26 func getProxyDialer(proxyURL string) (proxy.Dialer, error) {
27         fixedURL, err := url.Parse(proxyURL)
28         if err != nil {
29                 return nil, err
30         }
31
32         return proxy.FromURL(fixedURL, proxy.Direct)
33 }
34
35 func listen(network, addr, proxyURL string, f firewallCallback) (socket, error) {
36         if isTcpNetwork(network) {
37                 return listenTcp(network, addr, proxyURL)
38         } else if isUtpNetwork(network) {
39                 return listenUtp(network, addr, proxyURL, f)
40         } else {
41                 panic(fmt.Sprintf("unknown network %q", network))
42         }
43 }
44
45 func isTcpNetwork(s string) bool {
46         return strings.Contains(s, "tcp")
47 }
48
49 func isUtpNetwork(s string) bool {
50         return strings.Contains(s, "utp") || strings.Contains(s, "udp")
51 }
52
53 func listenTcp(network, address, proxyURL string) (s socket, err error) {
54         l, err := net.Listen(network, address)
55         if err != nil {
56                 return
57         }
58         defer func() {
59                 if err != nil {
60                         l.Close()
61                 }
62         }()
63
64         // If we don't need the proxy - then we should return default net.Dialer,
65         // otherwise, let's try to parse the proxyURL and return proxy.Dialer
66         if len(proxyURL) != 0 {
67                 // TODO: The error should be propagated, as proxy may be in use for
68                 // security or privacy reasons. Also just pass proxy.Dialer in from
69                 // the Config.
70                 if dialer, err := getProxyDialer(proxyURL); err == nil {
71                         return tcpSocket{l, func(ctx context.Context, addr string) (conn net.Conn, err error) {
72                                 defer perf.ScopeTimerErr(&err)()
73                                 return dialer.Dial(network, addr)
74                         }}, nil
75                 }
76         }
77         dialer := net.Dialer{}
78         return tcpSocket{l, func(ctx context.Context, addr string) (conn net.Conn, err error) {
79                 defer perf.ScopeTimerErr(&err)()
80                 return dialer.DialContext(ctx, network, addr)
81         }}, nil
82 }
83
84 type tcpSocket struct {
85         net.Listener
86         d func(ctx context.Context, addr string) (net.Conn, error)
87 }
88
89 func (me tcpSocket) dial(ctx context.Context, addr string) (net.Conn, error) {
90         return me.d(ctx, addr)
91 }
92
93 func setPort(addr string, port int) string {
94         host, _, err := net.SplitHostPort(addr)
95         if err != nil {
96                 panic(err)
97         }
98         return net.JoinHostPort(host, strconv.FormatInt(int64(port), 10))
99 }
100
101 func listenAll(networks []string, getHost func(string) string, port int, proxyURL string, f firewallCallback) ([]socket, error) {
102         if len(networks) == 0 {
103                 return nil, nil
104         }
105         var nahs []networkAndHost
106         for _, n := range networks {
107                 nahs = append(nahs, networkAndHost{n, getHost(n)})
108         }
109         for {
110                 ss, retry, err := listenAllRetry(nahs, port, proxyURL, f)
111                 if !retry {
112                         return ss, err
113                 }
114         }
115 }
116
117 type networkAndHost struct {
118         Network string
119         Host    string
120 }
121
122 func listenAllRetry(nahs []networkAndHost, port int, proxyURL string, f firewallCallback) (ss []socket, retry bool, err error) {
123         ss = make([]socket, 1, len(nahs))
124         portStr := strconv.FormatInt(int64(port), 10)
125         ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), proxyURL, f)
126         if err != nil {
127                 return nil, false, errors.Wrap(err, "first listen")
128         }
129         defer func() {
130                 if err != nil || retry {
131                         for _, s := range ss {
132                                 s.Close()
133                         }
134                         ss = nil
135                 }
136         }()
137         portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10)
138         for _, nah := range nahs[1:] {
139                 s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), proxyURL, f)
140                 if err != nil {
141                         return ss,
142                                 missinggo.IsAddrInUse(err) && port == 0,
143                                 errors.Wrap(err, "subsequent listen")
144                 }
145                 ss = append(ss, s)
146         }
147         return
148 }
149
150 type firewallCallback func(net.Addr) bool
151
152 func listenUtp(network, addr, proxyURL string, fc firewallCallback) (s socket, err error) {
153         us, err := NewUtpSocket(network, addr, fc)
154         if err != nil {
155                 return
156         }
157
158         // If we don't need the proxy - then we should return default net.Dialer,
159         // otherwise, let's try to parse the proxyURL and return proxy.Dialer
160         if len(proxyURL) != 0 {
161                 if dialer, err := getProxyDialer(proxyURL); err == nil {
162                         return utpSocketSocket{us, network, dialer}, nil
163                 }
164         }
165
166         return utpSocketSocket{us, network, nil}, nil
167 }
168
169 type utpSocketSocket struct {
170         utpSocket
171         network string
172         d       proxy.Dialer
173 }
174
175 func (me utpSocketSocket) dial(ctx context.Context, addr string) (conn net.Conn, err error) {
176         defer perf.ScopeTimerErr(&err)()
177         if me.d != nil {
178                 return me.d.Dial(me.network, addr)
179         }
180
181         return me.utpSocket.DialContext(ctx, me.network, addr)
182 }