Custom Swarm Configuration
For production deployments and advanced use cases, you'll often need more control over your libp2p swarm configuration than the bootstrap approach provides. Kameo's remote::Behaviour
is designed as a standard libp2p NetworkBehaviour
that integrates seamlessly with custom swarm setups, giving you complete control over transports, discovery mechanisms, and protocol composition.
When You Need Custom Configuration
Consider custom swarm configuration when you need:
- Specific transport protocols (WebSocket for browsers, memory for testing)
- Custom discovery mechanisms (bootstrap nodes, relay servers, no mDNS)
- Integration with existing libp2p services (Gossipsub, custom protocols)
- Production networking requirements (specific addresses, firewalls, NAT traversal)
- Security customization (custom authentication, certificate management)
- Performance tuning (connection limits, timeouts, bandwidth management)
Basic Custom Swarm Setup
Here's the minimal setup for a custom swarm with Kameo:
use kameo::{prelude::*, remote};
use libp2p::{noise, tcp, yamux, swarm::NetworkBehaviour};
#[derive(NetworkBehaviour)]
struct MyBehaviour {
kameo: remote::Behaviour,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut swarm = libp2p::SwarmBuilder::with_new_identity()
.with_tokio()
.with_tcp(tcp::Config::default(), noise::Config::new, yamux::Config::default())?
.with_behaviour(|key| {
let kameo = remote::Behaviour::new(
key.public().to_peer_id(),
remote::messaging::Config::default(),
);
Ok(MyBehaviour { kameo })
})?
.build();
// Initialize Kameo's global registry
swarm.behaviour().kameo.init_global();
// Listen on a specific address
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
// Run the swarm (see event handling section below)
// ... event loop code
Ok(())
}
Composing with Other Behaviors
One of the key advantages of custom configuration is the ability to compose Kameo with other libp2p protocols:
use libp2p::{gossipsub, mdns, kad, noise, tcp, yamux, swarm::NetworkBehaviour};
use kameo::remote;
#[derive(NetworkBehaviour)]
struct MyBehaviour {
kameo: remote::Behaviour,
gossipsub: gossipsub::Behaviour,
mdns: mdns::tokio::Behaviour,
kademlia: kad::Behaviour<kad::store::MemoryStore>,
}
// In your swarm builder:
.with_behaviour(|key| {
let peer_id = key.public().to_peer_id();
let kameo = remote::Behaviour::new(
peer_id,
remote::messaging::Config::default(),
);
let gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(key.clone()),
gossipsub::Config::default(),
)?;
let mdns = mdns::tokio::Behaviour::new(
mdns::Config::default(),
peer_id,
)?;
let kademlia = kad::Behaviour::new(
peer_id,
kad::store::MemoryStore::new(peer_id),
);
Ok(MyBehaviour { kameo, gossipsub, mdns, kademlia })
})?
Messaging Configuration
Customize Kameo's messaging behavior through the messaging::Config
:
let messaging_config = remote::messaging::Config::default()
.with_request_timeout(Duration::from_secs(30))
.with_max_concurrent_requests(1000)
.with_max_request_size(1024 * 1024) // 1MB
.with_max_response_size(10 * 1024 * 1024); // 10MB
let kameo = remote::Behaviour::new(peer_id, messaging_config);
Available configuration options:
- Request timeout: How long to wait for remote message responses
- Max concurrent streams: Limit concurrent outbound streams per peer
- Max request size: Limits the size in bytes of a single request
- Max response size: Limits the size in bytes of a single response
Transport Configuration
Configure exactly which transports your application supports:
use libp2p::{quic, websocket, relay};
let swarm = SwarmBuilder::with_new_identity()
.with_tokio()
// TCP with custom configuration
.with_tcp(
tcp::Config::default()
.port_reuse(true)
.nodelay(true),
noise::Config::new,
yamux::Config::default(),
)?
// QUIC for low-latency communication
.with_quic()
// WebSocket for browser connectivity
.with_websocket(
(websocket::Config::default(), tcp::Config::default()),
noise::Config::new,
yamux::Config::default(),
).await?
// Relay for NAT traversal
.with_relay_client(noise::Config::new, yamux::Config::default())?
// Your behavior configuration
.with_behaviour(|key| { /* ... */ })?
.build();
Event Handling
Handle both Kameo events and other protocol events in your swarm loop:
use libp2p::swarm::SwarmEvent;
use futures::StreamExt;
tokio::spawn(async move {
loop {
match swarm.select_next_some().await {
// Handle Kameo events
SwarmEvent::Behaviour(MyBehaviourEvent::Kameo(remote::Event::Registry(event))) => {
match event {
remote::registry::Event::LookupCompleted { .. } => {
println!("Actor lookup completed");
}
remote::registry::Event::RegisteredActor { .. } => {
println!("Actor registered successfully");
}
_ => {}
}
}
// Handle other protocol events
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(peers))) => {
for (peer_id, multiaddr) in peers {
println!("Discovered peer: {} at {}", peer_id, multiaddr);
swarm.add_peer_address(peer_id, multiaddr);
}
}
SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message {
message, ..
})) => {
println!("Received gossipsub message: {:?}", message.data);
}
// Handle connection events
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
println!("Connected to {}", peer_id);
}
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on {}", address);
}
_ => {}
}
}
});
Production Example
Here's a complete example suitable for production deployment:
use kameo::{prelude::*, remote};
use libp2p::{
multiaddr::Protocol,
noise, quic, tcp, yamux,
swarm::{NetworkBehaviour, SwarmEvent},
Multiaddr, PeerId,
};
use std::time::Duration;
use tracing::{info, warn};
#[derive(NetworkBehaviour)]
struct ProductionBehaviour {
kameo: remote::Behaviour,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create swarm with production-ready configuration
let mut swarm = libp2p::SwarmBuilder::with_new_identity()
.with_tokio()
.with_tcp(
tcp::Config::default()
.port_reuse(true)
.nodelay(true),
noise::Config::new,
yamux::Config::default(),
)?
.with_quic()
.with_behaviour(|key| {
let peer_id = key.public().to_peer_id();
let messaging_config = remote::messaging::Config::default()
.with_request_timeout(Duration::from_secs(60))
.with_max_concurrent_requests(500);
let kameo = remote::Behaviour::new(peer_id, messaging_config);
Ok(ProductionBehaviour { kameo })
})?
.with_swarm_config(|c| {
c.with_idle_connection_timeout(Duration::from_secs(300))
.with_max_negotiating_inbound_streams(1024)
})
.build();
// Initialize Kameo
swarm.behaviour().kameo.init_global();
// Listen on multiple addresses for redundancy
swarm.listen_on("/ip4/0.0.0.0/tcp/8020".parse()?)?;
swarm.listen_on("/ip4/0.0.0.0/udp/8021/quic-v1".parse()?)?;
// Connect to known bootstrap peers
let bootstrap_peers = vec![
"/ip4/203.0.113.1/tcp/8020/p2p/12D3KooWBootstrapPeer1".parse()?,
"/ip4/203.0.113.2/tcp/8020/p2p/12D3KooWBootstrapPeer2".parse()?,
];
for addr in bootstrap_peers {
swarm.dial(addr)?;
}
// Production event loop with error handling
loop {
match swarm.select_next_some().await {
SwarmEvent::Behaviour(ProductionBehaviourEvent::Kameo(event)) => {
info!("Kameo event: {:?}", event);
}
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => {
info!("Connected to {} via {}", peer_id, endpoint.get_remote_address());
}
SwarmEvent::ConnectionClosed { peer_id, cause, .. } => {
warn!("Connection to {} closed: {:?}", peer_id, cause);
}
SwarmEvent::NewListenAddr { address, .. } => {
info!("Listening on {}", address);
}
SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
warn!("Failed to connect to {:?}: {}", peer_id, error);
}
_ => {}
}
}
}
Migration from Bootstrap
If you start with bootstrap and later need custom configuration, the migration is straightforward:
- Replace
remote::bootstrap()
with a custom swarm setup - Add any additional protocols you need
- Configure transports and addressing for your deployment
The actor registration and messaging APIs remain exactly the same.
What's Next?
Now that you have your swarm configured, you're ready to register actors and enable peer discovery. The next step is understanding how actors register themselves and discover each other across the network.
Continue to Registering and Lookup to learn how to make your actors discoverable and find remote actors.