test(portmap): find the unmap among resent maps

The NAT-PMP client resends a map the gateway has not answered within
250 ms, and the mock gateway records every request, so a slow mock put a
resent map where the test expected the unmap and failed it. The test now
looks for the lifetime-0 request among whatever arrives after the drop.

On Windows the mock's answer to that resend meets an already-closed
socket, and the next receive reports the bounce as a connection reset,
which ended the mock's loop before the unmap. It now carries on past it.
This commit is contained in:
Michel de Graaf
2026-09-10 14:36:32 +02:00
parent 8854f8d005
commit 3c5a3dd648
+25 -13
View File
@@ -418,7 +418,18 @@ mod tests {
thread::spawn(move || { thread::spawn(move || {
let mut buf = [0u8; 64]; let mut buf = [0u8; 64];
// Answer many requests (initial map + cleanup unmap + any renews). // Answer many requests (initial map + cleanup unmap + any renews).
while let Ok((n, from)) = socket.recv_from(&mut buf) { loop {
let (n, from) = match socket.recv_from(&mut buf) {
Ok(received) => received,
// Windows reports a reply that met an already-closed
// client socket on the next receive; the gateway is fine.
Err(e)
if e.kind() == std::io::ErrorKind::ConnectionReset =>
{
continue;
}
Err(_) => break,
};
let request = buf[..n].to_vec(); let request = buf[..n].to_vec();
let internal = u16::from_be_bytes([request[4], request[5]]); let internal = u16::from_be_bytes([request[4], request[5]]);
let lifetime = u32::from_be_bytes([ let lifetime = u32::from_be_bytes([
@@ -465,20 +476,21 @@ mod tests {
); );
// Dropping the mapper must remove the mapping (a lifetime-0 request). // Dropping the mapper must remove the mapping (a lifetime-0 request).
// A map the gateway was slow to answer is sent again, and that resend
// can arrive first.
drop(mapper); drop(mapper);
let unmap_req = requests let unmapped = std::iter::from_fn(|| {
.recv_timeout(StdDuration::from_secs(3)) requests.recv_timeout(StdDuration::from_secs(3)).ok()
.expect("dropping the mapper should send an unmap request"); })
assert_eq!( .any(|request| {
u32::from_be_bytes([ u32::from_be_bytes([
unmap_req[8], request[8],
unmap_req[9], request[9],
unmap_req[10], request[10],
unmap_req[11] request[11],
]), ]) == 0
0, });
"cleanup should request lifetime 0" assert!(unmapped, "cleanup should request lifetime 0");
);
} }
/// A minimal in-process UPnP IGD: an HTTP server that answers every SOAP /// A minimal in-process UPnP IGD: an HTTP server that answers every SOAP