Merge branch 'refs/heads/1.2.x' into features/2024-07-30-分离协议栈

# Conflicts:
#	vnt/src/channel/punch.rs
This commit is contained in:
lbl8603
2024-07-31 09:57:55 +08:00
5 changed files with 142 additions and 172 deletions
+32 -126
View File
@@ -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)
<img width="506" alt="ssh" src="https://raw.githubusercontent.com/vnt-dev/vnt/main/documents/img/ssh.jpg">
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)
### 自行编译
<details> <summary>点击展开</summary>
前提条件:安装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代理性能更好
<details> <summary>NAT配置可参考如下示例,点击展开</summary>
### 在出口一端做如下配置
注意原有的-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协议 | 是 |
</details>
@@ -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等多种协议
- 支持数据压缩
### 结构
### 更多玩法
<details> <summary>展开</summary>
<pre>
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生成,用于对数据包完整性和真实性的校验
</pre>
</details>
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对你有帮助,欢迎打赏作者
<img width="300" alt="" src="https://github.com/vnt-dev/vnt/assets/49143209/0d3a7311-43fc-4ed7-9507-863b5d69b6b2">
+9 -5
View File
@@ -369,12 +369,12 @@ fn get_description(key: &str, language: &str) -> String {
("-o <out-ip>", ("配置点对网时使用,-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 <password>", ("使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "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>", ("自定义mtu(不加密默认为1450,加密默认为1410", "Customize MTU (1450 by default without encryption, 1410 with encryption)")),
("-u <mtu>", ("自定义mtu(默认为1420)", "Customize MTU (default is 1420)")),
("-f <conf_file>", ("读取配置文件中的配置", "Read configuration from file")),
("--ip <ip>", ("指定虚拟ip,指定的ip不能和其他设备重复,必须有效并且在服务端所属网段下,默认情况由服务端分配", "Specify virtual IP, must be unique and valid within server subnet, by default allocated by server")),
("--model <model>", ("加密模式(默认aes_gcm),可选值{}", "Encryption mode (default aes_gcm), options {}")),
("--model <model>", ("加密模式(默认aes_gcm),可选值", "Encryption mode (default aes_gcm), options ")),
("--finger", ("增加数据指纹校验,可增加安全性,如果服务端开启指纹校验,则客户端也必须开启", "Add data fingerprint verification for increased security, client must enable if server does")),
("--punch <punch>", ("取值ipv4/ipv6/all,ipv4表示仅使用ipv4打洞", "Values ipv4/ipv6/all, ipv4 for IPv4 hole punching only")),
("--punch <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 <port,port>", ("取值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")),
@@ -382,7 +382,7 @@ fn get_description(key: &str, language: &str) -> String {
("--use-channel <p2p>", ("使用通道 relay/p2p/all,默认两者都使用", "Use channel relay/p2p/all, defaults to using both")),
("--nic <tun0>", ("指定虚拟网卡名称", "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 <host:port>", ("DNS服务器地址,可使用多个dns,不指定时使用系统解析", "DNS server address, can specify multiple DNS servers, defaults to system resolution if not specified")),
("--mapping <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>", ("启用压缩,可选值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")),
@@ -391,6 +391,7 @@ fn get_description(key: &str, language: &str) -> String {
("--vnt-mapping <x>", ("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")),
@@ -584,7 +585,10 @@ fn print_usage(program: &str, _opts: Options) {
" --disable-stats {}",
get_description("--disable-stats", &language)
);
println!(" --allow-wg 允许接入WireGuard客户端");
println!(
" --allow-wg {}",
get_description("--allow-wg", &language)
);
println!();
#[cfg(feature = "command")]
{
+4 -4
View File
@@ -128,12 +128,12 @@ pub fn read_config(file_path: &str) -> anyhow::Result<(Config, Vec<String>, 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
}
+97 -36
View File
@@ -13,15 +13,40 @@ use rand::Rng;
use crate::channel::context::ChannelContext;
use crate::channel::sender::ConnectUtil;
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;
@@ -29,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
)),
}
}
}
@@ -186,7 +218,6 @@ pub struct Punch {
port_vec: Vec<u16>,
port_index: HashMap<Ipv4Addr, usize>,
punch_model: PunchModel,
is_tcp: bool,
connect_util: ConnectUtil,
nat_test: NatTest,
current_device: Arc<AtomicCell<CurrentDeviceInfo>>,
@@ -196,7 +227,6 @@ impl Punch {
pub fn new(
context: ChannelContext,
punch_model: PunchModel,
is_tcp: bool,
connect_util: ConnectUtil,
nat_test: NatTest,
current_device: Arc<AtomicCell<CurrentDeviceInfo>>,
@@ -210,7 +240,6 @@ impl Punch {
port_vec,
port_index: HashMap::new(),
punch_model,
is_tcp,
connect_util,
nat_test,
current_device,
@@ -238,42 +267,47 @@ impl Punch {
return Ok(());
}
let device_info = self.current_device.load();
nat_info
.public_ips
.retain(|ip| device_info.not_in_network(*ip));
nat_info
.local_ipv4
.filter(|ip| device_info.not_in_network(*ip));
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();
let main_len = self.context.main_len();
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 {
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(());
}
@@ -281,6 +315,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次的情况下
@@ -322,11 +383,11 @@ impl Punch {
}
let mut index = start
+ self.punch_symmetric(
&self.port_vec[start..end],
buf,
&nat_info.public_ips,
max_k2,
)?;
&self.port_vec[start..end],
buf,
&nat_info.public_ips,
max_k2,
)?;
if index >= self.port_vec.len() {
index = 0
}
-1
View File
@@ -287,7 +287,6 @@ impl VntInner {
let punch = Punch::new(
context.clone(),
config.punch_model,
config.protocol.is_base_tcp(),
connect_util.clone(),
nat_test.clone(),
current_device.clone(),