mirror of https://github.com/dswd/vpncloud.git
More generic
This commit is contained in:
parent
a559a10155
commit
30fab51be6
68
src/cloud.rs
68
src/cloud.rs
|
@ -6,16 +6,14 @@ use std::io::Read;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use time::{Duration, SteadyTime, precise_time_ns};
|
use time::{Duration, SteadyTime, precise_time_ns};
|
||||||
use epoll;
|
use epoll;
|
||||||
|
|
||||||
use super::types::{Table, Protocol, VirtualInterface, Range, Error, NetworkId, Behavior};
|
use super::types::{Table, Protocol, VirtualInterface, Range, Error, NetworkId};
|
||||||
use super::device::{TunDevice, TapDevice};
|
use super::device::Device;
|
||||||
use super::udpmessage::{encode, decode, Options, Message};
|
use super::udpmessage::{encode, decode, Options, Message};
|
||||||
use super::ethernet::{Frame, MacTable};
|
use super::{ethernet, ip};
|
||||||
use super::ip::{InternetProtocol, RoutingTable};
|
|
||||||
|
|
||||||
struct PeerList {
|
struct PeerList {
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
|
@ -85,25 +83,25 @@ impl PeerList {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct GenericCloud<T: Table, P: Protocol, I: VirtualInterface> {
|
pub struct GenericCloud<P: Protocol> {
|
||||||
peers: PeerList,
|
peers: PeerList,
|
||||||
addresses: Vec<Range>,
|
addresses: Vec<Range>,
|
||||||
learning: bool,
|
learning: bool,
|
||||||
broadcast: bool,
|
broadcast: bool,
|
||||||
reconnect_peers: Vec<SocketAddr>,
|
reconnect_peers: Vec<SocketAddr>,
|
||||||
table: T,
|
table: Box<Table>,
|
||||||
socket: UdpSocket,
|
socket: UdpSocket,
|
||||||
device: I,
|
device: Device,
|
||||||
network_id: Option<NetworkId>,
|
network_id: Option<NetworkId>,
|
||||||
next_peerlist: SteadyTime,
|
next_peerlist: SteadyTime,
|
||||||
update_freq: Duration,
|
update_freq: Duration,
|
||||||
buffer_out: [u8; 64*1024],
|
buffer_out: [u8; 64*1024],
|
||||||
next_housekeep: SteadyTime,
|
next_housekeep: SteadyTime,
|
||||||
_dummy_m: PhantomData<P>,
|
_dummy_p: PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Table, P: Protocol, I: VirtualInterface> GenericCloud<T, P, I> {
|
impl<P: Protocol> GenericCloud<P> {
|
||||||
pub fn new(device: I, listen: String, network_id: Option<NetworkId>, table: T,
|
pub fn new(device: Device, listen: String, network_id: Option<NetworkId>, table: Box<Table>,
|
||||||
peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>) -> Self {
|
peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>) -> Self {
|
||||||
let socket = match UdpSocket::bind(&listen as &str) {
|
let socket = match UdpSocket::bind(&listen as &str) {
|
||||||
Ok(socket) => socket,
|
Ok(socket) => socket,
|
||||||
|
@ -123,7 +121,7 @@ impl<T: Table, P: Protocol, I: VirtualInterface> GenericCloud<T, P, I> {
|
||||||
update_freq: peer_timeout/2,
|
update_freq: peer_timeout/2,
|
||||||
buffer_out: [0; 64*1024],
|
buffer_out: [0; 64*1024],
|
||||||
next_housekeep: SteadyTime::now(),
|
next_housekeep: SteadyTime::now(),
|
||||||
_dummy_m: PhantomData,
|
_dummy_p: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,47 +309,5 @@ impl<T: Table, P: Protocol, I: VirtualInterface> GenericCloud<T, P, I> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub type TapCloud = GenericCloud<MacTable, Frame, TapDevice>;
|
pub type TapCloud = GenericCloud<ethernet::Frame>;
|
||||||
|
pub type TunCloud = GenericCloud<ip::Packet>;
|
||||||
impl TapCloud {
|
|
||||||
pub fn new_tap_cloud(device: &str, listen: String, behavior: Behavior, network_id: Option<NetworkId>, mac_timeout: Duration, peer_timeout: Duration) -> Self {
|
|
||||||
let device = match TapDevice::new(device) {
|
|
||||||
Ok(device) => device,
|
|
||||||
_ => panic!("Failed to open tap device")
|
|
||||||
};
|
|
||||||
info!("Opened tap device {}", device.ifname());
|
|
||||||
let table = MacTable::new(mac_timeout);
|
|
||||||
let (learning, broadcasting) = match behavior {
|
|
||||||
Behavior::Normal => (true, true),
|
|
||||||
Behavior::Switch => (true, true),
|
|
||||||
Behavior::Hub => (false, true),
|
|
||||||
Behavior::Router => (false, false)
|
|
||||||
};
|
|
||||||
Self::new(device, listen, network_id, table, peer_timeout, learning, broadcasting, vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub type TunCloud = GenericCloud<RoutingTable, InternetProtocol, TunDevice>;
|
|
||||||
|
|
||||||
impl TunCloud {
|
|
||||||
pub fn new_tun_cloud(device: &str, listen: String, behavior: Behavior, network_id: Option<NetworkId>, range_strs: Vec<String>, peer_timeout: Duration) -> Self {
|
|
||||||
let device = match TunDevice::new(device) {
|
|
||||||
Ok(device) => device,
|
|
||||||
_ => panic!("Failed to open tun device")
|
|
||||||
};
|
|
||||||
info!("Opened tun device {}", device.ifname());
|
|
||||||
let table = RoutingTable::new();
|
|
||||||
let mut ranges = Vec::with_capacity(range_strs.len());
|
|
||||||
for s in range_strs {
|
|
||||||
ranges.push(Range::from_str(&s).expect("Invalid subnet"));
|
|
||||||
}
|
|
||||||
let (learning, broadcasting) = match behavior {
|
|
||||||
Behavior::Normal => (false, false),
|
|
||||||
Behavior::Switch => (true, true),
|
|
||||||
Behavior::Hub => (false, true),
|
|
||||||
Behavior::Router => (false, false)
|
|
||||||
};
|
|
||||||
Self::new(device, listen, network_id, table, peer_timeout, learning, broadcasting, ranges)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
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::marker::PhantomData;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use super::types::{Error, VirtualInterface};
|
use super::types::{Error, VirtualInterface, Type};
|
||||||
|
|
||||||
extern {
|
extern {
|
||||||
fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32;
|
fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32;
|
||||||
|
@ -11,56 +10,26 @@ extern {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
trait DeviceSetup {
|
pub struct Device {
|
||||||
fn setup_device(RawFd, &str) -> IoResult<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct TapSetup;
|
|
||||||
|
|
||||||
impl DeviceSetup for TapSetup {
|
|
||||||
fn setup_device(fd: RawFd, ifname: &str) -> IoResult<String> {
|
|
||||||
let mut ifname_string = String::with_capacity(32);
|
|
||||||
ifname_string.push_str(ifname);
|
|
||||||
ifname_string.push('\0');
|
|
||||||
let mut ifname_c = ifname_string.into_bytes();
|
|
||||||
let res = unsafe { setup_tap_device(fd, ifname_c.as_mut_ptr()) };
|
|
||||||
match res {
|
|
||||||
0 => Ok(String::from_utf8(ifname_c).unwrap()),
|
|
||||||
_ => Err(IoError::last_os_error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct TunSetup;
|
|
||||||
|
|
||||||
impl DeviceSetup for TunSetup {
|
|
||||||
fn setup_device(fd: RawFd, ifname: &str) -> IoResult<String> {
|
|
||||||
let mut ifname_string = String::with_capacity(32);
|
|
||||||
ifname_string.push_str(ifname);
|
|
||||||
ifname_string.push('\0');
|
|
||||||
let mut ifname_c = ifname_string.into_bytes();
|
|
||||||
let res = unsafe { setup_tun_device(fd, ifname_c.as_mut_ptr()) };
|
|
||||||
match res {
|
|
||||||
0 => Ok(String::from_utf8(ifname_c).unwrap()),
|
|
||||||
_ => Err(IoError::last_os_error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Device<T> {
|
|
||||||
fd: fs::File,
|
fd: fs::File,
|
||||||
ifname: String,
|
ifname: String
|
||||||
_dummy_t: PhantomData<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DeviceSetup> Device<T> {
|
impl Device {
|
||||||
pub fn new(ifname: &str) -> 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"));
|
||||||
let ifname = try!(T::setup_device(fd.as_raw_fd(), ifname));
|
let mut ifname_string = String::with_capacity(32);
|
||||||
Ok(Device{fd: fd, ifname: ifname, _dummy_t: PhantomData})
|
ifname_string.push_str(ifname);
|
||||||
|
ifname_string.push('\0');
|
||||||
|
let mut ifname_c = ifname_string.into_bytes();
|
||||||
|
let res = match type_ {
|
||||||
|
Type::Tun => unsafe { setup_tun_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) },
|
||||||
|
Type::Tap => unsafe { setup_tap_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) }
|
||||||
|
};
|
||||||
|
match res {
|
||||||
|
0 => Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap()}),
|
||||||
|
_ => Err(IoError::last_os_error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -69,13 +38,13 @@ impl<T: DeviceSetup> Device<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsRawFd for Device<T> {
|
impl AsRawFd for Device {
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
self.fd.as_raw_fd()
|
self.fd.as_raw_fd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VirtualInterface for Device<T> {
|
impl VirtualInterface for Device {
|
||||||
fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, Error> {
|
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"))
|
||||||
}
|
}
|
||||||
|
@ -87,6 +56,3 @@ impl<T> VirtualInterface for Device<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TapDevice = Device<TapSetup>;
|
|
||||||
pub type TunDevice = Device<TunSetup>;
|
|
||||||
|
|
|
@ -51,24 +51,23 @@ impl Protocol for Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct MacTableValue {
|
struct SwitchTableValue {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
timeout: SteadyTime
|
timeout: SteadyTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SwitchTable {
|
||||||
pub struct MacTable {
|
table: HashMap<Address, SwitchTableValue>,
|
||||||
table: HashMap<Address, MacTableValue>,
|
|
||||||
timeout: Duration
|
timeout: Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacTable {
|
impl SwitchTable {
|
||||||
pub fn new(timeout: Duration) -> MacTable {
|
pub fn new(timeout: Duration) -> Self {
|
||||||
MacTable{table: HashMap::new(), timeout: timeout}
|
SwitchTable{table: HashMap::new(), timeout: timeout}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Table for MacTable {
|
impl Table for SwitchTable {
|
||||||
fn housekeep(&mut self) {
|
fn housekeep(&mut self) {
|
||||||
let now = SteadyTime::now();
|
let now = SteadyTime::now();
|
||||||
let mut del: Vec<Address> = Vec::new();
|
let mut del: Vec<Address> = Vec::new();
|
||||||
|
@ -84,7 +83,7 @@ impl Table for MacTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = MacTableValue{address: addr, timeout: SteadyTime::now()+self.timeout};
|
let value = SwitchTableValue{address: addr, timeout: SteadyTime::now()+self.timeout};
|
||||||
if self.table.insert(key.clone(), value).is_none() {
|
if self.table.insert(key.clone(), value).is_none() {
|
||||||
info!("Learned address {:?} => {}", key, addr);
|
info!("Learned address {:?} => {}", key, addr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use super::types::{Protocol, Error, Table, Address};
|
||||||
use super::util::to_vec;
|
use super::util::to_vec;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct InternetProtocol;
|
pub struct Packet;
|
||||||
|
|
||||||
impl Protocol for InternetProtocol {
|
impl Protocol for 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"));
|
||||||
|
|
98
src/main.rs
98
src/main.rs
|
@ -16,13 +16,16 @@ use time::Duration;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
|
|
||||||
use std::hash::{Hash, SipHasher, Hasher};
|
use std::hash::{Hash, SipHasher, Hasher};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use types::{Error, Behavior};
|
use device::Device;
|
||||||
|
use ethernet::SwitchTable;
|
||||||
|
use ip::RoutingTable;
|
||||||
|
use types::{Error, Behavior, Type, Range, Table};
|
||||||
use cloud::{TapCloud, TunCloud};
|
use cloud::{TapCloud, TunCloud};
|
||||||
|
|
||||||
|
|
||||||
//TODO: hub behavior
|
//TODO: L2 routing
|
||||||
//TODO: L2 routing/L3 switching
|
|
||||||
//TODO: Implement IPv6
|
//TODO: Implement IPv6
|
||||||
//TODO: Encryption
|
//TODO: Encryption
|
||||||
//TODO: Call close
|
//TODO: Call close
|
||||||
|
@ -54,18 +57,13 @@ Options:
|
||||||
-c <connect>, --connect <connect> List of peers (addr:port) to connect to
|
-c <connect>, --connect <connect> List of peers (addr:port) to connect to
|
||||||
--network-id <network_id> Optional token that identifies the network
|
--network-id <network_id> Optional token that identifies the network
|
||||||
--peer-timeout <peer_timeout> Peer timeout in seconds [default: 1800]
|
--peer-timeout <peer_timeout> Peer timeout in seconds [default: 1800]
|
||||||
--subnet <subnet>... The local subnets to use (only for tun)
|
--subnet <subnet>... The local subnets to use
|
||||||
--mac-timeout <mac_timeout> Mac table entry timeout in seconds (only for tap) [default: 300]
|
--dst-timeout <dst_timeout> Switch table entry timeout in seconds [default: 300]
|
||||||
-v, --verbose Log verbosely
|
-v, --verbose Log verbosely
|
||||||
-q, --quiet Only print error messages
|
-q, --quiet Only print error messages
|
||||||
-h, --help Display the help
|
-h, --help Display the help
|
||||||
";
|
";
|
||||||
|
|
||||||
#[derive(RustcDecodable, Debug)]
|
|
||||||
enum Type {
|
|
||||||
Tun, Tap
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(RustcDecodable, Debug)]
|
#[derive(RustcDecodable, Debug)]
|
||||||
struct Args {
|
struct Args {
|
||||||
flag_type: Type,
|
flag_type: Type,
|
||||||
|
@ -76,49 +74,11 @@ struct Args {
|
||||||
flag_network_id: Option<String>,
|
flag_network_id: Option<String>,
|
||||||
flag_connect: Vec<String>,
|
flag_connect: Vec<String>,
|
||||||
flag_peer_timeout: usize,
|
flag_peer_timeout: usize,
|
||||||
flag_mac_timeout: usize,
|
flag_dst_timeout: usize,
|
||||||
flag_verbose: bool,
|
flag_verbose: bool,
|
||||||
flag_quiet: bool
|
flag_quiet: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tap_cloud(args: Args) {
|
|
||||||
let mut tapcloud = TapCloud::new_tap_cloud(
|
|
||||||
&args.flag_device,
|
|
||||||
args.flag_listen,
|
|
||||||
args.flag_behavior,
|
|
||||||
args.flag_network_id.map(|name| {
|
|
||||||
let mut s = SipHasher::new();
|
|
||||||
name.hash(&mut s);
|
|
||||||
s.finish()
|
|
||||||
}),
|
|
||||||
Duration::seconds(args.flag_mac_timeout as i64),
|
|
||||||
Duration::seconds(args.flag_peer_timeout as i64)
|
|
||||||
);
|
|
||||||
for addr in args.flag_connect {
|
|
||||||
tapcloud.connect(&addr as &str, true).expect("Failed to send");
|
|
||||||
}
|
|
||||||
tapcloud.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tun_cloud(args: Args) {
|
|
||||||
let mut tuncloud = TunCloud::new_tun_cloud(
|
|
||||||
&args.flag_device,
|
|
||||||
args.flag_listen,
|
|
||||||
args.flag_behavior,
|
|
||||||
args.flag_network_id.map(|name| {
|
|
||||||
let mut s = SipHasher::new();
|
|
||||||
name.hash(&mut s);
|
|
||||||
s.finish()
|
|
||||||
}),
|
|
||||||
args.flag_subnet,
|
|
||||||
Duration::seconds(args.flag_peer_timeout as i64)
|
|
||||||
);
|
|
||||||
for addr in args.flag_connect {
|
|
||||||
tuncloud.connect(&addr as &str, true).expect("Failed to send");
|
|
||||||
}
|
|
||||||
tuncloud.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
|
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
|
||||||
log::set_logger(|max_log_level| {
|
log::set_logger(|max_log_level| {
|
||||||
|
@ -133,8 +93,42 @@ fn main() {
|
||||||
Box::new(SimpleLogger)
|
Box::new(SimpleLogger)
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
debug!("Args: {:?}", args);
|
debug!("Args: {:?}", args);
|
||||||
match args.flag_type {
|
let device = Device::new(&args.flag_device, args.flag_type).expect("Failed to open virtual interface");
|
||||||
Type::Tap => tap_cloud(args),
|
info!("Opened device {}", device.ifname());
|
||||||
Type::Tun => tun_cloud(args)
|
let mut ranges = Vec::with_capacity(args.flag_subnet.len());
|
||||||
|
for s in args.flag_subnet {
|
||||||
|
ranges.push(Range::from_str(&s).expect("Invalid subnet"));
|
||||||
}
|
}
|
||||||
|
let dst_timeout = Duration::seconds(args.flag_dst_timeout as i64);
|
||||||
|
let peer_timeout = Duration::seconds(args.flag_peer_timeout as i64);
|
||||||
|
let (learning, broadcasting, table): (bool, bool, Box<Table>) = match args.flag_behavior {
|
||||||
|
Behavior::Normal => match args.flag_type {
|
||||||
|
Type::Tap => (true, true, Box::new(SwitchTable::new(dst_timeout))),
|
||||||
|
Type::Tun => (false, false, Box::new(RoutingTable::new()))
|
||||||
|
},
|
||||||
|
Behavior::Router => (false, false, Box::new(RoutingTable::new())),
|
||||||
|
Behavior::Switch => (true, true, Box::new(SwitchTable::new(dst_timeout))),
|
||||||
|
Behavior::Hub => (false, true, Box::new(SwitchTable::new(dst_timeout)))
|
||||||
|
};
|
||||||
|
let network_id = args.flag_network_id.map(|name| {
|
||||||
|
let mut s = SipHasher::new();
|
||||||
|
name.hash(&mut s);
|
||||||
|
s.finish()
|
||||||
|
});
|
||||||
|
match args.flag_type {
|
||||||
|
Type::Tap => {
|
||||||
|
let mut cloud = TapCloud::new(device, args.flag_listen, network_id, table, peer_timeout, learning, broadcasting, ranges);
|
||||||
|
for addr in args.flag_connect {
|
||||||
|
cloud.connect(&addr as &str, true).expect("Failed to send");
|
||||||
|
}
|
||||||
|
cloud.run()
|
||||||
|
},
|
||||||
|
Type::Tun => {
|
||||||
|
let mut cloud = TunCloud::new(device, args.flag_listen, network_id, table, peer_timeout, learning, broadcasting, ranges);
|
||||||
|
for addr in args.flag_connect {
|
||||||
|
cloud.connect(&addr as &str, true).expect("Failed to send");
|
||||||
|
}
|
||||||
|
cloud.run()
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,11 @@ impl FromStr for Range {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(RustcDecodable, Debug, Clone, Copy)]
|
||||||
|
pub enum Type {
|
||||||
|
Tun, Tap
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(RustcDecodable, Debug)]
|
#[derive(RustcDecodable, Debug)]
|
||||||
pub enum Behavior {
|
pub enum Behavior {
|
||||||
Normal, Hub, Switch, Router
|
Normal, Hub, Switch, Router
|
||||||
|
|
Loading…
Reference in New Issue