mirror of
https://github.com/pj1234678/RustyDHCP.git
synced 2026-10-19 02:43:10 +08:00
Version 2 Added config file and IPV6 support and benchmarks with massive performance uplifts.
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
//! DHCP standards accuracy gaps, part 4.
|
||||
//!
|
||||
//! Covers RFC 2131 / 2132 / 951 corners NOT locked by
|
||||
//! `test_dhcp_rfc.rs` / `test_dhcp_standards.rs` / `test_dhcp_tables.rs` /
|
||||
//! `test_packet*.rs` / `test_server*.rs`:
|
||||
//!
|
||||
//! - exhaustive option-code decode branch matrix (typed vs passthrough)
|
||||
//! - exact diagnostic strings for `MessageType::from` / `message_type()`
|
||||
//! - encode buffer-size boundaries (240 panic / 241 minimal / 300 exact)
|
||||
//! - missing-END with trailing byte succeeds vs exact-consumption panics
|
||||
//! - option 255 (END) asymmetry: `to_raw` preserves, decode panics
|
||||
//! - hops edge values + reply reset, flags wire bytes, siaddr zeroing
|
||||
//! - NAK `yiaddr` left to caller (library does not force 0)
|
||||
//! - example Offer shape + T1/T2 RFC defaults when 58/59 absent
|
||||
//! - overload values 2/3, retransmit identity (xid/chaddr stable, secs grows)
|
||||
//!
|
||||
//! `*_quirk` tests lock current deviations so refactors cannot change them
|
||||
//! silently. All tests pass against the current implementation.
|
||||
|
||||
use dhcp4r::options::*;
|
||||
use dhcp4r::packet::*;
|
||||
use dhcp4r::server;
|
||||
use std::net::{Ipv4Addr, UdpSocket};
|
||||
use std::time::Duration;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers (same patterns as the other RFC suites)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn unwrap_packet(r: Result<Packet, CustomErr<&[u8]>>) -> Packet {
|
||||
match r {
|
||||
Ok(p) => p,
|
||||
Err(_) => panic!("expected Ok Packet"),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_raw(
|
||||
op: u8,
|
||||
hlen: u8,
|
||||
hops: u8,
|
||||
xid: u32,
|
||||
secs: u16,
|
||||
flags: [u8; 2],
|
||||
ci: [u8; 4],
|
||||
yi: [u8; 4],
|
||||
si: [u8; 4],
|
||||
gi: [u8; 4],
|
||||
chaddr: [u8; 6],
|
||||
opts_after_cookie: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
let mut v = vec![0u8; 236];
|
||||
v[0] = op;
|
||||
v[1] = 1;
|
||||
v[2] = hlen;
|
||||
v[3] = hops;
|
||||
v[4..8].copy_from_slice(&xid.to_be_bytes());
|
||||
v[8..10].copy_from_slice(&secs.to_be_bytes());
|
||||
v[10] = flags[0];
|
||||
v[11] = flags[1];
|
||||
v[12..16].copy_from_slice(&ci);
|
||||
v[16..20].copy_from_slice(&yi);
|
||||
v[20..24].copy_from_slice(&si);
|
||||
v[24..28].copy_from_slice(&gi);
|
||||
v[28..34].copy_from_slice(&chaddr);
|
||||
v.extend_from_slice(&[99, 130, 83, 99]);
|
||||
v.extend_from_slice(&opts_after_cookie);
|
||||
v
|
||||
}
|
||||
|
||||
fn client_packet(xid: u32, chaddr: [u8; 6], opts: Vec<DhcpOption>) -> Packet {
|
||||
Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr,
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
fn reply_once(
|
||||
msg: MessageType,
|
||||
additional: Vec<DhcpOption>,
|
||||
offer: Ipv4Addr,
|
||||
req: Packet,
|
||||
) -> Packet {
|
||||
let srv_sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let srv_addr = srv_sock.local_addr().unwrap();
|
||||
struct H {
|
||||
msg: MessageType,
|
||||
add: Option<Vec<DhcpOption>>,
|
||||
offer: Ipv4Addr,
|
||||
}
|
||||
impl server::Handler for H {
|
||||
fn handle_request(&mut self, s: &server::Server, p: Packet) {
|
||||
let add = self.add.take().unwrap();
|
||||
let _ = s.reply(self.msg, add, self.offer, p);
|
||||
}
|
||||
}
|
||||
let h = H {
|
||||
msg,
|
||||
add: Some(additional),
|
||||
offer,
|
||||
};
|
||||
std::thread::spawn(move || {
|
||||
let _ = server::Server::serve(
|
||||
srv_sock,
|
||||
Ipv4Addr::new(192, 168, 1, 1),
|
||||
Ipv4Addr::new(192, 168, 1, 255),
|
||||
h,
|
||||
);
|
||||
});
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let client = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let mut buf = [0u8; 1500];
|
||||
let mut rbuf = [0u8; 1500];
|
||||
client
|
||||
.send_to(&req.encode(&mut buf).to_vec(), srv_addr)
|
||||
.unwrap();
|
||||
client
|
||||
.set_read_timeout(Some(Duration::from_secs(2)))
|
||||
.unwrap();
|
||||
let (n, _) = client.recv_from(&mut rbuf).expect("no reply");
|
||||
unwrap_packet(Packet::from(&rbuf[..n]))
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 1. Exhaustive option-code decode branch matrix
|
||||
// ===========================================================================
|
||||
|
||||
/// RFC 2132: only codes 1, 12, 50, 51, 53, 54, 55, 56 decode as typed
|
||||
/// (3/6 always error — see quirk test below). Every other code 0..=254 must
|
||||
/// decode as transparent `Unrecognized`. Code 255 (END) panics — tested
|
||||
/// separately. Locks that adding a typed branch cannot break wire compat.
|
||||
#[test]
|
||||
fn gap_decode_branch_matrix_typed_vs_passthrough() {
|
||||
// (code, wire bytes, expected typed check)
|
||||
// typed codes with valid payloads
|
||||
match decode_option(&[53, 1, 1]) {
|
||||
Ok((_, DhcpOption::DhcpMessageType(MessageType::Discover))) => {}
|
||||
_ => panic!("53 must be typed"),
|
||||
}
|
||||
match decode_option(&[54, 4, 10, 0, 0, 1]) {
|
||||
Ok((_, DhcpOption::ServerIdentifier(ip))) => {
|
||||
assert_eq!(ip, Ipv4Addr::new(10, 0, 0, 1))
|
||||
}
|
||||
_ => panic!("54 must be typed"),
|
||||
}
|
||||
match decode_option(&[50, 4, 10, 0, 0, 9]) {
|
||||
Ok((_, DhcpOption::RequestedIpAddress(ip))) => {
|
||||
assert_eq!(ip, Ipv4Addr::new(10, 0, 0, 9))
|
||||
}
|
||||
_ => panic!("50 must be typed"),
|
||||
}
|
||||
match decode_option(&[1, 4, 255, 255, 255, 0]) {
|
||||
Ok((_, DhcpOption::SubnetMask(ip))) => {
|
||||
assert_eq!(ip, Ipv4Addr::new(255, 255, 255, 0))
|
||||
}
|
||||
_ => panic!("1 must be typed"),
|
||||
}
|
||||
match decode_option(&[51, 4, 0, 0, 0, 10]) {
|
||||
Ok((_, DhcpOption::IpAddressLeaseTime(10))) => {}
|
||||
_ => panic!("51 must be typed"),
|
||||
}
|
||||
match decode_option(&[55, 2, 1, 3]) {
|
||||
Ok((_, DhcpOption::ParameterRequestList(v))) => assert_eq!(v, vec![1, 3]),
|
||||
_ => panic!("55 must be typed"),
|
||||
}
|
||||
match decode_option(&[12, 1, 65]) {
|
||||
Ok((_, DhcpOption::HostName(s))) => assert_eq!(s, "A"),
|
||||
_ => panic!("12 must be typed"),
|
||||
}
|
||||
match decode_option(&[56, 1, 66]) {
|
||||
Ok((_, DhcpOption::Message(s))) => assert_eq!(s, "B"),
|
||||
_ => panic!("56 must be typed"),
|
||||
}
|
||||
// 3/6 are shaped correctly but the decoder always fails (known quirk)
|
||||
assert!(matches!(
|
||||
decode_option(&[3, 4, 192, 168, 1, 1]),
|
||||
Err(CustomErr::InvalidHlen)
|
||||
));
|
||||
assert!(matches!(
|
||||
decode_option(&[6, 4, 8, 8, 8, 8]),
|
||||
Err(CustomErr::InvalidHlen)
|
||||
));
|
||||
// everything else 0..=254 (minus typed + 3/6/255) is passthrough
|
||||
let typed: std::collections::HashSet<u8> =
|
||||
[1, 3, 6, 12, 50, 51, 53, 54, 55, 56].iter().cloned().collect();
|
||||
for code in 0u8..=254 {
|
||||
if typed.contains(&code) {
|
||||
continue;
|
||||
}
|
||||
match decode_option(&[code, 1, 0xAB]) {
|
||||
Ok((rest, DhcpOption::Unrecognized(r))) => {
|
||||
assert_eq!(r.code, code, "code {}", code);
|
||||
assert_eq!(r.data, vec![0xAB], "code {}", code);
|
||||
assert!(rest.is_empty(), "code {}", code);
|
||||
}
|
||||
_ => panic!("code {} must be Unrecognized passthrough", code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 2. Exact diagnostic strings (log parsing must not break)
|
||||
// ===========================================================================
|
||||
|
||||
/// `MessageType::from` error text is `Invalid DHCP Message Type: <n>`.
|
||||
#[test]
|
||||
fn gap_message_type_from_error_string_exact() {
|
||||
assert_eq!(
|
||||
MessageType::from(9).unwrap_err(),
|
||||
"Invalid DHCP Message Type: 9"
|
||||
);
|
||||
assert_eq!(
|
||||
MessageType::from(0).unwrap_err(),
|
||||
"Invalid DHCP Message Type: 0"
|
||||
);
|
||||
assert_eq!(
|
||||
MessageType::from(255).unwrap_err(),
|
||||
"Invalid DHCP Message Type: 255"
|
||||
);
|
||||
}
|
||||
|
||||
/// `Packet::message_type()` distinguishes missing option vs mistyped option.
|
||||
#[test]
|
||||
fn gap_packet_message_type_error_strings_exact() {
|
||||
let bare = Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid: 1,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [0; 6],
|
||||
options: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
bare.message_type().unwrap_err(),
|
||||
"Packet does not have MessageType option"
|
||||
);
|
||||
let mistyped = Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid: 1,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [0; 6],
|
||||
options: vec![DhcpOption::Unrecognized(RawDhcpOption {
|
||||
code: 53,
|
||||
data: vec![1],
|
||||
})],
|
||||
};
|
||||
assert_eq!(
|
||||
mistyped.message_type().unwrap_err(),
|
||||
"Got wrong enum code 53 for DHCP_MESSAGE_TYPE"
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 3. Encode buffer-size boundaries
|
||||
// ===========================================================================
|
||||
|
||||
/// A 300-byte buffer is the exact maximum the encoder can emit; it must work
|
||||
/// and byte 299 must be END when options fill the packet.
|
||||
#[test]
|
||||
fn gap_encode_300_byte_buffer_exact_fit() {
|
||||
// 54-byte unrecognized + Msg(3) fills 240+3+56+1 = 300 (see truncation test)
|
||||
let p = Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid: 1,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [0; 6],
|
||||
options: vec![
|
||||
DhcpOption::DhcpMessageType(MessageType::Discover),
|
||||
DhcpOption::Unrecognized(RawDhcpOption {
|
||||
code: 200,
|
||||
data: vec![7u8; 54],
|
||||
}),
|
||||
],
|
||||
};
|
||||
let mut buf = [0u8; 300];
|
||||
let enc = p.encode(&mut buf).to_vec();
|
||||
assert_eq!(enc.len(), 300);
|
||||
assert_eq!(enc[299], 255);
|
||||
}
|
||||
|
||||
/// Smallest buffer that can hold an empty-options packet is 241 bytes
|
||||
/// (240 header+cookie + 1 END). 241 works; 240 panics on the return slice.
|
||||
#[test]
|
||||
fn gap_encode_241_byte_buffer_minimal_ok() {
|
||||
let p = client_packet(1, [0; 6], vec![]);
|
||||
let mut buf = [0u8; 241];
|
||||
let enc = p.encode(&mut buf).to_vec();
|
||||
assert_eq!(enc.len(), 241);
|
||||
assert_eq!(enc[240], 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn gap_encode_240_byte_buffer_panics_quirk() {
|
||||
let p = client_packet(1, [0; 6], vec![]);
|
||||
let mut buf = [0u8; 240];
|
||||
let _ = p.encode(&mut buf);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 4. Missing-END edge: trailing byte succeeds, exact consumption panics
|
||||
// ===========================================================================
|
||||
|
||||
/// No END but one trailing garbage byte: decode swallows the error, skips one
|
||||
/// byte via `split_at(1)`, and keeps the options decoded so far.
|
||||
#[test]
|
||||
fn gap_missing_end_with_trailing_byte_succeeds() {
|
||||
let raw = make_raw(
|
||||
1, 6, 0, 0xABCDEF01, 0, [0, 0],
|
||||
[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
|
||||
[1, 2, 3, 4, 5, 6],
|
||||
vec![53, 1, 1, 99], // valid Discover, then garbage 99, no END
|
||||
);
|
||||
let p = unwrap_packet(Packet::from(&raw));
|
||||
assert_eq!(p.options.len(), 1);
|
||||
assert_eq!(p.message_type(), Ok(MessageType::Discover));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 5. Option 255 (END) asymmetry
|
||||
// ===========================================================================
|
||||
|
||||
/// `to_raw` preserves code 255 byte-for-byte, but the decoder asserts
|
||||
/// `code != END`, so such a packet can be encoded yet never decoded.
|
||||
#[test]
|
||||
fn gap_unrecognized_255_to_raw_preserves() {
|
||||
let raw = DhcpOption::Unrecognized(RawDhcpOption {
|
||||
code: 255,
|
||||
data: vec![1],
|
||||
})
|
||||
.to_raw();
|
||||
assert_eq!(raw.code, 255);
|
||||
assert_eq!(raw.data, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn gap_unrecognized_255_packet_decode_panics_quirk() {
|
||||
let p = Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid: 1,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [0; 6],
|
||||
options: vec![DhcpOption::Unrecognized(RawDhcpOption {
|
||||
code: 255,
|
||||
data: vec![1],
|
||||
})],
|
||||
};
|
||||
let mut buf = [0u8; 1500];
|
||||
let enc = p.encode(&mut buf).to_vec();
|
||||
// first option byte on the wire is the END marker -> decode panics
|
||||
assert_eq!(enc[240], 255);
|
||||
let _ = Packet::from(&enc);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 6. Hops / flags wire bytes / siaddr / NAK yiaddr
|
||||
// ===========================================================================
|
||||
|
||||
/// RFC 2131 §2: `hops` 0/16/255 survive decode; server replies reset to 0.
|
||||
#[test]
|
||||
fn gap_hops_edges_preserved_then_reset() {
|
||||
for hops in [0u8, 16, 255] {
|
||||
let raw = make_raw(
|
||||
1, 6, hops, 0x1234, 0, [0, 0],
|
||||
[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
|
||||
[9; 6],
|
||||
vec![53, 1, 1, 255],
|
||||
);
|
||||
assert_eq!(unwrap_packet(Packet::from(&raw)).hops, hops);
|
||||
}
|
||||
let mut req = client_packet(0x99, [9; 6], vec![DhcpOption::DhcpMessageType(
|
||||
MessageType::Discover,
|
||||
)]);
|
||||
req.hops = 16;
|
||||
let rep = reply_once(
|
||||
MessageType::Offer,
|
||||
vec![],
|
||||
Ipv4Addr::new(192, 168, 1, 44),
|
||||
req,
|
||||
);
|
||||
assert_eq!(rep.hops, 0);
|
||||
}
|
||||
|
||||
/// Broadcast flag wire bytes: true -> [128,0], false -> [0,0].
|
||||
#[test]
|
||||
fn gap_broadcast_flag_wire_bytes() {
|
||||
for (bcast, expect) in [(false, [0u8, 0]), (true, [128u8, 0])] {
|
||||
let p = Packet {
|
||||
reply: true,
|
||||
hops: 0,
|
||||
xid: 1,
|
||||
secs: 0,
|
||||
broadcast: bcast,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::UNSPECIFIED,
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [0; 6],
|
||||
options: vec![],
|
||||
};
|
||||
let mut buf = [0u8; 1500];
|
||||
let enc = p.encode(&mut buf).to_vec();
|
||||
assert_eq!(&enc[10..12], &expect, "broadcast={}", bcast);
|
||||
}
|
||||
}
|
||||
|
||||
/// RFC 2131 §2: `siaddr` (next bootstrap server) is always zeroed on reply,
|
||||
/// even when the request carried a non-zero `siaddr`.
|
||||
#[test]
|
||||
fn gap_reply_siaddr_always_zeroed() {
|
||||
let req = Packet {
|
||||
reply: false,
|
||||
hops: 0,
|
||||
xid: 0x55,
|
||||
secs: 0,
|
||||
broadcast: false,
|
||||
ciaddr: Ipv4Addr::UNSPECIFIED,
|
||||
yiaddr: Ipv4Addr::UNSPECIFIED,
|
||||
siaddr: Ipv4Addr::new(5, 6, 7, 8),
|
||||
giaddr: Ipv4Addr::UNSPECIFIED,
|
||||
chaddr: [3; 6],
|
||||
options: vec![DhcpOption::DhcpMessageType(MessageType::Discover)],
|
||||
};
|
||||
let rep = reply_once(
|
||||
MessageType::Offer,
|
||||
vec![],
|
||||
Ipv4Addr::new(192, 168, 1, 45),
|
||||
req,
|
||||
);
|
||||
assert_eq!(rep.siaddr, Ipv4Addr::UNSPECIFIED);
|
||||
}
|
||||
|
||||
/// Library leaves NAK `yiaddr` to the caller (example passes 0.0.0.0 per
|
||||
/// RFC 2131 Table 3, but a non-zero offer IP is preserved as-is).
|
||||
#[test]
|
||||
fn gap_nak_yiaddr_left_to_caller_quirk() {
|
||||
let req = client_packet(0x66, [4; 6], vec![DhcpOption::DhcpMessageType(
|
||||
MessageType::Request,
|
||||
)]);
|
||||
let rep = reply_once(
|
||||
MessageType::Nak,
|
||||
vec![],
|
||||
Ipv4Addr::new(1, 2, 3, 4),
|
||||
req,
|
||||
);
|
||||
assert_eq!(rep.message_type(), Ok(MessageType::Nak));
|
||||
assert_eq!(rep.ciaddr, Ipv4Addr::UNSPECIFIED, "NAK ciaddr forced 0");
|
||||
assert_eq!(
|
||||
rep.yiaddr,
|
||||
Ipv4Addr::new(1, 2, 3, 4),
|
||||
"NAK yiaddr NOT forced (caller decides)"
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 7. Example Offer shape + T1/T2 defaults (RFC 2131 §4.4.5)
|
||||
// ===========================================================================
|
||||
|
||||
/// Example server Offers carry 53/54/51/1 on decode (router/DNS are on the
|
||||
/// wire but dropped by the Router/DNS decode quirk) and never 58/59.
|
||||
#[test]
|
||||
fn gap_example_offer_shape_and_no_t1_t2() {
|
||||
let req = client_packet(0x77, [0xAA; 6], vec![DhcpOption::DhcpMessageType(
|
||||
MessageType::Discover,
|
||||
)]);
|
||||
let rep = reply_once(
|
||||
MessageType::Offer,
|
||||
vec![
|
||||
DhcpOption::IpAddressLeaseTime(86400),
|
||||
DhcpOption::SubnetMask(Ipv4Addr::new(255, 255, 255, 0)),
|
||||
DhcpOption::Router(vec![Ipv4Addr::new(192, 168, 2, 1)]),
|
||||
DhcpOption::DomainNameServer(vec![Ipv4Addr::new(8, 8, 8, 8)]),
|
||||
],
|
||||
Ipv4Addr::new(192, 168, 2, 90),
|
||||
req,
|
||||
);
|
||||
let codes: Vec<u8> = rep.options.iter().map(|o| o.code()).collect();
|
||||
assert_eq!(codes, vec![53, 54, 51, 1], "router/DNS dropped by decode quirk");
|
||||
assert!(rep.option(RENEWAL_TIME_VALUE).is_none(), "no T1");
|
||||
assert!(rep.option(REBINDING_TIME_VALUE).is_none(), "no T2");
|
||||
}
|
||||
|
||||
/// RFC 2131 §4.4.5 defaults when T1/T2 absent: T1 = 0.5*lease,
|
||||
/// T2 = 0.875*lease. Locks the arithmetic clients must use with our replies.
|
||||
#[test]
|
||||
fn gap_t1_t2_defaults_from_lease_time() {
|
||||
fn defaults(lease: u32) -> (u32, u32) {
|
||||
((lease as u64 / 2) as u32, (lease as u64 * 7 / 8) as u32)
|
||||
}
|
||||
assert_eq!(defaults(86400), (43200, 75600));
|
||||
assert_eq!(defaults(3600), (1800, 3150));
|
||||
assert_eq!(defaults(0), (0, 0));
|
||||
// reply used above carries 86400 with no T1/T2, so these apply
|
||||
let rep = reply_once(
|
||||
MessageType::Ack,
|
||||
vec![DhcpOption::IpAddressLeaseTime(86400)],
|
||||
Ipv4Addr::new(192, 168, 2, 91),
|
||||
client_packet(0x78, [0xBB; 6], vec![DhcpOption::DhcpMessageType(
|
||||
MessageType::Request,
|
||||
)]),
|
||||
);
|
||||
match rep.option(IP_ADDRESS_LEASE_TIME) {
|
||||
Some(DhcpOption::IpAddressLeaseTime(86400)) => {}
|
||||
_ => panic!("lease must be 86400"),
|
||||
}
|
||||
assert!(rep.option(RENEWAL_TIME_VALUE).is_none());
|
||||
assert!(rep.option(REBINDING_TIME_VALUE).is_none());
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 8. Overload values, retransmit identity
|
||||
// ===========================================================================
|
||||
|
||||
/// RFC 2132 §9.3 Overload values 1=file, 2=sname, 3=both all passthrough.
|
||||
#[test]
|
||||
fn gap_overload_values_1_2_3_passthrough() {
|
||||
for v in [1u8, 2, 3] {
|
||||
match decode_option(&[52, 1, v]) {
|
||||
Ok((_, DhcpOption::Unrecognized(r))) => {
|
||||
assert_eq!(r.code, OVERLOAD);
|
||||
assert_eq!(r.data, vec![v]);
|
||||
}
|
||||
_ => panic!("overload {} must passthrough", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RFC 2131 §4.4.1 retransmission: same xid+chaddr, growing secs = same
|
||||
/// transaction. Decode must preserve the triple exactly.
|
||||
#[test]
|
||||
fn gap_retransmit_identity_xid_chaddr_secs() {
|
||||
let ch = [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01];
|
||||
let first = unwrap_packet(Packet::from(&make_raw(
|
||||
1, 6, 0, 0x77777777, 0, [0, 0],
|
||||
[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
|
||||
ch,
|
||||
vec![53, 1, 1, 255],
|
||||
)));
|
||||
let retry = unwrap_packet(Packet::from(&make_raw(
|
||||
1, 6, 0, 0x77777777, 5, [0, 0],
|
||||
[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
|
||||
ch,
|
||||
vec![53, 1, 1, 255],
|
||||
)));
|
||||
assert_eq!(first.xid, retry.xid);
|
||||
assert_eq!(first.chaddr, retry.chaddr);
|
||||
assert_eq!(first.secs, 0);
|
||||
assert_eq!(retry.secs, 5);
|
||||
assert_eq!(first.message_type(), retry.message_type());
|
||||
}
|
||||
Reference in New Issue
Block a user