diff --git a/README.md b/README.md index 8ded0ae..c75e846 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ - **Auto-discovery**: Automatic peer discovery via DHT, mDNS, and peer caching - **NAT traversal**: Built-in support for hole punching and relay connections - **Persistent peers**: Automatically caches and reconnects to known peers +- **Connection limiting**: Smart connection manager prioritizes topic peers over routing peers (default: 25-35 connections)
diff --git a/client.go b/client.go index 815a675..32f339d 100644 --- a/client.go +++ b/client.go @@ -32,6 +32,8 @@ import ( "github.com/libp2p/go-libp2p/p2p/discovery/mdns" drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/libp2p/go-libp2p/p2p/net/conngater" + "github.com/libp2p/go-libp2p/p2p/net/connmgr" + "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" ) @@ -232,6 +234,39 @@ func createPrivateIPConnectionGater(log logger, cancel context.CancelFunc) (*con func buildHostOptions(config Config, log logger, cancel context.CancelFunc) ([]libp2p.Option, error) { hostOpts := []libp2p.Option{libp2p.Identity(config.PrivateKey)} + // Explicitly configure only TCP transport to prevent WebRTC's mDNS usage + // WebRTC transport uses mDNS (port 5353) for ICE candidate discovery + // By only enabling TCP, we avoid any unwanted mDNS traffic + hostOpts = append(hostOpts, libp2p.Transport(tcp.NewTCPTransport)) + log.Infof("Configured TCP-only transport (WebRTC disabled to prevent mDNS)") + + // Configure connection manager to limit total connections + maxConns := config.MaxConnections + if maxConns == 0 { + maxConns = 35 // Default high water mark + } + minConns := config.MinConnections + if minConns == 0 { + minConns = 25 // Default low water mark + } + gracePeriod := config.ConnectionGracePeriod + if gracePeriod == 0 { + gracePeriod = 20 * time.Second // Default grace period + } + + connMgr, err := connmgr.NewConnManager( + minConns, + maxConns, + connmgr.WithGracePeriod(gracePeriod), + ) + if err != nil { + cancel() + return nil, fmt.Errorf("failed to create connection manager: %w", err) + } + + hostOpts = append(hostOpts, libp2p.ConnectionManager(connMgr)) + log.Infof("Connection manager configured: min=%d, max=%d, grace=%v", minConns, maxConns, gracePeriod) + // Add connection gater to block private IPs if AllowPrivateIPs is false (default) if !config.AllowPrivateIPs { ipFilter, err := createPrivateIPConnectionGater(log, cancel) @@ -494,6 +529,10 @@ func (c *client) Subscribe(topic string) <-chan Message { peerID := conn.RemotePeer() topicPeers := t.ListPeers() if slices.Contains(topicPeers, peerID) { + // Tag topic peers with high value to protect from connection manager pruning + c.host.ConnManager().TagPeer(peerID, fmt.Sprintf("topic:%s", topic), 100) + c.logger.Debugf("Tagged topic peer %s for protection", peerID) + name := c.peerTracker.getName(peerID) addr := conn.RemoteMultiaddr().String() c.logger.Infof("[CONNECTED] Topic peer %s [%s] %s", peerID.String(), name, addr) @@ -507,6 +546,8 @@ func (c *client) Subscribe(topic string) <-chan Message { peerID := conn.RemotePeer() topicPeers := t.ListPeers() if slices.Contains(topicPeers, peerID) { + // Untag peer when they disconnect from topic + c.host.ConnManager().UntagPeer(peerID, fmt.Sprintf("topic:%s", topic)) c.logger.Infof("[DISCONNECTED] Lost connection to topic peer %s", peerID.String()[:16]) } }, diff --git a/config.go b/config.go index 3e24468..9a8c9d4 100644 --- a/config.go +++ b/config.go @@ -117,4 +117,22 @@ type Config struct { // Default: false (private IPs filtered for production safety) // Set to true only for local development or private network deployments AllowPrivateIPs bool + + // MaxConnections is the high water mark for total peer connections. + // When this limit is reached, the connection manager will prune low-value connections + // (DHT routing peers) while protecting high-value peers (topic mesh peers). + // If not provided or zero, defaults to 35. + // Recommended: 25-50 depending on available bandwidth and number of topics. + MaxConnections int + + // MinConnections is the low water mark for total peer connections. + // The connection manager will not prune connections below this threshold. + // If not provided or zero, defaults to 25. + // Recommended: Set to MaxConnections - 10 for a reasonable buffer. + MinConnections int + + // ConnectionGracePeriod is the duration new connections are protected from pruning. + // This prevents rapid connect/disconnect cycles. + // If not provided or zero, defaults to 20 seconds. + ConnectionGracePeriod time.Duration }