use alloc::{vec, vec::Vec};
use smoltcp::{
phy::ChecksumCapabilities,
wire::{
DHCP_CLIENT_PORT, DHCP_SERVER_PORT, DhcpMessageType, DhcpPacket, DhcpRepr, EthernetAddress,
IpAddress, IpProtocol, Ipv4Address, Ipv4Packet, Ipv4Repr, UdpPacket, UdpRepr,
},
};
use crate::config::InterfaceId;
const LEASE_SECS: u32 = 86400;
pub struct DhcpServer {
pub server_ip: Ipv4Address,
pub client_ip: Ipv4Address,
pub subnet_mask: Ipv4Address,
pub dev: usize,
interface_id: InterfaceId,
leased_to: Option<EthernetAddress>,
}
impl DhcpServer {
pub fn new(
dev: usize,
interface_id: InterfaceId,
server_ip: Ipv4Address,
client_ip: Ipv4Address,
subnet_mask: Ipv4Address,
) -> Self {
Self {
server_ip,
client_ip,
subnet_mask,
dev,
interface_id,
leased_to: None,
}
}
pub fn process_packet(&mut self, interface_id: InterfaceId, packet: &[u8]) -> Option<Vec<u8>> {
if interface_id != self.interface_id {
return None;
}
let ipv4_packet = Ipv4Packet::new_checked(packet).ok()?;
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &ChecksumCapabilities::default()).ok()?;
if ipv4_repr.next_header != IpProtocol::Udp {
return None;
}
let udp_packet = UdpPacket::new_checked(ipv4_packet.payload()).ok()?;
let udp_repr = UdpRepr::parse(
&udp_packet,
&IpAddress::Ipv4(ipv4_repr.src_addr),
&IpAddress::Ipv4(ipv4_repr.dst_addr),
&ChecksumCapabilities::default(),
)
.ok()?;
if udp_repr.src_port != DHCP_CLIENT_PORT || udp_repr.dst_port != DHCP_SERVER_PORT {
return None;
}
let dhcp_packet = DhcpPacket::new_checked(udp_packet.payload()).ok()?;
let dhcp_repr = DhcpRepr::parse(&dhcp_packet).ok()?;
let client_mac = dhcp_repr.client_hardware_address;
let xid = dhcp_repr.transaction_id;
let reply_type = match dhcp_repr.message_type {
DhcpMessageType::Discover => {
info!(
"[dhcp-srv] Discover from {client_mac} -> Offer {}",
self.client_ip
);
DhcpMessageType::Offer
}
DhcpMessageType::Request => {
self.leased_to = Some(client_mac);
info!(
"[dhcp-srv] Request from {client_mac} -> Ack {}",
self.client_ip
);
DhcpMessageType::Ack
}
_ => return None,
};
Some(self.build_reply(client_mac, xid, reply_type))
}
fn build_reply(
&self,
client_mac: EthernetAddress,
xid: u32,
message_type: DhcpMessageType,
) -> Vec<u8> {
let dhcp_repr = DhcpRepr {
message_type,
transaction_id: xid,
secs: 0,
client_hardware_address: client_mac,
client_ip: Ipv4Address::UNSPECIFIED,
your_ip: self.client_ip,
server_ip: self.server_ip,
router: Some(self.server_ip),
subnet_mask: Some(self.subnet_mask),
relay_agent_ip: Ipv4Address::UNSPECIFIED,
broadcast: true,
requested_ip: None,
client_identifier: None,
server_identifier: Some(self.server_ip),
parameter_request_list: None,
dns_servers: None,
max_size: None,
lease_duration: Some(LEASE_SECS),
renew_duration: None,
rebind_duration: None,
additional_options: &[],
};
let udp_repr = UdpRepr {
src_port: DHCP_SERVER_PORT,
dst_port: DHCP_CLIENT_PORT,
};
let ipv4_repr = Ipv4Repr {
src_addr: self.server_ip,
dst_addr: Ipv4Address::BROADCAST,
next_header: IpProtocol::Udp,
payload_len: udp_repr.header_len() + dhcp_repr.buffer_len(),
hop_limit: 64,
};
let mut buffer = vec![0; ipv4_repr.buffer_len() + ipv4_repr.payload_len];
let checksum_caps = ChecksumCapabilities::default();
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut buffer);
ipv4_repr.emit(&mut ipv4_packet, &checksum_caps);
let mut udp_packet = UdpPacket::new_unchecked(ipv4_packet.payload_mut());
udp_repr.emit(
&mut udp_packet,
&IpAddress::Ipv4(ipv4_repr.src_addr),
&IpAddress::Ipv4(ipv4_repr.dst_addr),
dhcp_repr.buffer_len(),
|payload| {
dhcp_repr
.emit(&mut DhcpPacket::new_unchecked(payload))
.expect("failed to emit DHCP reply");
},
&checksum_caps,
);
buffer
}
}