5252//! classes. See the [`crate::tc`] module for details on the qdisc hierarchy.
5353
5454use std:: {
55- any:: Any ,
5655 collections:: { HashMap , HashSet } ,
5756 fmt:: { Debug , Display } ,
5857 io,
@@ -137,8 +136,8 @@ pub struct Link(pub PeerId, pub PeerId);
137136impl Link {
138137 /// Create a new directed link from source to destination.
139138 #[ inline]
140- pub fn new ( source : PeerId , destination : PeerId ) -> Self {
141- Link ( source, destination)
139+ pub fn new ( source : impl Into < PeerId > , destination : impl Into < PeerId > ) -> Self {
140+ Link ( source. into ( ) , destination. into ( ) )
142141 }
143142
144143 /// Get the source peer (traffic originates here).
@@ -194,7 +193,7 @@ impl PeerTcState {
194193}
195194
196195/// Map from peer ID to peer instance.
197- pub type PeerMap = HashMap < PeerId , Peer < Context > > ;
196+ pub type PeerMap = HashMap < PeerId , Peer < PeerContext > > ;
198197
199198/// Map from peer ID to traffic control state.
200199type TcStateMap = HashMap < PeerId , PeerTcState > ;
@@ -217,21 +216,66 @@ impl Peer {
217216 }
218217}
219218
219+ pub ( crate ) type RuntimeFactory = Box < dyn FnOnce ( ) -> tokio:: runtime:: Runtime + Send > ;
220+
221+ pub fn default_runtime_factory ( ) -> RuntimeFactory {
222+ Box :: new ( || {
223+ tokio:: runtime:: Builder :: new_multi_thread ( ) . enable_all ( ) . build ( ) . expect ( "to create runtime" )
224+ } )
225+ }
226+
227+ /// Common context provided to all namespaces.
228+ ///
229+ /// This context gives access to rtnetlink for network configuration.
230+ #[ derive( Debug ) ]
231+ pub struct CommonContext {
232+ /// Handle for sending rtnetlink messages within this namespace.
233+ handle : rtnetlink:: Handle ,
234+ /// Background task processing rtnetlink responses.
235+ _connection_task : tokio:: task:: JoinHandle < ( ) > ,
236+ }
237+
220238/// Context provided to tasks running within a peer's namespace.
221239///
222240/// This context gives access to rtnetlink for network configuration
223241/// and metadata about the peer's position in the network.
224242#[ derive( Debug ) ]
225- pub struct Context {
243+ pub struct PeerContext {
226244 /// Handle for sending rtnetlink messages within this namespace.
227- handle : rtnetlink:: Handle ,
245+ pub handle : rtnetlink:: Handle ,
228246 /// Background task processing rtnetlink responses.
229247 _connection_task : tokio:: task:: JoinHandle < ( ) > ,
230-
231248 /// The subnet this network uses.
232- subnet : Subnet ,
249+ pub subnet : Subnet ,
233250 /// This peer's ID.
234- peer_id : usize ,
251+ pub peer_id : PeerId ,
252+ }
253+
254+ /// Options for configuring a peer.
255+ pub struct PeerOptions {
256+ runtime_factory : RuntimeFactory ,
257+ }
258+
259+ impl Default for PeerOptions {
260+ fn default ( ) -> Self {
261+ Self {
262+ runtime_factory : Box :: new ( || {
263+ tokio:: runtime:: Builder :: new_multi_thread ( )
264+ . enable_all ( )
265+ . build ( )
266+ . expect ( "to create runtime" )
267+ } ) ,
268+ }
269+ }
270+ }
271+
272+ impl PeerOptions {
273+ /// Create new peer options with a custom runtime factory.
274+ pub fn with_runtime (
275+ runtime_factory : impl FnOnce ( ) -> tokio:: runtime:: Runtime + Send + ' static ,
276+ ) -> Self {
277+ Self { runtime_factory : Box :: new ( runtime_factory) }
278+ }
235279}
236280
237281// -------------------------------------------------------------------------------------
@@ -289,7 +333,7 @@ pub type Result<T> = std::result::Result<T, Error>;
289333/// # Example
290334///
291335/// ```no_run
292- /// use msg_sim::network::{Network, Link};
336+ /// use msg_sim::network::{Network, Link, PeerOptions };
293337/// use msg_sim::tc::impairment::LinkImpairment;
294338/// use msg_sim::ip::Subnet;
295339/// use std::net::Ipv4Addr;
@@ -344,7 +388,7 @@ pub struct Network {
344388 subnet : Subnet ,
345389
346390 /// The hub namespace containing the bridge device.
347- network_hub_namespace : NetworkNamespace < Context > ,
391+ network_hub_namespace : NetworkNamespace < CommonContext > ,
348392
349393 /// Rtnetlink handle bound to the host namespace.
350394 ///
@@ -379,11 +423,13 @@ impl Network {
379423 . map ( |( connection, handle, _) | ( handle, tokio:: task:: spawn ( connection) ) )
380424 . unwrap ( ) ;
381425
382- Context { handle, subnet , peer_id : 0 , _connection_task }
426+ CommonContext { handle, _connection_task }
383427 } ;
384428
385429 // Create the hub namespace that will host the bridge.
386- let namespace_hub = NetworkNamespace :: new ( Self :: hub_namespace_name ( ) , make_ctx) . await ?;
430+ let namespace_hub =
431+ NetworkNamespace :: new ( Self :: hub_namespace_name ( ) , default_runtime_factory ( ) , make_ctx)
432+ . await ?;
387433 let fd = namespace_hub. fd ( ) ;
388434
389435 let network = Self {
@@ -418,7 +464,7 @@ impl Network {
418464 /// 1. A new network namespace for the peer
419465 /// 2. A veth pair connecting the peer to the hub bridge
420466 /// 3. IP address assignment based on the subnet and peer ID
421- pub async fn add_peer ( & mut self ) -> Result < PeerId > {
467+ pub async fn add_peer_with_options ( & mut self , options : PeerOptions ) -> Result < PeerId > {
422468 let peer_id = PEER_ID_NEXT . load ( Ordering :: Relaxed ) ;
423469 let namespace_name = peer_id. namespace_name ( ) ;
424470 let veth_name = Arc :: new ( peer_id. veth_name ( ) ) ;
@@ -435,10 +481,12 @@ impl Network {
435481 . map ( |( connection, handle, _) | ( handle, tokio:: task:: spawn ( connection) ) )
436482 . expect ( "to create rtnetlink socket" ) ;
437483
438- Context { handle, peer_id , subnet, _connection_task }
484+ PeerContext { handle, _connection_task , subnet, peer_id }
439485 } ;
440486
441- let network_namespace = NetworkNamespace :: new ( namespace_name. clone ( ) , make_ctx) . await ?;
487+ let network_namespace =
488+ NetworkNamespace :: new ( namespace_name. clone ( ) , options. runtime_factory , make_ctx)
489+ . await ?;
442490
443491 // Step 1: Create the veth pair in the host namespace.
444492 // One end (veth_name) will go to the peer, the other (veth_br_name) to the bridge.
@@ -476,7 +524,7 @@ impl Network {
476524
477525 network_namespace
478526 . task_sender
479- . submit ( |ctx| {
527+ . submit ( |ctx : & mut PeerContext | {
480528 Box :: pin ( async move {
481529 let address = ctx. peer_id . veth_address ( ctx. subnet ) ;
482530 let mask = ctx. subnet . netmask ;
@@ -541,6 +589,11 @@ impl Network {
541589 Ok ( peer_id)
542590 }
543591
592+ /// See [`Self::add_peer_with_options`].
593+ pub async fn add_peer ( & mut self ) -> Result < PeerId > {
594+ self . add_peer_with_options ( PeerOptions :: default ( ) ) . await
595+ }
596+
544597 /// Run a task in a peer's network namespace.
545598 ///
546599 /// The provided closure receives a mutable reference to the namespace's context,
@@ -560,7 +613,7 @@ impl Network {
560613 ///
561614 /// ```no_run
562615 /// use msg_sim::ip::Subnet;
563- /// use msg_sim::network::Network;
616+ /// use msg_sim::network::{ Network, PeerOptions} ;
564617 /// use std::net::Ipv4Addr;
565618 /// use tokio::net::TcpListener;
566619 ///
@@ -589,8 +642,8 @@ impl Network {
589642 fut : F ,
590643 ) -> Result < impl Future < Output = std:: result:: Result < T , oneshot:: error:: RecvError > > >
591644 where
592- T : Any + Send + ' static ,
593- F : for < ' a > FnOnce ( & ' a mut Context ) -> DynFuture < ' a , T > + Send + ' static ,
645+ T : Send + ' static ,
646+ F : for < ' a > FnOnce ( & ' a mut PeerContext ) -> DynFuture < ' a , T > + Send + ' static ,
594647 {
595648 let Some ( peer) = self . peers . get ( & peer_id) else {
596649 return Err ( Error :: PeerNotFound ( peer_id) ) ;
@@ -630,7 +683,7 @@ impl Network {
630683 /// ```no_run
631684 /// use msg_sim::{
632685 /// ip::Subnet,
633- /// network::{Link, Network},
686+ /// network::{Link, Network, PeerOptions },
634687 /// tc::impairment::LinkImpairment
635688 /// };
636689 ///
@@ -681,7 +734,7 @@ impl Network {
681734 src_peer
682735 . namespace
683736 . task_sender
684- . submit ( move |ctx| {
737+ . submit ( move |ctx : & mut PeerContext | {
685738 let span = tracing:: debug_span!(
686739 "apply_impairment" ,
687740 link = %link,
@@ -996,13 +1049,17 @@ mod msg_sim_network {
9961049 req_socket_2. connect_sync ( SocketAddr :: new ( address_2, port) ) ;
9971050 req_socket_3. connect_sync ( SocketAddr :: new ( address_3, port) ) ;
9981051
999- // Measure RTT to peer 2 (should be ~200ms round trip for 100ms one-way)
1052+ // Wait for both TCP connections to be established before starting
1053+ // measurements. Peer 3 has 500ms one-way latency, so TCP handshake takes ~1s.
1054+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 1500 ) ) . await ;
1055+
1056+ // Measure RTT to peer 2 (should be ~100ms for one-way latency)
10001057 let start = Instant :: now ( ) ;
10011058 let resp = req_socket_2. request ( "ping" . into ( ) ) . await . unwrap ( ) ;
10021059 let rtt_2 = start. elapsed ( ) ;
10031060 assert_eq ! ( resp. as_ref( ) , b"peer2" ) ;
10041061
1005- // Measure RTT to peer 3 (should be ~1000ms round trip for 500ms one-way)
1062+ // Measure RTT to peer 3 (should be ~500ms for one-way latency )
10061063 let start = Instant :: now ( ) ;
10071064 let resp = req_socket_3. request ( "ping" . into ( ) ) . await . unwrap ( ) ;
10081065 let rtt_3 = start. elapsed ( ) ;
0 commit comments