ceremonyclient/go-libp2p/p2p/host/autorelay/addrsplosion.go
Cassie Heart ebb31fd792 v1.4.19
2024-06-08 11:32:45 +00:00

166 lines
3.5 KiB
Go

package autorelay
import (
"encoding/binary"
ma "github.com/multiformats/go-multiaddr"
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 ma.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 ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.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
}