mirror of https://github.com/libp2p/go-libp2p.git
Browse Source
* fix: don't prefer local ports from other addresses when dialing This address may already be in-use (on that other address) somewhere else. Thanks to @schomatis for figuring this out. fixes #1611 * chore: document reuseport dialer logicrelease-v022
Steven Allen
2 years ago
committed by
GitHub
6 changed files with 118 additions and 169 deletions
@ -0,0 +1,114 @@ |
|||
package reuseport |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"net" |
|||
|
|||
"github.com/libp2p/go-netroute" |
|||
) |
|||
|
|||
type dialer struct { |
|||
// All address that are _not_ loopback or unspecified (0.0.0.0 or ::).
|
|||
specific []*net.TCPAddr |
|||
// All loopback addresses (127.*.*.*, ::1).
|
|||
loopback []*net.TCPAddr |
|||
// Unspecified addresses (0.0.0.0, ::)
|
|||
unspecified []*net.TCPAddr |
|||
} |
|||
|
|||
func (d *dialer) Dial(network, addr string) (net.Conn, error) { |
|||
return d.DialContext(context.Background(), network, addr) |
|||
} |
|||
|
|||
func randAddr(addrs []*net.TCPAddr) *net.TCPAddr { |
|||
if len(addrs) > 0 { |
|||
return addrs[rand.Intn(len(addrs))] |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
// DialContext dials a target addr.
|
|||
//
|
|||
// In-order:
|
|||
//
|
|||
// 1. If we're _explicitly_ listening on the prefered source address for the destination address
|
|||
// (per the system's routes), we'll use that listener's port as the source port.
|
|||
// 2. If we're listening on one or more _unspecified_ addresses (zero address), we'll pick a source
|
|||
// port from one of these listener's.
|
|||
// 3. Otherwise, we'll let the system pick the source port.
|
|||
func (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { |
|||
// We only check this case if the user is listening on a specific address (loopback or
|
|||
// otherwise). Generally, users will listen on the "unspecified" address (0.0.0.0 or ::) and
|
|||
// we can skip this section.
|
|||
//
|
|||
// This lets us avoid resolving the address twice, in most cases.
|
|||
if len(d.specific) > 0 || len(d.loopback) > 0 { |
|||
tcpAddr, err := net.ResolveTCPAddr(network, addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
ip := tcpAddr.IP |
|||
if !ip.IsLoopback() && !ip.IsGlobalUnicast() { |
|||
return nil, fmt.Errorf("undialable IP: %s", ip) |
|||
} |
|||
|
|||
// If we're listening on some specific address and that specific address happens to
|
|||
// be the preferred source address for the target destination address, we try to
|
|||
// dial with that address/port.
|
|||
//
|
|||
// We skip this check if we _aren't_ listening on any specific addresses, because
|
|||
// checking routing tables can be expensive and users rarely listen on specific IP
|
|||
// addresses.
|
|||
if len(d.specific) > 0 { |
|||
if router, err := netroute.New(); err == nil { |
|||
if _, _, preferredSrc, err := router.Route(ip); err == nil { |
|||
for _, optAddr := range d.specific { |
|||
if optAddr.IP.Equal(preferredSrc) { |
|||
return reuseDial(ctx, optAddr, network, addr) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Otherwise, if we are listening on a loopback address and the destination is also
|
|||
// a loopback address, use the port from our loopback listener.
|
|||
if len(d.loopback) > 0 && ip.IsLoopback() { |
|||
return reuseDial(ctx, randAddr(d.loopback), network, addr) |
|||
} |
|||
} |
|||
|
|||
// If we're listening on any uspecified addresses, use a randomly chosen port from one of
|
|||
// these listeners.
|
|||
if len(d.unspecified) > 0 { |
|||
return reuseDial(ctx, randAddr(d.unspecified), network, addr) |
|||
} |
|||
|
|||
// Finally, just pick a random port.
|
|||
var dialer net.Dialer |
|||
return dialer.DialContext(ctx, network, addr) |
|||
} |
|||
|
|||
func newDialer(listeners map[*listener]struct{}) *dialer { |
|||
specific := make([]*net.TCPAddr, 0) |
|||
loopback := make([]*net.TCPAddr, 0) |
|||
unspecified := make([]*net.TCPAddr, 0) |
|||
|
|||
for l := range listeners { |
|||
addr := l.Addr().(*net.TCPAddr) |
|||
if addr.IP.IsLoopback() { |
|||
loopback = append(loopback, addr) |
|||
} else if addr.IP.IsUnspecified() { |
|||
unspecified = append(unspecified, addr) |
|||
} else { |
|||
specific = append(specific, addr) |
|||
} |
|||
} |
|||
return &dialer{ |
|||
specific: specific, |
|||
loopback: loopback, |
|||
unspecified: unspecified, |
|||
} |
|||
} |
@ -1,90 +0,0 @@ |
|||
package reuseport |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"net" |
|||
|
|||
"github.com/libp2p/go-netroute" |
|||
) |
|||
|
|||
type multiDialer struct { |
|||
listeningAddresses []*net.TCPAddr |
|||
loopback []*net.TCPAddr |
|||
unspecified []*net.TCPAddr |
|||
fallback net.TCPAddr |
|||
} |
|||
|
|||
func (d *multiDialer) Dial(network, addr string) (net.Conn, error) { |
|||
return d.DialContext(context.Background(), network, addr) |
|||
} |
|||
|
|||
func randAddr(addrs []*net.TCPAddr) *net.TCPAddr { |
|||
if len(addrs) > 0 { |
|||
return addrs[rand.Intn(len(addrs))] |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
// DialContext dials a target addr.
|
|||
// Dialing preference is
|
|||
// * If there is a listener on the local interface the OS expects to use to route towards addr, use that.
|
|||
// * If there is a listener on a loopback address, addr is loopback, use that.
|
|||
// * If there is a listener on an undefined address (0.0.0.0 or ::), use that.
|
|||
// * Use the fallback IP specified during construction, with a port that's already being listened on, if one exists.
|
|||
func (d *multiDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { |
|||
tcpAddr, err := net.ResolveTCPAddr(network, addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
ip := tcpAddr.IP |
|||
if !ip.IsLoopback() && !ip.IsGlobalUnicast() { |
|||
return nil, fmt.Errorf("undialable IP: %s", ip) |
|||
} |
|||
|
|||
if router, err := netroute.New(); err == nil { |
|||
if _, _, preferredSrc, err := router.Route(ip); err == nil { |
|||
for _, optAddr := range d.listeningAddresses { |
|||
if optAddr.IP.Equal(preferredSrc) { |
|||
return reuseDial(ctx, optAddr, network, addr) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ip.IsLoopback() && len(d.loopback) > 0 { |
|||
return reuseDial(ctx, randAddr(d.loopback), network, addr) |
|||
} |
|||
if len(d.unspecified) == 0 { |
|||
return reuseDial(ctx, &d.fallback, network, addr) |
|||
} |
|||
|
|||
return reuseDial(ctx, randAddr(d.unspecified), network, addr) |
|||
} |
|||
|
|||
func newMultiDialer(unspec net.IP, listeners map[*listener]struct{}) (m dialer) { |
|||
addrs := make([]*net.TCPAddr, 0) |
|||
loopback := make([]*net.TCPAddr, 0) |
|||
unspecified := make([]*net.TCPAddr, 0) |
|||
existingPort := 0 |
|||
|
|||
for l := range listeners { |
|||
addr := l.Addr().(*net.TCPAddr) |
|||
addrs = append(addrs, addr) |
|||
if addr.IP.IsLoopback() { |
|||
loopback = append(loopback, addr) |
|||
} else if addr.IP.IsGlobalUnicast() && existingPort == 0 { |
|||
existingPort = addr.Port |
|||
} else if addr.IP.IsUnspecified() { |
|||
unspecified = append(unspecified, addr) |
|||
} |
|||
} |
|||
m = &multiDialer{ |
|||
listeningAddresses: addrs, |
|||
loopback: loopback, |
|||
unspecified: unspecified, |
|||
fallback: net.TCPAddr{IP: unspec, Port: existingPort}, |
|||
} |
|||
return |
|||
} |
@ -1,16 +0,0 @@ |
|||
package reuseport |
|||
|
|||
import ( |
|||
"context" |
|||
"net" |
|||
) |
|||
|
|||
type singleDialer net.TCPAddr |
|||
|
|||
func (d *singleDialer) Dial(network, address string) (net.Conn, error) { |
|||
return d.DialContext(context.Background(), network, address) |
|||
} |
|||
|
|||
func (d *singleDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { |
|||
return reuseDial(ctx, (*net.TCPAddr)(d), network, address) |
|||
} |
Loading…
Reference in new issue