mirror of
https://github.com/pj1234678/RustyDHCP.git
synced 2026-10-15 00:06:42 +08:00
583 lines
19 KiB
Rust
583 lines
19 KiB
Rust
//! 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());
|
|
}
|