Skip to content

Commit

Permalink
Parse ipconfig instead of the routing table
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkisemils committed Oct 3, 2023
1 parent b42ed8b commit 850ee32
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 23 deletions.
2 changes: 1 addition & 1 deletion talpid-routing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ err-derive = { workspace = true }
futures = "0.3.15"
ipnetwork = "0.16"
log = { workspace = true }
tokio = { workspace = true, features = ["process", "rt-multi-thread", "net"] }
tokio = { workspace = true, features = ["process", "rt-multi-thread", "net", "io-util"] }

[target.'cfg(not(target_os="android"))'.dependencies]
talpid-types = { path = "../talpid-types" }
Expand Down
144 changes: 123 additions & 21 deletions talpid-routing/src/unix/macos/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use nix::net::if_::{if_nametoindex, InterfaceFlags};
use std::{
ffi::{CStr, CString},
io,
net::{Ipv4Addr, Ipv6Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};

use system_configuration::{
core_foundation::string::CFString,
network_configuration::{SCNetworkService, SCNetworkSet},
Expand Down Expand Up @@ -82,7 +83,6 @@ pub async fn get_unscoped_default_route(

Some(route)
}

/// Retrieve the best current default route. That is the first scoped default route, ordered by
/// network service order, and with interfaces filtered out if they do not have valid IP addresses
/// assigned.
Expand All @@ -98,34 +98,63 @@ pub async fn get_best_default_route(
msg = msg.set_gateway_route(true);

for iface in network_service_order() {
let iface_bytes = match CString::new(iface.as_bytes()) {
Ok(name) => name,
Err(error) => {
log::error!("Invalid interface name: {iface}, {error}");
continue;
}
};

// Get interface ID
let index = match if_nametoindex(iface_bytes.as_c_str()) {
Ok(index) => index,
Err(_error) => {
continue;
}
let Ok(Some(router_addr)) = get_router_address(family, &iface).await else {
continue;
};

// Request ifscoped default route for this interface
let route_msg = msg.clone().set_ifscope(u16::try_from(index).unwrap());
if let Ok(Some(route)) = routing_table.get_route(&route_msg).await {
if is_active_interface(&iface, family).unwrap_or(true) {
return Some(route);
}
let route_msg = msg.clone().set_gateway_addr(router_addr);
if is_active_interface(&iface, family).unwrap_or(true) {
return Some(route_msg);
}
}

None
}

async fn get_router_address(family: Family, interface_name: &str) -> io::Result<Option<IpAddr>> {
let output = tokio::process::Command::new("ipconfig")
.arg("getsummary")
.arg(interface_name)
.output()
.await?
.stdout;

let Ok(output_str) = std::str::from_utf8(&output) else {
return Ok(None);
};

match family {
Family::V4 => Ok(parse_v4_ipconfig_output(output_str)),
Family::V6 => Ok(parse_v6_ipconfig_output(output_str)),
}
}

fn parse_v4_ipconfig_output(output: &str) -> Option<IpAddr> {
let mut iter = output.split_whitespace();
loop {
let next_chunk = iter.next()?;
if next_chunk == "Router" && iter.next()? == ":" {
return iter.next()?.parse().ok();
}
}
}

fn parse_v6_ipconfig_output(output: &str) -> Option<IpAddr> {
let mut iter = output.split_whitespace();
let pattern = ["RouterAdvertisement", ":", "from"];
loop {
let mut next_chunk = iter.next()?;
for expected_chunk in pattern {
if expected_chunk != next_chunk {
continue;
}
next_chunk = iter.next()?;
}
return iter.next()?.trim_end_matches(",").parse().ok();
}
}

fn network_service_order() -> Vec<String> {
let prefs = SCPreferences::default(&CFString::new("talpid-routing"));
let services = SCNetworkService::get_services(&prefs);
Expand Down Expand Up @@ -176,3 +205,76 @@ fn is_routable_v6(addr: &Ipv6Addr) -> bool {
// !(link local)
&& (addr.segments()[0] & 0xffc0) != 0xfe80
}

#[cfg(test)]
const TEST_IPCONFIG_OUTPUT: &str = "<dictionary> {
Hashed-BSSID : 86:a2:7a:bb:7c:5c
IPv4 : <array> {
0 : <dictionary> {
Addresses : <array> {
0 : 192.168.1.3
}
ChildServiceID : LINKLOCAL-en0
ConfigMethod : Manual
IsPublished : TRUE
ManualAddress : 192.168.1.3
ManualSubnetMask : 255.255.255.0
Router : 192.168.1.1
RouterARPVerified : TRUE
ServiceID : 400B48FB-2585-41DF-8459-30C5C6D5621C
SubnetMasks : <array> {
0 : 255.255.255.0
}
}
1 : <dictionary> {
ConfigMethod : LinkLocal
IsPublished : TRUE
ParentServiceID : 400B48FB-2585-41DF-8459-30C5C6D5621C
ServiceID : LINKLOCAL-en0
}
}
IPv6 : <array> {
0 : <dictionary> {
ConfigMethod : Automatic
DHCPv6 : <dictionary> {
ElapsedTime : 2200
Mode : Stateful
State : Solicit
}
IsPublished : TRUE
RTADV : <dictionary> {
RouterAdvertisement : from fe80::5aef:68ff:fe0d:18db, length 88, hop limit 0, lifetime 1800s, reacha
ble 0ms, retransmit 0ms, flags 0xc4=[ managed other proxy ], pref=medium
source link-address option (1), length 8 (1): 58:ef:68:0d:18:db
prefix info option (3), length 32 (4): ::/64, Flags [ onlink ], valid time 2592000s, pref. time 604
800s
prefix info option (3), length 32 (4): 2a03:1b20:5:7::/64, Flags [ onlink auto ], valid time 259200
0s, pref. time 604800s
State : Acquired
}
ServiceID : 400B48FB-2585-41DF-8459-30C5C6D5621C
}
}
InterfaceType : WiFi
LinkStatusActive : TRUE
NetworkID : 350BCC68-6D65-4D4A-9187-264D7B543738
SSID : app-team-lab
Security : WPA2_PSK
}";

#[test]
fn test_parsing_v4_ipconfig_output() {
assert_eq!(
parse_v4_ipconfig_output(&TEST_IPCONFIG_OUTPUT).unwrap(),
"192.168.1.1".parse::<IpAddr>().unwrap()
)
}

#[test]
fn test_parsing_v6_ipconfig_output() {
assert_eq!(
parse_v6_ipconfig_output(&TEST_IPCONFIG_OUTPUT).unwrap(),
"fe80::5aef:68ff:fe0d:18db".parse::<IpAddr>().unwrap()
)
}
2 changes: 1 addition & 1 deletion talpid-routing/src/unix/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use futures::{
};
use ipnetwork::IpNetwork;
use nix::sys::socket::{AddressFamily, SockaddrLike, SockaddrStorage};
use std::pin::Pin;
use std::{
collections::{BTreeMap, HashSet},
pin::Pin,
time::Duration,
};
use talpid_types::ErrorExt;
Expand Down

0 comments on commit 850ee32

Please sign in to comment.