use rand;
use smol::Async;
use snafu::{ResultExt, Snafu};
use socket2::{Domain, Protocol, Socket, Type};
use std::io::Write;
use std::net::{IpAddr, SocketAddr};
pub const HEADER_SIZE: usize = 8;
const TOKEN_SIZE: usize = 24;
const ECHO_REQUEST_BUFFER_SIZE: usize = HEADER_SIZE + TOKEN_SIZE;
type Token = [u8; TOKEN_SIZE];
pub struct IcmpV4;
pub trait Proto {
const ECHO_REQUEST_TYPE: u8;
const ECHO_REQUEST_CODE: u8;
const ECHO_REPLY_TYPE: u8;
const ECHO_REPLY_CODE: u8;
}
impl Proto for IcmpV4 {
const ECHO_REQUEST_TYPE: u8 = 8;
const ECHO_REQUEST_CODE: u8 = 0;
const ECHO_REPLY_TYPE: u8 = 0;
const ECHO_REPLY_CODE: u8 = 0;
}
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("write to buffer fail: {}", source))]
WriteBufferError { source: std::io::Error },
#[snafu(display("setup socket({}) fail: {}", step, source))]
SetupSocket {
step: &'static str,
source: std::io::Error,
},
}
pub struct EchoRequest<'a> {
pub ident: u16,
pub seq_cnt: u16,
pub payload: &'a [u8],
}
fn write_checksum(buffer: &mut [u8]) {
let mut sum = 0u32;
for word in buffer.chunks(2) {
let mut part = u16::from(word[0]) << 8;
if word.len() > 1 {
part += u16::from(word[1]);
}
sum = sum.wrapping_add(u32::from(part));
}
while (sum >> 16) > 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
let sum = !sum as u16;
buffer[2] = (sum >> 8) as u8;
buffer[3] = (sum & 0xff) as u8;
}
impl<'a> EchoRequest<'a> {
pub fn encode<P: Proto>(&self, buffer: &mut [u8]) -> Result<(), Error> {
buffer[0] = P::ECHO_REQUEST_TYPE;
buffer[1] = P::ECHO_REQUEST_CODE;
buffer[4] = (self.ident >> 8) as u8;
buffer[5] = self.ident as u8;
buffer[6] = (self.seq_cnt >> 8) as u8;
buffer[7] = self.seq_cnt as u8;
(&mut buffer[8..])
.write(self.payload)
.context(WriteBufferError)?;
write_checksum(buffer);
Ok(())
}
}
pub async fn send_ping(addr: IpAddr) -> Result<(), Error> {
let dest = SocketAddr::new(addr, 0);
let mut buffer = [0; ECHO_REQUEST_BUFFER_SIZE];
let payload: &mut Token = &mut rand::random();
for i in 0..payload.len() {
payload[i] = i as u8;
}
let request = EchoRequest {
ident: rand::random(),
seq_cnt: 1,
payload: payload,
};
request.encode::<IcmpV4>(&mut buffer[..])?;
let socket = Socket::new(Domain::ipv4(), Type::raw(), Some(Protocol::icmpv4()))
.context(SetupSocket { step: "init" })?;
socket
.set_ttl(64)
.context(SetupSocket { step: "set_ttl" })?;
let socket = Async::new(socket).context(SetupSocket { step: "to_async" })?;
socket
.write_with(|io| io.send_to(&mut buffer, &dest.into()))
.await
.context(SetupSocket { step: "send" })?;
Ok(())
}