Skip to content

BUG: segfault with using quicreuse.ListenQUIC #3345

@derrandz

Description

@derrandz

Version: v0.42.1-0.20250702212416-7b7c3ed4ce0

Stack trace:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x9abe95]

goroutine 1 [running]:
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*refcountedTransport).Listen(0x13e6618?, 0x0?, 0xc000000001?)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/p2p/transport/quicreuse/reuse.go:149 +0x15
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.newQuicListener({0x13f3b30, 0xc000487140}, 0xc000139680)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/p2p/transport/quicreuse/listener.go:74 +0x264
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*ConnManager).ListenQUICAndAssociate(0xc0003f8a50, {0x0, 0x0}, {0xc0004a4200?, 0x1?, 0x13f36d0?}, 0xc0004a83c0, 0x12cbd28)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/p2p/transport/quicreuse/connmgr.go:231 +0x3ec
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*ConnManager).ListenQUIC(...)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/p2p/transport/quicreuse/connmgr.go:202
main.main.func1({0xc3, 0x85, 0xf, 0x39, 0x3e, 0x3a, 0xa2, 0x76, 0x19, 0xb1, ...}, ...)
        /home/ddz/Lab/nunet/go-libp2p-duplicate-quic-bug/main.go:128 +0x317
reflect.Value.call({0xe96840?, 0x12cbd20?, 0x416f14?}, {0x1039b73, 0x4}, {0xc0004a7860, 0x2, 0x500010000000003?})
        /snap/go/10907/src/reflect/value.go:584 +0xca6
reflect.Value.Call({0xe96840?, 0x12cbd20?, 0x0?}, {0xc0004a7860?, 0xc00029d1d8?, 0x471b93?})
        /snap/go/10907/src/reflect/value.go:368 +0xb9
go.uber.org/fx.paramTagsAnnotation.build.func1({0xc0004a7860?, 0x2?, 0x2?})
        /home/ddz/go/pkg/mod/go.uber.org/fx@v1.24.0/annotated.go:258 +0x54
reflect.Value.call({0xe96840?, 0xc00049ce40?, 0x30?}, {0x1039b73, 0x4}, {0xc0004a73e0, 0x2, 0x10?})
        /snap/go/10907/src/reflect/value.go:584 +0xca6
reflect.Value.Call({0xe96840?, 0xc00049ce40?, 0x4179a5?}, {0xc0004a73e0?, 0xf345c0?, 0xc0004a7801?})
        /snap/go/10907/src/reflect/value.go:368 +0xb9
go.uber.org/dig.defaultInvoker({0xe96840?, 0xc00049ce40?, 0xc00029deb0?}, {0xc0004a73e0?, 0x2?, 0x13fe770?})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/container.go:257 +0x25
go.uber.org/dig.(*constructorNode).Call(0xc000485a00, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/constructor.go:198 +0x472
go.uber.org/dig.paramSingle.Build({{0x0, 0x0}, 0x0, {0x1403340, 0xfd6020}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:288 +0x34d
go.uber.org/dig.paramList.BuildList({{0x1403340, 0xea2c40}, {0xc0003a3440, 0x3, 0x3}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:151 +0xad
go.uber.org/dig.(*constructorNode).Call(0xc0001172b0, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/constructor.go:160 +0x137
go.uber.org/dig.paramSingle.Build({{0x0, 0x0}, 0x0, {0x1403340, 0x1021140}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:288 +0x34d
go.uber.org/dig.paramObjectField.Build(...)
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:485
go.uber.org/dig.paramObject.Build({{0x1403340, 0xc000486f00}, {0xc000137b80, 0x2, 0x2}, {0x0, 0x0, 0x0}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:413 +0x5d2
go.uber.org/dig.paramList.BuildList({{0x1403340, 0xc000137b30}, {0xc0004ae4e0, 0x1, 0x1}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/param.go:151 +0xad
go.uber.org/dig.(*Scope).Invoke(0xc00015b600, {0xc000137b30, 0xc0004a6cf0}, {0x0, 0x0, 0x470c01?})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/invoke.go:123 +0x313
go.uber.org/dig.(*Container).Invoke(0xc0003f88f0?, {0xc000137b30?, 0xc0004a6cf0?}, {0x0?, 0x20?, 0xc00029e9f8?})
        /home/ddz/go/pkg/mod/go.uber.org/dig@v1.19.0/invoke.go:83 +0x25
go.uber.org/fx.runInvoke({0x776a10c89998, 0xc00011c4d8}, {{0xfdc9c0, 0xc00015b4a0}, {0xc0003a0b40, 0x6, 0x7}})
        /home/ddz/go/pkg/mod/go.uber.org/fx@v1.24.0/invoke.go:107 +0x129
go.uber.org/fx.(*module).invoke(0xc000116dd0, {{0xfdc9c0, 0xc00015b4a0}, {0xc0003a0b40, 0x6, 0x7}})
        /home/ddz/go/pkg/mod/go.uber.org/fx@v1.24.0/module.go:335 +0x145
go.uber.org/fx.(*module).invokeAll(0xc000116dd0)
        /home/ddz/go/pkg/mod/go.uber.org/fx@v1.24.0/module.go:321 +0xda
go.uber.org/fx.New({0xc0003ae008, 0x24, 0x7?})
        /home/ddz/go/pkg/mod/go.uber.org/fx@v1.24.0/app.go:507 +0x8b8
github.com/libp2p/go-libp2p/config.(*Config).NewNode(0xc000164508)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/config/config.go:610 +0x13d6
github.com/libp2p/go-libp2p.NewWithoutDefaults({0xc0003961c0, 0xf, 0x1c})
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/libp2p.go:67 +0x65
github.com/libp2p/go-libp2p.New(...)
        /home/ddz/go/pkg/mod/github.com/libp2p/go-libp2p@v0.42.1-0.20250702212416-7b7c3ed4ce04/libp2p.go:53
main.main()
        /home/ddz/Lab/nunet/go-libp2p-duplicate-quic-bug/main.go:199 +0xf6d
exit status 2

Script to reproduce:


package main

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/libp2p/go-libp2p"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/core/protocol"
	"github.com/libp2p/go-libp2p/core/routing"
	"github.com/libp2p/go-libp2p/p2p/host/autorelay"
	"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
	rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
	"github.com/libp2p/go-libp2p/p2p/net/connmgr"
	"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
	"github.com/libp2p/go-libp2p/p2p/security/noise"
	libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
	libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
	"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
	ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
	webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/quic-go/quic-go"
)

func main() {

	// Generate a private key for testing
	privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
	if err != nil {
		log.Fatalf("Failed to generate private key: %v", err)
	}

	// Create the libp2p config with duplicate QUIC listen addresses
	listenAddresses := []string{
		"/ip4/0.0.0.0/tcp/0",
		"/ip4/0.0.0.0/udp/1234/quic-v1", // First QUIC address
		// "/ip4/0.0.0.0/udp/1234/quic-v1", // Duplicate QUIC address - this triggers the bug
	}

	// Create connection manager
	connmgr, err := connmgr.NewConnManager(
		100,
		400,
		connmgr.WithGracePeriod(10*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create connection manager: %v", err)
	}

	// Create peerstore
	ps, err := pstoremem.NewPeerstore()
	if err != nil {
		log.Fatalf("Failed to create peerstore: %v", err)
	}

	// Set up resource manager
	mem := int64(1024 * 1024 * 1024) // 1GB
	fds := 512

	limits := rcmgr.DefaultLimits
	limits.SystemBaseLimit.ConnsInbound = 512
	limits.SystemBaseLimit.ConnsOutbound = 512
	limits.SystemBaseLimit.Conns = 1024
	limits.SystemBaseLimit.StreamsInbound = 8192
	limits.SystemBaseLimit.StreamsOutbound = 8192
	limits.SystemBaseLimit.Streams = 16384
	scaled := limits.Scale(mem, fds)

	mgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(scaled))
	if err != nil {
		log.Fatalf("Failed to create resource manager: %v", err)
	}

	// Create QUIC reuse function that demonstrates the bug
	newReuse := func(statelessResetKey quic.StatelessResetKey, tokenGeneratorKey quic.TokenGeneratorKey) (*quicreuse.ConnManager, error) {
		udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 1234})
		if err != nil {
			return nil, fmt.Errorf("failed to create UDP listener: %w", err)
		}

		reuse, err := quicreuse.NewConnManager(statelessResetKey, tokenGeneratorKey)
		if err != nil {
			return nil, fmt.Errorf("failed to create reuse: %w", err)
		}

		// This is where the bug should occur - trying to lend the same transport twice
		trDone, err := reuse.LendTransport("udp4", nil, udpConn)
		if err != nil {
			return nil, fmt.Errorf("failed to add transport to reuse: %w", err)
		}

		go func() {
			<-trDone
			fmt.Println("closing UDP connection")
			udpConn.Close()
		}()

		_, err = reuse.ListenQUIC(
			ma.StringCast(fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1", 1234)),
			&tls.Config{NextProtos: []string{"raw"}},
			func(*quic.Conn, uint64) bool { return false },
		)
		if err != nil {
			return nil, fmt.Errorf("failed to listen quic: %w", err)
		}

		return reuse, nil
	}

	// Create libp2p options
	var libp2pOpts []libp2p.Option
	dhtOpts := []dht.Option{
		dht.ProtocolPrefix(protocol.ID("/nunet")),
		dht.Mode(dht.ModeAutoServer),
	}

	libp2pOpts = append(libp2pOpts,
		libp2p.ListenAddrStrings(listenAddresses...),
		libp2p.ResourceManager(mgr),
		libp2p.Identity(privKey),
		libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
			idht, err := dht.New(context.Background(), h, dhtOpts...)
			return idht, err
		}),
		libp2p.Peerstore(ps),
		libp2p.Security(libp2ptls.ID, libp2ptls.New),
		libp2p.Security(noise.ID, noise.New),
		libp2p.ChainOptions(
			libp2p.Transport(tcp.NewTCPTransport),
			libp2p.Transport(libp2pquic.NewTransport),
			libp2p.Transport(webtransport.New),
			libp2p.Transport(ws.New),
		),
		libp2p.ConnectionManager(connmgr),
		libp2p.EnableRelay(),
		libp2p.EnableHolePunching(),
		libp2p.EnableRelayService(
			relay.WithLimit(&relay.RelayLimit{
				Duration: 5 * time.Minute,
				Data:     1 << 21, // 2 MiB
			}),
		),
		libp2p.EnableAutoRelayWithPeerSource(
			func(ctx context.Context, num int) <-chan peer.AddrInfo {
				r := make(chan peer.AddrInfo)
				go func() {
					defer close(r)
					for i := 0; i < num; i++ {
						select {
						case <-ctx.Done():
							return
						default:
							// No peers to provide
						}
					}
				}()
				return r
			},
			autorelay.WithBootDelay(time.Minute),
			autorelay.WithBackoff(30*time.Second),
			autorelay.WithMinCandidates(2),
			autorelay.WithMaxCandidates(3),
			autorelay.WithNumRelays(2),
		),
		libp2p.QUICReuse(newReuse),
	)

	// This is where the bug should occur
	host, err := libp2p.New(libp2pOpts...)
	if err != nil {
		fmt.Printf("Error creating libp2p host: %v\n", err)
		fmt.Println("This confirms the bug is reproducible with duplicate QUIC addresses!")
		return
	}

	// Clean up
	if host != nil {
		host.Close()
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions