diff --git a/Cargo.lock b/Cargo.lock index 10045db..d1b944a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -307,7 +307,7 @@ dependencies = [ [[package]] name = "common" -version = "1.2.12" +version = "1.2.13" dependencies = [ "anyhow", "chrono", @@ -858,9 +858,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -869,7 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1005,6 +1005,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "network-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433419f898328beca4f2c6c73a1b52540658d92b0a99f0269330457e0fd998d5" +dependencies = [ + "cc", + "libc", + "thiserror", + "winapi", +] + [[package]] name = "nom" version = "7.1.3" @@ -1747,9 +1759,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2106,7 +2118,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vn-link" -version = "1.2.12" +version = "1.2.13" dependencies = [ "anyhow", "crossbeam-utils", @@ -2119,7 +2131,7 @@ dependencies = [ [[package]] name = "vn-link-cli" -version = "1.2.12" +version = "1.2.13" dependencies = [ "common", "log", @@ -2129,7 +2141,7 @@ dependencies = [ [[package]] name = "vnt" -version = "1.2.12" +version = "1.2.13" dependencies = [ "aes", "aes-gcm", @@ -2153,6 +2165,7 @@ dependencies = [ "log", "lz4_flex", "mio", + "network-interface", "openssl-sys", "packet", "parking_lot", @@ -2171,12 +2184,13 @@ dependencies = [ "tokio", "tokio-tungstenite", "tun", + "windows-sys 0.59.0", "zstd", ] [[package]] name = "vnt-cli" -version = "1.2.12" +version = "1.2.13" dependencies = [ "anyhow", "chrono", @@ -2310,7 +2324,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2328,7 +2342,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2348,18 +2371,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2370,9 +2393,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2382,9 +2405,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2394,15 +2417,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2412,9 +2435,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2424,9 +2447,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2436,9 +2459,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2448,9 +2471,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "xml-rs" diff --git a/README.md b/README.md index a1006bc..74f7ee4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Vnt +# VNT -A virtual network tool (VPN) +🚀An efficient VPN -将不同网络下的多个设备虚拟到一个局域网下 +🚀一个简单、高效、能快速组建虚拟局域网的工具 ### vnt-cli参数详解 [参数说明](https://github.com/vnt-dev/vnt/blob/main/vnt-cli/README.md) -### 快速使用: +### 快速开始: 1. 指定一个token,在多台设备上运行该程序,例如: ```shell @@ -42,24 +42,23 @@ A virtual network tool (VPN) ssh 5. 帮助,使用-h命令查看 -### 更多玩法 - -1. 和远程桌面(如mstsc)搭配,超低延迟的体验 -2. 安装samba服务,共享磁盘 -3. 搭配公网服务器nginx反向代理,在公网访问内网文件或服务 -4. 点对网,访问内网其他机器、IP代理(结合启动参数'-i'和'-o') - ### 使用须知 - token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中 - 默认使用公共服务器做注册和中继,目前的配置是2核4G 4Mbps,有需要再扩展~ -- 需要root/管理员权限 - vnt-cli需要使用命令行运行 - Mac和Linux下需要加可执行权限(例如:chmod +x ./vnt-cli) -- 可以自己搭注册和中继服务器([server](https://github.com/vnt-dev/vnts)) -- vnt使用stun服务器探测网络NAT类型,默认使用谷歌和腾讯的stun服务器,也可自己搭建(-e参数指定) +- 可以自己搭中继服务器([server](https://github.com/vnt-dev/vnts)) -### 编译 +### 直接使用 + +[**下载release文件**](https://github.com/vnt-dev/vnt/releases) + +[**帮助文档**](https://rustvnt.com) + +### 自行编译 + +
点击展开 前提条件:安装rust编译环境([install rust](https://www.rust-lang.org/zh-CN/tools/install)) @@ -91,82 +90,9 @@ features说明 | file_config | yaml配置文件 | 是 | | lz4 | lz4压缩 | 是 | | zstd | zstd压缩 | 否 | - -### 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 -# 开启nat转发 表示来源10.26.0.0/24的数据通过nat映射后再从vnt-tun以外的其他网卡发出去 -sudo iptables -t nat -A POSTROUTING ! -o vnt-tun -s 10.26.0.0/24 -j MASQUERADE -# 或者这样 表示来源10.26.0.0/24的数据通过nat映射后再从eth0网卡发出去 -sudo iptables -t nat -A POSTROUTING -o eth0 -s 10.26.0.0/24 -j MASQUERADE -# 查看设置 -iptables -vnL -t nat -``` - -### Arch Linux - -[![Packaging status](https://repology.org/badge/vertical-allrepos/vnt.svg)](https://repology.org/project/vnt/versions) - -- 通过 AUR 安装 [vnt-git](https://aur.archlinux.org/packages/vnt-git) - -```bash -yay -Syu vnt -``` - -- 通过 `systemd` 设置开机自启及配置 - -```bash -sudo systemctl enable --now vnt-cli@ -sudo systemctl status vnt-cli@ -``` - -- 启用内置 `IPv4` 转发规则 - -```bash -sudo sysctl --system -``` - -- 通过内置防火墙文件配置防火墙转发规则 - -```bash -sudo cat /etc/vnt/iptables-vnt.rules >> /etc/iptables/iptables.rules -sudo iptables-restore iptables.rules -``` - -### macos - -```shell -# 开启ip转发 -sudo sysctl -w net.ipv4.ip_forward=1 -# 配置NAT转发规则 -# 在/etc/pf.conf文件中添加以下规则,en0是出口网卡,10.26.0.0/24是来源网段 -nat on en0 from 10.26.0.0/24 to any -> (en0) -# 加载规则 -sudo pfctl -f /etc/pf.conf -e -``` +| upnp | upnp协议 | 否 | +| ws | ws协议 | 是 | +| wss | wss协议 | 是 |
@@ -176,7 +102,7 @@ sudo pfctl -f /etc/pf.conf -e - Linux - Windows - 默认使用tun网卡 依赖wintun.dll([win-tun](https://www.wintun.net/))(将dll放到同目录下,建议使用版本0.14.1) - - 使用tap网卡 依赖tap-windows([win-tap](https://build.openvpn.net/downloads/releases/))(建议使用版本9.24.7) + - 可选择使用tap网卡 依赖tap-windows([win-tap](https://build.openvpn.net/downloads/releases/))(建议使用版本9.24.7) - Android ### GUI @@ -186,50 +112,28 @@ sudo pfctl -f /etc/pf.conf -e ### 特性 - IP层数据转发 - - tun虚拟网卡 - - tap虚拟网卡 - NAT穿透 - 点对点穿透 - 服务端中继转发 - 客户端中继转发 -- IP代理 +- IP代理(点对点、点对网) - p2p组播/广播 -- 客户端数据加密 -- 服务端数据加密 +- 客户端数据加密(`aes-gcm`、`chacha20-poly1305`等多种加密算法) +- 服务端数据加密(`rsa` + `aes-gcm`) +- 多通道UDP应对QOS +- 支持TCP、UDP、WebSocket等多种协议 +- 支持数据压缩 -### 结构 +### 更多玩法 -
展开 - -
-    
-   0                                            15                                              31
-   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9  0  1
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  |e |s |unused| 版本(4)  |      协议(8)        |     上层协议(8)        |初始ttl(4)|生存时间(4)  |
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  |                                        源ip地址(32)                                         |
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  |                                        目的ip地址(32)                                       |
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  |                                          数据体(n)                                          |
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  |                                                                                             |
-  |                                          指纹(96)                                           |
-  |                                                                                             |
-  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  注:
-  1. e为是否加密标志,s为服务端通信包标志,unused占两位未使用;
-  2. 开启加密时,数据体为加密后的密文(加密方式取决于密码长度和加密模式),
-     且会存在指纹,指纹使用sha256生成,用于对数据包完整性和真实性的校验
-
- - -
+1. 和远程桌面(如mstsc)搭配,超低延迟的体验 +2. 安装samba服务,共享磁盘 +3. 点对网,访问内网其他机器、IP代理(结合启动参数'-i'和'-o') ### Todo -- 桌面UI(测试中) +- ~~桌面UI(已支持)~~ +- 使用FEC、ARQ等方式提升弱网环境的稳定性 ### 常见问题 @@ -288,7 +192,9 @@ vnt默认使用10.26.0.0/24网段,和本地网络适配器的ip冲突 对VNT有任何问题均可以加群联系作者 QQ: 1034868233 + ### 赞助 + 如果VNT对你有帮助,欢迎打赏作者 diff --git a/common/Cargo.toml b/common/Cargo.toml index e7dd42b..27d0dfa 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "common" -version = "1.2.12" +version = "1.2.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/src/callback.rs b/common/src/callback.rs index a1801a3..4fffa03 100644 --- a/common/src/callback.rs +++ b/common/src/callback.rs @@ -10,7 +10,7 @@ impl VntCallback for VntHandler { fn success(&self) { println!(" {} ", style("====== Connect Successfully ======").green()) } - #[cfg(feature = "vnt-model")] + #[cfg(feature = "integrated_tun")] fn create_tun(&self, info: vnt::DeviceInfo) { println!("create_tun {}", info) } diff --git a/common/src/cli.rs b/common/src/cli.rs index d340ade..4199606 100644 --- a/common/src/cli.rs +++ b/common/src/cli.rs @@ -76,6 +76,7 @@ pub fn parse_args_config() -> anyhow::Result, bool)> opts.optmulti("", "vnt-mapping", "vnt-mapping", ""); opts.optopt("f", "", "配置文件", ""); opts.optopt("", "compressor", "压缩算法", ""); + opts.optopt("", "local-ipv4", "指定本地ipv4网卡IP", ""); opts.optflag("", "disable-stats", "关闭流量统计"); opts.optflag("", "allow-wg", "允许接入WireGuard"); //"后台运行时,查看其他设备列表" @@ -283,6 +284,15 @@ pub fn parse_args_config() -> anyhow::Result, bool)> #[cfg(feature = "port_mapping")] let port_mapping_list = matches.opt_strs("mapping"); let vnt_mapping_list = matches.opt_strs("vnt-mapping"); + let local_ipv4: Option = matches.opt_get("local-ipv4").unwrap(); + let local_ipv4 = local_ipv4 + .map(|v| Ipv4Addr::from_str(&v).expect(&format!("'--local-ipv4 {}' error", v))); + if let Some(local_ipv4) = local_ipv4 { + if local_ipv4.is_unspecified() || local_ipv4.is_broadcast() || local_ipv4.is_multicast() + { + return Err(anyhow::anyhow!("'--local-ipv4 {}' invalid", local_ipv4)); + } + } let disable_stats = matches.opt_present("disable-stats"); let allow_wire_guard = matches.opt_present("allow-wg"); let compressor = if let Some(compressor) = matches.opt_str("compressor").as_ref() { @@ -326,6 +336,7 @@ pub fn parse_args_config() -> anyhow::Result, bool)> compressor, !disable_stats, allow_wire_guard, + local_ipv4, ) { Ok(config) => config, Err(e) => { @@ -358,12 +369,12 @@ fn get_description(key: &str, language: &str) -> String { ("-o ", ("配置点对网时使用,-o 192.168.0.0/24表示允许将数据转发到192.168.0.0/24,可指定多个网段", "Used when configuring point-to-point network, -o 192.168.0.0/24 allows forwarding data to 192.168.0.0/24, specify multiple subnets")), ("-w ", ("使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "Encrypt client data with keys generated by this password, server cannot decrypt, clients must use the same password to communicate")), ("-W", ("加密当前客户端和服务端通信的数据,请留意服务端指纹是否正确", "Encrypt the data currently being communicated between the client and server, please pay attention to whether the server fingerprint is correct")), - ("-u ", ("自定义mtu(不加密默认为1450,加密默认为1410", "Customize MTU (1450 by default without encryption, 1410 with encryption)")), + ("-u ", ("自定义mtu(默认为1420)", "Customize MTU (default is 1420)")), ("-f ", ("读取配置文件中的配置", "Read configuration from file")), ("--ip ", ("指定虚拟ip,指定的ip不能和其他设备重复,必须有效并且在服务端所属网段下,默认情况由服务端分配", "Specify virtual IP, must be unique and valid within server subnet, by default allocated by server")), - ("--model ", ("加密模式(默认aes_gcm),可选值{}", "Encryption mode (default aes_gcm), options {}")), + ("--model ", ("加密模式(默认aes_gcm),可选值", "Encryption mode (default aes_gcm), options ")), ("--finger", ("增加数据指纹校验,可增加安全性,如果服务端开启指纹校验,则客户端也必须开启", "Add data fingerprint verification for increased security, client must enable if server does")), - ("--punch ", ("取值ipv4/ipv6/all,ipv4表示仅使用ipv4打洞", "Values ipv4/ipv6/all, ipv4 for IPv4 hole punching only")), + ("--punch ", ("取值ipv4/ipv6/ipv4-tcp/ipv4-udp/ipv6-tcp/ipv6-udp/all,ipv4表示仅使用ipv4打洞", "Values ipv4/ipv6/ipv4-tcp/ipv4-udp/ipv6-tcp/ipv6-udp/all, ipv4 for IPv4 hole punching only")), ("--ports ", ("取值0~65535,指定本地监听的一组端口,默认监听两个随机端口,使用过多端口会增加网络负担", "Values 0~65535, specify a group of local listening ports, defaults to two random ports, using many ports increases network load")), ("--cmd", ("开启交互式命令,使用此参数开启控制台输入", "Enable interactive command mode, use this parameter to enable console input")), ("--no-proxy", ("关闭内置代理,如需点对网则需要配置网卡NAT转发", "Disable built-in proxy, configure network card NAT forwarding for point-to-point networking")), @@ -371,14 +382,16 @@ fn get_description(key: &str, language: &str) -> String { ("--use-channel ", ("使用通道 relay/p2p/all,默认两者都使用", "Use channel relay/p2p/all, defaults to using both")), ("--nic ", ("指定虚拟网卡名称", "Specify virtual network card name")), ("--packet-loss <0>", ("模拟丢包,取值0~1之间的小数,程序会按设定的概率主动丢包,可用于模拟弱网", "Simulate packet loss, value between 0 and 1, program actively drops packets based on set probability, useful for simulating weak networks")), - ("--packet-delay <0>", ("模拟丢包,取值0~1之间的小数,程序会按设定的概率主动丢包,可用于模拟弱网", "Simulate latency, integer, in milliseconds (ms). The program will delay sending packets according to the set value and can be used to simulate weak networks")), + ("--packet-delay <0>", ("模拟延迟,正整数,单位毫秒,程序将根据设定值延迟发送数据包,可用于模拟弱网", "Simulate latency, integer, in milliseconds (ms). The program will delay sending packets according to the set value and can be used to simulate weak networks")), ("--dns ", ("DNS服务器地址,可使用多个dns,不指定时使用系统解析", "DNS server address, can specify multiple DNS servers, defaults to system resolution if not specified")), ("--mapping ", ("端口映射,例如 --mapping udp:0.0.0.0:80-domain:80 映射目标是本地路由能访问的设备", "Port mapping, e.g., --mapping udp:0.0.0.0:80-domain:80 maps to a device accessible by local routing")), ("--compressor-all ", ("启用压缩,可选值lz4/zstd<,level>,level为压缩级别,例如 --compressor lz4 或--compressor zstd,10", "Enable compression, options lz4/zstd<,level>, level is compression level, e.g., --compressor lz4 or --compressor zstd,10")), ("--compressor-lz4 ", ("启用压缩,可选值lz4,例如 --compressor lz4", "Enable compression, option lz4, e.g., --compressor lz4")), ("--compressor-zstd ", ("启用压缩,可选值zstd<,level>,level为压缩级别,例如 --compressor zstd,10", "Enable compression, options zstd<,level>, level is compression level, e.g., --compressor zstd,10")), ("--vnt-mapping ", ("vnt地址映射,例如 --vnt-mapping tcp:80-10.26.0.10:80 映射目标是vnt网络或其子网中的设备", "VNT address mapping, e.g., --vnt-mapping tcp:80-10.26.0.10:80 maps to a device in VNT network or its subnet")), + ("--local-ipv4", ("本地出口网卡的ipv4地址", "IPv4 address of local export network card")), ("--disable-stats", ("关闭流量统计", "Disable traffic statistics")), + ("--allow-wg", ("允许接入WireGuard客户端", "Allow access to WireGuard client")), ("--list", ("后台运行时,查看其他设备列表", "View list of other devices when running in background")), ("--all", ("后台运行时,查看其他设备完整信息", "View complete information of other devices when running in background")), ("--info", ("后台运行时,查看当前设备信息", "View information of current device when running in background")), @@ -564,11 +577,18 @@ fn print_usage(program: &str, _opts: Options) { " --vnt-mapping {}", green(get_description("--vnt-mapping ", &language).to_string()) ); + println!( + " --local-ipv4 {}", + get_description("--local-ipv4", &language) + ); println!( " --disable-stats {}", get_description("--disable-stats", &language) ); - println!(" --allow-wg 允许接入WireGuard客户端"); + println!( + " --allow-wg {}", + get_description("--allow-wg", &language) + ); println!(); #[cfg(feature = "command")] { diff --git a/common/src/config/file_config.rs b/common/src/config/file_config.rs index c5b2b1b..97c72a5 100644 --- a/common/src/config/file_config.rs +++ b/common/src/config/file_config.rs @@ -48,6 +48,7 @@ pub struct FileConfig { pub disable_stats: bool, // 允许传递wg流量 pub allow_wire_guard: bool, + pub local_ipv4: Option, } impl Default for FileConfig { @@ -93,6 +94,7 @@ impl Default for FileConfig { vnt_mapping: vec![], disable_stats: false, allow_wire_guard: false, + local_ipv4: None, } } } @@ -126,12 +128,12 @@ pub fn read_config(file_path: &str) -> anyhow::Result<(Config, Vec, bool None => None, Some(r) => Some(r.map_err(|e| anyhow!("ip {:?} error:{}", &file_conf.ip, e))?), }; - let cipher_model = { + let cipher_model = if let Some(v) = file_conf.cipher_model { + CipherModel::from_str(&v).map_err(|e| anyhow!("{}", e))? + } else { #[cfg(not(any(feature = "aes_gcm", feature = "server_encrypt")))] - if file_conf.password.is_some() && file_conf.cipher_model.is_none() { + if file_conf.password.is_some() { Err(anyhow!("cipher_model undefined"))? - } else if let Some(v) = file_conf.cipher_model { - CipherModel::from_str(&v).map_err(|e| anyhow!("{}", e))? } else { CipherModel::None } @@ -181,6 +183,7 @@ pub fn read_config(file_path: &str) -> anyhow::Result<(Config, Vec, bool compressor, !file_conf.disable_stats, file_conf.allow_wire_guard, + file_conf.local_ipv4, )?; Ok((config, file_conf.vnt_mapping, file_conf.cmd)) diff --git a/vn-link-cli/Cargo.toml b/vn-link-cli/Cargo.toml index 9f8ada2..a5b43fa 100644 --- a/vn-link-cli/Cargo.toml +++ b/vn-link-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vn-link-cli" -version = "1.2.12" +version = "1.2.13" edition = "2021" [dependencies] diff --git a/vn-link-cli/src/main.rs b/vn-link-cli/src/main.rs index 33d526d..85a7a3a 100644 --- a/vn-link-cli/src/main.rs +++ b/vn-link-cli/src/main.rs @@ -12,6 +12,11 @@ fn main() { } } Err(e) => { + log::error!( + "parse error={:?} cmd={:?}", + e, + std::env::args().collect::>() + ); println!("{}", e); return; } diff --git a/vn-link/Cargo.toml b/vn-link/Cargo.toml index 476a804..2c2b343 100644 --- a/vn-link/Cargo.toml +++ b/vn-link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vn-link" -version = "1.2.12" +version = "1.2.13" edition = "2021" [dependencies] diff --git a/vnt-cli/Cargo.toml b/vnt-cli/Cargo.toml index 5d4c665..b9495dd 100644 --- a/vnt-cli/Cargo.toml +++ b/vnt-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vnt-cli" -version = "1.2.12" +version = "1.2.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/vnt-cli/src/main.rs b/vnt-cli/src/main.rs index 4c5a23f..edad9a6 100644 --- a/vnt-cli/src/main.rs +++ b/vnt-cli/src/main.rs @@ -11,6 +11,11 @@ fn main() { } } Err(e) => { + log::error!( + "parse error={:?} cmd={:?}", + e, + std::env::args().collect::>() + ); println!("{}", e); return; } diff --git a/vnt/Cargo.toml b/vnt/Cargo.toml index 63d8c8a..1289af0 100644 --- a/vnt/Cargo.toml +++ b/vnt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vnt" -version = "1.2.12" +version = "1.2.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -18,7 +18,7 @@ rand = "0.8.5" sha2 = { version = "0.10.6", features = ["oid"] } thiserror = "1.0.37" protobuf = "=3.2.0" -socket2 = { version = "0.5.2", features = ["all"] } +socket2 = { version = "0.5.7", features = ["all"] } aes-gcm = { version = "0.10.2", optional = true } ring = { version = "0.17.0", optional = true } cbc = { version = "0.1.2", optional = true } @@ -46,10 +46,19 @@ fnv = "1.0.7" igd = { version = "0.12.1", optional = true } tokio-tungstenite = { version = "0.23.1", optional = true } rustls = { version = "0.23.0", features = ["ring"], default-features = false, optional = true } + +network-interface = "2.0.0" + futures-util = "0.3.30" [target.'cfg(target_os = "windows")'.dependencies] libloading = "0.8.0" - +windows-sys = {version = "0.59.0",features = [ "Win32_Foundation", + "Win32_NetworkManagement", + "Win32_NetworkManagement_IpHelper", + "Win32_Networking_WinSock", + "Win32_System_IO", + "Win32_System_Threading", + "Win32_System_WindowsProgramming",]} [build-dependencies] protobuf-codegen = "=3.2.0" diff --git a/vnt/src/channel/context.rs b/vnt/src/channel/context.rs index 0bdce08..505df6f 100644 --- a/vnt/src/channel/context.rs +++ b/vnt/src/channel/context.rs @@ -1,7 +1,7 @@ use fnv::FnvHashMap; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV6, UdpSocket}; +use std::net::{Ipv4Addr, SocketAddr, UdpSocket}; use std::ops::Deref; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; use std::sync::Arc; use std::time::{Duration, Instant}; use std::{io, thread}; @@ -12,6 +12,7 @@ use rand::Rng; use crate::channel::punch::NatType; use crate::channel::sender::{AcceptSocketSender, PacketSender}; +use crate::channel::socket::LocalInterface; use crate::channel::{ConnectProtocol, Route, RouteKey, UseChannelType, DEFAULT_RT}; use crate::protocol::NetPacket; use crate::util::limit::TrafficMeterMultiAddress; @@ -25,16 +26,17 @@ pub struct ChannelContext { impl ChannelContext { pub fn new( main_udp_socket: Vec, + v4_len: usize, use_channel_type: UseChannelType, first_latency: bool, protocol: ConnectProtocol, packet_loss_rate: Option, packet_delay: u32, - use_ipv6: bool, up_traffic_meter: Option, down_traffic_meter: Option, + default_interface: LocalInterface, ) -> Self { - let channel_num = main_udp_socket.len(); + let channel_num = v4_len; assert_ne!(channel_num, 0, "not channel"); let packet_loss_rate = packet_loss_rate .map(|v| { @@ -48,16 +50,16 @@ impl ChannelContext { .unwrap_or(0); let inner = ContextInner { main_udp_socket, + v4_len, sub_udp_socket: RwLock::new(Vec::new()), packet_map: RwLock::new(FnvHashMap::default()), route_table: RouteTable::new(use_channel_type, first_latency, channel_num), protocol, packet_loss_rate, packet_delay, - main_index: AtomicUsize::new(0), - use_ipv6, up_traffic_meter, down_traffic_meter, + default_interface, }; Self { inner: Arc::new(inner), @@ -80,6 +82,7 @@ const PACKET_LOSS_RATE_DENOMINATOR: u32 = 100_0000; pub struct ContextInner { // 核心udp socket pub(crate) main_udp_socket: Vec, + v4_len: usize, // 对称网络增加的udp socket sub_udp_socket: RwLock>, // tcp数据发送器 @@ -92,16 +95,18 @@ pub struct ContextInner { packet_loss_rate: u32, //控制延迟 packet_delay: u32, - main_index: AtomicUsize, - use_ipv6: bool, pub(crate) up_traffic_meter: Option, pub(crate) down_traffic_meter: Option, + default_interface: LocalInterface, } impl ContextInner { pub fn use_channel_type(&self) -> UseChannelType { self.route_table.use_channel_type } + pub fn default_interface(&self) -> &LocalInterface { + &self.default_interface + } /// 通过sub_udp_socket是否为空来判断是否为锥形网络 pub fn is_cone(&self) -> bool { self.sub_udp_socket.read().is_empty() @@ -120,7 +125,7 @@ impl ContextInner { &self, nat_type: NatType, udp_socket_sender: &AcceptSocketSender>>, - ) -> io::Result<()> { + ) -> anyhow::Result<()> { let mut write_guard = self.sub_udp_socket.write(); match nat_type { NatType::Symmetric => { @@ -129,9 +134,11 @@ impl ContextInner { } let mut vec = Vec::with_capacity(SYMMETRIC_CHANNEL_NUM); for _ in 0..SYMMETRIC_CHANNEL_NUM { - let udp = UdpSocket::bind("0.0.0.0:0")?; - //副通道使用异步io - udp.set_nonblocking(true)?; + let udp = crate::channel::socket::bind_udp( + "0.0.0.0:0".parse().unwrap(), + &self.default_interface, + )?; + let udp: UdpSocket = udp.into(); vec.push(udp); } let mut mio_vec = Vec::with_capacity(SYMMETRIC_CHANNEL_NUM); @@ -152,14 +159,18 @@ impl ContextInner { } Ok(()) } - + #[inline] pub fn channel_num(&self) -> usize { + self.v4_len + } + #[inline] + pub fn main_len(&self) -> usize { self.main_udp_socket.len() } /// 获取核心udp监听的端口,用于其他客户端连接 pub fn main_local_udp_port(&self) -> io::Result> { let mut ports = Vec::new(); - for udp in self.main_udp_socket.iter() { + for udp in self.main_udp_socket[..self.v4_len].iter() { ports.push(udp.local_addr()?.port()) } Ok(ports) @@ -171,20 +182,13 @@ impl ContextInner { Err(io::Error::from(io::ErrorKind::NotFound)) } } - pub fn send_main_udp(&self, index: usize, buf: &[u8], mut addr: SocketAddr) -> io::Result<()> { - if self.use_ipv6 { - //如果是v4地址则需要转换成v6 - if let SocketAddr::V4(ipv4) = addr { - addr = SocketAddr::V6(SocketAddrV6::new( - ipv4.ip().to_ipv6_mapped(), - ipv4.port(), - 0, - 0, - )); - } + pub fn send_main_udp(&self, index: usize, buf: &[u8], addr: SocketAddr) -> io::Result<()> { + if let Some(udp) = self.main_udp_socket.get(index) { + udp.send_to(buf, addr)?; + Ok(()) + } else { + Err(io::Error::new(io::ErrorKind::Other, "overflow")) } - self.main_udp_socket[index].send_to(buf, addr)?; - Ok(()) } /// 将数据发送到默认通道,一般发往服务器才用此方法 pub fn send_default>( @@ -193,7 +197,11 @@ impl ContextInner { addr: SocketAddr, ) -> io::Result<()> { if self.protocol.is_udp() { - self.send_main_udp(self.main_index.load(Ordering::Relaxed), buf.buffer(), addr)? + if addr.is_ipv4() { + self.send_main_udp(0, buf.buffer(), addr)? + } else { + self.send_main_udp(self.v4_len, buf.buffer(), addr)? + } } else { self.send_tcp(buf.buffer(), addr)? } @@ -203,10 +211,6 @@ impl ContextInner { Ok(()) } - pub fn change_main_index(&self) { - let index = (self.main_index.load(Ordering::Relaxed) + 1) % self.main_udp_socket.len(); - self.main_index.store(index, Ordering::Relaxed); - } /// 此方法仅用于对称网络打洞 pub fn try_send_all(&self, buf: &[u8], addr: SocketAddr) { self.try_send_all_main(buf, addr); @@ -287,7 +291,7 @@ impl ContextInner { if let Some(udp) = self .sub_udp_socket .read() - .get(route_key.index - self.main_udp_socket.len()) + .get(route_key.index - self.main_len()) { udp.send_to(buf.buffer(), route_key.addr)?; } else { diff --git a/vnt/src/channel/mod.rs b/vnt/src/channel/mod.rs index 639ab4c..8cc1f3b 100644 --- a/vnt/src/channel/mod.rs +++ b/vnt/src/channel/mod.rs @@ -6,6 +6,7 @@ use tokio::sync::mpsc::channel; use crate::channel::context::ChannelContext; use crate::channel::handler::RecvChannelHandler; use crate::channel::sender::{AcceptSocketSender, ConnectUtil}; +use crate::channel::socket::{bind_udp, LocalInterface}; use crate::channel::tcp_channel::tcp_listen; use crate::channel::udp_channel::udp_listen; #[cfg(feature = "ws")] @@ -19,6 +20,7 @@ pub mod idle; pub mod notify; pub mod punch; pub mod sender; +pub mod socket; pub mod tcp_channel; pub mod udp_channel; #[cfg(feature = "ws")] @@ -201,11 +203,13 @@ pub(crate) fn init_context( protocol: ConnectProtocol, packet_loss_rate: Option, packet_delay: u32, + default_interface: LocalInterface, up_traffic_meter: Option, down_traffic_meter: Option, ) -> anyhow::Result<(ChannelContext, std::net::TcpListener)> { assert!(!ports.is_empty(), "not channel"); - let mut udps = Vec::with_capacity(ports.len()); + let mut main_udp_socket_v4 = Vec::with_capacity(ports.len()); + let mut main_udp_socket_v6 = Vec::with_capacity(ports.len()); //检查系统是否支持ipv6 let use_ipv6 = match socket2::Socket::new(socket2::Domain::IPV6, socket2::Type::DGRAM, None) { Ok(_) => true, @@ -215,40 +219,33 @@ pub(crate) fn init_context( } }; for port in &ports { - //监听v6+v4双栈 - let (socket, address) = if use_ipv6 { - let address: SocketAddr = format!("[::]:{}", port).parse().unwrap(); - let socket = socket2::Socket::new(socket2::Domain::IPV6, socket2::Type::DGRAM, None)?; - socket - .set_only_v6(false) - .with_context(|| format!("set_only_v6 failed: {}", &address))?; - (socket, address) + let addr_v4: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap(); + if use_ipv6 { + let (main_channel_v4, main_channel_v6) = bind_udp_v4_and_v6(*port, &default_interface)?; + main_udp_socket_v4.push(main_channel_v4); + main_udp_socket_v6.push(main_channel_v6); } else { - let address: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap(); - ( - socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None)?, - address, - ) - }; - if let Err(e) = socket.set_recv_buffer_size(2 * 1024 * 1024) { - log::warn!("set_recv_buffer_size {:?}", e); + let socket = bind_udp(addr_v4, &default_interface)?; + let main_channel_v4: UdpSocket = socket.into(); + main_udp_socket_v4.push(main_channel_v4); } - socket - .bind(&address.into()) - .with_context(|| format!("bind failed: {}", &address))?; - let main_channel: UdpSocket = socket.into(); - udps.push(main_channel); } + let mut main_udp_socket = + Vec::with_capacity(main_udp_socket_v4.len() + main_udp_socket_v6.len()); + let v4_len = main_udp_socket_v4.len(); + main_udp_socket.append(&mut main_udp_socket_v4); + main_udp_socket.append(&mut main_udp_socket_v6); let context = ChannelContext::new( - udps, + main_udp_socket, + v4_len, use_channel_type, first_latency, protocol, packet_loss_rate, packet_delay, - use_ipv6, up_traffic_meter, down_traffic_meter, + default_interface, ); let port = context.main_local_udp_port()?[0]; @@ -265,7 +262,7 @@ pub(crate) fn init_context( let socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)?; (socket, address) }; - + let _ = socket.set_reuse_address(true); if let Err(e) = socket.bind(&address.into()) { if ports[0] == 0 { //端口可能冲突,则使用任意端口 @@ -285,9 +282,49 @@ pub(crate) fn init_context( } socket.listen(128)?; socket.set_nonblocking(true)?; - socket.set_nodelay(false)?; + socket.set_nodelay(true)?; Ok((context, socket.into())) } +fn bind_udp_v4_and_v6( + port: u16, + default_interface: &LocalInterface, +) -> anyhow::Result<(UdpSocket, UdpSocket)> { + let mut count = 0; + loop { + let addr_v4: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap(); + let socket = bind_udp(addr_v4, default_interface)?; + if let Err(e) = socket.set_recv_buffer_size(2 * 1024 * 1024) { + log::warn!("set_recv_buffer_size {:?}", e); + } + let main_channel_v4: UdpSocket = socket.into(); + let addr = main_channel_v4.local_addr()?; + let addr_v6: SocketAddr = format!("[::]:{}", addr.port()).parse().unwrap(); + let socket = if port == 0 { + match bind_udp(addr_v6, default_interface) { + Ok(socket) => socket, + Err(e) => { + if count > 10 { + return Err(e); + } + if let Some(e) = e.downcast_ref::() { + if e.kind() == std::io::ErrorKind::AddrInUse { + count += 1; + continue; + } + } + Err(e)? + } + } + } else { + bind_udp(addr_v6, default_interface)? + }; + if let Err(e) = socket.set_recv_buffer_size(2 * 1024 * 1024) { + log::warn!("set_recv_buffer_size {:?}", e); + } + let main_channel_v6: UdpSocket = socket.into(); + return Ok((main_channel_v4, main_channel_v6)); + } +} pub(crate) fn init_channel( tcp_listener: std::net::TcpListener, diff --git a/vnt/src/channel/punch.rs b/vnt/src/channel/punch.rs index 07764a8..76e476c 100644 --- a/vnt/src/channel/punch.rs +++ b/vnt/src/channel/punch.rs @@ -1,4 +1,3 @@ -use crossbeam_utils::atomic::AtomicCell; use std::collections::HashMap; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::ops::{Div, Mul}; @@ -7,22 +6,47 @@ use std::sync::Arc; use std::time::Duration; use std::{io, thread}; +use crossbeam_utils::atomic::AtomicCell; use rand::prelude::SliceRandom; use rand::Rng; use crate::channel::context::ChannelContext; use crate::channel::sender::ConnectUtil; -use crate::external_route::ExternalRoute; use crate::handle::CurrentDeviceInfo; -use crate::nat::NatTest; +use crate::nat::{is_ipv4_global, NatTest}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum PunchModel { IPv4, IPv6, + IPv4Tcp, + IPv4Udp, + IPv6Tcp, + IPv6Udp, All, } +impl PunchModel { + pub fn use_tcp(&self) -> bool { + self != &PunchModel::IPv4Udp && self != &PunchModel::IPv6Udp + } + pub fn use_udp(&self) -> bool { + self != &PunchModel::IPv4Tcp && self != &PunchModel::IPv6Tcp + } + pub fn use_ipv6(&self) -> bool { + self == &PunchModel::All + || self == &PunchModel::IPv6 + || self == &PunchModel::IPv6Tcp + || self == &PunchModel::IPv6Udp + } + pub fn use_ipv4(&self) -> bool { + self == &PunchModel::All + || self == &PunchModel::IPv4 + || self == &PunchModel::IPv4Tcp + || self == &PunchModel::IPv4Udp + } +} + impl FromStr for PunchModel { type Err = String; @@ -30,8 +54,15 @@ impl FromStr for PunchModel { match s.to_lowercase().trim() { "ipv4" => Ok(PunchModel::IPv4), "ipv6" => Ok(PunchModel::IPv6), + "ipv4-tcp" => Ok(PunchModel::IPv4Tcp), + "ipv4-udp" => Ok(PunchModel::IPv4Udp), + "ipv6-tcp" => Ok(PunchModel::IPv6Tcp), + "ipv6-udp" => Ok(PunchModel::IPv6Udp), "all" => Ok(PunchModel::All), - _ => Err(format!("not match '{}', enum: ipv4/ipv6/all", s)), + _ => Err(format!( + "not match '{}', enum: ipv4/ipv4-tcp/ipv4-udp/ipv6/ipv6-tcp/ipv6-udp/all", + s + )), } } } @@ -187,9 +218,7 @@ pub struct Punch { port_vec: Vec, port_index: HashMap, punch_model: PunchModel, - is_tcp: bool, connect_util: ConnectUtil, - external_route: ExternalRoute, nat_test: NatTest, current_device: Arc>, } @@ -198,9 +227,7 @@ impl Punch { pub fn new( context: ChannelContext, punch_model: PunchModel, - is_tcp: bool, connect_util: ConnectUtil, - external_route: ExternalRoute, nat_test: NatTest, current_device: Arc>, ) -> Self { @@ -213,9 +240,7 @@ impl Punch { port_vec, port_index: HashMap::new(), punch_model, - is_tcp, connect_util, - external_route, nat_test, current_device, } @@ -242,48 +267,46 @@ impl Punch { return Ok(()); } let device_info = self.current_device.load(); - nat_info.public_ips.retain(|ip| { - self.external_route.route(ip).is_none() && device_info.not_in_network(*ip) - }); - nat_info.local_ipv4.filter(|ip| { - self.external_route.route(ip).is_none() && device_info.not_in_network(*ip) - }); - nat_info.ipv6.filter(|ip| { - if let Some(ip) = ip.to_ipv4() { - self.external_route.route(&ip).is_none() - } else { - true - } - }); - if punch_tcp && self.is_tcp && nat_info.tcp_port != 0 { + + nat_info + .public_ips + .retain(|ip| is_ipv4_global(ip) && device_info.not_in_network(*ip)); + nat_info.public_ports.retain(|port| *port != 0); + nat_info.udp_ports.retain(|port| *port != 0); + + nat_info.local_ipv4 = nat_info + .local_ipv4 + .filter(|ip| device_info.not_in_network(*ip)); + + if punch_tcp && self.punch_model.use_tcp() && nat_info.tcp_port != 0 { //向tcp发起连接 - if let Some(ipv6_addr) = nat_info.local_tcp_ipv6addr() { - self.connect_tcp(buf, ipv6_addr) + if self.punch_model.use_ipv6() { + if let Some(ipv6_addr) = nat_info.local_tcp_ipv6addr() { + self.connect_tcp(buf, ipv6_addr) + } } - //向tcp发起连接 - if let Some(ipv4_addr) = nat_info.local_tcp_ipv4addr() { - self.connect_tcp(buf, ipv4_addr) - } - for ip in &nat_info.public_ips { - let addr = SocketAddr::V4(SocketAddrV4::new(*ip, nat_info.tcp_port)); - self.connect_tcp(buf, addr) - } - } - let channel_num = self.context.channel_num(); - for index in 0..channel_num { - if let Some(ipv4_addr) = nat_info.local_udp_ipv4addr(index) { - if !self.nat_test.is_local_address(false, ipv4_addr) { - let _ = self.context.send_main_udp(index, buf, ipv4_addr); + if self.punch_model.use_ipv4() { + if let Some(ipv4_addr) = nat_info.local_tcp_ipv4addr() { + self.connect_tcp(buf, ipv4_addr) + } + for ip in &nat_info.public_ips { + let addr = SocketAddr::V4(SocketAddrV4::new(*ip, nat_info.tcp_port)); + self.connect_tcp(buf, addr) } } } + if !self.punch_model.use_udp() { + return Ok(()); + } + let channel_num = self.context.channel_num(); + let main_len = self.context.main_len(); - if self.punch_model != PunchModel::IPv4 { - for index in 0..channel_num { + if self.punch_model.use_ipv6() { + for index in channel_num..main_len { if let Some(ipv6_addr) = nat_info.local_udp_ipv6addr(index) { if !self.nat_test.is_local_address(false, ipv6_addr) { let rs = self.context.send_main_udp(index, buf, ipv6_addr); - log::info!("发送到ipv6地址:{:?},rs={:?}", ipv6_addr, rs); + log::info!("发送到ipv6地址:{:?},rs={:?} {}", ipv6_addr, rs, id); if rs.is_ok() && self.punch_model == PunchModel::IPv6 { return Ok(()); } @@ -291,6 +314,33 @@ impl Punch { } } } + if !self.punch_model.use_ipv4() { + return Ok(()); + } + for index in 0..channel_num { + if let Some(ipv4_addr) = nat_info.local_udp_ipv4addr(index) { + if !self.nat_test.is_local_address(false, ipv4_addr) { + let _ = self.context.send_main_udp(index, buf, ipv4_addr); + } + } + } + // 可能是开放了端口的,需要打洞 + for index in 0..channel_num { + for port in &nat_info.udp_ports { + if *port == 0 { + continue; + } + for ip in &nat_info.public_ips { + if ip.is_unspecified() { + continue; + } + let addr = SocketAddrV4::new(*ip, *port); + let _ = self.context.send_main_udp(index, buf, addr.into()); + thread::sleep(Duration::from_millis(3)); + } + } + } + match nat_info.nat_type { NatType::Symmetric => { // 假设对方绑定n个端口,通过NAT对外映射出n个 公网ip:公网端口,自己随机尝试k次的情况下 diff --git a/vnt/src/channel/socket/mod.rs b/vnt/src/channel/socket/mod.rs new file mode 100644 index 0000000..14052c2 --- /dev/null +++ b/vnt/src/channel/socket/mod.rs @@ -0,0 +1,111 @@ +use anyhow::{anyhow, Context}; +use network_interface::{NetworkInterface, NetworkInterfaceConfig}; +use socket2::Protocol; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +#[cfg(unix)] +pub use unix::*; +#[cfg(windows)] +pub use windows::*; + +#[cfg(unix)] +mod unix; +#[cfg(windows)] +mod windows; + +pub trait VntSocketTrait { + fn set_ip_unicast_if(&self, _interface: &LocalInterface) -> anyhow::Result<()> { + Ok(()) + } +} + +#[derive(Clone, Debug, Default)] +pub struct LocalInterface { + index: u32, + #[cfg(unix)] + name: Option, +} + +pub async fn connect_tcp( + addr: SocketAddr, + default_interface: &LocalInterface, +) -> anyhow::Result { + let socket = create_tcp(addr.is_ipv4(), default_interface)?; + Ok(socket.connect(addr).await?) +} + +pub fn create_tcp( + v4: bool, + default_interface: &LocalInterface, +) -> anyhow::Result { + let socket = if v4 { + socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::STREAM, + Some(Protocol::TCP), + )? + } else { + socket2::Socket::new( + socket2::Domain::IPV6, + socket2::Type::STREAM, + Some(Protocol::TCP), + )? + }; + if v4 { + socket.set_ip_unicast_if(default_interface)?; + } + socket.set_nonblocking(true)?; + socket.set_nodelay(true)?; + Ok(tokio::net::TcpSocket::from_std_stream(socket.into())) +} +pub fn bind_udp_ops( + addr: SocketAddr, + only_v6: bool, + default_interface: &LocalInterface, +) -> anyhow::Result { + let socket = if addr.is_ipv4() { + let socket = socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::DGRAM, + Some(Protocol::UDP), + )?; + socket.set_ip_unicast_if(default_interface)?; + socket + } else { + let socket = socket2::Socket::new( + socket2::Domain::IPV6, + socket2::Type::DGRAM, + Some(Protocol::UDP), + )?; + socket + .set_only_v6(only_v6) + .with_context(|| format!("set_only_v6 failed: {}", &addr))?; + socket + }; + socket.set_nonblocking(true)?; + socket.bind(&addr.into())?; + Ok(socket) +} +pub fn bind_udp( + addr: SocketAddr, + default_interface: &LocalInterface, +) -> anyhow::Result { + bind_udp_ops(addr, true, default_interface).with_context(|| format!("{}", addr)) +} + +pub fn get_interface(dest_ip: Ipv4Addr) -> anyhow::Result { + let network_interfaces = NetworkInterface::show()?; + for iface in network_interfaces { + for addr in iface.addr { + if let IpAddr::V4(ip) = addr.ip() { + if ip == dest_ip { + return Ok(LocalInterface { + index: iface.index, + #[cfg(unix)] + name: Some(iface.name), + }); + } + } + } + } + Err(anyhow!("No network card with IP {} found", dest_ip)) +} diff --git a/vnt/src/channel/socket/unix.rs b/vnt/src/channel/socket/unix.rs new file mode 100644 index 0000000..d638cf6 --- /dev/null +++ b/vnt/src/channel/socket/unix.rs @@ -0,0 +1,35 @@ +use crate::channel::socket::{get_interface, LocalInterface, VntSocketTrait}; +use anyhow::Context; +use std::net::Ipv4Addr; + +#[cfg(any(target_os = "linux", target_os = "android"))] +impl VntSocketTrait for socket2::Socket { + fn set_ip_unicast_if(&self, interface: &LocalInterface) -> anyhow::Result<()> { + if let Some(name) = &interface.name { + self.bind_device(Some(name.as_bytes())) + .context("bind_device")?; + } + Ok(()) + } +} +#[cfg(target_os = "macos")] +impl VntSocketTrait for socket2::Socket { + fn set_ip_unicast_if(&self, interface: &LocalInterface) -> anyhow::Result<()> { + if interface.index != 0 { + self.bind_device_by_index_v4(std::num::NonZeroU32::new(interface.index)) + .with_context(|| format!("bind_device_by_index_v4 {:?}", interface))?; + } + Ok(()) + } +} + +pub fn get_best_interface(dest_ip: Ipv4Addr) -> anyhow::Result { + match get_interface(dest_ip) { + Ok(iface) => return Ok(iface), + Err(e) => { + log::warn!("not find interface e={:?},ip={}", e, dest_ip); + } + } + // 应该再查路由表找到默认路由的 + Ok(LocalInterface::default()) +} diff --git a/vnt/src/channel/socket/windows.rs b/vnt/src/channel/socket/windows.rs new file mode 100644 index 0000000..a518178 --- /dev/null +++ b/vnt/src/channel/socket/windows.rs @@ -0,0 +1,58 @@ +use std::mem; +use std::net::Ipv4Addr; +use std::os::windows::io::AsRawSocket; + +use windows_sys::core::PCSTR; +use windows_sys::Win32::NetworkManagement::IpHelper::GetBestInterfaceEx; +use windows_sys::Win32::Networking::WinSock::{ + htonl, setsockopt, AF_INET, IPPROTO_IP, IP_UNICAST_IF, SOCKADDR, SOCKADDR_IN, SOCKET_ERROR, +}; + +use crate::channel::socket::{LocalInterface, VntSocketTrait}; + +impl VntSocketTrait for socket2::Socket { + fn set_ip_unicast_if(&self, interface: &LocalInterface) -> anyhow::Result<()> { + let index = interface.index; + if index == 0 { + return Ok(()); + } + let raw_socket = self.as_raw_socket(); + let result = unsafe { + let best_interface = htonl(index); + setsockopt( + raw_socket as usize, + IPPROTO_IP, + IP_UNICAST_IF, + &best_interface as *const _ as PCSTR, + mem::size_of_val(&best_interface) as i32, + ) + }; + if result == SOCKET_ERROR { + Err(anyhow::anyhow!( + "Failed to set IP_UNICAST_IF: {:?} {}", + std::io::Error::last_os_error(), + index + ))?; + } + Ok(()) + } +} + +pub fn get_best_interface(dest_ip: Ipv4Addr) -> anyhow::Result { + // 获取最佳接口 + let index = unsafe { + let mut dest: SOCKADDR_IN = mem::zeroed(); + dest.sin_family = AF_INET as u16; + dest.sin_addr.S_un.S_addr = u32::from_ne_bytes(dest_ip.octets()); + + let mut index: u32 = 0; + if GetBestInterfaceEx(&dest as *const _ as *mut SOCKADDR, &mut index) != 0 { + Err(anyhow::anyhow!( + "Failed to GetBestInterfaceEx: {:?}", + std::io::Error::last_os_error() + ))?; + } + index + }; + Ok(LocalInterface { index }) +} diff --git a/vnt/src/channel/tcp_channel.rs b/vnt/src/channel/tcp_channel.rs index 97debc9..71aadd6 100644 --- a/vnt/src/channel/tcp_channel.rs +++ b/vnt/src/channel/tcp_channel.rs @@ -87,8 +87,11 @@ async fn connect_tcp0( where H: RecvChannelHandler, { - let mut stream = - tokio::time::timeout(Duration::from_secs(3), TcpStream::connect(addr)).await??; + let mut stream = tokio::time::timeout( + Duration::from_secs(3), + crate::channel::socket::connect_tcp(addr, context.default_interface()), + ) + .await??; tcp_write(&mut stream, &data).await?; tcp_stream_handle(stream, addr, recv_handler, context).await; diff --git a/vnt/src/channel/udp_channel.rs b/vnt/src/channel/udp_channel.rs index a895e6f..213de42 100644 --- a/vnt/src/channel/udp_channel.rs +++ b/vnt/src/channel/udp_channel.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::mpsc::{sync_channel, Receiver}; use std::{io, thread}; @@ -71,7 +70,8 @@ where let mut events = Events::with_capacity(1024); let mut buf = [0; BUFFER_SIZE]; let mut extend = [0; BUFFER_SIZE]; - let mut read_map: HashMap = HashMap::with_capacity(32); + let mut list: Vec = Vec::with_capacity(100); + let main_len = context.main_len(); loop { if let Err(e) = poll.poll(&mut events, None) { crate::ignore_io_interrupted(e)?; @@ -88,39 +88,43 @@ where match option { None => { log::info!("切换成锥形模式"); - for (_, mut udp_socket) in read_map.drain() { + for mut udp_socket in list.drain(..) { if let Err(e) = udp_socket.deregister(poll.registry()) { log::error!("{:?}", e); } } } Some(socket_list) => { + for mut udp_socket in list.drain(..) { + if let Err(e) = udp_socket.deregister(poll.registry()) { + log::error!("deregister {:?}", e); + } + } log::info!("切换成对称模式 监听端口数:{}", socket_list.len()); for (index, mut udp_socket) in socket_list.into_iter().enumerate() { - let token = Token(index + context.channel_num()); poll.registry().register( &mut udp_socket, - token, + Token(index), Interest::READABLE, )?; - read_map.insert(token, udp_socket); + list.push(udp_socket); } } } } } } - token => { - if let Some(udp_socket) = read_map.get(&token) { + Token(index) => { + if let Some(udp_socket) = list.get(index) { loop { match udp_socket.recv_from(&mut buf) { Ok((len, addr)) => { recv_handler.handle( &mut buf[..len], &mut extend, - RouteKey::new(ConnectProtocol::UDP, token.0, addr), + RouteKey::new(ConnectProtocol::UDP, index + main_len, addr), &context, ); } @@ -266,6 +270,7 @@ where for x in events.iter() { let index = match x.token() { NOTIFY => return Ok(()), + // 0的位置留给NOTIFY了,这里要再减回去,因为路由是通过index来找到对应udp的 Token(index) => index - 1, }; let udp = if let Some(udp) = udps.get(index) { diff --git a/vnt/src/core/conn.rs b/vnt/src/core/conn.rs index cacd907..248f8a8 100644 --- a/vnt/src/core/conn.rs +++ b/vnt/src/core/conn.rs @@ -12,6 +12,7 @@ use crate::channel::context::ChannelContext; use crate::channel::idle::Idle; use crate::channel::punch::{NatInfo, Punch}; use crate::channel::sender::IpPacketSender; +use crate::channel::socket::LocalInterface; use crate::channel::{init_channel, init_context, Route, RouteKey}; use crate::cipher::Cipher; #[cfg(feature = "server_encrypt")] @@ -29,7 +30,7 @@ use crate::tun_tap_device::tun_create_helper::{DeviceAdapter, TunDeviceHelper}; use crate::tun_tap_device::vnt_device::DeviceWrite; use crate::util::limit::TrafficMeterMultiAddress; use crate::util::{Scheduler, StopManager}; -use crate::{nat, VntCallback}; +use crate::{channel, nat, VntCallback}; #[derive(Clone)] pub struct Vnt { @@ -105,6 +106,7 @@ impl VntInner { } else { (None, None) }; + //服务端非对称加密 #[cfg(feature = "server_encrypt")] let rsa_cipher: Arc>> = Arc::new(Mutex::new(None)); @@ -131,6 +133,23 @@ impl VntInner { //设备列表 let device_map: Arc)>> = Arc::new(Mutex::new((0, HashMap::with_capacity(16)))); + let local_ipv4 = if let Some(local_ipv4) = config.local_ipv4 { + Some(local_ipv4) + } else { + nat::local_ipv4() + }; + + let default_interface = if config.in_ips.is_empty() { + //没有改变路由,不需要绑定网卡 + LocalInterface::default() + } else { + //vnt的流量都走这个接口 + let default_interface = + channel::socket::get_best_interface(local_ipv4.unwrap_or(Ipv4Addr::UNSPECIFIED))?; + log::info!("default_interface = {:?}", default_interface); + default_interface + }; + //基础信息 let config_info = BaseConfigInfo::new( config.name.clone(), @@ -149,6 +168,7 @@ impl VntInner { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] config.device_name.clone(), config.allow_wire_guard, + default_interface.clone(), ); // 服务停止管理器 let stop_manager = { @@ -179,10 +199,10 @@ impl VntInner { config.protocol, config.packet_loss_rate, config.packet_delay, + default_interface, up_traffic_meter.clone(), down_traffic_meter.clone(), )?; - let local_ipv4 = nat::local_ipv4(); let local_ipv6 = nat::local_ipv6(); let udp_ports = context.main_local_udp_port()?; let tcp_port = tcp_listener.local_addr()?.port(); @@ -194,6 +214,7 @@ impl VntInner { local_ipv6, udp_ports, tcp_port, + config.local_ipv4.is_none(), ); // 定时器 let scheduler = Scheduler::new(stop_manager.clone())?; @@ -266,9 +287,7 @@ impl VntInner { let punch = Punch::new( context.clone(), config.punch_model, - config.protocol.is_base_tcp(), connect_util.clone(), - external_route.clone(), nat_test.clone(), current_device.clone(), ); diff --git a/vnt/src/core/mod.rs b/vnt/src/core/mod.rs index 56701ad..ac2d54b 100644 --- a/vnt/src/core/mod.rs +++ b/vnt/src/core/mod.rs @@ -52,6 +52,7 @@ pub struct Config { pub compressor: Compressor, pub enable_traffic: bool, pub allow_wire_guard: bool, + pub local_ipv4: Option, } impl Config { @@ -91,6 +92,7 @@ impl Config { enable_traffic: bool, // 允许传递wg流量 allow_wire_guard: bool, + local_ipv4: Option, ) -> anyhow::Result { for x in stun_server.iter_mut() { if !x.contains(":") { @@ -147,6 +149,9 @@ impl Config { *dest = *mask & *dest; } in_ips.sort_by(|(dest1, _, _), (dest2, _, _)| dest2.cmp(dest1)); + if let Some(local_ip) = local_ipv4 { + let _ = crate::channel::socket::get_interface(local_ip)?; + } Ok(Self { #[cfg(feature = "integrated_tun")] #[cfg(target_os = "windows")] @@ -184,6 +189,7 @@ impl Config { compressor, enable_traffic, allow_wire_guard, + local_ipv4, }) } } diff --git a/vnt/src/handle/maintain/punch.rs b/vnt/src/handle/maintain/punch.rs index 8bbbe84..98ded7a 100644 --- a/vnt/src/handle/maintain/punch.rs +++ b/vnt/src/handle/maintain/punch.rs @@ -175,7 +175,7 @@ fn punch_request( client_cipher: Cipher, count: usize, punch_record: Arc>>, - mut last_punch_record: HashMap, + mut last_punch_record: HashMap, ) { let curr = current_device.load(); let secs = if curr.status.online() { @@ -213,6 +213,11 @@ fn punch_request( log::info!("定时任务停止"); } } +#[derive(Copy, Clone, Default)] +struct PunchRecordItem { + pub punch_record: usize, + pub last_p2p_num: usize, +} /// 随机对需要打洞的客户端发起打洞请求 fn punch0( @@ -222,7 +227,7 @@ fn punch0( current_device: CurrentDeviceInfo, client_cipher: &Cipher, punch_record: &Mutex>, - last_punch_record: &mut HashMap, + last_punch_record: &mut HashMap, total_count: usize, ) -> anyhow::Result<()> { let nat_info = nat_test.nat_info(); @@ -241,12 +246,17 @@ fn punch0( .lock() .1 .values() - .filter(|info| !info.wireguard && info.status.is_online() && info.virtual_ip > current_ip) + .filter(|info| !info.wireguard && info.virtual_ip > current_ip) .cloned() .collect(); list.shuffle(&mut rand::thread_rng()); for info in list { - let punch_count = punch_record + if info.status.is_offline() { + // 客户端掉线了要重置打洞记录 + punch_record.lock().remove(&info.virtual_ip); + continue; + } + let mut punch_count = punch_record .lock() .get(&info.virtual_ip) .cloned() @@ -256,12 +266,11 @@ fn punch0( let p2p_num = context.route_table.p2p_num(&info.virtual_ip); let mut max_punch_interval = 50; if p2p_num > 0 { - if punch_count == 0 { - continue; - } if p2p_num >= context.channel_num() { //通道数满足要求,不再打洞 - punch_record.lock().remove(&info.virtual_ip); + if punch_count != 0 { + punch_record.lock().remove(&info.virtual_ip); + } continue; } //有p2p通道,但是通道数量不够,则继续打洞 @@ -269,13 +278,18 @@ fn punch0( max_punch_interval = 300; } // 能发起打洞的前提是自己空闲,这里会间隔5秒以上发起一次打洞,所以假定上一轮打洞已结束 - let last_punch = last_punch_record - .get(&info.virtual_ip) - .cloned() - .unwrap_or(0); + let last_punch = last_punch_record.entry(info.virtual_ip).or_default(); + if last_punch.last_p2p_num > p2p_num { + // 打的洞掉线了,需要重置重新打 + punch_record.lock().remove(&info.virtual_ip); + punch_count = 0; + } + // 梯度增加打洞时间间隔 - if total_count > last_punch + punch_count.min(max_punch_interval) { - last_punch_record.insert(info.virtual_ip, total_count); + if total_count > last_punch.punch_record + punch_count.min(max_punch_interval) { + // 记录打洞周期,抑制下一次打洞,从而递减打洞频率 + last_punch.punch_record = total_count; + last_punch.last_p2p_num = p2p_num; let packet = punch_packet( client_cipher, current_device.virtual_ip(), diff --git a/vnt/src/handle/maintain/re_nat_type.rs b/vnt/src/handle/maintain/re_nat_type.rs index 0c784fc..ca6d29f 100644 --- a/vnt/src/handle/maintain/re_nat_type.rs +++ b/vnt/src/handle/maintain/re_nat_type.rs @@ -29,9 +29,13 @@ fn retrieve_nat_type0( .name("natTest".into()) .spawn(move || { if nat_test.can_update() { - let local_ipv4 = nat::local_ipv4(); + let local_ipv4 = if nat_test.update_local_ipv4 { + nat::local_ipv4() + } else { + None + }; let local_ipv6 = nat::local_ipv6(); - match nat_test.re_test(local_ipv4, local_ipv6) { + match nat_test.re_test(local_ipv4, local_ipv6, context.default_interface()) { Ok(nat_info) => { log::info!("当前nat信息:{:?}", nat_info); if let Err(e) = context.switch(nat_info.nat_type, &udp_socket_sender) { diff --git a/vnt/src/handle/mod.rs b/vnt/src/handle/mod.rs index 754b7b3..ea51732 100644 --- a/vnt/src/handle/mod.rs +++ b/vnt/src/handle/mod.rs @@ -1,3 +1,4 @@ +use crate::channel::socket::LocalInterface; use crossbeam_utils::atomic::AtomicCell; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -70,6 +71,7 @@ pub struct BaseConfigInfo { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] pub device_name: Option, pub allow_wire_guard: bool, + pub default_interface: LocalInterface, } impl BaseConfigInfo { @@ -90,6 +92,7 @@ impl BaseConfigInfo { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] device_name: Option, allow_wire_guard: bool, + default_interface: LocalInterface, ) -> Self { Self { name, @@ -108,6 +111,7 @@ impl BaseConfigInfo { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] device_name, allow_wire_guard, + default_interface, } } } diff --git a/vnt/src/handle/recv_data/server.rs b/vnt/src/handle/recv_data/server.rs index 2abce5c..a9a4380 100644 --- a/vnt/src/handle/recv_data/server.rs +++ b/vnt/src/handle/recv_data/server.rs @@ -105,12 +105,11 @@ impl PacketHandler for ServerPacketHandl ) -> anyhow::Result<()> { if !current_device.is_server_addr(route_key.addr) { //拦截不是服务端的流量 - log::info!( + log::warn!( "route_key={:?},不是来源于服务端地址{}", route_key, current_device.connect_server ); - return Ok(()); } context .route_table @@ -386,9 +385,10 @@ impl ServerPacketHandler { } else { match tun::Device::new(device_fd as _) { Ok(device) => { - if let Err(e) = - self.tun_device_helper.start(Arc::new(device)) - { + if let Err(e) = self.tun_device_helper.start( + Arc::new(device), + self.config_info.allow_wire_guard, + ) { self.callback.error(ErrorInfo::new_msg( ErrorType::Unknown, format!("{:?}", e), diff --git a/vnt/src/ip_proxy/icmp_proxy.rs b/vnt/src/ip_proxy/icmp_proxy.rs index ffb7d2e..60353df 100644 --- a/vnt/src/ip_proxy/icmp_proxy.rs +++ b/vnt/src/ip_proxy/icmp_proxy.rs @@ -13,6 +13,7 @@ use packet::icmp::icmp::HeaderOther; use packet::ip::ipv4::packet::IpV4Packet; use crate::channel::context::ChannelContext; +use crate::channel::socket::{LocalInterface, VntSocketTrait}; use crate::cipher::Cipher; use crate::handle::CurrentDeviceInfo; use crate::ip_proxy::ProxyHandler; @@ -30,6 +31,7 @@ impl IcmpProxy { context: ChannelContext, current_device: Arc>, client_cipher: Cipher, + default_interface: &LocalInterface, ) -> anyhow::Result { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] let icmp_socket = socket2::Socket::new( @@ -50,6 +52,7 @@ impl IcmpProxy { .bind(&socket2::SockAddr::from(addr)) .context("bind Socket ICMPV4 failed")?; icmp_socket.set_nonblocking(true)?; + icmp_socket.set_ip_unicast_if(default_interface)?; let std_socket: std::net::UdpSocket = icmp_socket.into(); let tokio_icmp_socket = UdpSocket::from_std(std_socket.try_clone()?)?; diff --git a/vnt/src/ip_proxy/mod.rs b/vnt/src/ip_proxy/mod.rs index 0361889..6f16916 100644 --- a/vnt/src/ip_proxy/mod.rs +++ b/vnt/src/ip_proxy/mod.rs @@ -68,14 +68,16 @@ pub fn init_proxy( } async fn init_proxy0( - _context: ChannelContext, + context: ChannelContext, _current_device: Arc>, _client_cipher: Cipher, ) -> anyhow::Result { + let default_interface = context.default_interface().clone(); #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - let icmp_proxy = IcmpProxy::new(_context, _current_device, _client_cipher).await?; - let tcp_proxy = TcpProxy::new().await?; - let udp_proxy = UdpProxy::new().await?; + let icmp_proxy = + IcmpProxy::new(context, _current_device, _client_cipher, &default_interface).await?; + let tcp_proxy = TcpProxy::new(default_interface.clone()).await?; + let udp_proxy = UdpProxy::new(default_interface.clone()).await?; Ok(IpProxyMap { #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] diff --git a/vnt/src/ip_proxy/tcp_proxy.rs b/vnt/src/ip_proxy/tcp_proxy.rs index 0e0c123..333f8df 100644 --- a/vnt/src/ip_proxy/tcp_proxy.rs +++ b/vnt/src/ip_proxy/tcp_proxy.rs @@ -5,13 +5,13 @@ use std::time::Duration; use std::{collections::HashMap, io, net::SocketAddr}; use parking_lot::Mutex; -use tokio::net::{TcpListener, TcpSocket, TcpStream}; +use tokio::net::{TcpListener, TcpStream}; +use crate::channel::socket::{create_tcp, LocalInterface}; +use crate::ip_proxy::ProxyHandler; use packet::ip::ipv4::packet::IpV4Packet; use packet::tcp::tcp::TcpPacket; -use crate::ip_proxy::ProxyHandler; - #[derive(Clone)] pub struct TcpProxy { port: u16, @@ -19,7 +19,7 @@ pub struct TcpProxy { } impl TcpProxy { - pub async fn new() -> anyhow::Result { + pub async fn new(default_interface: LocalInterface) -> anyhow::Result { let nat_map: Arc>> = Arc::new(Mutex::new(HashMap::with_capacity(16))); let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", 0)) @@ -28,7 +28,7 @@ impl TcpProxy { let port = tcp_listener.local_addr()?.port(); { let nat_map = nat_map.clone(); - tokio::spawn(tcp_proxy(tcp_listener, nat_map)); + tokio::spawn(tcp_proxy(tcp_listener, nat_map, default_interface)); } Ok(Self { port, nat_map }) } @@ -79,26 +79,33 @@ impl ProxyHandler for TcpProxy { async fn tcp_proxy( tcp_listener: TcpListener, nat_map: Arc>>, + default_interface: LocalInterface, ) { loop { match tcp_listener.accept().await { Ok((tcp_stream, sender_addr)) => match sender_addr { SocketAddr::V4(sender_addr) => { if let Some(dest_addr) = nat_map.lock().get(&sender_addr).cloned() { + let default_interface = default_interface.clone(); tokio::spawn(async move { - let peer_tcp_stream = - match tcp_connect(sender_addr.port(), dest_addr.into()).await { - Ok(peer_tcp_stream) => peer_tcp_stream, - Err(e) => { - log::warn!( - "tcp代理异常:{:?},来源:{},目标:{}", - e, - sender_addr, - dest_addr - ); - return; - } - }; + let peer_tcp_stream = match tcp_connect( + sender_addr.port(), + dest_addr.into(), + &default_interface, + ) + .await + { + Ok(peer_tcp_stream) => peer_tcp_stream, + Err(e) => { + log::warn!( + "tcp代理异常:{:?},来源:{},目标:{}", + e, + sender_addr, + dest_addr + ); + return; + } + }; proxy(sender_addr, dest_addr, tcp_stream, peer_tcp_stream).await }); } else { @@ -114,15 +121,19 @@ async fn tcp_proxy( } } /// 优先使用来源端口建立tcp连接 -async fn tcp_connect(src_port: u16, addr: SocketAddr) -> anyhow::Result { - let socket = TcpSocket::new_v4()?; +async fn tcp_connect( + src_port: u16, + addr: SocketAddr, + default_interface: &LocalInterface, +) -> anyhow::Result { + let socket = create_tcp(true, default_interface)?; if socket .bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, src_port).into()) .is_err() { socket.bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into())?; } - let _ = socket.set_nodelay(false); + let _ = socket.set_nodelay(true); let tcp_stream = tokio::time::timeout(Duration::from_secs(5), socket.connect(addr)) .await .with_context(|| format!("TCP connection timeout {}", addr))? diff --git a/vnt/src/ip_proxy/udp_proxy.rs b/vnt/src/ip_proxy/udp_proxy.rs index 0837749..591330a 100644 --- a/vnt/src/ip_proxy/udp_proxy.rs +++ b/vnt/src/ip_proxy/udp_proxy.rs @@ -8,11 +8,11 @@ use std::{collections::HashMap, io, net::SocketAddr}; use parking_lot::Mutex; use tokio::net::UdpSocket; +use crate::channel::socket::{bind_udp, LocalInterface}; +use crate::ip_proxy::ProxyHandler; use packet::ip::ipv4::packet::IpV4Packet; use packet::udp::udp::UdpPacket; -use crate::ip_proxy::ProxyHandler; - #[derive(Clone)] pub struct UdpProxy { port: u16, @@ -20,7 +20,7 @@ pub struct UdpProxy { } impl UdpProxy { - pub async fn new() -> anyhow::Result { + pub async fn new(default_interface: LocalInterface) -> anyhow::Result { let nat_map: Arc>> = Arc::new(Mutex::new(HashMap::with_capacity(16))); let udp = UdpSocket::bind(format!("0.0.0.0:{}", 0)) @@ -29,8 +29,8 @@ impl UdpProxy { let port = udp.local_addr()?.port(); { let nat_map = nat_map.clone(); - tokio::spawn(async { - if let Err(e) = udp_proxy(udp, nat_map).await { + tokio::spawn(async move { + if let Err(e) = udp_proxy(udp, nat_map, default_interface).await { log::warn!("udp_proxy:{:?}", e); } }); @@ -84,7 +84,8 @@ impl ProxyHandler for UdpProxy { async fn udp_proxy( udp: UdpSocket, nat_map: Arc>>, -) -> io::Result<()> { + default_interface: LocalInterface, +) -> anyhow::Result<()> { let mut buf = [0u8; 65536]; let inner_map: Arc, Arc>)>>> = @@ -94,9 +95,15 @@ async fn udp_proxy( match udp_socket.recv_from(&mut buf).await { Ok((len, sender_addr)) => match sender_addr { SocketAddr::V4(sender_addr) => { - if let Err(e) = - udp_proxy0(&buf[..len], sender_addr, &inner_map, &nat_map, &udp_socket) - .await + if let Err(e) = udp_proxy0( + &buf[..len], + sender_addr, + &inner_map, + &nat_map, + &udp_socket, + &default_interface, + ) + .await { log::warn!("udp proxy {} {:?}", sender_addr, e); } @@ -116,7 +123,8 @@ async fn udp_proxy0( inner_map: &Arc, Arc>)>>>, map: &Arc>>, udp_socket: &Arc, -) -> io::Result<()> { + default_interface: &LocalInterface, +) -> anyhow::Result<()> { let option = inner_map.lock().get(&sender_addr).cloned(); if let Some((udp, time)) = option { time.store(Instant::now()); @@ -125,11 +133,14 @@ async fn udp_proxy0( let option = map.lock().get(&sender_addr).cloned(); if let Some(dest_addr) = option { //先使用相同的端口,冲突了再随机端口 - let peer_udp_socket = - match UdpSocket::bind(format!("0.0.0.0:{}", sender_addr.port())).await { - Ok(udp) => udp, - Err(_) => UdpSocket::bind("0.0.0.0:0").await?, - }; + let peer_udp_socket = match bind_udp( + format!("0.0.0.0:{}", sender_addr.port()).parse().unwrap(), + default_interface, + ) { + Ok(udp) => udp, + Err(_) => bind_udp("0.0.0.0:0".parse().unwrap(), default_interface)?, + }; + let peer_udp_socket = UdpSocket::from_std(peer_udp_socket.into())?; peer_udp_socket.connect(dest_addr).await?; peer_udp_socket.send(buf).await?; let peer_udp_socket = Arc::new(peer_udp_socket); diff --git a/vnt/src/nat/mod.rs b/vnt/src/nat/mod.rs index 4f34810..1ef7b2b 100644 --- a/vnt/src/nat/mod.rs +++ b/vnt/src/nat/mod.rs @@ -11,6 +11,7 @@ use rand::prelude::SliceRandom; use rand::Rng; use crate::channel::punch::{NatInfo, NatType}; +use crate::channel::socket::LocalInterface; use crate::proto::message::PunchNatType; #[cfg(feature = "upnp")] use crate::util::UPnP; @@ -116,6 +117,7 @@ pub struct NatTest { tcp_port: u16, #[cfg(feature = "upnp")] upnp: UPnP, + pub(crate) update_local_ipv4: bool, } impl From for PunchNatType { @@ -144,6 +146,7 @@ impl NatTest { ipv6: Option, udp_ports: Vec, tcp_port: u16, + update_local_ipv4: bool, ) -> NatTest { let ports = vec![0; udp_ports.len()]; let nat_info = NatInfo::new( @@ -178,6 +181,7 @@ impl NatTest { tcp_port, #[cfg(feature = "upnp")] upnp, + update_local_ipv4, } } pub fn can_update(&self) -> bool { @@ -257,6 +261,7 @@ impl NatTest { &self, local_ipv4: Option, ipv6: Option, + default_interface: &LocalInterface, ) -> anyhow::Result { let mut stun_server = self.stun_server.clone(); if stun_server.len() > 5 { @@ -264,7 +269,8 @@ impl NatTest { stun_server.truncate(5); log::info!("stun_server truncate {:?}", stun_server); } - let (nat_type, public_ips, port_range) = stun::stun_test_nat(stun_server)?; + let (nat_type, public_ips, port_range) = + stun::stun_test_nat(stun_server, default_interface)?; if public_ips.is_empty() { Err(anyhow!("public_ips.is_empty"))? } @@ -272,7 +278,9 @@ impl NatTest { guard.nat_type = nat_type; guard.public_ips = public_ips; guard.public_port_range = port_range; - guard.local_ipv4 = local_ipv4; + if local_ipv4.is_some() { + guard.local_ipv4 = local_ipv4; + } guard.ipv6 = ipv6; Ok(guard.clone()) diff --git a/vnt/src/nat/stun.rs b/vnt/src/nat/stun.rs index dcc0e7e..6fcdb9b 100644 --- a/vnt/src/nat/stun.rs +++ b/vnt/src/nat/stun.rs @@ -4,17 +4,21 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::time::Duration; use crate::channel::punch::NatType; +use crate::channel::socket::{bind_udp, LocalInterface}; use rand::RngCore; use std::net::UdpSocket; use stun_format::Attr; -pub fn stun_test_nat(stun_servers: Vec) -> io::Result<(NatType, Vec, u16)> { +pub fn stun_test_nat( + stun_servers: Vec, + default_interface: &LocalInterface, +) -> anyhow::Result<(NatType, Vec, u16)> { let mut nat_type = NatType::Cone; let mut port_range = 0; let mut hash_set = HashSet::new(); for _ in 0..2 { let stun_servers = stun_servers.clone(); - match stun_test_nat0(stun_servers) { + match stun_test_nat0(stun_servers, default_interface) { Ok((nat_type_t, ip_list_t, port_range_t)) => { if nat_type_t == NatType::Symmetric { nat_type = NatType::Symmetric; @@ -34,8 +38,13 @@ pub fn stun_test_nat(stun_servers: Vec) -> io::Result<(NatType, Vec) -> io::Result<(NatType, Vec, u16)> { - let udp = UdpSocket::bind("0.0.0.0:0")?; +pub fn stun_test_nat0( + stun_servers: Vec, + default_interface: &LocalInterface, +) -> anyhow::Result<(NatType, Vec, u16)> { + let udp = bind_udp("0.0.0.0:0".parse().unwrap(), default_interface)?; + udp.set_nonblocking(false)?; + let udp: UdpSocket = udp.into(); udp.set_read_timeout(Some(Duration::from_millis(500)))?; let mut nat_type = NatType::Cone; let mut min_port = u16::MAX; diff --git a/vnt/src/port_mapping/tcp_mapping.rs b/vnt/src/port_mapping/tcp_mapping.rs index 6bdc61f..82e780c 100644 --- a/vnt/src/port_mapping/tcp_mapping.rs +++ b/vnt/src/port_mapping/tcp_mapping.rs @@ -32,6 +32,7 @@ async fn tcp_mapping_( } async fn copy(source_tcp: TcpStream, destination: &String) -> anyhow::Result<()> { + // 或许这里也应该绑定最匹配的网卡,不然全局代理会影响映射 let dest_tcp = TcpStream::connect(destination) .await .with_context(|| format!("TCP connection target failed {:?}", destination))?; diff --git a/vnt/tun/src/linux/route.rs b/vnt/tun/src/linux/route.rs index b50133c..b6c1630 100644 --- a/vnt/tun/src/linux/route.rs +++ b/vnt/tun/src/linux/route.rs @@ -4,7 +4,16 @@ use std::net::Ipv4Addr; use crate::unix::exe_cmd; pub fn add_route(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> io::Result<()> { - let cmd = format!("ip route add {:?}/{:?} dev {}", address, netmask, name); + let cmd = if netmask.is_broadcast() { + format!("route add -host {:?} {}", address, name) + } else { + format!( + "route add -net {}/{} {}", + address, + u32::from(netmask).count_ones(), + name + ) + }; exe_cmd(&cmd)?; Ok(()) }