diff --git a/src/packet/ipv4.rs b/src/packet/ipv4.rs index 2424c10..9cd0c2a 100644 --- a/src/packet/ipv4.rs +++ b/src/packet/ipv4.rs @@ -31,6 +31,7 @@ impl IpV4Protocol { pub struct IpV4Packet<'a> { #[allow(unused)] pub protocol: IpV4Protocol, + pub ttl: u8, pub data: &'a [u8], } @@ -56,8 +57,11 @@ impl<'a> IpV4Packet<'a> { None => return Err(Error::UnknownProtocol), }; + let ttl = data[8]; + Ok(Self { protocol: protocol, + ttl, data: &data[header_size..], }) } diff --git a/src/ping.rs b/src/ping.rs index 30dc670..9f6784f 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -1,4 +1,3 @@ -use std::io::Read; use std::net::{IpAddr, SocketAddr}; use std::time::{Duration, SystemTime}; @@ -25,9 +24,16 @@ pub struct PingResult { pub ident: u16, pub seq_cnt: u16, pub payload: Vec, + /// The actual source IP address from the reply packet. + pub source: IpAddr, + #[deprecated(since = "0.7.1", note = "use `source` instead")] pub target: IpAddr, + /// The TTL from the reply IP header. Only available for IPv4 RAW sockets; + /// `None` for IPv4 DGRAM (Linux, no IP header) and all IPv6 responses. + pub ttl: Option, } +#[allow(deprecated)] fn ping_with_socktype( socket_type: Type, addr: IpAddr, @@ -56,7 +62,7 @@ fn ping_with_socktype( payload: payload.unwrap_or(default_payload), }; - let mut socket = if dest.is_ipv4() { + let socket = if dest.is_ipv4() { if request.encode::(&mut buffer[..]).is_err() { return Err(Error::InternalError.into()); } @@ -96,8 +102,17 @@ fn ping_with_socktype( socket.set_read_timeout(Some(timeout - elapsed_time))?; let mut buffer: [u8; 2048] = [0; 2048]; - let n = socket.read(&mut buffer)?; - + // socket2 0.6 recv_from requires &mut [MaybeUninit]; cast is sound + // because MaybeUninit has the same layout as u8. + let (n, src_addr) = socket.recv_from(unsafe { + std::slice::from_raw_parts_mut( + buffer.as_mut_ptr() as *mut std::mem::MaybeUninit, + buffer.len(), + ) + })?; + let source_ip = src_addr.as_socket().map(|s| s.ip()).unwrap_or(addr); + + let mut recv_ttl: Option = None; let reply = if dest.is_ipv4() { // DGRAM socket on Linux may return pure ICMP packet without IP header. if n == ECHO_REQUEST_BUFFER_SIZE { @@ -110,6 +125,7 @@ fn ping_with_socktype( Ok(packet) => packet, Err(_) => return Err(Error::DecodeV4Error.into()), }; + recv_ttl = Some(ipv4_packet.ttl); match EchoReply::decode::(ipv4_packet.data) { Ok(reply) => reply, Err(_) => continue, @@ -135,7 +151,9 @@ fn ping_with_socktype( ident: reply.ident, seq_cnt: reply.seq_cnt, payload: reply.payload.to_vec(), + source: source_ip, target: addr, + ttl: recv_ttl, }); } diff --git a/tests/tests.rs b/tests/tests.rs index 42ecb49..f726795 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -164,7 +164,7 @@ fn ping_result_fields() { assert_eq!(result.seq_cnt, custom_seq); // Check that our custom payload starts the response payload assert!(result.payload.starts_with(&custom_payload)); - assert_eq!(result.target, addr); + assert_eq!(result.source, addr); // Verify payload is not empty and contains our custom data assert!(!result.payload.is_empty()); @@ -195,7 +195,7 @@ fn ping_result_fields_v6() { assert_eq!(result.ident, custom_ident); assert_eq!(result.seq_cnt, custom_seq); - assert_eq!(result.target, addr); + assert_eq!(result.source, addr); // Verify payload is not empty (should be random if not specified) assert!(!result.payload.is_empty()); @@ -219,7 +219,7 @@ fn ping_result_raw_socket() { // Verify PingResult contains expected data assert!(result.rtt > Duration::from_secs(0)); assert!(result.rtt < timeout); - assert_eq!(result.target, addr); + assert_eq!(result.source, addr); // Verify ident and seq_cnt are reasonable values assert!(result.ident > 0);