use clap::{Arg, Command};
use color_eyre::Result;
use color_eyre::eyre::eyre;
use std::convert::TryInto;
use std::net::Ipv4Addr;
use std::os::fd::FromRawFd;
use std::str::FromStr;
#[derive(Debug)]
pub struct IfconfigOptions {
pub interface: String,
pub address: Option<Ipv4Addr>,
pub netmask: Option<Ipv4Addr>,
pub up: bool,
pub down: bool,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<IfconfigOptions> {
let interface = matches.get_one::<String>("interface")
.ok_or_else(|| eyre!("ifconfig: missing interface name"))?
.clone();
let args: Vec<String> = matches.get_many::<String>("args")
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
let mut address = None;
let mut netmask = None;
let mut up = false;
let mut down = false;
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if let Ok(addr) = Ipv4Addr::from_str(arg) {
address = Some(addr);
} else if arg == "netmask" {
i += 1;
if i >= args.len() {
return Err(eyre!("ifconfig: netmask requires an address"));
}
netmask = Ipv4Addr::from_str(&args[i]).ok();
} else if arg == "up" {
up = true;
} else if arg == "down" {
down = true;
} else {
}
i += 1;
}
if up && down {
return Err(eyre!("ifconfig: cannot specify both up and down"));
}
Ok(IfconfigOptions {
interface,
address,
netmask,
up,
down,
})
}
pub fn command() -> Command {
Command::new("ifconfig")
.about("Configure network interface parameters")
.arg(Arg::new("interface")
.required(true)
.help("Network interface name (e.g., eth0)"))
.arg(Arg::new("args")
.help("Address, netmask, up/down keywords")
.num_args(0..)
.last(true))
}
fn set_interface_address(interface: &str, addr: Ipv4Addr, netmask: Option<Ipv4Addr>) -> Result<()> {
use std::os::fd::AsRawFd;
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
let e = std::io::Error::last_os_error();
return Err(eyre!("socket creation failed: {}", e));
}
let sock_fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(sock) };
let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() };
let ifname_bytes = interface.as_bytes();
if ifname_bytes.len() >= libc::IFNAMSIZ {
return Err(eyre!("interface name too long"));
}
unsafe {
std::ptr::copy_nonoverlapping(
ifname_bytes.as_ptr(),
ifr.ifr_name.as_mut_ptr() as *mut u8,
ifname_bytes.len(),
);
}
let addr_in = libc::sockaddr_in {
sin_family: libc::AF_INET as libc::sa_family_t,
sin_port: 0,
sin_addr: libc::in_addr {
s_addr: u32::from(addr).to_be(),
},
sin_zero: [0; 8],
};
unsafe {
std::ptr::write_unaligned(
&mut ifr.ifr_ifru.ifru_addr as *mut libc::sockaddr,
std::mem::transmute_copy(&addr_in),
);
}
if unsafe { libc::ioctl(sock_fd.as_raw_fd(), libc::SIOCSIFADDR.try_into().unwrap(), &ifr) } < 0 {
let e = std::io::Error::last_os_error();
log::debug!("ifconfig: SIOCSIFADDR {} failed: {}", interface, e);
return Err(eyre!("SIOCSIFADDR failed on {}: {}", interface, e));
}
if let Some(mask) = netmask {
let mask_in = libc::sockaddr_in {
sin_family: libc::AF_INET as libc::sa_family_t,
sin_port: 0,
sin_addr: libc::in_addr {
s_addr: u32::from(mask).to_be(),
},
sin_zero: [0; 8],
};
unsafe {
std::ptr::write_unaligned(
&mut ifr.ifr_ifru.ifru_netmask as *mut libc::sockaddr,
std::mem::transmute_copy(&mask_in),
);
}
if unsafe { libc::ioctl(sock_fd.as_raw_fd(), libc::SIOCSIFNETMASK.try_into().unwrap(), &ifr) } < 0 {
let e = std::io::Error::last_os_error();
log::debug!("ifconfig: SIOCSIFNETMASK {} failed: {}", interface, e);
return Err(eyre!("SIOCSIFNETMASK failed on {}: {}", interface, e));
}
}
Ok(())
}
fn set_interface_flags(interface: &str, up: bool) -> Result<()> {
use std::os::fd::AsRawFd;
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
let e = std::io::Error::last_os_error();
return Err(eyre!("socket creation failed: {}", e));
}
let sock_fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(sock) };
let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() };
let ifname_bytes = interface.as_bytes();
if ifname_bytes.len() >= libc::IFNAMSIZ {
return Err(eyre!("interface name too long"));
}
unsafe {
std::ptr::copy_nonoverlapping(
ifname_bytes.as_ptr(),
ifr.ifr_name.as_mut_ptr() as *mut u8,
ifname_bytes.len(),
);
}
if unsafe { libc::ioctl(sock_fd.as_raw_fd(), libc::SIOCGIFFLAGS.try_into().unwrap(), &ifr) } < 0 {
let e = std::io::Error::last_os_error();
log::debug!("ifconfig: SIOCGIFFLAGS {} failed: {}", interface, e);
return Err(eyre!("SIOCGIFFLAGS failed on {}: {} (device may not exist)", interface, e));
}
let flags = unsafe { ifr.ifr_ifru.ifru_flags };
let new_flags = if up {
flags | libc::IFF_UP as i16
} else {
flags & !(libc::IFF_UP as i16)
};
ifr.ifr_ifru.ifru_flags = new_flags as _;
if unsafe { libc::ioctl(sock_fd.as_raw_fd(), libc::SIOCSIFFLAGS.try_into().unwrap(), &ifr) } < 0 {
let e = std::io::Error::last_os_error();
log::debug!("ifconfig: SIOCSIFFLAGS {} failed: {}", interface, e);
return Err(eyre!("SIOCSIFFLAGS failed on {}: {}", interface, e));
}
Ok(())
}
pub fn run(options: IfconfigOptions) -> Result<()> {
log::debug!("ifconfig: {} addr={:?} netmask={:?} up={} down={}",
options.interface, options.address, options.netmask, options.up, options.down);
if let Some(addr) = options.address {
set_interface_address(&options.interface, addr, options.netmask)?;
}
if options.up {
set_interface_flags(&options.interface, true)?;
} else if options.down {
set_interface_flags(&options.interface, false)?;
}
Ok(())
}