@@ -77,6 +77,7 @@ use crate::{
7777 net_report:: { self , IfStateDetails , IpMappedAddresses , Report } ,
7878} ;
7979
80+ mod interface_priority;
8081mod metrics;
8182mod node_map;
8283
@@ -85,6 +86,7 @@ pub(crate) mod transports;
8586pub use node_map:: Source ;
8687
8788pub use self :: {
89+ interface_priority:: InterfacePriority ,
8890 metrics:: Metrics ,
8991 node_map:: { ConnectionType , ControlMsg , DirectAddrInfo , RemoteInfo } ,
9092} ;
@@ -158,6 +160,12 @@ pub(crate) struct Options {
158160 #[ cfg( any( test, feature = "test-utils" ) ) ]
159161 pub ( crate ) path_selection : PathSelection ,
160162
163+ /// Interface-based path prioritization configuration.
164+ ///
165+ /// Allows preferring certain network interfaces over others when multiple paths exist.
166+ /// Useful for scenarios like preferring Ethernet over Wi-Fi.
167+ pub ( crate ) interface_priority : InterfacePriority ,
168+
161169 pub ( crate ) metrics : EndpointMetrics ,
162170}
163171
@@ -688,7 +696,7 @@ impl MagicSock {
688696 // UDP
689697
690698 // Update the NodeMap and remap RecvMeta to the NodeIdMappedAddr.
691- match self . node_map . receive_udp ( * addr) {
699+ match self . node_map . receive_udp ( * addr, quinn_meta . dst_ip ) {
692700 None => {
693701 // Check if this address is mapped to an IpMappedAddr
694702 if let Some ( ip_mapped_addr) =
@@ -1367,9 +1375,27 @@ impl Handle {
13671375 insecure_skip_relay_cert_verify,
13681376 #[ cfg ( any ( test, feature = "test-utils" ) ) ]
13691377 path_selection,
1378+ interface_priority,
13701379 metrics,
13711380 } = opts;
13721381
1382+ // Load interface priority from environment if not explicitly set
1383+ let interface_priority = if interface_priority. is_empty ( ) {
1384+ match InterfacePriority :: from_env ( ) {
1385+ Ok ( Some ( priority) ) => {
1386+ info ! ( "Loaded interface priority from IROH_INTERFACE_PRIORITY" ) ;
1387+ priority
1388+ }
1389+ Ok ( None ) => InterfacePriority :: default ( ) ,
1390+ Err ( e) => {
1391+ warn ! ( "Failed to parse IROH_INTERFACE_PRIORITY: {}" , e) ;
1392+ InterfacePriority :: default ( )
1393+ }
1394+ }
1395+ } else {
1396+ interface_priority
1397+ } ;
1398+
13731399 let addr_v4 = addr_v4. unwrap_or_else ( || SocketAddrV4 :: new ( Ipv4Addr :: UNSPECIFIED , 0 ) ) ;
13741400
13751401 #[ cfg( not( wasm_browser) ) ]
@@ -1383,14 +1409,7 @@ impl Handle {
13831409 let ipv6_reported = false ;
13841410
13851411 // load the node data
1386- let node_map = node_map. unwrap_or_default ( ) ;
1387- let node_map = NodeMap :: load_from_vec (
1388- node_map,
1389- #[ cfg( any( test, feature = "test-utils" ) ) ]
1390- path_selection,
1391- ipv6_reported,
1392- & metrics. magicsock ,
1393- ) ;
1412+ let node_addrs = node_map. unwrap_or_default ( ) ;
13941413
13951414 let my_relay = Watchable :: new ( None ) ;
13961415 let ipv6_reported = Arc :: new ( AtomicBool :: new ( ipv6_reported) ) ;
@@ -1417,6 +1436,35 @@ impl Handle {
14171436 #[ cfg( wasm_browser) ]
14181437 let transports = Transports :: new ( relay_transports) ;
14191438
1439+ // Create network monitor early, so we can build the interface map
1440+ let network_monitor = netmon:: Monitor :: new ( )
1441+ . await
1442+ . context ( CreateNetmonMonitorSnafu ) ?;
1443+
1444+ // Build interface map from bind addresses and network state
1445+ #[ cfg( not( wasm_browser) ) ]
1446+ let ip_to_interface = {
1447+ let netmon_state = network_monitor. interface_state ( ) . get ( ) ;
1448+ let bind_addrs = transports. ip_bind_addrs ( ) ;
1449+ interface_priority:: build_interface_map ( & bind_addrs, & netmon_state)
1450+ } ;
1451+ #[ cfg( wasm_browser) ]
1452+ let ip_to_interface = Default :: default ( ) ;
1453+
1454+ // Create NodeMap with the interface map
1455+ let node_map = NodeMap :: load_from_vec (
1456+ node_addrs,
1457+ #[ cfg( any( test, feature = "test-utils" ) ) ]
1458+ path_selection,
1459+ interface_priority. clone ( ) ,
1460+ ip_to_interface,
1461+ #[ cfg( not( wasm_browser) ) ]
1462+ ipv6,
1463+ #[ cfg( wasm_browser) ]
1464+ false ,
1465+ & metrics. magicsock ,
1466+ ) ;
1467+
14201468 let ( disco, disco_receiver) = DiscoState :: new ( secret_encryption_key) ;
14211469
14221470 let msock = Arc :: new ( MagicSock {
@@ -1425,7 +1473,7 @@ impl Handle {
14251473 closed : AtomicBool :: new ( false ) ,
14261474 disco,
14271475 actor_sender : actor_sender. clone ( ) ,
1428- ipv6_reported,
1476+ ipv6_reported : ipv6_reported . clone ( ) ,
14291477 node_map,
14301478 ip_mapped_addrs : ip_mapped_addrs. clone ( ) ,
14311479 discovery,
@@ -1467,10 +1515,6 @@ impl Handle {
14671515 )
14681516 . context ( CreateQuinnEndpointSnafu ) ?;
14691517
1470- let network_monitor = netmon:: Monitor :: new ( )
1471- . await
1472- . context ( CreateNetmonMonitorSnafu ) ?;
1473-
14741518 let qad_endpoint = endpoint. clone ( ) ;
14751519
14761520 #[ cfg( any( test, feature = "test-utils" ) ) ]
@@ -1516,6 +1560,7 @@ impl Handle {
15161560 ) ;
15171561
15181562 let netmon_watcher = network_monitor. interface_state ( ) ;
1563+
15191564 let actor = Actor {
15201565 msg_receiver : actor_receiver,
15211566 msock : msock. clone ( ) ,
@@ -2589,6 +2634,7 @@ mod tests {
25892634 insecure_skip_relay_cert_verify : false ,
25902635 #[ cfg( any( test, feature = "test-utils" ) ) ]
25912636 path_selection : PathSelection :: default ( ) ,
2637+ interface_priority : Default :: default ( ) ,
25922638 discovery_user_data : None ,
25932639 metrics : Default :: default ( ) ,
25942640 }
@@ -3121,6 +3167,7 @@ mod tests {
31213167 server_config,
31223168 insecure_skip_relay_cert_verify : false ,
31233169 path_selection : PathSelection :: default ( ) ,
3170+ interface_priority : Default :: default ( ) ,
31243171 metrics : Default :: default ( ) ,
31253172 } ;
31263173 let msock = MagicSock :: spawn ( opts) . await ?;
0 commit comments