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

quic / webtransport: make it possible to listen on the same address / port #1905

Merged
merged 16 commits into from
Nov 28, 2022
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
9 changes: 9 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
circuitv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client"
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"

ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
Expand Down Expand Up @@ -78,6 +79,7 @@ type Config struct {

PeerKey crypto.PrivKey

QUICReuse []fx.Option
Transports []fx.Option
Muxers []tptu.StreamMuxer
SecurityTransports []Security
Expand Down Expand Up @@ -239,6 +241,13 @@ func (cfg *Config) addTransports(h host.Host) error {
)))
}

fxopts = append(fxopts, fx.Provide(PrivKeyToStatelessResetKey))
if cfg.QUICReuse != nil {
fxopts = append(fxopts, cfg.QUICReuse...)
} else {
fxopts = append(fxopts, fx.Provide(quicreuse.NewConnManager)) // TODO: close the ConnManager when shutting down the node
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this todo?

}

fxopts = append(fxopts, fx.Invoke(
fx.Annotate(
func(tpts []transport.Transport) error {
Expand Down
27 changes: 27 additions & 0 deletions config/quic_stateless_reset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package config

import (
"crypto/sha256"
"io"

"golang.org/x/crypto/hkdf"

"github.com/libp2p/go-libp2p/core/crypto"

"github.com/lucas-clemente/quic-go"
)

const statelessResetKeyInfo = "libp2p quic stateless reset key"

func PrivKeyToStatelessResetKey(key crypto.PrivKey) (quic.StatelessResetKey, error) {
var statelessResetKey quic.StatelessResetKey
keyBytes, err := key.Raw()
if err != nil {
return statelessResetKey, err
}
keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(statelessResetKeyInfo))
if _, err := io.ReadFull(keyReader, statelessResetKey[:]); err != nil {
return statelessResetKey, err
}
return statelessResetKey, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/libp2p/zeroconf/v2 v2.2.0
github.com/lucas-clemente/quic-go v0.31.0
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd
github.com/marten-seemann/webtransport-go v0.3.0
github.com/marten-seemann/webtransport-go v0.4.0
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b
github.com/minio/sha256-simd v1.0.0
github.com/mr-tron/base58 v1.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sN
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/marten-seemann/webtransport-go v0.3.0 h1:TqUSf7/qZN8bJyuGrDMz9nDrfMbgH8p7KqV3TYrkBgo=
github.com/marten-seemann/webtransport-go v0.3.0/go.mod h1:4xcfySgZMLP4aG5GBGj1egP7NlpfwgYJ1WJMvPPiVMU=
github.com/marten-seemann/webtransport-go v0.4.0 h1:seNdLfPIEQCZFrWlSF/o8jfx2DBib08lSyt95iC0jhs=
github.com/marten-seemann/webtransport-go v0.4.0/go.mod h1:4xcfySgZMLP4aG5GBGj1egP7NlpfwgYJ1WJMvPPiVMU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down
6 changes: 3 additions & 3 deletions libp2p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestChainOptions(t *testing.T) {

func TestTransportConstructorTCP(t *testing.T) {
h, err := New(
Transport(tcp.NewTCPTransport),
Transport(tcp.NewTCPTransport, tcp.DisableReuseport()),
DisableRelay(),
)
require.NoError(t, err)
Expand All @@ -186,7 +186,7 @@ func TestTransportConstructorTCP(t *testing.T) {

func TestTransportConstructorQUIC(t *testing.T) {
h, err := New(
Transport(quic.NewTransport, quic.DisableReuseport()),
Transport(quic.NewTransport),
DisableRelay(),
)
require.NoError(t, err)
Expand Down Expand Up @@ -248,7 +248,7 @@ func TestTransportConstructorWithWrongOpts(t *testing.T) {
Transport(quic.NewTransport, tcp.DisableReuseport()),
DisableRelay(),
)
require.EqualError(t, err, "transport option of type tcp.Option not assignable to libp2pquic.Option")
require.EqualError(t, err, "transport constructor doesn't take any options")
}

func TestSecurityConstructor(t *testing.T) {
Expand Down
28 changes: 28 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"

ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
Expand Down Expand Up @@ -96,6 +97,33 @@ func Muxer(name string, muxer network.Multiplexer) Option {
}
}

func QUICReuse(constructor interface{}, opts ...quicreuse.Option) Option {
return func(cfg *Config) error {
tag := `group:"quicreuseopts"`
typ := reflect.ValueOf(constructor).Type()
numParams := typ.NumIn()
isVariadic := typ.IsVariadic()

if !isVariadic && len(opts) > 0 {
return errors.New("QUICReuse constructor doesn't take any options")
}

var params []string
if isVariadic && len(opts) > 0 {
// If there are options, apply the tag.
// Since options are variadic, they have to be the last argument of the constructor.
params = make([]string, numParams)
params[len(params)-1] = tag
}

cfg.QUICReuse = append(cfg.QUICReuse, fx.Provide(fx.Annotate(constructor, fx.ParamTags(params...))))
for _, opt := range opts {
cfg.QUICReuse = append(cfg.QUICReuse, fx.Supply(fx.Annotate(opt, fx.ResultTags(tag))))
}
return nil
}
}

// Transport configures libp2p to use the given transport (or transport
// constructor).
//
Expand Down
7 changes: 6 additions & 1 deletion p2p/net/swarm/dial_worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
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"

ma "github.com/multiformats/go-multiaddr"
Expand Down Expand Up @@ -58,7 +59,11 @@ func makeSwarm(t *testing.T) *Swarm {
t.Fatal(err)
}

quicTransport, err := quic.NewTransport(priv, nil, nil, nil)
reuse, err := quicreuse.NewConnManager([32]byte{})
if err != nil {
t.Fatal(err)
}
quicTransport, err := quic.NewTransport(priv, reuse, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 7 additions & 4 deletions p2p/net/swarm/swarm_addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import (
"fmt"
"testing"

"github.com/libp2p/go-libp2p/core/peer"

ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/test"
"github.com/libp2p/go-libp2p/p2p/net/swarm"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
circuitv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client"
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"
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"

Expand Down Expand Up @@ -80,10 +80,13 @@ func TestDialAddressSelection(t *testing.T) {
tcpTr, err := tcp.NewTCPTransport(nil, nil)
require.NoError(t, err)
require.NoError(t, s.AddTransport(tcpTr))
quicTr, err := quic.NewTransport(priv, nil, nil, nil)
reuse, err := quicreuse.NewConnManager([32]byte{})
require.NoError(t, err)
defer reuse.Close()
quicTr, err := quic.NewTransport(priv, reuse, nil, nil, nil)
require.NoError(t, err)
require.NoError(t, s.AddTransport(quicTr))
webtransportTr, err := webtransport.New(priv, nil, nil)
webtransportTr, err := webtransport.New(priv, reuse, nil, nil)
require.NoError(t, err)
require.NoError(t, s.AddTransport(webtransportTr))
h := sha256.Sum256([]byte("foo"))
Expand Down
8 changes: 7 additions & 1 deletion p2p/net/swarm/testing/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"testing"
"time"

"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"

"github.com/libp2p/go-libp2p/core/connmgr"
"github.com/libp2p/go-libp2p/core/control"
"github.com/libp2p/go-libp2p/core/crypto"
Expand Down Expand Up @@ -160,7 +162,11 @@ func GenSwarm(t *testing.T, opts ...Option) *swarm.Swarm {
}
}
if !cfg.disableQUIC {
quicTransport, err := quic.NewTransport(priv, nil, cfg.connectionGater, nil)
reuse, err := quicreuse.NewConnManager([32]byte{})
if err != nil {
t.Fatal(err)
}
quicTransport, err := quic.NewTransport(priv, reuse, nil, cfg.connectionGater, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
169 changes: 169 additions & 0 deletions p2p/test/quic/quic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package quic_test

import (
"context"
"testing"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"

ma "github.com/multiformats/go-multiaddr"
"github.com/stretchr/testify/require"
)

func getQUICMultiaddrCode(addr ma.Multiaddr) int {
if _, err := addr.ValueForProtocol(ma.P_QUIC); err == nil {
return ma.P_QUIC
}
if _, err := addr.ValueForProtocol(ma.P_QUIC_V1); err == nil {
return ma.P_QUIC_V1
}
return 0
}

func TestQUICVersions(t *testing.T) {
h1, err := libp2p.New(
libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(webtransport.New),
libp2p.ListenAddrStrings(
"/ip4/127.0.0.1/udp/12345/quic", // QUIC draft-29
"/ip4/127.0.0.1/udp/12345/quic-v1", // QUIC v1
),
)
require.NoError(t, err)
defer h1.Close()

addrs := h1.Addrs()
require.Len(t, addrs, 2)
var quicDraft29Addr, quicV1Addr ma.Multiaddr
for _, addr := range addrs {
switch getQUICMultiaddrCode(addr) {
case ma.P_QUIC:
quicDraft29Addr = addr
case ma.P_QUIC_V1:
quicV1Addr = addr
}
}
require.NotNil(t, quicDraft29Addr, "expected to be listening on a QUIC draft-29 address")
require.NotNil(t, quicV1Addr, "expected to be listening on a QUIC v1 address")

// connect using QUIC draft-29
h2, err := libp2p.New(
libp2p.Transport(libp2pquic.NewTransport),
)
require.NoError(t, err)
require.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: []ma.Multiaddr{quicDraft29Addr}}))
conns := h2.Network().ConnsToPeer(h1.ID())
require.Len(t, conns, 1)
require.Equal(t, ma.P_QUIC, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))
require.Equal(t, ma.P_QUIC, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))
h2.Close()

// connect using QUIC v1
h3, err := libp2p.New(
libp2p.Transport(libp2pquic.NewTransport),
)
require.NoError(t, err)
require.NoError(t, h3.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: []ma.Multiaddr{quicV1Addr}}))
conns = h3.Network().ConnsToPeer(h1.ID())
require.Len(t, conns, 1)
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))
h3.Close()
}

func TestDisableQUICDraft29(t *testing.T) {
h1, err := libp2p.New(
libp2p.QUICReuse(quicreuse.NewConnManager, quicreuse.DisableDraft29()),
libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(webtransport.New),
libp2p.ListenAddrStrings(
"/ip4/127.0.0.1/udp/12346/quic", // QUIC draft-29
"/ip4/127.0.0.1/udp/12346/quic-v1", // QUIC v1
),
)
require.NoError(t, err)
defer h1.Close()

addrs := h1.Addrs()
require.Len(t, addrs, 1)
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(addrs[0]))

// connect using QUIC draft-29
h2, err := libp2p.New(
libp2p.Transport(libp2pquic.NewTransport),
)
require.NoError(t, err)
defer h2.Close()
require.ErrorContains(t,
h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/udp/12346/quic")}}),
"no compatible QUIC version found",
)
// make sure that dialing QUIC v1 works
require.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: []ma.Multiaddr{addrs[0]}}))
}

func TestQUICAndWebTransport(t *testing.T) {
h1, err := libp2p.New(
libp2p.QUICReuse(quicreuse.NewConnManager, quicreuse.DisableDraft29()),
libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(webtransport.New),
libp2p.ListenAddrStrings(
"/ip4/127.0.0.1/udp/12347/quic-v1",
"/ip4/127.0.0.1/udp/12347/quic-v1/webtransport",
),
)
require.NoError(t, err)
defer h1.Close()

addrs := h1.Addrs()
require.Len(t, addrs, 2)
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(addrs[0]))
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(addrs[1]))
var quicAddr, webtransportAddr ma.Multiaddr
for _, addr := range addrs {
if _, err := addr.ValueForProtocol(ma.P_WEBTRANSPORT); err == nil {
webtransportAddr = addr
} else {
quicAddr = addr
}
}
require.NotNil(t, webtransportAddr, "expected to have a WebTransport address")
require.NotNil(t, quicAddr, "expected to have a QUIC v1 address")

h2, err := libp2p.New(
libp2p.Transport(libp2pquic.NewTransport),
libp2p.NoListenAddrs,
)
require.NoError(t, err)
require.NoError(t, h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}))
for _, conns := range [][]network.Conn{h2.Network().ConnsToPeer(h1.ID()), h1.Network().ConnsToPeer(h2.ID())} {
require.Len(t, conns, 1)
if _, err := conns[0].LocalMultiaddr().ValueForProtocol(ma.P_WEBTRANSPORT); err == nil {
t.Fatalf("expected a QUIC connection, got a WebTransport connection (%s <-> %s)", conns[0].LocalMultiaddr(), conns[0].RemoteMultiaddr())
}
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))
}
h2.Close()

h3, err := libp2p.New(
libp2p.Transport(webtransport.New),
libp2p.NoListenAddrs,
)
require.NoError(t, err)
require.NoError(t, h3.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}))
for _, conns := range [][]network.Conn{h3.Network().ConnsToPeer(h1.ID()), h1.Network().ConnsToPeer(h3.ID())} {
require.Len(t, conns, 1)
if _, err := conns[0].LocalMultiaddr().ValueForProtocol(ma.P_WEBTRANSPORT); err != nil {
t.Fatalf("expected a WebTransport connection, got a QUIC connection (%s <-> %s)", conns[0].LocalMultiaddr(), conns[0].RemoteMultiaddr())
}
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].LocalMultiaddr()))
require.Equal(t, ma.P_QUIC_V1, getQUICMultiaddrCode(conns[0].RemoteMultiaddr()))
}
h3.Close()
}
Loading