-
-
Notifications
You must be signed in to change notification settings - Fork 4k
/
public.go
192 lines (173 loc) · 5.8 KB
/
public.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package dialer
import (
"context"
"errors"
"fmt"
"net"
"time"
"github.com/syncthing/syncthing/lib/connections/registry"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.org/x/net/proxy"
)
var errUnexpectedInterfaceType = errors.New("unexpected interface type")
// SetTCPOptions sets our default TCP options on a TCP connection, possibly
// digging through dialerConn to extract the *net.TCPConn
func SetTCPOptions(conn net.Conn) error {
switch conn := conn.(type) {
case dialerConn:
return SetTCPOptions(conn.Conn)
case *net.TCPConn:
var err error
if err = conn.SetLinger(0); err != nil {
return err
}
if err = conn.SetNoDelay(false); err != nil {
return err
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
return err
}
if err = conn.SetKeepAlive(true); err != nil {
return err
}
return nil
default:
return fmt.Errorf("unknown connection type %T", conn)
}
}
func SetTrafficClass(conn net.Conn, class int) error {
switch conn := conn.(type) {
case dialerConn:
return SetTrafficClass(conn.Conn, class)
case *net.TCPConn:
e1 := ipv4.NewConn(conn).SetTOS(class)
e2 := ipv6.NewConn(conn).SetTrafficClass(class)
if e1 != nil {
return e1
}
return e2
default:
return fmt.Errorf("unknown connection type %T", conn)
}
}
func dialContextWithFallback(ctx context.Context, fallback proxy.ContextDialer, network, addr string) (net.Conn, error) {
dialer, ok := proxy.FromEnvironment().(proxy.ContextDialer)
if !ok {
return nil, errUnexpectedInterfaceType
}
if dialer == proxy.Direct {
conn, err := fallback.DialContext(ctx, network, addr)
l.Debugf("Dialing direct result %s %s: %v %v", network, addr, conn, err)
return conn, err
}
if noFallback {
conn, err := dialer.DialContext(ctx, network, addr)
l.Debugf("Dialing no fallback result %s %s: %v %v", network, addr, conn, err)
if err != nil {
return nil, err
}
return dialerConn{conn, newDialerAddr(network, addr)}, nil
}
proxyDialFudgeAddress := func(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
return dialerConn{conn, newDialerAddr(network, addr)}, err
}
return dialTwicePreferFirst(ctx, proxyDialFudgeAddress, fallback.DialContext, "proxy", "fallback", network, addr)
}
// DialContext dials via context and/or directly, depending on how it is configured.
// If dialing via proxy and allowing fallback, dialing for both happens simultaneously
// and the proxy connection is returned if successful.
func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return dialContextWithFallback(ctx, proxy.Direct, network, addr)
}
// DialContextReusePort tries dialing via proxy if a proxy is configured, and falls back to
// a direct connection reusing the port from the connections registry, if no proxy is defined, or connecting via proxy
// fails. It also in parallel dials without reusing the port, just in case reusing the port affects routing decisions badly.
func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Context, network, addr string) (net.Conn, error) {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
// If proxy is configured, there is no point trying to reuse listen addresses.
if proxy.FromEnvironment() != proxy.Direct {
return DialContext(ctx, network, addr)
}
localAddrInterface := registry.Get(network, func(addr interface{}) bool {
return addr.(*net.TCPAddr).IP.IsUnspecified()
})
if localAddrInterface == nil {
// Nothing listening, nothing to reuse.
return DialContext(ctx, network, addr)
}
laddr, ok := localAddrInterface.(*net.TCPAddr)
if !ok {
return nil, errUnexpectedInterfaceType
}
// Dial twice, once reusing the listen address, another time not reusing it, just in case reusing the address
// influences routing and we fail to reach our destination.
dialer := net.Dialer{
Control: ReusePortControl,
LocalAddr: laddr,
}
return dialTwicePreferFirst(ctx, dialer.DialContext, (&net.Dialer{}).DialContext, "reuse", "non-reuse", network, addr)
}
}
type dialFunc func(ctx context.Context, network, address string) (net.Conn, error)
func dialTwicePreferFirst(ctx context.Context, first, second dialFunc, firstName, secondName, network, address string) (net.Conn, error) {
// Delay second dial by some time.
sleep := time.Second
if deadline, ok := ctx.Deadline(); ok {
timeout := time.Until(deadline)
if timeout > 0 {
sleep = timeout / 3
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var firstConn, secondConn net.Conn
var firstErr, secondErr error
firstDone := make(chan struct{})
secondDone := make(chan struct{})
go func() {
firstConn, firstErr = first(ctx, network, address)
l.Debugf("Dialing %s result %s %s: %v %v", firstName, network, address, firstConn, firstErr)
close(firstDone)
}()
go func() {
select {
case <-firstDone:
if firstErr == nil {
// First succeeded, no point doing anything in second
secondErr = errors.New("didn't dial")
close(secondDone)
return
}
case <-ctx.Done():
secondErr = ctx.Err()
close(secondDone)
return
case <-time.After(sleep):
}
secondConn, secondErr = second(ctx, network, address)
l.Debugf("Dialing %s result %s %s: %v %v", secondName, network, address, secondConn, secondErr)
close(secondDone)
}()
<-firstDone
if firstErr == nil {
go func() {
<-secondDone
if secondConn != nil {
_ = secondConn.Close()
}
}()
return firstConn, firstErr
}
<-secondDone
return secondConn, secondErr
}