@cryptotaxi247 / kubo / commits / 6de542b1e

p2p/net/conn: cleaner reuse port setup

This commit cleans up the reuse port setup, and fixes a problem: make sure to filter addrs out that we simply cannot dial with (e.g. loopback -> non-loopback, or linklocal -> nonlinklocal)

Juan Batiz-Benet committed Jan 19, 2015 at 18:52 UTC 6de542b1ebaf4e2addcc19b400330cc4b95b9524
2 files changed +67 -14
p2p/net/conn/dial.go
+63 -12
@@ -5,12 +5,14 @@ import (
5 "math/rand"
6 "net"
7 "strings"
8 + "syscall"
9
10 context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
11 ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
12 manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
13 reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport"
14
15 + addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
16 peer "github.com/jbenet/go-ipfs/p2p/peer"
17 debugerror "github.com/jbenet/go-ipfs/util/debugerror"
18 )
@@ -75,8 +77,7 @@ func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote pee
77
78 // before doing anything, check we're going to be able to dial.
79 // we may not support the given address.
78 - _, _, err := manet.DialArgs(raddr)
79 - if err != nil {
80 + if _, _, err := manet.DialArgs(raddr); err != nil {
81 return nil, err
82 }
83
@@ -86,45 +87,76 @@ func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote pee
87
88 // get local addr to use.
89 laddr := pickLocalAddr(d.LocalAddrs, raddr)
89 -
90 log.Debugf("%s dialing %s -- %s --> %s", d.LocalPeer, remote, laddr, raddr)
91 +
92 if laddr != nil {
93 // dial using reuseport.Dialer, because we're probably reusing addrs.
94 // this is optimistic, as the reuseDial may fail to bind the port.
94 - if nconn, err := d.reuseDial(laddr, raddr); err == nil {
95 + if nconn, retry, reuseErr := d.reuseDial(laddr, raddr); reuseErr == nil {
96 // if it worked, wrap the raw net.Conn with our manet.Conn
97 log.Debugf("%s reuse worked! %s %s %s", d.LocalPeer, laddr, nconn.RemoteAddr(), nconn)
98 return manet.WrapNetConn(nconn)
99 + } else if !retry {
100 + // reuseDial is sure this is a legitimate dial failure, not a reuseport failure.
101 + return nil, reuseErr
102 } else {
99 - log.Debugf("%s port reuse failed: %s %s", d.LocalPeer, laddr, err)
103 + // this is a failure to reuse port. log it.
104 + log.Debugf("%s port reuse failed: %s --> %s -- %s", d.LocalPeer, laddr, raddr, reuseErr)
105 }
101 - // if not, we fall back to regular Dial without a local addr specified.
106 }
107
104 - // no local addr, or failed to reuse. just dial straight with a new port.
108 + // no local addr, or reuseport failed. just dial straight with a new port.
109 return d.Dialer.Dial(raddr)
110 }
111
108 -func (d *Dialer) reuseDial(laddr, raddr ma.Multiaddr) (net.Conn, error) {
112 +func (d *Dialer) reuseDial(laddr, raddr ma.Multiaddr) (conn net.Conn, retry bool, err error) {
113 + if laddr == nil {
114 + // if we're given no local address no sense in using reuseport to dial, dial out as usual.
115 + return nil, true, reuseport.ErrReuseFailed
116 + }
117 +
118 // give reuse.Dialer the manet.Dialer's Dialer.
119 // (wow, Dialer should've so been an interface...)
120 rd := reuseport.Dialer{d.Dialer.Dialer}
121
122 // get the local net.Addr manually
114 - var err error
123 rd.D.LocalAddr, err = manet.ToNetAddr(laddr)
124 if err != nil {
117 - return nil, err
125 + return nil, true, err // something wrong with laddr. retry without.
126 }
127
128 // get the raddr dial args for rd.dial
129 network, netraddr, err := manet.DialArgs(raddr)
130 if err != nil {
123 - return nil, err
131 + return nil, true, err // something wrong with laddr. retry without.
132 }
133
134 // rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set.
127 - return rd.Dial(network, netraddr)
135 + conn, err = rd.Dial(network, netraddr)
136 + return conn, reuseErrShouldRetry(err), err // hey! it worked!
137 +}
138 +
139 +// reuseErrShouldRetry diagnoses whether to retry after a reuse error.
140 +// if we failed to bind, we should retry. if bind worked and this is a
141 +// real dial error (remote end didnt answer) then we should not retry.
142 +func reuseErrShouldRetry(err error) bool {
143 + if err == nil {
144 + return false // hey, it worked! no need to retry.
145 + }
146 +
147 + errno, ok := err.(syscall.Errno)
148 + if !ok { // not an errno? who knows what this is. retry.
149 + return true
150 + }
151 +
152 + switch errno {
153 + case syscall.EADDRINUSE, syscall.EADDRNOTAVAIL:
154 + return true // failure to bind. retry.
155 + case syscall.ECONNREFUSED:
156 + return false // real dial error
157 + default:
158 + return true // optimistically default to retry.
159 + }
160 }
161
162 func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiaddr) {
@@ -132,11 +164,30 @@ func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiadd
164 return nil
165 }
166
167 + // make sure that we ONLY use local addrs that match the remote addr.
168 laddrs = manet.AddrMatch(raddr, laddrs)
169 if len(laddrs) < 1 {
170 return nil
171 }
172
173 + // make sure that we ONLY use local addrs that CAN dial the remote addr.
174 + // filter out all the local addrs that aren't capable
175 + raddrIPLayer := ma.Split(raddr)[0]
176 + raddrIsLoopback := manet.IsIPLoopback(raddrIPLayer)
177 + raddrIsLinkLocal := manet.IsIP6LinkLocal(raddrIPLayer)
178 + laddrs = addrutil.FilterAddrs(laddrs, func(a ma.Multiaddr) bool {
179 + laddrIPLayer := ma.Split(a)[0]
180 + laddrIsLoopback := manet.IsIPLoopback(laddrIPLayer)
181 + laddrIsLinkLocal := manet.IsIP6LinkLocal(laddrIPLayer)
182 + if laddrIsLoopback { // our loopback addrs can only dial loopbacks.
183 + return raddrIsLoopback
184 + }
185 + if laddrIsLinkLocal {
186 + return raddrIsLinkLocal // out linklocal addrs can only dial link locals.
187 + }
188 + return true
189 + })
190 +
191 // TODO pick with a good heuristic
192 // we use a random one for now to prevent bad addresses from making nodes unreachable
193 // with a random selection, multiple tries may work.
p2p/net/swarm/swarm_dial.go
+4 -2
@@ -251,8 +251,10 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) {
251 log.Warning("Dial not given PrivateKey, so WILL NOT SECURE conn.")
252 }
253
254 - // get our own addrs
255 - localAddrs := s.peers.Addresses(s.local)
254 + // get our own addrs. try dialing out from our listener addresses (reusing ports)
255 + // Note that using our peerstore's addresses here is incorrect, as that would
256 + // include observed addresses. TODO: make peerstore's address book smarter.
257 + localAddrs := s.ListenAddresses()
258 if len(localAddrs) == 0 {
259 log.Debug("Dialing out with no local addresses.")
260 }