forked from libp2p/go-libp2p
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: don't prefer local ports from other addresses when dialing (libp…
…2p#1673) * 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 libp2p#1611 * chore: document reuseport dialer logic
- Loading branch information
Showing
6 changed files
with
118 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters