blob: 67dbab8886de6e1da73628c1cfc7cb56c883e39e [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.
#![deny(warnings)]
#![allow(unused)] // TODO(atait): Remove once there are non-test clients
use byteorder::{BigEndian, ByteOrder};
use protocol;
use protocol::{ConfigOption, Message, MessageType, OpCode, OptionCode};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::net::Ipv4Addr;
/// A minimal DHCP server.
///
/// This comment will be expanded upon in future CLs as the server design
/// is iterated upon.
pub struct Server {
cache: CachedClients,
pool: AddressPool,
config: ServerConfig,
}
impl Server {
/// Returns an initialized `Server` value.
pub fn new() -> Self {
Server {
cache: HashMap::new(),
pool: AddressPool::new(),
config: ServerConfig::new(),
}
}
fn handle_discover(&mut self, disc: Message) -> Option<Message> {
let offered_ip = self.get_addr(&disc)?;
let mut offer = build_offer(disc, &self.config);
offer.yiaddr = offered_ip;
self.update_server_cache(Ipv4Addr::from(offer.yiaddr), offer.chaddr, vec![]);
Some(offer)
}
fn get_addr(&mut self, client: &Message) -> Option<Ipv4Addr> {
if let Some(config) = self.cache.get(&client.chaddr) {
if !config.expired {
return Some(config.client_addr);
} else if self.pool.addr_is_available(config.client_addr) {
self.pool.allocate_addr(config.client_addr);
return Some(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)
.expect("out of range indexing on opt.value");
if self.pool.addr_is_available(requested_addr) {
self.pool.allocate_addr(requested_addr);
return Some(requested_addr);
}
}
}
self.pool.get_next_available_addr()
}
fn update_server_cache(
&mut self, client_addr: Ipv4Addr, client_mac: MacAddr, client_opts: Vec<ConfigOption>,
) {
let config = CachedConfig {
client_addr: client_addr,
options: client_opts,
expired: false,
};
self.cache.insert(client_mac, config);
self.pool.allocate_addr(client_addr);
}
fn handle_request(&mut self, req: Message) -> Option<Message> {
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 => None,
}
}
fn handle_request_selecting(&mut self, req: Message) -> Option<Message> {
let requested_ip = get_requested_ip_addr(&req)?;
if !is_recipient(self.config.server_ip, &req)
|| !is_assigned(&req, requested_ip, &self.cache, &self.pool)
{
return None;
}
Some(build_ack(req, requested_ip, &self.config))
}
fn handle_request_init_reboot(&mut self, req: Message) -> Option<Message> {
let requested_ip = get_requested_ip_addr(&req)?;
if !is_in_subnet(requested_ip, &self.config) {
return Some(build_nak(req, &self.config));
}
if !is_client_mac_known(req.chaddr, &self.cache) {
return None;
}
if !is_assigned(&req, requested_ip, &self.cache, &self.pool) {
return Some(build_nak(req, &self.config));
}
Some(build_ack(req, requested_ip, &self.config))
}
fn handle_request_renewing(&mut self, req: Message) -> Option<Message> {
let client_ip = req.ciaddr;
if !is_assigned(&req, client_ip, &self.cache, &self.pool) {
return None;
}
Some(build_ack(req, client_ip, &self.config))
}
}
/// 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>;
type MacAddr = [u8; 6];
#[derive(Debug)]
struct CachedConfig {
client_addr: Ipv4Addr,
options: Vec<ConfigOption>,
expired: bool,
}
impl CachedConfig {
fn new() -> Self {
CachedConfig {
client_addr: Ipv4Addr::new(0, 0, 0, 0),
options: vec![],
expired: false,
}
}
}
/// 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>,
}
impl AddressPool {
fn new() -> Self {
AddressPool {
available_addrs: BTreeSet::new(),
allocated_addrs: HashSet::new(),
}
}
fn get_next_available_addr(&self) -> Option<Ipv4Addr> {
let mut iter = self.available_addrs.iter();
match iter.next() {
Some(addr) => Some(*addr),
None => None,
}
}
fn allocate_addr(&mut self, addr: Ipv4Addr) {
self.available_addrs.remove(&addr);
self.allocated_addrs.insert(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(Debug)]
struct ServerConfig {
server_ip: Ipv4Addr,
default_lease_time: u32,
subnet_mask: u8,
}
impl ServerConfig {
fn new() -> Self {
ServerConfig {
server_ip: Ipv4Addr::new(0, 0, 0, 0),
default_lease_time: 0,
subnet_mask: 24,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ClientState {
Unknown,
Selecting,
InitReboot,
Renewing,
}
fn build_offer(client: Message, config: &ServerConfig) -> 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);
offer
}
fn add_required_options(offer: &mut Message, config: &ServerConfig) {
offer.options.clear();
let mut lease = vec![0; 4];
BigEndian::write_u32(&mut lease, config.default_lease_time);
offer.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: lease,
});
offer.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER as u8],
});
offer.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
}
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 is_assigned(
req: &Message, requested_ip: Ipv4Addr, cache: &CachedClients, pool: &AddressPool,
) -> bool {
if let Some(client_config) = cache.get(&req.chaddr) {
return client_config.client_addr == requested_ip && !client_config.expired
&& pool.addr_is_allocated(requested_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();
let mut lease = vec![0; 4];
BigEndian::write_u32(&mut lease, config.default_lease_time);
ack.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: lease,
});
ack.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPACK as u8],
});
ack.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
ack
}
fn is_in_subnet(ip: Ipv4Addr, config: &ServerConfig) -> bool {
apply_subnet_mask_to(config.subnet_mask, ip)
== apply_subnet_mask_to(config.subnet_mask, 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 as u8],
});
nak.options.push(ConfigOption {
code: OptionCode::ServerId,
value: config.server_ip.octets().to_vec(),
});
nak
}
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_some() && 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))
}
fn apply_subnet_mask_to(prefix_len: u8, ip_addr: Ipv4Addr) -> Ipv4Addr {
assert!(prefix_len < 32);
let subnet_mask_bits = ::std::u32::MAX << (32 - prefix_len);
let ip_addr_bits = BigEndian::read_u32(&ip_addr.octets());
Ipv4Addr::from(ip_addr_bits & subnet_mask_bits)
}
#[cfg(test)]
mod tests {
use super::*;
use protocol::{ConfigOption, Message, MessageType, OpCode, OptionCode};
use std::net::Ipv4Addr;
fn new_test_server() -> Server {
let mut server = Server::new();
server.config.server_ip = Ipv4Addr::new(192, 168, 1, 1);
server.config.default_lease_time = 42;
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 = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
disc
}
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 = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
offer.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: vec![0, 0, 0, 42],
});
offer.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER as u8],
});
offer.options.push(ConfigOption {
code: OptionCode::ServerId,
value: vec![192, 168, 1, 1],
});
offer
}
fn new_test_request() -> Message {
let mut req = Message::new();
req.xid = 42;
req.chaddr = [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 as u8],
});
req.options.push(ConfigOption {
code: OptionCode::ServerId,
value: vec![192, 168, 1, 1],
});
req
}
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 = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
ack.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: vec![0, 0, 0, 42],
});
ack.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPACK as u8],
});
ack.options.push(ConfigOption {
code: OptionCode::ServerId,
value: vec![192, 168, 1, 1],
});
ack
}
fn new_test_nak() -> Message {
let mut nak = Message::new();
nak.op = OpCode::BOOTREPLY;
nak.xid = 42;
nak.chaddr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
nak.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPNAK as u8],
});
nak.options.push(ConfigOption {
code: OptionCode::ServerId,
value: vec![192, 168, 1, 1],
});
nak
}
#[test]
fn test_handle_discover_returns_correct_response() {
let disc = new_test_discover();
let mut server = new_test_server();
let got = server.handle_discover(disc).unwrap();
let want = new_test_offer();
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_updates_server_state() {
let disc = new_test_discover();
let mac_addr = disc.chaddr;
let mut server = new_test_server();
let got = server.handle_discover(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]
fn test_handle_discover_with_client_binding_returns_bound_addr() {
let disc = new_test_discover();
let mut server = new_test_server();
let mut client_config = CachedConfig::new();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
server.cache.insert(disc.chaddr, client_config);
let got = server.handle_discover(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 42);
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_with_expired_client_binding_returns_available_old_addr() {
let disc = new_test_discover();
let mut server = new_test_server();
let mut client_config = CachedConfig::new();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expired = true;
server.cache.insert(disc.chaddr, client_config);
server
.pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.handle_discover(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 42);
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_with_unavailable_expired_client_binding_returns_new_addr() {
let disc = new_test_discover();
let mut server = new_test_server();
let mut client_config = CachedConfig::new();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
client_config.expired = true;
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.handle_discover(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_with_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();
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.handle_discover(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 3);
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_with_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();
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.handle_discover(disc).unwrap();
let mut want = new_test_offer();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
assert_eq!(got, want);
}
#[test]
fn test_handle_selecting_request_with_valid_selecting_request_returns_ack() {
let req = new_test_request();
let mut server = new_test_server();
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(requested_ip_addr);
let got = server.handle_request(req).unwrap();
let mut want = new_test_ack();
assert_eq!(got, want);
}
#[test]
fn test_handle_selecting_request_with_no_address_allocation_to_client_returns_none() {
let req = new_test_request();
let mut server = new_test_server();
let got = server.handle_request(req);
assert!(got.is_none());
}
#[test]
fn test_handle_selecting_request_with_wrong_server_id_returns_none() {
let req = new_test_request();
let mut server = new_test_server();
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(requested_ip_addr);
server.config.server_ip = Ipv4Addr::new(1, 2, 3, 4);
let got = server.handle_request(req);
assert!(got.is_none());
}
#[test]
fn test_handle_selecting_request_with_valid_selecting_request_maintains_server_invariants() {
let req = new_test_request();
let mut server = new_test_server();
let requested_ip_addr = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(requested_ip_addr);
let _ = server.handle_request(req.clone()).unwrap();
assert!(server.cache.contains_key(&req.chaddr));
assert!(server.pool.addr_is_allocated(requested_ip_addr));
}
#[test]
fn test_handle_selecting_request_with_no_address_allocation_maintains_server_invariants() {
let req = new_test_request();
let mut server = new_test_server();
let _ = server.handle_request(req.clone());
assert!(!server.cache.contains_key(&req.chaddr));
assert!(!server.pool.addr_is_allocated(Ipv4Addr::new(192, 168, 1, 2)));
}
#[test]
fn test_handle_init_reboot_request_with_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();
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: requested_ip_addr,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(requested_ip_addr);
let got = server.handle_request(req).unwrap();
let want = new_test_ack();
assert_eq!(got, want);
}
#[test]
fn test_handle_init_reboot_request_with_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();
let assigned_ip = Ipv4Addr::new(192, 168, 1, 2);
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: assigned_ip,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(assigned_ip);
let got = server.handle_request(req).unwrap();
let want = new_test_nak();
assert_eq!(got, want);
}
#[test]
fn test_handle_init_reboot_request_with_unknown_client_returns_none() {
let mut req = new_test_request();
req.options.remove(2);
let mut server = new_test_server();
let got = server.handle_request(req);
assert!(got.is_none());
}
#[test]
fn test_handle_init_reboot_request_with_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();
let got = server.handle_request(req).unwrap();
let want = new_test_nak();
assert_eq!(got, want);
}
#[test]
fn test_handle_renewing_request_with_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();
server.cache.insert(
req.chaddr,
CachedConfig {
client_addr: client_ip,
options: vec![],
expired: false,
},
);
server.pool.allocate_addr(client_ip);
let got = server.handle_request(req).unwrap();
let mut want = new_test_ack();
want.ciaddr = client_ip;
assert_eq!(got, want);
}
#[test]
fn test_handle_renewing_request_with_unknown_client_returns_none() {
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();
let got = server.handle_request(req);
assert!(got.is_none());
}
#[test]
fn test_get_client_state_with_selecting_returns_selecting() {
let msg = new_test_request();
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);
}
}