|
1 | 1 | use std::{ |
2 | 2 | collections::{BTreeSet, VecDeque}, |
3 | | - net::SocketAddr, |
| 3 | + net::{SocketAddr, SocketAddrV4, SocketAddrV6}, |
4 | 4 | pin::Pin, |
5 | 5 | sync::Arc, |
6 | 6 | task::Poll, |
@@ -74,6 +74,12 @@ mod path_state; |
74 | 74 | /// in a high frequency, and to keep data about previous path around for subsequent connections. |
75 | 75 | const ACTOR_MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(60); |
76 | 76 |
|
| 77 | +/// The minimum RTT difference to make it worth switching IP paths |
| 78 | +const RTT_SWITCHING_MIN_IP: Duration = Duration::from_millis(5); |
| 79 | + |
| 80 | +/// How much do we prefer IPv6 over IPv4? |
| 81 | +const IPV6_RTT_ADVANTAGE: Duration = Duration::from_millis(3); |
| 82 | + |
77 | 83 | /// A stream of events from all paths for all connections. |
78 | 84 | /// |
79 | 85 | /// The connection is identified using [`ConnId`]. The event `Err` variant happens when the |
@@ -856,34 +862,58 @@ impl RemoteStateActor { |
856 | 862 | .filter_map(|(addr, rtts)| rtts.into_iter().min().map(|rtt| (addr, rtt))) |
857 | 863 | .collect(); |
858 | 864 |
|
859 | | - // Find the fastest direct or relay path. |
860 | | - const IPV6_RTT_ADVANTAGE: Duration = Duration::from_millis(3); |
861 | | - let direct_path = path_rtts |
| 865 | + // Find the fastest direct IPv4 path. |
| 866 | + let direct_path_ipv4 = path_rtts |
862 | 867 | .iter() |
863 | | - .filter(|(addr, _rtt)| addr.is_ip()) |
864 | | - .map(|(addr, rtt)| { |
865 | | - if addr.is_ipv4() { |
866 | | - (*rtt + IPV6_RTT_ADVANTAGE, addr) |
| 868 | + .filter_map(|(addr, rtt)| { |
| 869 | + if let transports::Addr::Ip(SocketAddr::V4(addr)) = *addr { |
| 870 | + Some((addr, *rtt)) |
867 | 871 | } else { |
868 | | - (*rtt, addr) |
| 872 | + None |
869 | 873 | } |
870 | 874 | }) |
871 | | - .min(); |
872 | | - let selected_path = direct_path.or_else(|| { |
873 | | - // Find the fasted relay path. |
874 | | - path_rtts |
875 | | - .iter() |
876 | | - .filter(|(addr, _rtt)| addr.is_relay()) |
877 | | - .map(|(addr, rtt)| (*rtt, addr)) |
878 | | - .min() |
879 | | - }); |
880 | | - if let Some((rtt, addr)) = selected_path { |
| 875 | + .min_by_key(|(_addr, rtt)| *rtt); |
| 876 | + |
| 877 | + // Find the fastest direct IPv6 path. |
| 878 | + let direct_path_ipv6 = path_rtts |
| 879 | + .iter() |
| 880 | + .filter_map(|(addr, rtt)| { |
| 881 | + if let transports::Addr::Ip(SocketAddr::V6(addr)) = *addr { |
| 882 | + Some((addr, *rtt)) |
| 883 | + } else { |
| 884 | + None |
| 885 | + } |
| 886 | + }) |
| 887 | + .min_by_key(|(_addr, rtt)| *rtt); |
| 888 | + |
| 889 | + // Find the fastest relay path. |
| 890 | + let relay_path = path_rtts |
| 891 | + .iter() |
| 892 | + .filter(|(addr, _rtt)| addr.is_relay()) |
| 893 | + .min_by_key(|(_addr, rtt)| *rtt) |
| 894 | + .map(|(addr, rtt)| (addr.clone(), *rtt)); |
| 895 | + |
| 896 | + let current_path = self |
| 897 | + .selected_path |
| 898 | + .get() |
| 899 | + .and_then(|addr| path_rtts.get(&addr).copied().map(|rtt| (addr, rtt))); |
| 900 | + let selected_path = select_best_path( |
| 901 | + current_path.clone(), |
| 902 | + direct_path_ipv4, |
| 903 | + direct_path_ipv6, |
| 904 | + relay_path, |
| 905 | + ); |
| 906 | + |
| 907 | + // Apply our new path |
| 908 | + if let Some((addr, rtt)) = selected_path { |
881 | 909 | let prev = self.selected_path.set(Some(addr.clone())); |
882 | 910 | if prev.is_ok() { |
883 | 911 | debug!(?addr, ?rtt, ?prev, "selected new path"); |
884 | 912 | } |
885 | | - self.open_path(addr); |
886 | | - self.close_redundant_paths(addr); |
| 913 | + self.open_path(&addr); |
| 914 | + self.close_redundant_paths(&addr); |
| 915 | + } else { |
| 916 | + trace!(?current_path, "keeping current path"); |
887 | 917 | } |
888 | 918 | } |
889 | 919 |
|
@@ -932,6 +962,83 @@ impl RemoteStateActor { |
932 | 962 | } |
933 | 963 | } |
934 | 964 |
|
| 965 | +/// Returns `Some` if a new path should be selected, `None` if the `current_path` should |
| 966 | +/// continued to be used. |
| 967 | +fn select_best_path( |
| 968 | + current_path: Option<(transports::Addr, Duration)>, |
| 969 | + direct_path_ipv4: Option<(SocketAddrV4, Duration)>, |
| 970 | + direct_path_ipv6: Option<(SocketAddrV6, Duration)>, |
| 971 | + new_relay_path: Option<(transports::Addr, Duration)>, |
| 972 | +) -> Option<(transports::Addr, Duration)> { |
| 973 | + // Determine the best new IP path. |
| 974 | + let best_ip_path = match (direct_path_ipv4, direct_path_ipv6) { |
| 975 | + (Some((addr_v4, rtt_v4)), Some((addr_v6, rtt_v6))) => { |
| 976 | + Some(select_v4_v6(addr_v4, rtt_v4, addr_v6, rtt_v6)) |
| 977 | + } |
| 978 | + (None, Some((addr_v6, rtt_v6))) => Some((addr_v6.into(), rtt_v6)), |
| 979 | + (Some((addr_v4, rtt_v4)), None) => Some((addr_v4.into(), rtt_v4)), |
| 980 | + (None, None) => None, |
| 981 | + }; |
| 982 | + let best_ip_path = best_ip_path.map(|(addr, rtt)| (transports::Addr::Ip(addr), rtt)); |
| 983 | + |
| 984 | + match current_path { |
| 985 | + None => { |
| 986 | + // If we currently have no path |
| 987 | + if best_ip_path.is_some() { |
| 988 | + // Use the best IP path |
| 989 | + best_ip_path |
| 990 | + } else { |
| 991 | + // Use the new relay path |
| 992 | + new_relay_path |
| 993 | + } |
| 994 | + } |
| 995 | + Some((transports::Addr::Relay(..), _)) => { |
| 996 | + // If we have a current path, but it is relay |
| 997 | + if best_ip_path.is_some() { |
| 998 | + // Use the best IP path |
| 999 | + best_ip_path |
| 1000 | + } else { |
| 1001 | + // Use the new relay path |
| 1002 | + new_relay_path |
| 1003 | + } |
| 1004 | + } |
| 1005 | + Some((transports::Addr::Ip(_), current_rtt)) => { |
| 1006 | + match &best_ip_path { |
| 1007 | + Some((_, new_rtt)) => { |
| 1008 | + // Comparing available IP paths |
| 1009 | + if current_rtt >= *new_rtt + RTT_SWITCHING_MIN_IP { |
| 1010 | + // New IP path is faster |
| 1011 | + best_ip_path |
| 1012 | + } else { |
| 1013 | + // New IP is not faster |
| 1014 | + None |
| 1015 | + } |
| 1016 | + } |
| 1017 | + None => { |
| 1018 | + // No new IP path, don't switch away |
| 1019 | + None |
| 1020 | + } |
| 1021 | + } |
| 1022 | + } |
| 1023 | + } |
| 1024 | +} |
| 1025 | + |
| 1026 | +/// Compare two IP addrs v4 & v6 and selects the "best" one. |
| 1027 | +/// |
| 1028 | +/// This prefers IPv6 paths over IPv4 paths. |
| 1029 | +fn select_v4_v6( |
| 1030 | + addr_v4: SocketAddrV4, |
| 1031 | + rtt_v4: Duration, |
| 1032 | + addr_v6: SocketAddrV6, |
| 1033 | + rtt_v6: Duration, |
| 1034 | +) -> (SocketAddr, Duration) { |
| 1035 | + if rtt_v6 <= rtt_v4 + IPV6_RTT_ADVANTAGE { |
| 1036 | + (addr_v6.into(), rtt_v6) |
| 1037 | + } else { |
| 1038 | + (addr_v4.into(), rtt_v4) |
| 1039 | + } |
| 1040 | +} |
| 1041 | + |
935 | 1042 | fn send_datagram<'a>( |
936 | 1043 | sender: &'a mut TransportsSender, |
937 | 1044 | dst: transports::Addr, |
@@ -1319,3 +1426,32 @@ fn to_transports_addr( |
1319 | 1426 | } |
1320 | 1427 | }) |
1321 | 1428 | } |
| 1429 | + |
| 1430 | +#[cfg(test)] |
| 1431 | +mod tests { |
| 1432 | + use std::net::{Ipv4Addr, Ipv6Addr}; |
| 1433 | + |
| 1434 | + use super::*; |
| 1435 | + |
| 1436 | + #[test] |
| 1437 | + fn test_select_v4_v6_addr() { |
| 1438 | + let v4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1); |
| 1439 | + let v6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 1, 0, 0); |
| 1440 | + |
| 1441 | + // Same RTT, prefer v6 |
| 1442 | + let (addr, rtt) = |
| 1443 | + select_v4_v6(v4, Duration::from_millis(10), v6, Duration::from_millis(10)); |
| 1444 | + assert_eq!(addr, v6.into()); |
| 1445 | + assert_eq!(rtt, Duration::from_millis(10)); |
| 1446 | + |
| 1447 | + // v4 better |
| 1448 | + let (addr, rtt) = select_v4_v6(v4, Duration::from_millis(1), v6, Duration::from_millis(10)); |
| 1449 | + assert_eq!(addr, v4.into()); |
| 1450 | + assert_eq!(rtt, Duration::from_millis(1)); |
| 1451 | + |
| 1452 | + // v6 better |
| 1453 | + let (addr, rtt) = select_v4_v6(v4, Duration::from_millis(10), v6, Duration::from_millis(1)); |
| 1454 | + assert_eq!(addr, v6.into()); |
| 1455 | + assert_eq!(rtt, Duration::from_millis(1)); |
| 1456 | + } |
| 1457 | +} |
0 commit comments