use std::io::{Error, Result};
use std::net::IpAddr;
use libc::{
c_int, c_short, c_uchar, close, in6_addr, ioctl, sockaddr, sockaddr_in, sockaddr_in6, socket,
};
use super::{copy_slice, last_err, parse_mac_addr, IpTool};
pub const SIOCGIFINDEX: i32 = 0x8933;
impl IpTool {
pub fn new() -> Result<Self> {
let fd = Self::get_ctl_fd()?;
Ok(Self { fd })
}
pub fn set_up(&self, dev: &str, up: bool) -> Result<()> {
let mut ifr = Ifreq::new(dev);
let res = unsafe { ioctl(self.fd, libc::SIOCGIFFLAGS as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
let flag_val = libc::IFF_UP as i16;
unsafe {
ifr.ifr_ifru.ifru_flags = if up {
ifr.ifr_ifru.ifru_flags | flag_val
} else {
ifr.ifr_ifru.ifru_flags & (!flag_val)
};
}
let res = unsafe { ioctl(self.fd, libc::SIOCSIFFLAGS as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
if self.get_up(dev)? != up {
return Err(Error::from_raw_os_error(libc::ENOTRECOVERABLE));
}
Ok(())
}
pub fn get_up(&self, dev: &str) -> Result<bool> {
let mut ifr = Ifreq::new(dev);
let res = unsafe { ioctl(self.fd, libc::SIOCGIFFLAGS as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
let flags: i16 = unsafe { ifr.ifr_ifru.ifru_flags };
Ok((flags & libc::IFF_UP as i16) == 1)
}
pub fn set_mtu(&self, dev: &str, mtu: u32) -> Result<()> {
let mut ifr = Ifreq::new(dev);
ifr.ifr_ifru.ifru_mtu = mtu as i32;
let res = unsafe { ioctl(self.fd, libc::SIOCSIFMTU as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
Ok(())
}
pub fn get_mtu(&self, dev: &str) -> Result<u32> {
let mut ifr = Ifreq::new(dev);
let res = unsafe { ioctl(self.fd, libc::SIOCGIFMTU as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
let mtu = unsafe { ifr.ifr_ifru.ifru_mtu as u32 };
Ok(mtu)
}
pub fn get_index(&self, dev: &str) -> Result<c_int> {
let mut ifr = Ifreq::new(dev);
let res = unsafe { ioctl(self.fd, SIOCGIFINDEX as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
Ok(unsafe { ifr.ifr_ifru.ifru_ivalue })
}
pub fn set_address(&self, dev: &str, address: &IpAddr, prefix_length: u32) -> Result<()> {
let index = self.get_index(dev)?;
let res = match address {
IpAddr::V4(_addr) => {
return Err(Error::from_raw_os_error(libc::ENOSYS));
}
IpAddr::V6(addr) => {
let mut ifr = Ifreq6 {
prefix_length,
ifindex: index as _,
addr: in6_addr {
s6_addr: addr.octets(),
},
};
unsafe { ioctl(self.fd, libc::SIOCSIFADDR as _, &mut ifr) }
}
};
if res < 0 {
return Err(last_err());
}
Ok(())
}
pub fn get_address(&self, _dev: &str) -> Result<IpAddr> {
Err(Error::from_raw_os_error(libc::ENOSYS))
}
pub fn set_mac(&self, dev: &str, mac: &str) -> Result<()> {
self.set_mac_sa_data(dev, parse_mac_addr(mac)?)
}
pub fn set_mac_sa_data(&self, dev: &str, mac: [libc::c_char; 14]) -> Result<()> {
let mut ifr = Ifreq::new(dev);
ifr.ifr_ifru.ifru_hwaddr.sa_family = libc::ARPHRD_ETHER;
ifr.ifr_ifru.ifru_hwaddr.sa_data = mac;
let res = unsafe { ioctl(self.fd, libc::SIOCSIFHWADDR as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
Ok(())
}
pub fn get_mac_sa_data(&self, dev: &str) -> Result<[libc::c_char; 14]> {
let mut ifr = Ifreq::new(dev);
ifr.ifr_ifru.ifru_hwaddr.sa_family = libc::ARPHRD_ETHER;
let res = unsafe { ioctl(self.fd, libc::SIOCGIFHWADDR as _, &mut ifr) };
if res < 0 {
return Err(last_err());
}
let sa_data = unsafe { ifr.ifr_ifru.ifru_hwaddr.sa_data };
Ok(sa_data)
}
fn get_ctl_fd() -> Result<c_int> {
let fd = unsafe { socket(libc::PF_INET, libc::SOCK_DGRAM, 0) };
if fd >= 0 {
return Ok(fd);
}
let error = std::io::Error::last_os_error();
let fd = unsafe { socket(libc::PF_PACKET, libc::SOCK_DGRAM, 0) };
if fd >= 0 {
return Ok(fd);
}
let fd = unsafe { socket(libc::PF_INET6, libc::SOCK_DGRAM, 0) };
if fd >= 0 {
return Ok(fd);
}
Err(error)
}
}
impl Drop for IpTool {
fn drop(&mut self) {
unsafe { close(self.fd) };
}
}
#[repr(C)]
union IfrIfru {
ifru_addr: sockaddr,
ifru_hwaddr: sockaddr,
ifru_addr_v4: sockaddr_in,
ifru_addr_v6: sockaddr_in6,
ifru_dstaddr: sockaddr,
ifru_broadaddr: sockaddr,
ifru_flags: c_short,
ifru_metric: c_int,
ifru_ivalue: c_int,
ifru_mtu: c_int,
ifru_phys: c_int,
ifru_media: c_int,
ifru_intval: c_int,
ifru_wake_flags: u32,
ifru_route_refcnt: u32,
ifru_cap: [c_int; 2],
ifru_functional_type: u32,
}
#[repr(C)]
pub struct Ifreq {
ifr_name: [c_uchar; libc::IFNAMSIZ],
ifr_ifru: IfrIfru,
}
impl Ifreq {
pub fn new(dev: &str) -> Self {
let s: [u8; core::mem::size_of::<Self>()] = [0; core::mem::size_of::<Self>()];
let mut s: Self = unsafe { core::mem::transmute(s) };
copy_slice(&mut s.ifr_name, dev.as_bytes());
s
}
}
#[repr(C)]
pub struct Ifreq6 {
addr: in6_addr,
prefix_length: u32,
ifindex: libc::c_uint,
}
#[cfg(test)]
mod test {
use super::IpTool;
#[test]
#[ignore]
fn down() {
let ip_tool = IpTool::new().unwrap();
ip_tool.set_up("loop1", false).unwrap();
}
#[test]
#[ignore]
fn up() {
let ip_tool = IpTool::new().unwrap();
ip_tool.set_up("loop1", true).unwrap();
}
#[test]
#[ignore]
fn sleep_down_and_up() {
let ip_tool = IpTool::new().unwrap();
ip_tool.set_up("loop1", false).unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
ip_tool.set_up("loop1", true).unwrap();
}
#[test]
#[ignore]
fn mtu() {
let ip_tool = IpTool::new().unwrap();
ip_tool.set_mtu("loop1", 1420).unwrap();
assert_eq!(ip_tool.get_mtu("loop1").unwrap(), 1420);
}
#[test]
#[ignore]
fn mac() {
let ip_tool = IpTool::new().unwrap();
let mac = "5A:E6:60:8F:5F:DE";
ip_tool.set_mac("loop1", mac).unwrap();
let sa_data = ip_tool.get_mac_sa_data("loop1").unwrap();
assert_eq!(sa_data, super::parse_mac_addr(mac).unwrap());
}
}