diff --git a/vnt-cli/Cargo.toml b/vnt-cli/Cargo.toml index 4dbba10..683d11a 100644 --- a/vnt-cli/Cargo.toml +++ b/vnt-cli/Cargo.toml @@ -8,13 +8,12 @@ edition = "2021" [dependencies] vnt = { path = "../vnt", package = "vnt",default-features = false } common = { path = "../common" } -tokio = { version = "1.32.0", features = ["full"] } getopts = "0.2.21" console = "0.15.2" os_info = "3.7.0" serde = "1.0" -serde_json = "1.0.94" -serde_yaml = "0.8.26" +#serde_json = "1.0.94" +serde_yaml = "0.9.32" log = "0.4.17" log4rs = "1.2.0" [dependencies.uuid] diff --git a/vnt-cli/src/callback.rs b/vnt-cli/src/callback.rs new file mode 100644 index 0000000..4aeaa1a --- /dev/null +++ b/vnt-cli/src/callback.rs @@ -0,0 +1,36 @@ +use std::process; + +use vnt::handle::callback::ConnectInfo; +use vnt::{DeviceInfo, ErrorInfo, HandshakeInfo, RegisterInfo, VntCallback}; + +#[derive(Clone)] +pub struct VntHandler {} + +impl VntCallback for VntHandler { + fn create_tun(&self, info: DeviceInfo) { + println!("create_tun {}", info) + } + + fn connect(&self, info: ConnectInfo) { + println!("connect {}", info) + } + + fn handshake(&self, info: HandshakeInfo) -> bool { + println!("handshake {}", info); + true + } + + fn register(&self, info: RegisterInfo) -> bool { + println!("register {}", info); + true + } + + fn error(&self, info: ErrorInfo) { + println!("error {}", info); + } + + fn stop(&self) { + println!("stopped"); + process::exit(0) + } +} diff --git a/vnt-cli/src/command/client.rs b/vnt-cli/src/command/client.rs index 65a845f..bee8cbb 100644 --- a/vnt-cli/src/command/client.rs +++ b/vnt-cli/src/command/client.rs @@ -1,3 +1,4 @@ +use serde::Deserialize; use std::io; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}; use std::str::FromStr; @@ -6,6 +7,7 @@ use std::time::Duration; use crate::command::entity::{DeviceItem, Info, RouteItem}; pub struct CommandClient { + buf: [u8; 10240], udp: UdpSocket, } @@ -32,43 +34,30 @@ impl CommandClient { Ipv4Addr::new(127, 0, 0, 1), port, )))?; - Ok(Self { udp }) + Ok(Self { + udp, + buf: [0; 10240], + }) } } impl CommandClient { - pub fn list(&self) -> io::Result> { - self.udp.send(b"list")?; - let mut buf = [0; 10240]; - let len = self.udp.recv(&mut buf)?; - match serde_json::from_slice::>(&buf[..len]) { - Ok(val) => Ok(val), - Err(e) => { - log::error!("{:?}", e); - Err(io::Error::new(io::ErrorKind::Other, "data error")) - } - } + pub fn list(&mut self) -> io::Result> { + self.send_cmd(b"list") } - pub fn route(&self) -> io::Result> { - self.udp.send(b"route")?; - let mut buf = [0; 10240]; - let len = self.udp.recv(&mut buf)?; - match serde_json::from_slice::>(&buf[..len]) { - Ok(val) => Ok(val), - Err(e) => { - log::error!("{:?}", e); - Err(io::Error::new(io::ErrorKind::Other, "data error")) - } - } + pub fn route(&mut self) -> io::Result> { + self.send_cmd(b"route") } - pub fn info(&self) -> io::Result { - self.udp.send(b"info")?; - let mut buf = [0; 10240]; - let len = self.udp.recv(&mut buf)?; - match serde_json::from_slice::(&buf[..len]) { + pub fn info(&mut self) -> io::Result { + self.send_cmd(b"info") + } + fn send_cmd<'a, V: Deserialize<'a>>(&'a mut self, cmd: &[u8]) -> io::Result { + self.udp.send(cmd)?; + let len = self.udp.recv(&mut self.buf)?; + match serde_yaml::from_slice::(&self.buf[..len]) { Ok(val) => Ok(val), Err(e) => { - log::error!("{:?},{:?}", &buf[..len], e); + log::error!("{:?},{:?}", &self.buf[..len], e); Err(io::Error::new(io::ErrorKind::Other, "data error")) } } diff --git a/vnt-cli/src/command/entity.rs b/vnt-cli/src/command/entity.rs index 6952bd7..ee10c28 100644 --- a/vnt-cli/src/command/entity.rs +++ b/vnt-cli/src/command/entity.rs @@ -11,6 +11,8 @@ pub struct Info { pub public_ips: String, pub local_addr: String, pub ipv6_addr: String, + pub up: u64, + pub down: u64, } #[derive(Serialize, Deserialize, Debug)] diff --git a/vnt-cli/src/command/mod.rs b/vnt-cli/src/command/mod.rs index 5acede1..230e50c 100644 --- a/vnt-cli/src/command/mod.rs +++ b/vnt-cli/src/command/mod.rs @@ -22,7 +22,7 @@ pub fn command(cmd: CommandEnum) { } fn command_(cmd: CommandEnum) -> io::Result<()> { - let command_client = client::CommandClient::new()?; + let mut command_client = client::CommandClient::new()?; match cmd { CommandEnum::Route => { let list = command_client.route()?; @@ -50,25 +50,27 @@ fn command_(cmd: CommandEnum) -> io::Result<()> { pub fn command_route(vnt: &Vnt) -> Vec { 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); + for (destination, routes) in route_table { + for route in routes { + 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 } @@ -111,10 +113,17 @@ pub fn command_list(vnt: &Vnt) -> Vec { } else { "p2p" } - } else if route.addr == info.connect_server { - "server-relay" } else { - "client-relay" + let next_hop = vnt.route_key(&route.route_key()); + if let Some(next_hop) = next_hop { + if info.is_gateway(&next_hop) { + "server-relay" + } else { + "client-relay" + } + } else { + "server-relay" + } } .to_string(); let rt = if route.rt < 0 { @@ -166,6 +175,8 @@ pub fn command_info(vnt: &Vnt) -> Info { .ipv6() .map(|v| v.to_string()) .unwrap_or("None".to_string()); + let up = vnt.up_stream(); + let down = vnt.down_stream(); Info { name, virtual_ip, @@ -177,5 +188,7 @@ pub fn command_info(vnt: &Vnt) -> Info { public_ips, local_addr, ipv6_addr, + up, + down, } } diff --git a/vnt-cli/src/command/server.rs b/vnt-cli/src/command/server.rs index 603978a..b0a56ea 100644 --- a/vnt-cli/src/command/server.rs +++ b/vnt-cli/src/command/server.rs @@ -1,6 +1,6 @@ use std::io; use std::io::Write; -use tokio::net::UdpSocket; +use std::net::UdpSocket; use vnt::core::Vnt; @@ -13,26 +13,26 @@ impl CommandServer { } impl CommandServer { - pub async fn start(self, vnt: Vnt) -> io::Result<()> { - let udp = if let Ok(udp) = UdpSocket::bind("127.0.0.1:39271").await { + pub fn start(self, vnt: Vnt) -> io::Result<()> { + let udp = if let Ok(udp) = UdpSocket::bind("127.0.0.1:39271") { udp } else { - UdpSocket::bind("127.0.0.1:0").await? + UdpSocket::bind("127.0.0.1:0")? }; - let path_buf = crate::app_home()?.join("command-port"); - let mut file = std::fs::File::create(path_buf)?; let addr = udp.local_addr()?; - file.write_all(addr.port().to_string().as_bytes())?; - file.sync_all()?; log::info!("启动后台cmd:{:?}", addr); + if let Err(e) = save_port(addr.port()) { + log::warn!("保存后台命令端口失败:{:?}", e); + } + let mut buf = [0u8; 64]; loop { - let (len, addr) = udp.recv_from(&mut buf).await?; + let (len, addr) = udp.recv_from(&mut buf)?; match std::str::from_utf8(&buf[..len]) { Ok(cmd) => { log::info!("收到cmd={:?}", cmd); if let Ok(out) = command(cmd, &vnt) { - if let Err(e) = udp.send_to(out.as_bytes(), addr).await { + if let Err(e) = udp.send_to(out.as_bytes(), addr) { log::warn!("cmd={},err={:?}", cmd, e); } if "stopped" == &out { @@ -48,29 +48,23 @@ impl CommandServer { Ok(()) } } +fn save_port(port: u16) -> io::Result<()> { + let path_buf = crate::app_home()?.join("command-port"); + let mut file = std::fs::File::create(path_buf)?; + file.write_all(port.to_string().as_bytes())?; + file.sync_all() +} fn command(cmd: &str, vnt: &Vnt) -> io::Result { 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) - } - }, + "route" => serde_yaml::to_string(&crate::command::command_route(vnt)) + .unwrap_or_else(|e| format!("error {:?}", e)), + "list" => serde_yaml::to_string(&crate::command::command_list(vnt)) + .unwrap_or_else(|e| format!("error {:?}", e)), + "info" => serde_yaml::to_string(&crate::command::command_info(vnt)) + .unwrap_or_else(|e| format!("error {:?}", e)), "stop" => { - vnt.stop()?; + vnt.stop(); "stopped".to_string() } _ => { diff --git a/vnt-cli/src/config/mod.rs b/vnt-cli/src/config/mod.rs index 62b1318..1e75b6b 100644 --- a/vnt-cli/src/config/mod.rs +++ b/vnt-cli/src/config/mod.rs @@ -11,6 +11,7 @@ use vnt::core::Config; #[derive(Serialize, Deserialize, Debug)] #[serde(default)] pub struct FileConfig { + #[cfg(any(target_os = "windows", target_os = "linux"))] pub tap: bool, pub token: String, pub device_id: String, @@ -20,25 +21,27 @@ pub struct FileConfig { pub in_ips: Vec, pub out_ips: Vec, pub password: Option, - pub simulate_multicast: bool, - pub mtu: Option, + pub mtu: Option, pub tcp: bool, pub ip: Option, pub relay: bool, + #[cfg(feature = "ip_proxy")] pub no_proxy: bool, pub server_encrypt: bool, pub parallel: usize, pub cipher_model: String, pub finger: bool, pub punch_model: String, - pub port: u16, + pub ports: Option>, pub cmd: bool, pub first_latency: bool, + pub device_name: Option, } impl Default for FileConfig { fn default() -> Self { Self { + #[cfg(any(target_os = "windows", target_os = "linux"))] tap: false, token: "".to_string(), device_id: get_device_id(), @@ -52,20 +55,21 @@ impl Default for FileConfig { in_ips: vec![], out_ips: vec![], password: None, - simulate_multicast: false, mtu: None, tcp: false, ip: None, relay: false, + #[cfg(feature = "ip_proxy")] no_proxy: false, server_encrypt: false, parallel: 1, cipher_model: "aes_gcm".to_string(), finger: false, punch_model: "".to_string(), - port: 0, + ports: None, cmd: false, first_latency: false, + device_name: None, } } } @@ -134,6 +138,7 @@ pub fn read_config(file_path: &str) -> io::Result<(Config, bool)> { let punch_model = PunchModel::from_str(&file_conf.punch_model) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let config = Config::new( + #[cfg(any(target_os = "windows", target_os = "linux"))] file_conf.tap, file_conf.token, file_conf.device_id, @@ -144,7 +149,6 @@ pub fn read_config(file_path: &str) -> io::Result<(Config, bool)> { in_ips, out_ips, file_conf.password, - file_conf.simulate_multicast, file_conf.mtu, file_conf.tcp, virtual_ip, @@ -156,8 +160,9 @@ pub fn read_config(file_path: &str) -> io::Result<(Config, bool)> { cipher_model, file_conf.finger, punch_model, - file_conf.port, + file_conf.ports, file_conf.first_latency, + file_conf.device_name, ) .unwrap(); Ok((config, file_conf.cmd)) diff --git a/vnt-cli/src/console_out/mod.rs b/vnt-cli/src/console_out/mod.rs index ce88246..dc096ed 100644 --- a/vnt-cli/src/console_out/mod.rs +++ b/vnt-cli/src/console_out/mod.rs @@ -18,6 +18,30 @@ pub fn console_info(status: Info) { println!("Public ips: {}", style(status.public_ips).green()); println!("Local addr: {}", style(status.local_addr).green()); println!("IPv6: {}", style(status.ipv6_addr).green()); + println!("Up: {}", style(convert(status.up)).green()); + println!("Down: {}", style(convert(status.down)).green()); +} +fn convert(num: u64) -> String { + let gigabytes = num / (1024 * 1024 * 1024); + let remaining_bytes = num % (1024 * 1024 * 1024); + let megabytes = remaining_bytes / (1024 * 1024); + let remaining_bytes = remaining_bytes % (1024 * 1024); + let kilobytes = remaining_bytes / 1024; + let remaining_bytes = remaining_bytes % 1024; + let mut s = String::new(); + if gigabytes > 0 { + s.push_str(&format!("{} GB ", gigabytes)); + } + if megabytes > 0 { + s.push_str(&format!("{} MB ", megabytes)); + } + if kilobytes > 0 { + s.push_str(&format!("{} KB ", kilobytes)); + } + if remaining_bytes > 0 { + s.push_str(&format!("{} bytes", remaining_bytes)); + } + s } pub fn console_route_table(mut list: Vec) { diff --git a/vnt-cli/src/main.rs b/vnt-cli/src/main.rs index d348cdc..3e0c8af 100644 --- a/vnt-cli/src/main.rs +++ b/vnt-cli/src/main.rs @@ -1,19 +1,15 @@ -use std::io; use std::net::{Ipv4Addr, ToSocketAddrs}; use std::path::PathBuf; use std::str::FromStr; +use std::{io, thread}; use console::style; use getopts::Options; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::signal; use common::args_parse::{ips_parse, out_ips_parse}; use vnt::channel::punch::PunchModel; use vnt::cipher::CipherModel; -use vnt::core::{Config, Vnt, VntUtil}; -use vnt::handle::handshake_handler::HandshakeEnum; -use vnt::handle::registration_handler::ReqEnum; +use vnt::core::{Config, Vnt}; mod command; mod config; @@ -44,21 +40,20 @@ fn main() { opts.optopt("s", "", "注册和中继服务器地址", ""); opts.optmulti("e", "", "stun服务器", ""); opts.optflag("a", "", "使用tap模式"); + opts.optopt("", "nic", "虚拟网卡名称,windows下使用tap则必填", ""); opts.optmulti("i", "", "配置点对网(IP代理)入站时使用", ""); opts.optmulti("o", "", "配置点对网出站时使用", ""); opts.optopt("w", "", "客户端加密", ""); opts.optflag("W", "", "服务端加密"); - opts.optflag("m", "", "模拟组播"); opts.optopt("u", "", "自定义mtu(默认为1430)", ""); opts.optflag("", "tcp", "tcp"); opts.optopt("", "ip", "指定虚拟ip", ""); opts.optflag("", "relay", "仅使用服务器转发"); opts.optopt("", "par", "任务并行度(必须为正整数)", ""); - opts.optopt("", "thread", "线程数(必须为正整数)", ""); opts.optopt("", "model", "加密模式", ""); opts.optflag("", "finger", "指纹校验"); opts.optopt("", "punch", "取值ipv4/ipv6", ""); - opts.optopt("", "port", "监听的端口", ""); + opts.optopt("", "ports", "监听的端口", ""); opts.optflag("", "cmd", "开启窗口输入"); opts.optflag("", "no-proxy", "关闭内置代理"); opts.optflag("", "first-latency", "优先延迟"); @@ -119,7 +114,9 @@ fn main() { println!("parameter -k not found ."); return; } + #[cfg(any(target_os = "windows", target_os = "linux"))] let tap = matches.opt_present("a"); + let device_name = matches.opt_str("nic"); 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() { @@ -190,10 +187,9 @@ fn main() { return; } } - let simulate_multicast = matches.opt_present("m"); let mtu: Option = matches.opt_get("u").unwrap(); let mtu = if let Some(mtu) = mtu { - match u16::from_str(&mtu) { + match u32::from_str(&mtu) { Ok(mtu) => Some(mtu), Err(e) => { print_usage(&program, opts); @@ -260,12 +256,17 @@ fn main() { .opt_get::("punch") .unwrap() .unwrap_or(PunchModel::All); - let port = matches.opt_get::("port").unwrap_or(None).unwrap_or(0); + let ports = matches + .opt_get::("ports") + .unwrap_or(None) + .map(|v| v.split(",").map(|x| x.parse().unwrap_or(0)).collect()); + let cmd = matches.opt_present("cmd"); #[cfg(feature = "ip_proxy")] let no_proxy = matches.opt_present("no-proxy"); let first_latency = matches.opt_present("first-latency"); let config = Config::new( + #[cfg(any(target_os = "windows", target_os = "linux"))] tap, token, device_id, @@ -276,7 +277,6 @@ fn main() { in_ip, out_ip, password, - simulate_multicast, mtu, tcp_channel, virtual_ip, @@ -288,8 +288,9 @@ fn main() { cipher_model, finger, punch_model, - port, + ports, first_latency, + device_name, ) .unwrap(); (config, cmd) @@ -300,151 +301,35 @@ fn main() { std::process::exit(0); } -#[tokio::main] -async fn main0(config: Config, show_cmd: bool) { - let server_encrypt = config.server_encrypt; - let mut vnt_util = VntUtil::new(config).unwrap(); - let mut conn_count = 0; - let response = loop { - if conn_count > 0 { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - conn_count += 1; - if let Err(e) = vnt_util.connect() { - println!("connect server failed {}", e); - return; - } - match vnt_util.handshake() { - Ok(response) => { - if server_encrypt { - let finger = response.unwrap().finger().unwrap(); - println!("{}{}", green("server fingerprint:".to_string()), finger); - match vnt_util.secret_handshake() { - Ok(_) => {} - Err(e) => { - match e { - HandshakeEnum::NotSecret => {} - HandshakeEnum::KeyError => {} - HandshakeEnum::Timeout => { - println!("handshake timeout") - } - HandshakeEnum::ServerError(str) => { - println!("error:{}", str); - } - HandshakeEnum::Other(str) => { - println!("error:{}", str); - } - } - continue; - } - } - } - match vnt_util.register() { - Ok(response) => { - break response; - } - Err(e) => match e { - ReqEnum::TokenError => { - println!("token error"); - return; - } - ReqEnum::AddressExhausted => { - println!("address exhausted"); - return; - } - ReqEnum::Timeout => { - println!("timeout..."); - } - ReqEnum::ServerError(str) => { - println!("error:{}", str); - } - ReqEnum::Other(str) => { - println!("error:{}", str); - } - ReqEnum::IpAlreadyExists => { - println!("ip already exists"); - return; - } - ReqEnum::InvalidIp => { - println!("invalid ip"); - return; - } - }, - } - } - Err(e) => match e { - HandshakeEnum::NotSecret => { - println!("The server does not support encryption"); - return; - } - HandshakeEnum::KeyError => {} - HandshakeEnum::Timeout => { - println!("handshake timeout") - } - HandshakeEnum::ServerError(str) => { - println!("error:{}", str); - } - HandshakeEnum::Other(str) => { - println!("error:{}", str); - } - }, - } - }; - 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 { +mod callback; + +fn main0(config: Config, show_cmd: bool) { + let vnt_util = Vnt::new(config, callback::VntHandler {}).unwrap(); + let vnt_c = vnt_util.clone(); + thread::spawn(move || { + if let Err(e) = command::server::CommandServer::new().start(vnt_c) { log::warn!("cmd:{:?}", e); - println!("command error :{}", e); } }); if show_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()=>{ - return; - } - _ = signal::ctrl_c()=>{ - let _ = vnt.stop(); - vnt.wait_stop_ms(std::time::Duration::from_secs(3)).await; - std::process::exit(0); - } - rs = reader.read_line(&mut cmd)=>{ - match rs { - Ok(len) => { - if !command(&cmd[..len],&vnt){ - break; - } - } - Err(e) => { - println!("input err:{}",e); - break; - } + println!("======== input:list,info,route,all,stop ========"); + match io::stdin().read_line(&mut cmd) { + Ok(len) => { + if !command(&cmd[..len], &vnt_util) { + break; } } + Err(e) => { + println!("input err:{}", e); + break; + } } } } - vnt.wait_stop().await; + vnt_util.wait() } fn command(cmd: &str, vnt: &Vnt) -> bool { @@ -487,15 +372,14 @@ fn print_usage(program: &str, _opts: Options) { green("使用相同的token,就能组建一个局域网络".to_string()) ); println!(" -n 给设备一个名字,便于区分不同设备,默认使用系统版本"); - println!(" -d 设备唯一标识符,不使用--ip参数时,服务端凭此参数分配虚拟ip"); + println!(" -d 设备唯一标识符,不使用--ip参数时,服务端凭此参数分配虚拟ip,注意不能重复"); println!(" -s 注册和中继服务器地址"); println!(" -e stun服务器,用于探测NAT类型,可多次指定,如-e addr1 -e addr2"); println!(" -a 使用tap模式,默认使用tun模式"); println!(" -i 配置点对网(IP代理)时使用,-i 192.168.0.0/24,10.26.0.3表示允许接收网段192.168.0.0/24的数据"); println!(" 并转发到10.26.0.3,可指定多个网段"); + #[cfg(feature = "ip_proxy")] println!(" -o 配置点对网时使用,-o 192.168.0.0/24表示允许将数据转发到192.168.0.0/24,可指定多个网段"); - #[cfg(not(feature = "ip_proxy"))] - println!(" 注意需要在系统配置ip转发才可正常使用"); #[cfg(not(any( feature = "aes_gcm", feature = "server_encrypt", @@ -525,7 +409,6 @@ fn print_usage(program: &str, _opts: Options) { } #[cfg(feature = "server_encrypt")] println!(" -W 加密当前客户端和服务端通信的数据,请留意服务端指纹是否正确"); - println!(" -m 模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送"); println!(" -u 自定义mtu(不加密默认为1450,加密默认为1410)"); println!(" -f 读取配置文件中的配置"); @@ -535,19 +418,20 @@ fn print_usage(program: &str, _opts: Options) { println!(" --par 任务并行度(必须为正整数),默认值为1"); if !enums.is_empty() { println!( - " --model 加密模式(默认aes_gcm),可选值{}", + " --model 加密模式(默认aes_gcm),可选值{}", &enums[1..] ); } if !enums.is_empty() { - println!(" --finger 增加数据指纹校验,可增加安全性,如果服务端开启指纹校验,则客户端也必须开启"); + println!(" --finger 增加数据指纹校验,可增加安全性,如果服务端开启指纹校验,则客户端也必须开启"); } - println!(" --punch 取值ipv4/ipv6,ipv4表示仅使用ipv4打洞"); - println!(" --port 取值0~65535,指定本地监听的端口,默认取随机端口"); - println!(" --cmd 开启交互式命令,使用此参数开启控制台输入"); + println!(" --punch 取值ipv4/ipv6,ipv4表示仅使用ipv4打洞"); + println!(" --ports 取值0~65535,指定本地监听的一组端口,默认监听两个随机端口,使用过多端口会增加网络负担"); + println!(" --cmd 开启交互式命令,使用此参数开启控制台输入"); #[cfg(feature = "ip_proxy")] - println!(" --no-proxy 关闭内置代理,如需点对网则需要配置网卡NAT转发"); - println!(" --first-latency 优先低延迟的通道,默认情况优先使用p2p通道"); + println!(" --no-proxy 关闭内置代理,如需点对网则需要配置网卡NAT转发"); + println!(" --first-latency 优先低延迟的通道,默认情况优先使用p2p通道"); + println!(" --nic 虚拟网卡名称,windows下使用tap模式则必须指定此参数"); println!(); println!(