Skip to content

Commit

Permalink
swarm: implement smart dialing logic
Browse files Browse the repository at this point in the history
  • Loading branch information
sukunrt committed Apr 25, 2023
1 parent f7a45b6 commit 67dfaba
Show file tree
Hide file tree
Showing 9 changed files with 810 additions and 135 deletions.
9 changes: 9 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ type Config struct {

DisableMetrics bool
PrometheusRegisterer prometheus.Registerer

NoDelayNetworkDialRanker bool
NetworkDialRanker network.DialRanker
}

func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swarm, error) {
Expand Down Expand Up @@ -173,6 +176,12 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
if cfg.MultiaddrResolver != nil {
opts = append(opts, swarm.WithMultiaddrResolver(cfg.MultiaddrResolver))
}
if cfg.NetworkDialRanker != nil {
opts = append(opts, swarm.WithDialRanker(cfg.NetworkDialRanker))
}
if cfg.NoDelayNetworkDialRanker {
opts = append(opts, swarm.WithNoDialDelay())
}
if enableMetrics {
opts = append(opts,
swarm.WithMetricsTracer(swarm.NewMetricsTracer(swarm.WithRegisterer(cfg.PrometheusRegisterer))))
Expand Down
10 changes: 10 additions & 0 deletions core/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,13 @@ type Dialer interface {
Notify(Notifiee)
StopNotify(Notifiee)
}

// AddrDelay provides an address along with the delay after which the address
// should be dialed
type AddrDelay struct {
Addr ma.Multiaddr
Delay time.Duration
}

// DialRanker provides a schedule of dialing the provided addresses
type DialRanker func([]ma.Multiaddr) []*AddrDelay
22 changes: 22 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,25 @@ func PrometheusRegisterer(reg prometheus.Registerer) Option {
return nil
}
}

// NetworkDialRanker configures libp2p to use d as the dial ranker
func NetworkDialRanker(d network.DialRanker) Option {
return func(cfg *Config) error {
if cfg.NetworkDialRanker != nil {
return errors.New("dial ranker already set")
}
cfg.NetworkDialRanker = d
return nil
}
}

// NoDelayNetworkDialRanker configures libp2p to not delay any address while dialing
func NoDelayNetworkDialRanker() Option {
return func(cfg *Config) error {
if cfg.NetworkDialRanker != nil {
return errors.New("dial ranker already set")
}
cfg.NoDelayNetworkDialRanker = true
return nil
}
}
131 changes: 131 additions & 0 deletions p2p/net/swarm/dial_ranker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package swarm

import (
"time"

"github.com/libp2p/go-libp2p/core/network"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)

const (
publicTCPDelay = 300 * time.Millisecond
privateTCPDelay = 30 * time.Millisecond
relayDelay = 500 * time.Millisecond
)

func noDelayRanker(addrs []ma.Multiaddr) []*network.AddrDelay {
res := make([]*network.AddrDelay, len(addrs))
for i, a := range addrs {
res[i] = &network.AddrDelay{Addr: a, Delay: 0}
}
return res
}

// defaultDialRanker is the default ranking logic.
//
// we consider private, public ip4, public ip6, relay addresses separately.
//
// In each group, if a quic address is present, we delay tcp addresses.
//
// private: 30 ms delay.
// public ip4: 300 ms delay.
// public ip6: 300 ms delay.
//
// If a quic-v1 address is present we don't dial quic or webtransport address on the same (ip,port) combination.
// If a tcp address is present we don't dial ws or wss address on the same (ip, port) combination.
// If direct addresses are present we delay all relay addresses by 500 millisecond
func defaultDialRanker(addrs []ma.Multiaddr) []*network.AddrDelay {
ip4 := make([]ma.Multiaddr, 0, len(addrs))
ip6 := make([]ma.Multiaddr, 0, len(addrs))
pvt := make([]ma.Multiaddr, 0, len(addrs))
relay := make([]ma.Multiaddr, 0, len(addrs))

res := make([]*network.AddrDelay, 0, len(addrs))
for _, a := range addrs {
switch {
case !manet.IsPublicAddr(a):
pvt = append(pvt, a)
case isProtocolAddr(a, ma.P_IP4):
ip4 = append(ip4, a)
case isProtocolAddr(a, ma.P_IP6):
ip6 = append(ip6, a)
case isRelayAddr(a):
relay = append(relay, a)
default:
res = append(res, &network.AddrDelay{Addr: a, Delay: 0})
}
}
var roffset time.Duration = 0
if len(ip4) > 0 || len(ip6) > 0 {
roffset = relayDelay
}

res = append(res, getAddrDelay(pvt, privateTCPDelay, 0)...)
res = append(res, getAddrDelay(ip4, publicTCPDelay, 0)...)
res = append(res, getAddrDelay(ip6, publicTCPDelay, 0)...)
res = append(res, getAddrDelay(relay, publicTCPDelay, roffset)...)
return res
}

func getAddrDelay(addrs []ma.Multiaddr, tcpDelay time.Duration, offset time.Duration) []*network.AddrDelay {
var hasQuic, hasQuicV1 bool
quicV1Addr := make(map[string]struct{})
tcpAddr := make(map[string]struct{})
for _, a := range addrs {
switch {
case isProtocolAddr(a, ma.P_WEBTRANSPORT):
case isProtocolAddr(a, ma.P_QUIC):
hasQuic = true
case isProtocolAddr(a, ma.P_QUIC_V1):
hasQuicV1 = true
quicV1Addr[addrPort(a, ma.P_UDP)] = struct{}{}
case isProtocolAddr(a, ma.P_WS) || isProtocolAddr(a, ma.P_WSS):
case isProtocolAddr(a, ma.P_TCP):
tcpAddr[addrPort(a, ma.P_TCP)] = struct{}{}
}
}

res := make([]*network.AddrDelay, 0, len(addrs))
for _, a := range addrs {
delay := offset
switch {
case isProtocolAddr(a, ma.P_WEBTRANSPORT):
if hasQuicV1 {
if _, ok := quicV1Addr[addrPort(a, ma.P_UDP)]; ok {
continue
}
}
case isProtocolAddr(a, ma.P_QUIC):
if hasQuicV1 {
if _, ok := quicV1Addr[addrPort(a, ma.P_UDP)]; ok {
continue
}
}
case isProtocolAddr(a, ma.P_WS) || isProtocolAddr(a, ma.P_WSS):
if _, ok := tcpAddr[addrPort(a, ma.P_TCP)]; ok {
continue
}
if hasQuic || hasQuicV1 {
delay = tcpDelay
}
case isProtocolAddr(a, ma.P_TCP):
if hasQuic || hasQuicV1 {
delay = tcpDelay
}
}
res = append(res, &network.AddrDelay{Addr: a, Delay: delay})
}
return res
}

func addrPort(a ma.Multiaddr, p int) string {
c, _ := ma.SplitFirst(a)
port, _ := a.ValueForProtocol(p)
return c.Value() + ":" + port
}

func isProtocolAddr(a ma.Multiaddr, p int) bool {
_, err := a.ValueForProtocol(p)
return err == nil
}
Loading

0 comments on commit 67dfaba

Please sign in to comment.