-
Notifications
You must be signed in to change notification settings - Fork 258
/
TCPConn_bind.go
133 lines (114 loc) · 3.76 KB
/
TCPConn_bind.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
//go:build !windows
// +build !windows
/*
* Copyright (c) 2015, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package psiphon
import (
"context"
"math/rand"
"net"
"syscall"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
)
// tcpDial is the platform-specific part of DialTCP
func tcpDial(ctx context.Context, addr string, config *DialConfig) (net.Conn, error) {
// Get the remote IP and port, resolving a domain name if necessary
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, errors.Trace(err)
}
ipAddrs, err := LookupIP(ctx, host, config)
if err != nil {
return nil, errors.Trace(err)
}
if len(ipAddrs) < 1 {
return nil, errors.TraceNew("no IP address")
}
// When configured, attempt to synthesize IPv6 addresses from
// an IPv4 addresses for compatibility on DNS64/NAT64 networks.
// If synthesize fails, try the original addresses.
if config.IPv6Synthesizer != nil {
for i, ipAddr := range ipAddrs {
if ipAddr.To4() != nil {
synthesizedIPAddress := config.IPv6Synthesizer.IPv6Synthesize(ipAddr.String())
if synthesizedIPAddress != "" {
synthesizedAddr := net.ParseIP(synthesizedIPAddress)
if synthesizedAddr != nil {
ipAddrs[i] = synthesizedAddr
}
}
}
}
}
// Iterate over a pseudorandom permutation of the destination
// IPs and attempt connections.
//
// Only continue retrying as long as the dial context is not
// done. Unlike net.Dial, we do not fractionalize the context
// deadline, as the dial is generally intended to apply to a
// single attempt. So these serial retries are most useful in
// cases of immediate failure, such as "no route to host"
// errors when a host resolves to both IPv4 and IPv6 but IPv6
// addresses are unreachable.
//
// Retries at higher levels cover other cases: e.g.,
// Controller.remoteServerListFetcher will retry its entire
// operation and tcpDial will try a new permutation; or similarly,
// Controller.establishCandidateGenerator will retry a candidate
// tunnel server dials.
permutedIndexes := rand.Perm(len(ipAddrs))
lastErr := errors.TraceNew("unknown error")
for _, index := range permutedIndexes {
dialer := &net.Dialer{
Control: func(_, _ string, c syscall.RawConn) error {
var controlErr error
err := c.Control(func(fd uintptr) {
socketFD := int(fd)
setAdditionalSocketOptions(socketFD)
if config.BPFProgramInstructions != nil {
err := setSocketBPF(config.BPFProgramInstructions, socketFD)
if err != nil {
controlErr = errors.Tracef("setSocketBPF failed: %s", err)
return
}
}
if config.DeviceBinder != nil {
_, err := config.DeviceBinder.BindToDevice(socketFD)
if err != nil {
controlErr = errors.Tracef("BindToDevice failed: %s", err)
return
}
}
})
if controlErr != nil {
return errors.Trace(controlErr)
}
return errors.Trace(err)
},
}
conn, err := dialer.DialContext(
ctx, "tcp", net.JoinHostPort(ipAddrs[index].String(), port))
if err != nil {
lastErr = errors.Trace(err)
continue
}
return &TCPConn{Conn: conn}, nil
}
return nil, lastErr
}