feat: init commit

This commit is contained in:
2023-01-17 22:52:32 +08:00
commit 740203af92
9 changed files with 1062 additions and 0 deletions

44
src/config.rs Normal file
View File

@@ -0,0 +1,44 @@
use std::net;
use std::net::ToSocketAddrs;
use anyhow::bail;
use crate::error::RingError;
#[derive(Debug, Clone)]
pub struct Config {
pub count: u16,
pub packet_size: usize,
pub ttl: u32,
pub timeout: u64,
pub interval: u64,
pub id: u16,
pub sequence: u16,
pub destination: Address,
}
#[derive(Debug, Clone)]
pub struct Address {
pub ip: net::IpAddr,
pub raw: String,
}
impl Address {
pub fn parse(host: &str) -> anyhow::Result<Address> {
let raw = String::from(host);
let opt = host.parse::<net::IpAddr>().ok();
match opt {
Some(ip) => Ok(Address { ip: ip, raw: raw }),
None => {
let new = format!("{}:{}", host, 0);
let mut addrs = new.to_socket_addrs()?;
if let Some(addr) = addrs.next() {
Ok(Address {
ip: addr.ip(),
raw: raw,
})
} else {
bail!(RingError::InvalidConfig(String::from("address")))
}
}
}
}
}

19
src/error.rs Normal file
View File

@@ -0,0 +1,19 @@
#![allow(dead_code)]
#[derive(Debug, Clone, thiserror::Error)]
pub enum RingError {
#[error("invaild config")]
InvalidConfig(String),
#[error("internal error")]
InternalError,
#[error("invalid buffer size")]
InvalidBufferSize,
#[error("invalid packet")]
InvalidPacket,
#[error("timeout")]
Timeout,
}

7
src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
mod error;
mod ping;
mod config;
pub use error::RingError;
pub use ping::Pinger;
pub use config::*;

66
src/main.rs Normal file
View File

@@ -0,0 +1,66 @@
use clap::Parser;
use ring::{Address, Config, Pinger};
use std::format;
fn main() {
let args = Args::parse();
match Pinger::new(args.as_config()) {
Ok(pinger) => {
pinger.run().unwrap_or_else( |e|{
exit(format!("Error on run ping: {}", e));
});
},
Err(e) => {
exit(format!("Error on init: {}", e));
}
}
}
fn exit(msg: String) {
eprintln!("{}", msg);
std::process::exit(1);
}
/// ping but with rust, rust + ping -> ring
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// Count of ping times
#[arg(short, default_value_t = 4)]
count: u16,
/// Ping packet size
#[arg(short = 's', default_value_t = 64)]
packet_size: usize,
/// Ping ttl
#[arg(short = 't', default_value_t = 64)]
ttl: u32,
/// Ping timeout seconds
#[arg(short = 'w', default_value_t = 1)]
timeout: u64,
/// Ping interval duration milliseconds
#[arg(short = 'i', default_value_t = 1000)]
interval: u64,
/// Ping destination, ip or domain
#[arg(value_parser=Address::parse)]
destination: Address,
}
impl Args {
fn as_config(&self) -> Config {
Config {
count: self.count,
packet_size: self.packet_size,
ttl: self.ttl,
timeout: self.timeout,
interval: self.interval,
id: rand::random::<u16>(),
sequence: 1,
destination: self.destination.clone(),
}
}
}

161
src/ping.rs Normal file
View File

@@ -0,0 +1,161 @@
use std::net::{self, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use pnet::packet::icmp::echo_reply::EchoReplyPacket;
use pnet::packet::icmp::echo_request::{IcmpCodes, MutableEchoRequestPacket};
use pnet::packet::icmp::IcmpTypes;
use pnet::packet::{util, MutablePacket, Packet};
use signal_hook::consts::{SIGINT, SIGTERM};
use socket2::{Domain, Protocol, Socket, Type};
use crossbeam_channel::{self, bounded, select, Receiver};
use crate::config::Config;
use crate::error::RingError;
#[derive(Clone)]
pub struct Pinger {
config: Config,
dest: SocketAddr,
socket: Arc<Socket>,
}
impl Pinger {
pub fn new(config: Config) -> std::io::Result<Self> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
let src = SocketAddr::new(net::IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let dest = SocketAddr::new(config.destination.ip, 0);
socket.bind(&src.into())?;
socket.set_ttl(config.ttl)?;
socket.set_read_timeout(Some(Duration::new(config.timeout, 0)))?;
socket.set_write_timeout(Some(Duration::new(config.timeout, 0)))?;
Ok(Self {
config: config,
dest: dest,
socket: Arc::new(socket),
})
}
pub fn run(&self) -> std::io::Result<()> {
println!(
"PING {}({})",
self.config.destination.raw, self.config.destination.ip
);
let now = Instant::now();
let send = Arc::new(AtomicU64::new(0));
let _send = send.clone();
let this = Arc::new(self.clone());
let (sx, rx) = bounded(this.config.count as usize);
thread::spawn(move || {
for i in 0..this.config.count {
let _this = this.clone();
sx.send(thread::spawn(move || _this.ping(i))).unwrap();
_send.fetch_add(1, Ordering::SeqCst);
if i < this.config.count - 1 {
thread::sleep(Duration::from_millis(this.config.interval));
}
}
drop(sx);
});
let success = Arc::new(AtomicU64::new(0));
let _success = success.clone();
let (summary_s, summary_r) = bounded(1);
thread::spawn(move || {
for handle in rx.iter() {
if let Some(res) = handle.join().ok() {
if res.is_ok() {
_success.fetch_add(1, Ordering::SeqCst);
}
}
}
summary_s.send(()).unwrap();
});
let stop = signal_notify()?;
select!(
recv(stop) -> sig => {
if let Some(s) = sig.ok() {
println!("Receive signal {:?}", s);
}
},
recv(summary_r) -> summary => {
if let Some(e) = summary.err() {
println!("Error on summary: {}", e);
}
},
);
let total = now.elapsed().as_micros() as f64 / 1000.0;
let send = send.load(Ordering::SeqCst);
let success = success.load(Ordering::SeqCst);
let loss_rate = if send > 0 {
(send - success) * 100 / send
} else {
0
};
println!("\n--- {} ping statistics ---", self.config.destination.raw);
println!(
"{} packets transmitted, {} received, {}% packet loss, time {}ms",
send, success, loss_rate, total,
);
Ok(())
}
pub fn ping(&self, seq_offset: u16) -> anyhow::Result<()> {
// create icmp request packet
let mut buf = vec![0; self.config.packet_size];
let mut icmp =
MutableEchoRequestPacket::new(&mut buf[..]).ok_or(RingError::InvalidBufferSize)?;
icmp.set_icmp_type(IcmpTypes::EchoRequest);
icmp.set_icmp_code(IcmpCodes::NoCode);
icmp.set_sequence_number(self.config.sequence + seq_offset);
icmp.set_identifier(self.config.id);
icmp.set_checksum(util::checksum(icmp.packet(), 1));
let start = Instant::now();
// send request
self.socket.send_to(icmp.packet_mut(), &self.dest.into())?;
// handle recv
let mut mem_buf =
unsafe { &mut *(buf.as_mut_slice() as *mut [u8] as *mut [std::mem::MaybeUninit<u8>]) };
let (size, _) = self.socket.recv_from(&mut mem_buf)?;
let duration = start.elapsed().as_micros() as f64 / 1000.0;
let reply = EchoReplyPacket::new(&buf).ok_or(RingError::InvalidPacket)?;
println!(
"{} bytes from {}: icmp_seq={} ttl={} time={:.2}ms",
size,
self.config.destination.ip,
reply.get_sequence_number(),
self.config.ttl,
duration
);
Ok(())
}
}
fn signal_notify() -> std::io::Result<Receiver<i32>> {
let (s, r) = bounded(1);
let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM])?;
thread::spawn(move || {
for signal in signals.forever() {
s.send(signal).unwrap();
break;
}
});
Ok(r)
}