1048 lines
44 KiB
Rust
1048 lines
44 KiB
Rust
// See https://tools.ietf.org/html/rfc2131 for the DHCP specification.
|
|
|
|
use byteorder::{ByteOrder, NetworkEndian};
|
|
|
|
use crate::{Error, Result};
|
|
use crate::wire::{EthernetAddress, Ipv4Address};
|
|
use crate::wire::arp::Hardware;
|
|
|
|
const DHCP_MAGIC_NUMBER: u32 = 0x63825363;
|
|
|
|
enum_with_unknown! {
|
|
/// The possible opcodes of a DHCP packet.
|
|
pub enum OpCode(u8) {
|
|
Request = 1,
|
|
Reply = 2,
|
|
}
|
|
}
|
|
|
|
enum_with_unknown! {
|
|
/// The possible message types of a DHCP packet.
|
|
pub enum MessageType(u8) {
|
|
Discover = 1,
|
|
Offer = 2,
|
|
Request = 3,
|
|
Decline = 4,
|
|
Ack = 5,
|
|
Nak = 6,
|
|
Release = 7,
|
|
Inform = 8,
|
|
}
|
|
}
|
|
|
|
impl MessageType {
|
|
fn opcode(&self) -> OpCode {
|
|
match *self {
|
|
MessageType::Discover | MessageType::Inform | MessageType::Request |
|
|
MessageType::Decline | MessageType::Release => OpCode::Request,
|
|
MessageType::Offer | MessageType::Ack | MessageType::Nak => OpCode::Reply,
|
|
MessageType::Unknown(_) => OpCode::Unknown(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A representation of a single DHCP option.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum DhcpOption<'a> {
|
|
EndOfList,
|
|
Pad,
|
|
MessageType(MessageType),
|
|
RequestedIp(Ipv4Address),
|
|
ClientIdentifier(EthernetAddress),
|
|
ServerIdentifier(Ipv4Address),
|
|
Router(Ipv4Address),
|
|
SubnetMask(Ipv4Address),
|
|
MaximumDhcpMessageSize(u16),
|
|
Other { kind: u8, data: &'a [u8] }
|
|
}
|
|
|
|
impl<'a> DhcpOption<'a> {
|
|
pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], DhcpOption<'a>)> {
|
|
// See https://tools.ietf.org/html/rfc2132 for all possible DHCP options.
|
|
|
|
let (skip_len, option);
|
|
match *buffer.get(0).ok_or(Error::Truncated)? {
|
|
field::OPT_END => {
|
|
skip_len = 1;
|
|
option = DhcpOption::EndOfList;
|
|
}
|
|
field::OPT_PAD => {
|
|
skip_len = 1;
|
|
option = DhcpOption::Pad;
|
|
}
|
|
kind => {
|
|
let length = *buffer.get(1).ok_or(Error::Truncated)? as usize;
|
|
skip_len = length + 2;
|
|
let data = buffer.get(2..skip_len).ok_or(Error::Truncated)?;
|
|
match (kind, length) {
|
|
(field::OPT_END, _) |
|
|
(field::OPT_PAD, _) =>
|
|
unreachable!(),
|
|
(field::OPT_DHCP_MESSAGE_TYPE, 1) => {
|
|
option = DhcpOption::MessageType(MessageType::from(data[0]));
|
|
},
|
|
(field::OPT_REQUESTED_IP, 4) => {
|
|
option = DhcpOption::RequestedIp(Ipv4Address::from_bytes(data));
|
|
}
|
|
(field::OPT_CLIENT_ID, 7) => {
|
|
let hardware_type = Hardware::from(u16::from(data[0]));
|
|
if hardware_type != Hardware::Ethernet {
|
|
return Err(Error::Unrecognized);
|
|
}
|
|
option = DhcpOption::ClientIdentifier(EthernetAddress::from_bytes(&data[1..]));
|
|
}
|
|
(field::OPT_SERVER_IDENTIFIER, 4) => {
|
|
option = DhcpOption::ServerIdentifier(Ipv4Address::from_bytes(data));
|
|
}
|
|
(field::OPT_ROUTER, 4) => {
|
|
option = DhcpOption::Router(Ipv4Address::from_bytes(data));
|
|
}
|
|
(field::OPT_SUBNET_MASK, 4) => {
|
|
option = DhcpOption::SubnetMask(Ipv4Address::from_bytes(data));
|
|
}
|
|
(field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => {
|
|
option = DhcpOption::MaximumDhcpMessageSize(u16::from_be_bytes([data[0], data[1]]));
|
|
}
|
|
(_, _) => {
|
|
option = DhcpOption::Other { kind: kind, data: data };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok((&buffer[skip_len..], option))
|
|
}
|
|
|
|
pub fn buffer_len(&self) -> usize {
|
|
match self {
|
|
&DhcpOption::EndOfList => 1,
|
|
&DhcpOption::Pad => 1,
|
|
&DhcpOption::MessageType(_) => 3,
|
|
&DhcpOption::ClientIdentifier(eth_addr) => {
|
|
3 + eth_addr.as_bytes().len()
|
|
}
|
|
&DhcpOption::RequestedIp(ip) |
|
|
&DhcpOption::ServerIdentifier(ip) |
|
|
&DhcpOption::Router(ip) |
|
|
&DhcpOption::SubnetMask(ip) => {
|
|
2 + ip.as_bytes().len()
|
|
},
|
|
&DhcpOption::MaximumDhcpMessageSize(_) => {
|
|
4
|
|
}
|
|
&DhcpOption::Other { data, .. } => 2 + data.len()
|
|
}
|
|
}
|
|
|
|
pub fn emit<'b>(&self, buffer: &'b mut [u8]) -> &'b mut [u8] {
|
|
let skip_length;
|
|
match self {
|
|
&DhcpOption::EndOfList => {
|
|
skip_length = 1;
|
|
buffer[0] = field::OPT_END;
|
|
}
|
|
&DhcpOption::Pad => {
|
|
skip_length = 1;
|
|
buffer[0] = field::OPT_PAD;
|
|
}
|
|
_ => {
|
|
skip_length = self.buffer_len();
|
|
buffer[1] = (skip_length - 2) as u8;
|
|
match self {
|
|
&DhcpOption::EndOfList | &DhcpOption::Pad => unreachable!(),
|
|
&DhcpOption::MessageType(value) => {
|
|
buffer[0] = field::OPT_DHCP_MESSAGE_TYPE;
|
|
buffer[2] = value.into();
|
|
}
|
|
&DhcpOption::ClientIdentifier(eth_addr) => {
|
|
buffer[0] = field::OPT_CLIENT_ID;
|
|
buffer[2] = u16::from(Hardware::Ethernet) as u8;
|
|
buffer[3..9].copy_from_slice(eth_addr.as_bytes());
|
|
}
|
|
&DhcpOption::RequestedIp(ip) => {
|
|
buffer[0] = field::OPT_REQUESTED_IP;
|
|
buffer[2..6].copy_from_slice(ip.as_bytes());
|
|
}
|
|
&DhcpOption::ServerIdentifier(ip) => {
|
|
buffer[0] = field::OPT_SERVER_IDENTIFIER;
|
|
buffer[2..6].copy_from_slice(ip.as_bytes());
|
|
}
|
|
&DhcpOption::Router(ip) => {
|
|
buffer[0] = field::OPT_ROUTER;
|
|
buffer[2..6].copy_from_slice(ip.as_bytes());
|
|
}
|
|
&DhcpOption::SubnetMask(mask) => {
|
|
buffer[0] = field::OPT_SUBNET_MASK;
|
|
buffer[2..6].copy_from_slice(mask.as_bytes());
|
|
}
|
|
&DhcpOption::MaximumDhcpMessageSize(size) => {
|
|
buffer[0] = field::OPT_MAX_DHCP_MESSAGE_SIZE;
|
|
buffer[2..4].copy_from_slice(&size.to_be_bytes()[..]);
|
|
}
|
|
&DhcpOption::Other { kind, data: provided } => {
|
|
buffer[0] = kind;
|
|
buffer[2..skip_length].copy_from_slice(provided);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&mut buffer[skip_length..]
|
|
}
|
|
}
|
|
|
|
/// A read/write wrapper around a Dynamic Host Configuration Protocol packet buffer.
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Packet<T: AsRef<[u8]>> {
|
|
buffer: T
|
|
}
|
|
|
|
pub(crate) mod field {
|
|
#![allow(non_snake_case)]
|
|
#![allow(unused)]
|
|
|
|
use crate::wire::field::*;
|
|
|
|
pub const OP: usize = 0;
|
|
pub const HTYPE: usize = 1;
|
|
pub const HLEN: usize = 2;
|
|
pub const HOPS: usize = 3;
|
|
pub const XID: Field = 4..8;
|
|
pub const SECS: Field = 8..10;
|
|
pub const FLAGS: Field = 10..12;
|
|
pub const CIADDR: Field = 12..16;
|
|
pub const YIADDR: Field = 16..20;
|
|
pub const SIADDR: Field = 20..24;
|
|
pub const GIADDR: Field = 24..28;
|
|
pub const CHADDR: Field = 28..34;
|
|
pub const SNAME: Field = 34..108;
|
|
pub const FILE: Field = 108..236;
|
|
pub const MAGIC_NUMBER: Field = 236..240;
|
|
pub const OPTIONS: Rest = 240..;
|
|
|
|
// Vendor Extensions
|
|
pub const OPT_END: u8 = 255;
|
|
pub const OPT_PAD: u8 = 0;
|
|
pub const OPT_SUBNET_MASK: u8 = 1;
|
|
pub const OPT_TIME_OFFSET: u8 = 2;
|
|
pub const OPT_ROUTER: u8 = 3;
|
|
pub const OPT_TIME_SERVER: u8 = 4;
|
|
pub const OPT_NAME_SERVER: u8 = 5;
|
|
pub const OPT_DOMAIN_NAME_SERVER: u8 = 6;
|
|
pub const OPT_LOG_SERVER: u8 = 7;
|
|
pub const OPT_COOKIE_SERVER: u8 = 8;
|
|
pub const OPT_LPR_SERVER: u8 = 9;
|
|
pub const OPT_IMPRESS_SERVER: u8 = 10;
|
|
pub const OPT_RESOURCE_LOCATION_SERVER: u8 = 11;
|
|
pub const OPT_HOST_NAME: u8 = 12;
|
|
pub const OPT_BOOT_FILE_SIZE: u8 = 13;
|
|
pub const OPT_MERIT_DUMP: u8 = 14;
|
|
pub const OPT_DOMAIN_NAME: u8 = 15;
|
|
pub const OPT_SWAP_SERVER: u8 = 16;
|
|
pub const OPT_ROOT_PATH: u8 = 17;
|
|
pub const OPT_EXTENSIONS_PATH: u8 = 18;
|
|
|
|
// IP Layer Parameters per Host
|
|
pub const OPT_IP_FORWARDING: u8 = 19;
|
|
pub const OPT_NON_LOCAL_SOURCE_ROUTING: u8 = 20;
|
|
pub const OPT_POLICY_FILTER: u8 = 21;
|
|
pub const OPT_MAX_DATAGRAM_REASSEMBLY_SIZE: u8 = 22;
|
|
pub const OPT_DEFAULT_TTL: u8 = 23;
|
|
pub const OPT_PATH_MTU_AGING_TIMEOUT: u8 = 24;
|
|
pub const OPT_PATH_MTU_PLATEU_TABLE: u8 = 25;
|
|
|
|
// IP Layer Parameters per Interface
|
|
pub const OPT_INTERFACE_MTU: u8 = 26;
|
|
pub const OPT_ALL_SUBNETS_ARE_LOCAL: u8 = 27;
|
|
pub const OPT_BROADCAST_ADDRESS: u8 = 28;
|
|
pub const OPT_PERFORM_MASK_DISCOVERY: u8 = 29;
|
|
pub const OPT_MASK_SUPPLIER: u8 = 30;
|
|
pub const OPT_PERFORM_ROUTER_DISCOVERY: u8 = 31;
|
|
pub const OPT_ROUTER_SOLICITATION_ADDRESS: u8 = 32;
|
|
pub const OPT_STATIC_ROUTE: u8 = 33;
|
|
|
|
// Link Layer Parameters per Interface
|
|
pub const OPT_TRAILER_ENCAPSULATION: u8 = 34;
|
|
pub const OPT_ARP_CACHE_TIMEOUT: u8 = 35;
|
|
pub const OPT_ETHERNET_ENCAPSULATION: u8 = 36;
|
|
|
|
// TCP Parameters
|
|
pub const OPT_TCP_DEFAULT_TTL: u8 = 37;
|
|
pub const OPT_TCP_KEEPALIVE_INTERVAL: u8 = 38;
|
|
pub const OPT_TCP_KEEPALIVE_GARBAGE: u8 = 39;
|
|
|
|
// Application and Service Parameters
|
|
pub const OPT_NIS_DOMAIN: u8 = 40;
|
|
pub const OPT_NIS_SERVERS: u8 = 41;
|
|
pub const OPT_NTP_SERVERS: u8 = 42;
|
|
pub const OPT_VENDOR_SPECIFIC_INFO: u8 = 43;
|
|
pub const OPT_NETBIOS_NAME_SERVER: u8 = 44;
|
|
pub const OPT_NETBIOS_DISTRIBUTION_SERVER: u8 = 45;
|
|
pub const OPT_NETBIOS_NODE_TYPE: u8 = 46;
|
|
pub const OPT_NETBIOS_SCOPE: u8 = 47;
|
|
pub const OPT_X_WINDOW_FONT_SERVER: u8 = 48;
|
|
pub const OPT_X_WINDOW_DISPLAY_MANAGER: u8 = 49;
|
|
pub const OPT_NIS_PLUS_DOMAIN: u8 = 64;
|
|
pub const OPT_NIS_PLUS_SERVERS: u8 = 65;
|
|
pub const OPT_MOBILE_IP_HOME_AGENT: u8 = 68;
|
|
pub const OPT_SMTP_SERVER: u8 = 69;
|
|
pub const OPT_POP3_SERVER: u8 = 70;
|
|
pub const OPT_NNTP_SERVER: u8 = 71;
|
|
pub const OPT_WWW_SERVER: u8 = 72;
|
|
pub const OPT_FINGER_SERVER: u8 = 73;
|
|
pub const OPT_IRC_SERVER: u8 = 74;
|
|
pub const OPT_STREETTALK_SERVER: u8 = 75;
|
|
pub const OPT_STDA_SERVER: u8 = 76;
|
|
|
|
// DHCP Extensions
|
|
pub const OPT_REQUESTED_IP: u8 = 50;
|
|
pub const OPT_IP_LEASE_TIME: u8 = 51;
|
|
pub const OPT_OPTION_OVERLOAD: u8 = 52;
|
|
pub const OPT_TFTP_SERVER_NAME: u8 = 66;
|
|
pub const OPT_BOOTFILE_NAME: u8 = 67;
|
|
pub const OPT_DHCP_MESSAGE_TYPE: u8 = 53;
|
|
pub const OPT_SERVER_IDENTIFIER: u8 = 54;
|
|
pub const OPT_PARAMETER_REQUEST_LIST: u8 = 55;
|
|
pub const OPT_MESSAGE: u8 = 56;
|
|
pub const OPT_MAX_DHCP_MESSAGE_SIZE: u8 = 57;
|
|
pub const OPT_RENEWAL_TIME_VALUE: u8 = 58;
|
|
pub const OPT_REBINDING_TIME_VALUE: u8 = 59;
|
|
pub const OPT_VENDOR_CLASS_ID: u8 = 60;
|
|
pub const OPT_CLIENT_ID: u8 = 61;
|
|
}
|
|
|
|
impl<T: AsRef<[u8]>> Packet<T> {
|
|
/// Imbue a raw octet buffer with DHCP packet structure.
|
|
pub fn new_unchecked(buffer: T) -> Packet<T> {
|
|
Packet { buffer }
|
|
}
|
|
|
|
/// Shorthand for a combination of [new_unchecked] and [check_len].
|
|
///
|
|
/// [new_unchecked]: #method.new_unchecked
|
|
/// [check_len]: #method.check_len
|
|
pub fn new_checked(buffer: T) -> Result<Packet<T>> {
|
|
let packet = Self::new_unchecked(buffer);
|
|
packet.check_len()?;
|
|
Ok(packet)
|
|
}
|
|
|
|
/// Ensure that no accessor method will panic if called.
|
|
/// Returns `Err(Error::Truncated)` if the buffer is too short.
|
|
///
|
|
/// [set_header_len]: #method.set_header_len
|
|
pub fn check_len(&self) -> Result<()> {
|
|
let len = self.buffer.as_ref().len();
|
|
if len < field::MAGIC_NUMBER.end {
|
|
Err(Error::Truncated)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Consume the packet, returning the underlying buffer.
|
|
pub fn into_inner(self) -> T {
|
|
self.buffer
|
|
}
|
|
|
|
/// Returns the operation code of this packet.
|
|
pub fn opcode(&self) -> OpCode {
|
|
let data = self.buffer.as_ref();
|
|
OpCode::from(data[field::OP])
|
|
}
|
|
|
|
/// Returns the hardware protocol type (e.g. ethernet).
|
|
pub fn hardware_type(&self) -> Hardware {
|
|
let data = self.buffer.as_ref();
|
|
Hardware::from(u16::from(data[field::HTYPE]))
|
|
}
|
|
|
|
/// Returns the length of a hardware address in bytes (e.g. 6 for ethernet).
|
|
pub fn hardware_len(&self) -> u8 {
|
|
self.buffer.as_ref()[field::HLEN]
|
|
}
|
|
|
|
/// Returns the transaction ID.
|
|
///
|
|
/// The transaction ID (called `xid` in the specification) is a random number used to
|
|
/// associate messages and responses between client and server. The number is chosen by
|
|
/// the client.
|
|
pub fn transaction_id(&self) -> u32 {
|
|
let field = &self.buffer.as_ref()[field::XID];
|
|
NetworkEndian::read_u32(field)
|
|
}
|
|
|
|
/// Returns the hardware address of the client (called `chaddr` in the specification).
|
|
///
|
|
/// Only ethernet is supported by `smoltcp`, so this functions returns
|
|
/// an `EthernetAddress`.
|
|
pub fn client_hardware_address(&self) -> EthernetAddress {
|
|
let field = &self.buffer.as_ref()[field::CHADDR];
|
|
EthernetAddress::from_bytes(field)
|
|
}
|
|
|
|
/// Returns the value of the `hops` field.
|
|
///
|
|
/// The `hops` field is set to zero by clients and optionally used by relay agents.
|
|
pub fn hops(&self) -> u8 {
|
|
self.buffer.as_ref()[field::HOPS]
|
|
}
|
|
|
|
/// Returns the value of the `secs` field.
|
|
///
|
|
/// The secs field is filled by clients and describes the number of seconds elapsed
|
|
/// since client began process.
|
|
pub fn secs(&self) -> u16 {
|
|
let field = &self.buffer.as_ref()[field::SECS];
|
|
NetworkEndian::read_u16(field)
|
|
}
|
|
|
|
/// Returns the value of the `magic cookie` field in the DHCP options.
|
|
///
|
|
/// This field should be always be `0x63825363`.
|
|
pub fn magic_number(&self) -> u32 {
|
|
let field = &self.buffer.as_ref()[field::MAGIC_NUMBER];
|
|
NetworkEndian::read_u32(field)
|
|
}
|
|
|
|
/// Returns the Ipv4 address of the client, zero if not set.
|
|
///
|
|
/// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
|
|
/// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
|
|
/// and can respond to ARP requests”.
|
|
pub fn client_ip(&self) -> Ipv4Address {
|
|
let field = &self.buffer.as_ref()[field::CIADDR];
|
|
Ipv4Address::from_bytes(field)
|
|
}
|
|
|
|
/// Returns the value of the `yiaddr` field, zero if not set.
|
|
pub fn your_ip(&self) -> Ipv4Address {
|
|
let field = &self.buffer.as_ref()[field::YIADDR];
|
|
Ipv4Address::from_bytes(field)
|
|
}
|
|
|
|
/// Returns the value of the `siaddr` field, zero if not set.
|
|
pub fn server_ip(&self) -> Ipv4Address {
|
|
let field = &self.buffer.as_ref()[field::SIADDR];
|
|
Ipv4Address::from_bytes(field)
|
|
}
|
|
|
|
/// Returns the value of the `giaddr` field, zero if not set.
|
|
pub fn relay_agent_ip(&self) -> Ipv4Address {
|
|
let field = &self.buffer.as_ref()[field::GIADDR];
|
|
Ipv4Address::from_bytes(field)
|
|
}
|
|
|
|
/// Returns true if the broadcast flag is set.
|
|
pub fn broadcast_flag(&self) -> bool {
|
|
let field = &self.buffer.as_ref()[field::FLAGS];
|
|
NetworkEndian::read_u16(field) & 0b1 == 0b1
|
|
}
|
|
}
|
|
|
|
impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
|
|
/// Return a pointer to the options.
|
|
#[inline]
|
|
pub fn options(&self) -> Result<&'a [u8]> {
|
|
let data = self.buffer.as_ref();
|
|
data.get(field::OPTIONS).ok_or(Error::Malformed)
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|
/// Sets the optional `sname` (“server name”) and `file` (“boot file name”) fields to zero.
|
|
///
|
|
/// The fields are not commonly used, so we set their value always to zero. **This method
|
|
/// must be called when creating a packet, otherwise the emitted values for these fields
|
|
/// are undefined!**
|
|
pub fn set_sname_and_boot_file_to_zero(&mut self) {
|
|
let data = self.buffer.as_mut();
|
|
for byte in &mut data[field::SNAME] {
|
|
*byte = 0;
|
|
}
|
|
for byte in &mut data[field::FILE] {
|
|
*byte = 0;
|
|
}
|
|
}
|
|
|
|
/// Sets the `OpCode` for the packet.
|
|
pub fn set_opcode(&mut self, value: OpCode) {
|
|
let data = self.buffer.as_mut();
|
|
data[field::OP] = value.into();
|
|
}
|
|
|
|
/// Sets the hardware address type (only ethernet is supported).
|
|
pub fn set_hardware_type(&mut self, value: Hardware) {
|
|
let data = self.buffer.as_mut();
|
|
let number: u16 = value.into();
|
|
assert!(number <= u16::from(u8::max_value())); // TODO: Replace with TryFrom when it's stable
|
|
data[field::HTYPE] = number as u8;
|
|
}
|
|
|
|
/// Sets the hardware address length.
|
|
///
|
|
/// Only ethernet is supported, so this field should be set to the value `6`.
|
|
pub fn set_hardware_len(&mut self, value: u8) {
|
|
self.buffer.as_mut()[field::HLEN] = value;
|
|
}
|
|
|
|
/// Sets the transaction ID.
|
|
///
|
|
/// The transaction ID (called `xid` in the specification) is a random number used to
|
|
/// associate messages and responses between client and server. The number is chosen by
|
|
/// the client.
|
|
pub fn set_transaction_id(&mut self, value: u32) {
|
|
let field = &mut self.buffer.as_mut()[field::XID];
|
|
NetworkEndian::write_u32(field, value)
|
|
}
|
|
|
|
/// Sets the ethernet address of the client.
|
|
///
|
|
/// Sets the `chaddr` field.
|
|
pub fn set_client_hardware_address(&mut self, value: EthernetAddress) {
|
|
let field = &mut self.buffer.as_mut()[field::CHADDR];
|
|
field.copy_from_slice(value.as_bytes());
|
|
}
|
|
|
|
/// Sets the hops field.
|
|
///
|
|
/// The `hops` field is set to zero by clients and optionally used by relay agents.
|
|
pub fn set_hops(&mut self, value: u8) {
|
|
self.buffer.as_mut()[field::HOPS] = value;
|
|
}
|
|
|
|
/// Sets the `secs` field.
|
|
///
|
|
/// The secs field is filled by clients and describes the number of seconds elapsed
|
|
/// since client began process.
|
|
pub fn set_secs(&mut self, value: u16) {
|
|
let field = &mut self.buffer.as_mut()[field::SECS];
|
|
NetworkEndian::write_u16(field, value);
|
|
}
|
|
|
|
/// Sets the value of the `magic cookie` field in the DHCP options.
|
|
///
|
|
/// This field should be always be `0x63825363`.
|
|
pub fn set_magic_number(&mut self, value: u32) {
|
|
let field = &mut self.buffer.as_mut()[field::MAGIC_NUMBER];
|
|
NetworkEndian::write_u32(field, value);
|
|
}
|
|
|
|
/// Sets the Ipv4 address of the client.
|
|
///
|
|
/// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
|
|
/// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
|
|
/// and can respond to ARP requests”.
|
|
pub fn set_client_ip(&mut self, value: Ipv4Address) {
|
|
let field = &mut self.buffer.as_mut()[field::CIADDR];
|
|
field.copy_from_slice(value.as_bytes());
|
|
}
|
|
|
|
/// Sets the value of the `yiaddr` field.
|
|
pub fn set_your_ip(&mut self, value: Ipv4Address) {
|
|
let field = &mut self.buffer.as_mut()[field::YIADDR];
|
|
field.copy_from_slice(value.as_bytes());
|
|
}
|
|
|
|
/// Sets the value of the `siaddr` field.
|
|
pub fn set_server_ip(&mut self, value: Ipv4Address) {
|
|
let field = &mut self.buffer.as_mut()[field::SIADDR];
|
|
field.copy_from_slice(value.as_bytes());
|
|
}
|
|
|
|
/// Sets the value of the `giaddr` field.
|
|
pub fn set_relay_agent_ip(&mut self, value: Ipv4Address) {
|
|
let field = &mut self.buffer.as_mut()[field::GIADDR];
|
|
field.copy_from_slice(value.as_bytes());
|
|
}
|
|
|
|
/// Sets the broadcast flag to the specified value.
|
|
pub fn set_broadcast_flag(&mut self, value: bool) {
|
|
let field = &mut self.buffer.as_mut()[field::FLAGS];
|
|
NetworkEndian::write_u16(field, if value { 1 } else { 0 });
|
|
}
|
|
}
|
|
|
|
impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
|
|
/// Return a pointer to the options.
|
|
#[inline]
|
|
pub fn options_mut(&mut self) -> Result<&mut [u8]> {
|
|
let data = self.buffer.as_mut();
|
|
data.get_mut(field::OPTIONS).ok_or(Error::Truncated)
|
|
}
|
|
}
|
|
|
|
/// A high-level representation of a Dynamic Host Configuration Protocol packet.
|
|
///
|
|
/// DHCP messages have the following layout (see [RFC 2131](https://tools.ietf.org/html/rfc2131)
|
|
/// for details):
|
|
///
|
|
/// ```no_rust
|
|
/// 0 1 2 3
|
|
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
/// | message_type | htype (N/A) | hlen (N/A) | hops |
|
|
/// +---------------+---------------+---------------+---------------+
|
|
/// | transaction_id |
|
|
/// +-------------------------------+-------------------------------+
|
|
/// | secs | flags |
|
|
/// +-------------------------------+-------------------------------+
|
|
/// | client_ip |
|
|
/// +---------------------------------------------------------------+
|
|
/// | your_ip |
|
|
/// +---------------------------------------------------------------+
|
|
/// | server_ip |
|
|
/// +---------------------------------------------------------------+
|
|
/// | relay_agent_ip |
|
|
/// +---------------------------------------------------------------+
|
|
/// | |
|
|
/// | client_hardware_address |
|
|
/// | |
|
|
/// | |
|
|
/// +---------------------------------------------------------------+
|
|
/// | |
|
|
/// | sname (N/A) |
|
|
/// +---------------------------------------------------------------+
|
|
/// | |
|
|
/// | file (N/A) |
|
|
/// +---------------------------------------------------------------+
|
|
/// | |
|
|
/// | options |
|
|
/// +---------------------------------------------------------------+
|
|
/// ```
|
|
///
|
|
/// It is assumed that the access layer is Ethernet, so `htype` (the field representing the
|
|
/// hardware address type) is always set to `1`, and `hlen` (which represents the hardware address
|
|
/// length) is set to `6`.
|
|
///
|
|
/// The `options` field has a variable length.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub struct Repr<'a> {
|
|
/// This field is also known as `op` in the RFC. It indicates the type of DHCP message this
|
|
/// packet represents.
|
|
pub message_type: MessageType,
|
|
/// This field is also known as `xid` in the RFC. It is a random number chosen by the client,
|
|
/// used by the client and server to associate messages and responses between a client and a
|
|
/// server.
|
|
pub transaction_id: u32,
|
|
/// This field is also known as `chaddr` in the RFC and for networks where the access layer is
|
|
/// ethernet, it is the client MAC address.
|
|
pub client_hardware_address: EthernetAddress,
|
|
/// This field is also known as `ciaddr` in the RFC. It is only filled in if client is in
|
|
/// BOUND, RENEW or REBINDING state and can respond to ARP requests.
|
|
pub client_ip: Ipv4Address,
|
|
/// This field is also known as `yiaddr` in the RFC.
|
|
pub your_ip: Ipv4Address,
|
|
/// This field is also known as `siaddr` in the RFC. It may be set by the server in DHCPOFFER
|
|
/// and DHCPACK messages, and represent the address of the next server to use in bootstrap.
|
|
pub server_ip: Ipv4Address,
|
|
/// Default gateway
|
|
pub router: Option<Ipv4Address>,
|
|
/// This field comes from a corresponding DhcpOption.
|
|
pub subnet_mask: Option<Ipv4Address>,
|
|
/// This field is also known as `giaddr` in the RFC. In order to allow DHCP clients on subnets
|
|
/// not directly served by DHCP servers to communicate with DHCP servers, DHCP relay agents can
|
|
/// be installed on these subnets. The DHCP client broadcasts on the local link; the relay
|
|
/// agent receives the broadcast and transmits it to one or more DHCP servers using unicast.
|
|
/// The relay agent stores its own IP address in the `relay_agent_ip` field of the DHCP packet.
|
|
/// The DHCP server uses the `relay_agent_ip` to determine the subnet on which the relay agent
|
|
/// received the broadcast, and allocates an IP address on that subnet. When the DHCP server
|
|
/// replies to the client, it sends the reply to the `relay_agent_ip` address, again using
|
|
/// unicast. The relay agent then retransmits the response on the local network
|
|
pub relay_agent_ip: Ipv4Address,
|
|
/// Broadcast flags. It can be set in DHCPDISCOVER, DHCPINFORM and DHCPREQUEST message if the
|
|
/// client requires the response to be broadcasted.
|
|
pub broadcast: bool,
|
|
/// The "requested IP address" option. It can be used by clients in DHCPREQUEST or DHCPDISCOVER
|
|
/// messages, or by servers in DHCPDECLINE messages.
|
|
pub requested_ip: Option<Ipv4Address>,
|
|
/// The "client identifier" option.
|
|
///
|
|
/// The 'client identifier' is an opaque key, not to be interpreted by the server; for example,
|
|
/// the 'client identifier' may contain a hardware address, identical to the contents of the
|
|
/// 'chaddr' field, or it may contain another type of identifier, such as a DNS name. The
|
|
/// 'client identifier' chosen by a DHCP client MUST be unique to that client within the subnet
|
|
/// to which the client is attached. If the client uses a 'client identifier' in one message,
|
|
/// it MUST use that same identifier in all subsequent messages, to ensure that all servers
|
|
/// correctly identify the client.
|
|
pub client_identifier: Option<EthernetAddress>,
|
|
/// The "server identifier" option. It is used both to identify a DHCP server
|
|
/// in a DHCP message and as a destination address from clients to servers.
|
|
pub server_identifier: Option<Ipv4Address>,
|
|
/// The parameter request list informs the server about which configuration parameters
|
|
/// the client is interested in.
|
|
pub parameter_request_list: Option<&'a [u8]>,
|
|
/// DNS servers
|
|
pub dns_servers: Option<[Option<Ipv4Address>; 3]>,
|
|
/// The maximum size dhcp packet the interface can receive
|
|
pub max_size: Option<u16>,
|
|
}
|
|
|
|
impl<'a> Repr<'a> {
|
|
/// Return the length of a packet that will be emitted from this high-level representation.
|
|
pub fn buffer_len(&self) -> usize {
|
|
let mut len = field::OPTIONS.start;
|
|
// message type and end-of-options options
|
|
len += 3 + 1;
|
|
if self.requested_ip.is_some() { len += 6; }
|
|
if self.client_identifier.is_some() { len += 9; }
|
|
if self.server_identifier.is_some() { len += 6; }
|
|
if self.max_size.is_some() { len += 4; }
|
|
if let Some(list) = self.parameter_request_list { len += list.len() + 2; }
|
|
|
|
len
|
|
}
|
|
|
|
/// Parse a DHCP packet and return a high-level representation.
|
|
pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Self>
|
|
where T: AsRef<[u8]> + ?Sized {
|
|
|
|
let transaction_id = packet.transaction_id();
|
|
let client_hardware_address = packet.client_hardware_address();
|
|
let client_ip = packet.client_ip();
|
|
let your_ip = packet.your_ip();
|
|
let server_ip = packet.server_ip();
|
|
let relay_agent_ip = packet.relay_agent_ip();
|
|
|
|
// only ethernet is supported right now
|
|
match packet.hardware_type() {
|
|
Hardware::Ethernet => {
|
|
if packet.hardware_len() != 6 {
|
|
return Err(Error::Malformed);
|
|
}
|
|
}
|
|
Hardware::Unknown(_) => return Err(Error::Unrecognized), // unimplemented
|
|
}
|
|
|
|
if packet.magic_number() != DHCP_MAGIC_NUMBER {
|
|
return Err(Error::Malformed);
|
|
}
|
|
|
|
let mut message_type = Err(Error::Malformed);
|
|
let mut requested_ip = None;
|
|
let mut client_identifier = None;
|
|
let mut server_identifier = None;
|
|
let mut router = None;
|
|
let mut subnet_mask = None;
|
|
let mut parameter_request_list = None;
|
|
let mut dns_servers = None;
|
|
let mut max_size = None;
|
|
|
|
let mut options = packet.options()?;
|
|
while options.len() > 0 {
|
|
let (next_options, option) = DhcpOption::parse(options)?;
|
|
match option {
|
|
DhcpOption::EndOfList => break,
|
|
DhcpOption::Pad => {},
|
|
DhcpOption::MessageType(value) => {
|
|
if value.opcode() == packet.opcode() {
|
|
message_type = Ok(value);
|
|
}
|
|
},
|
|
DhcpOption::RequestedIp(ip) => {
|
|
requested_ip = Some(ip);
|
|
}
|
|
DhcpOption::ClientIdentifier(eth_addr) => {
|
|
client_identifier = Some(eth_addr);
|
|
}
|
|
DhcpOption::ServerIdentifier(ip) => {
|
|
server_identifier = Some(ip);
|
|
}
|
|
DhcpOption::Router(ip) => {
|
|
router = Some(ip);
|
|
}
|
|
DhcpOption::SubnetMask(mask) => {
|
|
subnet_mask = Some(mask);
|
|
},
|
|
DhcpOption::MaximumDhcpMessageSize(size) => {
|
|
max_size = Some(size);
|
|
}
|
|
DhcpOption::Other {kind: field::OPT_PARAMETER_REQUEST_LIST, data} => {
|
|
parameter_request_list = Some(data);
|
|
}
|
|
DhcpOption::Other {kind: field::OPT_DOMAIN_NAME_SERVER, data} => {
|
|
let mut dns_servers_inner = [None; 3];
|
|
for i in 0..3 {
|
|
let offset = 4 * i;
|
|
let end = offset + 4;
|
|
if end > data.len() { break }
|
|
dns_servers_inner[i] = Some(Ipv4Address::from_bytes(&data[offset..end]));
|
|
}
|
|
dns_servers = Some(dns_servers_inner);
|
|
}
|
|
DhcpOption::Other {..} => {}
|
|
}
|
|
options = next_options;
|
|
}
|
|
|
|
let broadcast = packet.broadcast_flag();
|
|
|
|
Ok(Repr {
|
|
transaction_id, client_hardware_address, client_ip, your_ip, server_ip, relay_agent_ip,
|
|
broadcast, requested_ip, server_identifier, router,
|
|
subnet_mask, client_identifier, parameter_request_list, dns_servers, max_size,
|
|
message_type: message_type?,
|
|
})
|
|
}
|
|
|
|
/// Emit a high-level representation into a Dynamic Host
|
|
/// Configuration Protocol packet.
|
|
pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
|
|
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
|
packet.set_sname_and_boot_file_to_zero();
|
|
packet.set_opcode(self.message_type.opcode());
|
|
packet.set_hardware_type(Hardware::Ethernet);
|
|
packet.set_hardware_len(6);
|
|
packet.set_transaction_id(self.transaction_id);
|
|
packet.set_client_hardware_address(self.client_hardware_address);
|
|
packet.set_hops(0);
|
|
packet.set_secs(0); // TODO
|
|
packet.set_magic_number(0x63825363);
|
|
packet.set_client_ip(self.client_ip);
|
|
packet.set_your_ip(self.your_ip);
|
|
packet.set_server_ip(self.server_ip);
|
|
packet.set_relay_agent_ip(self.relay_agent_ip);
|
|
packet.set_broadcast_flag(self.broadcast);
|
|
|
|
{
|
|
let mut options = packet.options_mut()?;
|
|
let tmp = options; options = DhcpOption::MessageType(self.message_type).emit(tmp);
|
|
if let Some(eth_addr) = self.client_identifier {
|
|
let tmp = options; options = DhcpOption::ClientIdentifier(eth_addr).emit(tmp);
|
|
}
|
|
if let Some(ip) = self.server_identifier {
|
|
let tmp = options; options = DhcpOption::ServerIdentifier(ip).emit(tmp);
|
|
}
|
|
if let Some(ip) = self.router {
|
|
let tmp = options; options = DhcpOption::Router(ip).emit(tmp);
|
|
}
|
|
if let Some(ip) = self.subnet_mask {
|
|
let tmp = options; options = DhcpOption::SubnetMask(ip).emit(tmp);
|
|
}
|
|
if let Some(ip) = self.requested_ip {
|
|
let tmp = options; options = DhcpOption::RequestedIp(ip).emit(tmp);
|
|
}
|
|
if let Some(size) = self.max_size {
|
|
let tmp = options; options = DhcpOption::MaximumDhcpMessageSize(size).emit(tmp);
|
|
}
|
|
if let Some(list) = self.parameter_request_list {
|
|
let option = DhcpOption::Other{ kind: field::OPT_PARAMETER_REQUEST_LIST, data: list };
|
|
let tmp = options; options = option.emit(tmp);
|
|
}
|
|
DhcpOption::EndOfList.emit(options);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::wire::Ipv4Address;
|
|
use super::*;
|
|
|
|
const MAGIC_COOKIE: u32 = 0x63825363;
|
|
|
|
static DISCOVER_BYTES: &[u8] = &[
|
|
0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x82, 0x01,
|
|
0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
|
0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00, 0x00,
|
|
0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
];
|
|
|
|
static ACK_BYTES: &[u8] = &[
|
|
0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06, 0x91,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17, 0xeb, 0xc9,
|
|
0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
|
0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x2b,
|
|
0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68, 0x79, 0x73,
|
|
0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00, 0x03, 0x04, 0x0a,
|
|
0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0xa3, 0x01, 0x4a,
|
|
0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0xa3,
|
|
0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08, 0xff
|
|
];
|
|
|
|
const IP_NULL: Ipv4Address = Ipv4Address([0, 0, 0, 0]);
|
|
const CLIENT_MAC: EthernetAddress = EthernetAddress([0x0, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
|
|
const DHCP_SIZE: u16 = 1500;
|
|
|
|
#[test]
|
|
fn test_deconstruct_discover() {
|
|
let packet = Packet::new_unchecked(DISCOVER_BYTES);
|
|
assert_eq!(packet.magic_number(), MAGIC_COOKIE);
|
|
assert_eq!(packet.opcode(), OpCode::Request);
|
|
assert_eq!(packet.hardware_type(), Hardware::Ethernet);
|
|
assert_eq!(packet.hardware_len(), 6);
|
|
assert_eq!(packet.hops(), 0);
|
|
assert_eq!(packet.transaction_id(), 0x3d1d);
|
|
assert_eq!(packet.secs(), 0);
|
|
assert_eq!(packet.client_ip(), IP_NULL);
|
|
assert_eq!(packet.your_ip(), IP_NULL);
|
|
assert_eq!(packet.server_ip(), IP_NULL);
|
|
assert_eq!(packet.relay_agent_ip(), IP_NULL);
|
|
assert_eq!(packet.client_hardware_address(), CLIENT_MAC);
|
|
let options = packet.options().unwrap();
|
|
assert_eq!(options.len(), 3 + 9 + 6 + 4 + 6 + 1 + 7);
|
|
|
|
let (options, message_type) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(message_type, DhcpOption::MessageType(MessageType::Discover));
|
|
assert_eq!(options.len(), 9 + 6 + 4 + 6 + 1 + 7);
|
|
|
|
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(client_id, DhcpOption::ClientIdentifier(CLIENT_MAC));
|
|
assert_eq!(options.len(), 6 + 4 + 6 + 1 + 7);
|
|
|
|
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(client_id, DhcpOption::RequestedIp(IP_NULL));
|
|
assert_eq!(options.len(), 4 + 6 + 1 + 7);
|
|
|
|
let (options, msg_size) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(msg_size, DhcpOption::MaximumDhcpMessageSize(DHCP_SIZE));
|
|
assert_eq!(options.len(), 6 + 1 + 7);
|
|
|
|
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(client_id, DhcpOption::Other {
|
|
kind: field::OPT_PARAMETER_REQUEST_LIST, data: &[1, 3, 6, 42]
|
|
});
|
|
assert_eq!(options.len(), 1 + 7);
|
|
|
|
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
|
assert_eq!(client_id, DhcpOption::EndOfList);
|
|
assert_eq!(options.len(), 7); // padding
|
|
}
|
|
|
|
#[test]
|
|
fn test_construct_discover() {
|
|
let mut bytes = vec![0xa5; 276];
|
|
let mut packet = Packet::new_unchecked(&mut bytes);
|
|
packet.set_magic_number(MAGIC_COOKIE);
|
|
packet.set_sname_and_boot_file_to_zero();
|
|
packet.set_opcode(OpCode::Request);
|
|
packet.set_hardware_type(Hardware::Ethernet);
|
|
packet.set_hardware_len(6);
|
|
packet.set_hops(0);
|
|
packet.set_transaction_id(0x3d1d);
|
|
packet.set_secs(0);
|
|
packet.set_broadcast_flag(false);
|
|
packet.set_client_ip(IP_NULL);
|
|
packet.set_your_ip(IP_NULL);
|
|
packet.set_server_ip(IP_NULL);
|
|
packet.set_relay_agent_ip(IP_NULL);
|
|
packet.set_client_hardware_address(CLIENT_MAC);
|
|
|
|
{
|
|
let mut options = packet.options_mut().unwrap();
|
|
let tmp = options; options = DhcpOption::MessageType(MessageType::Discover).emit(tmp);
|
|
let tmp = options; options = DhcpOption::ClientIdentifier(CLIENT_MAC).emit(tmp);
|
|
let tmp = options; options = DhcpOption::RequestedIp(IP_NULL).emit(tmp);
|
|
let tmp = options; options = DhcpOption::MaximumDhcpMessageSize(DHCP_SIZE).emit(tmp);
|
|
let option = DhcpOption::Other {
|
|
kind: field::OPT_PARAMETER_REQUEST_LIST, data: &[1, 3, 6, 42],
|
|
};
|
|
let tmp = options; options = option.emit(tmp);
|
|
DhcpOption::EndOfList.emit(options);
|
|
}
|
|
|
|
let packet = &mut packet.into_inner()[..];
|
|
for byte in &mut packet[269..276] {
|
|
*byte = 0; // padding bytes
|
|
}
|
|
|
|
assert_eq!(packet, DISCOVER_BYTES);
|
|
}
|
|
|
|
fn discover_repr() -> Repr<'static> {
|
|
Repr {
|
|
message_type: MessageType::Discover,
|
|
transaction_id: 0x3d1d,
|
|
client_hardware_address: CLIENT_MAC,
|
|
client_ip: IP_NULL,
|
|
your_ip: IP_NULL,
|
|
server_ip: IP_NULL,
|
|
router: None,
|
|
subnet_mask: None,
|
|
relay_agent_ip: IP_NULL,
|
|
broadcast: false,
|
|
max_size: Some(DHCP_SIZE),
|
|
requested_ip: Some(IP_NULL),
|
|
client_identifier: Some(CLIENT_MAC),
|
|
server_identifier: None,
|
|
parameter_request_list: Some(&[1, 3, 6, 42]),
|
|
dns_servers: None,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_discover() {
|
|
let packet = Packet::new_unchecked(DISCOVER_BYTES);
|
|
let repr = Repr::parse(&packet).unwrap();
|
|
assert_eq!(repr, discover_repr());
|
|
}
|
|
|
|
#[test]
|
|
fn test_emit_discover() {
|
|
let repr = discover_repr();
|
|
let mut bytes = vec![0xa5; repr.buffer_len()];
|
|
let mut packet = Packet::new_unchecked(&mut bytes);
|
|
repr.emit(&mut packet).unwrap();
|
|
let packet = &packet.into_inner()[..];
|
|
let packet_len = packet.len();
|
|
assert_eq!(packet, &DISCOVER_BYTES[..packet_len]);
|
|
for byte in &DISCOVER_BYTES[packet_len..] {
|
|
assert_eq!(*byte, 0); // padding bytes
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_emit_dhcp_option() {
|
|
static DATA: &[u8] = &[1, 3, 6];
|
|
let mut bytes = vec![0xa5; 5];
|
|
let dhcp_option = DhcpOption::Other {
|
|
kind: field::OPT_PARAMETER_REQUEST_LIST,
|
|
data: DATA,
|
|
};
|
|
{
|
|
let rest = dhcp_option.emit(&mut bytes);
|
|
assert_eq!(rest.len(), 0);
|
|
}
|
|
assert_eq!(&bytes[0..2], &[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8]);
|
|
assert_eq!(&bytes[2..], DATA);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_ack_dns_servers() {
|
|
let packet = Packet::new_unchecked(ACK_BYTES);
|
|
let repr = Repr::parse(&packet).unwrap();
|
|
// The packet described by ACK_BYTES advertises 4 DNS servers
|
|
// Here we ensure that we correctly parse the first 3 into our fixed
|
|
// length-3 array (see issue #305)
|
|
assert_eq!(repr.dns_servers, Some([
|
|
Some(Ipv4Address([163, 1, 74, 6])),
|
|
Some(Ipv4Address([163, 1, 74, 7])),
|
|
Some(Ipv4Address([163, 1, 74, 3]))]));
|
|
}
|
|
}
|