diff --git a/examples/ping4.rs b/examples/ping4.rs index de612be..c9835fc 100644 --- a/examples/ping4.rs +++ b/examples/ping4.rs @@ -11,23 +11,58 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::net::Ipv4Addr; +use std::{ + net::Ipv4Addr, + time::{Duration, Instant}, +}; +use icmp_socket::packet::WithEchoRequest; use icmp_socket::socket::IcmpSocket; use icmp_socket::*; pub fn main() { let address = std::env::args().nth(1).unwrap_or("127.0.0.1".to_owned()); + let parsed_addr = address.parse::().unwrap(); + let packet_handler = |pkt: Icmpv4Packet, send_time: Instant, addr: Ipv4Addr| -> Option<()> { + let now = Instant::now(); + let elapsed = now - send_time; + if addr == parsed_addr { + // TODO + if let Icmpv4Message::EchoReply { + identifier: _, + sequence, + payload, + } = pkt.message + { + println!( + "Ping {} seq={} time={}ms size={}", + addr, + sequence, + (elapsed.as_micros() as f64) / 1000.0, + payload.len() + ); + } else { + //eprintln!("Discarding non-reply {:?}", pkt); + return None; + } + Some(()) + } else { + eprintln!("Discarding packet from {}", addr); + None + } + }; let mut socket4 = IcmpSocket4::new().unwrap(); socket4 .bind("0.0.0.0".parse::().unwrap()) .unwrap(); - let mut echo_socket = echo::EchoSocket::new(socket4); - echo_socket - .send_ping( - address.parse::().unwrap(), + // TODO(jwall): The first packet we recieve will be the one we sent. + // We need to implement packet filtering for the socket. + let mut sequence = 0 as u16; + loop { + let packet = Icmpv4Packet::with_echo_request( 42, - &[ + sequence, + vec![ 0x20, 0x20, 0x75, 0x73, 0x74, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x65, 0x73, 0x68, 0x20, 0x77, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x20, 0x74, 0x69, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x61, 0x20, 0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68, 0x20, 0x20, 0x6b, 0x6e, @@ -35,12 +70,23 @@ pub fn main() { ], ) .unwrap(); - let _ = echo_socket.recv_ping(); - let (resp, _addr) = echo_socket.recv_ping().unwrap(); - println!( - "seq: {}, identifier: {} payload: {}", - resp.sequence, - resp.identifier, - resp.payload.len() - ); + let send_time = Instant::now(); + socket4 + .send_to(address.parse::().unwrap(), packet) + .unwrap(); + loop { + let (resp, sock_addr) = match socket4.rcv_with_timeout(Duration::from_secs(1)) { + Ok(tpl) => tpl, + Err(e) => { + //eprintln!("{:?}", e); + break; + } + }; + if packet_handler(resp, send_time, *sock_addr.as_inet().unwrap().ip()).is_some() { + std::thread::sleep(Duration::from_secs(1)); + break; + } + } + sequence = sequence.wrapping_add(1); + } } diff --git a/examples/ping6.rs b/examples/ping6.rs index 2c2e8e4..7cff2bd 100644 --- a/examples/ping6.rs +++ b/examples/ping6.rs @@ -11,21 +11,56 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::net::Ipv6Addr; +use std::{ + net::Ipv6Addr, + time::{Duration, Instant}, +}; +use icmp_socket::packet::WithEchoRequest; use icmp_socket::socket::IcmpSocket; use icmp_socket::*; pub fn main() { let address = std::env::args().nth(1).unwrap_or("::1".to_owned()); + let parsed_addr = address.parse::().unwrap(); + let packet_handler = |pkt: Icmpv6Packet, send_time: Instant, addr: Ipv6Addr| -> Option<()> { + let now = Instant::now(); + let elapsed = now - send_time; + if addr == parsed_addr { + // TODO + if let Icmpv6Message::EchoReply { + identifier: _, + sequence, + payload, + } = pkt.message + { + println!( + "Ping {} seq={} time={}ms size={}", + addr, + sequence, + (elapsed.as_micros() as f64) / 1000.0, + payload.len() + ); + } else { + //eprintln!("Discarding non-reply {:?}", pkt); + return None; + } + Some(()) + } else { + eprintln!("Discarding packet from {}", addr); + None + } + }; let mut socket6 = IcmpSocket6::new().unwrap(); socket6.bind("::0".parse::().unwrap()).unwrap(); - let mut echo_socket = echo::EchoSocket::new(socket6); - echo_socket - .send_ping( - address.parse::().unwrap(), + // TODO(jwall): The first packet we recieve will be the one we sent. + // We need to implement packet filtering for the socket. + let mut sequence = 0 as u16; + loop { + let packet = Icmpv6Packet::with_echo_request( 42, - &[ + sequence, + vec![ 0x20, 0x20, 0x75, 0x73, 0x74, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x65, 0x73, 0x68, 0x20, 0x77, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x20, 0x74, 0x69, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x61, 0x20, 0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68, 0x20, 0x20, 0x6b, 0x6e, @@ -33,14 +68,23 @@ pub fn main() { ], ) .unwrap(); - // TODO(jwall): The first packet we recieve will be the one we sent. - // We need to implement packet filtering for the socket. - let _ = echo_socket.recv_ping(); - let (resp, _addr) = echo_socket.recv_ping().unwrap(); - println!( - "seq: {}, identifier: {} payload: {}", - resp.sequence, - resp.identifier, - resp.payload.len() - ); + let send_time = Instant::now(); + socket6 + .send_to(address.parse::().unwrap(), packet) + .unwrap(); + loop { + let (resp, sock_addr) = match socket6.rcv_with_timeout(Duration::from_secs(1)) { + Ok(tpl) => tpl, + Err(e) => { + //eprintln!("{:?}", e); + break; + } + }; + if packet_handler(resp, send_time, *sock_addr.as_inet6().unwrap().ip()).is_some() { + std::thread::sleep(Duration::from_millis(1000)); + break; + } + } + sequence = sequence.wrapping_add(1); + } } diff --git a/src/echo.rs b/src/echo.rs deleted file mode 100644 index fb579bb..0000000 --- a/src/echo.rs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2021 Jeremy Wall -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use std::convert::{From, TryFrom, TryInto}; - -use socket2::SockAddr; - -use crate::{ - packet::{Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet, WithEchoRequest}, - socket::IcmpSocket, -}; - -#[derive(Debug)] -pub struct EchoResponse { - pub identifier: u16, - pub sequence: u16, - pub payload: Vec, -} - -impl TryFrom for EchoResponse { - type Error = std::io::Error; - - fn try_from(pkt: Icmpv6Packet) -> Result { - if let Icmpv6Message::EchoReply { - identifier, - sequence, - payload, - } = pkt.message - { - Ok(EchoResponse { - identifier, - sequence, - payload, - }) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Incorrect icmpv6 message: {:?}, code: {}", - pkt.message, pkt.code - ), - )) - } - } -} - -impl TryFrom for EchoResponse { - type Error = std::io::Error; - - fn try_from(pkt: Icmpv4Packet) -> Result { - if let Icmpv4Message::EchoReply { - identifier, - sequence, - payload, - } = pkt.message - { - Ok(EchoResponse { - identifier, - sequence, - payload, - }) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Incorrect icmpv4 message: {:?}, code: {}", - pkt.message, pkt.code - ), - )) - } - } -} - -pub struct EchoSocket { - sequence: u16, - inner: S, -} - -// TODO(jwall): Make this a trait -impl EchoSocket -where - S: IcmpSocket, - S::PacketType: WithEchoRequest - + TryInto - + std::fmt::Debug, -{ - pub fn new(sock: S) -> Self { - EchoSocket { - inner: sock, - sequence: 0, - } - } - - pub fn set_max_hops(&mut self, hops: u32) { - self.inner.set_max_hops(hops); - } - - pub fn send_ping( - &mut self, - dest: S::AddrType, - identifier: u16, - payload: &[u8], - ) -> std::io::Result<()> { - let packet = - S::PacketType::with_echo_request(identifier, self.sequence, payload.to_owned())?; - self.sequence += 1; - self.inner.send_to(dest, packet)?; - Ok(()) - } - - pub fn recv_ping(&mut self) -> std::io::Result<(EchoResponse, SockAddr)> { - let (packet, addr) = self.inner.rcv_from()?; - Ok((packet.try_into()?, addr)) - } -} - -impl From for EchoSocket -where - S: IcmpSocket, - S::PacketType: WithEchoRequest - + TryInto - + std::fmt::Debug, -{ - fn from(sock: S) -> Self { - EchoSocket::new(sock) - } -} diff --git a/src/lib.rs b/src/lib.rs index af4f17a..004be81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod echo; pub mod packet; pub mod socket; -pub use echo::EchoSocket; pub use packet::{Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet}; pub use socket::{IcmpSocket, IcmpSocket4, IcmpSocket6}; diff --git a/src/socket.rs b/src/socket.rs index 339ffe9..28986a0 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::{Into, TryFrom, TryInto}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::{ + convert::{Into, TryFrom, TryInto}, + time::Duration, +}; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; @@ -34,6 +37,11 @@ pub trait IcmpSocket { fn send_to(&mut self, dest: Self::AddrType, packet: Self::PacketType) -> std::io::Result<()>; fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)>; + + fn rcv_with_timeout( + &mut self, + timeout: Duration, + ) -> std::io::Result<(Self::PacketType, SockAddr)>; } pub struct Opts { @@ -85,6 +93,16 @@ impl IcmpSocket for IcmpSocket4 { } fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)> { + self.inner.set_read_timeout(None); + let (read_count, addr) = self.inner.recv_from(&mut self.buf)?; + Ok((self.buf[0..read_count].try_into()?, addr)) + } + + fn rcv_with_timeout( + &mut self, + timeout: Duration, + ) -> std::io::Result<(Self::PacketType, SockAddr)> { + self.inner.set_read_timeout(Some(timeout)); let (read_count, addr) = self.inner.recv_from(&mut self.buf)?; Ok((self.buf[0..read_count].try_into()?, addr)) } @@ -149,6 +167,16 @@ impl IcmpSocket for IcmpSocket6 { } fn rcv_from(&mut self) -> std::io::Result<(Self::PacketType, SockAddr)> { + self.inner.set_read_timeout(None); + let (read_count, addr) = self.inner.recv_from(&mut self.buf)?; + Ok((self.buf[0..read_count].try_into()?, addr)) + } + + fn rcv_with_timeout( + &mut self, + timeout: Duration, + ) -> std::io::Result<(Self::PacketType, SockAddr)> { + self.inner.set_read_timeout(Some(timeout)); let (read_count, addr) = self.inner.recv_from(&mut self.buf)?; Ok((self.buf[0..read_count].try_into()?, addr)) }