diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 4d37f9b4a3..0b5ff6070f 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -16,4 +16,4 @@ concurrency: jobs: release-check: - uses: marcopolo/unified-github-workflows/.github/workflows/release-check.yml@e66cb9667a2e1148efda4591e29c56258eaf385b + uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 diff --git a/README.md b/README.md index f785935fc2..8b9cde5634 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,25 @@ Guidelines: - ask questions or talk about things in our [discussion forums](https://discuss.libp2p.io), or open an [issue](https://github.com/libp2p/go-libp2p/issues) for bug reports, or #libp2p-implementers on [Filecoin slack](https://filecoin.io/slack). - ensure you are able to contribute (no legal issues please -- we use the DCO) - get in touch with @libp2p/go-libp2p-maintainers about how best to contribute +- No drive-by contributions seeking to collect airdrops. + - Many projects aim to reward contributors to common goods. Great. However, + this creates an unfortunate incentive for low-effort PRs, submitted solely to + claim rewards. These PRs consume maintainers’ time and energy to triage, with + little to no impact on end users. If we suspect this is the intent of a PR, + we may close it without comment. If you believe this was done in error, + contact us via email. Reference this README section and explain why your PR + is not a “drive-by contribution.” - have fun! There's a few things you can do right now to help out: - - Go through the modules below and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it - for instance, you may need to read up on p2p and more complex operations like muxing to be able to help technically. - **Perform code reviews**. - **Add tests**. There can never be enough tests. + - Go through the modules below and **check out existing issues**. This would + be especially useful for modules in active development. Some knowledge of + IPFS/libp2p may be required, as well as the infrastructure behind it - for + instance, you may need to read up on p2p and more complex operations like + muxing to be able to help technically. + ## Supported Go Versions diff --git a/config/config.go b/config/config.go index f3ea35855a..ea94c067a3 100644 --- a/config/config.go +++ b/config/config.go @@ -446,12 +446,9 @@ func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.B return h, nil } -// NewNode constructs a new libp2p Host from the Config. -// -// This function consumes the config. Do not reuse it (really!). -func (cfg *Config) NewNode() (host.Host, error) { +func (cfg *Config) validate() error { if cfg.EnableAutoRelay && !cfg.Relay { - return nil, fmt.Errorf("cannot enable autorelay; relay is not enabled") + return fmt.Errorf("cannot enable autorelay; relay is not enabled") } // If possible check that the resource manager conn limit is higher than the // limit set in the conn manager. @@ -462,6 +459,33 @@ func (cfg *Config) NewNode() (host.Host, error) { } } + if len(cfg.PSK) > 0 && cfg.ShareTCPListener { + return errors.New("cannot use shared TCP listener with PSK") + } + + return nil +} + +// NewNode constructs a new libp2p Host from the Config. +// +// This function consumes the config. Do not reuse it (really!). +func (cfg *Config) NewNode() (host.Host, error) { + + validateErr := cfg.validate() + if validateErr != nil { + if cfg.ResourceManager != nil { + cfg.ResourceManager.Close() + } + if cfg.ConnManager != nil { + cfg.ConnManager.Close() + } + if cfg.Peerstore != nil { + cfg.Peerstore.Close() + } + + return nil, validateErr + } + if !cfg.DisableMetrics { rcmgr.MustRegisterWith(cfg.PrometheusRegisterer) } diff --git a/libp2p_test.go b/libp2p_test.go index 3de82946d8..3dc7f2e8ab 100644 --- a/libp2p_test.go +++ b/libp2p_test.go @@ -26,6 +26,7 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/core/pnet" "github.com/libp2p/go-libp2p/core/routing" "github.com/libp2p/go-libp2p/core/transport" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" @@ -761,6 +762,7 @@ func TestSharedTCPAddr(t *testing.T) { ListenAddrStrings("/ip4/0.0.0.0/tcp/8888/ws"), ) require.NoError(t, err) + defer h.Close() sawTCP := false sawWS := false for _, addr := range h.Addrs() { @@ -773,5 +775,12 @@ func TestSharedTCPAddr(t *testing.T) { } require.True(t, sawTCP) require.True(t, sawWS) - h.Close() + + _, err = New( + ShareTCPListener(), + Transport(tcp.NewTCPTransport), + Transport(websocket.New), + PrivateNetwork(pnet.PSK([]byte{1, 2, 3})), + ) + require.ErrorContains(t, err, "cannot use shared TCP listener with PSK") } diff --git a/p2p/net/nat/nat.go b/p2p/net/nat/nat.go index 28ffd4a5b2..ebaa167568 100644 --- a/p2p/net/nat/nat.go +++ b/p2p/net/nat/nat.go @@ -6,6 +6,7 @@ import ( "fmt" "net/netip" "sync" + "sync/atomic" "time" logging "github.com/ipfs/go-log/v2" @@ -56,11 +57,11 @@ func DiscoverNAT(ctx context.Context) (*NAT, error) { ctx, cancel := context.WithCancel(context.Background()) nat := &NAT{ nat: natInstance, - extAddr: extAddr, mappings: make(map[entry]int), ctx: ctx, ctxCancel: cancel, } + nat.extAddr.Store(&extAddr) nat.refCount.Add(1) go func() { defer nat.refCount.Done() @@ -77,7 +78,7 @@ type NAT struct { natmu sync.Mutex nat nat.NAT // External IP of the NAT. Will be renewed periodically (every CacheTime). - extAddr netip.Addr + extAddr atomic.Pointer[netip.Addr] refCount sync.WaitGroup ctx context.Context @@ -103,14 +104,15 @@ func (nat *NAT) GetMapping(protocol string, port int) (addr netip.AddrPort, foun nat.mappingmu.Lock() defer nat.mappingmu.Unlock() - if !nat.extAddr.IsValid() { + if !nat.extAddr.Load().IsValid() { return netip.AddrPort{}, false } extPort, found := nat.mappings[entry{protocol: protocol, port: port}] - if !found { + // The mapping may have an invalid port. + if !found || extPort == 0 { return netip.AddrPort{}, false } - return netip.AddrPortFrom(nat.extAddr, uint16(extPort)), true + return netip.AddrPortFrom(*nat.extAddr.Load(), uint16(extPort)), true } // AddMapping attempts to construct a mapping on protocol and internal port. @@ -135,6 +137,9 @@ func (nat *NAT) AddMapping(ctx context.Context, protocol string, port int) error // do it once synchronously, so first mapping is done right away, and before exiting, // allowing users -- in the optimistic case -- to use results right after. extPort := nat.establishMapping(ctx, protocol, port) + // Don't validate the mapping here, we refresh the mappings based on this map. + // We can try getting a port again in case it succeeds. In the worst case, + // this is one extra LAN request every few minutes. nat.mappings[entry{protocol: protocol, port: port}] = extPort return nil } @@ -202,7 +207,7 @@ func (nat *NAT) background() { if err == nil { extAddr, _ = netip.AddrFromSlice(extIP) } - nat.extAddr = extAddr + nat.extAddr.Store(&extAddr) nextAddrUpdate = time.Now().Add(CacheTime) } t.Reset(time.Until(minTime(nextAddrUpdate, nextMappingUpdate))) diff --git a/p2p/net/nat/nat_test.go b/p2p/net/nat/nat_test.go index 772c876c72..e370fc8907 100644 --- a/p2p/net/nat/nat_test.go +++ b/p2p/net/nat/nat_test.go @@ -68,3 +68,18 @@ func TestRemoveMapping(t *testing.T) { _, found = nat.GetMapping("tcp", 10000) require.False(t, found, "didn't expect port mapping for deleted mapping") } + +func TestAddMappingInvalidPort(t *testing.T) { + mockNAT, reset := setupMockNAT(t) + defer reset() + + mockNAT.EXPECT().GetExternalAddress().Return(net.IPv4(1, 2, 3, 4), nil) + nat, err := DiscoverNAT(context.Background()) + require.NoError(t, err) + + mockNAT.EXPECT().AddPortMapping(gomock.Any(), "tcp", 10000, gomock.Any(), MappingDuration).Return(0, nil) + require.NoError(t, nat.AddMapping(context.Background(), "tcp", 10000)) + + _, found := nat.GetMapping("tcp", 10000) + require.False(t, found, "didn't expect a port mapping for invalid nat-ed port") +} diff --git a/p2p/net/swarm/swarm_dial_test.go b/p2p/net/swarm/swarm_dial_test.go index add6f5cbba..d3586ca5d9 100644 --- a/p2p/net/swarm/swarm_dial_test.go +++ b/p2p/net/swarm/swarm_dial_test.go @@ -399,8 +399,23 @@ func TestBlackHoledAddrBlocked(t *testing.T) { require.ErrorIs(t, err, ErrDialRefusedBlackHole) } +type mockDNSResolver struct { + ipsToReturn []net.IPAddr + txtsToReturn []string +} + +var _ madns.BasicResolver = (*mockDNSResolver)(nil) + +func (m *mockDNSResolver) LookupIPAddr(_ context.Context, _ string) ([]net.IPAddr, error) { + return m.ipsToReturn, nil +} + +func (m *mockDNSResolver) LookupTXT(_ context.Context, _ string) ([]string, error) { + return m.txtsToReturn, nil +} + func TestSkipDialingManyDNS(t *testing.T) { - resolver, err := madns.NewResolver() + resolver, err := madns.NewResolver(madns.WithDefaultResolver(&mockDNSResolver{ipsToReturn: []net.IPAddr{{IP: net.ParseIP("1.2.3.4")}, {IP: net.ParseIP("1.2.3.5")}}})) if err != nil { t.Fatal(err) } diff --git a/p2p/test/transport/transport_test.go b/p2p/test/transport/transport_test.go index 60f8ca0c06..4984419dce 100644 --- a/p2p/test/transport/transport_test.go +++ b/p2p/test/transport/transport_test.go @@ -31,6 +31,7 @@ import ( "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" + "github.com/libp2p/go-libp2p/p2p/transport/tcp" libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc" "go.uber.org/mock/gomock" @@ -116,6 +117,41 @@ var transportsToTest = []TransportTestCase{ return h }, }, + { + Name: "TCP-Shared-WithMetrics / TLS / Yamux", + HostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host { + libp2pOpts := transformOpts(opts) + libp2pOpts = append(libp2pOpts, libp2p.ShareTCPListener()) + libp2pOpts = append(libp2pOpts, libp2p.Security(tls.ID, tls.New)) + libp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport)) + libp2pOpts = append(libp2pOpts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithMetrics())) + if opts.NoListen { + libp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs) + } else { + libp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + } + h, err := libp2p.New(libp2pOpts...) + require.NoError(t, err) + return h + }, + }, + { + Name: "TCP-WithMetrics / TLS / Yamux", + HostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host { + libp2pOpts := transformOpts(opts) + libp2pOpts = append(libp2pOpts, libp2p.Security(tls.ID, tls.New)) + libp2pOpts = append(libp2pOpts, libp2p.Muxer(yamux.ID, yamux.DefaultTransport)) + libp2pOpts = append(libp2pOpts, libp2p.Transport(tcp.NewTCPTransport, tcp.WithMetrics())) + if opts.NoListen { + libp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs) + } else { + libp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + } + h, err := libp2p.New(libp2pOpts...) + require.NoError(t, err) + return h + }, + }, { Name: "WebSocket-Shared", HostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host { diff --git a/p2p/transport/tcp/metrics.go b/p2p/transport/tcp/metrics.go index 50820d870c..cbd2f92f73 100644 --- a/p2p/transport/tcp/metrics.go +++ b/p2p/transport/tcp/metrics.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/libp2p/go-libp2p/core/network" "github.com/marten-seemann/tcp" "github.com/mikioh/tcpinfo" manet "github.com/multiformats/go-multiaddr/net" @@ -252,6 +253,16 @@ func (c *tracingConn) Close() error { return c.closeErr } +func (c *tracingConn) Scope() network.ConnManagementScope { + if cs, ok := c.Conn.(interface { + Scope() network.ConnManagementScope + }); ok { + return cs.Scope() + } + // upgrader is expected to handle this + return nil +} + func (c *tracingConn) getTCPInfo() (*tcpinfo.Info, error) { var o tcpinfo.Info var b [256]byte diff --git a/p2p/transport/tcp/metrics_unix_test.go b/p2p/transport/tcp/metrics_test.go similarity index 98% rename from p2p/transport/tcp/metrics_unix_test.go rename to p2p/transport/tcp/metrics_test.go index 0a09526206..9a9946968b 100644 --- a/p2p/transport/tcp/metrics_unix_test.go +++ b/p2p/transport/tcp/metrics_test.go @@ -1,5 +1,3 @@ -// go:build: unix - package tcp import ( diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index d48a0aa537..4401175363 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -71,7 +71,7 @@ func newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) return m, nil } -// getCurrentTimeBucket returns the canonical start time of the given time as +// getCurrentBucketStartTime returns the canonical start time of the given time as // bucketed by ranges of certValidity since unix epoch (plus an offset). This // lets you get the same time ranges across reboots without having to persist // state. diff --git a/version.json b/version.json index 6e9b1709cc..6a5c68d20d 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "v0.38.1" + "version": "v0.38.2" }