Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ verbose = []
"socket-udp" = []
"socket-tcp" = []
"socket-icmp" = []
"proto-dhcpv4" = ["proto-ipv4"]
default = [
"std", "log", # needed for `cargo test --no-default-features --features default` :/
"phy-raw_socket", "phy-tap_interface",
Expand Down
60 changes: 57 additions & 3 deletions src/wire/dhcpv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum DhcpOption<'a> {
RequestedIp(Ipv4Address),
ClientIdentifier(EthernetAddress),
ServerIdentifier(Ipv4Address),
Router(Ipv4Address),
SubnetMask(Ipv4Address),
Other { kind: u8, data: &'a [u8] }
}

Expand Down Expand Up @@ -91,6 +93,12 @@ impl<'a> DhcpOption<'a> {
(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));
}
(_, _) => {
option = DhcpOption::Other { kind: kind, data: data };
}
Expand All @@ -108,7 +116,10 @@ impl<'a> DhcpOption<'a> {
&DhcpOption::ClientIdentifier(eth_addr) => {
3 + eth_addr.as_bytes().len()
}
&DhcpOption::RequestedIp(ip) | &DhcpOption::ServerIdentifier(ip) => {
&DhcpOption::RequestedIp(ip) |
&DhcpOption::ServerIdentifier(ip) |
&DhcpOption::Router(ip) |
&DhcpOption::SubnetMask(ip) => {
2 + ip.as_bytes().len()
},
&DhcpOption::Other { data, .. } => 2 + data.len()
Expand Down Expand Up @@ -148,6 +159,14 @@ impl<'a> DhcpOption<'a> {
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::Other { kind, data: provided } => {
buffer[0] = kind;
buffer[2..skip_length].copy_from_slice(provided);
Expand All @@ -165,7 +184,7 @@ pub struct Packet<T: AsRef<[u8]>> {
buffer: T
}

mod field {
pub(crate) mod field {
#![allow(non_snake_case)]
#![allow(unused)]

Expand Down Expand Up @@ -603,6 +622,10 @@ pub struct Repr<'a> {
/// 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
Expand Down Expand Up @@ -635,6 +658,8 @@ pub struct Repr<'a> {
/// 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]>,
}

impl<'a> Repr<'a> {
Expand Down Expand Up @@ -680,7 +705,10 @@ impl<'a> Repr<'a> {
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 options = packet.options()?;
while options.len() > 0 {
Expand All @@ -702,9 +730,25 @@ impl<'a> Repr<'a> {
DhcpOption::ServerIdentifier(ip) => {
server_identifier = Some(ip);
}
DhcpOption::Router(ip) => {
router = Some(ip);
}
DhcpOption::SubnetMask(mask) => {
subnet_mask = Some(mask);
}
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.. {
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;
Expand All @@ -714,7 +758,8 @@ impl<'a> Repr<'a> {

Ok(Repr {
transaction_id, client_hardware_address, client_ip, your_ip, server_ip, relay_agent_ip,
broadcast, requested_ip, server_identifier, client_identifier, parameter_request_list,
broadcast, requested_ip, server_identifier, router,
subnet_mask, client_identifier, parameter_request_list, dns_servers,
message_type: message_type?,
})
}
Expand Down Expand Up @@ -746,6 +791,12 @@ impl<'a> Repr<'a> {
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);
}
Expand Down Expand Up @@ -878,12 +929,15 @@ mod test {
client_ip: IP_NULL,
your_ip: IP_NULL,
server_ip: IP_NULL,
router: None,
subnet_mask: None,
relay_agent_ip: IP_NULL,
broadcast: false,
requested_ip: Some(IP_NULL),
client_identifier: Some(CLIENT_MAC),
server_identifier: None,
parameter_request_list: Some(&[1, 3, 6, 42]),
dns_servers: None,
}
}

Expand Down
97 changes: 97 additions & 0 deletions src/wire/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,34 @@ impl Address {
&Address::__Nonexhaustive => unreachable!()
}
}

/// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`,
/// where `prefix_len` is the number of leading zeroes. Return `None` otherwise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring wasn't particularly great; I fixed it.

pub fn to_prefix_len(&self) -> Option<u8> {
let mut ones = true;
let mut prefix_len = 0;
for byte in self.as_bytes() {
let mut mask = 0x80;
for _ in 0..8 {
let one = *byte & mask != 0;
if ones {
// Expect 1s until first 0
if one {
prefix_len += 1;
} else {
ones = false;
}
} else {
if one {
// 1 where 0 was expected
return None
}
}
mask >>= 1;
}
}
Some(prefix_len)
}
}

#[cfg(all(feature = "std", feature = "proto-ipv4", feature = "proto-ipv6"))]
Expand Down Expand Up @@ -1074,4 +1102,73 @@ pub(crate) mod test {
fn endpoint_unspecified() {
assert!(!Endpoint::UNSPECIFIED.is_specified());
}

#[test]
#[cfg(feature = "proto-ipv4")]
fn to_prefix_len_ipv4() {
fn test_eq<A: Into<Address>>(prefix_len: u8, mask: A) {
assert_eq!(
Some(prefix_len),
mask.into().to_prefix_len()
);
}

test_eq(0, Ipv4Address::new(0, 0, 0, 0));
test_eq(1, Ipv4Address::new(128, 0, 0, 0));
test_eq(2, Ipv4Address::new(192, 0, 0, 0));
test_eq(3, Ipv4Address::new(224, 0, 0, 0));
test_eq(4, Ipv4Address::new(240, 0, 0, 0));
test_eq(5, Ipv4Address::new(248, 0, 0, 0));
test_eq(6, Ipv4Address::new(252, 0, 0, 0));
test_eq(7, Ipv4Address::new(254, 0, 0, 0));
test_eq(8, Ipv4Address::new(255, 0, 0, 0));
test_eq(9, Ipv4Address::new(255, 128, 0, 0));
test_eq(10, Ipv4Address::new(255, 192, 0, 0));
test_eq(11, Ipv4Address::new(255, 224, 0, 0));
test_eq(12, Ipv4Address::new(255, 240, 0, 0));
test_eq(13, Ipv4Address::new(255, 248, 0, 0));
test_eq(14, Ipv4Address::new(255, 252, 0, 0));
test_eq(15, Ipv4Address::new(255, 254, 0, 0));
test_eq(16, Ipv4Address::new(255, 255, 0, 0));
test_eq(17, Ipv4Address::new(255, 255, 128, 0));
test_eq(18, Ipv4Address::new(255, 255, 192, 0));
test_eq(19, Ipv4Address::new(255, 255, 224, 0));
test_eq(20, Ipv4Address::new(255, 255, 240, 0));
test_eq(21, Ipv4Address::new(255, 255, 248, 0));
test_eq(22, Ipv4Address::new(255, 255, 252, 0));
test_eq(23, Ipv4Address::new(255, 255, 254, 0));
test_eq(24, Ipv4Address::new(255, 255, 255, 0));
test_eq(25, Ipv4Address::new(255, 255, 255, 128));
test_eq(26, Ipv4Address::new(255, 255, 255, 192));
test_eq(27, Ipv4Address::new(255, 255, 255, 224));
test_eq(28, Ipv4Address::new(255, 255, 255, 240));
test_eq(29, Ipv4Address::new(255, 255, 255, 248));
test_eq(30, Ipv4Address::new(255, 255, 255, 252));
test_eq(31, Ipv4Address::new(255, 255, 255, 254));
test_eq(32, Ipv4Address::new(255, 255, 255, 255));
}

#[cfg(feature = "proto-ipv4")]
fn to_prefix_len_ipv4_error() {
assert_eq!(None, IpAddress::from(Ipv4Address::new(255,255,255,1)).to_prefix_len());
}

#[test]
#[cfg(feature = "proto-ipv6")]
fn to_prefix_len_ipv6() {
fn test_eq<A: Into<Address>>(prefix_len: u8, mask: A) {
assert_eq!(
Some(prefix_len),
mask.into().to_prefix_len()
);
}

test_eq(0, Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0));
test_eq(128, Ipv6Address::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff));
}

#[cfg(feature = "proto-ipv6")]
fn to_prefix_len_ipv6_error() {
assert_eq!(None, IpAddress::from(Ipv6Address::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 1)).to_prefix_len());
}
}
9 changes: 5 additions & 4 deletions src/wire/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ mod ndiscoption;
mod mld;
mod udp;
mod tcp;
#[cfg(feature = "proto-ipv4")]
mod dhcpv4;
#[cfg(feature = "proto-dhcpv4")]
pub(crate) mod dhcpv4;

pub use self::pretty_print::PrettyPrinter;

Expand Down Expand Up @@ -216,6 +216,7 @@ pub use self::tcp::{SeqNumber as TcpSeqNumber,
Repr as TcpRepr,
Control as TcpControl};

#[cfg(feature = "proto-ipv4")]
#[cfg(feature = "proto-dhcpv4")]
pub use self::dhcpv4::{Packet as DhcpPacket,
Repr as DhcpRepr};
Repr as DhcpRepr,
MessageType as DhcpMessageType};