use crate::{mac::parse_mac, DhcpError, DhcpV4OptionCode, ErrorKind, ETH_ALEN};
const ARP_HW_TYPE_ETHERNET: u8 = 1;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DhcpV4Config {
pub iface_name: String,
pub iface_index: u32,
pub(crate) src_mac: [u8; ETH_ALEN],
pub(crate) client_id: Vec<u8>,
pub(crate) host_name: String,
pub is_proxy: bool,
pub(crate) request_opts: Vec<DhcpV4OptionCode>,
pub timeout_sec: u32,
}
impl Default for DhcpV4Config {
fn default() -> Self {
Self {
iface_name: String::new(),
iface_index: 0,
src_mac: [0u8; ETH_ALEN],
client_id: Vec::new(),
host_name: String::new(),
is_proxy: false,
timeout_sec: 0,
request_opts: vec![
DhcpV4OptionCode::HostName,
DhcpV4OptionCode::SubnetMask,
DhcpV4OptionCode::Router,
DhcpV4OptionCode::DomainNameServer,
DhcpV4OptionCode::DomainName,
DhcpV4OptionCode::InterfaceMtu,
DhcpV4OptionCode::NtpServers,
DhcpV4OptionCode::ClasslessStaticRoute,
DhcpV4OptionCode::MS_CLASSLESS_STATIC_ROUTE,
],
}
}
}
impl DhcpV4Config {
pub fn new(iface_name: &str) -> Self {
Self {
iface_name: iface_name.to_string(),
..Default::default()
}
}
pub fn set_iface_index(&mut self, index: u32) -> &mut Self {
self.iface_index = index;
self
}
pub fn set_iface_mac(&mut self, mac: &str) -> Result<&mut Self, DhcpError> {
let src_mac = parse_mac(mac)?;
self.set_iface_mac_raw(&src_mac)
}
pub fn set_iface_mac_raw(
&mut self,
mac: &[u8],
) -> Result<&mut Self, DhcpError> {
if mac.len() != ETH_ALEN {
return Err(DhcpError::new(
ErrorKind::NotSupported,
format!("Only support ethernet MAC address({ETH_ALEN} bytes)",),
));
}
self.src_mac.copy_from_slice(&mac[..ETH_ALEN]);
Ok(self)
}
pub(crate) fn need_resolve(&self) -> bool {
self.iface_index == 0 || self.src_mac.is_empty()
}
#[cfg(feature = "netlink")]
pub(crate) async fn resolve(&mut self) -> Result<(), DhcpError> {
if self.is_proxy {
self.iface_index =
crate::netlink::get_iface_index(&self.iface_name).await?;
} else {
let (iface_index, src_mac) =
crate::netlink::get_iface_index_mac(&self.iface_name).await?;
if src_mac.len() != ETH_ALEN {
return Err(DhcpError::new(
ErrorKind::NotSupported,
format!(
"Interface {} is holding MAC address {:?} which is not
supported yet, only support MAC with {} u8",
self.iface_name, src_mac, ETH_ALEN,
),
));
} else {
self.iface_index = iface_index;
self.src_mac.copy_from_slice(&src_mac[..ETH_ALEN]);
}
}
Ok(())
}
#[cfg(not(feature = "netlink"))]
pub(crate) async fn resolve(&mut self) -> Result<(), DhcpError> {
Err(DhcpError::new(
ErrorKind::InvalidArgument,
format!(
"Feature `netlink` not enabled, cannot resolve interface {} \
index and mac address, please set them manually",
self.iface_name,
),
))
}
pub fn new_proxy(
out_iface_name: &str,
proxy_mac: &str,
) -> Result<Self, DhcpError> {
let mac = parse_mac(proxy_mac)?;
if mac.len() != ETH_ALEN {
Err(DhcpError::new(
ErrorKind::NotSupported,
format!(
"Supported MAC address {proxy_mac}, expecting format \
01:02:2a:2c:f7:04"
),
))
} else {
let mut src_mac = [0; ETH_ALEN];
src_mac.copy_from_slice(&mac[..ETH_ALEN]);
Ok(Self {
iface_name: out_iface_name.to_string(),
src_mac,
is_proxy: true,
..Default::default()
})
}
}
pub fn set_host_name(&mut self, host_name: &str) -> &mut Self {
self.host_name = host_name.to_string();
self
}
pub fn use_mac_as_client_id(&mut self) -> &mut Self {
self.client_id = vec![ARP_HW_TYPE_ETHERNET];
self.client_id.extend_from_slice(&self.src_mac);
self
}
pub fn use_host_name_as_client_id(&mut self) -> &mut Self {
if !self.host_name.is_empty() {
let host_name = self.host_name.clone();
self.set_client_id(0, host_name.as_bytes());
}
self
}
pub fn set_timeout_sec(&mut self, timeout_sec: u32) -> &mut Self {
self.timeout_sec = timeout_sec;
self
}
pub fn set_client_id(
&mut self,
client_id_type: u8,
client_id: &[u8],
) -> &mut Self {
self.client_id = vec![client_id_type];
self.client_id.extend_from_slice(client_id);
self
}
pub fn request_extra_dhcp_opts(&mut self, opts: &[u8]) -> &mut Self {
for opt in opts {
self.request_opts.push((*opt).into());
}
self.request_opts.sort_unstable();
self.request_opts.dedup();
self
}
pub fn override_request_dhcp_opts(&mut self, opts: &[u8]) -> &mut Self {
self.request_opts =
opts.iter().map(|c| DhcpV4OptionCode::from(*c)).collect();
self.request_opts.sort_unstable();
self.request_opts.dedup();
self
}
}