Skip to content

Commit

Permalink
quic / webtransport: make it possible to listen on the same address /…
Browse files Browse the repository at this point in the history
… port (#1905)

* quic: add an integration test for QUIC version support

* quic: refactor the stateless reset test

* quic: simplify the interface of the noreuseConn

DecreaseCount now closes the underlying UDP conn, so that callers don't
need to pay attention if they're dealing with a reuseConn or a
noreuseConn.

* implement a quicreuse to manage QUIC connections

* quicreuse: introduce options

* config: construct the quicreuse.ConnManager using fx

* webtransport: use the quicreuse

* add integration test for QUIC and WebTranport sharing the same UDP addr

* Handle errors in accept loop goroutine

* Add comment

* Remove todo

* Rename mutexes

* Cleanup extra close

* Only log on err

* Use webtransport-go 0.4.0

* Fix expected error

Co-authored-by: Marco Munizaga <[email protected]>
  • Loading branch information
marten-seemann and MarcoPolo authored Nov 28, 2022
1 parent 9fad5b0 commit f732050
Show file tree
Hide file tree
Showing 35 changed files with 1,348 additions and 601 deletions.
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
}

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

0 comments on commit f732050

Please sign in to comment.