use anyhow::{Context, Result};
use reqwest::ClientBuilder;
use std::net::{IpAddr, SocketAddr};
pub fn get_interface_ip(interface: &str) -> Result<IpAddr> {
use if_addrs::get_if_addrs;
let addrs = get_if_addrs().context("Failed to enumerate network interfaces")?;
for addr in &addrs {
if addr.name == interface {
if let if_addrs::IfAddr::V4(v4) = &addr.addr {
return Ok(IpAddr::V4(v4.ip));
}
}
}
for addr in &addrs {
if addr.name == interface {
if let if_addrs::IfAddr::V6(v6) = &addr.addr {
return Ok(IpAddr::V6(v6.ip));
}
}
}
Err(anyhow::anyhow!(
"Interface {} not found or has no IP address assigned",
interface
))
}
pub fn resolve_bind_address(
interface: Option<&String>,
source_ip: Option<&String>,
) -> Result<Option<SocketAddr>> {
if let Some(ip_str) = source_ip {
let ip: IpAddr = ip_str.parse().context("Invalid source IP address format")?;
return Ok(Some(SocketAddr::new(ip, 0)));
}
if let Some(iface) = interface {
let ip = get_interface_ip(iface)
.with_context(|| format!("Failed to get IP for interface {}", iface))?;
return Ok(Some(SocketAddr::new(ip, 0)));
}
Ok(None)
}
pub fn apply_local_address(builder: ClientBuilder, bind_ip: Option<IpAddr>) -> ClientBuilder {
match bind_ip {
Some(ip) => builder.local_address(ip),
None => builder,
}
}
pub fn get_interface_for_ip(ip_str: &str) -> Option<String> {
let target_ip: IpAddr = ip_str.parse().ok()?;
let addrs = if_addrs::get_if_addrs().ok()?;
for addr in &addrs {
let iface_ip = match &addr.addr {
if_addrs::IfAddr::V4(v4) => IpAddr::V4(v4.ip),
if_addrs::IfAddr::V6(v6) => IpAddr::V6(v6.ip),
};
if iface_ip == target_ip {
return Some(addr.name.clone());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_interface_for_ip_loopback() {
let iface = get_interface_for_ip("127.0.0.1");
assert_eq!(iface, Some("lo".to_string()));
}
#[test]
fn test_get_interface_for_ip_not_found() {
let iface = get_interface_for_ip("198.51.100.99");
assert_eq!(iface, None);
}
#[test]
fn test_get_interface_for_ip_invalid() {
let iface = get_interface_for_ip("not-an-ip");
assert_eq!(iface, None);
}
#[test]
fn test_get_interface_ip_loopback() {
let ip = get_interface_ip("lo").unwrap();
assert_eq!(ip, "127.0.0.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_interface_ip_nonexistent() {
let result = get_interface_ip("nonexistent_iface_xyz");
assert!(result.is_err());
}
#[test]
fn test_roundtrip_interface_to_ip_and_back() {
let ip = get_interface_ip("lo").unwrap();
let iface = get_interface_for_ip(&ip.to_string());
assert_eq!(iface, Some("lo".to_string()));
}
#[test]
fn test_resolve_bind_address_none() {
let result = resolve_bind_address(None, None).unwrap();
assert!(result.is_none());
}
#[test]
fn test_resolve_bind_address_source_ip() {
let source = "127.0.0.1".to_string();
let result = resolve_bind_address(None, Some(&source)).unwrap();
let addr = result.unwrap();
assert_eq!(addr.ip(), "127.0.0.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_resolve_bind_address_invalid_source() {
let source = "not-an-ip".to_string();
let result = resolve_bind_address(None, Some(&source));
assert!(result.is_err());
}
#[test]
fn test_resolve_bind_address_interface() {
let iface = "lo".to_string();
let result = resolve_bind_address(Some(&iface), None).unwrap();
let addr = result.unwrap();
assert_eq!(addr.ip(), "127.0.0.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_resolve_bind_address_source_takes_priority() {
let iface = "lo".to_string();
let source = "192.168.1.1".to_string();
let result = resolve_bind_address(Some(&iface), Some(&source)).unwrap();
let addr = result.unwrap();
assert_eq!(addr.ip(), "192.168.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_apply_local_address_none() {
let builder = reqwest::Client::builder();
let client = apply_local_address(builder, None).build();
assert!(client.is_ok());
}
#[test]
fn test_apply_local_address_some() {
let builder = reqwest::Client::builder();
let ip: IpAddr = "127.0.0.1".parse().unwrap();
let client = apply_local_address(builder, Some(ip)).build();
assert!(client.is_ok());
}
}