From a43a6772c96877868f88b2166c7ceae45b5c5b79 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 15 Oct 2021 00:38:42 +0200 Subject: [PATCH] socket/dhcp: add retransmission/timeout tests --- src/socket/dhcpv4.rs | 192 +++++++++++++++++++++++++++++++++---------- 1 file changed, 148 insertions(+), 44 deletions(-) diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index a1fe717..b5bd824 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -545,30 +545,43 @@ mod test { socket.process(&cx, &ip_repr, &udp_repr, &payload) } - fn recv(socket: &mut Dhcpv4Socket, timestamp: Instant, mut f: F) - where - F: FnMut(Result<(Ipv4Repr, UdpRepr, DhcpRepr)>), - { + fn recv( + socket: &mut Dhcpv4Socket, + timestamp: Instant, + reprs: &[(Ipv4Repr, UdpRepr, DhcpRepr)], + ) { let mut cx = Context::DUMMY.clone(); cx.now = timestamp; - let result = socket.dispatch(&cx, |(mut ip_repr, udp_repr, dhcp_repr)| { - assert_eq!(ip_repr.protocol, IpProtocol::Udp); - assert_eq!( - ip_repr.payload_len, - udp_repr.header_len() + dhcp_repr.buffer_len() - ); - // We validated the payload len, change it to 0 to make equality testing easier - ip_repr.payload_len = 0; + let mut i = 0; - net_trace!("recv: {:?}", ip_repr); - net_trace!(" {:?}", udp_repr); - net_trace!(" {:?}", dhcp_repr); - Ok(f(Ok((ip_repr, udp_repr, dhcp_repr)))) - }); - match result { - Ok(()) => (), - Err(e) => f(Err(e)), + while socket.poll_at(&cx) <= PollAt::Time(timestamp) { + let _ = socket.dispatch(&cx, |(mut ip_repr, udp_repr, dhcp_repr)| { + assert_eq!(ip_repr.protocol, IpProtocol::Udp); + assert_eq!( + ip_repr.payload_len, + udp_repr.header_len() + dhcp_repr.buffer_len() + ); + + // We validated the payload len, change it to 0 to make equality testing easier + ip_repr.payload_len = 0; + + net_trace!("recv: {:?}", ip_repr); + net_trace!(" {:?}", udp_repr); + net_trace!(" {:?}", dhcp_repr); + + let got_repr = (ip_repr, udp_repr, dhcp_repr); + match reprs.get(i) { + Some(want_repr) => assert_eq!(want_repr, &got_repr), + None => panic!("Too many reprs emitted"), + } + i += 1; + Ok(()) + }); + } + + if i != reprs.len() { + panic!("Too few reprs emitted. Wanted {}, got {}", reprs.len(), i); } } @@ -584,20 +597,12 @@ mod test { } macro_rules! recv { - ($socket:ident, [$( $repr:expr ),*]) => ({ - $( recv!($socket, Ok($repr)); )* - recv!($socket, Err(Error::Exhausted)) + ($socket:ident, $reprs:expr) => ({ + recv!($socket, time 0, $reprs); }); - ($socket:ident, time $time:expr, [$( $repr:expr ),*]) => ({ - $( recv!($socket, time $time, Ok($repr)); )* - recv!($socket, time $time, Err(Error::Exhausted)) + ($socket:ident, time $time:expr, $reprs:expr) => ({ + recv(&mut $socket, Instant::from_millis($time), &$reprs); }); - ($socket:ident, $result:expr) => - (recv!($socket, time 0, $result)); - ($socket:ident, time $time:expr, $result:expr) => - (recv(&mut $socket, Instant::from_millis($time), |result| { - assert_eq!(result, $result) - })); } #[cfg(feature = "log")] @@ -710,7 +715,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(DNS_IPS), - lease_duration: Some(60), + lease_duration: Some(1000), ..DHCP_DEFAULT }; @@ -735,7 +740,7 @@ mod test { router: Some(SERVER_IP), subnet_mask: Some(MASK_24), dns_servers: Some(DNS_IPS), - lease_duration: Some(60), + lease_duration: Some(1000), ..DHCP_DEFAULT }; @@ -776,8 +781,8 @@ mod test { address: SERVER_IP, identifier: SERVER_IP, }, - renew_at: Instant::from_secs(30), - expires_at: Instant::from_secs(60), + renew_at: Instant::from_secs(500), + expires_at: Instant::from_secs(1000), }); s @@ -804,43 +809,142 @@ mod test { })) ); - match s.state { + match &s.state { ClientState::Renewing(r) => { - assert_eq!(r.renew_at, Instant::from_secs(30)); - assert_eq!(r.expires_at, Instant::from_secs(60)); + assert_eq!(r.renew_at, Instant::from_secs(500)); + assert_eq!(r.expires_at, Instant::from_secs(1000)); } _ => panic!("Invalid state"), } } + #[test] + fn test_discover_retransmit() { + let mut s = socket(); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + recv!(s, time 1_000, []); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + recv!(s, time 11_000, []); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + + // check after retransmits it still works + send!(s, time 20_000, (IP_RECV, UDP_RECV, DHCP_OFFER)); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + } + + #[test] + fn test_request_retransmit() { + let mut s = socket(); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + send!(s, time 0, (IP_RECV, UDP_RECV, DHCP_OFFER)); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 1_000, []); + recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 6_000, []); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 15_000, []); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + + // check after retransmits it still works + send!(s, time 20_000, (IP_RECV, UDP_RECV, DHCP_ACK)); + + match &s.state { + ClientState::Renewing(r) => { + assert_eq!(r.renew_at, Instant::from_secs(20 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(20 + 1000)); + } + _ => panic!("Invalid state"), + } + } + + #[test] + fn test_request_timeout() { + let mut s = socket(); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + send!(s, time 0, (IP_RECV, UDP_RECV, DHCP_OFFER)); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 30_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + + // After 5 tries and 70 seconds, it gives up. + // 5 + 5 + 10 + 10 + 20 = 70 + recv!(s, time 70_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + + // check it still works + send!(s, time 60_000, (IP_RECV, UDP_RECV, DHCP_OFFER)); + recv!(s, time 60_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + } + #[test] fn test_renew() { let mut s = socket_bound(); recv!(s, []); assert_eq!(s.poll(), None); - recv!(s, time 40_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); assert_eq!(s.poll(), None); match &s.state { ClientState::Renewing(r) => { // the expiration still hasn't been bumped, because // we haven't received the ACK yet - assert_eq!(r.expires_at, Instant::from_secs(60)); + assert_eq!(r.expires_at, Instant::from_secs(1000)); } _ => panic!("Invalid state"), } - send!(s, time 40_000, (IP_RECV, UDP_RECV, DHCP_ACK)); + send!(s, time 500_000, (IP_RECV, UDP_RECV, DHCP_ACK)); assert_eq!(s.poll(), None); match &s.state { ClientState::Renewing(r) => { // NOW the expiration gets bumped - assert_eq!(r.renew_at, Instant::from_secs(40 + 30)); - assert_eq!(r.expires_at, Instant::from_secs(40 + 60)); + assert_eq!(r.renew_at, Instant::from_secs(500 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(500 + 1000)); } _ => panic!("Invalid state"), } } + + #[test] + fn test_renew_retransmit() { + let mut s = socket_bound(); + + recv!(s, []); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + recv!(s, time 749_000, []); + recv!(s, time 750_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + recv!(s, time 874_000, []); + recv!(s, time 875_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + + // check it still works + send!(s, time 875_000, (IP_RECV, UDP_RECV, DHCP_ACK)); + match &s.state { + ClientState::Renewing(r) => { + // NOW the expiration gets bumped + assert_eq!(r.renew_at, Instant::from_secs(875 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(875 + 1000)); + } + _ => panic!("Invalid state"), + } + } + + #[test] + fn test_renew_timeout() { + let mut s = socket_bound(); + + recv!(s, []); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + recv!(s, time 999_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + recv!(s, time 1_000_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + match &s.state { + ClientState::Discovering(_) => {} + _ => panic!("Invalid state"), + } + } }