This repository has been archived by the owner on Dec 7, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
dial.go
262 lines (219 loc) · 7.17 KB
/
dial.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package conn
import (
"context"
"fmt"
"math/rand"
"strings"
"time"
addrutil "github.com/libp2p/go-addr-util"
ci "github.com/libp2p/go-libp2p-crypto"
iconn "github.com/libp2p/go-libp2p-interface-conn"
ipnet "github.com/libp2p/go-libp2p-interface-pnet"
lgbl "github.com/libp2p/go-libp2p-loggables"
peer "github.com/libp2p/go-libp2p-peer"
transport "github.com/libp2p/go-libp2p-transport"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr-net"
msmux "github.com/multiformats/go-multistream"
)
// DialTimeout is the maximum duration a Dial is allowed to take.
// This includes the time between dialing the raw network connection,
// protocol selection as well the handshake, if applicable.
var DialTimeout = 60 * time.Second
// Dialer is an object with a peer identity that can open connections.
//
// NewDialer must be used to instantiate new Dialer objects.
type Dialer struct {
// LocalPeer is the identity of the local Peer.
LocalPeer peer.ID
// LocalAddrs is a set of local addresses to use.
//LocalAddrs []ma.Multiaddr
// Dialers are the sub-dialers usable by this dialer,
// selected in order based on the address being dialed.
Dialers []transport.Dialer
// PrivateKey used to initialize a secure connection.
// Warning: if PrivateKey is nil, connection will not be secured.
PrivateKey ci.PrivKey
// Protector makes dialer part of a private network.
// It includes implementation details how connection are protected.
// Can be nil, then dialer is in public network.
Protector ipnet.Protector
// Wrapper to wrap the raw connection. Can be nil.
Wrapper ConnWrapper
fallback transport.Dialer
}
// NewDialer creates a new Dialer object.
//
// Before any calls to Dial are made, underlying dialers must be added
// with AddDialer, and Protector (if any) must be set.
func NewDialer(p peer.ID, pk ci.PrivKey, wrap ConnWrapper) *Dialer {
return &Dialer{
LocalPeer: p,
PrivateKey: pk,
Wrapper: wrap,
fallback: new(transport.FallbackDialer),
}
}
// String returns the string representation of this Dialer.
func (d *Dialer) String() string {
return fmt.Sprintf("<Dialer %s ...>", d.LocalPeer)
}
// Dial connects to a peer over a particular address.
// The remote peer ID is only verified if secure connections are in use.
// It returns once the connection is established, the protocol negotiated,
// and the handshake complete (if applicable).
func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (c iconn.Conn, err error) {
deadline := time.Now().Add(DialTimeout)
ctx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
logdial := lgbl.Dial("conn", d.LocalPeer, remote, nil, raddr)
logdial["encrypted"] = (d.PrivateKey != nil) // log wether this will be an encrypted dial or not.
logdial["inPrivNet"] = (d.Protector != nil)
defer log.EventBegin(ctx, "connDial", logdial).Done()
if d.Protector == nil && ipnet.ForcePrivateNetwork {
log.Error("tried to dial with no Private Network Protector but usage" +
" of Private Networks is forced by the enviroment")
return nil, ipnet.ErrNotInPrivateNetwork
}
defer func() {
if err != nil {
logdial["error"] = err.Error()
logdial["dial"] = "failure"
}
}()
maconn, err := d.rawConnDial(ctx, raddr, remote)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
maconn.Close()
}
}()
if d.Protector != nil {
maconn, err = d.Protector.Protect(maconn)
if err != nil {
return nil, err
}
}
if d.Wrapper != nil {
maconn = d.Wrapper(maconn)
}
cryptoProtoChoice := SecioTag
if !iconn.EncryptConnections || d.PrivateKey == nil {
cryptoProtoChoice = NoEncryptionTag
}
selectResult := make(chan error, 1)
go func() {
selectResult <- msmux.SelectProtoOrFail(cryptoProtoChoice, maconn)
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case err = <-selectResult:
if err != nil {
return nil, err
}
}
c = newSingleConn(ctx, d.LocalPeer, remote, maconn)
if d.PrivateKey == nil || !iconn.EncryptConnections {
log.Warning("dialer %s dialing INSECURELY %s at %s!", d, remote, raddr)
return c, nil
}
c2, err := newSecureConn(ctx, d.PrivateKey, c)
if err != nil {
c.Close()
return nil, err
}
// if the connection is not to whom we thought it would be...
connRemote := c2.RemotePeer()
if connRemote != remote {
c2.Close()
return nil, fmt.Errorf("misdial to %s through %s (got %s): %s", remote, raddr, connRemote, err)
}
logdial["dial"] = "success"
return c2, nil
}
// AddDialer adds a sub-dialer usable by this dialer.
// Dialers added first will be selected first, based on the address.
func (d *Dialer) AddDialer(pd transport.Dialer) {
d.Dialers = append(d.Dialers, pd)
}
// returns dialer that can dial the given address
func (d *Dialer) subDialerForAddr(raddr ma.Multiaddr) transport.Dialer {
for _, pd := range d.Dialers {
if pd.Matches(raddr) {
return pd
}
}
if d.fallback.Matches(raddr) {
return d.fallback
}
return nil
}
// rawConnDial dials the underlying net.Conn + manet.Conns
func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (transport.Conn, error) {
if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") {
log.Event(ctx, "connDialZeroAddr", lgbl.Dial("conn", d.LocalPeer, remote, nil, raddr))
return nil, fmt.Errorf("Attempted to connect to zero address: %s", raddr)
}
sd := d.subDialerForAddr(raddr)
if sd == nil {
return nil, fmt.Errorf("no dialer for %s", raddr)
}
return sd.DialContext(ctx, raddr)
}
func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiaddr) {
if len(laddrs) < 1 {
return nil
}
// make sure that we ONLY use local addrs that match the remote addr.
laddrs = manet.AddrMatch(raddr, laddrs)
if len(laddrs) < 1 {
return nil
}
// make sure that we ONLY use local addrs that CAN dial the remote addr.
// filter out all the local addrs that aren't capable
raddrIPLayer := ma.Split(raddr)[0]
raddrIsLoopback := manet.IsIPLoopback(raddrIPLayer)
raddrIsLinkLocal := manet.IsIP6LinkLocal(raddrIPLayer)
laddrs = addrutil.FilterAddrs(laddrs, func(a ma.Multiaddr) bool {
laddrIPLayer := ma.Split(a)[0]
laddrIsLoopback := manet.IsIPLoopback(laddrIPLayer)
laddrIsLinkLocal := manet.IsIP6LinkLocal(laddrIPLayer)
if laddrIsLoopback { // our loopback addrs can only dial loopbacks.
return raddrIsLoopback
}
if laddrIsLinkLocal {
return raddrIsLinkLocal // out linklocal addrs can only dial link locals.
}
return true
})
// TODO pick with a good heuristic
// we use a random one for now to prevent bad addresses from making nodes unreachable
// with a random selection, multiple tries may work.
return laddrs[rand.Intn(len(laddrs))]
}
// MultiaddrProtocolsMatch returns whether two multiaddrs match in protocol stacks.
func MultiaddrProtocolsMatch(a, b ma.Multiaddr) bool {
ap := a.Protocols()
bp := b.Protocols()
if len(ap) != len(bp) {
return false
}
for i, api := range ap {
if api.Code != bp[i].Code {
return false
}
}
return true
}
// MultiaddrNetMatch returns the first Multiaddr found to match network.
func MultiaddrNetMatch(tgt ma.Multiaddr, srcs []ma.Multiaddr) ma.Multiaddr {
for _, a := range srcs {
if MultiaddrProtocolsMatch(tgt, a) {
return a
}
}
return nil
}