mirror of https://github.com/dswd/vpncloud.git
Documentation and code cleanup
This commit is contained in:
parent
e31e5c362d
commit
97af7bcef4
103
src/device.rs
103
src/device.rs
|
@ -5,8 +5,9 @@
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::io::{Result as IoResult, Error as IoError, Read, Write};
|
use std::io::{Result as IoResult, Error as IoError, Read, Write};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use super::types::{Error, Type};
|
use super::types::Error;
|
||||||
|
|
||||||
extern {
|
extern {
|
||||||
fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32;
|
fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32;
|
||||||
|
@ -14,14 +15,55 @@ extern {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The type of a tun/tap device
|
||||||
|
#[derive(RustcDecodable, Debug, Clone, Copy)]
|
||||||
|
pub enum Type {
|
||||||
|
/// Tun interface: This interface transports IP packets.
|
||||||
|
Tun,
|
||||||
|
/// Tap interface: This insterface transports Ethernet frames.
|
||||||
|
Tap
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Type {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
Type::Tun => write!(formatter, "tun"),
|
||||||
|
Type::Tap => write!(formatter, "tap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Represents a tun/tap device
|
||||||
pub struct Device {
|
pub struct Device {
|
||||||
fd: fs::File,
|
fd: fs::File,
|
||||||
ifname: String
|
ifname: String,
|
||||||
|
type_: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
|
/// Creates a new tun/tap device
|
||||||
|
///
|
||||||
|
/// This method creates a new device of the `type_` kind with the name `ifname`.
|
||||||
|
///
|
||||||
|
/// The `ifname` must be an interface name not longer than 31 bytes. It can contain the string
|
||||||
|
/// `%d` which will be replaced with the next free index number that guarantees that the
|
||||||
|
/// interface name will be free. In this case, the `ifname()` method can be used to obtain the
|
||||||
|
/// final interface name.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will return an error when the underlying system call fails. Common cases are:
|
||||||
|
/// - The special device file `/dev/net/tun` does not exist or is not accessible by the current
|
||||||
|
/// user.
|
||||||
|
/// - The interface name is invalid or already in use.
|
||||||
|
/// - The current user does not have enough permissions to create tun/tap devices (this
|
||||||
|
/// requires root permissions).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This method panics if the interface name is longer than 31 bytes.
|
||||||
pub fn new(ifname: &str, type_: Type) -> IoResult<Self> {
|
pub fn new(ifname: &str, type_: Type) -> IoResult<Self> {
|
||||||
let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun"));
|
let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun"));
|
||||||
|
// Add trailing \0 to interface name
|
||||||
let mut ifname_string = String::with_capacity(32);
|
let mut ifname_string = String::with_capacity(32);
|
||||||
ifname_string.push_str(ifname);
|
ifname_string.push_str(ifname);
|
||||||
ifname_string.push('\0');
|
ifname_string.push('\0');
|
||||||
|
@ -33,30 +75,75 @@ impl Device {
|
||||||
};
|
};
|
||||||
match res {
|
match res {
|
||||||
0 => {
|
0 => {
|
||||||
|
// Remove trailing \0 from name
|
||||||
while ifname_c.last() == Some(&0) {
|
while ifname_c.last() == Some(&0) {
|
||||||
ifname_c.pop();
|
ifname_c.pop();
|
||||||
}
|
}
|
||||||
Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap()})
|
Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap(), type_: type_})
|
||||||
},
|
},
|
||||||
_ => Err(IoError::last_os_error())
|
_ => Err(IoError::last_os_error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
/// Returns the interface name of this device.
|
||||||
pub fn dummy(ifname: &str, path: &str) -> IoResult<Self> {
|
|
||||||
Ok(Device{fd: try!(fs::OpenOptions::new().create(true).read(true).write(true).open(path)), ifname: ifname.to_string()})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ifname(&self) -> &str {
|
pub fn ifname(&self) -> &str {
|
||||||
&self.ifname
|
&self.ifname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this device
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[inline]
|
||||||
|
pub fn get_type(&self) -> Type {
|
||||||
|
self.type_
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a dummy device based on an existing file
|
||||||
|
///
|
||||||
|
/// This method opens a regular or special file and reads from it to receive packets and
|
||||||
|
/// writes to it to send packets. This method does not use a networking device and therefore
|
||||||
|
/// can be used for testing.
|
||||||
|
///
|
||||||
|
/// The parameter `path` is the file that should be used. Special files like `/dev/null`,
|
||||||
|
/// named pipes and unix sockets can be used with this method.
|
||||||
|
///
|
||||||
|
/// Both `ifname` and `type_` parameters have no effect.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will return an error if the file can not be opened for reading and writing.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn dummy(ifname: &str, path: &str, type_: Type) -> IoResult<Self> {
|
||||||
|
Ok(Device{
|
||||||
|
fd: try!(fs::OpenOptions::new().create(true).read(true).write(true).open(path)),
|
||||||
|
ifname: ifname.to_string(),
|
||||||
|
type_: type_
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a packet/frame from the device
|
||||||
|
///
|
||||||
|
/// This method reads one packet or frame (depending on the device type) into the `buffer`.
|
||||||
|
/// The `buffer` must be large enough to hold a packet/frame of maximum size, otherwise the
|
||||||
|
/// packet/frame will be split.
|
||||||
|
/// The method will block until a packet/frame is ready to be read.
|
||||||
|
/// On success, the method will return the amount of bytes read into the buffer (starting at
|
||||||
|
/// position 0).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will return an error if the underlying read call fails.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, Error> {
|
pub fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, Error> {
|
||||||
self.fd.read(&mut buffer).map_err(|_| Error::TunTapDevError("Read error"))
|
self.fd.read(&mut buffer).map_err(|_| Error::TunTapDevError("Read error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a packet/frame to the device
|
||||||
|
///
|
||||||
|
/// This method writes one packet or frame (depending on the device type) from `data` to the
|
||||||
|
/// device.
|
||||||
|
/// The method will block until the packet/frame has been written.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will return an error if the underlying read call fails.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write(&mut self, data: &[u8]) -> Result<(), Error> {
|
pub fn write(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||||
match self.fd.write_all(data) {
|
match self.fd.write_all(data) {
|
||||||
|
|
|
@ -11,9 +11,19 @@ use fnv::FnvHasher;
|
||||||
use super::types::{Error, Table, Protocol, Address};
|
use super::types::{Error, Table, Protocol, Address};
|
||||||
use super::util::{now, Time, Duration};
|
use super::util::{now, Time, Duration};
|
||||||
|
|
||||||
|
/// An ethernet frame dissector
|
||||||
|
///
|
||||||
|
/// This dissector is able to extract the source and destination addresses of ethernet frames.
|
||||||
|
///
|
||||||
|
/// If the ethernet frame contains a VLAN tag, both addresses will be prefixed with that tag,
|
||||||
|
/// resulting in 8-byte addresses. Additional nested tags will be ignored.
|
||||||
pub struct Frame;
|
pub struct Frame;
|
||||||
|
|
||||||
impl Protocol for Frame {
|
impl Protocol for Frame {
|
||||||
|
/// Parses an ethernet frame and extracts the source and destination addresses
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will fail when the given data is not a valid ethernet frame.
|
||||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||||
if data.len() < 14 {
|
if data.len() < 14 {
|
||||||
return Err(Error::ParseError("Frame is too short"));
|
return Err(Error::ParseError("Frame is too short"));
|
||||||
|
@ -51,19 +61,27 @@ struct SwitchTableValue {
|
||||||
|
|
||||||
type Hash = BuildHasherDefault<FnvHasher>;
|
type Hash = BuildHasherDefault<FnvHasher>;
|
||||||
|
|
||||||
|
|
||||||
|
/// A table used to implement a learning switch
|
||||||
|
///
|
||||||
|
/// This table is a simple hash map between an address and the destination peer. It learns
|
||||||
|
/// addresses as they are seen and forgets them after some time.
|
||||||
pub struct SwitchTable {
|
pub struct SwitchTable {
|
||||||
|
/// The table storing the actual mapping
|
||||||
table: HashMap<Address, SwitchTableValue, Hash>,
|
table: HashMap<Address, SwitchTableValue, Hash>,
|
||||||
cache: Option<(Address, SocketAddr)>,
|
/// Timeout period for forgetting learnt addresses
|
||||||
timeout: Duration
|
timeout: Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwitchTable {
|
impl SwitchTable {
|
||||||
|
/// Creates a new switch table
|
||||||
pub fn new(timeout: Duration) -> Self {
|
pub fn new(timeout: Duration) -> Self {
|
||||||
SwitchTable{table: HashMap::default(), cache: None, timeout: timeout}
|
SwitchTable{table: HashMap::default(), timeout: timeout}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Table for SwitchTable {
|
impl Table for SwitchTable {
|
||||||
|
/// Forget addresses that have not been seen for the configured timeout
|
||||||
fn housekeep(&mut self) {
|
fn housekeep(&mut self) {
|
||||||
let now = now();
|
let now = now();
|
||||||
let mut del: Vec<Address> = Vec::new();
|
let mut del: Vec<Address> = Vec::new();
|
||||||
|
@ -76,9 +94,9 @@ impl Table for SwitchTable {
|
||||||
info!("Forgot address {}", key);
|
info!("Forgot address {}", key);
|
||||||
self.table.remove(&key);
|
self.table.remove(&key);
|
||||||
}
|
}
|
||||||
self.cache = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Learns the given address, inserting it in the hash map
|
||||||
#[inline]
|
#[inline]
|
||||||
fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) {
|
fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) {
|
||||||
let value = SwitchTableValue{address: addr, timeout: now()+self.timeout as Time};
|
let value = SwitchTableValue{address: addr, timeout: now()+self.timeout as Time};
|
||||||
|
@ -87,6 +105,7 @@ impl Table for SwitchTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves a peer for an address if it is inside the hash map
|
||||||
#[inline]
|
#[inline]
|
||||||
fn lookup(&mut self, key: &Address) -> Option<SocketAddr> {
|
fn lookup(&mut self, key: &Address) -> Option<SocketAddr> {
|
||||||
match self.table.get(key) {
|
match self.table.get(key) {
|
||||||
|
@ -95,11 +114,13 @@ impl Table for SwitchTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an address from the map and returns whether something has been removed
|
||||||
#[inline]
|
#[inline]
|
||||||
fn remove(&mut self, key: &Address) -> bool {
|
fn remove(&mut self, key: &Address) -> bool {
|
||||||
self.table.remove(key).is_some()
|
self.table.remove(key).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removed all addresses associated with a certain peer
|
||||||
fn remove_all(&mut self, addr: &SocketAddr) {
|
fn remove_all(&mut self, addr: &SocketAddr) {
|
||||||
let mut remove = Vec::new();
|
let mut remove = Vec::new();
|
||||||
for (key, val) in &self.table {
|
for (key, val) in &self.table {
|
||||||
|
|
31
src/ip.rs
31
src/ip.rs
|
@ -11,10 +11,18 @@ use fnv::FnvHasher;
|
||||||
use super::types::{Protocol, Error, Table, Address};
|
use super::types::{Protocol, Error, Table, Address};
|
||||||
|
|
||||||
|
|
||||||
|
/// An IP packet dissector
|
||||||
|
///
|
||||||
|
/// This dissector is able to extract the source and destination ip addresses of ipv4 packets and
|
||||||
|
/// ipv6 packets.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Packet;
|
pub struct Packet;
|
||||||
|
|
||||||
impl Protocol for Packet {
|
impl Protocol for Packet {
|
||||||
|
/// Parses an ip packet and extracts the source and destination addresses
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This method will fail when the given data is not a valid ipv4 and ipv6 packet.
|
||||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||||
if data.len() < 1 {
|
if data.len() < 1 {
|
||||||
return Err(Error::ParseError("Empty header"));
|
return Err(Error::ParseError("Empty header"));
|
||||||
|
@ -51,39 +59,56 @@ struct RoutingEntry {
|
||||||
|
|
||||||
type Hash = BuildHasherDefault<FnvHasher>;
|
type Hash = BuildHasherDefault<FnvHasher>;
|
||||||
|
|
||||||
|
/// A prefix-based routing table
|
||||||
|
///
|
||||||
|
/// This table contains a mapping of prefixes associated with peer addresses.
|
||||||
|
/// To speed up lookup, prefixes are grouped into full bytes and map to a list of prefixes with
|
||||||
|
/// more fine grained prefixes.
|
||||||
pub struct RoutingTable(HashMap<Vec<u8>, Vec<RoutingEntry>, Hash>);
|
pub struct RoutingTable(HashMap<Vec<u8>, Vec<RoutingEntry>, Hash>);
|
||||||
|
|
||||||
impl RoutingTable {
|
impl RoutingTable {
|
||||||
|
/// Creates a new empty routing table
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
RoutingTable(HashMap::default())
|
RoutingTable(HashMap::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Table for RoutingTable {
|
impl Table for RoutingTable {
|
||||||
|
/// Learns the given address, inserting it in the hash map
|
||||||
fn learn(&mut self, addr: Address, prefix_len: Option<u8>, address: SocketAddr) {
|
fn learn(&mut self, addr: Address, prefix_len: Option<u8>, address: SocketAddr) {
|
||||||
|
// If prefix length is not set, treat the whole addess as significant
|
||||||
let prefix_len = match prefix_len {
|
let prefix_len = match prefix_len {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => addr.len * 8
|
None => addr.len * 8
|
||||||
};
|
};
|
||||||
info!("New routing entry: {}/{} => {}", addr, prefix_len, address);
|
info!("New routing entry: {}/{} => {}", addr, prefix_len, address);
|
||||||
|
// Round the prefix length down to the next multiple of 8 and extraxt a prefix of that
|
||||||
|
// length.
|
||||||
let group_len = prefix_len as usize / 8;
|
let group_len = prefix_len as usize / 8;
|
||||||
assert!(group_len <= 16);
|
assert!(group_len <= 16);
|
||||||
let mut group_bytes = Vec::with_capacity(group_len);
|
let mut group_bytes = Vec::with_capacity(group_len);
|
||||||
group_bytes.extend_from_slice(&addr.data[0..group_len]);
|
group_bytes.extend_from_slice(&addr.data[0..group_len]);
|
||||||
|
// Create an entry
|
||||||
let routing_entry = RoutingEntry{address: address, bytes: addr.data, prefix_len: prefix_len};
|
let routing_entry = RoutingEntry{address: address, bytes: addr.data, prefix_len: prefix_len};
|
||||||
|
// Add the entry to the routing table, creating a new list of the prefix group is empty.
|
||||||
match self.0.entry(group_bytes) {
|
match self.0.entry(group_bytes) {
|
||||||
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry),
|
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry),
|
||||||
hash_map::Entry::Vacant(entry) => { entry.insert(vec![routing_entry]); () }
|
hash_map::Entry::Vacant(entry) => { entry.insert(vec![routing_entry]); () }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves a peer for an address if it is inside the routing table
|
||||||
fn lookup(&mut self, addr: &Address) -> Option<SocketAddr> {
|
fn lookup(&mut self, addr: &Address) -> Option<SocketAddr> {
|
||||||
let len = addr.len as usize;
|
let len = addr.len as usize;
|
||||||
let mut found = None;
|
let mut found = None;
|
||||||
let mut found_len: isize = -1;
|
let mut found_len: isize = -1;
|
||||||
|
// Iterate over the prefix length from longest prefix group to shortest (empty) prefix
|
||||||
|
// group
|
||||||
for i in 0..len+1 {
|
for i in 0..len+1 {
|
||||||
if let Some(group) = self.0.get(&addr.data[0..len-i]) {
|
if let Some(group) = self.0.get(&addr.data[0..len-i]) {
|
||||||
|
// If the group is not empty, check every entry
|
||||||
for entry in group {
|
for entry in group {
|
||||||
|
// Calculate the match length of the address and the prefix
|
||||||
let mut match_len = 0;
|
let mut match_len = 0;
|
||||||
for j in 0..addr.len as usize {
|
for j in 0..addr.len as usize {
|
||||||
let b = addr.data[j] ^ entry.bytes[j];
|
let b = addr.data[j] ^ entry.bytes[j];
|
||||||
|
@ -94,6 +119,8 @@ impl Table for RoutingTable {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the full prefix matches and the match is longer than the longest prefix
|
||||||
|
// found so far, remember the peer
|
||||||
if match_len as u8 >= entry.prefix_len && match_len as isize > found_len {
|
if match_len as u8 >= entry.prefix_len && match_len as isize > found_len {
|
||||||
found = Some(entry.address);
|
found = Some(entry.address);
|
||||||
found_len = match_len as isize;
|
found_len = match_len as isize;
|
||||||
|
@ -101,13 +128,16 @@ impl Table for RoutingTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Return the longest match found (if any).
|
||||||
found
|
found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method does not do anything.
|
||||||
fn housekeep(&mut self) {
|
fn housekeep(&mut self) {
|
||||||
//nothing to do
|
//nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an address from the map and returns whether something has been removed
|
||||||
#[inline]
|
#[inline]
|
||||||
fn remove(&mut self, addr: &Address) -> bool {
|
fn remove(&mut self, addr: &Address) -> bool {
|
||||||
let len = addr.len as usize;
|
let len = addr.len as usize;
|
||||||
|
@ -139,6 +169,7 @@ impl Table for RoutingTable {
|
||||||
found
|
found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removed all addresses associated with a certain peer
|
||||||
fn remove_all(&mut self, addr: &SocketAddr) {
|
fn remove_all(&mut self, addr: &SocketAddr) {
|
||||||
for (_key, entry) in &mut self.0 {
|
for (_key, entry) in &mut self.0 {
|
||||||
entry.retain(|entr| &entr.address != addr);
|
entry.retain(|entr| &entr.address != addr);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||||
|
|
||||||
#![cfg_attr(feature = "bench", feature(test))]
|
#![cfg_attr(feature = "bench", feature(test))]
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
|
@ -34,10 +35,10 @@ use std::hash::{Hash, SipHasher, Hasher};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use device::Device;
|
use device::{Device, Type};
|
||||||
use ethernet::SwitchTable;
|
use ethernet::SwitchTable;
|
||||||
use ip::RoutingTable;
|
use ip::RoutingTable;
|
||||||
use types::{Mode, Type, Range, Table, Protocol};
|
use types::{Mode, Range, Table, Protocol};
|
||||||
use cloud::GenericCloud;
|
use cloud::GenericCloud;
|
||||||
use udpmessage::VERSION;
|
use udpmessage::VERSION;
|
||||||
use crypto::{Crypto, CryptoMethod};
|
use crypto::{Crypto, CryptoMethod};
|
||||||
|
|
14
src/types.rs
14
src/types.rs
|
@ -192,20 +192,6 @@ impl fmt::Debug for Range {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(RustcDecodable, Debug, Clone, Copy)]
|
|
||||||
pub enum Type {
|
|
||||||
Tun, Tap
|
|
||||||
}
|
|
||||||
impl fmt::Display for Type {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
Type::Tun => write!(formatter, "tun"),
|
|
||||||
Type::Tap => write!(formatter, "tap"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(RustcDecodable, Debug, Clone, Copy)]
|
#[derive(RustcDecodable, Debug, Clone, Copy)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal, Hub, Switch, Router
|
Normal, Hub, Switch, Router
|
||||||
|
|
Loading…
Reference in New Issue