├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── simple.rs └── src ├── errors.rs ├── lib.rs ├── packet ├── icmp.rs ├── ipv4.rs └── mod.rs ├── ping.rs └── socket ├── mio.rs ├── mod.rs └── tokio.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macOS-latest 14 | rust: 15 | - 1.70.0 16 | - stable 17 | - beta 18 | - nightly 19 | steps: 20 | - name: Checkout sources 21 | uses: actions/checkout@v1 22 | 23 | - name: Install toolchain 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: ${{ matrix.rust }} 27 | override: true 28 | 29 | - name: Run cargo check 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: check 33 | 34 | - name: Run cargo test 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: test 38 | 39 | - name: Install clippy component 40 | run: rustup component add clippy --toolchain ${{ matrix.rust }} 41 | 42 | - name: Run cargo clippy 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | args: -- -D warnings 47 | 48 | - name: Run cargo docs 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: doc 52 | env: 53 | RUSTDOCFLAGS: "-D warnings" 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | **/*.rs.bk 3 | .*.sw[po] 4 | tags 5 | target/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | os: 7 | - linux 8 | - osx 9 | notifications: 10 | webhooks: 11 | - http://heimdallr.thesharp.org/travis 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.4.3] - 2025-05-03 10 | Another small maintenance release. 11 | 12 | ### Changed 13 | - dropped libc dependency 14 | - added `Eq` to `IpV4Protocol` 15 | - improved CI setup 16 | - documented 1.70.0 as the MSRV 17 | 18 | ## [0.4.2] - 2025-03-12 19 | Small maintenance release. 20 | 21 | ### Changed 22 | - update dependencies 23 | 24 | ## [0.4.1] - 2022-03-17 25 | Small maintenance release, only internal changes. 26 | 27 | ### Changed 28 | - updated dependencies 29 | - reformat code 30 | - fix clippy lints 31 | 32 | ## [0.4.0] - 2021-04-27 33 | This is a big change in tokio-ping, now known as tokio-icmp-echo: This is the first release of a fork of tokio-ping, which now works on current versions of the dependencies from the async rust ecosystem. General code cleanup and dependency updates was the theme of this release. 34 | 35 | ### Changed 36 | - update dependencies. Deps with API changes: 37 | - update tokio to 1.5, now on a stable release 38 | - update socket2 to 0.4, now uses `Read` trait for reading from socket 39 | - replace deprecated `failure` crate with `thiserror` 40 | 41 | ## [0.3.0] - 2019-09-23 42 | ### Fixed 43 | - Make PingChainStream lazier ([#13](https://github.com/knsd/tokio-ping/pull/13)) 44 | 45 | ### Changed 46 | - Use Duration instead of f64 47 | 48 | ## [0.2.1] - 2019-08-12 49 | ### Fixed 50 | - IcmpV6 typo ([#8](https://github.com/knsd/tokio-ping/pull/8)) 51 | - Memory leak ([#9](https://github.com/knsd/tokio-ping/pull/9)) 52 | 53 | ## [0.2.0] - 2018-06-17 54 | ### Changed 55 | - Use tokio instead of tokio-core 56 | - Use failure instead of error-chain 57 | - Simplify ICMP packets encoding and parsing 58 | 59 | ## [0.1.2] - 2018-03-18 60 | ### Fixed 61 | - Still EINVAL on ICMPv6 ([#5](https://github.com/knsd/tokio-ping/pull/5)) 62 | - Panic in debug builds ([#4](https://github.com/knsd/tokio-ping/issues/4)) 63 | 64 | ## [0.1.1] - 2018-02-17 65 | ### Fixed 66 | - EINVAL error on ICMPv6 ([#1](https://github.com/knsd/tokio-ping/issues/1), [#2](https://github.com/knsd/tokio-ping/pull/2)) 67 | 68 | ### Changed 69 | - Use socket2 instead of lazy\_socket ([#3](https://github.com/knsd/tokio-ping/pull/3)) 70 | 71 | ## [0.1.0] - 2017-12-06 72 | Initial release. 73 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-icmp-echo" 3 | version = "0.4.3" 4 | license = "MIT/Apache-2.0" 5 | authors = ["Fedor Gogolev ", "Jan Christian Grünhage println!("time={}", time), 30 | None => println!("timeout"), 31 | } 32 | Ok(()) 33 | }) 34 | }); 35 | 36 | tokio::run(future.map_err(|err| { 37 | eprintln!("Error: {}", err) 38 | })) 39 | } 40 | 41 | ``` 42 | 43 | # License 44 | 45 | This project is licensed under either of 46 | 47 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 48 | http://www.apache.org/licenses/LICENSE-2.0) 49 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 50 | http://opensource.org/licenses/MIT) 51 | 52 | at your option. 53 | 54 | ### Contribution 55 | 56 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions. 57 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate tokio; 3 | 4 | extern crate tokio_icmp_echo; 5 | 6 | use crate::futures::{future, StreamExt}; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | let addr = std::env::args().nth(1).unwrap().parse().unwrap(); 11 | 12 | let pinger = tokio_icmp_echo::Pinger::new().await.unwrap(); 13 | let stream = pinger.chain(addr).stream(); 14 | stream 15 | .take(3) 16 | .for_each(|mb_time| { 17 | match mb_time { 18 | Ok(Some(time)) => println!("time={:?}", time), 19 | Ok(None) => println!("timeout"), 20 | Err(err) => println!("error: {:?}", err), 21 | } 22 | future::ready(()) 23 | }) 24 | .await; 25 | } 26 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | pub enum Error { 3 | #[error("invalid procotol")] 4 | InvalidProtocol, 5 | #[error("internal error")] 6 | InternalError, 7 | #[error("io error: {0:?}")] 8 | IoError(#[from] std::io::Error), 9 | } 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! tokio-icmp-echo is an asynchronous ICMP pinging library. 2 | //! 3 | //! The repository is located at . 4 | //! # Usage example 5 | //! 6 | //! Note, sending and receiving ICMP packets requires privileges. 7 | //! 8 | //! ```rust,no_run 9 | //! use futures::{future, StreamExt}; 10 | //! 11 | //! #[tokio::main] 12 | //! async fn main() { 13 | //! let addr = std::env::args().nth(1).unwrap().parse().unwrap(); 14 | //! 15 | //! let pinger = tokio_icmp_echo::Pinger::new().await.unwrap(); 16 | //! let stream = pinger.chain(addr).stream(); 17 | //! stream.take(3).for_each(|mb_time| { 18 | //! match mb_time { 19 | //! Ok(Some(time)) => println!("time={:?}", time), 20 | //! Ok(None) => println!("timeout"), 21 | //! Err(err) => println!("error: {:?}", err) 22 | //! } 23 | //! future::ready(()) 24 | //! }).await; 25 | //! } 26 | //! ``` 27 | 28 | mod errors; 29 | mod packet; 30 | mod ping; 31 | mod socket; 32 | 33 | pub use errors::Error; 34 | pub use ping::{PingChain, PingChainStream, PingFuture, Pinger}; 35 | -------------------------------------------------------------------------------- /src/packet/icmp.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | pub const HEADER_SIZE: usize = 8; 4 | 5 | #[derive(thiserror::Error, Debug)] 6 | pub enum Error { 7 | #[error("invalid size")] 8 | InvalidSize, 9 | #[error("invalid packet")] 10 | InvalidPacket, 11 | } 12 | 13 | pub struct IcmpV4; 14 | pub struct IcmpV6; 15 | 16 | pub trait Proto { 17 | const ECHO_REQUEST_TYPE: u8; 18 | const ECHO_REQUEST_CODE: u8; 19 | const ECHO_REPLY_TYPE: u8; 20 | const ECHO_REPLY_CODE: u8; 21 | } 22 | 23 | impl Proto for IcmpV4 { 24 | const ECHO_REQUEST_TYPE: u8 = 8; 25 | const ECHO_REQUEST_CODE: u8 = 0; 26 | const ECHO_REPLY_TYPE: u8 = 0; 27 | const ECHO_REPLY_CODE: u8 = 0; 28 | } 29 | 30 | impl Proto for IcmpV6 { 31 | const ECHO_REQUEST_TYPE: u8 = 128; 32 | const ECHO_REQUEST_CODE: u8 = 0; 33 | const ECHO_REPLY_TYPE: u8 = 129; 34 | const ECHO_REPLY_CODE: u8 = 0; 35 | } 36 | 37 | pub struct EchoRequest<'a> { 38 | pub ident: u16, 39 | pub seq_cnt: u16, 40 | pub payload: &'a [u8], 41 | } 42 | 43 | impl EchoRequest<'_> { 44 | pub fn encode(&self, buffer: &mut [u8]) -> Result<(), Error> { 45 | buffer[0] = P::ECHO_REQUEST_TYPE; 46 | buffer[1] = P::ECHO_REQUEST_CODE; 47 | 48 | buffer[4] = (self.ident >> 8) as u8; 49 | buffer[5] = self.ident as u8; 50 | buffer[6] = (self.seq_cnt >> 8) as u8; 51 | buffer[7] = self.seq_cnt as u8; 52 | 53 | if (&mut buffer[8..]).write(self.payload).is_err() { 54 | return Err(Error::InvalidSize); 55 | } 56 | 57 | write_checksum(buffer); 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[allow(dead_code)] 63 | pub struct EchoReply<'a> { 64 | pub ident: u16, 65 | pub seq_cnt: u16, 66 | pub payload: &'a [u8], 67 | } 68 | 69 | impl<'a> EchoReply<'a> { 70 | pub fn decode(buffer: &'a [u8]) -> Result { 71 | if buffer.as_ref().len() < HEADER_SIZE { 72 | return Err(Error::InvalidSize); 73 | } 74 | 75 | let type_ = buffer[0]; 76 | let code = buffer[1]; 77 | if type_ != P::ECHO_REPLY_TYPE && code != P::ECHO_REPLY_CODE { 78 | return Err(Error::InvalidPacket); 79 | } 80 | 81 | let ident = (u16::from(buffer[4]) << 8) + u16::from(buffer[5]); 82 | let seq_cnt = (u16::from(buffer[6]) << 8) + u16::from(buffer[7]); 83 | 84 | let payload = &buffer[HEADER_SIZE..]; 85 | 86 | Ok(EchoReply { 87 | ident, 88 | seq_cnt, 89 | payload, 90 | }) 91 | } 92 | } 93 | 94 | fn write_checksum(buffer: &mut [u8]) { 95 | let mut sum = 0u32; 96 | for word in buffer.chunks(2) { 97 | let mut part = u16::from(word[0]) << 8; 98 | if word.len() > 1 { 99 | part += u16::from(word[1]); 100 | } 101 | sum = sum.wrapping_add(u32::from(part)); 102 | } 103 | 104 | while (sum >> 16) > 0 { 105 | sum = (sum & 0xffff) + (sum >> 16); 106 | } 107 | 108 | let sum = !sum as u16; 109 | 110 | buffer[2] = (sum >> 8) as u8; 111 | buffer[3] = (sum & 0xff) as u8; 112 | } 113 | -------------------------------------------------------------------------------- /src/packet/ipv4.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | pub enum Error { 3 | #[error("too small header")] 4 | TooSmallHeader, 5 | #[error("invalid header size")] 6 | InvalidHeaderSize, 7 | #[error("invalid version")] 8 | InvalidVersion, 9 | #[error("unknown protocol")] 10 | UnknownProtocol, 11 | } 12 | 13 | const MINIMUM_PACKET_SIZE: usize = 20; 14 | 15 | #[derive(Debug, Eq, PartialEq)] 16 | pub enum IpV4Protocol { 17 | Icmp, 18 | } 19 | 20 | impl IpV4Protocol { 21 | fn decode(data: u8) -> Option { 22 | match data { 23 | 1 => Some(Self::Icmp), 24 | _ => None, 25 | } 26 | } 27 | } 28 | 29 | pub struct IpV4Packet<'a> { 30 | pub protocol: IpV4Protocol, 31 | pub data: &'a [u8], 32 | } 33 | 34 | impl<'a> IpV4Packet<'a> { 35 | pub fn decode(data: &'a [u8]) -> Result { 36 | if data.len() < MINIMUM_PACKET_SIZE { 37 | return Err(Error::TooSmallHeader); 38 | } 39 | let byte0 = data[0]; 40 | let version = (byte0 & 0xf0) >> 4; 41 | let header_size = 4 * ((byte0 & 0x0f) as usize); 42 | 43 | if version != 4 { 44 | return Err(Error::InvalidVersion); 45 | } 46 | 47 | if data.len() < header_size { 48 | return Err(Error::InvalidHeaderSize); 49 | } 50 | 51 | let protocol = match IpV4Protocol::decode(data[9]) { 52 | Some(protocol) => protocol, 53 | None => return Err(Error::UnknownProtocol), 54 | }; 55 | 56 | Ok(Self { 57 | protocol, 58 | data: &data[header_size..], 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/packet/mod.rs: -------------------------------------------------------------------------------- 1 | mod icmp; 2 | mod ipv4; 3 | 4 | pub use self::icmp::{EchoReply, EchoRequest, IcmpV4, IcmpV6, HEADER_SIZE as ICMP_HEADER_SIZE}; 5 | 6 | pub use self::ipv4::{IpV4Packet, IpV4Protocol}; 7 | -------------------------------------------------------------------------------- /src/ping.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use std::collections::HashMap; 4 | use std::net::{IpAddr, SocketAddr}; 5 | use std::pin::Pin; 6 | use std::sync::Arc; 7 | use std::task::{Context, Poll}; 8 | use std::time::{Duration, Instant}; 9 | 10 | use futures::channel::oneshot; 11 | use futures::future::{select, Future, FutureExt}; 12 | use futures::stream::Stream; 13 | use parking_lot::Mutex; 14 | use rand::random; 15 | use socket2::{Domain, Protocol, Type}; 16 | 17 | use tokio::time::{sleep_until, Sleep}; 18 | 19 | use crate::packet::{EchoReply, EchoRequest, IcmpV4, IcmpV6, ICMP_HEADER_SIZE}; 20 | use crate::packet::{IpV4Packet, IpV4Protocol}; 21 | use crate::socket::{Send, Socket}; 22 | use crate::Error; 23 | 24 | const DEFAULT_TIMEOUT: u64 = 2; 25 | const TOKEN_SIZE: usize = 24; 26 | const ECHO_REQUEST_BUFFER_SIZE: usize = ICMP_HEADER_SIZE + TOKEN_SIZE; 27 | type Token = [u8; TOKEN_SIZE]; 28 | type EchoRequestBuffer = [u8; ECHO_REQUEST_BUFFER_SIZE]; 29 | 30 | #[derive(Clone)] 31 | struct PingState { 32 | inner: Arc>>>, 33 | } 34 | 35 | impl PingState { 36 | fn new() -> Self { 37 | Self { 38 | inner: Arc::new(Mutex::new(HashMap::new())), 39 | } 40 | } 41 | 42 | fn insert(&self, key: Token, value: oneshot::Sender) { 43 | self.inner.lock().insert(key, value); 44 | } 45 | 46 | fn remove(&self, key: &[u8]) -> Option> { 47 | self.inner.lock().remove(key) 48 | } 49 | } 50 | 51 | /// Represent a future that resolves into ping response time, resolves into `None` if timed out. 52 | #[must_use = "futures do nothing unless polled"] 53 | pub struct PingFuture { 54 | inner: PingFutureKind, 55 | } 56 | 57 | enum PingFutureKind { 58 | Normal(Box), 59 | PacketEncodeError, 60 | InvalidProtocol, 61 | } 62 | 63 | struct NormalPingFutureKind { 64 | start_time: Instant, 65 | state: PingState, 66 | token: Token, 67 | sleep: Pin>, 68 | send: Option>, 69 | receiver: oneshot::Receiver, 70 | } 71 | 72 | impl Future for PingFuture { 73 | type Output = Result, Error>; 74 | 75 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 76 | match self.inner { 77 | PingFutureKind::Normal(ref mut normal) => { 78 | let mut swap_send = false; 79 | 80 | if let Some(ref mut send) = normal.send { 81 | match Pin::new(send).poll(cx) { 82 | Poll::Pending => (), 83 | Poll::Ready(Ok(_)) => swap_send = true, 84 | Poll::Ready(Err(_)) => return Poll::Ready(Err(Error::InternalError)), 85 | } 86 | } 87 | 88 | if swap_send { 89 | normal.send = None; 90 | } 91 | 92 | match Pin::new(&mut normal.receiver).poll(cx) { 93 | Poll::Pending => (), 94 | Poll::Ready(Ok(stop_time)) => { 95 | return Poll::Ready(Ok(Some(stop_time - normal.start_time))) 96 | } 97 | Poll::Ready(Err(_)) => return Poll::Ready(Err(Error::InternalError)), 98 | } 99 | 100 | match normal.sleep.as_mut().poll(cx) { 101 | Poll::Pending => (), 102 | Poll::Ready(_) => return Poll::Ready(Ok(None)), 103 | } 104 | } 105 | PingFutureKind::InvalidProtocol => return Poll::Ready(Err(Error::InvalidProtocol)), 106 | PingFutureKind::PacketEncodeError => return Poll::Ready(Err(Error::InternalError)), 107 | } 108 | Poll::Pending 109 | } 110 | } 111 | 112 | impl Drop for PingFuture { 113 | fn drop(&mut self) { 114 | match self.inner { 115 | PingFutureKind::Normal(ref normal) => { 116 | normal.state.remove(&normal.token); 117 | } 118 | PingFutureKind::InvalidProtocol | PingFutureKind::PacketEncodeError => (), 119 | } 120 | } 121 | } 122 | 123 | /// Ping the same host several times. 124 | pub struct PingChain { 125 | pinger: Pinger, 126 | hostname: IpAddr, 127 | ident: Option, 128 | seq_cnt: Option, 129 | timeout: Option, 130 | } 131 | 132 | impl PingChain { 133 | fn new(pinger: Pinger, hostname: IpAddr) -> Self { 134 | Self { 135 | pinger, 136 | hostname, 137 | ident: None, 138 | seq_cnt: None, 139 | timeout: None, 140 | } 141 | } 142 | 143 | /// Set ICMP ident. Default value is randomized. 144 | pub fn ident(mut self, ident: u16) -> Self { 145 | self.ident = Some(ident); 146 | self 147 | } 148 | 149 | /// Set ICMP seq_cnt, this value will be incremented by one for every `send`. 150 | /// Default value is 0. 151 | pub fn seq_cnt(mut self, seq_cnt: u16) -> Self { 152 | self.seq_cnt = Some(seq_cnt); 153 | self 154 | } 155 | 156 | /// Set ping timeout. Default timeout is two seconds. 157 | pub fn timeout(mut self, timeout: Duration) -> Self { 158 | self.timeout = Some(timeout); 159 | self 160 | } 161 | 162 | /// Send ICMP request and wait for response. 163 | pub fn send(&mut self) -> PingFuture { 164 | let ident = match self.ident { 165 | Some(ident) => ident, 166 | None => { 167 | let ident = random(); 168 | self.ident = Some(ident); 169 | ident 170 | } 171 | }; 172 | 173 | let seq_cnt = match self.seq_cnt { 174 | Some(seq_cnt) => { 175 | self.seq_cnt = Some(seq_cnt.wrapping_add(1)); 176 | seq_cnt 177 | } 178 | None => { 179 | self.seq_cnt = Some(1); 180 | 0 181 | } 182 | }; 183 | 184 | let timeout = match self.timeout { 185 | Some(timeout) => timeout, 186 | None => { 187 | let timeout = Duration::from_secs(DEFAULT_TIMEOUT); 188 | self.timeout = Some(timeout); 189 | timeout 190 | } 191 | }; 192 | 193 | self.pinger.ping(self.hostname, ident, seq_cnt, timeout) 194 | } 195 | 196 | /// Create infinite stream of ping response times. 197 | pub fn stream(self) -> PingChainStream { 198 | PingChainStream::new(self) 199 | } 200 | } 201 | 202 | /// Stream of sequential ping response times, iterates `None` if timed out. 203 | pub struct PingChainStream { 204 | chain: PingChain, 205 | future: Option>>, 206 | } 207 | 208 | impl PingChainStream { 209 | fn new(chain: PingChain) -> Self { 210 | Self { 211 | chain, 212 | future: None, 213 | } 214 | } 215 | } 216 | 217 | impl Stream for PingChainStream { 218 | type Item = Result, Error>; 219 | 220 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 221 | let mut future = self 222 | .future 223 | .take() 224 | .unwrap_or_else(|| Box::pin(self.chain.send())); 225 | 226 | match future.as_mut().poll(cx) { 227 | Poll::Ready(result) => Poll::Ready(Some(result)), 228 | Poll::Pending => { 229 | self.future = Some(future); 230 | Poll::Pending 231 | } 232 | } 233 | } 234 | } 235 | 236 | /// ICMP packets sender and receiver. 237 | #[derive(Clone)] 238 | pub struct Pinger { 239 | inner: Arc, 240 | } 241 | 242 | struct PingInner { 243 | sockets: Sockets, 244 | state: PingState, 245 | _v4_finalize: Option>, 246 | _v6_finalize: Option>, 247 | } 248 | 249 | enum Sockets { 250 | V4(Socket), 251 | V6(Socket), 252 | Both { v4: Socket, v6: Socket }, 253 | } 254 | 255 | impl Sockets { 256 | fn new() -> io::Result { 257 | let mb_v4socket = Socket::new(Domain::IPV4, Type::RAW, Protocol::ICMPV4); 258 | let mb_v6socket = Socket::new(Domain::IPV6, Type::RAW, Protocol::ICMPV6); 259 | match (mb_v4socket, mb_v6socket) { 260 | (Ok(v4_socket), Ok(v6_socket)) => Ok(Self::Both { 261 | v4: v4_socket, 262 | v6: v6_socket, 263 | }), 264 | (Ok(v4_socket), Err(_)) => Ok(Self::V4(v4_socket)), 265 | (Err(_), Ok(v6_socket)) => Ok(Self::V6(v6_socket)), 266 | (Err(err), Err(_)) => Err(err), 267 | } 268 | } 269 | 270 | fn v4(&self) -> Option<&Socket> { 271 | match *self { 272 | Self::V4(ref socket) => Some(socket), 273 | Self::Both { ref v4, .. } => Some(v4), 274 | Self::V6(_) => None, 275 | } 276 | } 277 | 278 | fn v6(&self) -> Option<&Socket> { 279 | match *self { 280 | Self::V4(_) => None, 281 | Self::Both { ref v6, .. } => Some(v6), 282 | Self::V6(ref socket) => Some(socket), 283 | } 284 | } 285 | } 286 | 287 | impl Pinger { 288 | /// Create new `Pinger` instance, will fail if unable to create both IPv4 and IPv6 sockets. 289 | pub async fn new() -> Result { 290 | let sockets = Sockets::new()?; 291 | 292 | let state = PingState::new(); 293 | 294 | let v4_finalize = if let Some(v4_socket) = sockets.v4() { 295 | let (s, r) = oneshot::channel(); 296 | let receiver = Receiver::::new(v4_socket.clone(), state.clone()); 297 | tokio::spawn(select(receiver, r).map(|_| ())); 298 | Some(s) 299 | } else { 300 | None 301 | }; 302 | 303 | let v6_finalize = if let Some(v6_socket) = sockets.v6() { 304 | let (s, r) = oneshot::channel(); 305 | let receiver = Receiver::::new(v6_socket.clone(), state.clone()); 306 | tokio::spawn(select(receiver, r).map(|_| ())); 307 | Some(s) 308 | } else { 309 | None 310 | }; 311 | 312 | let inner = PingInner { 313 | sockets, 314 | state, 315 | _v4_finalize: v4_finalize, 316 | _v6_finalize: v6_finalize, 317 | }; 318 | 319 | Ok(Self { 320 | inner: Arc::new(inner), 321 | }) 322 | } 323 | 324 | /// Ping the same host several times. 325 | pub fn chain(&self, hostname: IpAddr) -> PingChain { 326 | PingChain::new(self.clone(), hostname) 327 | } 328 | 329 | /// Send ICMP request and wait for response. 330 | pub fn ping( 331 | &self, 332 | hostname: IpAddr, 333 | ident: u16, 334 | seq_cnt: u16, 335 | timeout: Duration, 336 | ) -> PingFuture { 337 | let (sender, receiver) = oneshot::channel(); 338 | 339 | let deadline = Instant::now() + timeout; 340 | 341 | let token = random(); 342 | self.inner.state.insert(token, sender); 343 | 344 | let dest = SocketAddr::new(hostname, 0); 345 | let mut buffer = [0; ECHO_REQUEST_BUFFER_SIZE]; 346 | 347 | let request = EchoRequest { 348 | ident, 349 | seq_cnt, 350 | payload: &token, 351 | }; 352 | 353 | let (encode_result, mb_socket) = { 354 | if dest.is_ipv4() { 355 | ( 356 | request.encode::(&mut buffer[..]), 357 | self.inner.sockets.v4().cloned(), 358 | ) 359 | } else { 360 | ( 361 | request.encode::(&mut buffer[..]), 362 | self.inner.sockets.v6().cloned(), 363 | ) 364 | } 365 | }; 366 | 367 | let socket = match mb_socket { 368 | Some(socket) => socket, 369 | None => { 370 | return PingFuture { 371 | inner: PingFutureKind::InvalidProtocol, 372 | } 373 | } 374 | }; 375 | 376 | if encode_result.is_err() { 377 | return PingFuture { 378 | inner: PingFutureKind::PacketEncodeError, 379 | }; 380 | } 381 | 382 | let send_future = socket.send_to(buffer, &dest); 383 | 384 | PingFuture { 385 | inner: PingFutureKind::Normal(Box::new(NormalPingFutureKind { 386 | start_time: Instant::now(), 387 | state: self.inner.state.clone(), 388 | token, 389 | sleep: Box::pin(sleep_until(deadline.into())), 390 | send: Some(send_future), 391 | receiver, 392 | })), 393 | } 394 | } 395 | } 396 | 397 | struct Receiver { 398 | socket: Socket, 399 | state: PingState, 400 | _phantom: ::std::marker::PhantomData, 401 | } 402 | 403 | trait ParseReply { 404 | fn reply_payload(data: &[u8]) -> Option<&[u8]>; 405 | } 406 | 407 | impl ParseReply for IcmpV4 { 408 | fn reply_payload(data: &[u8]) -> Option<&[u8]> { 409 | if let Ok(ipv4_packet) = IpV4Packet::decode(data) { 410 | if ipv4_packet.protocol != IpV4Protocol::Icmp { 411 | return None; 412 | } 413 | 414 | if let Ok(reply) = EchoReply::decode::(ipv4_packet.data) { 415 | return Some(reply.payload); 416 | } 417 | } 418 | None 419 | } 420 | } 421 | 422 | impl ParseReply for IcmpV6 { 423 | fn reply_payload(data: &[u8]) -> Option<&[u8]> { 424 | if let Ok(reply) = EchoReply::decode::(data) { 425 | return Some(reply.payload); 426 | } 427 | None 428 | } 429 | } 430 | 431 | impl Receiver { 432 | fn new(socket: Socket, state: PingState) -> Self { 433 | Self { 434 | socket, 435 | state, 436 | _phantom: ::std::marker::PhantomData, 437 | } 438 | } 439 | } 440 | 441 | impl Future for Receiver { 442 | type Output = Result<(), ()>; 443 | 444 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 445 | let mut buffer = [0; 2048]; 446 | match self.socket.recv(&mut buffer, cx) { 447 | Poll::Ready(Ok(bytes)) => { 448 | if let Some(payload) = Message::reply_payload(&buffer[..bytes]) { 449 | let now = Instant::now(); 450 | if let Some(sender) = self.state.remove(payload) { 451 | sender.send(now).unwrap_or_default() 452 | } 453 | } 454 | self.poll(cx) 455 | } 456 | Poll::Pending => Poll::Pending, 457 | Poll::Ready(Err(_)) => Poll::Ready(Err(())), 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/socket/mio.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | 5 | use std::io::Read; 6 | 7 | use mio::unix::SourceFd; 8 | use mio::{event::Source, Interest, Registry, Token}; 9 | use socket2::{Domain, Protocol, SockAddr, Socket as Socket2, Type}; 10 | 11 | pub struct Socket { 12 | socket: Socket2, 13 | } 14 | 15 | impl Socket { 16 | pub fn new(domain: Domain, type_: Type, protocol: Protocol) -> io::Result { 17 | let socket = Socket2::new(domain, type_, Some(protocol))?; 18 | socket.set_nonblocking(true)?; 19 | 20 | Ok(Self { socket }) 21 | } 22 | 23 | pub fn send_to(&self, buf: &[u8], target: &SockAddr) -> io::Result { 24 | self.socket.send_to(buf, target) 25 | } 26 | 27 | pub fn recv(&self, buf: &mut [u8]) -> io::Result { 28 | (&self.socket).read(buf) 29 | } 30 | } 31 | 32 | impl AsRawFd for Socket { 33 | fn as_raw_fd(&self) -> RawFd { 34 | self.socket.as_raw_fd() 35 | } 36 | } 37 | 38 | impl Source for Socket { 39 | fn register(&mut self, poll: &Registry, token: Token, interest: Interest) -> io::Result<()> { 40 | SourceFd(&self.as_raw_fd()).register(poll, token, interest) 41 | } 42 | 43 | fn reregister(&mut self, poll: &Registry, token: Token, interest: Interest) -> io::Result<()> { 44 | SourceFd(&self.as_raw_fd()).reregister(poll, token, interest) 45 | } 46 | 47 | fn deregister(&mut self, poll: &Registry) -> io::Result<()> { 48 | SourceFd(&self.as_raw_fd()).deregister(poll) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/socket/mod.rs: -------------------------------------------------------------------------------- 1 | mod mio; 2 | mod tokio; 3 | 4 | pub use self::tokio::{Send, Socket}; 5 | -------------------------------------------------------------------------------- /src/socket/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::pin::Pin; 3 | use std::sync::Arc; 4 | use std::task::{Context, Poll}; 5 | 6 | use socket2::{Domain, Protocol, SockAddr, Type}; 7 | use std::future::Future; 8 | use std::net::SocketAddr; 9 | use tokio::io::unix::AsyncFd; 10 | 11 | use super::mio; 12 | 13 | #[derive(Clone)] 14 | pub struct Socket { 15 | socket: Arc>, 16 | } 17 | 18 | impl Socket { 19 | pub fn new(domain: Domain, type_: Type, protocol: Protocol) -> io::Result { 20 | let socket = mio::Socket::new(domain, type_, protocol)?; 21 | let socket = AsyncFd::new(socket)?; 22 | Ok(Self { 23 | socket: Arc::new(socket), 24 | }) 25 | } 26 | 27 | pub fn send_to(&self, buf: T, target: &SocketAddr) -> Send 28 | where 29 | T: AsRef<[u8]>, 30 | { 31 | Send { 32 | state: SendState::Writing { 33 | socket: self.socket.clone(), 34 | addr: (*target).into(), 35 | buf, 36 | }, 37 | } 38 | } 39 | 40 | pub fn recv(&self, buffer: &mut [u8], cx: &mut Context<'_>) -> Poll> { 41 | loop { 42 | match self.socket.poll_read_ready(cx) { 43 | Poll::Ready(Ok(mut guard)) => match guard.try_io(|fd| fd.get_ref().recv(buffer)) { 44 | Ok(res) => return Poll::Ready(res), 45 | Err(_) => continue, 46 | }, 47 | Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), 48 | Poll::Pending => return Poll::Pending, 49 | } 50 | } 51 | } 52 | } 53 | 54 | pub struct Send { 55 | state: SendState, 56 | } 57 | 58 | enum SendState { 59 | Writing { 60 | socket: Arc>, 61 | buf: T, 62 | addr: SockAddr, 63 | }, 64 | Empty, 65 | } 66 | 67 | fn send_to( 68 | socket: &Arc>, 69 | buf: &[u8], 70 | target: &SockAddr, 71 | cx: &mut Context<'_>, 72 | ) -> Poll> { 73 | loop { 74 | match socket.poll_write_ready(cx) { 75 | Poll::Ready(Ok(mut guard)) => { 76 | match guard.try_io(|fd| fd.get_ref().send_to(buf, target)) { 77 | Ok(res) => return Poll::Ready(res), 78 | Err(_) => continue, 79 | } 80 | } 81 | Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), 82 | Poll::Pending => return Poll::Pending, 83 | } 84 | } 85 | } 86 | 87 | impl Future for Send 88 | where 89 | T: AsRef<[u8]> + Unpin, 90 | { 91 | type Output = Result<(), io::Error>; 92 | 93 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 94 | match self.state { 95 | SendState::Writing { 96 | ref socket, 97 | ref buf, 98 | ref addr, 99 | } => { 100 | let n = match send_to(socket, buf.as_ref(), addr, cx) { 101 | Poll::Ready(Ok(n)) => n, 102 | Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), 103 | Poll::Pending => return Poll::Pending, 104 | }; 105 | if n != buf.as_ref().len() { 106 | return Poll::Ready(Err(io::Error::new( 107 | io::ErrorKind::Other, 108 | "failed to send entire packet", 109 | ))); 110 | } 111 | } 112 | SendState::Empty => panic!("poll a Send after it's done"), 113 | } 114 | 115 | match ::std::mem::replace(&mut self.state, SendState::Empty) { 116 | SendState::Writing { .. } => Poll::Ready(Ok(())), 117 | SendState::Empty => unreachable!(), 118 | } 119 | } 120 | } 121 | --------------------------------------------------------------------------------