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