forked from NationTech/harmony
163 lines
4.8 KiB
Rust
163 lines
4.8 KiB
Rust
use opnsense_config_xml::Range;
|
|
use opnsense_config_xml::StaticMap;
|
|
use std::cmp::Ordering;
|
|
use std::net::Ipv4Addr;
|
|
|
|
use opnsense_config_xml::OPNsense;
|
|
|
|
pub struct DhcpConfig<'a> {
|
|
opnsense: &'a mut OPNsense,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum DhcpError {
|
|
InvalidMacAddress(String),
|
|
InvalidIpAddress(String),
|
|
IpAddressAlreadyMapped(String),
|
|
MacAddressAlreadyMapped(String),
|
|
IpAddressOutOfRange(String),
|
|
}
|
|
|
|
impl std::fmt::Display for DhcpError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
DhcpError::InvalidMacAddress(mac) => write!(f, "Invalid MAC address format: {}", mac),
|
|
DhcpError::InvalidIpAddress(ip) => write!(f, "Invalid IP address format: {}", ip),
|
|
DhcpError::IpAddressAlreadyMapped(ip) => {
|
|
write!(f, "IP address {} is already mapped", ip)
|
|
}
|
|
DhcpError::MacAddressAlreadyMapped(mac) => {
|
|
write!(f, "MAC address {} is already mapped", mac)
|
|
}
|
|
DhcpError::IpAddressOutOfRange(ip) => {
|
|
write!(f, "IP address {} is out of interface range", ip)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for DhcpError {}
|
|
|
|
impl<'a> DhcpConfig<'a> {
|
|
pub fn new(opnsense: &'a mut OPNsense) -> Self {
|
|
Self { opnsense }
|
|
}
|
|
|
|
pub fn add_static_mapping(
|
|
&mut self,
|
|
mac: &str,
|
|
ipaddr: Ipv4Addr,
|
|
hostname: &str,
|
|
) -> Result<(), DhcpError> {
|
|
let mac = mac.to_string();
|
|
let hostname = hostname.to_string();
|
|
let range = &self.opnsense.dhcpd.lan.range;
|
|
|
|
if !Self::is_valid_mac(&mac) {
|
|
return Err(DhcpError::InvalidMacAddress(mac));
|
|
}
|
|
|
|
if !Self::is_ip_in_range(&ipaddr, range) {
|
|
return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
|
|
}
|
|
|
|
let existing_mappings = &self.opnsense.dhcpd.lan.staticmaps;
|
|
|
|
if existing_mappings.iter().any(|m| {
|
|
m.ipaddr
|
|
.parse::<Ipv4Addr>()
|
|
.expect("Mapping contains invalid ipv4")
|
|
== ipaddr
|
|
}) {
|
|
return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string()));
|
|
}
|
|
|
|
if existing_mappings.iter().any(|m| m.mac == mac) {
|
|
return Err(DhcpError::MacAddressAlreadyMapped(mac));
|
|
}
|
|
|
|
let static_map = StaticMap {
|
|
mac,
|
|
ipaddr: ipaddr.to_string(),
|
|
hostname,
|
|
descr: Default::default(),
|
|
winsserver: Default::default(),
|
|
dnsserver: Default::default(),
|
|
ntpserver: Default::default(),
|
|
};
|
|
|
|
self.opnsense.dhcpd.lan.staticmaps.push(static_map);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_static_mappings(&self) -> &[StaticMap] {
|
|
&self.opnsense.dhcpd.lan.staticmaps
|
|
}
|
|
|
|
fn is_valid_mac(mac: &str) -> bool {
|
|
let parts: Vec<&str> = mac.split(':').collect();
|
|
if parts.len() != 6 {
|
|
return false;
|
|
}
|
|
|
|
parts
|
|
.iter()
|
|
.all(|part| part.len() == 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
|
|
}
|
|
|
|
fn is_ip_in_range(ip: &Ipv4Addr, range: &Range) -> bool {
|
|
let range_start = range
|
|
.from
|
|
.parse::<Ipv4Addr>()
|
|
.expect("Invalid DHCP range start");
|
|
let range_end = range.to.parse::<Ipv4Addr>().expect("Invalid DHCP range to");
|
|
|
|
let start_compare = range_start.cmp(ip);
|
|
let end_compare = range_end.cmp(ip);
|
|
|
|
if (Ordering::Less == start_compare || Ordering::Equal == start_compare)
|
|
&& (Ordering::Greater == end_compare || Ordering::Equal == end_compare)
|
|
{
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use std::net::Ipv4Addr;
|
|
|
|
#[test]
|
|
fn test_ip_in_range() {
|
|
let range = Range {
|
|
from: "192.168.1.100".to_string(),
|
|
to: "192.168.1.200".to_string(),
|
|
};
|
|
|
|
// Test IP within range
|
|
let ip = "192.168.1.150".parse::<Ipv4Addr>().unwrap();
|
|
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
|
|
|
|
// Test IP at start of range
|
|
let ip = "192.168.1.100".parse::<Ipv4Addr>().unwrap();
|
|
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
|
|
|
|
// Test IP at end of range
|
|
let ip = "192.168.1.200".parse::<Ipv4Addr>().unwrap();
|
|
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
|
|
|
|
// Test IP before range
|
|
let ip = "192.168.1.99".parse::<Ipv4Addr>().unwrap();
|
|
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false);
|
|
|
|
// Test IP after range
|
|
let ip = "192.168.1.201".parse::<Ipv4Addr>().unwrap();
|
|
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false);
|
|
}
|
|
|
|
}
|