blob: 0071cda813b97a9281c7e457ba321253d14e264a [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 {
client_configs_cache: HashMap<MacAddr, CachedConfig>,
addr_pool: AddressPool,
server_config: ServerConfig,
}
impl Server {
/// Returns an initialized `Server` value.
pub fn new() -> Self {
Server {
client_configs_cache: HashMap::new(),
addr_pool: AddressPool::new(),
server_config: ServerConfig::new(),
}
}
fn handle_discover_message(&mut self, disc_msg: Message) -> Option<Message> {
let offer_msg = self.generate_offer_message(disc_msg)?;
self.update_server_cache(Ipv4Addr::from(offer_msg.yiaddr), offer_msg.chaddr, vec![]);
Some(offer_msg)
}
fn generate_offer_message(&mut self, client_msg: Message) -> Option<Message> {
let mut offer_msg = client_msg.clone();
offer_msg.op = OpCode::BOOTREPLY;
offer_msg.secs = 0;
offer_msg.ciaddr = Ipv4Addr::new(0, 0, 0, 0);
offer_msg.siaddr = Ipv4Addr::new(0, 0, 0, 0);
offer_msg.sname = String::new();
offer_msg.file = String::new();
self.add_required_options_to(&mut offer_msg);
offer_msg.yiaddr = self.get_addr_for_msg(client_msg)?;
Some(offer_msg)
}
fn add_required_options_to(&self, offer_msg: &mut Message) {
offer_msg.options.clear();
let mut lease = vec![0; 4];
BigEndian::write_u32(&mut lease, self.server_config.default_lease_time);
offer_msg.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: lease,
});
offer_msg.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER as u8],
});
offer_msg.options.push(ConfigOption {
code: OptionCode::ServerId,
value: self.server_config.server_ip.octets().to_vec(),
});
}
fn get_addr_for_msg(&mut self, client_msg: Message) -> Option<Ipv4Addr> {
if let Some(config) = self.client_configs_cache.get(&client_msg.chaddr) {
if !config.expired {
return Some(config.client_addr);
} else if self.addr_pool.addr_is_available(config.client_addr) {
self.addr_pool.allocate_addr(config.client_addr);
return Some(config.client_addr);
}
}
if let Some(opt) = client_msg.get_config_option_with(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.addr_pool.addr_is_available(requested_addr) {
self.addr_pool.allocate_addr(requested_addr);
return Some(requested_addr);
}
}
}
self.addr_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.client_configs_cache.insert(client_mac, config);
self.addr_pool.allocate_addr(client_addr);
}
}
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,
}
}
}
#[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)
}
}
#[derive(Debug)]
struct ServerConfig {
server_ip: Ipv4Addr,
default_lease_time: u32,
}
impl ServerConfig {
fn new() -> Self {
ServerConfig {
server_ip: Ipv4Addr::new(0, 0, 0, 0),
default_lease_time: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::{CachedConfig, Server};
use protocol::{ConfigOption, Message, MessageType, OpCode, OptionCode};
use std::net::Ipv4Addr;
fn new_test_client_msg() -> Message {
let mut client_msg = Message::new();
client_msg.xid = 42;
client_msg.chaddr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
client_msg
}
fn new_test_server() -> Server {
let mut server = Server::new();
server.server_config.server_ip = Ipv4Addr::new(192, 168, 1, 1);
server.server_config.default_lease_time = 42;
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::from([192, 168, 1, 2]));
server
}
fn new_test_server_msg() -> Message {
let mut server_msg = Message::new();
server_msg.op = OpCode::BOOTREPLY;
server_msg.xid = 42;
server_msg.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
server_msg.chaddr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
server_msg.options.push(ConfigOption {
code: OptionCode::IpAddrLeaseTime,
value: vec![0, 0, 0, 42],
});
server_msg.options.push(ConfigOption {
code: OptionCode::DhcpMessageType,
value: vec![MessageType::DHCPOFFER as u8],
});
server_msg.options.push(ConfigOption {
code: OptionCode::ServerId,
value: vec![192, 168, 1, 1],
});
server_msg
}
#[test]
fn test_handle_discover_returns_correct_response() {
let disc_msg = new_test_client_msg();
let mut server = new_test_server();
let got = server.handle_discover_message(disc_msg).unwrap();
let want = new_test_server_msg();
assert_eq!(got, want);
}
#[test]
fn test_handle_discover_updates_server_state() {
let disc_msg = new_test_client_msg();
let mac_addr = disc_msg.chaddr;
let mut server = new_test_server();
let got = server.handle_discover_message(disc_msg).unwrap();
assert_eq!(server.addr_pool.available_addrs.len(), 0);
assert_eq!(server.addr_pool.allocated_addrs.len(), 1);
assert_eq!(server.client_configs_cache.len(), 1);
let want_config = server.client_configs_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_msg = new_test_client_msg();
let mut server = new_test_server();
let mut client_config = CachedConfig::new();
client_config.client_addr = Ipv4Addr::new(192, 168, 1, 42);
server
.client_configs_cache
.insert(disc_msg.chaddr, client_config);
let got = server.handle_discover_message(disc_msg).unwrap();
let mut want = new_test_server_msg();
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_msg = new_test_client_msg();
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
.client_configs_cache
.insert(disc_msg.chaddr, client_config);
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.handle_discover_message(disc_msg).unwrap();
let mut want = new_test_server_msg();
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_msg = new_test_client_msg();
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
.client_configs_cache
.insert(disc_msg.chaddr, client_config);
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 2));
server
.addr_pool
.allocated_addrs
.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.handle_discover_message(disc_msg).unwrap();
let mut want = new_test_server_msg();
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_msg = new_test_client_msg();
disc_msg.options.push(ConfigOption {
code: OptionCode::RequestedIpAddr,
value: vec![192, 168, 1, 3],
});
let mut server = new_test_server();
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 2));
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 3));
let got = server.handle_discover_message(disc_msg).unwrap();
let mut want = new_test_server_msg();
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_msg = new_test_client_msg();
disc_msg.options.push(ConfigOption {
code: OptionCode::RequestedIpAddr,
value: vec![192, 168, 1, 42],
});
let mut server = new_test_server();
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 2));
server
.addr_pool
.available_addrs
.insert(Ipv4Addr::new(192, 168, 1, 3));
server
.addr_pool
.allocated_addrs
.insert(Ipv4Addr::new(192, 168, 1, 42));
let got = server.handle_discover_message(disc_msg).unwrap();
let mut want = new_test_server_msg();
want.yiaddr = Ipv4Addr::new(192, 168, 1, 2);
assert_eq!(got, want);
}
}