Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<br/>

Expand Down
41 changes: 41 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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])
}
},
Expand Down
18 changes: 18 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}