Probe one server. `Ok(Some(_))` if it answered with a well-formed status within `timeout`; `Ok(None)` if it did not respond (unreachable / wrong protocol).
(
addr: A,
timeout: Duration,
)
| 20 | /// Probe one server. `Ok(Some(_))` if it answered with a well-formed status within |
| 21 | /// `timeout`; `Ok(None)` if it did not respond (unreachable / wrong protocol). |
| 22 | pub fn query_server<A: ToSocketAddrs>( |
| 23 | addr: A, |
| 24 | timeout: Duration, |
| 25 | ) -> io::Result<Option<ServerStatus>> { |
| 26 | let target = addr |
| 27 | .to_socket_addrs()? |
| 28 | .next() |
| 29 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no address resolved"))?; |
| 30 | |
| 31 | let bind_any: SocketAddr = if target.is_ipv6() { |
| 32 | "[::]:0".parse() |
| 33 | } else { |
| 34 | "0.0.0.0:0".parse() |
| 35 | } |
| 36 | .expect("static bind address"); |
| 37 | let socket = UdpSocket::bind(bind_any)?; |
| 38 | socket.set_read_timeout(Some(timeout))?; |
| 39 | |
| 40 | let request = framing::frame_request(&codec::encode_enum_request(codec::MAGIC_APP), 1); |
| 41 | let sent = Instant::now(); |
| 42 | socket.send_to(&request, target)?; |
| 43 | |
| 44 | let deadline = sent + timeout; |
| 45 | let mut buf = [0u8; MSG_HEADER_LEN + SESSION_PACKET_4]; |
| 46 | loop { |
| 47 | match socket.recv_from(&mut buf) { |
| 48 | Ok((n, from)) => { |
| 49 | // Ignore datagrams from a different host (basic anti-spoofing) or that |
| 50 | // are not a valid response; keep waiting until the deadline. |
| 51 | if from.ip() == target.ip() { |
| 52 | if let Some(session) = framing::unframe_response(&buf[..n]) |
| 53 | .and_then(codec::decode_session_response) |
| 54 | { |
| 55 | let ping_ms = u32::try_from(sent.elapsed().as_millis()).unwrap_or(u32::MAX); |
| 56 | return Ok(Some(ServerStatus { session, ping_ms })); |
| 57 | } |
| 58 | } |
| 59 | match deadline.checked_duration_since(Instant::now()) { |
| 60 | Some(remaining) if !remaining.is_zero() => { |
| 61 | socket.set_read_timeout(Some(remaining))?; |
| 62 | } |
| 63 | _ => return Ok(None), |
| 64 | } |
| 65 | } |
| 66 | Err(e) |
| 67 | if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut => |
| 68 | { |
| 69 | return Ok(None); |
| 70 | } |
| 71 | Err(e) => return Err(e), |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | #[cfg(test)] |
| 77 | mod tests { |