Skip to content

Commit

Permalink
feat(libp2p): shared TCP listeners and AutoTLS.AutoWSS (#10565)
Browse files Browse the repository at this point in the history
* feat(libp2p): enable shared TCP listeners

* docs: switch mentions of /ws to /tcp/4001

* feat: AutoTLS.AutoWSS

This adds AutoTLS.AutoWSS flag that is set to true by default.

It will check if Addresses.Swarm contain explicit /ws listener,
and if not found, it will append one per every /tcp listener

This way existing TCP ports are reused without any extra configuration,
but we don't break user's who have custom / explicit /ws listener
already.

I also moved logger around, to include Addresses.Swarm inspection
results in `autotls` logger.

* chore: go-libp2p v0.38.1

https://github.com/libp2p/go-libp2p/releases/tag/v0.38.0
https://github.com/libp2p/go-libp2p/releases/tag/v0.38.1

* docs: AutoTLS.AutoWSS and go-libp2p v0.38.x

* chore: p2p-forge/client v0.2.0

https://github.com/ipshipyard/p2p-forge/releases/tag/v0.2.0

* fix: disable libp2p.ShareTCPListener() in PNET

* chore(ci): timeout sharness after 15m

average successful run is  <9 minutes, no need to wait for 20
https://github.com/ipfs/kubo/actions/workflows/sharness.yml?query=is%3Asuccess

---------

Co-authored-by: Andrew Gillis <[email protected]>
Co-authored-by: Marcin Rataj <[email protected]>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent f2c1905 commit 397c346
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sharness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
sharness-test:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ${{ fromJSON(github.repository == 'ipfs/kubo' && '["self-hosted", "linux", "x64", "4xlarge"]' || '"ubuntu-latest"') }}
timeout-minutes: 20
timeout-minutes: ${{ github.repository == 'ipfs/kubo' && 15 || 60 }}
defaults:
run:
shell: bash
Expand Down
18 changes: 13 additions & 5 deletions cmd/ipfs/kubo/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,21 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
}
}

// Private setups can't leverage peers returned by default IPNIs (Routing.Type=auto)
// To avoid breaking existing setups, switch them to DHT-only.
if routingOption == routingOptionAutoKwd {
if key, _ := repo.SwarmKey(); key != nil || pnet.ForcePrivateNetwork {
if key, _ := repo.SwarmKey(); key != nil || pnet.ForcePrivateNetwork {
// Private setups can't leverage peers returned by default IPNIs (Routing.Type=auto)
// To avoid breaking existing setups, switch them to DHT-only.
if routingOption == routingOptionAutoKwd {
log.Error("Private networking (swarm.key / LIBP2P_FORCE_PNET) does not work with public HTTP IPNIs enabled by Routing.Type=auto. Kubo will use Routing.Type=dht instead. Update config to remove this message.")
routingOption = routingOptionDHTKwd
}

// Private setups should not use public AutoTLS infrastructure
// as it will leak their existence and PeerID identity to CA
// and they will show up at https://crt.sh/?q=libp2p.direct
// Below ensures we hard fail if someone tries to enable both
if cfg.AutoTLS.Enabled.WithDefault(config.DefaultAutoTLSEnabled) {
return errors.New("private networking (swarm.key / LIBP2P_FORCE_PNET) does not work with AutoTLS.Enabled=true, update config to remove this message")
}
}

switch routingOption {
Expand Down Expand Up @@ -467,7 +475,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
fmt.Printf("Swarm key fingerprint: %x\n", node.PNetFingerprint)
}

if (pnet.ForcePrivateNetwork || node.PNetFingerprint != nil) && routingOption == routingOptionAutoKwd {
if (pnet.ForcePrivateNetwork || node.PNetFingerprint != nil) && (routingOption == routingOptionAutoKwd || routingOption == routingOptionAutoClientKwd) {
// This should never happen, but better safe than sorry
log.Fatal("Private network does not work with Routing.Type=auto. Update your config to Routing.Type=dht (or none, and do manual peering)")
}
Expand Down
6 changes: 5 additions & 1 deletion config/autotls.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import p2pforge "github.com/ipshipyard/p2p-forge/client"
// for obtaining a domain and TLS certificate to improve connectivity for web
// browser clients. More: https://github.com/ipshipyard/p2p-forge#readme
type AutoTLS struct {
// Enables the p2p-forge feature
// Enables the p2p-forge feature and all related features.
Enabled Flag `json:",omitempty"`

// Optional, controls if Kubo should add /tls/sni/.../ws listener to every /tcp port if no explicit /ws is defined in Addresses.Swarm
AutoWSS Flag `json:",omitempty"`

// Optional override of the parent domain that will be used
DomainSuffix *OptionalString `json:",omitempty"`

Expand All @@ -27,4 +30,5 @@ const (
DefaultDomainSuffix = p2pforge.DefaultForgeDomain
DefaultRegistrationEndpoint = p2pforge.DefaultForgeEndpoint
DefaultCAEndpoint = p2pforge.DefaultCAEndpoint
DefaultAutoWSS = true // requires AutoTLS.Enabled
)
47 changes: 44 additions & 3 deletions core/node/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -115,6 +116,8 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
enableRelayService := cfg.Swarm.RelayService.Enabled.WithDefault(enableRelayTransport)
enableRelayClient := cfg.Swarm.RelayClient.Enabled.WithDefault(enableRelayTransport)
enableAutoTLS := cfg.AutoTLS.Enabled.WithDefault(config.DefaultAutoTLSEnabled)
enableAutoWSS := cfg.AutoTLS.AutoWSS.WithDefault(config.DefaultAutoWSS)
atlsLog := log.Logger("autotls")

// Log error when relay subsystem could not be initialized due to missing dependency
if !enableRelayTransport {
Expand All @@ -125,21 +128,59 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
logger.Fatal("Failed to enable `Swarm.RelayClient`, it requires `Swarm.Transports.Network.Relay` to be true.")
}
}

if enableAutoTLS {
if !cfg.Swarm.Transports.Network.TCP.WithDefault(true) {
logger.Fatal("Invalid configuration: AutoTLS.Enabled=true requires Swarm.Transports.Network.TCP to be true as well.")
}
if !cfg.Swarm.Transports.Network.Websocket.WithDefault(true) {
logger.Fatal("Invalid configuration: AutoTLS.Enabled=true requires Swarm.Transports.Network.Websocket to be true as well.")
}

// AutoTLS for Secure WebSockets: ensure WSS listeners are in place (manual or automatic)
wssWildcard := fmt.Sprintf("/tls/sni/*.%s/ws", cfg.AutoTLS.DomainSuffix.WithDefault(config.DefaultDomainSuffix))
wssWildcardPresent := false
customWsPresent := false
customWsRegex := regexp.MustCompile(`/wss?$`)
tcpRegex := regexp.MustCompile(`/tcp/\d+$`)

// inspect listeners defined in config at Addresses.Swarm
var tcpListeners []string
for _, listener := range cfg.Addresses.Swarm {
// detect if user manually added /tls/sni/.../ws listener matching AutoTLS.DomainSuffix
if strings.Contains(listener, wssWildcard) {
atlsLog.Infof("found compatible wildcard listener in Addresses.Swarm. AutoTLS will be used on %s", listener)
wssWildcardPresent = true
break
}
// detect if user manually added own /ws or /wss listener that is
// not related to AutoTLS feature
if customWsRegex.MatchString(listener) {
atlsLog.Infof("found custom /ws listener set by user in Addresses.Swarm. AutoTLS will not be used on %s.", listener)
customWsPresent = true
break
}
// else, remember /tcp listeners that can be reused for /tls/sni/../ws
if tcpRegex.MatchString(listener) {
tcpListeners = append(tcpListeners, listener)
}
}
if !wssWildcardPresent {
logger.Fatal(fmt.Sprintf("Invalid configuration: AutoTLS.Enabled=true requires a catch-all Addresses.Swarm listener ending with %q to be present, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls", wssWildcard))

// Append AutoTLS's wildcard listener
// if no manual /ws listener was set by the user
if enableAutoWSS && !wssWildcardPresent && !customWsPresent {
if len(tcpListeners) == 0 {
logger.Fatal("Invalid configuration: AutoTLS.AutoWSS=true requires at least one /tcp listener present in Addresses.Swarm, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls")
}
for _, tcpListener := range tcpListeners {
wssListener := tcpListener + wssWildcard
cfg.Addresses.Swarm = append(cfg.Addresses.Swarm, wssListener)
atlsLog.Infof("appended AutoWSS listener: %s", wssListener)
}
}

if !wssWildcardPresent && !enableAutoWSS {
logger.Fatal(fmt.Sprintf("Invalid configuration: AutoTLS.Enabled=true requires a /tcp listener ending with %q to be present in Addresses.Swarm or AutoTLS.AutoWSS=true, see https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls", wssWildcard))
}
}

Expand All @@ -152,7 +193,7 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part

// Services (resource management)
fx.Provide(libp2p.ResourceManager(bcfg.Repo.Path(), cfg.Swarm, userResourceOverrides)),
maybeProvide(libp2p.P2PForgeCertMgr(bcfg.Repo.Path(), cfg.AutoTLS), enableAutoTLS),
maybeProvide(libp2p.P2PForgeCertMgr(bcfg.Repo.Path(), cfg.AutoTLS, atlsLog), enableAutoTLS),
maybeInvoke(libp2p.StartP2PAutoTLS, enableAutoTLS),
fx.Provide(libp2p.AddrFilters(cfg.Swarm.AddrFilters)),
fx.Provide(libp2p.AddrsFactory(cfg.Addresses.Announce, cfg.Addresses.AppendAnnounce, cfg.Addresses.NoAnnounce)),
Expand Down
11 changes: 5 additions & 6 deletions core/node/libp2p/addrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,20 @@ func ListenOn(addresses []string) interface{} {
}
}

func P2PForgeCertMgr(repoPath string, cfg config.AutoTLS) interface{} {
func P2PForgeCertMgr(repoPath string, cfg config.AutoTLS, atlsLog *logging.ZapEventLogger) interface{} {
return func() (*p2pforge.P2PForgeCertMgr, error) {
storagePath := filepath.Join(repoPath, "p2p-forge-certs")

forgeLogger := logging.Logger("autotls").Desugar()

// TODO: this should not be necessary, but we do it to help tracking
// down any race conditions causing
// https://github.com/ipshipyard/p2p-forge/issues/8
certmagic.Default.Logger = forgeLogger.Named("default_fixme")
certmagic.DefaultACME.Logger = forgeLogger.Named("default_acme_client_fixme")
rawLogger := atlsLog.Desugar()
certmagic.Default.Logger = rawLogger.Named("default_fixme")
certmagic.DefaultACME.Logger = rawLogger.Named("default_acme_client_fixme")

certStorage := &certmagic.FileStorage{Path: storagePath}
certMgr, err := p2pforge.NewP2PForgeCertMgr(
p2pforge.WithLogger(forgeLogger.Sugar()),
p2pforge.WithLogger(rawLogger.Sugar()),
p2pforge.WithForgeDomain(cfg.DomainSuffix.WithDefault(config.DefaultDomainSuffix)),
p2pforge.WithForgeRegistrationEndpoint(cfg.RegistrationEndpoint.WithDefault(config.DefaultRegistrationEndpoint)),
p2pforge.WithCAEndpoint(cfg.CAEndpoint.WithDefault(config.DefaultCAEndpoint)),
Expand Down
16 changes: 14 additions & 2 deletions core/node/libp2p/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package libp2p

import (
"fmt"
"os"

"github.com/ipfs/kubo/config"
"github.com/ipshipyard/p2p-forge/client"
"github.com/libp2p/go-libp2p"
Expand All @@ -24,19 +26,29 @@ func Transports(tptConfig config.Transports) interface{} {
) (opts Libp2pOpts, err error) {
privateNetworkEnabled := params.Fprint != nil

if tptConfig.Network.TCP.WithDefault(true) {
tcpEnabled := tptConfig.Network.TCP.WithDefault(true)
wsEnabled := tptConfig.Network.Websocket.WithDefault(true)
if tcpEnabled {
// TODO(9290): Make WithMetrics configurable
opts.Opts = append(opts.Opts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithMetrics()))
}

if tptConfig.Network.Websocket.WithDefault(true) {
if wsEnabled {
if params.ForgeMgr == nil {
opts.Opts = append(opts.Opts, libp2p.Transport(websocket.New))
} else {
opts.Opts = append(opts.Opts, libp2p.Transport(websocket.New, websocket.WithTLSConfig(params.ForgeMgr.TLSConfig())))
}
}

if tcpEnabled && wsEnabled && os.Getenv("LIBP2P_TCP_MUX") != "false" {
if privateNetworkEnabled {
log.Error("libp2p.ShareTCPListener() is not supported in private networks, please disable Swarm.Transports.Network.Websocket or run with LIBP2P_TCP_MUX=false to make this message go away")
} else {
opts.Opts = append(opts.Opts, libp2p.ShareTCPListener())
}
}

if tptConfig.Network.QUIC.WithDefault(!privateNetworkEnabled) {
if privateNetworkEnabled {
return opts, fmt.Errorf(
Expand Down
28 changes: 24 additions & 4 deletions docs/changelogs/v0.33.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,40 @@

- [Overview](#overview)
- [πŸ”¦ Highlights](#-highlights)
- [Shared TCP listeners](#shared-tcp-listeners)
- [AutoTLS takes care of Secure WebSockets setup](#autotls-takes-care-of-secure-websockets-setup)
- [Bitswap improvements from Boxo](#bitswap-improvements-from-boxo)
- [Using default `libp2p_rcmgr` metrics](#using-default-libp2p_rcmgr--metrics)
- [Flatfs does not `sync` on each write](#flatfs-does-not-sync-on-each-write)
- [`ipfs add --to-files` no longer works with `--wrap`](#ipfs-add---to-files-no-longer-works-with---wrap)
- [New options for faster writes: `WriteThrough`, `BlockKeyCacheSize`, `BatchMaxNodes`, `BatchMaxSize`](#new-options-for-faster-writes-writethrough-blockkeycachesize-batchmaxnodes-batchmaxsize)
- [MFS stability with large number of writes](#mfs-stability-with-large-number-of-writes)
- [πŸ“¦οΈ Dependency updates](#-dependency-updates)
- [πŸ“¦οΈ Important dependency updates](#-important-dependency-updates)
- [πŸ“ Changelog](#-changelog)
- [πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ Contributors](#-contributors)

### Overview

### πŸ”¦ Highlights

#### Shared TCP listeners

Kubo now supports sharing the same TCP port (`4001` by default) by both [raw TCP](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmtransportsnetworktcp) and [WebSockets](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmtransportsnetworkwebsocket) libp2p transports.

This feature is not yet compatible with Private Networks and can be disabled by setting `LIBP2P_TCP_MUX=false` if causes any issues.

#### AutoTLS takes care of Secure WebSockets setup

It is no longer necessary to manually add `/tcp/../ws` listeners to `Addresses.Swarm` when [`AutoTLS.Enabled`](https://github.com/ipfs/kubo/blob/master/docs/config.md#autotlsenabled) is set to `true`. Kubo will detect if `/ws` listener is missing and add one on the same port as pre-existing TCP (e.g. `/tcp/4001`), removing the need for any extra configuration.
> [!TIP]
> Give it a try:
> ```console
> $ ipfs config --json AutoTLS.Enabled true
> ```
> And restart the node. If you are behind NAT, make sure your node is publicly diallable (uPnP or port forwarding), and wait a few minutes to pass all checks and for the changes to take effect.
See [`AutoTLS`](https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls) for more information.
#### Bitswap improvements from Boxo
This release includes some refactorings and improvements affecting Bitswap which should improve reliability. One of the changes affects blocks providing. Previously, the bitswap layer took care itself of announcing new blocks -added or received- with the configured provider (i.e. DHT). This bypassed the "Reprovider", that is, the system that manages precisely "providing" the blocks stored by Kubo. The Reprovider knows how to take advantage of the [AcceleratedDHTClient](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingaccelerateddhtclient), is able to handle priorities, logs statistics and is able to resume on daemon reboot where it left off. From now on, Bitswap will not be doing any providing on-the-side and all announcements are managed by the reprovider. In some cases, when the reproviding queue is full with other elements, this may cause additional delays, but more likely this will result in improved block-providing behaviour overall.
Expand Down Expand Up @@ -62,11 +82,11 @@ We recommend users trying Pebble as a datastore backend to disable both blocksto
We have fixed a number of issues that were triggered by writing or copying many files onto an MFS folder: increased memory usage first, then CPU, disk usage, and eventually a deadlock on write operations. The details of the fixes can be read at [#10630](https://github.com/ipfs/kubo/pull/10630) and [#10623](https://github.com/ipfs/kubo/pull/10623). The result is that writing large amounts of files to an MFS folder should now be possible without major issues. It is possible, as before, to speed up the operations using the `ipfs files --flush=false <op> ...` flag, but it is recommended to switch to `ipfs files --flush=true <op> ...` regularly, or call `ipfs files flush` on the working directory regularly, as this will flush, clear the directory cache and speed up reads.
#### πŸ“¦οΈ Dependency updates
#### πŸ“¦οΈ Important dependency updates
- update `boxo` to [v0.26.0](https://github.com/ipfs/boxo/releases/tag/v0.26.0)
- update `boxo` to [v0.26.0](https://github.com/ipfs/boxo/releases/tag/v0.26.0) (incl. [v0.25.0](https://github.com/ipfs/boxo/releases/tag/v0.25.0))
- update `go-libp2p` to [v0.38.1](https://github.com/libp2p/go-libp2p/releases/tag/v0.38.1) (incl. [v0.37.1](https://github.com/libp2p/go-libp2p/releases/tag/v0.37.1) + [v0.37.2](https://github.com/libp2p/go-libp2p/releases/tag/v0.37.2) + [v0.38.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.38.0))
- update `p2p-forge/client` to [v0.1.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.1.0)
- update `p2p-forge/client` to [v0.2.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.2.0) (incl. [v0.1.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.1.0))
- update `ipfs-webui` to [v4.4.1](https://github.com/ipfs/ipfs-webui/releases/tag/v4.4.1)
### πŸ“ Changelog
Expand Down
Loading

0 comments on commit 397c346

Please sign in to comment.