Skip to content

Commit

Permalink
feat: relay v2 discovery (go-libp2p v0.19.0) (#8868)
Browse files Browse the repository at this point in the history
* update go-libp2p to v0.19.0
* chore: go-namesys v0.5.0
* refactor(config): cleanup relay handling
* docs(config): document updated defaults
* fix(tests): panic during sharness

* fix: t0160-resolve.sh
See ipfs/go-namesys#32

* fix: t0182-circuit-relay.sh
* test: transport encryption

Old tests were no longer working because go-libp2p 0.19 removed
the undocumented 'ls' pseudoprotocol.

This replaces these tests with handshake attempt (name is echoed back on
OK or 'na' is returned when protocol is not available) for tls and noise
variants + adds explicit test that safeguards us against enabling
plaintext by default by a mistake.

* fix: ./t0182-circuit-relay.sh

test is flaky, for now we just restart the testbed when we get
NO_RESERVATION error

* refactor: AutoRelayFeeder with exp. backoff

It starts at feeding peers ever 15s, then backs off each time
until it is done once an hour

Should be acceptable until we have smarter mechanism in go-lib2p 0.20

* feat(AutoRelay): prioritize Peering.Peers

This ensures we feed trusted Peering.Peers in addition to any peers
discovered over DHT.

* docs(CHANGELOG): document breaking changes

Co-authored-by: Marcin Rataj <lidel@lidel.org>
Co-authored-by: Gus Eggert <gus@gus.dev>
  • Loading branch information
3 people authored Apr 28, 2022
1 parent 4e2028d commit 232ccb4
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 171 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
- `ipfs cid codecs` command
- it now lists codecs from https://github.com/multiformats/go-multicodec
- `ipfs cid codecs --supported` can be passed to only show codecs supported in various go-ipfs commands

- `Swarm` configuration
- Daemon will refuse to start if long-deprecated RelayV1 config key `Swarm.EnableAutoRelay` or `Swarm.DisableRelay` is set to `true`
- When `Swarm.Transports.Network.Relay` is disabled, `Swarm.RelayService` and `Swarm.RelayClient` are also disabled (unless user explicitly enabled them).
- If user enabled them manually, then we error on start and inform they require `Swarm.Transports.Network.Relay`

## v0.12.2 and v0.11.1 2022-04-08

Expand Down
50 changes: 28 additions & 22 deletions core/node/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

blockstore "github.com/ipfs/go-ipfs-blockstore"
util "github.com/ipfs/go-ipfs-util"
config "github.com/ipfs/go-ipfs/config"
"github.com/ipfs/go-ipfs/config"
"github.com/ipfs/go-log"
"github.com/libp2p/go-libp2p-core/peer"
pubsub "github.com/libp2p/go-libp2p-pubsub"
Expand Down Expand Up @@ -111,52 +111,57 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {
autonat = fx.Provide(libp2p.AutoNATService(cfg.AutoNAT.Throttle))
}

// If `cfg.Swarm.DisableRelay` is set and `Network.RelayTransport` isn't, use the former.
enableRelayTransport := cfg.Swarm.Transports.Network.Relay.WithDefault(!cfg.Swarm.DisableRelay) // nolint
enableRelayTransport := cfg.Swarm.Transports.Network.Relay.WithDefault(true) // nolint
enableRelayService := cfg.Swarm.RelayService.Enabled.WithDefault(enableRelayTransport)
enableRelayClient := cfg.Swarm.RelayClient.Enabled.WithDefault(enableRelayTransport)

// Warn about a deprecated option.
// Log error when relay subsystem could not be initialized due to missing dependency
if !enableRelayTransport {
if enableRelayService {
logger.Fatal("Failed to enable `Swarm.RelayService`, it requires `Swarm.Transports.Network.Relay` to be true.")
}
if enableRelayClient {
logger.Fatal("Failed to enable `Swarm.RelayClient`, it requires `Swarm.Transports.Network.Relay` to be true.")
}
}

// Force users to migrate old config.
// nolint
if cfg.Swarm.DisableRelay {
logger.Error("The 'Swarm.DisableRelay' config field is deprecated.")
if enableRelayTransport {
logger.Error("'Swarm.DisableRelay' has been overridden by 'Swarm.Transports.Network.Relay'")
} else {
logger.Error("Use the 'Swarm.Transports.Network.Relay' config field instead")
}
logger.Fatal("The 'Swarm.DisableRelay' config field was removed." +
"Use the 'Swarm.Transports.Network.Relay' instead.")
}
// nolint
if cfg.Swarm.EnableAutoRelay {
logger.Error("The 'Swarm.EnableAutoRelay' config field is deprecated.")
if cfg.Swarm.RelayClient.Enabled == config.Default {
logger.Error("Use the 'Swarm.AutoRelay.Enabled' config field instead")
} else {
logger.Error("'Swarm.EnableAutoRelay' has been overridden by 'Swarm.AutoRelay.Enabled'")
}
logger.Fatal("The 'Swarm.EnableAutoRelay' config field was removed." +
"Use the 'Swarm.RelayClient.Enabled' instead.")
}
// nolint
if cfg.Swarm.EnableRelayHop {
logger.Fatal("The `Swarm.EnableRelayHop` config field is ignored.\n" +
logger.Fatal("The `Swarm.EnableRelayHop` config field was removed.\n" +
"Use `Swarm.RelayService` to configure the circuit v2 relay.\n" +
"If you want to continue running a circuit v1 relay, please use the standalone relay daemon: https://github.com/libp2p/go-libp2p-relay-daemon (with RelayV1.Enabled: true)")
"If you want to continue running a circuit v1 relay, please use the standalone relay daemon: https://dist.ipfs.io/#libp2p-relay-daemon (with RelayV1.Enabled: true)")
}

peerChan := make(libp2p.AddrInfoChan)
// Gather all the options
opts := fx.Options(
BaseLibP2P,

fx.Supply(peerChan),

// Services (resource management)
fx.Provide(libp2p.ResourceManager(cfg.Swarm)),
fx.Provide(libp2p.AddrFilters(cfg.Swarm.AddrFilters)),
fx.Provide(libp2p.AddrsFactory(cfg.Addresses.Announce, cfg.Addresses.AppendAnnounce, cfg.Addresses.NoAnnounce)),
fx.Provide(libp2p.SmuxTransport(cfg.Swarm.Transports)),
fx.Provide(libp2p.RelayTransport(enableRelayTransport)),
fx.Provide(libp2p.RelayService(cfg.Swarm.RelayService.Enabled.WithDefault(true), cfg.Swarm.RelayService)),
fx.Provide(libp2p.RelayService(enableRelayService, cfg.Swarm.RelayService)),
fx.Provide(libp2p.Transports(cfg.Swarm.Transports)),
fx.Invoke(libp2p.StartListening(cfg.Addresses.Swarm)),
fx.Invoke(libp2p.SetupDiscovery(cfg.Discovery.MDNS.Enabled, cfg.Discovery.MDNS.Interval)),
fx.Provide(libp2p.ForceReachability(cfg.Internal.Libp2pForceReachability)),
fx.Provide(libp2p.StaticRelays(cfg.Swarm.RelayClient.StaticRelays)),
fx.Provide(libp2p.HolePunching(cfg.Swarm.EnableHolePunching, cfg.Swarm.RelayClient.Enabled.WithDefault(false))),
fx.Provide(libp2p.HolePunching(cfg.Swarm.EnableHolePunching, enableRelayClient)),

fx.Provide(libp2p.Security(!bcfg.DisableEncryptedConnections, cfg.Swarm.Transports)),

Expand All @@ -166,7 +171,8 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {

maybeProvide(libp2p.BandwidthCounter, !cfg.Swarm.DisableBandwidthMetrics),
maybeProvide(libp2p.NatPortMap, !cfg.Swarm.DisableNatPortMap),
maybeProvide(libp2p.AutoRelay(len(cfg.Swarm.RelayClient.StaticRelays) == 0), cfg.Swarm.RelayClient.Enabled.WithDefault(false)),
maybeProvide(libp2p.AutoRelay(cfg.Swarm.RelayClient.StaticRelays, peerChan), enableRelayClient),
maybeInvoke(libp2p.AutoRelayFeeder(cfg.Peering), enableRelayClient),
autonat,
connmgr,
ps,
Expand Down
42 changes: 19 additions & 23 deletions core/node/libp2p/relay.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package libp2p

import (
config "github.com/ipfs/go-ipfs/config"
"github.com/libp2p/go-libp2p-core/peer"

"github.com/ipfs/go-ipfs/config"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
)

Expand Down Expand Up @@ -43,30 +43,26 @@ func RelayService(enable bool, relayOpts config.RelayService) func() (opts Libp2
}
}

func StaticRelays(relays []string) func() (opts Libp2pOpts, err error) {
func AutoRelay(staticRelays []string, peerChan <-chan peer.AddrInfo) func() (opts Libp2pOpts, err error) {
return func() (opts Libp2pOpts, err error) {
staticRelays := make([]peer.AddrInfo, 0, len(relays))
for _, s := range relays {
var addr *peer.AddrInfo
addr, err = peer.AddrInfoFromString(s)
if err != nil {
return
}
staticRelays = append(staticRelays, *addr)
}
var autoRelayOpts []autorelay.Option
if len(staticRelays) > 0 {
opts.Opts = append(opts.Opts, libp2p.StaticRelays(staticRelays))
static := make([]peer.AddrInfo, 0, len(staticRelays))
for _, s := range staticRelays {
var addr *peer.AddrInfo
addr, err = peer.AddrInfoFromString(s)
if err != nil {
return
}
static = append(static, *addr)
}
autoRelayOpts = append(autoRelayOpts, autorelay.WithStaticRelays(static))
autoRelayOpts = append(autoRelayOpts, autorelay.WithCircuitV1Support())
}
return
}
}

func AutoRelay(addDefaultRelays bool) func() (opts Libp2pOpts, err error) {
return func() (opts Libp2pOpts, err error) {
opts.Opts = append(opts.Opts, libp2p.EnableAutoRelay())
if addDefaultRelays {
opts.Opts = append(opts.Opts, libp2p.DefaultStaticRelays())
if peerChan != nil {
autoRelayOpts = append(autoRelayOpts, autorelay.WithPeerSource(peerChan))
}
opts.Opts = append(opts.Opts, libp2p.EnableAutoRelay(autoRelayOpts...))
return
}
}
Expand Down
90 changes: 87 additions & 3 deletions core/node/libp2p/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@ package libp2p

import (
"context"
"fmt"
"runtime/debug"
"sort"
"time"

"github.com/ipfs/go-ipfs/core/node/helpers"

config "github.com/ipfs/go-ipfs/config"
"github.com/ipfs/go-ipfs/repo"
host "github.com/libp2p/go-libp2p-core/host"
routing "github.com/libp2p/go-libp2p-core/routing"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/routing"
dht "github.com/libp2p/go-libp2p-kad-dht"
ddht "github.com/libp2p/go-libp2p-kad-dht/dual"
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
"github.com/libp2p/go-libp2p-pubsub"
pubsub "github.com/libp2p/go-libp2p-pubsub"
namesys "github.com/libp2p/go-libp2p-pubsub-router"
record "github.com/libp2p/go-libp2p-record"
routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"

"github.com/cenkalti/backoff/v4"
"go.uber.org/fx"
)

Expand Down Expand Up @@ -55,6 +60,8 @@ type processInitialRoutingOut struct {
BaseRT BaseIpfsRouting
}

type AddrInfoChan chan peer.AddrInfo

func BaseRouting(experimentalDHTClient bool) interface{} {
return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, in processInitialRoutingIn) (out processInitialRoutingOut, err error) {
var dr *ddht.DHT
Expand Down Expand Up @@ -179,3 +186,80 @@ func PubsubRouter(mctx helpers.MetricsCtx, lc fx.Lifecycle, in p2pPSRoutingIn) (
},
}, psRouter, nil
}

func AutoRelayFeeder(cfgPeering config.Peering) func(fx.Lifecycle, host.Host, AddrInfoChan, *ddht.DHT) {
return func(lc fx.Lifecycle, h host.Host, peerChan AddrInfoChan, dht *ddht.DHT) {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})

defer func() {
if r := recover(); r != nil {
fmt.Println("Recovering from unexpected error in AutoRelayFeeder:", r)
debug.PrintStack()
}
}()
go func() {
defer close(done)

// Feed peers more often right after the bootstrap, then backoff
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = 15 * time.Second
bo.Multiplier = 3
bo.MaxInterval = 1 * time.Hour
bo.MaxElapsedTime = 0 // never stop
t := backoff.NewTicker(bo)
defer t.Stop()
for {
select {
case <-t.C:
case <-ctx.Done():
return
}

// Always feed trusted IDs (Peering.Peers in the config)
for _, trustedPeer := range cfgPeering.Peers {
if len(trustedPeer.Addrs) == 0 {
continue
}
select {
case peerChan <- trustedPeer:
case <-ctx.Done():
return
}
}

// Additionally, feed closest peers discovered via DHT
if dht == nil {
/* noop due to missing dht.WAN. happens in some unit tests,
not worth fixing as we will refactor this after go-libp2p 0.20 */
continue
}
closestPeers, err := dht.WAN.GetClosestPeers(ctx, h.ID().String())
if err != nil {
// no-op: usually 'failed to find any peer in table' during startup
continue
}
for _, p := range closestPeers {
addrs := h.Peerstore().Addrs(p)
if len(addrs) == 0 {
continue
}
dhtPeer := peer.AddrInfo{ID: p, Addrs: addrs}
select {
case peerChan <- dhtPeer:
case <-ctx.Done():
return
}
}
}
}()

lc.Append(fx.Hook{
OnStop: func(_ context.Context) error {
cancel()
<-done
return nil
},
})
}
}
43 changes: 12 additions & 31 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ config file at runtime.
- [`Swarm.DisableBandwidthMetrics`](#swarmdisablebandwidthmetrics)
- [`Swarm.DisableNatPortMap`](#swarmdisablenatportmap)
- [`Swarm.EnableHolePunching`](#swarmenableholepunching)
- [`Swarm.EnableAutoRelay`](#swarmenableautorelay)
- [`Swarm.RelayClient`](#swarmrelayclient)
- [`Swarm.RelayClient.Enabled`](#swarmrelayclientenabled)
- [`Swarm.RelayClient.StaticRelays`](#swarmrelayclientstaticrelays)
Expand All @@ -122,7 +121,6 @@ config file at runtime.
- [`Swarm.RelayService.MaxReservationsPerPeer`](#swarmrelayservicemaxreservationsperpeer)
- [`Swarm.RelayService.MaxReservationsPerIP`](#swarmrelayservicemaxreservationsperip)
- [`Swarm.RelayService.MaxReservationsPerASN`](#swarmrelayservicemaxreservationsperasn)
- [`Swarm.DisableRelay`](#swarmdisablerelay)
- [`Swarm.EnableAutoNATService`](#swarmenableautonatservice)
- [`Swarm.ConnMgr`](#swarmconnmgr)
- [`Swarm.ConnMgr.Type`](#swarmconnmgrtype)
Expand Down Expand Up @@ -1376,21 +1374,9 @@ Type: `flag`

### `Swarm.EnableAutoRelay`

Deprecated: Set `Swarm.RelayClient.Enabled` to `true`.

Enables "automatic relay user" mode for this node.

Your node will automatically _use_ public relays from the network if it detects
that it cannot be reached from the public internet (e.g., it's behind a
firewall) and get a `/p2p-circuit` address from a public relay.

This is likely the feature you're looking for, but see also:
- [`Swarm.RelayService.Enabled`](#swarmrelayserviceenabled) if your node should act as a limited relay for other peers
- Docs: [Libp2p Circuit Relay](https://docs.libp2p.io/concepts/circuit-relay/)

Default: `false`
**REMOVED**

Type: `bool`
See `Swarm.RelayClient` instead.

### `Swarm.RelayClient`

Expand All @@ -1408,14 +1394,14 @@ Your node will automatically _use_ public relays from the network if it detects
that it cannot be reached from the public internet (e.g., it's behind a
firewall) and get a `/p2p-circuit` address from a public relay.

Default: `false`
Default: `true`

Type: `bool`
Type: `flag`

#### `Swarm.RelayClient.StaticRelays`

Your node will use these statically configured relay servers instead of
discovering public relays from the network.
Your node will use these statically configured relay servers (V1 or V2)
instead of discovering public relays V2 from the network.

Default: `[]`

Expand All @@ -1438,7 +1424,7 @@ NOTE: This is the service/server part of the relay system.
Disabling this will prevent this node from running as a relay server.
Use [`Swarm.RelayClient.Enabled`](#swarmrelayclientenabled) for turning your node into a relay user.

Default: Enabled
Default: `true`

Type: `flag`

Expand Down Expand Up @@ -1536,15 +1522,9 @@ Replaced with [`Swarm.RelayService.Enabled`](#swarmrelayserviceenabled).

### `Swarm.DisableRelay`

Deprecated: Set `Swarm.Transports.Network.Relay` to `false`.

Disables the p2p-circuit relay transport. This will prevent this node from
connecting to nodes behind relays, or accepting connections from nodes behind
relays.

Default: `false`
**REMOVED**

Type: `bool`
Set `Swarm.Transports.Network.Relay` to `false` instead.

### `Swarm.EnableAutoNATService`

Expand Down Expand Up @@ -1765,8 +1745,9 @@ NATs.

See also:
- Docs: [Libp2p Circuit Relay](https://docs.libp2p.io/concepts/circuit-relay/)
- [`Swarm.EnableAutoRelay`](#swarmenableautorelay) for getting a public
`/p2p-circuit` address when behind a firewall.
- [`Swarm.RelayClient.Enabled`](#swarmrelayclientenabled) for getting a public
- `/p2p-circuit` address when behind a firewall.
- [`Swarm.EnableHolePunching`](#swarmenableholepunching) for direct connection upgrade through relay
- [`Swarm.RelayService.Enabled`](#swarmrelayserviceenabled) for becoming a
limited relay for other peers

Expand Down
Loading

0 comments on commit 232ccb4

Please sign in to comment.