-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #598 from libp2p/feat/moar-relays
autorelay: curtail addrsplosion
- Loading branch information
Showing
5 changed files
with
419 additions
and
88 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package relay | ||
|
||
import ( | ||
"encoding/binary" | ||
|
||
circuit "github.com/libp2p/go-libp2p-circuit" | ||
ma "github.com/multiformats/go-multiaddr" | ||
dns "github.com/multiformats/go-multiaddr-dns" | ||
manet "github.com/multiformats/go-multiaddr-net" | ||
) | ||
|
||
// This function cleans up a relay's address set to remove private addresses and curtail | ||
// addrsplosion. | ||
func cleanupAddressSet(addrs []ma.Multiaddr) []ma.Multiaddr { | ||
var public, private []ma.Multiaddr | ||
|
||
for _, a := range addrs { | ||
if isRelayAddr(a) { | ||
continue | ||
} | ||
|
||
if manet.IsPublicAddr(a) || isDNSAddr(a) { | ||
public = append(public, a) | ||
continue | ||
} | ||
|
||
// discard unroutable addrs | ||
if manet.IsPrivateAddr(a) { | ||
private = append(private, a) | ||
} | ||
} | ||
|
||
if !hasAddrsplosion(public) { | ||
return public | ||
} | ||
|
||
return sanitizeAddrsplodedSet(public, private) | ||
} | ||
|
||
func isRelayAddr(a ma.Multiaddr) bool { | ||
isRelay := false | ||
|
||
ma.ForEach(a, func(c ma.Component) bool { | ||
switch c.Protocol().Code { | ||
case circuit.P_CIRCUIT: | ||
isRelay = true | ||
return false | ||
default: | ||
return true | ||
} | ||
}) | ||
|
||
return isRelay | ||
} | ||
|
||
func isDNSAddr(a ma.Multiaddr) bool { | ||
if first, _ := ma.SplitFirst(a); first != nil { | ||
switch first.Protocol().Code { | ||
case dns.P_DNS4, dns.P_DNS6, dns.P_DNSADDR: | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// we have addrsplosion if for some protocol we advertise multiple ports on | ||
// the same base address. | ||
func hasAddrsplosion(addrs []ma.Multiaddr) bool { | ||
aset := make(map[string]int) | ||
|
||
for _, a := range addrs { | ||
key, port := addrKeyAndPort(a) | ||
xport, ok := aset[key] | ||
if ok && port != xport { | ||
return true | ||
} | ||
aset[key] = port | ||
} | ||
|
||
return false | ||
} | ||
|
||
func addrKeyAndPort(a ma.Multiaddr) (string, int) { | ||
var ( | ||
key string | ||
port int | ||
) | ||
|
||
ma.ForEach(a, func(c ma.Component) bool { | ||
switch c.Protocol().Code { | ||
case ma.P_TCP, ma.P_UDP: | ||
port = int(binary.BigEndian.Uint16(c.RawValue())) | ||
key += "/" + c.Protocol().Name | ||
default: | ||
val := c.Value() | ||
if val == "" { | ||
val = c.Protocol().Name | ||
} | ||
key += "/" + val | ||
} | ||
return true | ||
}) | ||
|
||
return key, port | ||
} | ||
|
||
// clean up addrsplosion | ||
// the following heuristic is used: | ||
// - for each base address/protocol combination, if there are multiple ports advertised then | ||
// only accept the default port if present. | ||
// - If the default port is not present, we check for non-standard ports by tracking | ||
// private port bindings if present. | ||
// - If there is no default or private port binding, then we can't infer the correct | ||
// port and give up and return all addrs (for that base address) | ||
func sanitizeAddrsplodedSet(public, private []ma.Multiaddr) []ma.Multiaddr { | ||
type portAndAddr struct { | ||
addr ma.Multiaddr | ||
port int | ||
} | ||
|
||
privports := make(map[int]struct{}) | ||
pubaddrs := make(map[string][]portAndAddr) | ||
|
||
for _, a := range private { | ||
_, port := addrKeyAndPort(a) | ||
privports[port] = struct{}{} | ||
} | ||
|
||
for _, a := range public { | ||
key, port := addrKeyAndPort(a) | ||
pubaddrs[key] = append(pubaddrs[key], portAndAddr{addr: a, port: port}) | ||
} | ||
|
||
var result []ma.Multiaddr | ||
for _, pas := range pubaddrs { | ||
if len(pas) == 1 { | ||
// it's not addrsploded | ||
result = append(result, pas[0].addr) | ||
continue | ||
} | ||
|
||
haveAddr := false | ||
for _, pa := range pas { | ||
if _, ok := privports[pa.port]; ok { | ||
// it matches a privately bound port, use it | ||
result = append(result, pa.addr) | ||
haveAddr = true | ||
continue | ||
} | ||
|
||
if pa.port == 4001 || pa.port == 4002 { | ||
// it's a default port, use it | ||
result = append(result, pa.addr) | ||
haveAddr = true | ||
} | ||
} | ||
|
||
if !haveAddr { | ||
// we weren't able to select a port; bite the bullet and use them all | ||
for _, pa := range pas { | ||
result = append(result, pa.addr) | ||
} | ||
} | ||
} | ||
|
||
return result | ||
} |
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,122 @@ | ||
package relay | ||
|
||
import ( | ||
"testing" | ||
|
||
ma "github.com/multiformats/go-multiaddr" | ||
_ "github.com/multiformats/go-multiaddr-dns" | ||
) | ||
|
||
func TestCleanupAddrs(t *testing.T) { | ||
// test with no addrsplosion | ||
addrs := makeAddrList( | ||
"/ip4/127.0.0.1/tcp/4001", | ||
"/ip4/127.0.0.1/udp/4002/quic", | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
"/dnsaddr/somedomain.com/tcp/4002/ws", | ||
) | ||
clean := makeAddrList( | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
"/dnsaddr/somedomain.com/tcp/4002/ws", | ||
) | ||
|
||
r := cleanupAddressSet(addrs) | ||
if !sameAddrs(clean, r) { | ||
t.Fatal("cleaned up set doesn't match expected") | ||
} | ||
|
||
// test with default port addrspolosion | ||
addrs = makeAddrList( | ||
"/ip4/127.0.0.1/tcp/4001", | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/tcp/33333", | ||
"/ip4/1.2.3.4/tcp/33334", | ||
"/ip4/1.2.3.4/tcp/33335", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
) | ||
clean = makeAddrList( | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
) | ||
r = cleanupAddressSet(addrs) | ||
if !sameAddrs(clean, r) { | ||
t.Fatal("cleaned up set doesn't match expected") | ||
} | ||
|
||
// test with default port addrsplosion but no private addrs | ||
addrs = makeAddrList( | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/tcp/33333", | ||
"/ip4/1.2.3.4/tcp/33334", | ||
"/ip4/1.2.3.4/tcp/33335", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
) | ||
clean = makeAddrList( | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/udp/4002/quic", | ||
) | ||
r = cleanupAddressSet(addrs) | ||
if !sameAddrs(clean, r) { | ||
t.Fatal("cleaned up set doesn't match expected") | ||
} | ||
|
||
// test with non-standard port addrsplosion | ||
addrs = makeAddrList( | ||
"/ip4/127.0.0.1/tcp/12345", | ||
"/ip4/1.2.3.4/tcp/12345", | ||
"/ip4/1.2.3.4/tcp/33333", | ||
"/ip4/1.2.3.4/tcp/33334", | ||
"/ip4/1.2.3.4/tcp/33335", | ||
) | ||
clean = makeAddrList( | ||
"/ip4/1.2.3.4/tcp/12345", | ||
) | ||
r = cleanupAddressSet(addrs) | ||
if !sameAddrs(clean, r) { | ||
t.Fatal("cleaned up set doesn't match expected") | ||
} | ||
|
||
// test with a squeaky clean address set | ||
addrs = makeAddrList( | ||
"/ip4/1.2.3.4/tcp/4001", | ||
"/ip4/1.2.3.4/udp/4001/quic", | ||
) | ||
clean = addrs | ||
r = cleanupAddressSet(addrs) | ||
if !sameAddrs(clean, r) { | ||
t.Fatal("cleaned up set doesn't match expected") | ||
} | ||
} | ||
|
||
func makeAddrList(strs ...string) []ma.Multiaddr { | ||
result := make([]ma.Multiaddr, 0, len(strs)) | ||
for _, s := range strs { | ||
a := ma.StringCast(s) | ||
result = append(result, a) | ||
} | ||
return result | ||
} | ||
|
||
func sameAddrs(as, bs []ma.Multiaddr) bool { | ||
if len(as) != len(bs) { | ||
return false | ||
} | ||
|
||
for _, a := range as { | ||
if !findAddr(a, bs) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func findAddr(a ma.Multiaddr, bs []ma.Multiaddr) bool { | ||
for _, b := range bs { | ||
if a.Equal(b) { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
Oops, something went wrong.