host arg and gif

This commit is contained in:
0m.ax 2026-07-18 15:53:56 +02:00
parent f70c6c716a
commit 07021aef6b
9 changed files with 455 additions and 293 deletions

View file

@ -30,7 +30,7 @@ impl RawSender {
/// from the routing table and ARP cache.
///
/// Requires `CAP_NET_RAW` or root.
pub fn new(dst_host: &str, dst_port: u16, src_port: u16) -> io::Result<Self> {
pub fn new(dst_host: &str, dst_port: u16, src_port: u16, interface: Option<&str>) -> io::Result<Self> {
// Resolve destination IP.
let dst_ip: Ipv4Addr = dst_host
.parse()
@ -44,16 +44,21 @@ impl RawSender {
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not resolve host"))
})?;
// Discover source IP and interface via a temporary connected UDP socket.
let probe = UdpSocket::bind("0.0.0.0:0")?;
probe.connect((dst_ip, dst_port))?;
let src_ip: Ipv4Addr = match probe.local_addr()? {
std::net::SocketAddr::V4(a) => *a.ip(),
_ => return Err(io::Error::new(io::ErrorKind::Other, "Not IPv4")),
// Discover or use the specified source interface and IP.
let (ifname, ifindex, src_ip) = if let Some(ifname) = interface {
let ifindex = get_ifindex(ifname)?;
let src_ip = get_interface_ip(ifname)?;
(ifname.to_owned(), ifindex, src_ip)
} else {
let probe = UdpSocket::bind("0.0.0.0:0")?;
probe.connect((dst_ip, dst_port))?;
let src_ip: Ipv4Addr = match probe.local_addr()? {
std::net::SocketAddr::V4(a) => *a.ip(),
_ => return Err(io::Error::new(io::ErrorKind::Other, "Not IPv4")),
};
let (ifname, ifindex) = discover_interface(dst_ip)?;
(ifname, ifindex, src_ip)
};
// Discover interface name and index from routing.
let (ifname, ifindex) = discover_interface(dst_ip)?;
eprintln!("[raw] interface: {} (index {})", ifname, ifindex);
// Read source MAC from sysfs.
@ -311,6 +316,42 @@ fn ip_checksum(header: &[u8]) -> u16 {
!(sum as u16)
}
/// Gets the ifindex for a named interface from sysfs.
fn get_ifindex(ifname: &str) -> io::Result<i32> {
let path = format!("/sys/class/net/{}/ifindex", ifname);
let s = fs::read_to_string(&path).map_err(|e| {
io::Error::new(e.kind(), format!("Interface '{}' not found: {}", ifname, e))
})?;
s.trim().parse().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("Bad ifindex: {}", e))
})
}
/// Gets the IPv4 address of a named interface using ioctl.
fn get_interface_ip(ifname: &str) -> io::Result<Ipv4Addr> {
let sock = UdpSocket::bind("0.0.0.0:0")?;
let fd = sock.as_raw_fd();
let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() };
let name_bytes = ifname.as_bytes();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
unsafe {
std::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
ifr.ifr_name.as_mut_ptr() as *mut u8,
copy_len,
);
}
let ret = unsafe { libc::ioctl(fd, libc::SIOCGIFADDR, &mut ifr) };
if ret < 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Could not get IP for interface '{}'", ifname),
));
}
let addr = unsafe { &*(&ifr.ifr_ifru as *const _ as *const libc::sockaddr_in) };
Ok(Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)))
}
/// Discovers the outbound interface name and index for a destination IP.
fn discover_interface(dst_ip: Ipv4Addr) -> io::Result<(String, i32)> {
// Use a temporary UDP socket to discover the interface.