@@ -79,6 +79,7 @@ use crate::{
7979 net_report:: { self , IfStateDetails , IpMappedAddresses , Report } ,
8080} ;
8181
82+ mod interface_priority;
8283mod metrics;
8384mod node_map;
8485
@@ -87,6 +88,7 @@ pub(crate) mod transports;
8788pub use node_map:: Source ;
8889
8990pub use self :: {
91+ interface_priority:: InterfacePriority ,
9092 metrics:: Metrics ,
9193 node_map:: { ConnectionType , ControlMsg , DirectAddrInfo } ,
9294} ;
@@ -157,6 +159,12 @@ pub(crate) struct Options {
157159 #[ cfg( any( test, feature = "test-utils" ) ) ]
158160 pub ( crate ) path_selection : PathSelection ,
159161
162+ /// Interface-based path prioritization configuration.
163+ ///
164+ /// Allows preferring certain network interfaces over others when multiple paths exist.
165+ /// Useful for scenarios like preferring Ethernet over Wi-Fi.
166+ pub ( crate ) interface_priority : InterfacePriority ,
167+
160168 pub ( crate ) metrics : EndpointMetrics ,
161169}
162170
@@ -685,7 +693,7 @@ impl MagicSock {
685693 // UDP
686694
687695 // Update the NodeMap and remap RecvMeta to the NodeIdMappedAddr.
688- match self . node_map . receive_udp ( * addr) {
696+ match self . node_map . receive_udp ( * addr, quinn_meta . dst_ip ) {
689697 None => {
690698 // Check if this address is mapped to an IpMappedAddr
691699 if let Some ( ip_mapped_addr) =
@@ -1365,6 +1373,7 @@ impl Handle {
13651373 insecure_skip_relay_cert_verify,
13661374 #[ cfg ( any ( test, feature = "test-utils" ) ) ]
13671375 path_selection,
1376+ interface_priority,
13681377 metrics,
13691378 } = opts;
13701379
@@ -1384,6 +1393,23 @@ impl Handle {
13841393 }
13851394 } ;
13861395
1396+ // Load interface priority from environment if not explicitly set
1397+ let interface_priority = if interface_priority. is_empty ( ) {
1398+ match InterfacePriority :: from_env ( ) {
1399+ Ok ( Some ( priority) ) => {
1400+ info ! ( "Loaded interface priority from IROH_INTERFACE_PRIORITY" ) ;
1401+ priority
1402+ }
1403+ Ok ( None ) => InterfacePriority :: default ( ) ,
1404+ Err ( e) => {
1405+ warn ! ( "Failed to parse IROH_INTERFACE_PRIORITY: {}" , e) ;
1406+ InterfacePriority :: default ( )
1407+ }
1408+ }
1409+ } else {
1410+ interface_priority
1411+ } ;
1412+
13871413 let addr_v4 = addr_v4. unwrap_or_else ( || SocketAddrV4 :: new ( Ipv4Addr :: UNSPECIFIED , 0 ) ) ;
13881414
13891415 #[ cfg( not( wasm_browser) ) ]
@@ -1397,13 +1423,7 @@ impl Handle {
13971423 let ipv6_reported = false ;
13981424
13991425 // load the node data
1400- let node_map = NodeMap :: load_from_vec (
1401- Vec :: new ( ) ,
1402- #[ cfg( any( test, feature = "test-utils" ) ) ]
1403- path_selection,
1404- ipv6_reported,
1405- & metrics. magicsock ,
1406- ) ;
1426+ let node_addrs = node_map. unwrap_or_default ( ) ;
14071427
14081428 let my_relay = Watchable :: new ( None ) ;
14091429 let ipv6_reported = Arc :: new ( AtomicBool :: new ( ipv6_reported) ) ;
@@ -1430,6 +1450,35 @@ impl Handle {
14301450 #[ cfg( wasm_browser) ]
14311451 let transports = Transports :: new ( relay_transports) ;
14321452
1453+ // Create network monitor early, so we can build the interface map
1454+ let network_monitor = netmon:: Monitor :: new ( )
1455+ . await
1456+ . context ( CreateNetmonMonitorSnafu ) ?;
1457+
1458+ // Build interface map from bind addresses and network state
1459+ #[ cfg( not( wasm_browser) ) ]
1460+ let ip_to_interface = {
1461+ let netmon_state = network_monitor. interface_state ( ) . get ( ) ;
1462+ let bind_addrs = transports. ip_bind_addrs ( ) ;
1463+ interface_priority:: build_interface_map ( & bind_addrs, & netmon_state)
1464+ } ;
1465+ #[ cfg( wasm_browser) ]
1466+ let ip_to_interface = Default :: default ( ) ;
1467+
1468+ // Create NodeMap with the interface map
1469+ let node_map = NodeMap :: load_from_vec (
1470+ node_addrs,
1471+ #[ cfg( any( test, feature = "test-utils" ) ) ]
1472+ path_selection,
1473+ interface_priority. clone ( ) ,
1474+ ip_to_interface,
1475+ #[ cfg( not( wasm_browser) ) ]
1476+ ipv6,
1477+ #[ cfg( wasm_browser) ]
1478+ false ,
1479+ & metrics. magicsock ,
1480+ ) ;
1481+
14331482 let ( disco, disco_receiver) = DiscoState :: new ( secret_encryption_key) ;
14341483
14351484 let msock = Arc :: new ( MagicSock {
@@ -1438,7 +1487,7 @@ impl Handle {
14381487 closed : AtomicBool :: new ( false ) ,
14391488 disco,
14401489 actor_sender : actor_sender. clone ( ) ,
1441- ipv6_reported,
1490+ ipv6_reported : ipv6_reported . clone ( ) ,
14421491 node_map,
14431492 ip_mapped_addrs : ip_mapped_addrs. clone ( ) ,
14441493 discovery,
@@ -1479,10 +1528,6 @@ impl Handle {
14791528 )
14801529 . context ( CreateQuinnEndpointSnafu ) ?;
14811530
1482- let network_monitor = netmon:: Monitor :: new ( )
1483- . await
1484- . context ( CreateNetmonMonitorSnafu ) ?;
1485-
14861531 let qad_endpoint = endpoint. clone ( ) ;
14871532
14881533 #[ cfg( any( test, feature = "test-utils" ) ) ]
@@ -1528,6 +1573,7 @@ impl Handle {
15281573 ) ;
15291574
15301575 let netmon_watcher = network_monitor. interface_state ( ) ;
1576+
15311577 let actor = Actor {
15321578 msg_receiver : actor_receiver,
15331579 msock : msock. clone ( ) ,
@@ -2563,6 +2609,7 @@ mod tests {
25632609 addr_v6 : None ,
25642610 secret_key,
25652611 relay_map : RelayMap :: empty ( ) ,
2612+ node_map : None ,
25662613 discovery : Default :: default ( ) ,
25672614 proxy_url : None ,
25682615 dns_resolver : DnsResolver :: new ( ) ,
@@ -2571,6 +2618,7 @@ mod tests {
25712618 insecure_skip_relay_cert_verify : false ,
25722619 #[ cfg( any( test, feature = "test-utils" ) ) ]
25732620 path_selection : PathSelection :: default ( ) ,
2621+ interface_priority : Default :: default ( ) ,
25742622 discovery_user_data : None ,
25752623 metrics : Default :: default ( ) ,
25762624 }
@@ -3109,6 +3157,7 @@ mod tests {
31093157 server_config,
31103158 insecure_skip_relay_cert_verify : false ,
31113159 path_selection : PathSelection :: default ( ) ,
3160+ interface_priority : Default :: default ( ) ,
31123161 metrics : Default :: default ( ) ,
31133162 } ;
31143163 let msock = MagicSock :: spawn ( opts) . await ?;
0 commit comments