blob: 7da713ae8870adf8cd9622c50d83ff5ee7ceb94a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::configuration::{ClientConfig, ServerConfig};
use crate::protocol::{self, ConfigOption, Message, MessageType, OpCode, OptionCode};
use failure::Fail;
use byteorder::{BigEndian, ByteOrder};
use fidl_fuchsia_hardware_ethernet_ext::MacAddress as MacAddr;
use std::cmp;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::net::Ipv4Addr;
use std::ops::Fn;
/// A minimal DHCP server.
///
/// This comment will be expanded upon in future CLs as the server design
/// is iterated upon.
pub struct Server<F>
where
F: Fn() -> i64,
{
cache: CachedClients,
pool: AddressPool,
config: ServerConfig,
time_provider: F,
}
// A wrapper around the error types which can be returned
// by DHCP Server in response to client requests
#[derive(Debug, Fail, PartialEq)]
pub enum ServerError {
#[fail(display = "Received Client Message Type is invalid : {:?}", _0)]
InvalidClientMessage(MessageType),
#[fail(display = "Requested Ipv4 Address by client is invalid : {}", _0)]
BadRequestedIpv4Addr(String),
#[fail(display = "Server encountered error in local address pool manipulation : {}", _0)]
ServerAddressPoolFailure(AddressPoolError),
#[fail(display = "Server is not the intended target for this client request.")]
UnwantedDHCPServer,
#[fail(display = "Unable to assign the requested ipv4 address to client : {}", _0)]
RequestedAddrAssignmentFailure(String),
#[fail(display = "Requested Ipv4Addr not found")]
RequestedIpv4AddrNotFound,
#[fail(display = "Client Mac address could not be identified")]
UnknownClientMac,
#[fail(display = "Client State could not be identified during Init Reboot")]
UnknownClientStatAtReboot,
#[fail(display = "The declined Ipv4 address by client not found in server pool")]
DeclinedIpv4AddrNotFound,
#[fail(display = "Client declined offered Ipv4 address : {}", _0)]
ClientDeclinedOfferedAddr(Ipv4Addr),
#[fail(display = "Client released Ipv4 address : {}", _0)]
ClientReleasedIpv4Addr(Ipv4Addr),
#[fail(display = "Unable to identify Client Request Type")]
UnknownClientRequest
}
impl From<AddressPoolError> for ServerError {
fn from(e: AddressPoolError) -> Self {
ServerError::ServerAddressPoolFailure(e)
}
}
impl<F> Server<F>
where
F: Fn() -> i64,
{
/// Returns an initialized `Server` value.
pub fn new(time_provider: F) -> Server<F> {
Server {
cache: HashMap::new(),
pool: AddressPool::new(),
config: ServerConfig::new(),
time_provider: time_provider,
}
}
/// Instantiates a `Server` value from the provided `ServerConfig`.
pub fn from_config(config: ServerConfig, time_provider: F) -> Server<F> {
let mut server = Server {
cache: HashMap::new(),
pool: AddressPool::new(),
config: config,
time_provider: time_provider,
};
server.pool.load_pool(&server.config.managed_addrs);
server
}
/// Dispatches an incoming DHCP message to the appropriate handler for processing.
///
/// If the incoming message is a valid client DHCP message, then the server will attempt to
/// take appropriate action to serve the client's request, update the internal server state,
/// and return a response Ok(Message) in the 'Result'. If the incoming message is invalid,
/// or the server is unable to serve the request, or the processing of the client's request
/// resulted in an error, then `dispatch()` will return `Err(ServerError)` as the 'Result'.
/// with the necessary information about the error
pub fn dispatch(&mut self, msg: Message) -> Result<Message, ServerError> {
match msg.get_dhcp_type() {
Some(MessageType::DHCPDISCOVER) => self.handle_discover(msg),
Some(MessageType::DHCPOFFER) => Err(ServerError::InvalidClientMessage(MessageType::DHCPOFFER)),
Some(MessageType::DHCPREQUEST) => self.handle_request(msg),
Some(MessageType::DHCPDECLINE) => self.handle_decline(msg),
Some(MessageType::DHCPACK) => Err(ServerError::InvalidClientMessage(MessageType::DHCPACK)),
Some(MessageType::DHCPNAK) => Err(ServerError::InvalidClientMessage(MessageType::DHCPNAK)),
Some(MessageType::DHCPRELEASE) => self.handle_release(msg),
Some(MessageType::DHCPINFORM) => self.handle_inform(msg),
None => Err(ServerError::UnknownClientRequest),
}
}
fn handle_discover(&mut self, disc: Message) -> Result<Message, ServerError> {
let client_config = self.client_config(&disc);
let offered_ip = self.get_addr(&disc)?;
let mut offer = build_offer(disc, &self.config, &client_config);
offer.yiaddr = offered_ip;
let () = self.update_server_cache(
Ipv4Addr::from(offer.yiaddr),
offer.chaddr,
vec![],
&client_config,
)?;
Ok(offer)
}
fn get_addr(&mut self, client: &Message) -> Result<Ipv4Addr, ServerError> {
if let Some(config) = self.cache.get(&client.chaddr) {
if !config.expired((self.time_provider)()) {
// Free cached address so that it can be reallocated to same client.
let () = self.pool.free_addr(config.client_addr)?;
return Ok(config.client_addr);
} else if self.pool.addr_is_available(config.client_addr) {
return Ok(config.client_addr);
}
}
if let Some(opt) = client.get_config_option(OptionCode::RequestedIpAddr) {
if opt.value.len() >= 4 {
let requested_addr = protocol::ip_addr_from_buf_at(&opt.value, 0)
.ok_or_else(|| ServerError::BadRequestedIpv4Addr("out of range indexing on opt.value".to_owned()))?;
if self.pool.addr_is_available(requested_addr) {
return Ok(requested_addr);
}
}
}
let addr = self.pool.get_next_available_addr()?;
Ok(addr)
}
fn update_server_cache(
&mut self,
client_addr: Ipv4Addr,
client_mac: MacAddr,
client_opts: Vec<ConfigOption>,
client_config: &ClientConfig,
) -> Result<(), ServerError> {
let config = CachedConfig {
client_addr: client_addr,
options: client_opts,
expiration: (self.time_provider)() + client_config.lease_time_s as i64,
};
self.cache.insert(client_mac, config);
let () = self.pool.allocate_addr(client_addr)?;
Ok(())
}
fn handle_request(&mut self, req: Message) -> Result<Message, ServerError> {
match get_client_state(&req) {
ClientState::Selecting => self.handle_request_selecting(req),
ClientState::InitReboot => self.handle_request_init_reboot(req),
ClientState::Renewing => self.handle_request_renewing(req),
ClientState::Unknown => Err(ServerError::UnknownClientStatAtReboot),
}
}
fn handle_request_selecting(&mut self, req: Message) -> Result<Message, ServerError> {
let requested_ip = req.ciaddr;
if !is_recipient(self.config.server_ip, &req) {
Err(ServerError::UnwantedDHCPServer)
} else {
let () = self.check_ip_allocation(&req, requested_ip)?;
Ok(build_ack(req, requested_ip, &self.config))
}
/*
let requested_ip = req.ciaddr;
if !is_recipient(self.config.server_ip, &req) || !self.is_assigned(&req, requested_ip) {
return Err(ServerError::IncorrectDHCP);
}
Some(build_ack(req, requested_ip, &self.config))
*/
}
fn check_ip_allocation(&self, req: &Message, requested_ip: Ipv4Addr) -> Result<(), ServerError> {
if let Some(client_config) = self.cache.get(&req.chaddr) {
if client_config.client_addr != requested_ip {
Err(ServerError::RequestedAddrAssignmentFailure("Requested Ipv4 \
address does not match initial address requested in discovery".to_owned()))
} else if client_config.expired((self.time_provider)()) {
Err(ServerError::RequestedAddrAssignmentFailure("Client Config has expired".to_owned()))
} else if !self.pool.addr_is_allocated(requested_ip) {
Err(ServerError::RequestedAddrAssignmentFailure("Server failed to reserve requested Ipv4 adddress".to_owned()))
} else {
Ok(())
}
} else {
Err(ServerError::RequestedAddrAssignmentFailure("Could not retrieve client config".to_owned()))
}
/* if let Some(client_config) = self.cache.get(&req.chaddr) {
client_config.client_addr == requested_ip
&& !client_config.expired((self.time_provider)())
&& self.pool.addr_is_allocated(requested_ip)
} else {
false
}*/
}
fn handle_request_init_reboot(&mut self, req: Message) -> Result<Message, ServerError> {
let requested_ip = get_requested_ip_addr(&req).ok_or_else(|| ServerError::RequestedIpv4AddrNotFound)?;
if !is_in_subnet(requested_ip, &self.config) {
//return Ok(build_nak(req, &self.config))
let msg = build_nak(req, &self.config);
/* let s = String::from("hello world");
add_request_nak_message(&mut msg, s);*/
return Ok(msg)
}
if !is_client_mac_known(req.chaddr, &self.cache) {
return Err(ServerError::UnknownClientMac)
}
if self.check_ip_allocation(&req, requested_ip).is_err() {
return Ok(build_nak(req, &self.config))
}
Ok(build_ack(req, requested_ip, &self.config))
}
fn handle_request_renewing(&mut self, req: Message) -> Result<Message, ServerError> {
let client_ip = req.ciaddr;
let () = self.check_ip_allocation(&req, client_ip)?;
/* if !self.is_assigned(&req, client_ip) {
return None;
}*/
Ok(build_ack(req, client_ip, &self.config))
}
fn handle_decline(&mut self, dec: Message) -> Result<Message, ServerError> {
let declined_ip = get_requested_ip_addr(&dec).ok_or_else(|| ServerError::DeclinedIpv4AddrNotFound)?;
if is_recipient(self.config.server_ip, &dec) && self.check_ip_allocation(&dec, declined_ip).is_err() {
let () = self.pool.allocate_addr(declined_ip)?;
}
self.cache.remove(&dec.chaddr);
Err(ServerError::ClientDeclinedOfferedAddr(declined_ip))
/* let declined_ip = get_requested_ip_addr(&dec)?;
if is_recipient(self.config.server_ip, &dec) && !self.is_assigned(&dec, declined_ip) {
self.pool.allocate_addr(declined_ip);
}
self.cache.remove(&dec.chaddr);
None*/
}
fn handle_release(&mut self, rel: Message) -> Result<Message, ServerError> {
if self.cache.contains_key(&rel.chaddr) {
let () = self.pool.free_addr(rel.ciaddr)?;
}
Err(ServerError::ClientReleasedIpv4Addr(rel.ciaddr))
}
fn handle_inform(&mut self, inf: Message) -> Result<Message, ServerError> {
// When responding to an INFORM, the server must leave yiaddr zeroed.
let yiaddr = Ipv4Addr::new(0, 0, 0, 0);
let mut ack = build_ack(inf, yiaddr, &self.config);
ack.options.clear();
add_inform_ack_options(&mut ack, &self.config);
Ok(ack)
}
/// Releases all allocated IP addresses whose leases have expired back to
/// the pool of addresses available for allocation.
pub fn release_expired_leases(&mut self) {
let now = (self.time_provider)();
let expired_clients: Vec<(MacAddr, Ipv4Addr)> = self
.cache
.iter()
.filter(|(_mac, config)| config.expired(now))
.map(|(mac, config)| (*mac, config.client_addr))
.collect();
// Expired client entries must be removed in a separate statement because otherwise we
// would be attempting to change a cache as we iterate over it.
expired_clients.iter().for_each(|(mac, ip)| {
let _ = self.pool.free_addr(*ip);
self.cache.remove(mac);
});
}
pub fn client_config(&self, client_message: &Message) -> ClientConfig {
let requested_config = client_message.parse_to_config();
ClientConfig {
lease_time_s: match requested_config.lease_time_s {
None => self.config.default_lease_time,
Some(t) => cmp::min(t, self.config.max_lease_time_s),
},
}
}
}
/// A cache mapping clients to their configuration data.
///
/// The server should store configuration data for all clients
/// to which it has sent a DHCPOFFER message. Entries in the cache
/// will eventually timeout, although such functionality is currently
/// unimplemented.
type CachedClients = HashMap<MacAddr, CachedConfig>;
#[derive(Clone, Debug, PartialEq)]
struct CachedConfig {
client_addr: Ipv4Addr,
options: Vec<ConfigOption>,
expiration: i64,
}
impl Default for CachedConfig {
fn default() -> Self {
CachedConfig {
client_addr: Ipv4Addr::new(0, 0, 0, 0),
options: vec![],
expiration: std::i64::MAX,
}
}
}
impl CachedConfig {
fn expired(&self, now: i64) -> bool {
self.expiration <= now
}
}
/// The pool of addresses managed by the server.
///
/// Any address managed by the server should be stored in only one
/// of the available/allocated sets at a time. In other words, an
/// address in `available_addrs` must not be in `allocated_addrs` and
/// vice-versa.
#[derive(Debug)]
struct AddressPool {
// available_addrs uses a BTreeSet so that addresses are allocated
// in a deterministic order.
available_addrs: BTreeSet<Ipv4Addr>,
allocated_addrs: HashSet<Ipv4Addr>,
}
//This is a wrapper around different errors that could be returned by
// the DHCP server address pool during address allocation/de-allocation
#[derive(Debug, Fail, PartialEq)]
pub enum AddressPoolError {
#[fail(display = "Address Pool does not have any more available Ipv4 addresses")]
Ipv4AddrExhaustion,
#[fail(display = "Invalid Server State: attempted to allocate unavailable address : {}", _0)]
UnavailableIpv4AddrAllocation(Ipv4Addr),
#[fail(display = "Invalid Server State: attempted to free unallocated address : {}", _0)]
UnallocatedIpv4AddrRelease(Ipv4Addr),
}
impl AddressPool {
fn new() -> Self {
AddressPool { available_addrs: BTreeSet::new(), allocated_addrs: HashSet::new() }
}
fn load_pool(&mut self, addrs: &[Ipv4Addr]) {
for addr in addrs {
if !self.allocated_addrs.contains(&addr) {
self.available_addrs.insert(*addr);
}
}
}
//TODO(sshrivy): Should the address be chosen as per the client subnet
//
//RFC2131#section-4.3.1
//
//A new address allocated from the server's pool of available
//addresses; the address is selected based on the subnet from which
//the message was received (if 'giaddr' is 0) or on the address of
//the relay agent that forwarded the message ('giaddr' when not 0)
fn get_next_available_addr(&self) -> Result<Ipv4Addr, AddressPoolError> {
let mut iter = self.available_addrs.iter();
match iter.next() {
Some(addr) => Ok(*addr),
None => Err(AddressPoolError::Ipv4AddrExhaustion),
}
}
fn allocate_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
if self.available_addrs.remove(&addr) {
self.allocated_addrs.insert(addr);
Ok(())
} else {
Err(AddressPoolError::UnavailableIpv4AddrAllocation(addr))
}
}
fn free_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
if self.allocated_addrs.remove(&addr) {
self.available_addrs.insert(addr);
Ok(())
} else {
Err(AddressPoolError::UnallocatedIpv4AddrRelease(addr))
}
}
fn addr_is_available(&self, addr: Ipv4Addr) -> bool {
self.available_addrs.contains(&addr) && !self.allocated_addrs.contains(&addr)
}
fn addr_is_allocated(&self, addr: Ipv4Addr) -> bool {
!self.available_addrs.contains(&addr) && self.allocated_addrs.contains(&addr)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ClientState {
Unknown,
Selecting,
InitReboot,
Renewing,
}
fn build_offer(client: Message, config: &ServerConfig, client_config: &ClientConfig) -> Message {
let mut offer = client;
offer.op = OpCode::BOOTREPLY;
offer.secs = 0;
offer.ciaddr = Ipv4Addr::new(0, 0, 0, 0);
offer.siaddr = Ipv4Addr::new(0, 0, 0, 0);
offer.sname = String::new();
offer.file = String::new();
add_required_options(&mut offer, config, client_config, MessageType::DHCPOFFER);
add_recommended_options(&mut offer, config);
offer
}
fn add_required_options(
msg: &mut Message,
config: &ServerConfig,
client_config: &ClientConfig,
msg_type: MessageType,
) {
msg.options.clear();
let mut lease = vec![0; 4];
BigEndian::write_u32(&mut lease, client_config.lease_time_s);
msg.options.push(ConfigOption { code: OptionCode::IpAddrLeaseTime, value: lease });
msg.options.push(ConfigOption {
code: OptionCode::SubnetMask,
value: config.subnet_mask.octets().to_vec(),
});
msg.options
.push(ConfigOption { code: OptionCode::DhcpMessageType, value: vec![msg_type.into()] });
msg.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
}
fn add_recommended_options(msg: &mut Message, config: &ServerConfig) {
msg.options
.push(ConfigOption { code: OptionCode::Router, value: ip_vec_to_bytes(&config.routers) });
msg.options.push(ConfigOption {
code: OptionCode::NameServer,
value: ip_vec_to_bytes(&config.name_servers),
});
let mut renewal_time = vec![0, 0, 0, 0];
BigEndian::write_u32(&mut renewal_time, config.default_lease_time / 2);
msg.options.push(ConfigOption { code: OptionCode::RenewalTime, value: renewal_time });
let mut rebinding_time = vec![0, 0, 0, 0];
BigEndian::write_u32(&mut rebinding_time, config.default_lease_time / 4);
msg.options.push(ConfigOption { code: OptionCode::RebindingTime, value: rebinding_time });
}
fn add_inform_ack_options(msg: &mut Message, config: &ServerConfig) {
msg.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPINFORM.into()],
});
msg.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
msg.options
.push(ConfigOption { code: OptionCode::Router, value: ip_vec_to_bytes(&config.routers) });
msg.options.push(ConfigOption {
code: OptionCode::NameServer,
value: ip_vec_to_bytes(&config.name_servers),
});
}
fn ip_vec_to_bytes<'a, T>(ips: T) -> Vec<u8>
where
T: IntoIterator<Item = &'a Ipv4Addr>,
{
ips.into_iter().flat_map(|ip| ip.octets().to_vec()).collect()
}
fn is_recipient(server_ip: Ipv4Addr, req: &Message) -> bool {
if let Some(server_id) = get_server_id_from(&req) {
return server_id == server_ip;
}
false
}
fn build_ack(req: Message, requested_ip: Ipv4Addr, config: &ServerConfig) -> Message {
let mut ack = req;
ack.op = OpCode::BOOTREPLY;
ack.secs = 0;
ack.yiaddr = requested_ip;
ack.options.clear();
add_required_options(
&mut ack,
config,
&ClientConfig::new(config.default_lease_time),
MessageType::DHCPACK,
);
add_recommended_options(&mut ack, config);
ack
}
fn is_in_subnet(ip: Ipv4Addr, config: &ServerConfig) -> bool {
config.subnet_mask.apply_to(ip) == config.subnet_mask.apply_to(config.server_ip)
}
fn is_client_mac_known(mac: MacAddr, cache: &CachedClients) -> bool {
cache.get(&mac).is_some()
}
fn build_nak(req: Message, config: &ServerConfig) -> Message {
let mut nak = req;
nak.op = OpCode::BOOTREPLY;
nak.secs = 0;
nak.ciaddr = Ipv4Addr::new(0, 0, 0, 0);
nak.yiaddr = Ipv4Addr::new(0, 0, 0, 0);
nak.siaddr = Ipv4Addr::new(0, 0, 0, 0);
nak.options.clear();
let mut lease = vec![0; 4];
BigEndian::write_u32(&mut lease, config.default_lease_time);
nak.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPNAK.into()],
});
nak.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
nak
}
/*fn add_request_nak_message(nak: &mut Message, msg: String) {
nak.options.push(ConfigOption {
code: OptionCode::Message,
value: msg.into_bytes(),
});
}*/
fn get_client_state(msg: &Message) -> ClientState {
let maybe_server_id = get_server_id_from(&msg);
let maybe_requested_ip = get_requested_ip_addr(&msg);
let zero_ciaddr = Ipv4Addr::new(0, 0, 0, 0);
if maybe_server_id.is_some() && maybe_requested_ip.is_none() && msg.ciaddr != zero_ciaddr {
return ClientState::Selecting;
} else if maybe_requested_ip.is_some() && msg.ciaddr == zero_ciaddr {
return ClientState::InitReboot;
} else if msg.ciaddr != zero_ciaddr {
return ClientState::Renewing;
} else {
return ClientState::Unknown;
}
}
fn get_requested_ip_addr(req: &Message) -> Option<Ipv4Addr> {
let req_ip_opt = req.options.iter().find(|opt| opt.code == OptionCode::RequestedIpAddr)?;
let raw_ip = BigEndian::read_u32(&req_ip_opt.value);
Some(Ipv4Addr::from(raw_ip))
}
fn get_server_id_from(req: &Message) -> Option<Ipv4Addr> {
let server_id_opt = req.options.iter().find(|opt| opt.code == OptionCode::ServerId)?;
let raw_server_id = BigEndian::read_u32(&server_id_opt.value);
Some(Ipv4Addr::from(raw_server_id))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::configuration::SubnetMask;
use crate::protocol::{ConfigOption, Message, MessageType, OpCode, OptionCode};
use std::convert::TryFrom;
use std::net::Ipv4Addr;
//use std::str::from_utf8;
fn new_test_server<F>(time_provider: F) -> Server<F>
where
F: Fn() -> i64,
{
let mut server = Server::new(time_provider);
server.config.server_ip = Ipv4Addr::new(192, 168, 1, 1);
server.config.default_lease_time = 100;
server.config.routers.push(Ipv4Addr::new(192, 168, 1, 1));
server
.config
.name_servers
.extend_from_slice(&vec![Ipv4Addr::new(8, 8, 8, 8), Ipv4Addr::new(8, 8, 4, 4)]);
server.pool.available_addrs.insert(Ipv4Addr::from([192, 168, 1, 2]));
server
}
fn new_test_discover() -> Message {
let mut disc = Message::new();
disc.xid = 42;
disc.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
disc.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPDISCOVER.into()],
});
disc
}
fn new_test_client_offer() -> Message {
let mut client_offer = Message::new();
client_offer.op = OpCode::BOOTREQUEST;
client_offer.xid = 42;
client_offer.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
client_offer.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER.into()],
});
// Skipping other settings as they are not needed
client_offer
}
fn new_test_offer() -> Message {
let mut offer = Message::new();
offer.op = OpCode::BOOTREPLY;
offer.xid = 42;
offer.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
offer.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
offer
.options
.push(ConfigOption { code: OptionCode::IpAddrLeaseTime, value: vec![0, 0, 0, 100] });
offer.options.push(ConfigOption {
code: OptionCode::SubnetMask,
value: SubnetMask::try_from(24).unwrap().octets().to_vec(),
});
offer.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER.into()],
});
offer
.options
.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
offer.options.push(ConfigOption { code: OptionCode::Router, value: vec![192, 168, 1, 1] });
offer.options.push(ConfigOption {
code: OptionCode::NameServer,
value: vec![8, 8, 8, 8, 8, 8, 4, 4],
});
offer
.options
.push(ConfigOption { code: OptionCode::RenewalTime, value: vec![0, 0, 0, 50] });
offer
.options
.push(ConfigOption { code: OptionCode::RebindingTime, value: vec![0, 0, 0, 25] });
offer
}
fn new_test_request() -> Message {
let mut req = Message::new();
req.xid = 42;
req.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
req.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 2] });
req.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPREQUEST.into()],
});
req.options.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
req
}
/*
fn new_test_request_with_no_requested_address() -> Message {
let mut req = Message::new();
req.xid = 42;
req.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
req.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPREQUEST.into()],
});
req.options.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
req
}
*/
fn new_test_client_ack() -> Message {
let mut client_ack = Message::new();
client_ack.op = OpCode::BOOTREQUEST;
client_ack.xid = 42;
client_ack.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
client_ack.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPACK.into()],
});
client_ack
// Skipping other option settings as they are not needed
}
fn new_test_ack() -> Message {
let mut ack = Message::new();
ack.op = OpCode::BOOTREPLY;
ack.xid = 42;
ack.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
ack.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
ack.options
.push(ConfigOption { code: OptionCode::IpAddrLeaseTime, value: vec![0, 0, 0, 100] });
ack.options.push(ConfigOption {
code: OptionCode::SubnetMask,
value: SubnetMask::try_from(24).unwrap().octets().to_vec(),
});
ack.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPACK.into()],
});
ack.options.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
ack.options.push(ConfigOption { code: OptionCode::Router, value: vec![192, 168, 1, 1] });
ack.options.push(ConfigOption {
code: OptionCode::NameServer,
value: vec![8, 8, 8, 8, 8, 8, 4, 4],
});
ack.options.push(ConfigOption { code: OptionCode::RenewalTime, value: vec![0, 0, 0, 50] });
ack.options
.push(ConfigOption { code: OptionCode::RebindingTime, value: vec![0, 0, 0, 25] });
ack
}
fn new_test_client_nak() -> Message {
let mut client_nak = Message::new();
client_nak.op = OpCode::BOOTREQUEST;
client_nak.xid = 42;
client_nak.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
client_nak.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPNAK.into()],
});
client_nak
}
fn new_test_nak() -> Message {
let mut nak = Message::new();
nak.op = OpCode::BOOTREPLY;
nak.xid = 42;
nak.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
nak.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPNAK.into()],
});
nak.options.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
nak
}
fn new_test_release() -> Message {
let mut release = Message::new();
release.xid = 42;
release.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
release.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
release.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPRELEASE.into()],
});
release
}
fn new_test_inform() -> Message {
let mut inform = Message::new();
inform.xid = 42;
inform.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
inform.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
inform.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPINFORM.into()],
});
inform
}
fn new_test_inform_ack() -> Message {
let mut ack = Message::new();
ack.op = OpCode::BOOTREPLY;
ack.xid = 42;
ack.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
ack.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
ack.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPINFORM.into()],
});
ack.options.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
ack.options.push(ConfigOption { code: OptionCode::Router, value: vec![192, 168, 1, 1] });
ack.options.push(ConfigOption {
code: OptionCode::NameServer,
value: vec![8, 8, 8, 8, 8, 8, 4, 4],
});
ack
}
fn new_test_decline() -> Message {
let mut decline = Message::new();
decline.xid = 42;
decline.chaddr = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
decline.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPDECLINE.into()],
});
decline
.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 2] });
decline
.options
.push(ConfigOption { code: OptionCode::ServerId, value: vec![192, 168, 1, 1] });
decline
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_returns_correct_response() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
let got = server.dispatch(disc).unwrap();
let want = new_test_offer();
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_updates_server_state() {
let disc = new_test_discover();
let mac_addr = disc.chaddr;
let mut server = new_test_server(|| 42);
let _ = server.dispatch(disc).unwrap();
assert_eq!(server.pool.available_addrs.len(), 0);
assert_eq!(server.pool.allocated_addrs.len(), 1);
assert_eq!(server.cache.len(), 1);
let want_config = server.cache.get(&mac_addr).unwrap();
assert_eq!(want_config.client_addr, Ipv4Addr::new(192, 168, 1, 2));
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_client_binding_returns_bound_addr() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
let client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.client_addr = client_addr;
server.pool.allocated_addrs.insert(client_addr);
server.cache.insert(disc.chaddr, client_config);
let got = server.dispatch(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 42);
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_client_binding_returns_error_when_addr_previously_not_bound() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
let client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.client_addr = client_addr;
server.cache.insert(disc.chaddr, client_config);
let got = server.dispatch(disc);
let want = Err(ServerError::ServerAddressPoolFailure(AddressPoolError::UnallocatedIpv4AddrRelease(client_addr)));
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_expired_client_binding_returns_available_old_addr() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expiration = 0;
server.cache.insert(disc.chaddr, client_config);
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.dispatch(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 42);
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_unavailable_expired_client_binding_returns_new_addr() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expiration = 0;
server.cache.insert(disc.chaddr, client_config);
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.dispatch(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_expired_client_binding_returns_available_requested_addr() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 2] });
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expiration = 0;
server.cache.insert(disc.chaddr, client_config);
let got = server.dispatch(disc).unwrap();
let want = new_test_offer();
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_expired_client_binding_returns_next_addr_for_unavailable_requested_addr() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 3] });
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expiration = 0;
server.cache.insert(disc.chaddr, client_config);
let got = server.dispatch(disc).unwrap();
let want = new_test_offer();
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_expired_client_binding_returns_next_addr_for_bad_requested_addr() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1] });
let mut server = new_test_server(|| 42);
let mut client_config = CachedConfig::default();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expiration = 0;
server.cache.insert(disc.chaddr, client_config);
let got = server.dispatch(disc).unwrap();
let want = new_test_offer();
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_available_requested_addr_returns_requested_addr() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 3] });
let mut server = new_test_server(|| 42);
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 3));
let got = server.dispatch(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 3);
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_unavailable_requested_addr_returns_next_addr() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 42] });
let mut server = new_test_server(|| 42);
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.pool.available_addrs.insert(Ipv4Addr::new(192, 168, 1, 3));
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.dispatch(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_unavailable_requested_addr_no_available_addr_returns_error() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 42] });
let mut server = new_test_server(|| 42);
server.pool.available_addrs.clear();
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.dispatch(disc);
let want : Result<Message, ServerError> = Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion));
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_discover_no_requested_addr_no_available_addr_returns_error() {
let disc = new_test_discover();
let mut server = new_test_server(|| 42);
server.pool.available_addrs.clear();
let got = server.dispatch(disc);
let want : Result<Message, ServerError> = Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion));
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_client_offer_message_returns_error() {
let client_offer = new_test_client_offer();
let mut server = new_test_server(|| 42);
let got = server.dispatch(client_offer);
let want : Result<Message, ServerError> = Err(ServerError::InvalidClientMessage(MessageType::DHCPOFFER));
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_client_ack_message_returns_error() {
let client_ack = new_test_client_ack();
let mut server = new_test_server(|| 42);
let got = server.dispatch(client_ack);
let want : Result<Message, ServerError> = Err(ServerError::InvalidClientMessage(MessageType::DHCPACK));
assert_eq!(got, want);
}
#[test]
//TODO: Done
fn test_dispatch_with_client_nak_message_returns_error() {
let client_nak = new_test_client_nak();
let mut server = new_test_server(|| 42);
let got = server.dispatch(client_nak);
let want : Result<Message, ServerError> = Err(ServerError::InvalidClientMessage(MessageType::DHCPNAK));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_valid_selecting_request_returns_ack() {
let mut req = new_test_request();
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
req.ciaddr = requested_ip_addr;
req.options.remove(0);
let mut server = new_test_server(|| 42);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
let got = server.dispatch(req).unwrap();
let mut want = new_test_ack();
want.ciaddr = requested_ip_addr;
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_wrong_server_id_returns_error() {
let mut req = new_test_request();
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
req.ciaddr = requested_ip_addr;
req.options.remove(0);
let mut server = new_test_server(|| 42);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
server.config.server_ip = Ipv4Addr::new(1, 2, 3, 4);
let got = server.dispatch(req);
let want = Err(ServerError::UnwantedDHCPServer);
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_mismatched_requested_addr_returns_error() {
let mut req = new_test_request();
req.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
req.options.remove(0);
let mut server = new_test_server(|| 42);
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 3);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
let got = server.dispatch(req);
let want = Err(ServerError::RequestedAddrAssignmentFailure("Requested Ipv4 address does not match initial address requested in discovery".to_owned()));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_expired_config_returns_error() {
let mut req = new_test_request();
req.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
req.options.remove(0);
let mut server = new_test_server(|| 42);
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: 0,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
let got = server.dispatch(req);
let want = Err(ServerError::RequestedAddrAssignmentFailure("Client Config has expired".to_owned()));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_failed_addr_allocation_returns_error() {
let mut req = new_test_request();
req.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
req.options.remove(0);
let mut server = new_test_server(|| 42);
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let got = server.dispatch(req);
let want = Err(ServerError::RequestedAddrAssignmentFailure("Server failed to reserve requested Ipv4 adddress".to_owned()));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_no_client_config_returns_error() {
let mut req = new_test_request();
req.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
req.options.remove(0);
let mut server = new_test_server(|| 42);
let got = server.dispatch(req);
let want = Err(ServerError::RequestedAddrAssignmentFailure("Could not retrieve client config".to_owned()));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_selecting_request_valid_selecting_request_maintains_server_invariants() {
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
let mut req = new_test_request();
req.ciaddr = requested_ip_addr;
req.options.remove(0);
let mut server = new_test_server(|| 42);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
let _ = server.dispatch(req.clone()).unwrap();
assert!(server.cache.contains_key(&req.chaddr));
assert!(server.pool.addr_is_allocated(requested_ip_addr));
}
#[test]
fn test_dispatch_with_selecting_request_no_address_allocation_maintains_server_invariants() {
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
let mut req = new_test_request();
req.ciaddr = requested_ip_addr;
req.options.remove(0);
let mut server = new_test_server(|| 42);
let _ = server.dispatch(req.clone());
assert!(!server.cache.contains_key(&req.chaddr));
assert!(!server.pool.addr_is_allocated(Ipv4Addr::new(192, 168, 1, 2)));
}
#[test]
fn test_dispatch_with_init_boot_request_correct_address_returns_ack() {
let mut req = new_test_request();
req.options.remove(2);
let requested_ip_addr = get_requested_ip_addr(&req).unwrap();
let mut server = new_test_server(|| 42);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expiration: std::i64::MAX,
},
);
let _ = server.pool.allocate_addr(requested_ip_addr);
let got = server.dispatch(req).unwrap();
let want = new_test_ack();
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_init_boot_request_incorrect_address_returns_nak() {
let mut req = new_test_request();
req.options.remove(0);
req.options.remove(1);
req.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![192, 168, 1, 42] });
let mut server = new_test_server(|| 42);
let assigned_ip = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig { client_addr: assigned_ip, options: vec![], expiration: std::i64::MAX },
);
let _ = server.pool.allocate_addr(assigned_ip);
let got = server.dispatch(req).unwrap();
let want = new_test_nak();
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_init_boot_request_unknown_client_returns_error() {
let mut req = new_test_request();
req.options.remove(2);
let mut server = new_test_server(|| 42);
let got = server.dispatch(req);
let want = Err(ServerError::UnknownClientMac);
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_init_boot_request_client_on_wrong_subnet_returns_nak() {
let mut req = new_test_request();
req.options.remove(0);
req.options.remove(1);
req.options
.push(ConfigOption { code: OptionCode::RequestedIpAddr, value: vec![10, 0, 0, 1] });
let mut server = new_test_server(|| 42);
let got = server.dispatch(req).unwrap();
let want = new_test_nak();
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_renewing_request_valid_request_returns_ack() {
let mut req = new_test_request();
req.options.remove(0);
req.options.remove(1);
let client_ip = Ipv4Addr::new(192, 168, 1, 2);
req.ciaddr = client_ip;
let mut server = new_test_server(|| 42);
server.cache.insert(
req.chaddr,
CachedConfig { client_addr: client_ip, options: vec![], expiration: std::i64::MAX },
);
let _ = server.pool.allocate_addr(client_ip);
let got = server.dispatch(req).unwrap();
let mut want = new_test_ack();
want.ciaddr = client_ip;
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_renewing_request_unknown_client_returns_error() {
let mut req = new_test_request();
req.options.remove(0);
req.options.remove(1);
let client_ip = Ipv4Addr::new(192, 168, 1, 2);
req.ciaddr = client_ip;
let mut server = new_test_server(|| 42);
let got = server.dispatch(req);
let want = Err(ServerError::RequestedAddrAssignmentFailure("Could not retrieve client config".to_owned()));
assert_eq!(got, want);
}
#[test]
fn test_dispatch_with_unknown_client_state_returns_error() {
let mut req = new_test_request();
req.options.remove(0);
req.options.remove(1);
let mut server = new_test_server(|| 42);
let got = server.dispatch(req);
let want = Err(ServerError::UnknownClientStatAtReboot);
assert_eq!(got, want);
}
#[test]
fn test_get_client_state_with_selecting_returns_selecting() {
let mut msg = new_test_request();
msg.ciaddr = Ipv4Addr::new(192, 168, 1, 2);
msg.options.remove(0);
let got = get_client_state(&msg);
assert_eq!(got, ClientState::Selecting);
}
#[test]
fn test_get_client_state_with_initreboot_returns_initreboot() {
let mut msg = new_test_request();
msg.options.remove(2);
let got = get_client_state(&msg);
assert_eq!(got, ClientState::InitReboot);
}
#[test]
fn test_get_client_state_with_renewing_returns_renewing() {
let mut msg = new_test_request();
msg.options.remove(0);
msg.options.remove(1);
msg.ciaddr = Ipv4Addr::new(1, 2, 3, 4);
let got = get_client_state(&msg);
assert_eq!(got, ClientState::Renewing);
}
#[test]
fn test_get_client_state_with_unknown_returns_unknown() {
let mut msg = new_test_request();
msg.options.clear();
let got = get_client_state(&msg);
assert_eq!(got, ClientState::Unknown);
}
#[test]
fn test_dispatch_with_unknown_client_msg_returns_error() {
let mut msg = new_test_request();
msg.options.clear();
let mut server = new_test_server(|| 42);
let got = server.dispatch(msg);
let want = Err(ServerError::UnknownClientRequest);
assert_eq!(got, want);
}
#[test]
fn test_release_expired_leases_with_none_expired_releases_none() {
let mut server = new_test_server(|| 42);
server.pool.available_addrs.clear();
server.cache.insert(
MacAddr { octets: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 2),
options: vec![],
expiration: std::i64::MAX,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.cache.insert(
MacAddr { octets: [0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 3),
options: vec![],
expiration: std::i64::MAX,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 3));
server.cache.insert(
MacAddr { octets: [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 4),
options: vec![],
expiration: std::i64::MAX,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 4));
server.release_expired_leases();
assert_eq!(server.cache.len(), 3);
assert_eq!(server.pool.available_addrs.len(), 0);
assert_eq!(server.pool.allocated_addrs.len(), 3);
}
#[test]
fn test_release_expired_leases_with_all_expired_releases_all() {
let mut server = new_test_server(|| 42);
server.pool.available_addrs.clear();
server.cache.insert(
MacAddr { octets: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 2),
options: vec![],
expiration: 0,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.cache.insert(
MacAddr { octets: [0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 3),
options: vec![],
expiration: 0,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 3));
server.cache.insert(
MacAddr { octets: [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 4),
options: vec![],
expiration: 0,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 4));
server.release_expired_leases();
assert_eq!(server.cache.len(), 0);
assert_eq!(server.pool.available_addrs.len(), 3);
assert_eq!(server.pool.allocated_addrs.len(), 0);
}
#[test]
fn test_release_expired_leases_with_some_expired_releases_expired() {
let mut server = new_test_server(|| 42);
server.pool.available_addrs.clear();
server.cache.insert(
MacAddr { octets: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 2),
options: vec![],
expiration: std::i64::MAX,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 2));
server.cache.insert(
MacAddr { octets: [0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 3),
options: vec![],
expiration: 0,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 3));
server.cache.insert(
MacAddr { octets: [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] },
CachedConfig {
client_addr: Ipv4Addr::new(192, 168, 1, 4),
options: vec![],
expiration: std::i64::MAX,
},
);
server.pool.allocated_addrs.insert(Ipv4Addr::new(192, 168, 1, 4));
server.release_expired_leases();
assert_eq!(server.cache.len(), 2);
assert!(!server
.cache
.contains_key(&MacAddr { octets: [0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB] }));
assert_eq!(server.pool.available_addrs.len(), 1);
assert_eq!(server.pool.allocated_addrs.len(), 2);
}
#[test]
fn test_dispatch_with_known_release() {
let release = new_test_release();
let mut server = new_test_server(|| 42);
let client_ip = Ipv4Addr::new(192, 168, 1, 2);
let _ = server.pool.allocate_addr(client_ip);
let client_mac = MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] };
let client_config =
CachedConfig { client_addr: client_ip, options: vec![], expiration: std::i64::MAX };
server.cache.insert(client_mac, client_config.clone());
let got = server.dispatch(release);
assert!(got.is_err(), "server returned a Message value");
assert!(!server.pool.addr_is_allocated(client_ip), "server did not free client address");
assert!(server.pool.addr_is_available(client_ip), "server did not free client address");
assert!(
server.cache.contains_key(&client_mac),
"server did not retain cached client settings"
);
assert_eq!(
server.cache.get(&client_mac).unwrap(),
&client_config,
"server did not retain cached client settings"
);
}
#[test]
fn test_dispatch_with_unknown_release() {
let release = new_test_release();
let mut server = new_test_server(|| 42);
let client_ip = Ipv4Addr::new(192, 168, 1, 2);
let _ = server.pool.allocate_addr(client_ip);
let cached_mac = MacAddr { octets: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] };
let client_config =
CachedConfig { client_addr: client_ip, options: vec![], expiration: std::i64::MAX };
server.cache.insert(cached_mac, client_config.clone());
let got = server.dispatch(release);
assert!(got.is_err(), "server returned a Message value");
assert!(server.pool.addr_is_allocated(client_ip), "server did not free client address");
assert!(!server.pool.addr_is_available(client_ip), "server did not free client address");
assert!(
server.cache.contains_key(&cached_mac),
"server did not retain cached client settings"
);
assert_eq!(
server.cache.get(&cached_mac).unwrap(),
&client_config,
"server did not retain cached client settings"
);
}
#[test]
fn test_dispatch_with_inform_returns_ack() {
let inform = new_test_inform();
let mut server = new_test_server(|| 42);
let got = server.dispatch(inform).unwrap();
let want = new_test_inform_ack();
assert_eq!(got, want, "expected: {:?}\ngot: {:?}", want, got);
}
#[test]
fn test_dispatch_with_decline_marks_addr_allocated() {
let decline = new_test_decline();
let mut server = new_test_server(|| 42);
let already_used_ip = Ipv4Addr::new(192, 168, 1, 2);
server.config.managed_addrs.push(already_used_ip);
let client_config = CachedConfig {
client_addr: already_used_ip,
options: vec![],
expiration: std::i64::MAX,
};
server.cache.insert(decline.chaddr, client_config);
let got = server.dispatch(decline);
assert!(got.is_err(), "server returned a Message value");
assert!(!server.pool.addr_is_available(already_used_ip), "addr still marked available");
assert!(server.pool.addr_is_allocated(already_used_ip), "addr not marked allocated");
assert!(
!server.cache.contains_key(&MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] }),
"client config retained"
);
}
#[test]
fn test_client_requested_lease_time() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::IpAddrLeaseTime, value: vec![0, 0, 0, 20] });
let mut server = new_test_server(|| 42);
let result = server.dispatch(disc).unwrap();
assert_eq!(
BigEndian::read_u32(
&result.get_config_option(OptionCode::IpAddrLeaseTime).unwrap().value
),
20
);
let cached_config =
server.cache.get(&MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] }).unwrap();
assert_eq!(cached_config.expiration, 42 + 20);
}
#[test]
fn test_client_requested_lease_time_greater_than_max() {
let mut disc = new_test_discover();
disc.options
.push(ConfigOption { code: OptionCode::IpAddrLeaseTime, value: vec![0, 0, 0, 20] });
let mut server = new_test_server(|| 42);
server.config.max_lease_time_s = 10;
let result = server.dispatch(disc).unwrap();
assert_eq!(
BigEndian::read_u32(
&result.get_config_option(OptionCode::IpAddrLeaseTime).unwrap().value
),
10
);
let cached_config =
server.cache.get(&MacAddr { octets: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] }).unwrap();
assert_eq!(cached_config.expiration, 42 + 10);
}
/* #[test]
fn test_add_msg_nak() {
let mut nak = new_test_nak();
let s = String::from("hello world");
add_request_nak_message(&mut nak, s);
println!("This is NAK - {:?}", nak);
let v = nak.get_config_option(OptionCode::Message).unwrap();
let s2 = v.to_owned().value;
let str = from_utf8(&s2);
println!("This is the text - {:?}", str)
}*/
}