支持自定义ip、服务端tcp通道、可选择禁止p2p

This commit is contained in:
lubeilin
2023-07-24 00:42:27 +08:00
parent 2f7817ce5b
commit 9b42c5d092
147 changed files with 1078 additions and 1754 deletions
+84
View File
@@ -0,0 +1,84 @@
use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::str::FromStr;
use std::time::Duration;
use crate::command::entity::{DeviceItem, RouteItem, Info};
pub struct CommandClient {
udp: UdpSocket,
}
impl CommandClient {
pub fn new() -> io::Result<Self> {
let path_buf = crate::app_home()?.join("command-port");
if !path_buf.exists() {
return Err(io::Error::new(io::ErrorKind::Other, "not started"));
}
let port = std::fs::read_to_string(path_buf)?;
let port = match u16::from_str(&port) {
Ok(port) => { port }
Err(_) => {
return Err(io::Error::new(io::ErrorKind::Other, "'command-port' file error"));
}
};
let udp = UdpSocket::bind("127.0.0.1:0")?;
udp.set_read_timeout(Some(Duration::from_secs(2)))?;
udp.connect(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
port,
)))?;
Ok(Self { udp })
}
}
impl CommandClient {
pub fn list(&self) -> io::Result<Vec<DeviceItem>> {
self.udp.send(b"list")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
match serde_json::from_slice::<Vec<DeviceItem>>(&buf[..len]) {
Ok(val) => {
Ok(val)
}
Err(e) => {
log::error!("{:?}",e);
Err(io::Error::new(io::ErrorKind::Other, "data error"))
}
}
}
pub fn route(&self) -> io::Result<Vec<RouteItem>> {
self.udp.send(b"route")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
match serde_json::from_slice::<Vec<RouteItem>>(&buf[..len]) {
Ok(val) => {
Ok(val)
}
Err(e) => {
log::error!("{:?}",e);
Err(io::Error::new(io::ErrorKind::Other, "data error"))
}
}
}
pub fn info(&self) -> io::Result<Info> {
self.udp.send(b"info")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
match serde_json::from_slice::<Info>(&buf[..len]) {
Ok(val) => {
Ok(val)
}
Err(e) => {
log::error!("{:?},{:?}",&buf[..len],e);
Err(io::Error::new(io::ErrorKind::Other, "data error"))
}
}
}
pub fn stop(&self) -> io::Result<String> {
self.udp.send(b"stop")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
Ok(String::from_utf8(buf[..len].to_vec()).unwrap())
}
}
+34
View File
@@ -0,0 +1,34 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Info {
pub name: String,
pub virtual_ip: String,
pub virtual_gateway: String,
pub virtual_netmask: String,
pub connect_status: String,
pub relay_server: String,
pub nat_type: String,
pub public_ips: String,
pub local_ip: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct RouteItem {
pub destination: String,
pub next_hop: String,
pub metric: String,
pub rt: String,
pub interface: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DeviceItem {
pub name: String,
pub virtual_ip: String,
pub nat_type: String,
pub public_ips: String,
pub local_ip: String,
pub nat_traversal_type: String,
pub rt: String,
pub status: String,
}
+141
View File
@@ -0,0 +1,141 @@
use std::io;
use vnt::core::Vnt;
use crate::command::entity::{DeviceItem, RouteItem, Info};
use crate::console_out;
pub mod client;
pub mod server;
pub mod entity;
pub enum CommandEnum {
Route,
List,
All,
Info,
Stop,
}
pub fn command(cmd: CommandEnum) {
if let Err(e) = command_(cmd) {
println!("cmd: {}", e);
}
}
fn command_(cmd: CommandEnum) -> io::Result<()> {
let command_client = client::CommandClient::new()?;
match cmd {
CommandEnum::Route => {
let list = command_client.route()?;
console_out::console_route_table(list);
}
CommandEnum::List => {
let list = command_client.list()?;
console_out::console_device_list(list);
}
CommandEnum::All => {
let list = command_client.list()?;
console_out::console_device_list_all(list);
}
CommandEnum::Info => {
let info = command_client.info()?;
console_out::console_info(info);
}
CommandEnum::Stop => {
command_client.stop()?;
}
}
Ok(())
}
pub fn command_route(vnt: &Vnt) -> Vec<RouteItem> {
let route_table = vnt.route_table();
let mut route_list = Vec::with_capacity(route_table.len());
for (destination, route) in route_table {
let next_hop = vnt.route_key(&route.route_key()).map_or(String::new(), |v| v.to_string());
let metric = route.metric.to_string();
let rt = if route.rt < 0 {
"".to_string()
} else {
route.rt.to_string()
};
let interface = route.addr.to_string();
let item = RouteItem {
destination: destination.to_string(),
next_hop,
metric,
rt,
interface,
};
route_list.push(item);
}
route_list
}
pub fn command_list(vnt: &Vnt) -> Vec<DeviceItem> {
let device_list = vnt.device_list();
let mut list = Vec::new();
for peer in device_list {
let name = peer.name;
let virtual_ip = peer.virtual_ip.to_string();
let (nat_type, public_ips, local_ip) = if let Some(nat_info) = vnt.peer_nat_info(&peer.virtual_ip) {
let nat_type = format!("{:?}", nat_info.nat_type);
let public_ips: Vec<String> = nat_info.public_ips.iter().map(|v| v.to_string()).collect();
let public_ips = public_ips.join(",");
let local_ip = nat_info.local_ip.to_string();
(nat_type, public_ips, local_ip)
} else {
("".to_string(), "".to_string(), "".to_string())
};
let (nat_traversal_type, rt) = if let Some(route) = vnt.route(&peer.virtual_ip) {
let nat_traversal_type = if route.metric == 1 { "p2p" } else { "relay" }.to_string();
let rt = if route.rt < 0 {
"".to_string()
} else {
route.rt.to_string()
};
(nat_traversal_type, rt)
} else {
("relay".to_string(), "".to_string())
};
let status = format!("{:?}", peer.status);
let item = DeviceItem {
name,
virtual_ip,
nat_type,
public_ips,
local_ip,
nat_traversal_type,
rt,
status,
};
list.push(item);
}
list
}
pub fn command_info(vnt: &Vnt) -> Info {
let current_device = vnt.current_device();
let nat_info = vnt.nat_info();
let name = vnt.name().to_string();
let virtual_ip = current_device.virtual_ip().to_string();
let virtual_gateway = current_device.virtual_gateway().to_string();
let virtual_netmask = current_device.virtual_netmask.to_string();
let connect_status = format!("{:?}", vnt.connection_status());
let relay_server = current_device.connect_server.to_string();
let nat_type = format!("{:?}", nat_info.nat_type);
let public_ips: Vec<String> = nat_info.public_ips.iter().map(|v| v.to_string()).collect();
let public_ips = public_ips.join(",");
let local_ip = nat_info.local_ip.to_string();
Info {
name,
virtual_ip,
virtual_gateway,
virtual_netmask,
connect_status,
relay_server,
nat_type,
public_ips,
local_ip,
}
}
+102
View File
@@ -0,0 +1,102 @@
use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use tokio::net::UdpSocket;
use vnt::core::Vnt;
pub struct CommandServer {}
impl CommandServer {
pub fn new() -> Self {
Self {}
}
}
impl CommandServer {
pub async fn start(self, vnt: Vnt) -> io::Result<()> {
let mut port = 21637 as u16;
let udp = loop {
match UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
port,
))).await {
Ok(udp) => {
break udp;
}
Err(e) => {
if e.kind() == io::ErrorKind::AddrInUse {
port += 1;
} else {
log::error!("创建udp失败 {:?}", e);
return Err(e);
}
}
}
};
let path_buf = crate::app_home()?.join("command-port");
std::fs::write(path_buf, udp.local_addr()?.port().to_string())?;
let mut buf = [0u8; 64];
loop {
let (len, addr) = udp.recv_from(&mut buf).await?;
match std::str::from_utf8(&buf[..len]) {
Ok(cmd) => {
if let Ok(out) = command(cmd, &vnt) {
let _ = udp.send_to(out.as_bytes(), addr).await;
if "stopped" == &out {
break;
}
}
}
Err(e) => {
log::warn!("{:?}", e);
}
}
}
Ok(())
}
}
fn command(cmd: &str, vnt: &Vnt) -> io::Result<String> {
let out_str = match cmd {
"route" => {
match serde_json::to_string(&crate::command::command_route(vnt)) {
Ok(str) => {
str
}
Err(e) => {
format!("{:?}", e)
}
}
}
"list" => {
match serde_json::to_string(&crate::command::command_list(vnt)) {
Ok(str) => {
str
}
Err(e) => {
format!("{:?}", e)
}
}
}
"info" => {
match serde_json::to_string(&crate::command::command_info(vnt)) {
Ok(str) => {
str
}
Err(e) => {
format!("{:?}", e)
}
}
}
"stop" => {
vnt.stop()?;
"stopped".to_string()
}
_ => {
format!("command '{}' not found. \n Try to enter: 'help'\n", cmd)
}
};
Ok(out_str)
}
+133
View File
@@ -0,0 +1,133 @@
use console::{style, Style};
use crate::command::entity::{DeviceItem, RouteItem, Info};
pub mod table;
pub fn console_info(status: Info) {
println!("Name: {}", style(status.name).green());
println!("Virtual ip: {}", style(status.virtual_ip).green());
println!("Virtual gateway: {}", style(status.virtual_gateway).green());
println!("Virtual netmask: {}", style(status.virtual_netmask).green());
println!("Connection status: {}", style(status.connect_status).green());
println!("NAT type: {}", style(status.nat_type).green());
println!("Relay server: {}", style(status.relay_server).green());
println!("Public ips: {}", style(status.public_ips).green());
println!("Local ip: {}", style(status.local_ip).green());
}
pub fn console_route_table(mut list: Vec<RouteItem>) {
if list.is_empty() {
println!("No route found");
return;
}
list.sort_by(|t1, t2| t1.destination.cmp(&t2.destination));
let mut out_list = Vec::with_capacity(list.len());
out_list.push(vec![("Destination".to_string(), Style::new()),
("Next Hop".to_string(), Style::new()),
("Metric".to_string(), Style::new()),
("Rt".to_string(), Style::new()),
("Interface".to_string(), Style::new()), ]);
for item in list {
out_list.push(vec![(item.destination, Style::new().green()),
(item.next_hop, Style::new().green()),
(item.metric, Style::new().green()),
(item.rt, Style::new().green()),
(item.interface, Style::new().green())]);
}
table::println_table(out_list)
}
pub fn console_device_list(mut list: Vec<DeviceItem>) {
if list.is_empty() {
println!("No other devices found");
return;
}
list.sort_by(|t1, t2| t1.virtual_ip.cmp(&t2.virtual_ip));
list.sort_by(|t1, t2| t1.status.cmp(&t2.status));
let mut out_list = Vec::with_capacity(list.len());
//表头
out_list.push(vec![("Name".to_string(), Style::new()),
("Virtual Ip".to_string(), Style::new()),
("Status".to_string(), Style::new()),
("P2P/Relay".to_string(), Style::new()),
("Rt".to_string(), Style::new())]);
for item in list {
if &item.status == "Online" {
if &item.nat_traversal_type == "p2p" {
out_list.push(vec![(item.name, Style::new().green()),
(item.virtual_ip, Style::new().green()),
(item.status, Style::new().green()),
(item.nat_traversal_type, Style::new().green()),
(item.rt, Style::new().green())]);
} else {
out_list.push(vec![(item.name, Style::new().yellow()),
(item.virtual_ip, Style::new().yellow()),
(item.status, Style::new().yellow()),
(item.nat_traversal_type, Style::new().yellow()),
(item.rt, Style::new().yellow())]);
}
} else {
out_list.push(vec![(item.name, Style::new().color256(102)),
(item.virtual_ip, Style::new().color256(102)),
(item.status, Style::new().color256(102)),
("".to_string(), Style::new().color256(102)),
("".to_string(), Style::new().color256(102))]);
}
}
table::println_table(out_list)
}
pub fn console_device_list_all(mut list: Vec<DeviceItem>) {
if list.is_empty() {
println!("No other devices found");
return;
}
list.sort_by(|t1, t2| t1.virtual_ip.cmp(&t2.virtual_ip));
list.sort_by(|t1, t2| t1.status.cmp(&t2.status));
let mut out_list = Vec::with_capacity(list.len());
//表头
out_list.push(vec![("Name".to_string(), Style::new()),
("Virtual Ip".to_string(), Style::new()),
("Status".to_string(), Style::new()),
("NAT Type".to_string(), Style::new()),
("Public Ips".to_string(), Style::new()),
("Local Ip".to_string(), Style::new()),
("P2P/Relay".to_string(), Style::new()),
("Rt".to_string(), Style::new())]);
for item in list {
if &item.status == "Online" {
if &item.nat_traversal_type == "p2p" {
out_list.push(vec![(item.name, Style::new().green()),
(item.virtual_ip, Style::new().green()),
(item.status, Style::new().green()),
(item.nat_traversal_type, Style::new().green()),
(item.rt, Style::new().green()),
(item.nat_type, Style::new().green()),
(item.public_ips, Style::new().green()),
(item.local_ip, Style::new().green())]);
} else {
out_list.push(vec![(item.name, Style::new().yellow()),
(item.virtual_ip, Style::new().yellow()),
(item.status, Style::new().yellow()),
(item.nat_traversal_type, Style::new().yellow()),
(item.rt, Style::new().yellow()),
(item.nat_type, Style::new().yellow()),
(item.public_ips, Style::new().yellow()),
(item.local_ip, Style::new().yellow()), ]);
}
} else {
out_list.push(vec![(item.name, Style::new().color256(102)),
(item.virtual_ip, Style::new().color256(102)),
(item.status, Style::new().color256(102)),
("".to_string(), Style::new().color256(102)),
("".to_string(), Style::new().color256(102)),
("".to_string(), Style::new().color256(102)),
("".to_string(), Style::new().color256(102)),
("".to_string(), Style::new().color256(102)), ]);
}
}
table::println_table(out_list)
}
+23
View File
@@ -0,0 +1,23 @@
use console::Style;
pub fn println_table(table: Vec<Vec<(String, Style)>>) {
if table.is_empty() {
return;
}
let mut width_list = vec![0; table[0].len()];
for in_list in table.iter() {
for (index, (item, _)) in in_list.iter().enumerate() {
let width = console::measure_text_width(item) + 6;
if width_list[index] < width {
width_list[index] = width;
}
}
}
for in_list in table {
for (col, (item, style)) in in_list.iter().enumerate() {
let str = format!("{:1$}", item, width_list[col]);
print!("{}", style.apply_to(str));
}
println!()
}
}
+324
View File
@@ -0,0 +1,324 @@
use std::io;
use std::net::{Ipv4Addr, ToSocketAddrs};
use std::path::PathBuf;
use std::str::FromStr;
use console::style;
use getopts::Options;
use tokio::io::{AsyncBufReadExt, BufReader};
use common::args_parse::ips_parse;
use vnt::core::{Config, VntUtil};
use vnt::handle::registration_handler::ReqEnum;
mod command;
mod console_out;
mod root_check;
pub fn app_home() -> io::Result<PathBuf> {
let path = dirs::home_dir().ok_or(io::Error::new(io::ErrorKind::Other, "not home"))?.join(".vnt-cli");
if !path.exists() {
std::fs::create_dir_all(&path)?;
}
Ok(path)
}
#[tokio::main]
async fn main() {
main0().await;
std::process::exit(0);
}
async fn main0() {
let args: Vec<String> = std::env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.long_only(false);
opts.optopt("k", "", &format!("{}", green("必选,使用相同的token,就能组建一个局域网络".to_string())), "<token>");
opts.optopt("n", "", "给设备一个名字,默认使用系统版本", "<name>");
opts.optopt("d", "", "设备唯一标识符,不使用--ip参数时,服务端凭此参数分配ip", "<id>");
opts.optflag("c", "", "关闭交互式命令,使用此参数禁用控制台输入");
opts.optopt("s", "", "注册和中继服务器地址", "<server>");
opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", "<addr1,addr2>");
opts.optflag("a", "", "使用tap模式,默认使用tun模式");
opts.optmulti("i", "", "配置点对网(IP代理)时使用,-i 192.168.0.0/24,10.26.0.3,表示允许接收网段192.168.0.0/24的数据并转发到10.26.0.3", "<in-ip>");
opts.optmulti("o", "", "配置点对网时使用,-o 192.168.0.0/24,192.168.0.10,表示允许目标为192.168.0.0/24的数据从网卡192.168.0.10转发出去", "<out-ip>");
opts.optopt("w", "", "使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "<password>");
opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
opts.optopt("u", "", "虚拟网卡mtu值", "<mtu>");
opts.optflag("", "tcp", "和服务端使用tcp通信,默认使用udp,一般来说udp延迟和消耗更低");
opts.optopt("", "ip", "指定虚拟ip,指定的ip不能和其他设备重复,必须有效并且在服务端所属网段下,默认情况由服务端分配", "<IP>");
opts.optflag("", "relay", "仅使用服务器转发,不使用p2p,默认情况允许使用p2p");
//"后台运行时,查看其他设备列表"
opts.optflag("", "list", &format!("{}", yellow("后台运行时,查看其他设备列表".to_string())));
opts.optflag("", "all", &format!("{}", yellow("后台运行时,查看其他设备完整信息".to_string())));
opts.optflag("", "info", &format!("{}", yellow("后台运行时,查看当前设备信息".to_string())));
opts.optflag("", "route", &format!("{}", yellow("后台运行时,查看数据转发路径".to_string())));
opts.optflag("", "stop", &format!("{}", yellow("停止后台运行".to_string())));
opts.optflag("h", "help", "帮助");
let matches = match opts.parse(&args[1..]) {
Ok(m) => { m }
Err(f) => {
print_usage(&program, opts);
println!("{}", f.to_string());
return;
}
};
if matches.opt_present("h") || args.len() == 1 {
print_usage(&program, opts);
return;
}
if !root_check::is_app_elevated() {
println!("Please run it with administrator or root privileges");
#[cfg(any(target_os = "linux", target_os = "macos"))]
sudo::escalate_if_needed().unwrap();
return;
}
if matches.opt_present("list") {
command::command(command::CommandEnum::List);
return;
} else if matches.opt_present("info") {
command::command(command::CommandEnum::Info);
return;
} else if matches.opt_present("stop") {
command::command(command::CommandEnum::Stop);
return;
} else if matches.opt_present("route") {
command::command(command::CommandEnum::Route);
return;
} else if matches.opt_present("all") {
command::command(command::CommandEnum::All);
return;
}
if !matches.opt_present("k") {
print_usage(&program, opts);
println!("parameter -k not found .");
return;
}
let tap = matches.opt_present("a");
let token: String = matches.opt_get("k").unwrap().unwrap();
let device_id = matches.opt_get_default("d", String::new()).unwrap();
let device_id = if device_id.is_empty() {
if let Some(id) = common::identifier::get_unique_identifier() {
id
} else {
let path_buf = app_home().unwrap().join("device-id");
if let Ok(id) = std::fs::read_to_string(path_buf.as_path()) {
id
} else {
let id = uuid::Uuid::new_v4().to_string();
let _ = std::fs::write(path_buf, &id);
id
}
}
} else {
device_id
};
if device_id.is_empty() {
print_usage(&program, opts);
println!("parameter -d not found .");
return;
}
let name = matches.opt_get_default("n", os_info::get().to_string()).unwrap();
let server_address_str = matches.opt_get_default("s", "nat1.wherewego.top:29871".to_string()).unwrap();
let server_address = match server_address_str.to_socket_addrs() {
Ok(mut addr) => {
if let Some(addr) = addr.next() {
addr
} else {
println!("parameter -s error .");
return;
}
}
Err(e) => {
println!("parameter -s error {}.", e);
return;
}
};
let nat_test_server = matches.opt_get_default("e",
"nat1.wherewego.top:35061,nat1.wherewego.top:35062,nat2.wherewego.top:35061,nat2.wherewego.top:35062".to_string()).unwrap();
let nat_test_server = nat_test_server.split(",").flat_map(|a| a.to_socket_addrs()).flatten()
.collect::<Vec<_>>();
let in_ip = matches.opt_strs("i");
let in_ip = match ips_parse(&in_ip) {
Ok(in_ip) => { in_ip }
Err(e) => {
print_usage(&program, opts);
println!();
println!("-i {}", e);
return;
}
};
let out_ip = matches.opt_strs("o");
let out_ip = match ips_parse(&out_ip) {
Ok(out_ip) => { out_ip }
Err(e) => {
print_usage(&program, opts);
println!();
println!("-o {}", e);
return;
}
};
let password: Option<String> = matches.opt_get("w").unwrap();
let simulate_multicast = matches.opt_present("m");
let unused_cmd = matches.opt_present("c");
let mtu: Option<String> = matches.opt_get("u").unwrap();
let mtu = if let Some(mtu) = mtu {
match u16::from_str(&mtu) {
Ok(mtu) => {
Some(mtu)
}
Err(e) => {
print_usage(&program, opts);
println!();
println!("-u {}", e);
return;
}
}
} else {
None
};
let virtual_ip: Option<String> = matches.opt_get("ip").unwrap();
let virtual_ip = virtual_ip.map(|v| Ipv4Addr::from_str(&v).expect("--ip error"));
if let Some(virtual_ip) = virtual_ip {
if virtual_ip.is_unspecified() || virtual_ip.is_broadcast() || virtual_ip.is_multicast() {
println!("--ip invalid");
return;
}
}
let tcp_channel = matches.opt_present("tcp");
let relay = matches.opt_present("relay");
let config = Config::new(tap,
token, device_id, name,
server_address, server_address_str,
nat_test_server, in_ip,
out_ip, password, simulate_multicast, mtu, tcp_channel, virtual_ip, relay);
let mut vnt_util = VntUtil::new(config).await.unwrap();
let response = loop {
match vnt_util.connect().await {
Ok(response) => {
break response;
}
Err(e) => {
match e {
ReqEnum::TokenError => {
println!("token error");
}
ReqEnum::AddressExhausted => {
println!("address exhausted");
}
ReqEnum::Timeout => {
println!("timeout...");
continue;
}
ReqEnum::ServerError(str) => {
println!("error:{}", str);
continue;
}
ReqEnum::Other(str) => {
println!("error:{}", str);
continue;
}
ReqEnum::IpAlreadyExists => {
println!("ip already exists");
}
ReqEnum::InvalidIp => {
println!("invalid ip");
}
}
return;
}
}
};
println!(" ====== Connect Successfully ====== ");
println!("virtual_gateway:{}", response.virtual_gateway);
println!("virtual_ip:{}", green(response.virtual_ip.to_string()));
let driver_info = vnt_util.create_iface().unwrap();
println!(" ====== Create Network Interface Successfully ====== ");
println!("name:{}", driver_info.name);
println!("version:{}", driver_info.version);
let mut vnt = match vnt_util.build().await {
Ok(vnt) => {
vnt
}
Err(e) => {
println!("error:{}", e);
return;
}
};
println!(" ====== Start Successfully ====== ");
let vnt_c = vnt.clone();
tokio::spawn(async {
if let Err(e) = command::server::CommandServer::new().start(vnt_c).await {
println!("command error :{}", e);
}
});
if !unused_cmd {
let stdin = tokio::io::stdin();
let mut cmd = String::new();
let mut reader = BufReader::new(stdin);
loop {
cmd.clear();
println!("input:list,info,route,all,stop");
tokio::select! {
_ = vnt.wait_stop()=>{
break;
}
rs = reader.read_line(&mut cmd)=>{
match rs {
Ok(len) => {
if len ==0 {
break;
}
match cmd[..len].to_lowercase().trim() {
"list" => {
let list = command::command_list(&vnt);
console_out::console_device_list(list);
}
"info"=>{
let info = command::command_info(&vnt);
console_out::console_info(info);
}
"route" =>{
let route = command::command_route(&vnt);
console_out::console_route_table(route);
}
"all" =>{
let list = command::command_list(&vnt);
console_out::console_device_list_all(list);
}
"stop" =>{
let _ = vnt.stop();
break;
}
_ => {
}
}
println!();
}
Err(e) => {
println!("input err:{}",e);
break;
}
}
}
}
}
}
vnt.wait_stop().await;
}
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} [options]", program);
println!("version:1.1.1");
println!("{}", opts.usage(&brief));
}
fn green(str: String) -> impl std::fmt::Display {
style(str).green()
}
fn yellow(str: String) -> impl std::fmt::Display {
style(str).yellow()
}
+11
View File
@@ -0,0 +1,11 @@
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
pub use windows::is_app_elevated;
#[cfg(any(target_os = "linux", target_os = "macos"))]
mod unix;
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub use unix::is_app_elevated;
+3
View File
@@ -0,0 +1,3 @@
pub fn is_app_elevated() -> bool {
sudo::RunningAs::Root == sudo::check()
}
+76
View File
@@ -0,0 +1,76 @@
/// 使用 https://github.com/spa5k/is_sudo/blob/main/src/window.rs
use std::io::Error;
use std::ptr;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
use winapi::um::securitybaseapi::GetTokenInformation;
use winapi::um::winnt::{TokenElevation, HANDLE, TOKEN_ELEVATION, TOKEN_QUERY};
// Use std::io::Error::last_os_error for errors.
// NOTE: For this example I'm simple passing on the OS error.
// However, customising the error could provide more context
/// Returns true if the current process has admin rights, otherwise false.
pub fn is_app_elevated() -> bool {
_is_app_elevated().unwrap_or(false)
}
/// On success returns a bool indicating if the current process has admin rights.
/// Otherwise returns an OS error.
///
/// This is unlikely to fail but if it does it's even more unlikely that you have admin permissions anyway.
/// Therefore the public function above simply eats the error and returns a bool.
fn _is_app_elevated() -> Result<bool, Error> {
let token = QueryAccessToken::from_current_process()?;
token.is_elevated()
}
/// A safe wrapper around querying Windows access tokens.
pub struct QueryAccessToken(HANDLE);
impl QueryAccessToken {
pub fn from_current_process() -> Result<Self, Error> {
unsafe {
let mut handle: HANDLE = ptr::null_mut();
let result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle);
if result != 0 {
Ok(Self(handle))
} else {
Err(Error::last_os_error())
}
}
}
/// On success returns a bool indicating if the access token has elevated privilidges.
/// Otherwise returns an OS error.
pub fn is_elevated(&self) -> Result<bool, Error> {
unsafe {
let mut elevation = TOKEN_ELEVATION::default();
let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let mut ret_size = size;
// The weird looking repetition of `as *mut _` is casting the reference to a c_void pointer.
if GetTokenInformation(
self.0,
TokenElevation,
&mut elevation as *mut _ as *mut _,
size,
&mut ret_size,
) != 0
{
Ok(elevation.TokenIsElevated != 0)
} else {
Err(Error::last_os_error())
}
}
}
}
impl Drop for QueryAccessToken {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { CloseHandle(self.0) };
}
}
}