use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::time::Duration;
use std::mem::MaybeUninit;
use anyhow::{Context, Result, anyhow};
#[cfg(feature = "dhcp-debug")]
use tracing::{debug, info};
#[cfg(feature = "dhcp-debug")]
macro_rules! dhcp_debug {
($($arg:tt)*) => {
debug!("[{}:{}] {}", file!(), line!(), format!($($arg)*))
};
}
#[cfg(not(feature = "dhcp-debug"))]
macro_rules! dhcp_debug {
($($arg:tt)*) => {};
}
#[cfg(feature = "dhcp-debug")]
macro_rules! dhcp_info {
($($arg:tt)*) => {
info!("[{}:{}] {}", file!(), line!(), format!($($arg)*))
};
}
#[cfg(not(feature = "dhcp-debug"))]
macro_rules! dhcp_info {
($($arg:tt)*) => {};
}
pub enum RawSocketType {
IpLayer,
LinkLayer,
}
pub struct DhcpRawSocket {
socket: socket2::Socket,
socket_type: RawSocketType,
interface: Option<String>,
}
impl DhcpRawSocket {
pub fn new(socket_type: RawSocketType, interface: Option<&str>) -> Result<Self> {
dhcp_debug!("DEBUG: DhcpRawSocket::new() called");
dhcp_debug!(" socket_type: {:?}", match &socket_type {
RawSocketType::IpLayer => "IpLayer",
RawSocketType::LinkLayer => "LinkLayer",
});
dhcp_debug!(" interface: {:?}", interface);
let socket = match &socket_type {
RawSocketType::IpLayer => {
dhcp_debug!("DEBUG: Creating IP layer socket");
Self::create_ip_layer_socket()?
},
RawSocketType::LinkLayer => {
let iface = interface.ok_or_else(|| {
anyhow!("Interface name required for LinkLayer socket")
})?;
dhcp_debug!("DEBUG: Creating link layer socket for interface: {}", iface);
Self::create_link_layer_socket(iface)?
}
};
dhcp_debug!("DEBUG: DhcpRawSocket created successfully");
Ok(Self {
socket,
socket_type,
interface: interface.map(String::from),
})
}
fn create_ip_layer_socket() -> Result<socket2::Socket> {
dhcp_debug!("DEBUG: create_ip_layer_socket() - Creating raw socket");
dhcp_debug!(" AF_INET={}, SOCK_RAW={}, IPPROTO_RAW={}",
libc::AF_INET, libc::SOCK_RAW, libc::IPPROTO_RAW);
let socket_fd = unsafe {
libc::socket(libc::AF_INET, libc::SOCK_RAW, libc::IPPROTO_RAW)
};
dhcp_debug!("DEBUG: socket() returned fd={}", socket_fd);
if socket_fd < 0 {
let errno = unsafe { *libc::__errno_location() };
dhcp_debug!("DEBUG: socket() FAILED with errno={}", errno);
return Err(anyhow!(
"Failed to create raw socket (errno={}). Requires CAP_NET_RAW or root privileges",
errno
));
}
let socket = unsafe { socket2::Socket::from_raw_fd(socket_fd) };
dhcp_debug!("DEBUG: Socket created successfully, fd={}", socket_fd);
dhcp_debug!("DEBUG: Setting IP_HDRINCL=1");
unsafe {
let optval: libc::c_int = 1;
let ret = libc::setsockopt(
socket.as_raw_fd(),
libc::IPPROTO_IP,
libc::IP_HDRINCL,
&optval as *const _ as *const libc::c_void,
std::mem::size_of_val(&optval) as libc::socklen_t,
);
dhcp_debug!("DEBUG: setsockopt(IP_HDRINCL) returned {}", ret);
if ret < 0 {
let _errno = *libc::__errno_location();
dhcp_debug!("DEBUG: setsockopt(IP_HDRINCL) FAILED with errno={}", _errno);
return Err(anyhow!("Failed to set IP_HDRINCL"));
}
}
dhcp_debug!("DEBUG: Setting SO_BROADCAST=1");
unsafe {
let optval: libc::c_int = 1;
let ret = libc::setsockopt(
socket.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_BROADCAST,
&optval as *const _ as *const libc::c_void,
std::mem::size_of_val(&optval) as libc::socklen_t,
);
dhcp_debug!("DEBUG: setsockopt(SO_BROADCAST) returned {}", ret);
if ret < 0 {
let _errno = *libc::__errno_location();
dhcp_debug!("DEBUG: setsockopt(SO_BROADCAST) FAILED with errno={}", _errno);
return Err(anyhow!("Failed to set SO_BROADCAST"));
}
}
dhcp_debug!("DEBUG: IP layer socket created and configured successfully");
Ok(socket)
}
fn create_link_layer_socket(interface: &str) -> Result<socket2::Socket> {
dhcp_debug!("DEBUG: create_link_layer_socket() - Creating AF_PACKET socket");
dhcp_debug!(" interface: {}", interface);
let if_index = Self::get_interface_index(interface)?;
dhcp_debug!("DEBUG: Interface index for {}: {}", interface, if_index);
let protocol = (libc::ETH_P_IP as u16).to_be() as i32;
dhcp_debug!("DEBUG: Protocol (ETH_P_IP in network order): 0x{:04x}", protocol);
dhcp_debug!(" AF_PACKET={}, SOCK_RAW={}", libc::AF_PACKET, libc::SOCK_RAW);
let socket_fd = unsafe {
libc::socket(libc::AF_PACKET, libc::SOCK_RAW, protocol)
};
dhcp_debug!("DEBUG: socket() returned fd={}", socket_fd);
if socket_fd < 0 {
let errno = unsafe { *libc::__errno_location() };
dhcp_debug!("DEBUG: socket() FAILED with errno={}", errno);
return Err(anyhow!(
"Failed to create AF_PACKET socket (errno={}). Requires CAP_NET_RAW or root privileges",
errno
));
}
let socket = unsafe { socket2::Socket::from_raw_fd(socket_fd) };
dhcp_debug!("DEBUG: AF_PACKET socket created successfully, fd={}", socket_fd);
let mut sll: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
sll.sll_family = libc::AF_PACKET as u16;
sll.sll_protocol = protocol as u16;
sll.sll_ifindex = if_index;
dhcp_debug!("DEBUG: Binding socket to interface");
dhcp_debug!(" sll_family: {}", sll.sll_family);
dhcp_debug!(" sll_protocol: 0x{:04x}", sll.sll_protocol);
dhcp_debug!(" sll_ifindex: {}", sll.sll_ifindex);
let ret = unsafe {
libc::bind(
socket.as_raw_fd(),
&sll as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
)
};
dhcp_debug!("DEBUG: bind() returned {}", ret);
if ret < 0 {
let errno = unsafe { *libc::__errno_location() };
dhcp_debug!("DEBUG: bind() FAILED with errno={}", errno);
return Err(anyhow!(
"Failed to bind to interface {} (errno={})",
interface,
errno
));
}
dhcp_debug!("DEBUG: Link layer socket created and bound successfully");
Ok(socket)
}
fn get_interface_index(interface: &str) -> Result<i32> {
use std::ffi::CString;
dhcp_debug!("DEBUG: get_interface_index() called for: {}", interface);
let iface_cstr = CString::new(interface)
.context("Invalid interface name")?;
let index = unsafe { libc::if_nametoindex(iface_cstr.as_ptr()) };
dhcp_debug!("DEBUG: if_nametoindex() returned: {}", index);
if index == 0 {
dhcp_debug!("DEBUG: Interface {} not found (index=0)", interface);
return Err(anyhow!("Interface {} not found", interface));
}
dhcp_debug!("DEBUG: Interface {} has index {}", interface, index);
Ok(index as i32)
}
pub fn send(&self, packet: &[u8]) -> Result<usize> {
dhcp_debug!("DEBUG: DhcpRawSocket::send() called");
dhcp_debug!(" packet length: {} bytes", packet.len());
dhcp_debug!(" socket_type: {:?}", match self.socket_type {
RawSocketType::IpLayer => "IpLayer",
RawSocketType::LinkLayer => "LinkLayer",
});
dhcp_debug!(" interface: {:?}", self.interface);
match self.socket_type {
RawSocketType::IpLayer => {
dhcp_debug!("DEBUG: Sending via IP layer socket");
if packet.len() < 20 {
dhcp_debug!("DEBUG: ERROR - Packet too short ({} bytes)", packet.len());
return Err(anyhow!("Packet too short for IP header"));
}
let dst_ip = Ipv4Addr::new(packet[16], packet[17], packet[18], packet[19]);
dhcp_debug!("DEBUG: Extracted destination IP from packet: {}", dst_ip);
let dest_addr = std::net::SocketAddr::new(
std::net::IpAddr::V4(dst_ip),
0
);
let dest_sockaddr = socket2::SockAddr::from(dest_addr);
dhcp_debug!("DEBUG: Calling sendto() with {} bytes to {}", packet.len(), dst_ip);
eprintln!(">>> SENDING DHCP PACKET (IP LAYER) <<<");
eprintln!(" Destination: {}", dst_ip);
eprintln!(" Length: {} bytes", packet.len());
eprintln!(" Packet hex (first 256 bytes): {}", hex::encode(&packet[..packet.len().min(256)]));
if packet.len() > 256 {
eprintln!(" (truncated, showing first 256 of {} total bytes)", packet.len());
}
eprintln!();
let result = self.socket
.send_to(packet, &dest_sockaddr)
.context("Failed to send packet");
match &result {
Ok(_bytes_sent) => {
dhcp_debug!("DEBUG: sendto() SUCCESS - sent {} bytes", _bytes_sent);
dhcp_info!("PACKET SENT: {} bytes via IP layer to {}", _bytes_sent, dst_ip);
}
Err(_e) => {
dhcp_debug!("DEBUG: sendto() FAILED: {}", _e);
}
}
result
}
RawSocketType::LinkLayer => {
dhcp_debug!("DEBUG: Sending via link layer socket");
dhcp_debug!("DEBUG: First 14 bytes (Ethernet header): {:02x?}", &packet[..14.min(packet.len())]);
dhcp_debug!("DEBUG: Calling send() with {} bytes", packet.len());
eprintln!(">>> SENDING DHCP PACKET (LINK LAYER) <<<");
eprintln!(" Interface: {:?}", self.interface);
eprintln!(" Length: {} bytes", packet.len());
eprintln!();
eprintln!(" Packet hex (8-byte chunks):");
for (i, chunk) in packet.chunks(8).enumerate() {
eprint!(" {:04x}: ", i * 8);
for byte in chunk {
eprint!("{:02x} ", byte);
}
eprintln!();
if (i + 1) * 8 >= 256 {
if packet.len() > 256 {
eprintln!(" ... (truncated, showing first 256 of {} total bytes)", packet.len());
}
break;
}
}
eprintln!();
if packet.len() >= 14 + 20 + 8 + 240 {
eprintln!(" Packet structure:");
eprintln!(" Ethernet Header (14 bytes):");
eprintln!(" Dst MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
packet[0], packet[1], packet[2], packet[3], packet[4], packet[5]);
eprintln!(" Src MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
packet[6], packet[7], packet[8], packet[9], packet[10], packet[11]);
eprintln!(" EtherType: 0x{:02x}{:02x}", packet[12], packet[13]);
let ip_start = 14;
eprintln!(" IP Header (20 bytes at offset {}):", ip_start);
eprintln!(" Src IP: {}.{}.{}.{}", packet[ip_start+12], packet[ip_start+13], packet[ip_start+14], packet[ip_start+15]);
eprintln!(" Dst IP: {}.{}.{}.{}", packet[ip_start+16], packet[ip_start+17], packet[ip_start+18], packet[ip_start+19]);
let udp_start = ip_start + 20;
eprintln!(" UDP Header (8 bytes at offset {}):", udp_start);
let src_port = u16::from_be_bytes([packet[udp_start], packet[udp_start+1]]);
let dst_port = u16::from_be_bytes([packet[udp_start+2], packet[udp_start+3]]);
eprintln!(" Src Port: {}", src_port);
eprintln!(" Dst Port: {}", dst_port);
let dhcp_start = udp_start + 8;
eprintln!(" DHCP Message (at offset {}):", dhcp_start);
eprintln!(" op: {} (1=BOOTREQUEST, 2=BOOTREPLY)", packet[dhcp_start]);
eprintln!(" htype: {}", packet[dhcp_start+1]);
eprintln!(" hlen: {}", packet[dhcp_start+2]);
eprintln!(" hops: {}", packet[dhcp_start+3]);
let xid = u32::from_be_bytes([packet[dhcp_start+4], packet[dhcp_start+5], packet[dhcp_start+6], packet[dhcp_start+7]]);
eprintln!(" xid: 0x{:08x}", xid);
}
eprintln!();
let result = self.socket
.send(packet)
.context("Failed to send frame");
match &result {
Ok(_bytes_sent) => {
dhcp_debug!("DEBUG: send() SUCCESS - sent {} bytes", _bytes_sent);
dhcp_info!("PACKET SENT: {} bytes via link layer on {:?}", _bytes_sent, self.interface);
}
Err(_e) => {
dhcp_debug!("DEBUG: send() FAILED: {}", _e);
}
}
result
}
}
}
pub fn recv(&self, buf: &mut [u8], timeout: Duration) -> std::io::Result<usize> {
dhcp_debug!("DEBUG: DhcpRawSocket::recv() called");
dhcp_debug!(" buffer size: {} bytes", buf.len());
dhcp_debug!(" timeout: {:?}", timeout);
dhcp_debug!(" socket_type: {:?}", match self.socket_type {
RawSocketType::IpLayer => "IpLayer",
RawSocketType::LinkLayer => "LinkLayer",
});
self.socket.set_read_timeout(Some(timeout))?;
dhcp_debug!("DEBUG: Calling recv() to read packet");
let mut uninit_buf = vec![MaybeUninit::<u8>::uninit(); buf.len()];
let (bytes_received, _addr) = self.socket.recv_from(&mut uninit_buf)?;
for (i, byte) in uninit_buf.iter().take(bytes_received).enumerate() {
unsafe {
buf[i] = byte.assume_init();
}
}
dhcp_debug!("DEBUG: recv() SUCCESS - received {} bytes", bytes_received);
dhcp_info!("PACKET RECEIVED: {} bytes", bytes_received);
eprintln!("<<< RECEIVED DHCP PACKET >>>");
eprintln!(" Length: {} bytes", bytes_received);
eprintln!();
eprintln!(" Packet hex (8-byte chunks):");
for (i, chunk) in buf[..bytes_received].chunks(8).enumerate() {
eprint!(" {:04x}: ", i * 8);
for byte in chunk {
eprint!("{:02x} ", byte);
}
eprintln!();
if (i + 1) * 8 >= 256 {
if bytes_received > 256 {
eprintln!(" ... (truncated, showing first 256 of {} total bytes)", bytes_received);
}
break;
}
}
eprintln!();
match self.socket_type {
RawSocketType::LinkLayer => {
if bytes_received >= 14 + 20 + 8 + 240 {
eprintln!(" Packet structure (Link Layer):");
eprintln!(" Ethernet Header (14 bytes):");
eprintln!(" Dst MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
eprintln!(" Src MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);
eprintln!(" EtherType: 0x{:02x}{:02x}", buf[12], buf[13]);
let ip_start = 14;
eprintln!(" IP Header (20 bytes at offset {}):", ip_start);
eprintln!(" Src IP: {}.{}.{}.{}", buf[ip_start+12], buf[ip_start+13], buf[ip_start+14], buf[ip_start+15]);
eprintln!(" Dst IP: {}.{}.{}.{}", buf[ip_start+16], buf[ip_start+17], buf[ip_start+18], buf[ip_start+19]);
let udp_start = ip_start + 20;
eprintln!(" UDP Header (8 bytes at offset {}):", udp_start);
let src_port = u16::from_be_bytes([buf[udp_start], buf[udp_start+1]]);
let dst_port = u16::from_be_bytes([buf[udp_start+2], buf[udp_start+3]]);
eprintln!(" Src Port: {}", src_port);
eprintln!(" Dst Port: {}", dst_port);
let dhcp_start = udp_start + 8;
eprintln!(" DHCP Message (at offset {}):", dhcp_start);
eprintln!(" op: {} (1=BOOTREQUEST, 2=BOOTREPLY)", buf[dhcp_start]);
eprintln!(" htype: {}", buf[dhcp_start+1]);
eprintln!(" hlen: {}", buf[dhcp_start+2]);
eprintln!(" hops: {}", buf[dhcp_start+3]);
let xid = u32::from_be_bytes([buf[dhcp_start+4], buf[dhcp_start+5], buf[dhcp_start+6], buf[dhcp_start+7]]);
eprintln!(" xid: 0x{:08x}", xid);
eprintln!(" yiaddr (offered IP): {}.{}.{}.{}",
buf[dhcp_start+16], buf[dhcp_start+17], buf[dhcp_start+18], buf[dhcp_start+19]);
eprintln!(" Magic cookie at offset {}: {:02x} {:02x} {:02x} {:02x}",
dhcp_start+236, buf[dhcp_start+236], buf[dhcp_start+237], buf[dhcp_start+238], buf[dhcp_start+239]);
}
}
RawSocketType::IpLayer => {
if bytes_received >= 20 + 8 + 240 {
eprintln!(" Packet structure (IP Layer):");
let ip_start = 0;
eprintln!(" IP Header (20 bytes at offset {}):", ip_start);
eprintln!(" Src IP: {}.{}.{}.{}", buf[ip_start+12], buf[ip_start+13], buf[ip_start+14], buf[ip_start+15]);
eprintln!(" Dst IP: {}.{}.{}.{}", buf[ip_start+16], buf[ip_start+17], buf[ip_start+18], buf[ip_start+19]);
let udp_start = ip_start + 20;
eprintln!(" UDP Header (8 bytes at offset {}):", udp_start);
let src_port = u16::from_be_bytes([buf[udp_start], buf[udp_start+1]]);
let dst_port = u16::from_be_bytes([buf[udp_start+2], buf[udp_start+3]]);
eprintln!(" Src Port: {}", src_port);
eprintln!(" Dst Port: {}", dst_port);
let dhcp_start = udp_start + 8;
eprintln!(" DHCP Message (at offset {}):", dhcp_start);
eprintln!(" op: {} (1=BOOTREQUEST, 2=BOOTREPLY)", buf[dhcp_start]);
eprintln!(" htype: {}", buf[dhcp_start+1]);
eprintln!(" hlen: {}", buf[dhcp_start+2]);
eprintln!(" hops: {}", buf[dhcp_start+3]);
let xid = u32::from_be_bytes([buf[dhcp_start+4], buf[dhcp_start+5], buf[dhcp_start+6], buf[dhcp_start+7]]);
eprintln!(" xid: 0x{:08x}", xid);
eprintln!(" yiaddr (offered IP): {}.{}.{}.{}",
buf[dhcp_start+16], buf[dhcp_start+17], buf[dhcp_start+18], buf[dhcp_start+19]);
eprintln!(" Magic cookie at offset {}: {:02x} {:02x} {:02x} {:02x}",
dhcp_start+236, buf[dhcp_start+236], buf[dhcp_start+237], buf[dhcp_start+238], buf[dhcp_start+239]);
}
}
}
eprintln!();
Ok(bytes_received)
}
pub fn as_raw_fd(&self) -> i32 {
self.socket.as_raw_fd()
}
}
pub fn build_dhcp_discover_ip_packet(
mac_address: [u8; 6],
xid: u32,
hostname: Option<&str>,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_discover_ip_packet() called");
dhcp_debug!(" MAC address: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
dhcp_debug!(" Transaction ID (xid): 0x{:08x}", xid);
dhcp_debug!(" Hostname: {:?}", hostname);
let src_ip = Ipv4Addr::new(0, 0, 0, 0); let dst_ip = Ipv4Addr::new(255, 255, 255, 255); let src_port: u16 = 68; let dst_port: u16 = 67;
dhcp_debug!("DEBUG: IP layer - src={} dst={}", src_ip, dst_ip);
dhcp_debug!("DEBUG: UDP layer - src_port={} dst_port={}", src_port, dst_port);
let dhcp_msg = build_dhcp_message(mac_address, xid, hostname);
dhcp_debug!("DEBUG: DHCP message built, size={} bytes", dhcp_msg.len());
let udp_len = (8 + dhcp_msg.len()) as u16;
dhcp_debug!("DEBUG: UDP total length: {} bytes", udp_len);
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
dhcp_debug!("DEBUG: IP total length: {} bytes (IP header=20, UDP header=8, DHCP={})",
total_len, dhcp_msg.len());
let mut packet = Vec::new();
packet.push(0x45); packet.push(0x00); packet.extend_from_slice(&total_len.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0u16.to_be_bytes()); packet.push(64); packet.push(17); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&src_ip.octets()); packet.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&packet[0..20]);
dhcp_debug!("DEBUG: Calculated IP checksum: 0x{:04x}", ip_checksum);
packet[10] = (ip_checksum >> 8) as u8;
packet[11] = (ip_checksum & 0xFF) as u8;
packet.extend_from_slice(&udp_header);
packet.extend_from_slice(&dhcp_msg);
dhcp_debug!("DEBUG: Complete packet built, total size={} bytes", packet.len());
dhcp_debug!("DEBUG: Packet breakdown - IP:20 + UDP:8 + DHCP:{} = {}",
dhcp_msg.len(), packet.len());
packet
}
pub fn build_dhcp_discover_link_frame(
src_mac: [u8; 6],
xid: u32,
hostname: Option<&str>,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_discover_link_frame() called");
dhcp_debug!(" Source MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]);
dhcp_debug!(" Transaction ID (xid): 0x{:08x}", xid);
dhcp_debug!(" Hostname: {:?}", hostname);
let dst_mac = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let dst_ip = Ipv4Addr::new(255, 255, 255, 255);
let src_port: u16 = 68;
let dst_port: u16 = 67;
dhcp_debug!("DEBUG: Ethernet - dst_mac=ff:ff:ff:ff:ff:ff (broadcast)");
dhcp_debug!("DEBUG: IP layer - src={} dst={}", src_ip, dst_ip);
dhcp_debug!("DEBUG: UDP layer - src_port={} dst_port={}", src_port, dst_port);
let mut frame = Vec::new();
frame.extend_from_slice(&dst_mac); frame.extend_from_slice(&src_mac); frame.extend_from_slice(&[0x08, 0x00]); dhcp_debug!("DEBUG: Ethernet header built (14 bytes)");
let dhcp_msg = build_dhcp_message(src_mac, xid, hostname);
dhcp_debug!("DEBUG: DHCP message built, size={} bytes", dhcp_msg.len());
let udp_len = (8 + dhcp_msg.len()) as u16;
dhcp_debug!("DEBUG: UDP total length: {} bytes", udp_len);
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
dhcp_debug!("DEBUG: IP total length: {} bytes", total_len);
let ip_start = frame.len();
frame.push(0x45);
frame.push(0x00);
frame.extend_from_slice(&total_len.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.push(64);
frame.push(17); frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&src_ip.octets()); frame.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&frame[ip_start..ip_start + 20]);
dhcp_debug!("DEBUG: Calculated IP checksum: 0x{:04x}", ip_checksum);
frame[ip_start + 10] = (ip_checksum >> 8) as u8;
frame[ip_start + 11] = (ip_checksum & 0xFF) as u8;
frame.extend_from_slice(&udp_header);
frame.extend_from_slice(&dhcp_msg);
dhcp_debug!("DEBUG: Complete frame built, total size={} bytes", frame.len());
dhcp_debug!("DEBUG: Frame breakdown - Ethernet:14 + IP:20 + UDP:8 + DHCP:{} = {}",
dhcp_msg.len(), frame.len());
frame
}
fn build_dhcp_message(mac: [u8; 6], xid: u32, hostname: Option<&str>) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_message() called");
dhcp_debug!(" MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
dhcp_debug!(" XID: 0x{:08x}", xid);
dhcp_debug!(" Hostname: {:?}", hostname);
let mut msg = Vec::new();
dhcp_debug!("DEBUG: Building BOOTP header (236 bytes)");
msg.push(0x01); msg.push(0x01); msg.push(0x06); msg.push(0x00);
msg.extend_from_slice(&xid.to_be_bytes()); msg.extend_from_slice(&0u16.to_be_bytes()); msg.extend_from_slice(&0x8000u16.to_be_bytes()); dhcp_debug!(" op=1 (BOOTREQUEST), htype=1 (Ethernet), hlen=6, hops=0");
dhcp_debug!(" xid=0x{:08x}, secs=0, flags=0x8000 (broadcast)", xid);
msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); dhcp_debug!(" ciaddr=0.0.0.0, yiaddr=0.0.0.0, siaddr=0.0.0.0, giaddr=0.0.0.0");
msg.extend_from_slice(&mac); msg.extend_from_slice(&[0; 10]); dhcp_debug!(" chaddr (client MAC): {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
msg.extend_from_slice(&[0; 64]); msg.extend_from_slice(&[0; 128]);
msg.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]);
dhcp_debug!("DEBUG: BOOTP header complete, size={} bytes", msg.len());
dhcp_debug!("DEBUG: Adding DHCP magic cookie: 63 82 53 63");
msg.extend_from_slice(&[53, 1, 1]);
dhcp_debug!("DEBUG: Added Option 53 (Message Type): DISCOVER (1)");
msg.push(61); msg.push(7); msg.push(0x01); msg.extend_from_slice(&mac);
dhcp_debug!("DEBUG: Added Option 61 (Client ID): hwtype=1, MAC={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
if let Some(name) = hostname {
let name_bytes = name.as_bytes();
if name_bytes.len() <= 255 {
msg.push(12); msg.push(name_bytes.len() as u8); msg.extend_from_slice(name_bytes);
dhcp_debug!("DEBUG: Added Option 12 (Hostname): \"{}\"", name);
}
}
msg.push(55); #[cfg(not(feature = "dhcp4-options"))]
{
msg.push(6); msg.push(1); msg.push(3); msg.push(6); msg.push(15); msg.push(42); msg.push(121); dhcp_debug!("DEBUG: Added Option 55 (Parameter Request List): [1,3,6,15,42,121]");
}
#[cfg(feature = "dhcp4-options")]
{
msg.push(18); msg.push(1); msg.push(3); msg.push(6); msg.push(15); msg.push(42); msg.push(121); msg.push(41); msg.push(65); msg.push(66); msg.push(67); msg.push(69); msg.push(70); msg.push(71); msg.push(72); msg.push(77); msg.push(81); msg.push(95); msg.push(119); dhcp_debug!("DEBUG: Added Option 55 (Parameter Request List): [1,3,6,15,42,121,41,65,66,67,69,70,71,72,77,81,95,119]");
}
msg.push(255);
dhcp_debug!("DEBUG: Added Option 255 (End)");
dhcp_debug!("DEBUG: DHCP message complete, total size={} bytes", msg.len());
msg
}
pub fn build_dhcp_request_ip_packet(
mac_address: [u8; 6],
xid: u32,
requested_ip: Ipv4Addr,
server_id: Ipv4Addr,
hostname: Option<&str>,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_request_ip_packet() called");
dhcp_debug!(" MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
dhcp_debug!(" XID: 0x{:08x}", xid);
dhcp_debug!(" Requested IP: {}", requested_ip);
dhcp_debug!(" Server ID: {}", server_id);
let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let dst_ip = Ipv4Addr::new(255, 255, 255, 255);
let src_port: u16 = 68;
let dst_port: u16 = 67;
let dhcp_msg = build_dhcp_request_message(mac_address, xid, requested_ip, server_id, hostname);
dhcp_debug!("DEBUG: DHCP REQUEST message built, size={} bytes", dhcp_msg.len());
let udp_len = (8 + dhcp_msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
let mut packet = Vec::new();
packet.push(0x45);
packet.push(0x00);
packet.extend_from_slice(&total_len.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.push(64);
packet.push(17);
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&src_ip.octets());
packet.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&packet[0..20]);
packet[10] = (ip_checksum >> 8) as u8;
packet[11] = (ip_checksum & 0xFF) as u8;
packet.extend_from_slice(&udp_header);
packet.extend_from_slice(&dhcp_msg);
dhcp_debug!("DEBUG: Complete REQUEST packet built, total size={} bytes", packet.len());
packet
}
pub fn build_dhcp_request_link_frame(
src_mac: [u8; 6],
xid: u32,
requested_ip: Ipv4Addr,
server_id: Ipv4Addr,
hostname: Option<&str>,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_request_link_frame() called");
let dst_mac = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let dst_ip = Ipv4Addr::new(255, 255, 255, 255);
let src_port: u16 = 68;
let dst_port: u16 = 67;
let mut frame = Vec::new();
frame.extend_from_slice(&dst_mac); frame.extend_from_slice(&src_mac); frame.extend_from_slice(&[0x08, 0x00]);
let dhcp_msg = build_dhcp_request_message(src_mac, xid, requested_ip, server_id, hostname);
let udp_len = (8 + dhcp_msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
let ip_start = frame.len();
frame.push(0x45);
frame.push(0x00);
frame.extend_from_slice(&total_len.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.push(64);
frame.push(17); frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&src_ip.octets()); frame.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&frame[ip_start..ip_start + 20]);
frame[ip_start + 10] = (ip_checksum >> 8) as u8;
frame[ip_start + 11] = (ip_checksum & 0xFF) as u8;
frame.extend_from_slice(&udp_header);
frame.extend_from_slice(&dhcp_msg);
dhcp_debug!("DEBUG: Complete REQUEST frame built, total size={} bytes", frame.len());
frame
}
fn build_dhcp_request_message(
mac: [u8; 6],
xid: u32,
requested_ip: Ipv4Addr,
server_id: Ipv4Addr,
hostname: Option<&str>,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_request_message() called");
let mut msg = Vec::new();
msg.push(0x01);
msg.push(0x01);
msg.push(0x06);
msg.push(0x00);
msg.extend_from_slice(&xid.to_be_bytes());
msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&0x8000u16.to_be_bytes());
msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&mac);
msg.extend_from_slice(&[0; 10]);
msg.extend_from_slice(&[0; 64]);
msg.extend_from_slice(&[0; 128]);
msg.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]);
msg.extend_from_slice(&[53, 1, 3]);
msg.push(50);
msg.push(4);
msg.extend_from_slice(&requested_ip.octets());
dhcp_debug!("DEBUG: Added Option 50 (Requested IP): {}", requested_ip);
msg.push(54);
msg.push(4);
msg.extend_from_slice(&server_id.octets());
dhcp_debug!("DEBUG: Added Option 54 (Server ID): {}", server_id);
msg.push(61);
msg.push(7);
msg.push(0x01);
msg.extend_from_slice(&mac);
if let Some(name) = hostname {
let name_bytes = name.as_bytes();
if name_bytes.len() <= 255 {
msg.push(12);
msg.push(name_bytes.len() as u8);
msg.extend_from_slice(name_bytes);
}
}
msg.push(55);
#[cfg(not(feature = "dhcp4-options"))]
{
msg.push(6);
msg.push(1); msg.push(3); msg.push(6); msg.push(15); msg.push(42); msg.push(121); }
#[cfg(feature = "dhcp4-options")]
{
msg.push(18); msg.push(1); msg.push(3); msg.push(6); msg.push(15); msg.push(42); msg.push(121); msg.push(41); msg.push(65); msg.push(66); msg.push(67); msg.push(69); msg.push(70); msg.push(71); msg.push(72); msg.push(77); msg.push(81); msg.push(95); msg.push(119); }
msg.push(255);
dhcp_debug!("DEBUG: DHCP REQUEST message complete, size={} bytes", msg.len());
msg
}
pub fn build_dhcp_release_ip_packet(
mac_address: [u8; 6],
xid: u32,
client_ip: Ipv4Addr,
server_id: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_release_ip_packet() called");
let src_ip = client_ip;
let dst_ip = server_id;
let src_port: u16 = 68;
let dst_port: u16 = 67;
let mut msg = Vec::new();
msg.push(0x01);
msg.push(0x01);
msg.push(0x06);
msg.push(0x00);
msg.extend_from_slice(&xid.to_be_bytes());
msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&client_ip.octets()); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&mac_address);
msg.extend_from_slice(&[0; 10]);
msg.extend_from_slice(&[0; 64]);
msg.extend_from_slice(&[0; 128]);
msg.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]);
msg.extend_from_slice(&[53, 1, 7]);
msg.push(54);
msg.push(4);
msg.extend_from_slice(&server_id.octets());
msg.push(61);
msg.push(7);
msg.push(0x01);
msg.extend_from_slice(&mac_address);
msg.push(255);
dhcp_debug!("DEBUG: DHCP RELEASE message built, size={} bytes", msg.len());
let udp_len = (8 + msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + msg.len()) as u16;
let mut packet = Vec::new();
packet.push(0x45);
packet.push(0x00);
packet.extend_from_slice(&total_len.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.push(64);
packet.push(17);
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&src_ip.octets());
packet.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&packet[0..20]);
packet[10] = (ip_checksum >> 8) as u8;
packet[11] = (ip_checksum & 0xFF) as u8;
packet.extend_from_slice(&udp_header);
packet.extend_from_slice(&msg);
packet
}
pub fn build_dhcp_decline_ip_packet(
mac_address: [u8; 6],
xid: u32,
declined_ip: Ipv4Addr,
server_id: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_decline_ip_packet() called");
let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let dst_ip = Ipv4Addr::new(255, 255, 255, 255);
let src_port: u16 = 68;
let dst_port: u16 = 67;
let dhcp_msg = build_dhcp_decline_message(mac_address, xid, declined_ip, server_id);
dhcp_debug!("DEBUG: DHCP DECLINE message built, size={} bytes", dhcp_msg.len());
let udp_len = (8 + dhcp_msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
let mut packet = Vec::new();
packet.push(0x45);
packet.push(0x00);
packet.extend_from_slice(&total_len.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes());
packet.push(64);
packet.push(17);
packet.extend_from_slice(&0u16.to_be_bytes());
packet.extend_from_slice(&src_ip.octets());
packet.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&packet[0..20]);
packet[10] = (ip_checksum >> 8) as u8;
packet[11] = (ip_checksum & 0xFF) as u8;
packet.extend_from_slice(&udp_header);
packet.extend_from_slice(&dhcp_msg);
packet
}
pub fn build_dhcp_decline_link_frame(
src_mac: [u8; 6],
xid: u32,
declined_ip: Ipv4Addr,
server_id: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_decline_link_frame() called");
let dst_mac = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let dst_ip = Ipv4Addr::new(255, 255, 255, 255);
let src_port: u16 = 68;
let dst_port: u16 = 67;
let mut frame = Vec::new();
frame.extend_from_slice(&dst_mac); frame.extend_from_slice(&src_mac); frame.extend_from_slice(&[0x08, 0x00]);
let dhcp_msg = build_dhcp_decline_message(src_mac, xid, declined_ip, server_id);
let udp_len = (8 + dhcp_msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + dhcp_msg.len()) as u16;
let ip_start = frame.len();
frame.push(0x45);
frame.push(0x00);
frame.extend_from_slice(&total_len.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.push(64);
frame.push(17);
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&src_ip.octets());
frame.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&frame[ip_start..ip_start + 20]);
frame[ip_start + 10] = (ip_checksum >> 8) as u8;
frame[ip_start + 11] = (ip_checksum & 0xFF) as u8;
frame.extend_from_slice(&udp_header);
frame.extend_from_slice(&dhcp_msg);
dhcp_debug!("DEBUG: Complete DECLINE frame built, total size={} bytes", frame.len());
frame
}
fn build_dhcp_decline_message(
mac: [u8; 6],
xid: u32,
declined_ip: Ipv4Addr,
server_id: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_decline_message() called");
dhcp_debug!(" Declined IP: {}", declined_ip);
dhcp_debug!(" Server ID: {}", server_id);
let mut msg = Vec::new();
msg.push(0x01); msg.push(0x01); msg.push(0x06); msg.push(0x00);
msg.extend_from_slice(&xid.to_be_bytes()); msg.extend_from_slice(&0u16.to_be_bytes()); msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]);
msg.extend_from_slice(&mac); msg.extend_from_slice(&[0; 10]);
msg.extend_from_slice(&[0; 64]); msg.extend_from_slice(&[0; 128]);
msg.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]);
msg.extend_from_slice(&[53, 1, 4]);
dhcp_debug!("DEBUG: Added Option 53 (Message Type): DECLINE (4)");
msg.push(50);
msg.push(4);
msg.extend_from_slice(&declined_ip.octets());
dhcp_debug!("DEBUG: Added Option 50 (Requested IP): {}", declined_ip);
msg.push(54);
msg.push(4);
msg.extend_from_slice(&server_id.octets());
dhcp_debug!("DEBUG: Added Option 54 (Server ID): {}", server_id);
msg.push(61);
msg.push(7);
msg.push(0x01); msg.extend_from_slice(&mac);
dhcp_debug!("DEBUG: Added Option 61 (Client ID)");
msg.push(255);
dhcp_debug!("DEBUG: DHCP DECLINE message complete, size={} bytes", msg.len());
msg
}
pub fn build_dhcp_release_link_frame(
src_mac: [u8; 6],
xid: u32,
client_ip: Ipv4Addr,
server_id: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_dhcp_release_link_frame() called");
let dst_mac = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; let src_ip = client_ip;
let dst_ip = server_id;
let src_port: u16 = 68;
let dst_port: u16 = 67;
let mut frame = Vec::new();
frame.extend_from_slice(&dst_mac); frame.extend_from_slice(&src_mac); frame.extend_from_slice(&[0x08, 0x00]);
let mut msg = Vec::new();
msg.push(0x01);
msg.push(0x01);
msg.push(0x06);
msg.push(0x00);
msg.extend_from_slice(&xid.to_be_bytes());
msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&0u16.to_be_bytes());
msg.extend_from_slice(&client_ip.octets()); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&[0; 4]); msg.extend_from_slice(&src_mac);
msg.extend_from_slice(&[0; 10]);
msg.extend_from_slice(&[0; 64]);
msg.extend_from_slice(&[0; 128]);
msg.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]);
msg.extend_from_slice(&[53, 1, 7]);
msg.push(54);
msg.push(4);
msg.extend_from_slice(&server_id.octets());
msg.push(61);
msg.push(7);
msg.push(0x01);
msg.extend_from_slice(&src_mac);
msg.push(255);
let udp_len = (8 + msg.len()) as u16;
let mut udp_header = Vec::new();
udp_header.extend_from_slice(&src_port.to_be_bytes());
udp_header.extend_from_slice(&dst_port.to_be_bytes());
udp_header.extend_from_slice(&udp_len.to_be_bytes());
udp_header.extend_from_slice(&0u16.to_be_bytes());
let total_len = (20 + 8 + msg.len()) as u16;
let ip_start = frame.len();
frame.push(0x45);
frame.push(0x00);
frame.extend_from_slice(&total_len.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&0u16.to_be_bytes());
frame.push(64);
frame.push(17);
frame.extend_from_slice(&0u16.to_be_bytes());
frame.extend_from_slice(&src_ip.octets());
frame.extend_from_slice(&dst_ip.octets());
let ip_checksum = calculate_checksum(&frame[ip_start..ip_start + 20]);
frame[ip_start + 10] = (ip_checksum >> 8) as u8;
frame[ip_start + 11] = (ip_checksum & 0xFF) as u8;
frame.extend_from_slice(&udp_header);
frame.extend_from_slice(&msg);
dhcp_debug!("DEBUG: Complete RELEASE frame built, total size={} bytes", frame.len());
frame
}
pub fn parse_dhcp_packet(packet: &[u8]) -> Result<(u8, u32, Vec<u8>)> {
dhcp_debug!("DEBUG: parse_dhcp_packet() called, packet size={} bytes", packet.len());
let dhcp_start = 42;
if packet.len() < dhcp_start + 240 {
dhcp_debug!("DEBUG: Packet too short - need {} bytes, got {}", dhcp_start + 240, packet.len());
return Err(anyhow!("Packet too short for DHCP message"));
}
dhcp_debug!("DEBUG: DHCP message starts at offset {}", dhcp_start);
let dhcp_msg = &packet[dhcp_start..];
if dhcp_msg[0] != 0x02 {
dhcp_debug!("DEBUG: Not a BOOTP reply (op={})", dhcp_msg[0]);
return Err(anyhow!("Not a BOOTP reply"));
}
let xid = u32::from_be_bytes([dhcp_msg[4], dhcp_msg[5], dhcp_msg[6], dhcp_msg[7]]);
dhcp_debug!("DEBUG: Transaction ID: 0x{:08x}", xid);
if &dhcp_msg[236..240] != &[0x63, 0x82, 0x53, 0x63] {
dhcp_debug!("DEBUG: Invalid DHCP magic cookie");
return Err(anyhow!("Invalid DHCP magic cookie"));
}
let mut msg_type: Option<u8> = None;
let mut i = 240;
while i < dhcp_msg.len() {
let option = dhcp_msg[i];
if option == 255 {
break; }
if option == 0 {
i += 1; continue;
}
if i + 1 >= dhcp_msg.len() {
break;
}
let length = dhcp_msg[i + 1] as usize;
if option == 53 && length == 1 {
msg_type = Some(dhcp_msg[i + 2]);
dhcp_debug!("DEBUG: Found DHCP message type: {}", dhcp_msg[i + 2]);
}
i += 2 + length;
}
let msg_type = msg_type.ok_or_else(|| anyhow!("No DHCP message type option found"))?;
Ok((msg_type, xid, dhcp_msg.to_vec()))
}
pub fn extract_offered_ip(dhcp_msg: &[u8]) -> Result<Ipv4Addr> {
if dhcp_msg.len() < 20 {
return Err(anyhow!("DHCP message too short"));
}
let ip = Ipv4Addr::new(dhcp_msg[16], dhcp_msg[17], dhcp_msg[18], dhcp_msg[19]);
dhcp_debug!("DEBUG: Extracted offered IP: {}", ip);
Ok(ip)
}
pub fn extract_server_id(dhcp_msg: &[u8]) -> Result<Ipv4Addr> {
let mut i = 240;
while i < dhcp_msg.len() {
let option = dhcp_msg[i];
if option == 255 {
break;
}
if option == 0 {
i += 1;
continue;
}
if i + 1 >= dhcp_msg.len() {
break;
}
let length = dhcp_msg[i + 1] as usize;
if option == 54 && length == 4 {
let server_id = Ipv4Addr::new(
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
);
dhcp_debug!("DEBUG: Extracted server ID: {}", server_id);
return Ok(server_id);
}
i += 2 + length;
}
Err(anyhow!("Server ID option not found"))
}
#[cfg(feature = "dhcp4-options")]
#[derive(Debug, Clone)]
pub struct ClientFqdn {
pub flags: u8,
pub rcode1: u8,
pub rcode2: u8,
pub domain_name: String,
}
#[cfg(feature = "dhcp4-options")]
#[derive(Debug, Clone)]
pub struct ClasslessRoute {
pub destination: Ipv4Addr,
pub prefix_len: u8,
pub gateway: Ipv4Addr,
}
#[derive(Debug, Clone)]
pub struct DhcpLeaseInfo {
pub ip_address: Ipv4Addr,
pub subnet_mask: Option<Ipv4Addr>,
pub router: Option<Ipv4Addr>,
pub dns_servers: Vec<Ipv4Addr>,
pub domain_name: Option<String>,
pub ntp_servers: Vec<Ipv4Addr>,
pub lease_time: u32,
pub renewal_time: Option<u32>,
pub rebinding_time: Option<u32>,
pub server_id: Ipv4Addr,
#[cfg(feature = "dhcp4-options")]
pub nis_servers: Vec<Ipv4Addr>, #[cfg(feature = "dhcp4-options")]
pub nisplus_domain: Option<String>, #[cfg(feature = "dhcp4-options")]
pub tftp_server_name: Option<String>, #[cfg(feature = "dhcp4-options")]
pub bootfile_name: Option<String>, #[cfg(feature = "dhcp4-options")]
pub smtp_servers: Vec<Ipv4Addr>, #[cfg(feature = "dhcp4-options")]
pub pop3_servers: Vec<Ipv4Addr>, #[cfg(feature = "dhcp4-options")]
pub nntp_servers: Vec<Ipv4Addr>, #[cfg(feature = "dhcp4-options")]
pub www_servers: Vec<Ipv4Addr>, #[cfg(feature = "dhcp4-options")]
pub user_class: Option<Vec<u8>>, #[cfg(feature = "dhcp4-options")]
pub client_fqdn: Option<ClientFqdn>, #[cfg(feature = "dhcp4-options")]
pub ldap_servers: Option<String>, #[cfg(feature = "dhcp4-options")]
pub domain_search: Vec<String>, #[cfg(feature = "dhcp4-options")]
pub classless_routes: Vec<ClasslessRoute>, }
pub fn extract_lease_info(dhcp_msg: &[u8]) -> Result<DhcpLeaseInfo> {
dhcp_debug!("DEBUG: extract_lease_info() called");
let ip_address = extract_offered_ip(dhcp_msg)?;
dhcp_debug!("DEBUG: IP address: {}", ip_address);
let mut subnet_mask: Option<Ipv4Addr> = None;
let mut router: Option<Ipv4Addr> = None;
let mut dns_servers: Vec<Ipv4Addr> = Vec::new();
let mut domain_name: Option<String> = None;
let mut ntp_servers: Vec<Ipv4Addr> = Vec::new();
let mut lease_time: Option<u32> = None;
let mut renewal_time: Option<u32> = None;
let mut rebinding_time: Option<u32> = None;
let mut server_id: Option<Ipv4Addr> = None;
#[cfg(feature = "dhcp4-options")]
let mut nis_servers: Vec<Ipv4Addr> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut nisplus_domain: Option<String> = None;
#[cfg(feature = "dhcp4-options")]
let mut tftp_server_name: Option<String> = None;
#[cfg(feature = "dhcp4-options")]
let mut bootfile_name: Option<String> = None;
#[cfg(feature = "dhcp4-options")]
let mut smtp_servers: Vec<Ipv4Addr> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut pop3_servers: Vec<Ipv4Addr> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut nntp_servers: Vec<Ipv4Addr> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut www_servers: Vec<Ipv4Addr> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut user_class: Option<Vec<u8>> = None;
#[cfg(feature = "dhcp4-options")]
let mut client_fqdn: Option<ClientFqdn> = None;
#[cfg(feature = "dhcp4-options")]
let mut ldap_servers: Option<String> = None;
#[cfg(feature = "dhcp4-options")]
let mut domain_search: Vec<String> = Vec::new();
#[cfg(feature = "dhcp4-options")]
let mut classless_routes: Vec<ClasslessRoute> = Vec::new();
let mut i = 240;
while i < dhcp_msg.len() {
let option = dhcp_msg[i];
if option == 255 {
break;
}
if option == 0 {
i += 1;
continue;
}
if i + 1 >= dhcp_msg.len() {
break;
}
let length = dhcp_msg[i + 1] as usize;
if i + 2 + length > dhcp_msg.len() {
break;
}
match option {
1 => {
if length == 4 {
subnet_mask = Some(Ipv4Addr::new(
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
));
dhcp_debug!("DEBUG: Subnet mask: {:?}", subnet_mask);
}
}
3 => {
if length >= 4 {
router = Some(Ipv4Addr::new(
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
));
dhcp_debug!("DEBUG: Router: {:?}", router);
}
}
6 => {
let mut j = 0;
while j + 4 <= length {
let dns = Ipv4Addr::new(
dhcp_msg[i + 2 + j],
dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j],
dhcp_msg[i + 5 + j],
);
dns_servers.push(dns);
j += 4;
}
dhcp_debug!("DEBUG: DNS servers: {:?}", dns_servers);
}
15 => {
if let Ok(name) = String::from_utf8(dhcp_msg[i + 2..i + 2 + length].to_vec()) {
domain_name = Some(name.clone());
dhcp_debug!("DEBUG: Domain name: {}", name);
}
}
42 => {
let mut j = 0;
while j + 4 <= length {
let ntp = Ipv4Addr::new(
dhcp_msg[i + 2 + j],
dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j],
dhcp_msg[i + 5 + j],
);
ntp_servers.push(ntp);
j += 4;
}
dhcp_debug!("DEBUG: NTP servers: {:?}", ntp_servers);
}
51 => {
if length == 4 {
lease_time = Some(u32::from_be_bytes([
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
]));
dhcp_debug!("DEBUG: Lease time: {} seconds", lease_time.unwrap());
}
}
54 => {
if length == 4 {
server_id = Some(Ipv4Addr::new(
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
));
dhcp_debug!("DEBUG: Server ID: {:?}", server_id);
}
}
58 => {
if length == 4 {
renewal_time = Some(u32::from_be_bytes([
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
]));
dhcp_debug!("DEBUG: Renewal time (T1): {} seconds", renewal_time.unwrap());
}
}
59 => {
if length == 4 {
rebinding_time = Some(u32::from_be_bytes([
dhcp_msg[i + 2],
dhcp_msg[i + 3],
dhcp_msg[i + 4],
dhcp_msg[i + 5],
]));
dhcp_debug!("DEBUG: Rebinding time (T2): {} seconds", rebinding_time.unwrap());
}
}
#[cfg(feature = "dhcp4-options")]
41 => {
let mut j = 0;
while j + 4 <= length {
let addr = Ipv4Addr::new(
dhcp_msg[i + 2 + j], dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j], dhcp_msg[i + 5 + j],
);
nis_servers.push(addr);
j += 4;
}
dhcp_debug!("DEBUG: NIS servers: {:?}", nis_servers);
}
#[cfg(feature = "dhcp4-options")]
65 => {
if let Ok(name) = String::from_utf8(dhcp_msg[i + 2..i + 2 + length].to_vec()) {
nisplus_domain = Some(name.clone());
dhcp_debug!("DEBUG: NIS+ domain: {}", name);
}
}
#[cfg(feature = "dhcp4-options")]
66 => {
if let Ok(name) = String::from_utf8(dhcp_msg[i + 2..i + 2 + length].to_vec()) {
tftp_server_name = Some(name.trim_end_matches('\0').to_string());
dhcp_debug!("DEBUG: TFTP server: {:?}", tftp_server_name);
}
}
#[cfg(feature = "dhcp4-options")]
67 => {
if let Ok(name) = String::from_utf8(dhcp_msg[i + 2..i + 2 + length].to_vec()) {
bootfile_name = Some(name.trim_end_matches('\0').to_string());
dhcp_debug!("DEBUG: Bootfile: {:?}", bootfile_name);
}
}
#[cfg(feature = "dhcp4-options")]
69 => {
let mut j = 0;
while j + 4 <= length {
let addr = Ipv4Addr::new(
dhcp_msg[i + 2 + j], dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j], dhcp_msg[i + 5 + j],
);
smtp_servers.push(addr);
j += 4;
}
dhcp_debug!("DEBUG: SMTP servers: {:?}", smtp_servers);
}
#[cfg(feature = "dhcp4-options")]
70 => {
let mut j = 0;
while j + 4 <= length {
let addr = Ipv4Addr::new(
dhcp_msg[i + 2 + j], dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j], dhcp_msg[i + 5 + j],
);
pop3_servers.push(addr);
j += 4;
}
dhcp_debug!("DEBUG: POP3 servers: {:?}", pop3_servers);
}
#[cfg(feature = "dhcp4-options")]
71 => {
let mut j = 0;
while j + 4 <= length {
let addr = Ipv4Addr::new(
dhcp_msg[i + 2 + j], dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j], dhcp_msg[i + 5 + j],
);
nntp_servers.push(addr);
j += 4;
}
dhcp_debug!("DEBUG: NNTP servers: {:?}", nntp_servers);
}
#[cfg(feature = "dhcp4-options")]
72 => {
let mut j = 0;
while j + 4 <= length {
let addr = Ipv4Addr::new(
dhcp_msg[i + 2 + j], dhcp_msg[i + 3 + j],
dhcp_msg[i + 4 + j], dhcp_msg[i + 5 + j],
);
www_servers.push(addr);
j += 4;
}
dhcp_debug!("DEBUG: WWW servers: {:?}", www_servers);
}
#[cfg(feature = "dhcp4-options")]
77 => {
user_class = Some(dhcp_msg[i + 2..i + 2 + length].to_vec());
dhcp_debug!("DEBUG: User class: {:?}", user_class);
}
#[cfg(feature = "dhcp4-options")]
81 => {
if length >= 3 {
let flags = dhcp_msg[i + 2];
let rcode1 = dhcp_msg[i + 3];
let rcode2 = dhcp_msg[i + 4];
let name = if length > 3 {
String::from_utf8_lossy(&dhcp_msg[i + 5..i + 2 + length])
.trim_end_matches('\0').to_string()
} else {
String::new()
};
client_fqdn = Some(ClientFqdn { flags, rcode1, rcode2, domain_name: name });
dhcp_debug!("DEBUG: Client FQDN: {:?}", client_fqdn);
}
}
#[cfg(feature = "dhcp4-options")]
95 => {
if let Ok(urls) = String::from_utf8(dhcp_msg[i + 2..i + 2 + length].to_vec()) {
ldap_servers = Some(urls);
dhcp_debug!("DEBUG: LDAP servers: {:?}", ldap_servers);
}
}
#[cfg(feature = "dhcp4-options")]
119 => {
domain_search = parse_domain_search(&dhcp_msg[i + 2..i + 2 + length]);
dhcp_debug!("DEBUG: Domain search: {:?}", domain_search);
}
#[cfg(feature = "dhcp4-options")]
121 => {
classless_routes = parse_classless_routes(&dhcp_msg[i + 2..i + 2 + length]);
dhcp_debug!("DEBUG: Classless routes: {:?}", classless_routes);
}
_ => {
dhcp_debug!("DEBUG: Skipping option {}, length {}", option, length);
}
}
i += 2 + length;
}
let lease_time = lease_time.ok_or_else(|| anyhow!("Lease time not found"))?;
let server_id = server_id.ok_or_else(|| anyhow!("Server ID not found"))?;
let renewal_time = renewal_time.unwrap_or(lease_time / 2);
let rebinding_time = rebinding_time.unwrap_or(lease_time * 7 / 8);
dhcp_debug!("DEBUG: Final renewal time: {} seconds", renewal_time);
dhcp_debug!("DEBUG: Final rebinding time: {} seconds", rebinding_time);
Ok(DhcpLeaseInfo {
ip_address,
subnet_mask,
router,
dns_servers,
domain_name,
ntp_servers,
lease_time,
renewal_time: Some(renewal_time),
rebinding_time: Some(rebinding_time),
server_id,
#[cfg(feature = "dhcp4-options")]
nis_servers,
#[cfg(feature = "dhcp4-options")]
nisplus_domain,
#[cfg(feature = "dhcp4-options")]
tftp_server_name,
#[cfg(feature = "dhcp4-options")]
bootfile_name,
#[cfg(feature = "dhcp4-options")]
smtp_servers,
#[cfg(feature = "dhcp4-options")]
pop3_servers,
#[cfg(feature = "dhcp4-options")]
nntp_servers,
#[cfg(feature = "dhcp4-options")]
www_servers,
#[cfg(feature = "dhcp4-options")]
user_class,
#[cfg(feature = "dhcp4-options")]
client_fqdn,
#[cfg(feature = "dhcp4-options")]
ldap_servers,
#[cfg(feature = "dhcp4-options")]
domain_search,
#[cfg(feature = "dhcp4-options")]
classless_routes,
})
}
#[cfg(feature = "dhcp4-options")]
fn parse_domain_search(data: &[u8]) -> Vec<String> {
let mut domains = Vec::new();
let mut i = 0;
while i < data.len() {
let mut domain_parts = Vec::new();
let mut j = i;
while j < data.len() {
let len = data[j] as usize;
if len == 0 {
j += 1;
break;
}
if len >= 192 {
j += 2;
break;
}
if j + 1 + len > data.len() {
break;
}
if let Ok(label) = String::from_utf8(data[j + 1..j + 1 + len].to_vec()) {
domain_parts.push(label);
}
j += 1 + len;
}
if !domain_parts.is_empty() {
domains.push(domain_parts.join("."));
}
if j <= i {
break;
}
i = j;
}
domains
}
#[cfg(feature = "dhcp4-options")]
fn parse_classless_routes(data: &[u8]) -> Vec<ClasslessRoute> {
let mut routes = Vec::new();
let mut i = 0;
while i < data.len() {
if i >= data.len() {
break;
}
let prefix_len = data[i];
i += 1;
let addr_bytes = ((prefix_len + 7) / 8) as usize;
if i + addr_bytes + 4 > data.len() {
break;
}
let mut dest = [0u8; 4];
for (j, byte) in data[i..i + addr_bytes].iter().enumerate() {
if j < 4 {
dest[j] = *byte;
}
}
let destination = Ipv4Addr::from(dest);
i += addr_bytes;
let gateway = Ipv4Addr::new(data[i], data[i + 1], data[i + 2], data[i + 3]);
i += 4;
routes.push(ClasslessRoute {
destination,
prefix_len,
gateway,
});
}
routes
}
pub const ARP_OP_REQUEST: u16 = 1;
pub const ARP_OP_REPLY: u16 = 2;
pub fn build_arp_probe_frame(
src_mac: [u8; 6],
target_ip: Ipv4Addr,
) -> Vec<u8> {
dhcp_debug!("DEBUG: build_arp_probe_frame() called");
dhcp_debug!(" Source MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]);
dhcp_debug!(" Target IP: {}", target_ip);
let mut frame = Vec::new();
frame.extend_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); frame.extend_from_slice(&src_mac); frame.extend_from_slice(&[0x08, 0x06]);
frame.extend_from_slice(&1u16.to_be_bytes()); frame.extend_from_slice(&0x0800u16.to_be_bytes()); frame.push(6); frame.push(4); frame.extend_from_slice(&ARP_OP_REQUEST.to_be_bytes());
frame.extend_from_slice(&src_mac);
frame.extend_from_slice(&[0, 0, 0, 0]);
frame.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
frame.extend_from_slice(&target_ip.octets());
dhcp_debug!("DEBUG: ARP probe frame built, size={} bytes", frame.len());
frame
}
pub fn create_arp_socket(interface: &str) -> Result<socket2::Socket> {
dhcp_debug!("DEBUG: create_arp_socket() called for interface: {}", interface);
let if_index = DhcpRawSocket::get_interface_index(interface)?;
let protocol = (0x0806u16).to_be() as i32;
let socket_fd = unsafe {
libc::socket(libc::AF_PACKET, libc::SOCK_RAW, protocol)
};
if socket_fd < 0 {
let errno = unsafe { *libc::__errno_location() };
return Err(anyhow!(
"Failed to create ARP socket (errno={}). Requires CAP_NET_RAW or root privileges",
errno
));
}
let socket = unsafe { socket2::Socket::from_raw_fd(socket_fd) };
let mut sll: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
sll.sll_family = libc::AF_PACKET as u16;
sll.sll_protocol = protocol as u16;
sll.sll_ifindex = if_index;
let ret = unsafe {
libc::bind(
socket.as_raw_fd(),
&sll as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
)
};
if ret < 0 {
let errno = unsafe { *libc::__errno_location() };
return Err(anyhow!(
"Failed to bind ARP socket to interface {} (errno={})",
interface,
errno
));
}
dhcp_debug!("DEBUG: ARP socket created and bound successfully");
Ok(socket)
}
pub fn arp_probe_address(
interface: &str,
src_mac: [u8; 6],
target_ip: Ipv4Addr,
probe_count: u32,
probe_wait_ms: u64,
timeout_ms: u64,
) -> Result<bool> {
dhcp_info!("ARP probing {} on interface {} ({} probes)", target_ip, interface, probe_count);
let socket = create_arp_socket(interface)?;
let probe_frame = build_arp_probe_frame(src_mac, target_ip);
for i in 0..probe_count {
dhcp_debug!("DEBUG: Sending ARP probe {}/{} for {}", i + 1, probe_count, target_ip);
let _sent = socket.send(&probe_frame)
.map_err(|e| anyhow!("Failed to send ARP probe: {}", e))?;
dhcp_debug!("DEBUG: ARP probe sent ({} bytes)", _sent);
if i < probe_count - 1 {
std::thread::sleep(Duration::from_millis(probe_wait_ms));
}
}
dhcp_info!("Listening for ARP replies (timeout: {}ms)", timeout_ms);
socket.set_read_timeout(Some(Duration::from_millis(timeout_ms)))
.map_err(|e| anyhow!("Failed to set socket timeout: {}", e))?;
let mut buf = [0u8; 64]; let start = std::time::Instant::now();
let timeout = Duration::from_millis(timeout_ms);
while start.elapsed() < timeout {
let mut uninit_buf = vec![MaybeUninit::<u8>::uninit(); buf.len()];
match socket.recv_from(&mut uninit_buf) {
Ok((size, _)) => {
for (i, byte) in uninit_buf.iter().take(size).enumerate() {
unsafe { buf[i] = byte.assume_init(); }
}
dhcp_debug!("DEBUG: Received packet ({} bytes)", size);
if let Some(conflict) = parse_arp_reply(&buf[..size], src_mac, target_ip) {
if conflict {
dhcp_info!("ARP conflict detected! IP {} is already in use", target_ip);
return Ok(true); }
}
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock ||
e.kind() == std::io::ErrorKind::TimedOut {
break;
}
dhcp_debug!("DEBUG: ARP recv error: {}", e);
}
}
}
dhcp_info!("No ARP conflict detected for {}", target_ip);
Ok(false) }
fn parse_arp_reply(packet: &[u8], our_mac: [u8; 6], target_ip: Ipv4Addr) -> Option<bool> {
if packet.len() < 42 {
return None;
}
if packet[12] != 0x08 || packet[13] != 0x06 {
return None;
}
let arp = &packet[14..];
if arp[0] != 0x00 || arp[1] != 0x01 || arp[2] != 0x08 || arp[3] != 0x00 {
return None;
}
if arp[4] != 6 || arp[5] != 4 {
return None;
}
let _operation = u16::from_be_bytes([arp[6], arp[7]]);
let sender_mac: [u8; 6] = [arp[8], arp[9], arp[10], arp[11], arp[12], arp[13]];
let sender_ip = Ipv4Addr::new(arp[14], arp[15], arp[16], arp[17]);
dhcp_debug!("DEBUG: ARP packet - op={}, sender_mac={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, sender_ip={}",
_operation, sender_mac[0], sender_mac[1], sender_mac[2],
sender_mac[3], sender_mac[4], sender_mac[5], sender_ip);
if sender_mac == our_mac {
dhcp_debug!("DEBUG: Ignoring our own ARP packet");
return Some(false);
}
if sender_ip == target_ip {
dhcp_info!("ARP conflict: {} is being used by {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
sender_ip, sender_mac[0], sender_mac[1], sender_mac[2],
sender_mac[3], sender_mac[4], sender_mac[5]);
return Some(true); }
Some(false)
}
fn calculate_checksum(data: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i < data.len() - 1 {
let word = u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += word;
i += 2;
}
if i < data.len() {
sum += (data[i] as u32) << 8;
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "dhcp4-options")]
fn test_parse_domain_search() {
let data = vec![
7, b'e', b'x', b'a', b'm', b'p', b'l', b'e',
3, b'c', b'o', b'm', 0,
4, b't', b'e', b's', b't',
3, b'o', b'r', b'g', 0
];
let domains = parse_domain_search(&data);
assert_eq!(domains.len(), 2);
assert_eq!(domains[0], "example.com");
assert_eq!(domains[1], "test.org");
}
#[test]
#[cfg(feature = "dhcp4-options")]
fn test_parse_classless_routes() {
let data = vec![
8, 10, 192, 168, 1, 1, 16, 192, 168, 192, 168, 1, 254, ];
let routes = parse_classless_routes(&data);
assert_eq!(routes.len(), 2);
assert_eq!(routes[0].prefix_len, 8);
assert_eq!(routes[0].destination, Ipv4Addr::new(10, 0, 0, 0));
assert_eq!(routes[0].gateway, Ipv4Addr::new(192, 168, 1, 1));
assert_eq!(routes[1].prefix_len, 16);
assert_eq!(routes[1].destination, Ipv4Addr::new(192, 168, 0, 0));
assert_eq!(routes[1].gateway, Ipv4Addr::new(192, 168, 1, 254));
}
#[test]
#[cfg(feature = "dhcp4-options")]
fn test_client_fqdn_struct() {
let fqdn = ClientFqdn {
flags: 0x01, rcode1: 0,
rcode2: 0,
domain_name: "host.example.com".to_string(),
};
assert_eq!(fqdn.flags, 0x01);
assert_eq!(fqdn.domain_name, "host.example.com");
}
#[test]
#[cfg(feature = "dhcp4-options")]
fn test_classless_route_struct() {
let route = ClasslessRoute {
destination: Ipv4Addr::new(10, 0, 0, 0),
prefix_len: 8,
gateway: Ipv4Addr::new(192, 168, 1, 1),
};
assert_eq!(route.prefix_len, 8);
assert_eq!(route.destination, Ipv4Addr::new(10, 0, 0, 0));
assert_eq!(route.gateway, Ipv4Addr::new(192, 168, 1, 1));
}
#[test]
#[cfg(feature = "dhcp4-options")]
fn test_extended_parameter_request_list() {
let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let xid = 0x12345678;
let frame = build_dhcp_discover_link_frame(mac, xid, None);
let dhcp_options_start = 42 + 236 + 4;
let options = &frame[dhcp_options_start..];
let mut i = 0;
let mut found_opt55 = false;
let mut opt55_len = 0;
while i < options.len() {
if options[i] == 255 { break; } if options[i] == 0 { i += 1; continue; } let opt_code = options[i];
let opt_len = options[i + 1] as usize;
if opt_code == 55 {
found_opt55 = true;
opt55_len = opt_len;
break;
}
i += 2 + opt_len;
}
assert!(found_opt55, "Option 55 not found");
assert_eq!(opt55_len, 18, "Extended options should have 18 parameters");
}
#[test]
fn test_checksum() {
let data = vec![
0x45, 0x00, 0x00, 0x3c, 0x1c, 0x46, 0x40, 0x00,
0x40, 0x06, 0x00, 0x00, 0xac, 0x10, 0x0a, 0x63,
0xac, 0x10, 0x0a, 0x0c
];
let checksum = calculate_checksum(&data);
assert!(checksum != 0);
}
#[test]
fn test_dhcp_message_structure() {
let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let xid = 0x12345678;
let msg = build_dhcp_message(mac, xid, Some("testhost"));
assert!(msg.len() >= 240);
assert_eq!(msg[0], 0x01);
assert_eq!(&msg[236..240], &[0x63, 0x82, 0x53, 0x63]);
}
#[test]
fn test_ip_packet_structure() {
let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
let xid = 0xabcdef01;
let packet = build_dhcp_discover_ip_packet(mac, xid, None);
assert_eq!(packet[0], 0x45);
assert_eq!(packet[9], 17);
assert_eq!(&packet[12..16], &[0, 0, 0, 0]);
assert_eq!(&packet[16..20], &[255, 255, 255, 255]);
}
#[test]
fn test_link_frame_structure() {
let mac = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let xid = 0x99887766;
let frame = build_dhcp_discover_link_frame(mac, xid, None);
assert_eq!(&frame[0..6], &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); assert_eq!(&frame[6..12], &mac); assert_eq!(&frame[12..14], &[0x08, 0x00]);
assert_eq!(frame[14], 0x45);
}
}