libtun 0.1.0

a cross-platform(macosx, linux) tunnel library
Documentation
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(())
}