toe-beans 0.10.0

DHCP library, client, and server
Documentation
#![allow(missing_docs)]

// Using the cargo alias `cargo tests` will apply the following:
// - `--nocapture` to show hidden debug logs
// - `--test-threads=1` to prevent socket binding side effects
// - `--features integration` to disable the leases file because files are a side effect between tests
use mac_address::get_mac_address;
use std::sync::Once;
use std::{net::Ipv4Addr, thread};
use toe_beans::v4::*;

static INIT: Once = Once::new();

pub fn initialize() {
    INIT.call_once(|| {
        let _ = env_logger::builder()
            .is_test(true)
            .filter_level(log::LevelFilter::Debug)
            .try_init();
    });
}

fn get_server_config() -> Config {
    Config {
        interface: Some("lo".to_string()),
        ..Config::default()
    }
}

fn get_client_config() -> ClientConfig {
    ClientConfig {
        interface: Some("lo".to_string()),
        ..ClientConfig::default()
    }
}

#[test]
fn discover_offer() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8067".parse().unwrap();
    let mut server = Server::new(config);

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let message = client.discover();

    client.socket.broadcast(&message, 8067).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Offer) = message_type {
    } else {
        panic!("Discover did not receive Offer");
    }
}

#[test]
fn rapid_commit() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8167".parse().unwrap();
    let mut server = Server::new(config);

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let mut message = client.discover();
    message.add_option(MessageOptions::RapidCommit);
    message.add_option(MessageOptions::RequestedIp(AddressOption::new(
        Ipv4Addr::UNSPECIFIED,
    ))); // TODO
    client.socket.broadcast(&message, 8167).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}

#[test]
fn request_ack() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8267".parse().unwrap();
    let mut server = Server::new(config);

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let discover_message = client.discover();
    let offer_message = server.offer(discover_message).unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let message = client.request(offer_message);
    client.socket.broadcast(&message, 8267).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Request did not receive Ack");
    }
}

#[test]
fn ignores_requests_to_other_servers() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8367".parse().unwrap();
    let mut server = Server::new(config);

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let discover_message = client.discover();
    let offer_message = server.offer(discover_message).unwrap();
    let offered_ip = offer_message.yiaddr;

    assert!(!server.leases.is_available(&offered_ip));

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
        assert!(server.leases.is_available(&offered_ip));
    });

    let mut message = client.request(offer_message);
    // fake choosing a different dhcp server
    message.options.set(
        2,
        MessageOptions::ServerIdentifier(AddressOption::new(Ipv4Addr::new(127, 0, 0, 2))),
    );

    client.socket.broadcast(&message, 8367).unwrap();

    handle.join().unwrap();
}

#[test]
fn verify_existing_lease() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8467".parse().unwrap();
    let mut server = Server::new(config);

    let ip = server
        .leases
        .ack(get_mac_address().unwrap().unwrap())
        .unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let verify_message = client.verify(ip);
    client.socket.broadcast(&verify_message, 8467).unwrap();

    handle.join().unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");
    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}

#[test]
fn extend_existing_lease() {
    initialize();

    let mut config = get_server_config();
    config.listen_address = "0.0.0.0:8567".parse().unwrap();
    let mut server = Server::new(config);

    let ip = server
        .leases
        .ack(get_mac_address().unwrap().unwrap())
        .unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(&client_config);
    let verify_message = client.extend(ip);
    client.socket.broadcast(&verify_message, 8567).unwrap();

    handle.join().unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");
    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}