libtun 0.1.0

a cross-platform(macosx, linux) tunnel library
Documentation
use std::fmt;
use std::net;

use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Cursor, Write};
type ByteOrder = BigEndian;

#[cfg(target_os = "macos")]
static LOOPBACK_SIZE: usize = 4;
#[cfg(target_os = "macos")]
static LOOPBACK: [u8; 4] = [0, 0, 0, 2];
#[cfg(not(target_os = "macos"))]
static LOOPBACK_SIZE: usize = 0;
#[cfg(not(target_os = "macos"))]
static LOOPBACK: [u8; 0] = [];

pub struct Packet {
  inner: Vec<u8>,
}

impl Packet {
  pub fn new(buf: &[u8]) -> Packet {
    let mut inner = vec![0; buf.len() - LOOPBACK_SIZE];
    inner.copy_from_slice(&buf[LOOPBACK_SIZE..]);
    return Packet { inner };
  }

  pub fn out(&self) -> Vec<u8> {
    let mut inner = vec![0; self.inner.len() + LOOPBACK_SIZE];
    inner[..LOOPBACK_SIZE].copy_from_slice(&LOOPBACK[..]);
    inner[LOOPBACK_SIZE..].copy_from_slice(self.as_ref());
    inner
  }

  pub fn source_addr(&self) -> net::IpAddr {
    return self.read_ip(&self.inner[12..16]);
  }

  pub fn write_source_addr(&mut self, addr: net::IpAddr) {
    write_ip(&mut self.inner[12..16], addr);
  }

  pub fn dest_addr(&self) -> net::IpAddr {
    return self.read_ip(&self.inner[16..20]);
  }

  pub fn write_dest_addr(&mut self, addr: net::IpAddr) {
    write_ip(&mut self.inner[16..20], addr);
  }

  pub fn total_length(&self) -> usize {
    self.read_u16(2) as _
  }

  pub fn version(&self) -> u8 {
    self.read_bits(0, 4)
  }

  pub fn ihl(&self) -> u8 {
    self.read_bits(4, 4)
  }

  pub fn type_of_service(&self) -> u8 {
    return self.read_u8(1);
  }

  pub fn identification(&self) -> u16 {
    return self.read_u16(4);
  }

  pub fn flags(&self) -> u8 {
    return self.read_bits(48, 3);
  }

  pub fn ttl(&self) -> u8 {
    return self.read_u8(8);
  }

  /// This field indicates the next level protocol used in the data
  /// portion of the internet datagram.  The values for various protocols
  /// are specified in "Assigned Numbers" [9].
  /// 0   ICMP
  /// 6   TCP
  /// 17  UDP
  pub fn protocol(&self) -> u8 {
    return self.read_u8(9);
  }

  pub fn checksum(&self) -> u16 {
    self.read_u16(10)
  }

  pub fn write_checksum(&mut self, checksum: u16) {
    Cursor::new(&mut self.inner[10..])
      .write_u16::<ByteOrder>(checksum)
      .unwrap();
  }

  pub fn cal_checksum(&self) -> u16 {
    let mut result: u32 = 0xffff;
    let mut cursor = Cursor::new(&self.inner[..]);
    let end = self.ihl() as u64 * 4;
    while cursor.position() < end {
      let val = cursor.read_u16::<ByteOrder>().unwrap();
      if cursor.position() == 12 {
        continue;
      }
      result += val as u32;
      if result > 0xffff {
        result -= 0xffff;
      }
    }
    return !result as u16;
  }

  pub fn payload(&self) -> &[u8] {
    &self.inner[self.ihl() as usize * 4..]
  }

  pub fn payload_mut(&mut self) -> &mut [u8] {
    let start = self.ihl();
    &mut self.inner[start as usize * 4..]
  }
}

impl Packet {
  fn cursor(&self, off: usize) -> Cursor<&[u8]> {
    Cursor::new(&self.inner[off..])
  }

  fn read_u16(&self, off: usize) -> u16 {
    self.cursor(off).read_u16::<ByteOrder>().unwrap() as _
  }

  fn read_u8(&self, off: usize) -> u8 {
    self.cursor(off).read_u8().unwrap()
  }

  fn read_bits(&self, bit_start: usize, bit_size: u8) -> u8 {
    assert!((bit_start % 8) + bit_size as usize <= 8);
    let val = self.cursor(bit_start / 8).read_u8().unwrap();
    // println!("val: {}", val);
    let mask: u8 = ((2 ^ bit_size) - 1) as _;
    // println!("mask: {}", mask);
    let bit_off: u8 = (bit_start % 8) as _;
    // println!("bit_off: {}", bit_off);
    let shift = 8 - bit_size - bit_off;
    // println!("shift: {}", shift);
    (val & (mask << shift)) >> shift
  }

  fn read_ip(&self, buf: &[u8]) -> net::IpAddr {
    let ip = Cursor::new(buf).read_u32::<ByteOrder>().unwrap();
    net::IpAddr::from(net::Ipv4Addr::from(ip))
  }
}

fn write_ip(dest: &mut [u8], addr: net::IpAddr) {
  match addr {
    net::IpAddr::V4(addr) => Cursor::new(dest)
      .write_u32::<ByteOrder>(addr.into())
      .unwrap(),
    _ => panic!("unsupport ipv6"),
  }
}

impl AsRef<[u8]> for Packet {
  fn as_ref(&self) -> &[u8] {
    self.inner.as_ref()
  }
}

impl fmt::Debug for Packet {
  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    write!(
      f,
      "{}->{}, {}",
      self.source_addr(),
      self.dest_addr(),
      self.inner.len()
    )
  }
}

#[cfg(test)]
mod test {
  use crate::*;
  use std::net::IpAddr;

  #[test]
  fn test() {
    let data = &[
      0, 0, 0, 2, 69, 0, 0, 84, 155, 56, 0, 0, 64, 1, 72, 29, 192, 168, 11, 3, 192, 168, 11, 0,
      162, 232, 154, 232, 87, 72, 0, 0, 94, 235, 247, 137, 0, 10, 196, 76, 8, 9, 10, 11, 12, 13,
      14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
      37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
    ];
    let mut pkt = Packet::new(data);
    assert_eq!(pkt.protocol(), 1);
    assert_eq!(pkt.source_addr(), "192.168.11.3".parse::<IpAddr>().unwrap());
    assert_eq!(pkt.dest_addr(), "192.168.11.0".parse::<IpAddr>().unwrap());
    assert_eq!(pkt.ihl(), 5);
    let mut icmp = protocol::icmp::from(pkt.payload_mut());
    let echo = icmp.echo();
    assert_eq!(echo.typ(), 0);
    assert_eq!(echo.checksum(), echo.cal_checksum());
  }

  #[test]
  fn ping() {
    let data = &[
      0, 0, 0, 2, 69, 0, 0, 84, 114, 154, 0, 0, 64, 1, 112, 187, 192, 168, 11, 0, 192, 168, 11, 3,
      8, 0, 14, 229, 245, 73, 0, 0, 94, 235, 248, 49, 0, 0, 177, 176, 8, 9, 10, 11, 12, 13, 14, 15,
      16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
      39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
    ];
    let mut pkt = Packet::new(data);
    assert_eq!(pkt.protocol(), 1);
    assert_eq!(pkt.dest_addr(), "192.168.11.3".parse::<IpAddr>().unwrap());
    assert_eq!(pkt.source_addr(), "192.168.11.0".parse::<IpAddr>().unwrap());
    assert_eq!(pkt.ihl(), 5);
    let mut icmp = protocol::icmp::from(pkt.payload_mut());
    let echo = icmp.echo();
    assert_eq!(echo.typ(), 8);
    assert_eq!(echo.checksum(), echo.cal_checksum());
    {
      assert_eq!(echo.data().len(), 56);
      let data = &[];
      assert_eq!(echo.data(), data);
    }
  }
}