diff --git a/Cargo.lock b/Cargo.lock index b785879..cd67c2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1625,6 +1625,7 @@ dependencies = [ name = "vnt-cli" version = "1.2.9" dependencies = [ + "anyhow", "chrono", "common", "console", diff --git a/README.md b/README.md index aa14b66..7017090 100644 --- a/README.md +++ b/README.md @@ -85,24 +85,33 @@ features说明 | server_encrypt | 支持服务端加密 | 是 | | ip_proxy | 内置ip代理 | 是 | | port_mapping | 端口映射 | 是 | +| log | 日志 | 是 | +| command | list、route等命令 | 是 | +| file_config | yaml配置文件 | 是 | ### ip转发/代理 + 如果编译时去除了内置的ip代理(或使用--no-proxy关闭了代理),则可以使用网卡NAT转发来实现点对网, 一般来说使用网卡NAT转发会比内置的ip代理性能更好
NAT配置可参考如下示例,点击展开 ### 在出口一端做如下配置 + 注意原有的-i(入口)和-o(出口)的参数不能少 ### windows + 参考 https://learn.microsoft.com/zh-cn/virtualization/hyper-v-on-windows/user-guide/setup-nat-network + ```shell #设置nat,名字可以自己取,网段是vnt的网段 New-NetNat -Name vntnat -InternalIPInterfaceAddressPrefix 10.26.0.0/24 #查看设置 Get-NetNat ``` + ### linux + ```shell # 开启ip转发 sudo sysctl -w net.ipv4.ip_forward=1 @@ -145,6 +154,7 @@ sudo iptables-restore iptables.rules ``` ### macos + ```shell # 开启ip转发 sudo sysctl -w net.ipv4.ip_forward=1 @@ -154,6 +164,7 @@ nat on en0 from 10.26.0.0/24 to any -> (en0) # 加载规则 sudo pfctl -f /etc/pf.conf -e ``` +
### 支持平台 @@ -256,10 +267,15 @@ vnt默认使用10.26.0.0/24网段,和本地网络适配器的ip冲突 2. 如果p2p后效果很差,可以选择禁用p2p(vnt-cli增加--use-channel relay 参数) #### 问题4:重启后虚拟IP发生变化,或指定了IP不能启动 + ##### 可能原因: + 设备重启后程序自动获取的id值改变,导致注册时重新分配了新的IP,或是IP冲突 + ##### 解决方法: + 1. 命令行启动增加-d参数(使用配置文件启动则在配置文件中增加device_id参数),要保证每个设备的值都不一样,取值可以任意64位以内字符串 + ### 交流群 diff --git a/vnt-cli/Cargo.toml b/vnt-cli/Cargo.toml index 80eede1..e591254 100644 --- a/vnt-cli/Cargo.toml +++ b/vnt-cli/Cargo.toml @@ -14,7 +14,8 @@ os_info = "3.7.0" serde = "1.0" serde_yaml = "0.9.32" log = "0.4.17" -log4rs = "1.2.0" +log4rs = { version = "1.2.0", optional = true } +anyhow = "1.0.82" [dependencies.uuid] version = "1.4.1" features = [ @@ -28,7 +29,7 @@ sudo = "0.6.0" winapi = { version = "0.3.9", features = ["handleapi", "processthreadsapi", "winnt", "securitybaseapi", "impl-default"] } [features] -default = ["server_encrypt", "aes_gcm", "aes_cbc", "aes_ecb", "sm4_cbc", "ip_proxy", "port_mapping"] +default = ["server_encrypt", "aes_gcm", "aes_cbc", "aes_ecb", "sm4_cbc", "ip_proxy", "port_mapping", "log", "command", "file_config"] openssl = ["vnt/openssl"] openssl-vendored = ["vnt/openssl-vendored"] ring-cipher = ["vnt/ring-cipher"] @@ -39,6 +40,9 @@ aes_gcm = ["vnt/aes_gcm"] server_encrypt = ["vnt/server_encrypt"] ip_proxy = ["vnt/ip_proxy"] port_mapping = ["vnt/port_mapping"] +log = ["log4rs"] +command = [] +file_config = [] [build-dependencies] embed-manifest = "1.4.0" rand = "0.8.5" diff --git a/vnt-cli/src/config/mod.rs b/vnt-cli/src/config/mod.rs index 0cbe330..4472e71 100644 --- a/vnt-cli/src/config/mod.rs +++ b/vnt-cli/src/config/mod.rs @@ -1,171 +1,12 @@ -use std::io; -use std::net::Ipv4Addr; -use std::str::FromStr; +#[cfg(feature = "file_config")] +mod file_config; -use serde::{Deserialize, Serialize}; +#[cfg(feature = "file_config")] +pub use file_config::read_config; -use vnt::channel::punch::PunchModel; -use vnt::channel::UseChannelType; -use vnt::cipher::CipherModel; -use vnt::core::Config; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(default)] -pub struct FileConfig { - #[cfg(target_os = "windows")] - pub tap: bool, - pub token: String, - pub device_id: String, - pub name: String, - pub server_address: String, - pub stun_server: Vec, - pub dns: Vec, - pub in_ips: Vec, - pub out_ips: Vec, - pub password: Option, - pub mtu: Option, - pub tcp: bool, - pub ip: Option, - pub use_channel: String, - #[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 ports: Option>, - pub cmd: bool, - pub first_latency: bool, - pub device_name: Option, - pub packet_loss: Option, - pub packet_delay: u32, - #[cfg(feature = "port_mapping")] - pub mapping: Vec, -} - -impl Default for FileConfig { - fn default() -> Self { - Self { - #[cfg(target_os = "windows")] - tap: false, - token: "".to_string(), - device_id: get_device_id(), - name: os_info::get().to_string(), - server_address: "nat1.wherewego.top:29872".to_string(), - stun_server: vec![ - "stun1.l.google.com:19302".to_string(), - "stun2.l.google.com:19302".to_string(), - "stun.miwifi.com:3478".to_string(), - ], - dns: vec![], - in_ips: vec![], - out_ips: vec![], - password: None, - mtu: None, - tcp: false, - ip: None, - use_channel: "all".to_string(), - #[cfg(feature = "ip_proxy")] - no_proxy: false, - server_encrypt: false, - parallel: 1, - cipher_model: "aes_gcm".to_string(), - finger: false, - punch_model: "all".to_string(), - ports: None, - cmd: false, - first_latency: false, - device_name: None, - packet_loss: None, - packet_delay: 0, - #[cfg(feature = "port_mapping")] - mapping: vec![], - } - } -} - -pub fn read_config(file_path: &str) -> io::Result<(Config, bool)> { - let conf = std::fs::read_to_string(file_path)?; - let file_conf = match serde_yaml::from_str::(&conf) { - Ok(val) => val, - Err(e) => { - log::error!("{:?}", e); - return Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))); - } - }; - if file_conf.token.is_empty() { - return Err(io::Error::new(io::ErrorKind::Other, "token is_empty")); - } - - let in_ips = match common::args_parse::ips_parse(&file_conf.in_ips) { - Ok(in_ips) => in_ips, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("in_ips {:?} error:{}", &file_conf.in_ips, e), - )); - } - }; - let out_ips = match common::args_parse::out_ips_parse(&file_conf.out_ips) { - Ok(out_ips) => out_ips, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("out_ips {:?} error:{}", &file_conf.out_ips, e), - )); - } - }; - let virtual_ip = match file_conf.ip.clone().map(|v| Ipv4Addr::from_str(&v)) { - None => None, - Some(r) => Some(r.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("ip {:?} error:{}", &file_conf.ip, e), - ) - })?), - }; - - let cipher_model = CipherModel::from_str(&file_conf.cipher_model) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - let punch_model = PunchModel::from_str(&file_conf.punch_model) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let use_channel_type = UseChannelType::from_str(&file_conf.use_channel) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let config = Config::new( - #[cfg(target_os = "windows")] - file_conf.tap, - file_conf.token, - file_conf.device_id, - file_conf.name, - file_conf.server_address, - file_conf.dns, - file_conf.stun_server, - in_ips, - out_ips, - file_conf.password, - file_conf.mtu, - file_conf.tcp, - virtual_ip, - #[cfg(feature = "ip_proxy")] - file_conf.no_proxy, - file_conf.server_encrypt, - file_conf.parallel, - cipher_model, - file_conf.finger, - punch_model, - file_conf.ports, - file_conf.first_latency, - file_conf.device_name, - use_channel_type, - file_conf.packet_loss, - file_conf.packet_delay, - #[cfg(feature = "port_mapping")] - file_conf.mapping, - ) - .unwrap(); - Ok((config, file_conf.cmd)) +#[cfg(not(feature = "file_config"))] +pub fn read_config(_file_path: &str) -> anyhow::Result<(vnt::core::Config, bool)> { + unimplemented!() } pub fn get_device_id() -> String { diff --git a/vnt-cli/src/main.rs b/vnt-cli/src/main.rs index e3611c6..9ad1521 100644 --- a/vnt-cli/src/main.rs +++ b/vnt-cli/src/main.rs @@ -1,7 +1,7 @@ +use std::io; use std::net::Ipv4Addr; use std::path::PathBuf; use std::str::FromStr; -use std::{io, thread}; use console::style; use getopts::Options; @@ -12,8 +12,10 @@ use vnt::channel::UseChannelType; use vnt::cipher::CipherModel; use vnt::core::{Config, Vnt}; +#[cfg(feature = "command")] mod command; mod config; +#[cfg(feature = "command")] mod console_out; mod generated_serial_number; mod root_check; @@ -41,6 +43,7 @@ pub fn app_home() -> io::Result { } fn main() { + #[cfg(feature = "log")] let _ = log4rs::init_file("log4rs.yaml", Default::default()); let args: Vec = std::env::args().collect(); let program = args[0].clone(); @@ -100,6 +103,7 @@ fn main() { sudo::escalate_if_needed().unwrap(); return; } + #[cfg(feature = "command")] if matches.opt_present("list") { command::command(command::CommandEnum::List); return; @@ -342,7 +346,7 @@ fn main() { mod callback; -fn main0(config: Config, show_cmd: bool) { +fn main0(config: Config, _show_cmd: bool) { #[cfg(feature = "port_mapping")] for (is_tcp, addr, dest) in config.port_mapping_list.iter() { if *is_tcp { @@ -352,36 +356,40 @@ fn main0(config: Config, show_cmd: bool) { } } let vnt_util = Vnt::new(config, callback::VntHandler {}).unwrap(); - let vnt_c = vnt_util.clone(); - thread::Builder::new() - .name("CommandServer".into()) - .spawn(move || { - if let Err(e) = command::server::CommandServer::new().start(vnt_c) { - log::warn!("cmd:{:?}", e); - } - }) - .expect("CommandServer"); - if show_cmd { - let mut cmd = String::new(); - loop { - cmd.clear(); - println!("======== input:list,info,route,all,stop ========"); - match io::stdin().read_line(&mut cmd) { - Ok(len) => { - if !command(&cmd[..len], &vnt_util) { + #[cfg(feature = "command")] + { + let vnt_c = vnt_util.clone(); + std::thread::Builder::new() + .name("CommandServer".into()) + .spawn(move || { + if let Err(e) = command::server::CommandServer::new().start(vnt_c) { + log::warn!("cmd:{:?}", e); + } + }) + .expect("CommandServer"); + if _show_cmd { + let mut cmd = String::new(); + loop { + cmd.clear(); + 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; } } - Err(e) => { - println!("input err:{}", e); - break; - } } } } + vnt_util.wait() } - +#[cfg(feature = "command")] fn command(cmd: &str, vnt: &Vnt) -> bool { if cmd.is_empty() { return false; @@ -427,10 +435,11 @@ fn print_usage(program: &str, _opts: Options) { println!(" -s 注册和中继服务器地址,以'TXT:'开头表示解析TXT记录"); println!(" -e stun服务器,用于探测NAT类型,可使用多个地址,如-e stun1.l.google.com -e stun2.l.google.com"); #[cfg(target_os = "windows")] - println!(" -a 使用tap模式,默认使用tun模式"); + println!( + " -a 使用tap模式,默认使用tun模式,使用tap时需要配合'--nic'参数指定tap网卡" + ); 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(any( feature = "aes_gcm", @@ -462,6 +471,7 @@ fn print_usage(program: &str, _opts: Options) { #[cfg(feature = "server_encrypt")] println!(" -W 加密当前客户端和服务端通信的数据,请留意服务端指纹是否正确"); println!(" -u 自定义mtu(不加密默认为1450,加密默认为1410)"); + #[cfg(feature = "file_config")] println!(" -f 读取配置文件中的配置"); println!(" --tcp 和服务端使用tcp通信,默认使用udp,遇到udp qos时可指定使用tcp"); @@ -478,6 +488,7 @@ fn print_usage(program: &str, _opts: Options) { } println!(" --punch 取值ipv4/ipv6/all,ipv4表示仅使用ipv4打洞"); println!(" --ports 取值0~65535,指定本地监听的一组端口,默认监听两个随机端口,使用过多端口会增加网络负担"); + #[cfg(feature = "command")] println!(" --cmd 开启交互式命令,使用此参数开启控制台输入"); #[cfg(feature = "ip_proxy")] println!(" --no-proxy 关闭内置代理,如需点对网则需要配置网卡NAT转发"); @@ -493,26 +504,29 @@ fn print_usage(program: &str, _opts: Options) { println!(" --mapping 端口映射,例如 --mapping udp:0.0.0.0:80->10.26.0.10:80 --mapping tcp:0.0.0.0:80->10.26.0.10:80"); println!(); - println!( - " --list {}", - yellow("后台运行时,查看其他设备列表".to_string()) - ); - println!( - " --all {}", - yellow("后台运行时,查看其他设备完整信息".to_string()) - ); - println!( - " --info {}", - yellow("后台运行时,查看当前设备信息".to_string()) - ); - println!( - " --route {}", - yellow("后台运行时,查看数据转发路径".to_string()) - ); - println!( - " --stop {}", - yellow("停止后台运行".to_string()) - ); + #[cfg(feature = "command")] + { + println!( + " --list {}", + yellow("后台运行时,查看其他设备列表".to_string()) + ); + println!( + " --all {}", + yellow("后台运行时,查看其他设备完整信息".to_string()) + ); + println!( + " --info {}", + yellow("后台运行时,查看当前设备信息".to_string()) + ); + println!( + " --route {}", + yellow("后台运行时,查看数据转发路径".to_string()) + ); + println!( + " --stop {}", + yellow("停止后台运行".to_string()) + ); + } println!(" -h, --help 帮助"); } @@ -520,6 +534,7 @@ fn green(str: String) -> impl std::fmt::Display { style(str).green() } +#[cfg(feature = "command")] fn yellow(str: String) -> impl std::fmt::Display { style(str).yellow() } diff --git a/vnt/src/core/conn.rs b/vnt/src/core/conn.rs index 960b7d6..411e9f6 100644 --- a/vnt/src/core/conn.rs +++ b/vnt/src/core/conn.rs @@ -6,7 +6,6 @@ use std::time::Duration; use crossbeam_utils::atomic::AtomicCell; use parking_lot::{Mutex, RwLock}; use rand::Rng; -use sha2::Digest; #[cfg(not(target_os = "android"))] use tun::device::IFace; @@ -80,14 +79,7 @@ impl Vnt { config.name.clone(), config.token.clone(), config.ip, - config.password.as_ref().map(|v| { - let mut hasher = sha2::Sha256::new(); - hasher.update(config.cipher_model.to_string().as_bytes()); - hasher.update(v.as_bytes()); - hasher.update(config.token.as_bytes()); - let key: [u8; 32] = hasher.finalize().into(); - key[16..].try_into().unwrap() - }), + config.password_hash(), config.server_encrypt, config.device_id.clone(), config.server_address_str.clone(), diff --git a/vnt/src/core/mod.rs b/vnt/src/core/mod.rs index bd1da8e..9ade95b 100644 --- a/vnt/src/core/mod.rs +++ b/vnt/src/core/mod.rs @@ -143,3 +143,33 @@ impl Config { }) } } +impl Config { + #[cfg(any( + feature = "aes_gcm", + feature = "server_encrypt", + feature = "aes_cbc", + feature = "aes_ecb", + feature = "sm4_cbc" + ))] + pub fn password_hash(&self) -> Option<[u8; 16]> { + self.password.as_ref().map(|v| { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(self.cipher_model.to_string().as_bytes()); + hasher.update(v.as_bytes()); + hasher.update(self.token.as_bytes()); + let key: [u8; 32] = hasher.finalize().into(); + key[16..].try_into().unwrap() + }) + } + #[cfg(not(any( + feature = "aes_gcm", + feature = "server_encrypt", + feature = "aes_cbc", + feature = "aes_ecb", + feature = "sm4_cbc" + )))] + pub fn password_hash(&self) -> Option<[u8; 16]> { + None + } +} diff --git a/vnt/src/ip_proxy/icmp_proxy.rs b/vnt/src/ip_proxy/icmp_proxy.rs index df7d6e3..93557cf 100644 --- a/vnt/src/ip_proxy/icmp_proxy.rs +++ b/vnt/src/ip_proxy/icmp_proxy.rs @@ -87,38 +87,36 @@ async fn icmp_proxy( client_cipher: Cipher, ) -> io::Result<()> { let mut buf = [0u8; 65535 - 20 - 8]; + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + let start = 12; + #[cfg(target_os = "android")] + let start = 12 + 20; loop { - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - let start = 12; - #[cfg(target_os = "android")] - let start = 12 + 20; - loop { - let (len, addr) = icmp_socket.recv_from(&mut buf[start..]).await?; - if let IpAddr::V4(peer_ip) = addr.ip() { - #[cfg(target_os = "android")] - { - let buf = &mut buf[12..]; - // ipv4 头部20字节 - buf[0] = 0b0100_0110; - //写入总长度 - buf[2..4].copy_from_slice(&((20 + len) as u16).to_be_bytes()); + let (len, addr) = icmp_socket.recv_from(&mut buf[start..]).await?; + if let IpAddr::V4(peer_ip) = addr.ip() { + #[cfg(target_os = "android")] + { + let buf = &mut buf[12..]; + // ipv4 头部20字节 + buf[0] = 0b0100_0110; + //写入总长度 + buf[2..4].copy_from_slice(&((20 + len) as u16).to_be_bytes()); - let mut ipv4 = IpV4Packet::unchecked(buf); - ipv4.set_flags(2); - ipv4.set_ttl(1); - ipv4.set_protocol(packet::ip::ipv4::protocol::Protocol::Icmp); - ipv4.set_source_ip(peer_ip); - } - recv_handle( - &mut buf, - start + len, - peer_ip, - &nat_map, - &context, - ¤t_device, - &client_cipher, - ); + let mut ipv4 = IpV4Packet::unchecked(buf); + ipv4.set_flags(2); + ipv4.set_ttl(1); + ipv4.set_protocol(packet::ip::ipv4::protocol::Protocol::Icmp); + ipv4.set_source_ip(peer_ip); } + recv_handle( + &mut buf, + start + len, + peer_ip, + &nat_map, + &context, + ¤t_device, + &client_cipher, + ); } } } diff --git a/vnt/src/util/dns_query.rs b/vnt/src/util/dns_query.rs index cd4f77a..3104e2b 100644 --- a/vnt/src/util/dns_query.rs +++ b/vnt/src/util/dns_query.rs @@ -41,8 +41,8 @@ pub fn address_choose(addrs: Vec) -> anyhow::Result { /// 后续实现选择延迟最低的可用地址,需要服务端配合 /// 现在是选择第一个地址,优先ipv6 fn address_choose0(addrs: Vec) -> anyhow::Result { - let v4: Vec = addrs.iter().filter(|v| v.is_ipv4()).map(|v| *v).collect(); - let v6: Vec = addrs.iter().filter(|v| v.is_ipv6()).map(|v| *v).collect(); + let v4: Vec = addrs.iter().filter(|v| v.is_ipv4()).copied().collect(); + let v6: Vec = addrs.iter().filter(|v| v.is_ipv6()).copied().collect(); let check_addr = |addrs: &Vec| -> anyhow::Result { if !addrs.is_empty() { let udp = if addrs[0].is_ipv6() { @@ -78,9 +78,7 @@ pub fn dns_query_all( mut name_servers: Vec, ) -> anyhow::Result> { match SocketAddr::from_str(domain) { - Ok(addr) => { - return Ok(vec![addr]); - } + Ok(addr) => Ok(vec![addr]), Err(_) => { let txt_domain = domain .to_lowercase() @@ -94,7 +92,6 @@ pub fn dns_query_all( return Ok(domain .to_socket_addrs() .with_context(|| format!("DNS query failed {:?}", domain))? - .into_iter() .collect()); } } @@ -119,7 +116,7 @@ pub fn dns_query_all( continue; } let end_index = domain - .rfind(":") + .rfind(':') .with_context(|| format!("{:?} not port", domain))?; let host = &domain[..end_index]; let port = u16::from_str(&domain[end_index + 1..]) @@ -219,7 +216,7 @@ fn query<'a>( domain )); } - if pkt.answers.len() == 0 { + if pkt.answers.is_empty() { return Err(anyhow::anyhow!( "No records received DNS {:?} domain {:?}", name_server, @@ -240,8 +237,8 @@ pub fn txt_dns(domain: &str, name_server: String) -> anyhow::Result