feat: add TAP device mode and Ethernet interoperability
This commit is contained in:
@@ -64,12 +64,22 @@ pnpm dev:desktop
|
|||||||
2. 提升流量稳定性,支持使用quic代理流量,支持FEC冗余传输
|
2. 提升流量稳定性,支持使用quic代理流量,支持FEC冗余传输
|
||||||
3. 简化操作,去除了大量vnt1.0的重复和无用的配置参数
|
3. 简化操作,去除了大量vnt1.0的重复和无用的配置参数
|
||||||
4. vnt-link、vnt合二为一
|
4. vnt-link、vnt合二为一
|
||||||
5. 支持有tun模式、无tun模式、端口映射
|
5. 支持无网卡、TUN(三层)和 TAP(二层)模式及端口映射;三种模式的 IPv4 流量可互通
|
||||||
6. 全功能的情况下,减少程序体积
|
6. 全功能的情况下,减少程序体积
|
||||||
7. 性能提升,支持linux-offload
|
7. 性能提升,支持linux-offload
|
||||||
8. 更规范的api接入,可以轻松自定义客户端
|
8. 更规范的api接入,可以轻松自定义客户端
|
||||||
9. 支持同时连接多个服务端,可以容灾和负载均衡
|
9. 支持同时连接多个服务端,可以容灾和负载均衡
|
||||||
|
|
||||||
|
## 虚拟网卡模式
|
||||||
|
|
||||||
|
配置文件使用 `device_mode = "no|tun|tap"`,默认值为 `tun`;命令行可用 `--device-mode` 覆盖。旧的 `no_tun` 配置已移除,程序会提示迁移而不会静默按 TUN 启动。
|
||||||
|
|
||||||
|
- `no`:不创建虚拟网卡,只提供流量出口和端口映射。
|
||||||
|
- `tun`:创建三层网卡,网卡收发 IPv4 包。
|
||||||
|
- `tap`:创建二层网卡,完整透传 Ethernet 帧,并与 TUN/NO 节点转换 IPv4、兼容 ARP。
|
||||||
|
|
||||||
|
Linux 和 macOS 使用系统提供的 TUN/TAP 能力。Windows 的 TUN 模式使用随程序提供的 `wintun.dll`;TAP 模式需要管理员权限并预先安装 `tap-windows`(硬件 ID `tap0901`)。Android VpnService 仅支持 TUN。
|
||||||
|
|
||||||
# 说明
|
# 说明
|
||||||
|
|
||||||
vnt2.0整体重构了一遍,和1.0不兼容,同时也可能引入新的bug,欢迎反馈
|
vnt2.0整体重构了一遍,和1.0不兼容,同时也可能引入新的bug,欢迎反馈
|
||||||
|
|||||||
+70
-9
@@ -4,7 +4,7 @@ use ipnet::Ipv4Net;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use vnt_core::context::config::Config;
|
use vnt_core::context::config::{Config, DeviceMode};
|
||||||
use vnt_core::nat::NetInput;
|
use vnt_core::nat::NetInput;
|
||||||
use vnt_core::tls::verifier::CertValidationMode;
|
use vnt_core::tls::verifier::CertValidationMode;
|
||||||
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
|
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
|
||||||
@@ -23,7 +23,9 @@ pub struct FileConfig {
|
|||||||
pub input: Option<Vec<NetInput>>,
|
pub input: Option<Vec<NetInput>>,
|
||||||
pub output: Option<Vec<Ipv4Net>>,
|
pub output: Option<Vec<Ipv4Net>>,
|
||||||
pub no_nat: Option<bool>,
|
pub no_nat: Option<bool>,
|
||||||
pub no_tun: Option<bool>,
|
pub device_mode: Option<DeviceMode>,
|
||||||
|
#[serde(rename = "no_tun", skip_serializing)]
|
||||||
|
pub legacy_no_tun: Option<bool>,
|
||||||
pub mtu: Option<u16>,
|
pub mtu: Option<u16>,
|
||||||
pub ctrl_port: Option<u16>,
|
pub ctrl_port: Option<u16>,
|
||||||
pub port_mapping: Option<Vec<String>>,
|
pub port_mapping: Option<Vec<String>>,
|
||||||
@@ -134,9 +136,9 @@ pub struct Args {
|
|||||||
/// 关闭内置子网NAT
|
/// 关闭内置子网NAT
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub no_nat: bool,
|
pub no_nat: bool,
|
||||||
/// 禁用tun,禁用后只能充当流量出口或者进行端口映射,无需管理员权限
|
/// 虚拟网卡模式:no(无网卡)、tun(三层网卡)、tap(二层网卡)
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub no_tun: bool,
|
pub device_mode: Option<DeviceMode>,
|
||||||
/// 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
/// 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub port_mapping: Vec<PortMapping>,
|
pub port_mapping: Vec<PortMapping>,
|
||||||
@@ -177,6 +179,14 @@ pub fn build_config_from_args_and_file(
|
|||||||
args: Option<Args>,
|
args: Option<Args>,
|
||||||
file: Option<FileConfig>,
|
file: Option<FileConfig>,
|
||||||
) -> anyhow::Result<(Config, CtrlConfig)> {
|
) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||||
|
if file
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|config| config.legacy_no_tun.is_some())
|
||||||
|
{
|
||||||
|
return Err(anyhow!(
|
||||||
|
"configuration key 'no_tun' was removed; use device_mode = \"no|tun|tap\""
|
||||||
|
));
|
||||||
|
}
|
||||||
match (args, file) {
|
match (args, file) {
|
||||||
(Some(args), Some(file)) => build_from_args_and_file(args, file),
|
(Some(args), Some(file)) => build_from_args_and_file(args, file),
|
||||||
(Some(args), None) => build_from_args_only(args),
|
(Some(args), None) => build_from_args_only(args),
|
||||||
@@ -258,7 +268,7 @@ fn build_from_args_and_file(args: Args, file: FileConfig) -> anyhow::Result<(Con
|
|||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
no_nat: args.no_nat || file.no_nat.unwrap_or(false),
|
no_nat: args.no_nat || file.no_nat.unwrap_or(false),
|
||||||
no_tun: args.no_tun || file.no_tun.unwrap_or(false),
|
device_mode: args.device_mode.or(file.device_mode).unwrap_or_default(),
|
||||||
mtu: args.mtu.or(file.mtu),
|
mtu: args.mtu.or(file.mtu),
|
||||||
port_mapping,
|
port_mapping,
|
||||||
allow_port_mapping: args.allow_mapping || file.allow_mapping.unwrap_or(false),
|
allow_port_mapping: args.allow_mapping || file.allow_mapping.unwrap_or(false),
|
||||||
@@ -299,7 +309,7 @@ fn build_from_args_only(args: Args) -> anyhow::Result<(Config, CtrlConfig)> {
|
|||||||
.unwrap_or(CertValidationMode::InsecureSkipVerification),
|
.unwrap_or(CertValidationMode::InsecureSkipVerification),
|
||||||
output: args.output,
|
output: args.output,
|
||||||
no_nat: args.no_nat,
|
no_nat: args.no_nat,
|
||||||
no_tun: args.no_tun,
|
device_mode: args.device_mode.unwrap_or_default(),
|
||||||
mtu: args.mtu,
|
mtu: args.mtu,
|
||||||
port_mapping: args.port_mapping,
|
port_mapping: args.port_mapping,
|
||||||
allow_port_mapping: args.allow_mapping,
|
allow_port_mapping: args.allow_mapping,
|
||||||
@@ -313,6 +323,11 @@ fn build_from_args_only(args: Args) -> anyhow::Result<(Config, CtrlConfig)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
|
fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||||
|
if file.legacy_no_tun.is_some() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"configuration key 'no_tun' was removed; use device_mode = \"no|tun|tap\""
|
||||||
|
));
|
||||||
|
}
|
||||||
let server_addr = file.to_server_addr()?;
|
let server_addr = file.to_server_addr()?;
|
||||||
let port_mapping = file.to_port_mapping()?;
|
let port_mapping = file.to_port_mapping()?;
|
||||||
|
|
||||||
@@ -358,7 +373,7 @@ fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)
|
|||||||
cert_mode,
|
cert_mode,
|
||||||
output: file.output.unwrap_or_default(),
|
output: file.output.unwrap_or_default(),
|
||||||
no_nat: file.no_nat.unwrap_or(false),
|
no_nat: file.no_nat.unwrap_or(false),
|
||||||
no_tun: file.no_tun.unwrap_or(false),
|
device_mode: file.device_mode.unwrap_or_default(),
|
||||||
mtu: file.mtu,
|
mtu: file.mtu,
|
||||||
port_mapping,
|
port_mapping,
|
||||||
allow_port_mapping: file.allow_mapping.unwrap_or(false),
|
allow_port_mapping: file.allow_mapping.unwrap_or(false),
|
||||||
@@ -422,8 +437,8 @@ server = ["quic://1.2.3.4:29872"]
|
|||||||
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
||||||
# no_nat = false
|
# no_nat = false
|
||||||
|
|
||||||
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
|
# 虚拟网卡模式:no(无网卡)、tun(三层网卡,默认)、tap(二层网卡)
|
||||||
# no_tun = false
|
# device_mode = "tun"
|
||||||
|
|
||||||
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||||
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
||||||
@@ -512,4 +527,50 @@ mod tests {
|
|||||||
assert_eq!(config.tunnel_port, Some(12345));
|
assert_eq!(config.tunnel_port, Some(12345));
|
||||||
assert_eq!(config.outbound_interface.as_deref(), Some("Ethernet"));
|
assert_eq!(config.outbound_interface.as_deref(), Some("Ethernet"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_mode_cli_and_legacy_rejection() {
|
||||||
|
let args = Args::try_parse_from([
|
||||||
|
"vnt",
|
||||||
|
"-s",
|
||||||
|
"quic://127.0.0.1:29872",
|
||||||
|
"-n",
|
||||||
|
"test-net",
|
||||||
|
"--device-mode",
|
||||||
|
"tap",
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
let (config, _) = build_from_args_only(args).unwrap();
|
||||||
|
assert_eq!(config.device_mode, DeviceMode::Tap);
|
||||||
|
|
||||||
|
let legacy: FileConfig = toml::from_str("no_tun = true").unwrap();
|
||||||
|
let error = match build_config_from_args_and_file(None, Some(legacy)) {
|
||||||
|
Err(error) => error,
|
||||||
|
Ok(_) => panic!("legacy no_tun must be rejected"),
|
||||||
|
};
|
||||||
|
assert!(error.to_string().contains("device_mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_mode_cli_overrides_file_and_file_defaults() {
|
||||||
|
let file: FileConfig = toml::from_str("device_mode = \"no\"").unwrap();
|
||||||
|
let args = Args::try_parse_from(["vnt", "-s", "quic://127.0.0.1:29872", "-n", "test-net"])
|
||||||
|
.unwrap();
|
||||||
|
let (config, _) = build_config_from_args_and_file(Some(args), Some(file)).unwrap();
|
||||||
|
assert_eq!(config.device_mode, DeviceMode::No);
|
||||||
|
|
||||||
|
let file: FileConfig = toml::from_str("device_mode = \"no\"").unwrap();
|
||||||
|
let args = Args::try_parse_from([
|
||||||
|
"vnt",
|
||||||
|
"-s",
|
||||||
|
"quic://127.0.0.1:29872",
|
||||||
|
"-n",
|
||||||
|
"test-net",
|
||||||
|
"--device-mode",
|
||||||
|
"tap",
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
let (config, _) = build_config_from_args_and_file(Some(args), Some(file)).unwrap();
|
||||||
|
assert_eq!(config.device_mode, DeviceMode::Tap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -104,18 +104,26 @@ async fn main0() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !network_manager.is_no_tun() {
|
if network_manager.device_mode().has_device() {
|
||||||
log::info!("启动网络:{}/{}", reg_msg.ip, reg_msg.prefix_len);
|
log::info!(
|
||||||
network_manager.start_tun().await.context("start tun")?;
|
"启动网络:{}/{} ({})",
|
||||||
|
reg_msg.ip,
|
||||||
|
reg_msg.prefix_len,
|
||||||
|
network_manager.device_mode()
|
||||||
|
);
|
||||||
network_manager
|
network_manager
|
||||||
.set_tun_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
.start_device()
|
||||||
|
.await
|
||||||
|
.context("start device")?;
|
||||||
|
network_manager
|
||||||
|
.set_device_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
||||||
.await
|
.await
|
||||||
.context("set network ip")?;
|
.context("set network ip")?;
|
||||||
if !sub_input.is_empty() {
|
if !sub_input.is_empty() {
|
||||||
let if_index = network_manager
|
let if_index = network_manager
|
||||||
.tun_if_index()
|
.device_if_index()
|
||||||
.await
|
.await
|
||||||
.context("tun_if_index")?;
|
.context("device_if_index")?;
|
||||||
let mut route_manager = route_manager::RouteManager::new()?;
|
let mut route_manager = route_manager::RouteManager::new()?;
|
||||||
for x in sub_input {
|
for x in sub_input {
|
||||||
let route = Route::new(x.net.network().into(), x.net.prefix_len())
|
let route = Route::new(x.net.network().into(), x.net.prefix_len())
|
||||||
|
|||||||
@@ -89,11 +89,13 @@ mod tests {
|
|||||||
|
|
||||||
// --- 构造原始包 ---
|
// --- 构造原始包 ---
|
||||||
let payload = vec![1u8; 200];
|
let payload = vec![1u8; 200];
|
||||||
let original = make_packet(&payload);
|
let mut original = make_packet(&payload);
|
||||||
|
original.set_ethernet_flag(true);
|
||||||
|
|
||||||
// --- 压缩 ---
|
// --- 压缩 ---
|
||||||
let compressed = lz.compress(original, 0).unwrap();
|
let compressed = lz.compress(original, 0).unwrap();
|
||||||
assert!(compressed.is_compressed());
|
assert!(compressed.is_compressed());
|
||||||
|
assert!(compressed.is_ethernet());
|
||||||
|
|
||||||
// 压缩后的 payload 应变小
|
// 压缩后的 payload 应变小
|
||||||
assert!(
|
assert!(
|
||||||
@@ -106,11 +108,14 @@ mod tests {
|
|||||||
|
|
||||||
// 标志应清除
|
// 标志应清除
|
||||||
assert!(!decompressed.is_compressed());
|
assert!(!decompressed.is_compressed());
|
||||||
|
assert!(decompressed.is_ethernet());
|
||||||
|
|
||||||
// HEAD 不变
|
// HEAD 不变
|
||||||
|
let mut expected_head = [0u8; HEAD_LENGTH];
|
||||||
|
expected_head[2] = 0x10;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&decompressed.buffer()[..HEAD_LENGTH],
|
&decompressed.buffer()[..HEAD_LENGTH],
|
||||||
&[0u8; HEAD_LENGTH][..],
|
&expected_head,
|
||||||
"HEAD 必须保持不变"
|
"HEAD 必须保持不变"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use crate::tunnel_core::server::transport::config::{ConnectRegConfig, ProtocolAd
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use ipnet::Ipv4Net;
|
use ipnet::Ipv4Net;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub const MAX_NETWORK_CODE_LEN: usize = 32;
|
pub const MAX_NETWORK_CODE_LEN: usize = 32;
|
||||||
pub const MAX_DEVICE_ID_LEN: usize = 64;
|
pub const MAX_DEVICE_ID_LEN: usize = 64;
|
||||||
@@ -14,6 +16,44 @@ pub const MAX_NAME_LEN: usize = 128;
|
|||||||
pub const MAX_VERSION_LEN: usize = 32;
|
pub const MAX_VERSION_LEN: usize = 32;
|
||||||
pub const MAX_MTU: u16 = 1500;
|
pub const MAX_MTU: u16 = 1500;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum DeviceMode {
|
||||||
|
No,
|
||||||
|
#[default]
|
||||||
|
Tun,
|
||||||
|
Tap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceMode {
|
||||||
|
pub fn has_device(self) -> bool {
|
||||||
|
!matches!(self, Self::No)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DeviceMode {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::No => "no",
|
||||||
|
Self::Tun => "tun",
|
||||||
|
Self::Tap => "tap",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DeviceMode {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
match value.to_ascii_lowercase().as_str() {
|
||||||
|
"no" => Ok(Self::No),
|
||||||
|
"tun" => Ok(Self::Tun),
|
||||||
|
"tap" => Ok(Self::Tap),
|
||||||
|
_ => bail!("invalid device_mode '{value}', expected one of: no, tun, tap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub server_addr: Vec<ProtocolAddress>,
|
pub server_addr: Vec<ProtocolAddress>,
|
||||||
@@ -33,7 +73,7 @@ pub struct Config {
|
|||||||
pub input: Vec<NetInput>,
|
pub input: Vec<NetInput>,
|
||||||
pub output: Vec<Ipv4Net>,
|
pub output: Vec<Ipv4Net>,
|
||||||
pub no_nat: bool,
|
pub no_nat: bool,
|
||||||
pub no_tun: bool,
|
pub device_mode: DeviceMode,
|
||||||
pub mtu: Option<u16>,
|
pub mtu: Option<u16>,
|
||||||
pub port_mapping: Vec<PortMapping>,
|
pub port_mapping: Vec<PortMapping>,
|
||||||
pub allow_port_mapping: bool,
|
pub allow_port_mapping: bool,
|
||||||
@@ -43,6 +83,10 @@ pub struct Config {
|
|||||||
}
|
}
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn check(&self) -> anyhow::Result<()> {
|
pub fn check(&self) -> anyhow::Result<()> {
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios", target_os = "tvos"))]
|
||||||
|
if self.device_mode == DeviceMode::Tap {
|
||||||
|
bail!("TAP mode is not supported on mobile VPN interfaces");
|
||||||
|
}
|
||||||
if self.server_addr.is_empty() {
|
if self.server_addr.is_empty() {
|
||||||
bail!("服务器地址不能为空");
|
bail!("服务器地址不能为空");
|
||||||
}
|
}
|
||||||
@@ -107,3 +151,22 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_mode_parse_and_display() {
|
||||||
|
for (text, mode) in [
|
||||||
|
("no", DeviceMode::No),
|
||||||
|
("tun", DeviceMode::Tun),
|
||||||
|
("tap", DeviceMode::Tap),
|
||||||
|
] {
|
||||||
|
assert_eq!(text.parse::<DeviceMode>().unwrap(), mode);
|
||||||
|
assert_eq!(mode.to_string(), text);
|
||||||
|
}
|
||||||
|
assert!("bridge".parse::<DeviceMode>().is_err());
|
||||||
|
assert_eq!(DeviceMode::default(), DeviceMode::Tun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+49
-24
@@ -1,6 +1,6 @@
|
|||||||
use crate::api::VntApi;
|
use crate::api::VntApi;
|
||||||
use crate::compression::PacketCompression;
|
use crate::compression::PacketCompression;
|
||||||
use crate::context::config::Config;
|
use crate::context::config::{Config, DeviceMode};
|
||||||
use crate::context::{AppState, NetworkAddr, NetworkRoute};
|
use crate::context::{AppState, NetworkAddr, NetworkRoute};
|
||||||
use crate::crypto::PacketCrypto;
|
use crate::crypto::PacketCrypto;
|
||||||
use crate::enhanced_tunnel::enhanced_ipv4_tunnel;
|
use crate::enhanced_tunnel::enhanced_ipv4_tunnel;
|
||||||
@@ -21,7 +21,7 @@ use crate::tunnel_core::server::connection_manager::{
|
|||||||
};
|
};
|
||||||
use crate::tunnel_core::server::rpc::ServerRPC;
|
use crate::tunnel_core::server::rpc::ServerRPC;
|
||||||
use crate::utils::task_control::TaskGroup;
|
use crate::utils::task_control::TaskGroup;
|
||||||
use anyhow::bail;
|
use anyhow::{Context, bail};
|
||||||
use ipnet::Ipv4Net;
|
use ipnet::Ipv4Net;
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
@@ -137,12 +137,12 @@ impl NetworkManager {
|
|||||||
fec_encoder,
|
fec_encoder,
|
||||||
);
|
);
|
||||||
let port_mapping_manager = PortMappingManager::new(
|
let port_mapping_manager = PortMappingManager::new(
|
||||||
config.no_tun,
|
config.device_mode == DeviceMode::No,
|
||||||
config.allow_port_mapping,
|
config.allow_port_mapping,
|
||||||
app_state.network.clone(),
|
app_state.network.clone(),
|
||||||
default_interface.clone(),
|
default_interface.clone(),
|
||||||
);
|
);
|
||||||
let internal_nat_inbound = if config.no_nat && !config.no_tun {
|
let internal_nat_inbound = if config.no_nat && config.device_mode != DeviceMode::No {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let nat_inbound = InternalNatInbound::create(
|
let nat_inbound = InternalNatInbound::create(
|
||||||
@@ -151,26 +151,32 @@ impl NetworkManager {
|
|||||||
hybrid_outbound.clone(),
|
hybrid_outbound.clone(),
|
||||||
allow_subnet.clone(),
|
allow_subnet.clone(),
|
||||||
app_state.network.clone(),
|
app_state.network.clone(),
|
||||||
config.no_tun,
|
config.device_mode == DeviceMode::No,
|
||||||
default_interface.clone(),
|
default_interface.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Some(nat_inbound)
|
Some(nat_inbound)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (enhanced_tun_inbound, tun_receiver) = if config.no_tun {
|
let (enhanced_tun_inbound, tun_receiver) = match config.device_mode {
|
||||||
(
|
DeviceMode::No => (
|
||||||
EnhancedTunInbound::Nat(
|
EnhancedTunInbound::Nat(
|
||||||
internal_nat_inbound
|
internal_nat_inbound
|
||||||
.clone()
|
.clone()
|
||||||
.expect("internal_nat_inbound must be Some when no_tun is true"),
|
.expect("internal_nat_inbound must be Some in no-device mode"),
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
),
|
||||||
} else {
|
mode @ (DeviceMode::Tun | DeviceMode::Tap) => {
|
||||||
let (tun_inbound, tun_receiver) = tun_channel();
|
let (tun_inbound, tun_receiver) = tun_channel();
|
||||||
let tun_data_sender = TunDataInbound::new(tun_inbound, allow_subnet.clone());
|
let tun_data_sender = TunDataInbound::new(tun_inbound, allow_subnet.clone(), mode);
|
||||||
(EnhancedTunInbound::Tun(tun_data_sender), Some(tun_receiver))
|
let inbound = if mode == DeviceMode::Tap {
|
||||||
|
EnhancedTunInbound::Tap(tun_data_sender)
|
||||||
|
} else {
|
||||||
|
EnhancedTunInbound::Tun(tun_data_sender)
|
||||||
|
};
|
||||||
|
(inbound, Some(tun_receiver))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (enhanced_inbound, enhanced_outbound) = enhanced_ipv4_tunnel(
|
let (enhanced_inbound, enhanced_outbound) = enhanced_ipv4_tunnel(
|
||||||
@@ -182,6 +188,7 @@ impl NetworkManager {
|
|||||||
password: config.password.clone(),
|
password: config.password.clone(),
|
||||||
open_quic_client: config.rtx,
|
open_quic_client: config.rtx,
|
||||||
port_mapping: config.port_mapping.clone(),
|
port_mapping: config.port_mapping.clone(),
|
||||||
|
device_mode: config.device_mode,
|
||||||
},
|
},
|
||||||
crate::enhanced_tunnel::TunnelComponents {
|
crate::enhanced_tunnel::TunnelComponents {
|
||||||
hybrid_outbound: hybrid_outbound.clone(),
|
hybrid_outbound: hybrid_outbound.clone(),
|
||||||
@@ -325,16 +332,25 @@ impl NetworkManager {
|
|||||||
Ok(RegisterResponse::Success(network_addr))
|
Ok(RegisterResponse::Success(network_addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_no_tun(&self) -> bool {
|
pub fn device_mode(&self) -> DeviceMode {
|
||||||
self.config.no_tun
|
self.config.device_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_tun(&mut self) -> anyhow::Result<()> {
|
pub async fn start_device(&mut self) -> anyhow::Result<()> {
|
||||||
if self.tun_receiver.is_none() || self.enhanced_outbound.is_none() {
|
if self.tun_receiver.is_none() || self.enhanced_outbound.is_none() {
|
||||||
bail!("start_tun can only be called once");
|
bail!("start_device requires tun/tap mode and can only be called once");
|
||||||
}
|
}
|
||||||
let mut config = DeviceConfig::default();
|
let mut config = DeviceConfig::default();
|
||||||
config = config.set_mtu(self.config.mtu.unwrap_or(DEFAULT_MTU));
|
config = config
|
||||||
|
.set_device_mode(self.config.device_mode)
|
||||||
|
.set_mtu(self.config.mtu.unwrap_or(DEFAULT_MTU));
|
||||||
|
if self.config.device_mode == DeviceMode::Tap {
|
||||||
|
let net = self
|
||||||
|
.app_state
|
||||||
|
.get_network()
|
||||||
|
.context("network is not registered")?;
|
||||||
|
config = config.set_mac_addr(crate::ethernet::mac_from_ip(net.ip));
|
||||||
|
}
|
||||||
if let Some(tun_name) = self.config.tun_name.clone() {
|
if let Some(tun_name) = self.config.tun_name.clone() {
|
||||||
config = config.set_tun_name(tun_name);
|
config = config.set_tun_name(tun_name);
|
||||||
}
|
}
|
||||||
@@ -344,11 +360,20 @@ impl NetworkManager {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub async fn start_tun_fd(&mut self, tun_fd: Option<i32>) -> anyhow::Result<()> {
|
pub async fn start_device_fd(&mut self, tun_fd: Option<i32>) -> anyhow::Result<()> {
|
||||||
if self.tun_receiver.is_none() || self.enhanced_outbound.is_none() {
|
if self.tun_receiver.is_none() || self.enhanced_outbound.is_none() {
|
||||||
bail!("start_tun_fd can only be called once");
|
bail!("start_device_fd requires tun/tap mode and can only be called once");
|
||||||
|
}
|
||||||
|
let mut config = DeviceConfig::default()
|
||||||
|
.set_device_mode(self.config.device_mode)
|
||||||
|
.set_mtu(self.config.mtu.unwrap_or(DEFAULT_MTU));
|
||||||
|
if self.config.device_mode == DeviceMode::Tap {
|
||||||
|
let net = self
|
||||||
|
.app_state
|
||||||
|
.get_network()
|
||||||
|
.context("network is not registered")?;
|
||||||
|
config = config.set_mac_addr(crate::ethernet::mac_from_ip(net.ip));
|
||||||
}
|
}
|
||||||
let mut config = DeviceConfig::default();
|
|
||||||
if let Some(tun_fd) = tun_fd {
|
if let Some(tun_fd) = tun_fd {
|
||||||
config = config.set_tun_fd(tun_fd);
|
config = config.set_tun_fd(tun_fd);
|
||||||
}
|
}
|
||||||
@@ -360,7 +385,7 @@ impl NetworkManager {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
pub async fn set_tun_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
pub async fn set_device_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
||||||
self.device_io_manager.set_network(ip, prefix_len).await?;
|
self.device_io_manager.set_network(ip, prefix_len).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -370,8 +395,8 @@ impl NetworkManager {
|
|||||||
self.app_state.stop_network();
|
self.app_state.stop_network();
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
|
pub async fn device_if_index(&self) -> anyhow::Result<u32> {
|
||||||
self.device_io_manager.tun_if_index().await
|
self.device_io_manager.device_if_index().await
|
||||||
}
|
}
|
||||||
pub async fn wait_all_stopped(&mut self) {
|
pub async fn wait_all_stopped(&mut self) {
|
||||||
self.task_group.wait_all_stopped().await;
|
self.task_group.wait_all_stopped().await;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ impl PacketCrypto {
|
|||||||
|
|
||||||
/// AAD 承担"认证"职责:覆盖传输中不变、但不参与 nonce 的头部字节
|
/// AAD 承担"认证"职责:覆盖传输中不变、但不参与 nonce 的头部字节
|
||||||
/// byte0(msg_type)/byte2(flags)/byte3(reserved)。
|
/// byte0(msg_type)/byte2(flags)/byte3(reserved)。
|
||||||
/// msg_type 与 flags(COMPRESSED/FEC/GATEWAY)只由发送方设置、
|
/// msg_type 与 flags(COMPRESSED/FEC/GATEWAY/ETHERNET)只由发送方设置、
|
||||||
/// 传输中不会被修改,必须纳入认证,否则中间人可翻转造成不可检测的
|
/// 传输中不会被修改,必须纳入认证,否则中间人可翻转造成不可检测的
|
||||||
/// 丢包/语义篡改;ttl(byte1) 在中继转发时会递减,不能纳入 AAD。
|
/// 丢包/语义篡改;ttl(byte1) 在中继转发时会递减,不能纳入 AAD。
|
||||||
fn make_aad<B: AsRef<[u8]>>(pkt: &NetPacket<B>) -> [u8; 3] {
|
fn make_aad<B: AsRef<[u8]>>(pkt: &NetPacket<B>) -> [u8; 3] {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
use crate::context::config::DeviceMode;
|
||||||
use crate::context::{NetworkAddr, TrafficStats};
|
use crate::context::{NetworkAddr, TrafficStats};
|
||||||
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
|
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
|
||||||
|
use crate::ethernet::{ETHERTYPE_IPV4, build_arp_reply, parse_arp_ipv4, parse_frame};
|
||||||
use crate::nat::internal_nat::InternalNatInbound;
|
use crate::nat::internal_nat::InternalNatInbound;
|
||||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||||
use crate::protocol::transmission::TransmissionBytes;
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
use crate::tun::enhanced_tun::EnhancedTunInbound;
|
use crate::tun::enhanced_tun::EnhancedTunInbound;
|
||||||
|
use crate::tunnel_core::outbound::HybridOutbound;
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use pnet_packet::ipv4::Ipv4Packet;
|
use pnet_packet::ipv4::Ipv4Packet;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
@@ -14,6 +17,8 @@ pub(crate) struct EnhancedInbound {
|
|||||||
quic_inbound: EnhancedQuicInbound,
|
quic_inbound: EnhancedQuicInbound,
|
||||||
internal_nat_inbound: Option<InternalNatInbound>,
|
internal_nat_inbound: Option<InternalNatInbound>,
|
||||||
traffic_stats: TrafficStats,
|
traffic_stats: TrafficStats,
|
||||||
|
device_mode: DeviceMode,
|
||||||
|
hybrid_outbound: HybridOutbound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnhancedInbound {
|
impl EnhancedInbound {
|
||||||
@@ -22,12 +27,16 @@ impl EnhancedInbound {
|
|||||||
quic_inbound: EnhancedQuicInbound,
|
quic_inbound: EnhancedQuicInbound,
|
||||||
internal_nat_inbound: Option<InternalNatInbound>,
|
internal_nat_inbound: Option<InternalNatInbound>,
|
||||||
traffic_stats: TrafficStats,
|
traffic_stats: TrafficStats,
|
||||||
|
device_mode: DeviceMode,
|
||||||
|
hybrid_outbound: HybridOutbound,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tun_data_inbound,
|
tun_data_inbound,
|
||||||
quic_inbound,
|
quic_inbound,
|
||||||
internal_nat_inbound,
|
internal_nat_inbound,
|
||||||
traffic_stats,
|
traffic_stats,
|
||||||
|
device_mode,
|
||||||
|
hybrid_outbound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn inbound(
|
pub async fn inbound(
|
||||||
@@ -37,26 +46,65 @@ impl EnhancedInbound {
|
|||||||
src: Ipv4Addr,
|
src: Ipv4Addr,
|
||||||
packet: NetPacket<TransmissionBytes>,
|
packet: NetPacket<TransmissionBytes>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let ethernet = packet.is_ethernet();
|
||||||
let mut buf = packet.into_buffer();
|
let mut buf = packet.into_buffer();
|
||||||
self.traffic_stats.record_rx(src, buf.len() as u64);
|
self.traffic_stats.record_rx(src, buf.len() as u64);
|
||||||
buf.advance_head(HEAD_LENGTH)?;
|
buf.advance_head(HEAD_LENGTH)?;
|
||||||
|
|
||||||
|
if ethernet && parse_frame(buf.as_ref()).is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if ethernet && self.device_mode != DeviceMode::Tap {
|
||||||
|
if let Some(arp) = parse_arp_ipv4(buf.as_ref())
|
||||||
|
&& arp.operation == 1
|
||||||
|
&& arp.target_ip == network_addr.ip
|
||||||
|
{
|
||||||
|
if let Some(reply) = build_arp_reply(buf.as_ref(), network_addr.ip) {
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_unicast_outbound(*network_addr, src, reply)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if parse_frame(buf.as_ref()).is_none_or(|frame| frame.ethertype != ETHERTYPE_IPV4) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match msg_type {
|
match msg_type {
|
||||||
MsgType::Turn => {
|
MsgType::Turn => {
|
||||||
if let Some(internal_nat_inbound) = self.internal_nat_inbound.as_ref() {
|
if let Some(internal_nat_inbound) = self.internal_nat_inbound.as_ref() {
|
||||||
let Some(ipv4) = Ipv4Packet::new(&buf) else {
|
let ip_data = if ethernet {
|
||||||
|
let Some(frame) = parse_frame(buf.as_ref()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if frame.ethertype != ETHERTYPE_IPV4 {
|
||||||
|
self.tun_data_inbound
|
||||||
|
.inbound(buf, network_addr, src, true)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
&buf[frame.payload_offset..]
|
||||||
|
} else {
|
||||||
|
buf.as_ref()
|
||||||
|
};
|
||||||
|
let Some(ipv4) = Ipv4Packet::new(ip_data) else {
|
||||||
bail!("EnhancedInbound not ipv4")
|
bail!("EnhancedInbound not ipv4")
|
||||||
};
|
};
|
||||||
let dest = ipv4.get_destination();
|
let dest = ipv4.get_destination();
|
||||||
if dest != network_addr.ip && !network_addr.network().contains(&dest) {
|
if dest != network_addr.ip && !network_addr.network().contains(&dest) {
|
||||||
internal_nat_inbound.send(&buf, network_addr).await?;
|
internal_nat_inbound.send(ip_data, network_addr).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tun_data_inbound.inbound(buf, network_addr).await?;
|
self.tun_data_inbound
|
||||||
|
.inbound(buf, network_addr, src, ethernet)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
MsgType::Broadcast | MsgType::ExcludeBroadcast => {
|
MsgType::Broadcast | MsgType::ExcludeBroadcast => {
|
||||||
self.tun_data_inbound.inbound(buf, network_addr).await?;
|
self.tun_data_inbound
|
||||||
|
.inbound(buf, network_addr, src, ethernet)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
MsgType::Quic => {
|
MsgType::Quic => {
|
||||||
let payload = buf.into_bytes().freeze();
|
let payload = buf.into_bytes().freeze();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::context::AppState;
|
use crate::context::AppState;
|
||||||
|
use crate::context::config::DeviceMode;
|
||||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||||
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
||||||
use crate::nat::SubnetExternalRoute;
|
use crate::nat::SubnetExternalRoute;
|
||||||
@@ -18,6 +19,7 @@ pub(crate) struct TunnelConfig {
|
|||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
pub open_quic_client: bool,
|
pub open_quic_client: bool,
|
||||||
pub port_mapping: Vec<PortMapping>,
|
pub port_mapping: Vec<PortMapping>,
|
||||||
|
pub device_mode: DeviceMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct TunnelComponents {
|
pub(crate) struct TunnelComponents {
|
||||||
@@ -36,7 +38,7 @@ pub(crate) async fn enhanced_ipv4_tunnel(
|
|||||||
) -> anyhow::Result<(EnhancedInbound, Option<EnhancedOutbound>)> {
|
) -> anyhow::Result<(EnhancedInbound, Option<EnhancedOutbound>)> {
|
||||||
let password = config.password.unwrap_or_else(|| "password".to_string());
|
let password = config.password.unwrap_or_else(|| "password".to_string());
|
||||||
let tun = match &tun_data_sender {
|
let tun = match &tun_data_sender {
|
||||||
EnhancedTunInbound::Tun(tun) => Some(tun.clone()),
|
EnhancedTunInbound::Tun(tun) | EnhancedTunInbound::Tap(tun) => Some(tun.clone()),
|
||||||
EnhancedTunInbound::Nat(_) => None,
|
EnhancedTunInbound::Nat(_) => None,
|
||||||
};
|
};
|
||||||
let (inbound, outbound) = quic_over::boot::quic_tunnel_start(
|
let (inbound, outbound) = quic_over::boot::quic_tunnel_start(
|
||||||
@@ -62,6 +64,8 @@ pub(crate) async fn enhanced_ipv4_tunnel(
|
|||||||
inbound,
|
inbound,
|
||||||
components.internal_nat_inbound,
|
components.internal_nat_inbound,
|
||||||
app_state.traffic_stats.clone(),
|
app_state.traffic_stats.clone(),
|
||||||
|
config.device_mode,
|
||||||
|
components.hybrid_outbound.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let enhanced_outbound = outbound.map(|outbound| {
|
let enhanced_outbound = outbound.map(|outbound| {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
use crate::context::SharedNetworkAddr;
|
use crate::context::SharedNetworkAddr;
|
||||||
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
|
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
|
||||||
|
use crate::ethernet::{
|
||||||
|
ETHERTYPE_ARP, ETHERTYPE_IPV4, build_arp_reply, ip_from_mac, is_broadcast_or_multicast,
|
||||||
|
parse_arp_ipv4, parse_frame,
|
||||||
|
};
|
||||||
use crate::protocol::transmission::TransmissionBytes;
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
use crate::tunnel_core::outbound::HybridOutbound;
|
use crate::tunnel_core::outbound::HybridOutbound;
|
||||||
use pnet_packet::ipv4::Ipv4Packet;
|
use pnet_packet::ipv4::Ipv4Packet;
|
||||||
@@ -30,6 +34,74 @@ impl EnhancedOutbound {
|
|||||||
log::warn!("EnhancedOutbound error: {:?}", e);
|
log::warn!("EnhancedOutbound error: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn ethernet_outbound(&self, data: TransmissionBytes) -> Option<TransmissionBytes> {
|
||||||
|
match self.ethernet_outbound_impl(data).await {
|
||||||
|
Ok(reply) => reply,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("EnhancedOutbound Ethernet error: {e:?}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn ethernet_outbound_impl(
|
||||||
|
&self,
|
||||||
|
data: TransmissionBytes,
|
||||||
|
) -> anyhow::Result<Option<TransmissionBytes>> {
|
||||||
|
let Some(frame) = parse_frame(data.as_ref()) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(net) = self.network.get() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
match frame.ethertype {
|
||||||
|
ETHERTYPE_IPV4 => {
|
||||||
|
let Some(ipv4) = Ipv4Packet::new(&data[frame.payload_offset..]) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let src = ipv4.get_source();
|
||||||
|
let dest = ipv4.get_destination();
|
||||||
|
if dest == src || dest.is_unspecified() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_ipv4_outbound(net, data, dest)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ETHERTYPE_ARP => {
|
||||||
|
let Some(arp) = parse_arp_ipv4(data.as_ref()) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
if arp.operation == 1 && arp.target_ip == net.gateway {
|
||||||
|
return Ok(build_arp_reply(data.as_ref(), net.gateway));
|
||||||
|
}
|
||||||
|
if arp.operation == 2 {
|
||||||
|
let dest = ip_from_mac(frame.destination).unwrap_or(arp.target_ip);
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_unicast_outbound(net, dest, data)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_broadcast_outbound(net, data)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !is_broadcast_or_multicast(frame.destination)
|
||||||
|
&& let Some(dest) = ip_from_mac(frame.destination)
|
||||||
|
&& net.network().contains(&dest)
|
||||||
|
{
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_unicast_outbound(net, dest, data)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
self.hybrid_outbound
|
||||||
|
.ethernet_broadcast_outbound(net, data)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
async fn ipv4_outbound_impl(&self, data: TransmissionBytes) -> anyhow::Result<()> {
|
async fn ipv4_outbound_impl(&self, data: TransmissionBytes) -> anyhow::Result<()> {
|
||||||
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
|
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use crate::tun::TunDataInbound;
|
|||||||
use crate::tunnel_core::outbound::HybridOutbound;
|
use crate::tunnel_core::outbound::HybridOutbound;
|
||||||
use crate::utils::task_control::TaskGroup;
|
use crate::utils::task_control::TaskGroup;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use pnet_packet::ipv4::Ipv4Packet;
|
||||||
use quinn::congestion::BbrConfig;
|
use quinn::congestion::BbrConfig;
|
||||||
use quinn::crypto::rustls::QuicServerConfig;
|
use quinn::crypto::rustls::QuicServerConfig;
|
||||||
use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig, default_runtime};
|
use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig, default_runtime};
|
||||||
@@ -186,7 +187,13 @@ async fn ip_stack_recv_task(
|
|||||||
log::error!("not network");
|
log::error!("not network");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
match tun_data_sender.send((&buf[..len]).into(), &net).await {
|
let Some(ipv4) = Ipv4Packet::new(&buf[..len]) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match tun_data_sender
|
||||||
|
.send_ip((&buf[..len]).into(), &net, ipv4.get_source())
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("IP stack send error: {:?}", e);
|
log::error!("IP stack send error: {:?}", e);
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
use crate::context::NetworkAddr;
|
||||||
|
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
||||||
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
pub const ETHERNET_HEADER_LEN: usize = 14;
|
||||||
|
pub const ETHERTYPE_IPV4: u16 = 0x0800;
|
||||||
|
pub const ETHERTYPE_ARP: u16 = 0x0806;
|
||||||
|
const ETHERTYPE_VLAN: u16 = 0x8100;
|
||||||
|
const ETHERTYPE_QINQ: u16 = 0x88a8;
|
||||||
|
const ETHERTYPE_VLAN_9100: u16 = 0x9100;
|
||||||
|
const ARP_IPV4_LEN: usize = 28;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct FrameInfo {
|
||||||
|
pub destination: [u8; 6],
|
||||||
|
pub source: [u8; 6],
|
||||||
|
pub ethertype: u16,
|
||||||
|
pub payload_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct ArpIpv4 {
|
||||||
|
pub operation: u16,
|
||||||
|
pub sender_mac: [u8; 6],
|
||||||
|
pub sender_ip: Ipv4Addr,
|
||||||
|
pub target_mac: [u8; 6],
|
||||||
|
pub target_ip: Ipv4Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mac_from_ip(ip: Ipv4Addr) -> [u8; 6] {
|
||||||
|
let octets = ip.octets();
|
||||||
|
[0x02, 0x00, octets[0], octets[1], octets[2], octets[3]]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ip_from_mac(mac: [u8; 6]) -> Option<Ipv4Addr> {
|
||||||
|
(mac[0] == 0x02 && mac[1] == 0x00).then(|| Ipv4Addr::new(mac[2], mac[3], mac[4], mac[5]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_frame(frame: &[u8]) -> Option<FrameInfo> {
|
||||||
|
if frame.len() < ETHERNET_HEADER_LEN {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let destination = frame[0..6].try_into().ok()?;
|
||||||
|
let source = frame[6..12].try_into().ok()?;
|
||||||
|
let mut ethertype = u16::from_be_bytes(frame[12..14].try_into().ok()?);
|
||||||
|
let mut payload_offset = ETHERNET_HEADER_LEN;
|
||||||
|
// Support stacked VLAN tags. Four tags is already beyond normal Q-in-Q usage
|
||||||
|
// and bounds work performed for an untrusted frame.
|
||||||
|
for _ in 0..4 {
|
||||||
|
if !matches!(
|
||||||
|
ethertype,
|
||||||
|
ETHERTYPE_VLAN | ETHERTYPE_QINQ | ETHERTYPE_VLAN_9100
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if frame.len() < payload_offset + 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
ethertype = u16::from_be_bytes(
|
||||||
|
frame[payload_offset + 2..payload_offset + 4]
|
||||||
|
.try_into()
|
||||||
|
.ok()?,
|
||||||
|
);
|
||||||
|
payload_offset += 4;
|
||||||
|
}
|
||||||
|
(frame.len() >= payload_offset).then_some(FrameInfo {
|
||||||
|
destination,
|
||||||
|
source,
|
||||||
|
ethertype,
|
||||||
|
payload_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_arp_ipv4(frame: &[u8]) -> Option<ArpIpv4> {
|
||||||
|
let info = parse_frame(frame)?;
|
||||||
|
if info.ethertype != ETHERTYPE_ARP || frame.len() < info.payload_offset + ARP_IPV4_LEN {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let arp = &frame[info.payload_offset..];
|
||||||
|
if u16::from_be_bytes(arp[0..2].try_into().ok()?) != 1
|
||||||
|
|| u16::from_be_bytes(arp[2..4].try_into().ok()?) != ETHERTYPE_IPV4
|
||||||
|
|| arp[4] != 6
|
||||||
|
|| arp[5] != 4
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(ArpIpv4 {
|
||||||
|
operation: u16::from_be_bytes(arp[6..8].try_into().ok()?),
|
||||||
|
sender_mac: arp[8..14].try_into().ok()?,
|
||||||
|
sender_ip: Ipv4Addr::from(<[u8; 4]>::try_from(&arp[14..18]).ok()?),
|
||||||
|
target_mac: arp[18..24].try_into().ok()?,
|
||||||
|
target_ip: Ipv4Addr::from(<[u8; 4]>::try_from(&arp[24..28]).ok()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_arp_reply(request: &[u8], own_ip: Ipv4Addr) -> Option<TransmissionBytes> {
|
||||||
|
let info = parse_frame(request)?;
|
||||||
|
let arp = parse_arp_ipv4(request)?;
|
||||||
|
if arp.operation != 1 || arp.target_ip != own_ip {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let frame_len = request.len().max(info.payload_offset + ARP_IPV4_LEN);
|
||||||
|
let mut bytes = TransmissionBytes::with_capacity(HEAD_LENGTH, HEAD_LENGTH + frame_len);
|
||||||
|
bytes.put(request).ok()?;
|
||||||
|
if bytes.len() < frame_len {
|
||||||
|
bytes.extend_end(frame_len - bytes.len());
|
||||||
|
}
|
||||||
|
let own_mac = mac_from_ip(own_ip);
|
||||||
|
bytes[0..6].copy_from_slice(&arp.sender_mac);
|
||||||
|
bytes[6..12].copy_from_slice(&own_mac);
|
||||||
|
let payload = info.payload_offset;
|
||||||
|
bytes[payload + 6..payload + 8].copy_from_slice(&2u16.to_be_bytes());
|
||||||
|
bytes[payload + 8..payload + 14].copy_from_slice(&own_mac);
|
||||||
|
bytes[payload + 14..payload + 18].copy_from_slice(&own_ip.octets());
|
||||||
|
bytes[payload + 18..payload + 24].copy_from_slice(&arp.sender_mac);
|
||||||
|
bytes[payload + 24..payload + 28].copy_from_slice(&arp.sender_ip.octets());
|
||||||
|
Some(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strip_ipv4(mut frame: TransmissionBytes) -> Option<TransmissionBytes> {
|
||||||
|
let info = parse_frame(frame.as_ref())?;
|
||||||
|
if info.ethertype != ETHERTYPE_IPV4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
frame.advance_head(info.payload_offset).ok()?;
|
||||||
|
Some(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap_ipv4(
|
||||||
|
mut packet: TransmissionBytes,
|
||||||
|
src_node: Ipv4Addr,
|
||||||
|
net: &NetworkAddr,
|
||||||
|
) -> Option<TransmissionBytes> {
|
||||||
|
let ipv4 = pnet_packet::ipv4::Ipv4Packet::new(packet.as_ref())?;
|
||||||
|
let destination_ip = ipv4.get_destination();
|
||||||
|
let destination_mac = if destination_ip.is_broadcast() || destination_ip == net.broadcast {
|
||||||
|
[0xff; 6]
|
||||||
|
} else if destination_ip.is_multicast() {
|
||||||
|
let octets = destination_ip.octets();
|
||||||
|
[0x01, 0x00, 0x5e, octets[1] & 0x7f, octets[2], octets[3]]
|
||||||
|
} else {
|
||||||
|
mac_from_ip(net.ip)
|
||||||
|
};
|
||||||
|
packet.retreat_head(ETHERNET_HEADER_LEN).ok()?;
|
||||||
|
packet[0..6].copy_from_slice(&destination_mac);
|
||||||
|
packet[6..12].copy_from_slice(&mac_from_ip(src_node));
|
||||||
|
packet[12..14].copy_from_slice(ÐERTYPE_IPV4.to_be_bytes());
|
||||||
|
Some(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_broadcast_or_multicast(mac: [u8; 6]) -> bool {
|
||||||
|
mac == [0xff; 6] || mac[0] & 1 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn node_mac_round_trip() {
|
||||||
|
let ip = Ipv4Addr::new(10, 26, 1, 9);
|
||||||
|
assert_eq!(mac_from_ip(ip), [2, 0, 10, 26, 1, 9]);
|
||||||
|
assert_eq!(ip_from_mac(mac_from_ip(ip)), Some(ip));
|
||||||
|
assert_eq!(ip_from_mac([0; 6]), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_vlan_ipv4() {
|
||||||
|
let mut frame = vec![0u8; 18 + 20];
|
||||||
|
frame[12..14].copy_from_slice(ÐERTYPE_VLAN.to_be_bytes());
|
||||||
|
frame[16..18].copy_from_slice(ÐERTYPE_IPV4.to_be_bytes());
|
||||||
|
let info = parse_frame(&frame).unwrap();
|
||||||
|
assert_eq!(info.ethertype, ETHERTYPE_IPV4);
|
||||||
|
assert_eq!(info.payload_offset, 18);
|
||||||
|
assert!(parse_frame(&[0u8; 13]).is_none());
|
||||||
|
let mut truncated_vlan = vec![0u8; 16];
|
||||||
|
truncated_vlan[12..14].copy_from_slice(ÐERTYPE_VLAN.to_be_bytes());
|
||||||
|
assert!(parse_frame(&truncated_vlan).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_arp_reply_for_node_ip() {
|
||||||
|
let sender_ip = Ipv4Addr::new(10, 26, 0, 8);
|
||||||
|
let target_ip = Ipv4Addr::new(10, 26, 0, 9);
|
||||||
|
let sender_mac = mac_from_ip(sender_ip);
|
||||||
|
let mut request = vec![0u8; 42];
|
||||||
|
request[0..6].copy_from_slice(&[0xff; 6]);
|
||||||
|
request[6..12].copy_from_slice(&sender_mac);
|
||||||
|
request[12..14].copy_from_slice(ÐERTYPE_ARP.to_be_bytes());
|
||||||
|
request[14..16].copy_from_slice(&1u16.to_be_bytes());
|
||||||
|
request[16..18].copy_from_slice(ÐERTYPE_IPV4.to_be_bytes());
|
||||||
|
request[18] = 6;
|
||||||
|
request[19] = 4;
|
||||||
|
request[20..22].copy_from_slice(&1u16.to_be_bytes());
|
||||||
|
request[22..28].copy_from_slice(&sender_mac);
|
||||||
|
request[28..32].copy_from_slice(&sender_ip.octets());
|
||||||
|
request[38..42].copy_from_slice(&target_ip.octets());
|
||||||
|
|
||||||
|
let reply = build_arp_reply(&request, target_ip).unwrap();
|
||||||
|
let arp = parse_arp_ipv4(reply.as_ref()).unwrap();
|
||||||
|
assert_eq!(arp.operation, 2);
|
||||||
|
assert_eq!(arp.sender_ip, target_ip);
|
||||||
|
assert_eq!(arp.sender_mac, mac_from_ip(target_ip));
|
||||||
|
assert_eq!(arp.target_ip, sender_ip);
|
||||||
|
assert_eq!(arp.target_mac, sender_mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wraps_ipv4_for_tap_and_strips_it_again() {
|
||||||
|
let net = NetworkAddr {
|
||||||
|
gateway: Ipv4Addr::new(10, 26, 0, 1),
|
||||||
|
broadcast: Ipv4Addr::new(10, 26, 0, 255),
|
||||||
|
ip: Ipv4Addr::new(10, 26, 0, 9),
|
||||||
|
prefix_len: 24,
|
||||||
|
};
|
||||||
|
let src = Ipv4Addr::new(10, 26, 0, 8);
|
||||||
|
let mut packet = TransmissionBytes::with_capacity(HEAD_LENGTH, HEAD_LENGTH + 20);
|
||||||
|
packet.put(&[0u8; 20]).unwrap();
|
||||||
|
packet[0] = 0x45;
|
||||||
|
packet[12..16].copy_from_slice(&src.octets());
|
||||||
|
packet[16..20].copy_from_slice(&net.ip.octets());
|
||||||
|
let original = packet.as_ref().to_vec();
|
||||||
|
|
||||||
|
let frame = wrap_ipv4(packet, src, &net).unwrap();
|
||||||
|
let info = parse_frame(frame.as_ref()).unwrap();
|
||||||
|
assert_eq!(info.source, mac_from_ip(src));
|
||||||
|
assert_eq!(info.destination, mac_from_ip(net.ip));
|
||||||
|
assert_eq!(strip_ipv4(frame).unwrap().as_ref(), original);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -436,6 +436,18 @@ mod tests {
|
|||||||
shard
|
shard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_original_fec_packet_preserves_ethernet_flag() {
|
||||||
|
let decoder = FecDecoder::new();
|
||||||
|
let packets = decoder
|
||||||
|
.receive(build_data_packet(99, 0, 0x81, 0x10, &[1, 2, 3]))
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(packets.len(), 1);
|
||||||
|
assert!(packets[0].is_ethernet());
|
||||||
|
assert!(!packets[0].is_fec());
|
||||||
|
}
|
||||||
|
|
||||||
/// 变长批次:丢一个数据包,靠校验包必须能恢复(修复前必报 IncorrectShardSize)
|
/// 变长批次:丢一个数据包,靠校验包必须能恢复(修复前必报 IncorrectShardSize)
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reconstruct_variable_length_batch() {
|
fn test_reconstruct_variable_length_batch() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ pub(crate) mod compression;
|
|||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
pub(crate) mod ethernet;
|
||||||
pub(crate) mod fec;
|
pub(crate) mod fec;
|
||||||
pub mod nat;
|
pub mod nat;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
0 15 31
|
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
|
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
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| 1 | msg_type(7) |max ttl(4) |curr ttl(4)| C | G | R | reserve(13) |
|
| 1 | msg_type(7) |max ttl(4) |curr ttl(4)| C | G | F | E | reserve(12) |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| seq(32) |
|
| seq(32) |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -27,7 +27,7 @@ pub struct NetHeader {
|
|||||||
pub type_byte: u8,
|
pub type_byte: u8,
|
||||||
/// Byte 1: high 4 = max ttl, low 4 = curr ttl
|
/// Byte 1: high 4 = max ttl, low 4 = curr ttl
|
||||||
pub ttl_byte: u8,
|
pub ttl_byte: u8,
|
||||||
/// Byte 2: C(0x80) | G(0x40) | reserve
|
/// Byte 2: C(0x80) | G(0x40) | F(0x20) | ETHERNET(0x10) | reserve
|
||||||
pub flags_byte: u8,
|
pub flags_byte: u8,
|
||||||
/// Byte 3: reserve
|
/// Byte 3: reserve
|
||||||
pub _reserved: u8,
|
pub _reserved: u8,
|
||||||
@@ -39,6 +39,7 @@ pub struct NetHeader {
|
|||||||
const COMPRESSED: u8 = 0x80;
|
const COMPRESSED: u8 = 0x80;
|
||||||
const GATEWAY: u8 = 0x40;
|
const GATEWAY: u8 = 0x40;
|
||||||
const FEC: u8 = 0x20;
|
const FEC: u8 = 0x20;
|
||||||
|
const ETHERNET: u8 = 0x10;
|
||||||
impl NetHeader {
|
impl NetHeader {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn msg_type(&self) -> u8 {
|
pub fn msg_type(&self) -> u8 {
|
||||||
@@ -210,6 +211,9 @@ impl<B: AsRef<[u8]>> NetPacket<B> {
|
|||||||
pub fn is_fec(&self) -> bool {
|
pub fn is_fec(&self) -> bool {
|
||||||
(self.header().flags_byte & FEC) != 0
|
(self.header().flags_byte & FEC) != 0
|
||||||
}
|
}
|
||||||
|
pub fn is_ethernet(&self) -> bool {
|
||||||
|
(self.header().flags_byte & ETHERNET) != 0
|
||||||
|
}
|
||||||
pub fn head(&self) -> &[u8] {
|
pub fn head(&self) -> &[u8] {
|
||||||
&self.buffer.as_ref()[..HEAD_LENGTH]
|
&self.buffer.as_ref()[..HEAD_LENGTH]
|
||||||
}
|
}
|
||||||
@@ -258,6 +262,9 @@ impl<B: AsRef<[u8]> + AsMut<[u8]>> NetPacket<B> {
|
|||||||
pub fn set_fec_flag(&mut self, fec: bool) {
|
pub fn set_fec_flag(&mut self, fec: bool) {
|
||||||
self.header_mut().set_flag(FEC, fec);
|
self.header_mut().set_flag(FEC, fec);
|
||||||
}
|
}
|
||||||
|
pub fn set_ethernet_flag(&mut self, ethernet: bool) {
|
||||||
|
self.header_mut().set_flag(ETHERNET, ethernet);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_payload(&mut self, data: &[u8]) -> io::Result<()> {
|
pub fn set_payload(&mut self, data: &[u8]) -> io::Result<()> {
|
||||||
let buf = self.buffer.as_mut();
|
let buf = self.buffer.as_mut();
|
||||||
@@ -345,6 +352,19 @@ mod tests {
|
|||||||
assert!(MsgType::try_from(20u8).is_err());
|
assert!(MsgType::try_from(20u8).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ethernet_flag_round_trip() {
|
||||||
|
let mut packet = NetPacket::new(BytesMut::from(&[0u8; HEAD_LENGTH][..])).unwrap();
|
||||||
|
assert!(!packet.is_ethernet());
|
||||||
|
packet.set_ethernet_flag(true);
|
||||||
|
assert!(packet.is_ethernet());
|
||||||
|
packet.set_fec_flag(true);
|
||||||
|
assert!(packet.is_ethernet());
|
||||||
|
packet.set_ethernet_flag(false);
|
||||||
|
assert!(!packet.is_ethernet());
|
||||||
|
assert!(packet.is_fec());
|
||||||
|
}
|
||||||
|
|
||||||
/// 中继转发语义:包每经过一跳 curr_ttl 减 1,curr_ttl >= 1 时才继续转发,
|
/// 中继转发语义:包每经过一跳 curr_ttl 减 1,curr_ttl >= 1 时才继续转发,
|
||||||
/// 接收方以 metric = max_ttl - curr_ttl 计算路由距离。
|
/// 接收方以 metric = max_ttl - curr_ttl 计算路由距离。
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,18 +1,134 @@
|
|||||||
use crate::context::NetworkAddr;
|
use crate::context::NetworkAddr;
|
||||||
|
use crate::ethernet::strip_ipv4;
|
||||||
use crate::nat::internal_nat::InternalNatInbound;
|
use crate::nat::internal_nat::InternalNatInbound;
|
||||||
use crate::protocol::transmission::TransmissionBytes;
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
use crate::tun::TunDataInbound;
|
use crate::tun::TunDataInbound;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum EnhancedTunInbound {
|
pub enum EnhancedTunInbound {
|
||||||
Tun(TunDataInbound),
|
Tun(TunDataInbound),
|
||||||
|
Tap(TunDataInbound),
|
||||||
Nat(InternalNatInbound),
|
Nat(InternalNatInbound),
|
||||||
}
|
}
|
||||||
impl EnhancedTunInbound {
|
impl EnhancedTunInbound {
|
||||||
pub async fn inbound(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
|
pub async fn inbound(
|
||||||
|
&self,
|
||||||
|
data: TransmissionBytes,
|
||||||
|
net: &NetworkAddr,
|
||||||
|
src_node: Ipv4Addr,
|
||||||
|
ethernet: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
EnhancedTunInbound::Tun(tun) => tun.send(data, net).await,
|
EnhancedTunInbound::Tun(tun) => {
|
||||||
EnhancedTunInbound::Nat(nat) => nat.send(&data, net).await,
|
let data = if ethernet {
|
||||||
|
let Some(ip) = strip_ipv4(data) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
ip
|
||||||
|
} else {
|
||||||
|
data
|
||||||
|
};
|
||||||
|
tun.send_ip(data, net, src_node).await
|
||||||
|
}
|
||||||
|
EnhancedTunInbound::Tap(tap) => {
|
||||||
|
if ethernet {
|
||||||
|
tap.send_frame(data).await
|
||||||
|
} else {
|
||||||
|
tap.send_ip(data, net, src_node).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EnhancedTunInbound::Nat(nat) => {
|
||||||
|
let data = if ethernet {
|
||||||
|
let Some(ip) = strip_ipv4(data) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
ip
|
||||||
|
} else {
|
||||||
|
data
|
||||||
|
};
|
||||||
|
nat.send(&data, net).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::context::config::DeviceMode;
|
||||||
|
use crate::ethernet::{ETHERTYPE_IPV4, parse_frame, wrap_ipv4};
|
||||||
|
use crate::nat::AllowSubnetExternalRoute;
|
||||||
|
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
||||||
|
use crate::tun::{TunDataInbound, tun_channel};
|
||||||
|
|
||||||
|
fn network() -> NetworkAddr {
|
||||||
|
NetworkAddr {
|
||||||
|
gateway: Ipv4Addr::new(10, 26, 0, 1),
|
||||||
|
broadcast: Ipv4Addr::new(10, 26, 0, 255),
|
||||||
|
ip: Ipv4Addr::new(10, 26, 0, 9),
|
||||||
|
prefix_len: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ipv4(src: Ipv4Addr, dest: Ipv4Addr) -> TransmissionBytes {
|
||||||
|
let mut packet = TransmissionBytes::with_capacity(HEAD_LENGTH, HEAD_LENGTH + 20);
|
||||||
|
packet.put(&[0u8; 20]).unwrap();
|
||||||
|
packet[0] = 0x45;
|
||||||
|
packet[12..16].copy_from_slice(&src.octets());
|
||||||
|
packet[16..20].copy_from_slice(&dest.octets());
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tap_wraps_ip_and_tun_strips_ethernet() {
|
||||||
|
let net = network();
|
||||||
|
let src = Ipv4Addr::new(10, 26, 0, 8);
|
||||||
|
|
||||||
|
let (tap_tx, mut tap_rx) = tun_channel();
|
||||||
|
let tap = EnhancedTunInbound::Tap(TunDataInbound::new(
|
||||||
|
tap_tx,
|
||||||
|
AllowSubnetExternalRoute::new(vec![]),
|
||||||
|
DeviceMode::Tap,
|
||||||
|
));
|
||||||
|
tap.inbound(ipv4(src, net.ip), &net, src, false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let frame = tap_rx.receiver.recv().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_frame(frame.as_ref()).unwrap().ethertype,
|
||||||
|
ETHERTYPE_IPV4
|
||||||
|
);
|
||||||
|
|
||||||
|
let (tun_tx, mut tun_rx) = tun_channel();
|
||||||
|
let tun = EnhancedTunInbound::Tun(TunDataInbound::new(
|
||||||
|
tun_tx,
|
||||||
|
AllowSubnetExternalRoute::new(vec![]),
|
||||||
|
DeviceMode::Tun,
|
||||||
|
));
|
||||||
|
let frame = wrap_ipv4(ipv4(src, net.ip), src, &net).unwrap();
|
||||||
|
tun.inbound(frame, &net, src, true).await.unwrap();
|
||||||
|
let packet = tun_rx.receiver.recv().await.unwrap();
|
||||||
|
assert_eq!(packet[0] >> 4, 4);
|
||||||
|
assert_eq!(&packet[16..20], &net.ip.octets());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tap_keeps_arbitrary_ethernet_frame() {
|
||||||
|
let net = network();
|
||||||
|
let src = Ipv4Addr::new(10, 26, 0, 8);
|
||||||
|
let (tap_tx, mut tap_rx) = tun_channel();
|
||||||
|
let tap = EnhancedTunInbound::Tap(TunDataInbound::new(
|
||||||
|
tap_tx,
|
||||||
|
AllowSubnetExternalRoute::new(vec![]),
|
||||||
|
DeviceMode::Tap,
|
||||||
|
));
|
||||||
|
let mut raw = vec![0u8; 32];
|
||||||
|
raw[0..6].copy_from_slice(&[0xff; 6]);
|
||||||
|
raw[12..14].copy_from_slice(&0x88b5u16.to_be_bytes());
|
||||||
|
tap.inbound(raw.as_slice().into(), &net, src, true)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(tap_rx.receiver.recv().await.unwrap().as_ref(), raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+67
-20
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::context::config::DeviceMode;
|
||||||
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
||||||
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
||||||
use crate::protocol::transmission::TransmissionBytes;
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
@@ -10,9 +11,9 @@ use std::net::Ipv4Addr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tun_rs::AsyncDevice;
|
use tun_rs::AsyncDevice;
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
use tun_rs::DeviceBuilder;
|
|
||||||
use tun_rs::async_framed::{Decoder, DeviceFramedRead, DeviceFramedWrite, Encoder};
|
use tun_rs::async_framed::{Decoder, DeviceFramedRead, DeviceFramedWrite, Encoder};
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
use tun_rs::{DeviceBuilder, Layer};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DeviceIOManager {
|
pub struct DeviceIOManager {
|
||||||
@@ -27,10 +28,12 @@ pub struct DeviceTask {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DeviceConfig {
|
pub struct DeviceConfig {
|
||||||
|
pub device_mode: DeviceMode,
|
||||||
pub tun_name: Option<String>,
|
pub tun_name: Option<String>,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub tun_fd: Option<i32>,
|
pub tun_fd: Option<i32>,
|
||||||
pub mtu: Option<u16>,
|
pub mtu: Option<u16>,
|
||||||
|
pub mac_addr: Option<[u8; 6]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceConfig {
|
impl DeviceConfig {
|
||||||
@@ -47,6 +50,14 @@ impl DeviceConfig {
|
|||||||
self.mtu = Some(mtu);
|
self.mtu = Some(mtu);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn set_device_mode(mut self, device_mode: DeviceMode) -> Self {
|
||||||
|
self.device_mode = device_mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_mac_addr(mut self, mac_addr: [u8; 6]) -> Self {
|
||||||
|
self.mac_addr = Some(mac_addr);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TunInbound {
|
pub struct TunInbound {
|
||||||
@@ -54,7 +65,7 @@ pub struct TunInbound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct TunReceiver {
|
pub struct TunReceiver {
|
||||||
receiver: Receiver<TransmissionBytes>,
|
pub(crate) receiver: Receiver<TransmissionBytes>,
|
||||||
}
|
}
|
||||||
pub fn tun_channel() -> (TunInbound, TunReceiver) {
|
pub fn tun_channel() -> (TunInbound, TunReceiver) {
|
||||||
let (sender, receiver) = tokio::sync::mpsc::channel(1024);
|
let (sender, receiver) = tokio::sync::mpsc::channel(1024);
|
||||||
@@ -84,9 +95,10 @@ impl DeviceIOManager {
|
|||||||
bail!("device task already started");
|
bail!("device task already started");
|
||||||
}
|
}
|
||||||
self.stop_task().await;
|
self.stop_task().await;
|
||||||
// 先执行可能失败的 tun 设备创建,成功后才消费 receiver/outbound,
|
// 先执行可能失败的 TUN/TAP 设备创建,成功后才消费 receiver/outbound,
|
||||||
// 保证失败时调用方状态完整、可以重试
|
// 保证失败时调用方状态完整、可以重试
|
||||||
let device = Arc::new(create_tun(device_config)?);
|
let device_mode = device_config.device_mode;
|
||||||
|
let device = Arc::new(create_device(device_config)?);
|
||||||
let receiver = receiver.take().unwrap();
|
let receiver = receiver.take().unwrap();
|
||||||
let enhanced_outbound = enhanced_outbound.take().unwrap();
|
let enhanced_outbound = enhanced_outbound.take().unwrap();
|
||||||
let task = create(
|
let task = create(
|
||||||
@@ -94,12 +106,13 @@ impl DeviceIOManager {
|
|||||||
device,
|
device,
|
||||||
receiver.receiver,
|
receiver.receiver,
|
||||||
enhanced_outbound,
|
enhanced_outbound,
|
||||||
|
device_mode,
|
||||||
);
|
);
|
||||||
self.device.lock().await.0.replace(task);
|
self.device.lock().await.0.replace(task);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
|
pub async fn device_if_index(&self) -> anyhow::Result<u32> {
|
||||||
let guard = self.device.lock().await;
|
let guard = self.device.lock().await;
|
||||||
if let Some(v) = &guard.0 {
|
if let Some(v) = &guard.0 {
|
||||||
Ok(v.device.if_index()?)
|
Ok(v.device.if_index()?)
|
||||||
@@ -111,7 +124,7 @@ impl DeviceIOManager {
|
|||||||
pub async fn set_network(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
pub async fn set_network(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
||||||
let mut guard = self.device.lock().await;
|
let mut guard = self.device.lock().await;
|
||||||
let Some(dev) = guard.0.as_ref() else {
|
let Some(dev) = guard.0.as_ref() else {
|
||||||
bail!("未启动tun")
|
bail!("虚拟网卡尚未启动")
|
||||||
};
|
};
|
||||||
if let Some(v) = guard.1.as_ref()
|
if let Some(v) = guard.1.as_ref()
|
||||||
&& v.0 == ip
|
&& v.0 == ip
|
||||||
@@ -127,7 +140,7 @@ impl DeviceIOManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
fn create_device(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
let fd = config
|
let fd = config
|
||||||
@@ -146,21 +159,45 @@ fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
|||||||
unsafe { return Ok(AsyncDevice::from_fd(fd)?) }
|
unsafe { return Ok(AsyncDevice::from_fd(fd)?) }
|
||||||
}
|
}
|
||||||
let mut builder = DeviceBuilder::new();
|
let mut builder = DeviceBuilder::new();
|
||||||
|
builder = builder.layer(match config.device_mode {
|
||||||
|
DeviceMode::Tap => Layer::L2,
|
||||||
|
DeviceMode::Tun => Layer::L3,
|
||||||
|
DeviceMode::No => bail!("cannot create a device in no mode"),
|
||||||
|
});
|
||||||
if let Some(tun_name) = config.tun_name {
|
if let Some(tun_name) = config.tun_name {
|
||||||
builder = builder.name(tun_name);
|
builder = builder.name(tun_name);
|
||||||
}
|
}
|
||||||
if let Some(mtu) = config.mtu {
|
if let Some(mtu) = config.mtu {
|
||||||
builder = builder.mtu(mtu);
|
builder = builder.mtu(mtu);
|
||||||
}
|
}
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "windows",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
if let Some(mac_addr) = config.mac_addr {
|
||||||
|
builder = builder.mac_addr(mac_addr);
|
||||||
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
builder = builder.metric(1);
|
builder = builder.metric(1);
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
if config.device_mode == DeviceMode::Tun {
|
||||||
builder = builder.offload(true);
|
builder = builder.offload(true);
|
||||||
}
|
}
|
||||||
let dev = builder.build_async().context("创建tun失败")?;
|
let dev = builder.build_async().with_context(|| {
|
||||||
|
if config.device_mode == DeviceMode::Tap && cfg!(windows) {
|
||||||
|
"创建 TAP 失败;Windows TAP 模式需要预先安装 tap-windows (tap0901) 驱动"
|
||||||
|
} else if config.device_mode == DeviceMode::Tap {
|
||||||
|
"创建 TAP 失败"
|
||||||
|
} else {
|
||||||
|
"创建 TUN 失败"
|
||||||
|
}
|
||||||
|
})?;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
_ = dev.set_tx_queue_len(1000);
|
_ = dev.set_tx_queue_len(1000);
|
||||||
@@ -173,26 +210,28 @@ fn create(
|
|||||||
device: Arc<AsyncDevice>,
|
device: Arc<AsyncDevice>,
|
||||||
receiver: Receiver<TransmissionBytes>,
|
receiver: Receiver<TransmissionBytes>,
|
||||||
enhanced_outbound: EnhancedOutbound,
|
enhanced_outbound: EnhancedOutbound,
|
||||||
|
device_mode: DeviceMode,
|
||||||
) -> DeviceTask {
|
) -> DeviceTask {
|
||||||
let device_framed_read = DeviceFramedRead::new(device.clone(), BytesCodec::new());
|
let device_framed_read = DeviceFramedRead::new(device.clone(), BytesCodec::new());
|
||||||
let device_framed_write = DeviceFramedWrite::new(device.clone(), BytesCodec::new());
|
let device_framed_write = DeviceFramedWrite::new(device.clone(), BytesCodec::new());
|
||||||
|
let outbound_device = device.clone();
|
||||||
|
|
||||||
// 读写两个方向合并为一个任务:任一方向结束(出错或设备关闭)即
|
// 读写两个方向合并为一个任务:任一方向结束(出错或设备关闭)即
|
||||||
// 通过 select! 取消另一方向,避免单侧失败后另一侧继续运行的半开状态
|
// 通过 select! 取消另一方向,避免单侧失败后另一侧继续运行的半开状态
|
||||||
let task = task_group.spawn(async move {
|
let task = task_group.spawn(async move {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
rs = in_tun_loop(receiver, device_framed_write) => {
|
rs = in_device_loop(receiver, device_framed_write) => {
|
||||||
if let Err(e) = rs {
|
if let Err(e) = rs {
|
||||||
log::error!("in_tun_loop error, stopping out_tun_loop: {e:?}");
|
log::error!("in_device_loop error, stopping out_device_loop: {e:?}");
|
||||||
} else {
|
} else {
|
||||||
log::warn!("in_tun_loop exited, stopping out_tun_loop");
|
log::warn!("in_device_loop exited, stopping out_device_loop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rs = out_tun_loop(device_framed_read, enhanced_outbound) => {
|
rs = out_device_loop(device_framed_read, outbound_device, enhanced_outbound, device_mode) => {
|
||||||
if let Err(e) = rs {
|
if let Err(e) = rs {
|
||||||
log::error!("out_tun_loop error, stopping in_tun_loop: {e:?}");
|
log::error!("out_device_loop error, stopping in_device_loop: {e:?}");
|
||||||
} else {
|
} else {
|
||||||
log::warn!("out_tun_loop exited, stopping in_tun_loop");
|
log::warn!("out_device_loop exited, stopping in_device_loop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +240,7 @@ fn create(
|
|||||||
DeviceTask { device, task }
|
DeviceTask { device, task }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn in_tun_loop(
|
async fn in_device_loop(
|
||||||
mut receiver: Receiver<TransmissionBytes>,
|
mut receiver: Receiver<TransmissionBytes>,
|
||||||
mut device_framed_write: DeviceFramedWrite<BytesCodec, Arc<AsyncDevice>>,
|
mut device_framed_write: DeviceFramedWrite<BytesCodec, Arc<AsyncDevice>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@@ -209,7 +248,7 @@ async fn in_tun_loop(
|
|||||||
match device_framed_write.send(data).await {
|
match device_framed_write.send(data).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("send to tun error: {:?}", e);
|
log::error!("send to virtual device error: {:?}", e);
|
||||||
return Err(anyhow::anyhow!(e));
|
return Err(anyhow::anyhow!(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,13 +256,21 @@ async fn in_tun_loop(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn out_tun_loop(
|
async fn out_device_loop(
|
||||||
mut device_framed_read: DeviceFramedRead<BytesCodec, Arc<AsyncDevice>>,
|
mut device_framed_read: DeviceFramedRead<BytesCodec, Arc<AsyncDevice>>,
|
||||||
|
device: Arc<AsyncDevice>,
|
||||||
enhanced_outbound: EnhancedOutbound,
|
enhanced_outbound: EnhancedOutbound,
|
||||||
|
device_mode: DeviceMode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
while let Some(rs) = device_framed_read.next().await {
|
while let Some(rs) = device_framed_read.next().await {
|
||||||
let bytes_mut = rs?;
|
let bytes_mut = rs?;
|
||||||
enhanced_outbound.ipv4_outbound(bytes_mut).await;
|
if device_mode == DeviceMode::Tap {
|
||||||
|
if let Some(reply) = enhanced_outbound.ethernet_outbound(bytes_mut).await {
|
||||||
|
device.send(reply.as_ref()).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enhanced_outbound.ipv4_outbound(bytes_mut).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,39 @@
|
|||||||
use crate::context::NetworkAddr;
|
use crate::context::NetworkAddr;
|
||||||
|
use crate::context::config::DeviceMode;
|
||||||
|
use crate::ethernet::wrap_ipv4;
|
||||||
use crate::nat::AllowSubnetExternalRoute;
|
use crate::nat::AllowSubnetExternalRoute;
|
||||||
use crate::protocol::transmission::TransmissionBytes;
|
use crate::protocol::transmission::TransmissionBytes;
|
||||||
use crate::tun::TunInbound;
|
use crate::tun::TunInbound;
|
||||||
use pnet_packet::ipv4::Ipv4Packet;
|
use pnet_packet::ipv4::Ipv4Packet;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TunDataInbound {
|
pub struct TunDataInbound {
|
||||||
allow_subnet: AllowSubnetExternalRoute,
|
allow_subnet: AllowSubnetExternalRoute,
|
||||||
tun_inbound: TunInbound,
|
tun_inbound: TunInbound,
|
||||||
|
device_mode: DeviceMode,
|
||||||
}
|
}
|
||||||
impl TunDataInbound {
|
impl TunDataInbound {
|
||||||
pub fn new(tun_inbound: TunInbound, allow_subnet: AllowSubnetExternalRoute) -> Self {
|
pub fn new(
|
||||||
|
tun_inbound: TunInbound,
|
||||||
|
allow_subnet: AllowSubnetExternalRoute,
|
||||||
|
device_mode: DeviceMode,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
allow_subnet,
|
allow_subnet,
|
||||||
tun_inbound,
|
tun_inbound,
|
||||||
|
device_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunDataInbound {
|
impl TunDataInbound {
|
||||||
pub async fn send(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
|
pub async fn send_ip(
|
||||||
|
&self,
|
||||||
|
data: TransmissionBytes,
|
||||||
|
net: &NetworkAddr,
|
||||||
|
src_node: Ipv4Addr,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
if data.is_empty() || data[0] >> 4 != 4 {
|
if data.is_empty() || data[0] >> 4 != 4 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -33,10 +47,23 @@ impl TunDataInbound {
|
|||||||
|| dest.is_multicast()
|
|| dest.is_multicast()
|
||||||
|| self.allow_subnet.allow(&dest)
|
|| self.allow_subnet.allow(&dest)
|
||||||
{
|
{
|
||||||
|
let data = if self.device_mode == DeviceMode::Tap {
|
||||||
|
let Some(frame) = wrap_ipv4(data, src_node, net) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
frame
|
||||||
|
} else {
|
||||||
|
data
|
||||||
|
};
|
||||||
self.tun_inbound.sender.send(data).await?;
|
self.tun_inbound.sender.send(data).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_frame(&self, data: TransmissionBytes) -> anyhow::Result<()> {
|
||||||
|
self.tun_inbound.sender.send(data).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -44,7 +71,6 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::nat::AllowSubnetExternalRoute;
|
use crate::nat::AllowSubnetExternalRoute;
|
||||||
use crate::tun::tun_channel;
|
use crate::tun::tun_channel;
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
|
|
||||||
fn test_net() -> NetworkAddr {
|
fn test_net() -> NetworkAddr {
|
||||||
NetworkAddr {
|
NetworkAddr {
|
||||||
@@ -59,17 +85,29 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_send_empty_or_short_packet_does_not_panic() {
|
async fn test_send_empty_or_short_packet_does_not_panic() {
|
||||||
let (tun_inbound, _receiver) = tun_channel();
|
let (tun_inbound, _receiver) = tun_channel();
|
||||||
let inbound = TunDataInbound::new(tun_inbound, AllowSubnetExternalRoute::new(vec![]));
|
let inbound = TunDataInbound::new(
|
||||||
|
tun_inbound,
|
||||||
|
AllowSubnetExternalRoute::new(vec![]),
|
||||||
|
DeviceMode::Tun,
|
||||||
|
);
|
||||||
|
|
||||||
// 零载荷包(头部被剥离后为空)
|
// 零载荷包(头部被剥离后为空)
|
||||||
inbound
|
inbound
|
||||||
.send(TransmissionBytes::zeroed(0), &test_net())
|
.send_ip(
|
||||||
|
TransmissionBytes::zeroed(0),
|
||||||
|
&test_net(),
|
||||||
|
Ipv4Addr::new(10, 26, 0, 3),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// 过短的包(不足 IPv4 头)
|
// 过短的包(不足 IPv4 头)
|
||||||
inbound
|
inbound
|
||||||
.send(TransmissionBytes::zeroed(3), &test_net())
|
.send_ip(
|
||||||
|
TransmissionBytes::zeroed(3),
|
||||||
|
&test_net(),
|
||||||
|
Ipv4Addr::new(10, 26, 0, 3),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,6 +234,60 @@ impl HybridOutbound {
|
|||||||
self.traffic_stats.record_tx(dest, len);
|
self.traffic_stats.record_tx(dest, len);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn ethernet_ipv4_outbound(
|
||||||
|
&self,
|
||||||
|
net: NetworkAddr,
|
||||||
|
data: TransmissionBytes,
|
||||||
|
mut dest: Ipv4Addr,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if dest == net.gateway {
|
||||||
|
let Some(ip) = crate::ethernet::strip_ipv4(data) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
return self.ipv4_gateway_outbound(net, ip).await;
|
||||||
|
}
|
||||||
|
if dest.is_multicast() || dest == net.broadcast || dest.is_broadcast() {
|
||||||
|
return self.ethernet_broadcast_outbound(net, data).await;
|
||||||
|
}
|
||||||
|
if !net.network().contains(&dest) {
|
||||||
|
if let Some(route) = self.external_route.route(&dest) {
|
||||||
|
dest = route;
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ethernet_unicast_outbound(net, dest, data).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ethernet_unicast_outbound(
|
||||||
|
&self,
|
||||||
|
net: NetworkAddr,
|
||||||
|
dest: Ipv4Addr,
|
||||||
|
mut data: TransmissionBytes,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let len = data.len() as u64;
|
||||||
|
data.retreat_head(HEAD_LENGTH)?;
|
||||||
|
let mut packet = NetPacket::new(data)?;
|
||||||
|
packet.set_msg_type(MsgType::Turn);
|
||||||
|
packet.set_src_id(net.ip.into());
|
||||||
|
packet.set_dest_id(dest.into());
|
||||||
|
packet.set_ttl(5);
|
||||||
|
packet.set_ethernet_flag(true);
|
||||||
|
let packet = self
|
||||||
|
.packet_compression
|
||||||
|
.compress(packet, self.basic_outbound.encrypt_reserve())?;
|
||||||
|
let packet = if let Some(fec_encoder) = &self.fec_encoder {
|
||||||
|
fec_encoder.encode(packet)?
|
||||||
|
} else {
|
||||||
|
packet
|
||||||
|
};
|
||||||
|
self.basic_outbound
|
||||||
|
.send_encrypted_packet(dest, packet)
|
||||||
|
.await?;
|
||||||
|
self.traffic_stats.record_tx(dest, len);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub async fn ipv4_gateway_outbound(
|
pub async fn ipv4_gateway_outbound(
|
||||||
&self,
|
&self,
|
||||||
net: NetworkAddr,
|
net: NetworkAddr,
|
||||||
@@ -279,6 +333,37 @@ impl HybridOutbound {
|
|||||||
.send_raw_broadcast(exclude_ips, packet_bytes)
|
.send_raw_broadcast(exclude_ips, packet_bytes)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn ethernet_broadcast_outbound(
|
||||||
|
&self,
|
||||||
|
net: NetworkAddr,
|
||||||
|
mut data: TransmissionBytes,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
data.retreat_head(HEAD_LENGTH)?;
|
||||||
|
let mut packet = NetPacket::new(data)?;
|
||||||
|
packet.set_msg_type(MsgType::Broadcast);
|
||||||
|
packet.set_src_id(net.ip.into());
|
||||||
|
packet.set_dest_id(Ipv4Addr::BROADCAST.into());
|
||||||
|
packet.set_ttl(5);
|
||||||
|
packet.set_ethernet_flag(true);
|
||||||
|
let mut packet = self
|
||||||
|
.packet_compression
|
||||||
|
.compress(packet, self.basic_outbound.encrypt_reserve())?;
|
||||||
|
self.basic_outbound.encrypt_in_place(&mut packet)?;
|
||||||
|
let packet_bytes = packet.into_bytes();
|
||||||
|
let list = self.server_info.client_online_ips();
|
||||||
|
let exclude_ips = self
|
||||||
|
.basic_outbound
|
||||||
|
.p2p_broadcast_transmission(&list, 16, &packet_bytes);
|
||||||
|
if let Some(exclude_ips) = &exclude_ips
|
||||||
|
&& exclude_ips.len() == list.len()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.basic_outbound
|
||||||
|
.send_raw_broadcast(exclude_ips, packet_bytes)
|
||||||
|
.await
|
||||||
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn has_route(&self, dest: &Ipv4Addr) -> bool {
|
pub fn has_route(&self, dest: &Ipv4Addr) -> bool {
|
||||||
self.basic_outbound.exists_route(dest)
|
self.basic_outbound.exists_route(dest)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
|
|||||||
pnpm build:desktop
|
pnpm build:desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
Windows 使用虚拟网卡模式时,可能需要以管理员身份运行。
|
Windows 使用虚拟网卡模式时,可能需要以管理员身份运行;TAP(二层)模式还需要预先安装 `tap-windows`(`tap0901`)驱动,内置的 `wintun.dll` 只用于 TUN(三层)模式。
|
||||||
|
|
||||||
### 发布桌面更新
|
### 发布桌面更新
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class VntConfig {
|
|||||||
private final boolean rtx;
|
private final boolean rtx;
|
||||||
private final boolean fec;
|
private final boolean fec;
|
||||||
private final boolean noNat;
|
private final boolean noNat;
|
||||||
private final boolean noTun;
|
private final String deviceMode;
|
||||||
private final Integer mtu;
|
private final Integer mtu;
|
||||||
private final boolean allowMapping;
|
private final boolean allowMapping;
|
||||||
private final List<String> portMapping;
|
private final List<String> portMapping;
|
||||||
@@ -48,7 +48,7 @@ public class VntConfig {
|
|||||||
this.rtx = builder.rtx;
|
this.rtx = builder.rtx;
|
||||||
this.fec = builder.fec;
|
this.fec = builder.fec;
|
||||||
this.noNat = builder.noNat;
|
this.noNat = builder.noNat;
|
||||||
this.noTun = builder.noTun;
|
this.deviceMode = builder.deviceMode;
|
||||||
this.mtu = builder.mtu;
|
this.mtu = builder.mtu;
|
||||||
this.allowMapping = builder.allowMapping;
|
this.allowMapping = builder.allowMapping;
|
||||||
this.portMapping = builder.portMapping;
|
this.portMapping = builder.portMapping;
|
||||||
@@ -86,7 +86,7 @@ public class VntConfig {
|
|||||||
json.put("rtx", rtx);
|
json.put("rtx", rtx);
|
||||||
json.put("fec", fec);
|
json.put("fec", fec);
|
||||||
json.put("no_nat", noNat);
|
json.put("no_nat", noNat);
|
||||||
json.put("no_tun", noTun);
|
json.put("device_mode", deviceMode);
|
||||||
json.put("allow_mapping", allowMapping);
|
json.put("allow_mapping", allowMapping);
|
||||||
|
|
||||||
// 数组
|
// 数组
|
||||||
@@ -135,7 +135,7 @@ public class VntConfig {
|
|||||||
private boolean rtx = false;
|
private boolean rtx = false;
|
||||||
private boolean fec = false;
|
private boolean fec = false;
|
||||||
private boolean noNat = false;
|
private boolean noNat = false;
|
||||||
private boolean noTun = false;
|
private String deviceMode = "tun";
|
||||||
private Integer mtu;
|
private Integer mtu;
|
||||||
private boolean allowMapping = false;
|
private boolean allowMapping = false;
|
||||||
private List<String> portMapping = new ArrayList<>();
|
private List<String> portMapping = new ArrayList<>();
|
||||||
@@ -258,10 +258,13 @@ public class VntConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无TUN模式(默认false)
|
* 设置虚拟网卡模式:no、tun(默认)或 tap。
|
||||||
*/
|
*/
|
||||||
public Builder setNoTun(boolean noTun) {
|
public Builder setDeviceMode(String deviceMode) {
|
||||||
this.noTun = noTun;
|
if (!"no".equals(deviceMode) && !"tun".equals(deviceMode) && !"tap".equals(deviceMode)) {
|
||||||
|
throw new IllegalArgumentException("deviceMode must be no, tun, or tap");
|
||||||
|
}
|
||||||
|
this.deviceMode = deviceMode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-10
@@ -8,7 +8,7 @@ use std::net::Ipv4Addr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use vnt_core::api::VntApi;
|
use vnt_core::api::VntApi;
|
||||||
use vnt_core::context::config::Config;
|
use vnt_core::context::config::{Config, DeviceMode};
|
||||||
use vnt_core::core::{NetworkManager, RegisterResponse};
|
use vnt_core::core::{NetworkManager, RegisterResponse};
|
||||||
use vnt_core::nat::NetInput;
|
use vnt_core::nat::NetInput;
|
||||||
use vnt_core::port_mapping::PortMapping;
|
use vnt_core::port_mapping::PortMapping;
|
||||||
@@ -269,13 +269,13 @@ pub extern "system" fn Java_com_vnt_VntNetwork_nativeStartTun(
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let tun_fd = if tun_fd < 0 { None } else { Some(tun_fd) };
|
let tun_fd = if tun_fd < 0 { None } else { Some(tun_fd) };
|
||||||
runtime.block_on(async { manager.start_tun_fd(tun_fd).await })?;
|
runtime.block_on(async { manager.start_device_fd(tun_fd).await })?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
let _ = tun_fd; // 避免未使用警告
|
let _ = tun_fd; // 避免未使用警告
|
||||||
runtime.block_on(async { manager.start_tun().await })?;
|
runtime.block_on(async { manager.start_device().await })?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -331,7 +331,9 @@ pub extern "system" fn Java_com_vnt_VntNetwork_nativeSetNetworkIp<'local>(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.context("Network manager already destroyed")?;
|
.context("Network manager already destroyed")?;
|
||||||
runtime.block_on(async {
|
runtime.block_on(async {
|
||||||
manager.set_tun_network_ip(ip_addr, prefix_len as u8).await
|
manager
|
||||||
|
.set_device_network_ip(ip_addr, prefix_len as u8)
|
||||||
|
.await
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})();
|
})();
|
||||||
@@ -410,19 +412,19 @@ pub extern "system" fn Java_com_vnt_VntNetwork_nativeIsNoTun(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.context("Network manager already destroyed")?;
|
.context("Network manager already destroyed")?;
|
||||||
|
|
||||||
Ok(manager.is_no_tun())
|
Ok(manager.device_mode() == DeviceMode::No)
|
||||||
})();
|
})();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(is_no_tun) => {
|
Ok(is_no_device) => {
|
||||||
if is_no_tun {
|
if is_no_device {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = env.throw(format!("Failed to check no_tun: {:?}", e));
|
let _ = env.throw(format!("Failed to check device mode: {:?}", e));
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,7 +916,9 @@ fn parse_config_from_json(json_str: &str) -> anyhow::Result<Config> {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
no_nat: bool,
|
no_nat: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
no_tun: bool,
|
device_mode: DeviceMode,
|
||||||
|
#[serde(default, rename = "no_tun")]
|
||||||
|
legacy_no_tun: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
mtu: Option<u16>,
|
mtu: Option<u16>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -930,6 +934,9 @@ fn parse_config_from_json(json_str: &str) -> anyhow::Result<Config> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cfg: ConfigJson = serde_json::from_str(json_str)?;
|
let cfg: ConfigJson = serde_json::from_str(json_str)?;
|
||||||
|
if cfg.legacy_no_tun.is_some() {
|
||||||
|
anyhow::bail!("configuration key 'no_tun' was removed; use device_mode = \"no|tun|tap\"");
|
||||||
|
}
|
||||||
|
|
||||||
let server_addrs: Vec<ProtocolAddress> = cfg
|
let server_addrs: Vec<ProtocolAddress> = cfg
|
||||||
.server
|
.server
|
||||||
@@ -999,7 +1006,7 @@ fn parse_config_from_json(json_str: &str) -> anyhow::Result<Config> {
|
|||||||
input: cfg.input,
|
input: cfg.input,
|
||||||
output: cfg.output,
|
output: cfg.output,
|
||||||
no_nat: cfg.no_nat,
|
no_nat: cfg.no_nat,
|
||||||
no_tun: cfg.no_tun,
|
device_mode: cfg.device_mode,
|
||||||
mtu: cfg.mtu,
|
mtu: cfg.mtu,
|
||||||
port_mapping,
|
port_mapping,
|
||||||
allow_port_mapping: cfg.allow_mapping,
|
allow_port_mapping: cfg.allow_mapping,
|
||||||
|
|||||||
+26
-7
@@ -18,9 +18,18 @@ fn main() {
|
|||||||
|
|
||||||
// UI 源码变化时重新运行本脚本
|
// UI 源码变化时重新运行本脚本
|
||||||
println!("cargo:rerun-if-changed={}", ui_dir.join("src").display());
|
println!("cargo:rerun-if-changed={}", ui_dir.join("src").display());
|
||||||
println!("cargo:rerun-if-changed={}", ui_dir.join("index.html").display());
|
println!(
|
||||||
println!("cargo:rerun-if-changed={}", ui_dir.join("vite.config.js").display());
|
"cargo:rerun-if-changed={}",
|
||||||
println!("cargo:rerun-if-changed={}", ui_dir.join("package.json").display());
|
ui_dir.join("index.html").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"cargo:rerun-if-changed={}",
|
||||||
|
ui_dir.join("vite.config.js").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"cargo:rerun-if-changed={}",
|
||||||
|
ui_dir.join("package.json").display()
|
||||||
|
);
|
||||||
// 产物被删除时也要重新运行
|
// 产物被删除时也要重新运行
|
||||||
println!(
|
println!(
|
||||||
"cargo:rerun-if-changed={}",
|
"cargo:rerun-if-changed={}",
|
||||||
@@ -53,9 +62,9 @@ fn main() {
|
|||||||
|
|
||||||
if !ui_dir.join("node_modules").is_dir() {
|
if !ui_dir.join("node_modules").is_dir() {
|
||||||
// ui 依赖使用 workspace catalog,必须在仓库根目录安装
|
// ui 依赖使用 workspace catalog,必须在仓库根目录安装
|
||||||
run_or_panic(&pnpm, &["install", "--frozen-lockfile"], workspace_root);
|
run_or_panic(pnpm, &["install", "--frozen-lockfile"], workspace_root);
|
||||||
}
|
}
|
||||||
run_or_panic(&pnpm, &["--filter", "vnt-web-ui", "build"], workspace_root);
|
run_or_panic(pnpm, &["--filter", "vnt-web-ui", "build"], workspace_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// pnpm 命令名(Windows 上是 pnpm.cmd,由 cmd.exe 执行)
|
/// pnpm 命令名(Windows 上是 pnpm.cmd,由 cmd.exe 执行)
|
||||||
@@ -72,14 +81,24 @@ fn find_pnpm() -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_or_panic(program: &str, args: &[&str], dir: &Path) {
|
fn run_or_panic(program: &str, args: &[&str], dir: &Path) {
|
||||||
println!("cargo:warning=执行前端构建: {} {} ({})", program, args.join(" "), dir.display());
|
println!(
|
||||||
|
"cargo:warning=执行前端构建: {} {} ({})",
|
||||||
|
program,
|
||||||
|
args.join(" "),
|
||||||
|
dir.display()
|
||||||
|
);
|
||||||
let status = Command::new(program)
|
let status = Command::new(program)
|
||||||
.args(args)
|
.args(args)
|
||||||
.current_dir(dir)
|
.current_dir(dir)
|
||||||
.status()
|
.status()
|
||||||
.unwrap_or_else(|e| panic!("执行 {} 失败: {}", program, e));
|
.unwrap_or_else(|e| panic!("执行 {} 失败: {}", program, e));
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
panic!("前端构建失败: {} {} (exit: {:?})", program, args.join(" "), status.code());
|
panic!(
|
||||||
|
"前端构建失败: {} {} (exit: {:?})",
|
||||||
|
program,
|
||||||
|
args.join(" "),
|
||||||
|
status.code()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-12
@@ -28,7 +28,7 @@ use tokio_util::sync::CancellationToken;
|
|||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use vnt_core::api::VntApi;
|
use vnt_core::api::VntApi;
|
||||||
use vnt_core::context::config::Config as CoreConfig;
|
use vnt_core::context::config::{Config as CoreConfig, DeviceMode};
|
||||||
use vnt_core::core::{DEFAULT_MTU, NetworkManager, RegisterResponse};
|
use vnt_core::core::{DEFAULT_MTU, NetworkManager, RegisterResponse};
|
||||||
use vnt_core::nat::NetInput;
|
use vnt_core::nat::NetInput;
|
||||||
use vnt_core::port_mapping::PortMapping;
|
use vnt_core::port_mapping::PortMapping;
|
||||||
@@ -271,7 +271,9 @@ pub struct StartConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub no_nat: bool,
|
pub no_nat: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub no_tun: bool,
|
pub device_mode: DeviceMode,
|
||||||
|
#[serde(default, rename = "no_tun", skip_serializing)]
|
||||||
|
pub legacy_no_tun: Option<bool>,
|
||||||
pub mtu: Option<u16>,
|
pub mtu: Option<u16>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub port_mapping: Vec<String>,
|
pub port_mapping: Vec<String>,
|
||||||
@@ -284,6 +286,15 @@ pub struct StartConfig {
|
|||||||
pub tunnel_port: Option<u16>,
|
pub tunnel_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StartConfig {
|
||||||
|
fn reject_legacy_no_tun(&self) -> anyhow::Result<()> {
|
||||||
|
if self.legacy_no_tun.is_some() {
|
||||||
|
bail!("configuration key 'no_tun' was removed; use device_mode = \"no|tun|tap\"")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SaveConfigReq {
|
struct SaveConfigReq {
|
||||||
file_name: Option<String>,
|
file_name: Option<String>,
|
||||||
@@ -982,19 +993,20 @@ async fn start_vnt_network(
|
|||||||
format!("注册成功 {}/{}", reg_msg.ip, reg_msg.prefix_len),
|
format!("注册成功 {}/{}", reg_msg.ip, reg_msg.prefix_len),
|
||||||
);
|
);
|
||||||
log::info!("Network Started: {}/{}", reg_msg.ip, reg_msg.prefix_len);
|
log::info!("Network Started: {}/{}", reg_msg.ip, reg_msg.prefix_len);
|
||||||
if !network_manager.is_no_tun() {
|
if network_manager.device_mode().has_device() {
|
||||||
state.record_log(&file_name, "正在创建 TUN 虚拟网卡");
|
let mode = network_manager.device_mode();
|
||||||
network_manager.start_tun().await?;
|
state.record_log(&file_name, format!("正在创建 {} 虚拟网卡", mode));
|
||||||
|
network_manager.start_device().await?;
|
||||||
|
|
||||||
state.record_log(&file_name, "创建 TUN 虚拟网卡成功,设置 IP");
|
state.record_log(&file_name, format!("创建 {} 虚拟网卡成功,设置 IP", mode));
|
||||||
network_manager
|
network_manager
|
||||||
.set_tun_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
.set_device_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
||||||
.await?;
|
.await?;
|
||||||
state.record_log(&file_name, "设置 IP 成功");
|
state.record_log(&file_name, "设置 IP 成功");
|
||||||
|
|
||||||
// 配置子网路由
|
// 配置子网路由
|
||||||
if !sub_input.is_empty()
|
if !sub_input.is_empty()
|
||||||
&& let Ok(if_index) = network_manager.tun_if_index().await
|
&& let Ok(if_index) = network_manager.device_if_index().await
|
||||||
&& let Ok(mut route_manager) = route_manager::RouteManager::new()
|
&& let Ok(mut route_manager) = route_manager::RouteManager::new()
|
||||||
{
|
{
|
||||||
state.record_log(&file_name, "配置子网路由");
|
state.record_log(&file_name, "配置子网路由");
|
||||||
@@ -1011,6 +1023,8 @@ async fn start_vnt_network(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
state.record_log(&file_name, "device_mode=no,不创建虚拟网卡");
|
||||||
}
|
}
|
||||||
|
|
||||||
state.starting_to_running(&file_name);
|
state.starting_to_running(&file_name);
|
||||||
@@ -1278,7 +1292,13 @@ async fn list_configs() -> Json<ApiResponse<Vec<ConfigSummary>>> {
|
|||||||
|
|
||||||
async fn save_config(Json(req): Json<SaveConfigReq>) -> Json<ApiResponse<()>> {
|
async fn save_config(Json(req): Json<SaveConfigReq>) -> Json<ApiResponse<()>> {
|
||||||
// 验证配置格式
|
// 验证配置格式
|
||||||
if let Err(e) = toml::from_str::<StartConfig>(&req.config) {
|
let parsed = toml::from_str::<StartConfig>(&req.config).and_then(|config| {
|
||||||
|
config
|
||||||
|
.reject_legacy_no_tun()
|
||||||
|
.map(|_| config)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
});
|
||||||
|
if let Err(e) = parsed {
|
||||||
log::warn!("Failed to parse configuration: {:?}", e);
|
log::warn!("Failed to parse configuration: {:?}", e);
|
||||||
return Json(ApiResponse::error(format!("Invalid TOML format: {}", e)));
|
return Json(ApiResponse::error(format!("Invalid TOML format: {}", e)));
|
||||||
}
|
}
|
||||||
@@ -1358,6 +1378,7 @@ async fn delete_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
|
fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
|
||||||
|
cfg.reject_legacy_no_tun()?;
|
||||||
let server_addrs: Vec<ProtocolAddress> = cfg
|
let server_addrs: Vec<ProtocolAddress> = cfg
|
||||||
.server
|
.server
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1423,7 +1444,7 @@ fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
|
|||||||
input: cfg.input,
|
input: cfg.input,
|
||||||
output: cfg.output,
|
output: cfg.output,
|
||||||
no_nat: cfg.no_nat,
|
no_nat: cfg.no_nat,
|
||||||
no_tun: cfg.no_tun,
|
device_mode: cfg.device_mode,
|
||||||
mtu: cfg.mtu,
|
mtu: cfg.mtu,
|
||||||
port_mapping,
|
port_mapping,
|
||||||
allow_port_mapping: cfg.allow_mapping,
|
allow_port_mapping: cfg.allow_mapping,
|
||||||
@@ -1750,8 +1771,9 @@ mod tests {
|
|||||||
input: Vec::new(),
|
input: Vec::new(),
|
||||||
output: Vec::new(),
|
output: Vec::new(),
|
||||||
no_nat: false,
|
no_nat: false,
|
||||||
// 默认 no_tun,避免无关用例意外触发 tun_name 冲突
|
// 默认无网卡,避免无关用例意外触发 tun_name 冲突
|
||||||
no_tun: true,
|
device_mode: DeviceMode::No,
|
||||||
|
legacy_no_tun: None,
|
||||||
mtu: None,
|
mtu: None,
|
||||||
port_mapping: Vec::new(),
|
port_mapping: Vec::new(),
|
||||||
allow_mapping: false,
|
allow_mapping: false,
|
||||||
@@ -1761,6 +1783,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_mode_config_and_legacy_rejection() {
|
||||||
|
let base = r#"server = ["quic://127.0.0.1:29872"]
|
||||||
|
network_code = "test"
|
||||||
|
"#;
|
||||||
|
let default_cfg: StartConfig = toml::from_str(base).unwrap();
|
||||||
|
assert_eq!(default_cfg.device_mode, DeviceMode::Tun);
|
||||||
|
|
||||||
|
let tap_cfg: StartConfig =
|
||||||
|
toml::from_str(&format!("{base}device_mode = \"tap\"\n")).unwrap();
|
||||||
|
assert_eq!(tap_cfg.device_mode, DeviceMode::Tap);
|
||||||
|
|
||||||
|
let legacy: StartConfig = toml::from_str(&format!("{base}no_tun = true\n")).unwrap();
|
||||||
|
assert!(legacy.reject_legacy_no_tun().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
/// 两个实例同时处于 Starting 互不影响
|
/// 两个实例同时处于 Starting 互不影响
|
||||||
#[test]
|
#[test]
|
||||||
fn test_two_instances_starting_independent() {
|
fn test_two_instances_starting_independent() {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const emptyFormData = () => ({
|
|||||||
input: [],
|
input: [],
|
||||||
output: [],
|
output: [],
|
||||||
no_nat: false,
|
no_nat: false,
|
||||||
no_tun: false,
|
device_mode: "tun",
|
||||||
port_mapping: [],
|
port_mapping: [],
|
||||||
allow_mapping: false,
|
allow_mapping: false,
|
||||||
device_name: "",
|
device_name: "",
|
||||||
@@ -78,7 +78,13 @@ export const parseTomlToForm = (toml) => {
|
|||||||
} else if (trimmed.match(/^no_nat\s*=/)) {
|
} else if (trimmed.match(/^no_nat\s*=/)) {
|
||||||
data.no_nat = trimmed.includes("true");
|
data.no_nat = trimmed.includes("true");
|
||||||
} else if (trimmed.match(/^no_tun\s*=/)) {
|
} else if (trimmed.match(/^no_tun\s*=/)) {
|
||||||
data.no_tun = trimmed.includes("true");
|
throw new Error('配置项 no_tun 已移除,请改用 device_mode = "no|tun|tap"');
|
||||||
|
} else if (trimmed.match(/^device_mode\s*=/)) {
|
||||||
|
const match = trimmed.match(/device_mode\s*=\s*"([^"]*)"/);
|
||||||
|
if (!match || !["no", "tun", "tap"].includes(match[1])) {
|
||||||
|
throw new Error('device_mode 必须是 "no"、"tun" 或 "tap"');
|
||||||
|
}
|
||||||
|
data.device_mode = match[1];
|
||||||
} else if (trimmed.startsWith("port_mapping")) {
|
} else if (trimmed.startsWith("port_mapping")) {
|
||||||
const match = trimmed.match(/port_mapping\s*=\s*\[(.*)\]/);
|
const match = trimmed.match(/port_mapping\s*=\s*\[(.*)\]/);
|
||||||
if (match) {
|
if (match) {
|
||||||
@@ -196,10 +202,8 @@ export const formToToml = (formData) => {
|
|||||||
toml += "no_nat = true\n";
|
toml += "no_nat = true\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.no_tun) {
|
toml += "\n# 虚拟网卡模式:no(无网卡)、tun(三层网卡)、tap(二层网卡)\n";
|
||||||
toml += "\n# 是否关闭TUN虚拟网卡,关闭后只能充当流量出口或者进行端口映射,关闭后无需管理员权限\n";
|
toml += `device_mode = "${formData.device_mode || "tun"}"\n`;
|
||||||
toml += "no_tun = true\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const portMappings = formData.port_mapping.filter((s) => s.trim());
|
const portMappings = formData.port_mapping.filter((s) => s.trim());
|
||||||
if (portMappings.length > 0) {
|
if (portMappings.length > 0) {
|
||||||
@@ -310,8 +314,9 @@ server = ["quic://1.2.3.4:29872"]
|
|||||||
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
||||||
# no_nat = false
|
# no_nat = false
|
||||||
|
|
||||||
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
|
# 虚拟网卡模式:no(无网卡)、tun(三层网卡,默认)、tap(二层网卡)
|
||||||
# no_tun = false
|
# Windows 的 tap 模式需要预先安装 tap-windows (tap0901) 驱动
|
||||||
|
device_mode = "tun"
|
||||||
|
|
||||||
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||||
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
||||||
|
|||||||
@@ -26,10 +26,15 @@ const hasFormChanges = ref(false);
|
|||||||
const isParsingToml = ref(false);
|
const isParsingToml = ref(false);
|
||||||
const formData = ref(emptyFormData());
|
const formData = ref(emptyFormData());
|
||||||
const certificateModeOptions = [
|
const certificateModeOptions = [
|
||||||
{ value: "skip", label: "跳过验证(默认)" },
|
{ value: "skip", label: "跳过验证(默认)" },
|
||||||
{ value: "standard", label: "系统证书验证" },
|
{ value: "standard", label: "系统证书验证" },
|
||||||
{ value: "finger", label: "证书指纹验证" },
|
{ value: "finger", label: "证书指纹验证" },
|
||||||
];
|
];
|
||||||
|
const deviceModeOptions = [
|
||||||
|
{ value: "no", label: "无虚拟网卡" },
|
||||||
|
{ value: "tun", label: "TUN(三层)" },
|
||||||
|
{ value: "tap", label: "TAP(二层)" },
|
||||||
|
];
|
||||||
|
|
||||||
// 打开时加载内容
|
// 打开时加载内容
|
||||||
watch(
|
watch(
|
||||||
@@ -68,13 +73,17 @@ watch(
|
|||||||
// 切换到表单模式
|
// 切换到表单模式
|
||||||
const switchToFormMode = () => {
|
const switchToFormMode = () => {
|
||||||
if (editMode.value === "toml") {
|
if (editMode.value === "toml") {
|
||||||
editMode.value = "form";
|
try {
|
||||||
// 从TOML解析到表单
|
const parsed = parseTomlToForm(editorContent.value);
|
||||||
isParsingToml.value = true;
|
editMode.value = "form";
|
||||||
formData.value = parseTomlToForm(editorContent.value);
|
isParsingToml.value = true;
|
||||||
nextTick(() => {
|
formData.value = parsed;
|
||||||
isParsingToml.value = false;
|
nextTick(() => {
|
||||||
});
|
isParsingToml.value = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
ui.toast.error("配置解析失败: " + e.message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
editMode.value = "form";
|
editMode.value = "form";
|
||||||
}
|
}
|
||||||
@@ -440,13 +449,13 @@ const sectionTitleClass = "text-md mb-4 flex items-center font-bold text-slate-9
|
|||||||
</div>
|
</div>
|
||||||
<input v-model="formData.no_nat" type="checkbox" :class="checkboxClass" />
|
<input v-model="formData.no_nat" type="checkbox" :class="checkboxClass" />
|
||||||
</label>
|
</label>
|
||||||
<label :class="toggleLabelClass">
|
<div class="rounded-lg border border-slate-200 bg-slate-50 p-3 dark:border-slate-700 dark:bg-slate-800/50">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="text-sm font-medium text-slate-800 dark:text-white">关闭TUN网卡</div>
|
<div class="text-sm font-medium text-slate-800 dark:text-white">虚拟网卡模式</div>
|
||||||
<div class="text-xs text-slate-400 mt-0.5">仅作流量出口或端口映射</div>
|
<div class="text-xs text-slate-400 mt-0.5">TAP 为二层网卡;Windows 需要 tap-windows 驱动</div>
|
||||||
</div>
|
</div>
|
||||||
<input v-model="formData.no_tun" type="checkbox" :class="checkboxClass" />
|
<AppSelect v-model="formData.device_mode" :options="deviceModeOptions" class="mt-2" aria-label="虚拟网卡模式" />
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user