Update default interface selection to use active routing table (#194)

* Update default interface selection to use active routing table

Use a connectionless UDP socket to determine the active routed local IP from the OS routing table. Matches this IP to an available capture device to securely detect the preferred default network interface without sending traffic.

* Add contributor Ken Tobias and fix formatting

* fix: collapse nested if-let and fix unused import for clippy

---------

Co-authored-by: Marco Cadetg <cadetg@gmail.com>
This commit is contained in:
Ken Tobias
2026-03-20 16:15:55 -07:00
committed by GitHub
parent 795f7a1e9a
commit 23922640f0
3 changed files with 43 additions and 1 deletions
+1
View File
@@ -12,6 +12,7 @@ We would like to thank these people for their valuable contributions:
- **DeepChirp** ([@DeepChirp](https://github.com/DeepChirp)) - Code contributions
- **Conor O'Callaghan** ([@Conor0Callaghan](https://github.com/Conor0Callaghan)) - JSON/SIEM logging research and design input
- **Ken Tobias** ([@l1a](https://github.com/l1a)) - Code contributions
## Contributing
+41
View File
@@ -367,6 +367,27 @@ fn find_capture_device(interface_name: &Option<String>) -> Result<Device> {
None => {
log::info!("No interface specified, using default");
// Resolve active interface via OS routing table by creating a connectionless UDP socket
if let Some(active_ip) = std::net::UdpSocket::bind("0.0.0.0:0")
.and_then(|s| {
let _ = s.connect("8.8.8.8:53");
s.local_addr()
})
.ok()
.map(|addr| addr.ip())
{
log::info!("Found active routed IP: {}", active_ip);
if let Ok(devices) = Device::list()
&& let Some(device) = devices
.into_iter()
.find(|d| d.addresses.iter().any(|a| a.addr == active_ip))
{
log::info!("Selected interface {} based on active route", device.name);
return Ok(device);
}
}
log::info!("Fallback: using libpcap default device logic");
// Try to get default device
match Device::lookup() {
Ok(Some(device)) => {
@@ -497,4 +518,24 @@ mod tests {
assert_eq!(config.snaplen, 1514);
assert!(config.filter.is_none()); // Default starts without filter
}
#[test]
fn test_udp_routing_resolution_can_execute() {
// Sanity-check test to ensure the OS handles UDP metric routing cleanly.
// It's perfectly fine if this fails in hermetic CI environments without outbound routes.
if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0") {
if socket.connect("8.8.8.8:53").is_ok() {
if let Ok(addr) = socket.local_addr() {
assert!(
!addr.ip().is_loopback(),
"Active routed IP should not be loopback"
);
assert!(
!addr.ip().is_unspecified(),
"Active routed IP should not be unspecified"
);
}
}
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
//! network packets on different platforms (Linux, macOS, Windows).
use anyhow::Result;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
#[cfg(target_os = "linux")]
use anyhow::anyhow;
#[cfg(any(
not(any(