Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: swarm: refactor address resolution #2990

Merged
merged 7 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import (
"github.com/prometheus/client_golang/prometheus"

ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/quic-go/quic-go"
"go.uber.org/fx"
Expand Down Expand Up @@ -114,7 +113,7 @@ type Config struct {
Peerstore peerstore.Peerstore
Reporter metrics.Reporter

MultiaddrResolver *madns.Resolver
MultiaddrResolver network.MultiaddrDNSResolver

DisablePing bool

Expand Down Expand Up @@ -286,7 +285,6 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
fx.Provide(func() connmgr.ConnectionGater { return cfg.ConnectionGater }),
fx.Provide(func() pnet.PSK { return cfg.PSK }),
fx.Provide(func() network.ResourceManager { return cfg.ResourceManager }),
fx.Provide(func() *madns.Resolver { return cfg.MultiaddrResolver }),
fx.Provide(func(cm *quicreuse.ConnManager, sw *swarm.Swarm) libp2pwebrtc.ListenUDPFn {
hasQuicAddrPortFor := func(network string, laddr *net.UDPAddr) bool {
quicAddrPorts := map[string]struct{}{}
Expand Down
8 changes: 8 additions & 0 deletions core/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ type Network interface {
ResourceManager() ResourceManager
}

type MultiaddrDNSResolver interface {
// ResolveDNSAddr resolves the first /dnsaddr component in a multiaddr.
// Recurisvely resolves DNSADDRs up to the recursion limit
ResolveDNSAddr(ctx context.Context, expectedPeerID peer.ID, maddr ma.Multiaddr, recursionLimit, outputLimit int) ([]ma.Multiaddr, error)
// ResolveDNSComponent resolves the first /{dns,dns4,dns6} component in a multiaddr.
ResolveDNSComponent(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error)
}

// Dialer represents a service that can dial out to peers
// (this is usually just a Network, but other services may not need the whole
// stack, and thus it becomes easier to mock)
Expand Down
18 changes: 18 additions & 0 deletions core/peer/addrinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ func SplitAddr(m ma.Multiaddr) (transport ma.Multiaddr, id ID) {
return transport, id
}

// IDFromP2PAddr extracts the peer ID from a p2p Multiaddr
func IDFromP2PAddr(m ma.Multiaddr) (ID, error) {
if m == nil {
return "", ErrInvalidAddr
}
var lastComponent ma.Component
ma.ForEach(m, func(c ma.Component) bool {
lastComponent = c
return true
})
if lastComponent.Protocol().Code != ma.P_P2P {
return "", ErrInvalidAddr
}

id := ID(lastComponent.RawValue()) // already validated by the multiaddr library.
return id, nil
}

// AddrInfoFromString builds an AddrInfo from the string representation of a Multiaddr
func AddrInfoFromString(s string) (*AddrInfo, error) {
a, err := ma.NewMultiaddr(s)
Expand Down
14 changes: 14 additions & 0 deletions core/peer/addrinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

. "github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require"

ma "github.com/multiformats/go-multiaddr"
)
Expand Down Expand Up @@ -50,6 +51,19 @@ func TestSplitAddr(t *testing.T) {
}
}

func TestIDFromP2PAddr(t *testing.T) {
id, err := IDFromP2PAddr(maddrFull)
require.NoError(t, err)
require.Equal(t, testID, id)

id, err = IDFromP2PAddr(maddrPeer)
require.NoError(t, err)
require.Equal(t, testID, id)

_, err = IDFromP2PAddr(maddrTpt)
require.ErrorIs(t, err, ErrInvalidAddr)
}

func TestAddrInfoFromP2pAddr(t *testing.T) {
ai, err := AddrInfoFromP2pAddr(maddrFull)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions core/transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ type CapableConn interface {
// shutdown. NOTE: `Dial` and `Listen` may be called after or concurrently with
// `Close`.
//
// In addition to the Transport interface, transports may implement
// Resolver or SkipResolver interface. When wrapping/embedding a transport, you should
// ensure that the Resolver/SkipResolver interface is handled correctly.
//
// For a conceptual overview, see https://docs.libp2p.io/concepts/transport/
type Transport interface {
// Dial dials a remote peer. It should try to reuse local listener
Expand Down Expand Up @@ -85,6 +89,14 @@ type Resolver interface {
Resolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error)
}

// SkipResolver can be optionally implemented by transports that don't want to
// resolve or transform the multiaddr. Useful for transports that indirectly
// wrap other transports (e.g. p2p-circuit). This lets the inner transport
// specify how a multiaddr is resolved later.
type SkipResolver interface {
SkipResolve(ctx context.Context, maddr ma.Multiaddr) bool
}

// Listener is an interface closely resembling the net.Listener interface. The
// only real difference is that Accept() returns Conn's of the type in this
// package, and also exposes a Multiaddr method as opposed to a regular Addr
Expand Down
10 changes: 0 additions & 10 deletions defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/prometheus/client_golang/prometheus"

"github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
)

// DefaultSecurity is the default security option.
Expand Down Expand Up @@ -128,11 +127,6 @@ var DefaultConnectionManager = func(cfg *Config) error {
return cfg.Apply(ConnectionManager(mgr))
}

// DefaultMultiaddrResolver creates a default connection manager
var DefaultMultiaddrResolver = func(cfg *Config) error {
return cfg.Apply(MultiaddrResolver(madns.DefaultResolver))
}

// DefaultPrometheusRegisterer configures libp2p to use the default registerer
var DefaultPrometheusRegisterer = func(cfg *Config) error {
return cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer))
Expand Down Expand Up @@ -198,10 +192,6 @@ var defaults = []struct {
fallback: func(cfg *Config) bool { return cfg.ConnManager == nil },
opt: DefaultConnectionManager,
},
{
fallback: func(cfg *Config) bool { return cfg.MultiaddrResolver == nil },
opt: DefaultMultiaddrResolver,
},
{
fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },
opt: DefaultPrometheusRegisterer,
Expand Down
100 changes: 98 additions & 2 deletions libp2p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ package libp2p

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"io"
"math/big"
"net"
"net/netip"
"regexp"
Expand All @@ -26,11 +32,12 @@ import (
"github.com/libp2p/go-libp2p/p2p/net/swarm"
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
"github.com/libp2p/go-libp2p/p2p/security/noise"
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
sectls "github.com/libp2p/go-libp2p/p2p/security/tls"
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
"github.com/libp2p/go-libp2p/p2p/transport/websocket"
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
"go.uber.org/goleak"

Expand Down Expand Up @@ -256,7 +263,7 @@ func TestSecurityConstructor(t *testing.T) {
h, err := New(
Transport(tcp.NewTCPTransport),
Security("/noisy", noise.New),
Security("/tls", tls.New),
Security("/tls", sectls.New),
DefaultListenAddrs,
DisableRelay(),
)
Expand Down Expand Up @@ -655,3 +662,92 @@ func TestUseCorrectTransportForDialOut(t *testing.T) {
}
}
}

func TestCircuitBehindWSS(t *testing.T) {
relayTLSConf := getTLSConf(t, net.IPv4(127, 0, 0, 1), time.Now(), time.Now().Add(time.Hour))
serverNameChan := make(chan string, 2) // Channel that returns what server names the client hello specified
relayTLSConf.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) {
serverNameChan <- chi.ServerName
return relayTLSConf, nil
}

relay, err := New(
EnableRelayService(),
ForceReachabilityPublic(),
Transport(websocket.New, websocket.WithTLSConfig(relayTLSConf)),
ListenAddrStrings("/ip4/127.0.0.1/tcp/0/wss"),
)
require.NoError(t, err)
defer relay.Close()

relayAddrPort, _ := relay.Addrs()[0].ValueForProtocol(ma.P_TCP)
relayAddrWithSNIString := fmt.Sprintf(
"/dns4/localhost/tcp/%s/wss", relayAddrPort,
)
relayAddrWithSNI := []ma.Multiaddr{ma.StringCast(relayAddrWithSNIString)}

h, err := New(
NoListenAddrs,
EnableRelay(),
Transport(websocket.New, websocket.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})),
ForceReachabilityPrivate())
require.NoError(t, err)
defer h.Close()

peerBehindRelay, err := New(
NoListenAddrs,
Transport(websocket.New, websocket.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})),
EnableRelay(),
EnableAutoRelayWithStaticRelays([]peer.AddrInfo{{ID: relay.ID(), Addrs: relayAddrWithSNI}}),
ForceReachabilityPrivate())
require.NoError(t, err)
defer peerBehindRelay.Close()

require.Equal(t,
"localhost",
<-serverNameChan, // The server connects to the relay
)

// Connect to the peer behind the relay
h.Connect(context.Background(), peer.AddrInfo{
ID: peerBehindRelay.ID(),
Addrs: []ma.Multiaddr{ma.StringCast(
fmt.Sprintf("%s/p2p/%s/p2p-circuit", relayAddrWithSNIString, relay.ID()),
)},
})
require.NoError(t, err)

require.Equal(t,
"localhost",
<-serverNameChan, // The client connects to the relay and sends the SNI
)
}

// getTLSConf is a helper to generate a self-signed TLS config
func getTLSConf(t *testing.T, ip net.IP, start, end time.Time) *tls.Config {
t.Helper()
certTempl := &x509.Certificate{
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{Organization: []string{"websocket"}},
NotBefore: start,
NotAfter: end,
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IPAddresses: []net.IP{ip},
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
caBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &priv.PublicKey, priv)
require.NoError(t, err)
cert, err := x509.ParseCertificate(caBytes)
require.NoError(t, err)
return &tls.Config{
Certificates: []tls.Certificate{{
Certificate: [][]byte{cert.Raw},
PrivateKey: priv,
Leaf: cert,
}},
}
}
3 changes: 1 addition & 2 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/prometheus/client_golang/prometheus"

ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
"go.uber.org/fx"
)

Expand Down Expand Up @@ -495,7 +494,7 @@ func UserAgent(userAgent string) Option {
}

// MultiaddrResolver sets the libp2p dns resolver
func MultiaddrResolver(rslv *madns.Resolver) Option {
func MultiaddrResolver(rslv network.MultiaddrDNSResolver) Option {
return func(cfg *Config) error {
cfg.MultiaddrResolver = rslv
return nil
Expand Down
11 changes: 0 additions & 11 deletions p2p/host/basic/basic_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (

logging "github.com/ipfs/go-log/v2"
ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
manet "github.com/multiformats/go-multiaddr/net"
msmux "github.com/multiformats/go-multistream"
)
Expand Down Expand Up @@ -81,7 +80,6 @@ type BasicHost struct {
hps *holepunch.Service
pings *ping.PingService
natmgr NATManager
maResolver *madns.Resolver
cmgr connmgr.ConnManager
eventbus event.Bus
relayManager *relaysvc.RelayManager
Expand Down Expand Up @@ -130,10 +128,6 @@ type HostOpts struct {
// If omitted, there's no override or filtering, and the results of Addrs and AllAddrs are the same.
AddrsFactory AddrsFactory

// MultiaddrResolves holds the go-multiaddr-dns.Resolver used for resolving
// /dns4, /dns6, and /dnsaddr addresses before trying to connect to a peer.
MultiaddrResolver *madns.Resolver

// NATManager takes care of setting NAT port mappings, and discovering external addresses.
// If omitted, this will simply be disabled.
NATManager func(network.Network) NATManager
Expand Down Expand Up @@ -194,7 +188,6 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
mux: msmux.NewMultistreamMuxer[protocol.ID](),
negtimeout: DefaultNegotiationTimeout,
AddrsFactory: DefaultAddrsFactory,
maResolver: madns.DefaultResolver,
eventbus: opts.EventBus,
addrChangeChan: make(chan struct{}, 1),
ctx: hostCtx,
Expand Down Expand Up @@ -303,10 +296,6 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
h.natmgr = opts.NATManager(n)
}

if opts.MultiaddrResolver != nil {
h.maResolver = opts.MultiaddrResolver
}

if opts.ConnManager == nil {
h.cmgr = &connmgr.NullConnMgr{}
} else {
Expand Down
2 changes: 1 addition & 1 deletion p2p/net/swarm/dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestBasicDialPeerWithResolver(t *testing.T) {
resolver, err := madns.NewResolver(madns.WithDomainResolver("example.com", &mockResolver))
require.NoError(t, err)

swarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithMultiaddrResolver(resolver)))
swarms := makeSwarms(t, 2, swarmt.WithSwarmOpts(swarm.WithMultiaddrResolver(swarm.ResolverFromMaDNS{resolver})))
defer closeSwarms(swarms)
s1 := swarms[0]
s2 := swarms[1]
Expand Down
Loading
Loading