From 42e24773e50acb4bbba8e98d48e4360707ecd8c0 Mon Sep 17 00:00:00 2001 From: Cg8 <5712.cg8@gmail.com> Date: Sun, 19 Apr 2026 09:10:53 +0800 Subject: [PATCH 1/5] feat: add network secret for registration validation - Introduce `network_secret` field in `FileConfig` and `Args` structs - Update configuration handling to include `network_secret` - Modify Java example to set `network_secret` for client configuration - Update HTML forms to include input for `network_secret` This change adds a new optional field for a network join secret, which is used for server-side admission control. It enhances security by allowing validation of network registrations. The new field is reflected in both the configuration files and the user interface. --- src/args_config.rs | 11 ++++++++++ src/main_cli.rs | 7 +++++-- vnt-core/src/context/config.rs | 6 +++--- vnt-jni/java_example/AndroidVpnExample.java | 3 ++- vnt-jni/java_example/com/vnt/VntConfig.java | 14 ++++++++++++- vnt-jni/src/lib.rs | 3 +++ vnt-web/src/service_http.rs | 2 ++ vnt-web/static/index.html | 23 ++++++++++++++++++--- 8 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/args_config.rs b/src/args_config.rs index 507a722a..34e74caf 100644 --- a/src/args_config.rs +++ b/src/args_config.rs @@ -15,6 +15,7 @@ use vnt_ipc::port_mapping::PortMapping; pub struct FileConfig { pub server: Option>, pub network_code: Option, + pub network_secret: Option, pub ip: Option, pub no_punch: Option, pub rtx: Option, @@ -91,6 +92,9 @@ pub struct Args { pub network_code: Option, #[clap(short = 'k', long, hide = true)] pub token: Option, + /// Network join secret used for registration validation + #[clap(long)] + pub network_secret: Option, /// 自定义虚拟IP #[clap(long)] pub ip: Option, @@ -235,6 +239,7 @@ fn build_from_args_and_file(args: Args, file: FileConfig) -> anyhow::Result<(Con let config = Config { server_addr, network_code, + network_secret: args.network_secret.or_else(|| file.network_secret.clone()), ip: args.ip.or(file.ip), no_punch: args.no_punch || file.no_punch.unwrap_or(false), rtx: args.rtx || file.rtx.unwrap_or(false), @@ -276,6 +281,7 @@ fn build_from_args_only(args: Args) -> anyhow::Result<(Config, CtrlConfig)> { network_code: args .network_code .ok_or_else(|| anyhow!("network_code is required"))?, + network_secret: args.network_secret, ip: args.ip, no_punch: args.no_punch, rtx: args.rtx, @@ -335,6 +341,7 @@ fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig) network_code: file .network_code .ok_or_else(|| anyhow!("network_code is required"))?, + network_secret: file.network_secret.clone(), ip: file.ip, no_punch: file.no_punch.unwrap_or(false), rtx: file.rtx.unwrap_or(false), @@ -450,6 +457,10 @@ server = ["quic://1.2.3.4:29872"] # --- 安全配置 --- # 加密密码 (可选) +# Join secret for server-side admission control (optional) +# network_secret = "replace_with_a_long_random_secret" + +# Data-plane packet encryption password (optional) # password = "123456" # 证书校验方式: diff --git a/src/main_cli.rs b/src/main_cli.rs index 4adf8ce3..53944ef3 100644 --- a/src/main_cli.rs +++ b/src/main_cli.rs @@ -74,8 +74,11 @@ async fn main0() -> anyhow::Result<()> { log::info!("Sub network output:{x}"); } - if let Some(password_sign) = config.key_sign() { - log::info!("password sign: {:?}", password_sign); + if config.network_secret.is_some() { + log::info!("network authentication: enabled"); + } + if config.password.is_some() { + log::info!("payload encryption: enabled"); } let group_manager = TaskGroupManager::new(); diff --git a/vnt-core/src/context/config.rs b/vnt-core/src/context/config.rs index 9f4befaa..1dfe8d4e 100644 --- a/vnt-core/src/context/config.rs +++ b/vnt-core/src/context/config.rs @@ -1,4 +1,3 @@ -use crate::crypto::PacketCrypto; use crate::nat::NetInput; use crate::port_mapping::PortMapping; use crate::tls::verifier::CertValidationMode; @@ -19,6 +18,7 @@ pub struct Config { pub server_addr: Vec, pub cert_mode: CertValidationMode, pub network_code: String, + pub network_secret: Option, pub device_id: String, pub device_name: String, pub tun_name: Option, @@ -85,17 +85,17 @@ impl Config { Ok(()) } pub fn key_sign(&self) -> Option { - self.password.as_ref().map(|p| PacketCrypto::key_sign(p)) + self.network_secret.clone() } pub(crate) fn to_connect_config(&self, index: usize) -> ConnectRegConfig { ConnectRegConfig { server_addr: self.server_addr[index].clone(), cert_mode: self.cert_mode.clone(), network_code: self.network_code.clone(), + key_sign: self.key_sign(), device_id: self.device_id.clone(), device_name: self.device_name.clone(), ip: self.ip, - key_sign: self.key_sign(), ip_variable: self.ip.is_none(), } } diff --git a/vnt-jni/java_example/AndroidVpnExample.java b/vnt-jni/java_example/AndroidVpnExample.java index 93b70b01..f7cc0427 100644 --- a/vnt-jni/java_example/AndroidVpnExample.java +++ b/vnt-jni/java_example/AndroidVpnExample.java @@ -38,7 +38,8 @@ private void startVpn() throws Exception { VntConfig config = new VntConfig.Builder() .addServer("tcp://101.35.230.139:6660") .setNetworkCode("your_network_code") - .setPassword("123456") + .setNetworkSecret("replace_with_a_long_random_secret") + .setPassword("optional_packet_password") .setDeviceName("AndroidDevice") .setCompress(true) .setMtu(1380) diff --git a/vnt-jni/java_example/com/vnt/VntConfig.java b/vnt-jni/java_example/com/vnt/VntConfig.java index 93cccf5e..2d1c026b 100644 --- a/vnt-jni/java_example/com/vnt/VntConfig.java +++ b/vnt-jni/java_example/com/vnt/VntConfig.java @@ -14,6 +14,7 @@ public class VntConfig { private final List servers; private final String networkCode; + private final String networkSecret; private final String password; private final String deviceId; private final String deviceName; @@ -35,6 +36,7 @@ public class VntConfig { private VntConfig(Builder builder) { this.servers = builder.servers; this.networkCode = builder.networkCode; + this.networkSecret = builder.networkSecret; this.password = builder.password; this.deviceId = builder.deviceId; this.deviceName = builder.deviceName; @@ -69,6 +71,7 @@ String toJson() { json.put("network_code", networkCode); // 可选项 + if (networkSecret != null) json.put("network_secret", networkSecret); if (password != null) json.put("password", password); if (deviceId != null) json.put("device_id", deviceId); if (deviceName != null) json.put("device_name", deviceName); @@ -120,6 +123,7 @@ String toJson() { public static class Builder { private List servers = new ArrayList<>(); private String networkCode; + private String networkSecret; private String password; private String deviceId; private String deviceName; @@ -157,7 +161,15 @@ public Builder setNetworkCode(String networkCode) { } /** - * 设置密码(可选) + * Set the join secret used for network admission validation. + */ + public Builder setNetworkSecret(String networkSecret) { + this.networkSecret = networkSecret; + return this; + } + + /** + * Set the optional packet encryption password. */ public Builder setPassword(String password) { this.password = password; diff --git a/vnt-jni/src/lib.rs b/vnt-jni/src/lib.rs index dad6b0bd..25b00e9e 100644 --- a/vnt-jni/src/lib.rs +++ b/vnt-jni/src/lib.rs @@ -812,6 +812,8 @@ fn parse_config_from_json(json_str: &str) -> anyhow::Result { server: Vec, network_code: String, #[serde(default)] + network_secret: Option, + #[serde(default)] device_id: Option, #[serde(default)] device_name: Option, @@ -910,6 +912,7 @@ fn parse_config_from_json(json_str: &str) -> anyhow::Result { Ok(Config { server_addr: server_addrs, network_code: cfg.network_code, + network_secret: cfg.network_secret, ip: cfg.ip, no_punch: cfg.no_punch, rtx: cfg.rtx, diff --git a/vnt-web/src/service_http.rs b/vnt-web/src/service_http.rs index 21a546eb..2bdf000b 100644 --- a/vnt-web/src/service_http.rs +++ b/vnt-web/src/service_http.rs @@ -165,6 +165,7 @@ pub struct StartConfig { pub server: Vec, pub cert_mode: Option, pub network_code: String, + pub network_secret: Option, pub device_id: Option, pub device_name: Option, pub tun_name: Option, @@ -938,6 +939,7 @@ fn convert_config(cfg: StartConfig) -> anyhow::Result { Ok(CoreConfig { server_addr: server_addrs, network_code: cfg.network_code, + network_secret: cfg.network_secret, ip: cfg.ip, no_punch: cfg.no_punch, rtx: cfg.rtx, diff --git a/vnt-web/static/index.html b/vnt-web/static/index.html index bdbe32b4..8c626958 100644 --- a/vnt-web/static/index.html +++ b/vnt-web/static/index.html @@ -1044,8 +1044,11 @@

- - 入网验证密钥 + + +
@@ -1743,6 +1746,7 @@

路由表

device_name: "", device_id: "", tun_name: "", + network_secret: "", password: "", cert_mode: "skip", fingerprint: "", @@ -1772,6 +1776,7 @@

路由表

device_name: "", device_id: "", tun_name: "", + network_secret: "", password: "", cert_mode: "skip", fingerprint: "", @@ -1844,6 +1849,9 @@

路由表

} else if (trimmed.includes('tun_name')) { const match = trimmed.match(/tun_name\s*=\s*"([^"]*)"/); if (match) data.tun_name = match[1]; + } else if (trimmed.includes('network_secret')) { + const match = trimmed.match(/network_secret\s*=\s*"([^"]*)"/); + if (match) data.network_secret = match[1]; } else if (trimmed.includes('password =')) { const match = trimmed.match(/password\s*=\s*"([^"]*)"/); if (match) data.password = match[1]; @@ -1982,8 +1990,12 @@

路由表

} toml += '\n# --- 安全配置 ---\n'; + if (formData.value.network_secret) { + toml += '\n# Network join secret used for server-side admission control\n'; + toml += `network_secret = "${formData.value.network_secret}"\n`; + } if (formData.value.password) { - toml += '\n# 组网加密密码 (可选)\n'; + toml += '\n# Data-plane packet encryption password\n'; toml += `password = "${formData.value.password}"\n`; } if (formData.value.cert_mode && formData.value.cert_mode !== 'skip') { @@ -2109,6 +2121,7 @@

路由表

device_name: "", device_id: "", tun_name: "", + network_secret: "", password: "", cert_mode: "skip", fingerprint: "", @@ -2185,6 +2198,10 @@

路由表

# --- 安全配置 --- # 加密密码 (可选) +# Network join secret used for server-side admission control +# network_secret = "replace_with_a_long_random_secret" + +# Data-plane packet encryption password # password = "123456" # 证书校验方式: From c37c2460cc449c0c546089c6208466a3a484c590 Mon Sep 17 00:00:00 2001 From: Cg8 <5712.cg8@gmail.com> Date: Sun, 19 Apr 2026 09:13:30 +0800 Subject: [PATCH 2/5] refactor: simplify route addition and function signature - Consolidate the route addition method call into a single line - Simplify the function signature of `route_timeout_task` These changes improve code readability by reducing the number of lines and making the function signatures more concise. The functionality remains unchanged, but the code is now cleaner and easier to follow. --- vnt-core/src/tunnel_core/p2p/inbound.rs | 6 ++---- vnt-core/src/tunnel_core/p2p/transport/task.rs | 5 +---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/vnt-core/src/tunnel_core/p2p/inbound.rs b/vnt-core/src/tunnel_core/p2p/inbound.rs index 8608dbcd..e25709eb 100644 --- a/vnt-core/src/tunnel_core/p2p/inbound.rs +++ b/vnt-core/src/tunnel_core/p2p/inbound.rs @@ -280,10 +280,8 @@ impl P2pInboundHandler { } MsgType::RelayProbeReply => { let metric = ctx.max_ttl - ctx.ttl; - self.route_table.add_route( - ctx.src_ip, - Route::from_default_rt(route_key, metric), - ); + self.route_table + .add_route(ctx.src_ip, Route::from_default_rt(route_key, metric)); } _ => {} } diff --git a/vnt-core/src/tunnel_core/p2p/transport/task.rs b/vnt-core/src/tunnel_core/p2p/transport/task.rs index d487567f..e9fc90d9 100644 --- a/vnt-core/src/tunnel_core/p2p/transport/task.rs +++ b/vnt-core/src/tunnel_core/p2p/transport/task.rs @@ -145,10 +145,7 @@ pub async fn ping_all( } } } -pub async fn route_timeout_task( - route_table: RouteTable, - packet_loss_stats: PacketLossStats, -) { +pub async fn route_timeout_task(route_table: RouteTable, packet_loss_stats: PacketLossStats) { loop { tokio::time::sleep(Duration::from_secs(10)).await; let expired_time = std::time::Instant::now() - Duration::from_secs(10); From 3353aac01e013576734b7025133bdf95abc06d1f Mon Sep 17 00:00:00 2001 From: Cg8 <5712.cg8@gmail.com> Date: Sun, 19 Apr 2026 09:23:23 +0800 Subject: [PATCH 3/5] feat: add Docker support and configuration files - Create Dockerfile for building the application - Add docker-compose.yml for service orchestration - Include example configuration file for user reference - Add .dockerignore to exclude unnecessary files from the image This commit introduces Docker support for the project, allowing for easier deployment and management of the application. The Dockerfile sets up the build environment, while the docker-compose.yml facilitates running the application with necessary configurations. An example configuration file is provided to guide users in setting up their own configurations. --- .dockerignore | 6 +++ Dockerfile | 34 ++++++++++++++++ data/config.example.toml | 52 ++++++++++++++++++++++++ docker-compose.yml | 24 +++++++++++ vnt-core/src/crypto/chacha20_poly1305.rs | 18 -------- vnt-core/src/crypto/mod.rs | 4 -- 6 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 data/config.example.toml create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..65595e36 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.github +target +data +logs +*.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9c76e2b4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +ARG RUST_VERSION=1.93.1 + +FROM rust:${RUST_VERSION}-bookworm AS builder +WORKDIR /build + +RUN apt-get update \ + && apt-get install -y --no-install-recommends protobuf-compiler pkg-config \ + && rm -rf /var/lib/apt/lists/* + +COPY .cargo ./.cargo +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY vnt-core ./vnt-core +COPY vnt-ipc ./vnt-ipc +COPY vnt-jni ./vnt-jni +COPY vnt-web ./vnt-web + +RUN cargo build --release --locked --bin vnt2_web --features vnt-web + +FROM debian:bookworm-slim AS runtime + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app/data + +COPY --from=builder /build/target/release/vnt2_web /usr/local/bin/vnt2_web + +VOLUME ["/app/data"] + +EXPOSE 19099/tcp + +CMD ["vnt2_web", "--addr", "0.0.0.0:19099"] diff --git a/data/config.example.toml b/data/config.example.toml new file mode 100644 index 00000000..05a6b77b --- /dev/null +++ b/data/config.example.toml @@ -0,0 +1,52 @@ +# Display name shown in the client web UI. +config_name = "default" + +# One or more control servers. Supported schemes: quic:// tcp:// wss:// dynamic:// +server = ["quic://YOUR_SERVER_IP:29872"] + +# Must match a network_code known by the server. +network_code = "default" + +# Must match the server-side secret configured for that network_code. +network_secret = "replace_with_the_server_side_network_secret" + +# Packet/data encryption inside the virtual network. +# Leave commented out to disable payload encryption. +# password = "optional_packet_password" + +# Quick-start setting for self-signed server certificates. +# Use "finger:" or "standard" in production. +cert_mode = "skip" + +# Optional fixed overlay IP inside the selected network CIDR. +# ip = "172.16.57.10" + +# Optional device identity. +device_name = "docker-client" +# device_id = "docker-client-01" +# tun_name = "vnt-tun" + +# Transport tuning. +# rtx = true +# fec = false +# compress = false +# no_punch = false + +# For a registration-only smoke test on a host without /dev/net/tun, uncomment: +# no_tun = true + +# Optional fixed UDP port for P2P traffic. +# If you set this in Docker, publish the same UDP port in docker-compose.yml. +# tunnel_port = 30001 + +# Optional subnet/gateway features. +# input = ["192.168.10.0/24,172.16.57.10"] +# output = ["0.0.0.0/0"] +# no_nat = false +# mtu = 1380 +# port_mapping = ["tcp://0.0.0.0:8080-172.16.57.20-172.16.57.20:80"] +# allow_mapping = false + +# Optional STUN overrides. +# udp_stun = ["stun.chat.bilibili.com:3478"] +# tcp_stun = ["stun.nextcloud.com:443"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..4b9b5574 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + vnt-client: + build: + context: . + args: + RUST_VERSION: 1.93.1 + image: vnt2-client:local + container_name: vnt2-client + restart: unless-stopped + command: ["vnt2_web", "--addr", "0.0.0.0:19099", "--conf", "/app/data/config.toml"] + cap_add: + - NET_ADMIN + - NET_RAW + devices: + - /dev/net/tun:/dev/net/tun + volumes: + # The app writes config.toml, vnt_config/, vnt_current_config.txt and logs + # relative to its working directory. Persist all of them in ./data. + - ./data:/app/data + ports: + - "19099:19099/tcp" + # If you set tunnel_port in data/config.toml, publish the same UDP port here. + # Example: + # - "30001:30001/udp" diff --git a/vnt-core/src/crypto/chacha20_poly1305.rs b/vnt-core/src/crypto/chacha20_poly1305.rs index 633a4d03..12e1513f 100644 --- a/vnt-core/src/crypto/chacha20_poly1305.rs +++ b/vnt-core/src/crypto/chacha20_poly1305.rs @@ -10,24 +10,6 @@ pub struct PacketCrypto { } impl PacketCrypto { - pub fn key_sign(s: &str) -> String { - use ring::digest::{Context, SHA256}; - - const PREFIX: &[u8] = b"KEY-BEGIN"; - const SUFFIX: &[u8] = b"KEY-END"; - - let mut ctx = Context::new(&SHA256); - ctx.update(PREFIX); - ctx.update(s.as_bytes()); - ctx.update(SUFFIX); - let digest = ctx.finish(); - let mut key_bytes = [0u8; 16]; - key_bytes.copy_from_slice(&digest.as_ref()[..16]); - key_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::() - } pub fn new(key_bytes: [u8; 32]) -> Self { let unbound = UnboundKey::new(&CHACHA20_POLY1305, &key_bytes).unwrap(); let key = LessSafeKey::new(unbound); diff --git a/vnt-core/src/crypto/mod.rs b/vnt-core/src/crypto/mod.rs index 6b9ae6c1..e65ee216 100644 --- a/vnt-core/src/crypto/mod.rs +++ b/vnt-core/src/crypto/mod.rs @@ -12,10 +12,6 @@ pub(crate) struct PacketCrypto { crypto: Option>, } impl PacketCrypto { - pub(crate) fn key_sign(s: &str) -> String { - chacha20_poly1305::PacketCrypto::key_sign(s) - } - pub(crate) fn new_from_str(s: Option<&str>) -> Self { Self { crypto: s From f93bb1170ae3531424db7c75e2e32c66e5e07786 Mon Sep 17 00:00:00 2001 From: Cg8 <5712.cg8@gmail.com> Date: Sun, 19 Apr 2026 10:42:27 +0800 Subject: [PATCH 4/5] docs: update comments and documentation - Translate comments in Rust code from Chinese to English - Update HTML comments for better clarity - Ensure consistency in language across the project These changes improve the documentation and comments in the codebase, making it more understandable for developers who may not be fluent in Chinese. --- src/args_config.rs | 150 ++++---- vnt-web/static/index.html | 697 +++++++++++++++++++------------------- 2 files changed, 437 insertions(+), 410 deletions(-) diff --git a/src/args_config.rs b/src/args_config.rs index 34e74caf..c653688a 100644 --- a/src/args_config.rs +++ b/src/args_config.rs @@ -1,4 +1,4 @@ -use anyhow::anyhow; +use anyhow::anyhow; use clap::Parser; use ipnet::Ipv4Net; use serde::{Deserialize, Serialize}; @@ -84,10 +84,10 @@ impl FileConfig { #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { - /// 服务器地址 例如 `-s quic://127.0.0.1:29872`, 支持quic/tcp/wss/dynamic + /// 鏈嶅姟鍣ㄥ湴鍧€ 渚嬪 `-s quic://127.0.0.1:29872`, 鏀寔quic/tcp/wss/dynamic #[clap(short, long)] pub server: Vec, - /// 网络编号,相同编号的会组同一个局域网 + /// 缃戠粶缂栧彿锛岀浉鍚岀紪鍙风殑浼氱粍鍚屼竴涓眬鍩熺綉 #[clap(short, long)] pub network_code: Option, #[clap(short = 'k', long, hide = true)] @@ -95,67 +95,67 @@ pub struct Args { /// Network join secret used for registration validation #[clap(long)] pub network_secret: Option, - /// 自定义虚拟IP + /// 鑷畾涔夎櫄鎷烮P #[clap(long)] pub ip: Option, - /// 启用加密,设置加密密码 + /// 鍚敤鍔犲瘑锛岃缃姞瀵嗗瘑鐮? #[clap(short, long)] pub password: Option, - /// 启用quic优化传输 + /// 鍚敤quic浼樺寲浼犺緭 #[clap(long)] pub rtx: bool, - /// 启用压缩 (LZ4) + /// 鍚敤鍘嬬缉 (LZ4) #[clap(short = 'z', long)] pub compress: bool, - /// 启用 FEC 前向纠错,损失一定带宽来提升网络稳定性 + /// 鍚敤 FEC 鍓嶅悜绾犻敊锛屾崯澶变竴瀹氬甫瀹芥潵鎻愬崌缃戠粶绋冲畾鎬? #[clap(long)] pub fec: bool, - /// 入栈监听网段 + /// 鍏ユ爤鐩戝惉缃戞 #[clap(short, long)] pub input: Vec, - /// 出栈允许网段 + /// 鍑烘爤鍏佽缃戞 #[clap(short, long)] pub output: Vec, - /// 自定义设备名称 + /// 鑷畾涔夎澶囧悕绉? #[clap(long, alias = "name")] pub device_name: Option, - /// 设备id + /// 璁惧id #[clap(long, alias = "id")] pub device_id: Option, - /// 关闭打洞 + /// 鍏抽棴鎵撴礊 #[clap(long)] pub no_punch: bool, - /// 服务端证书验证 + /// 鏈嶅姟绔瘉涔﹂獙璇? #[clap(long)] pub cert_mode: Option, - /// 虚拟网卡名称 + /// 铏氭嫙缃戝崱鍚嶇О #[clap(long)] pub tun_name: Option, - /// 关闭内置子网NAT + /// 鍏抽棴鍐呯疆瀛愮綉NAT #[clap(long)] pub no_nat: bool, - /// 禁用tun,禁用后只能充当流量出口或者进行端口映射,无需管理员权限 + /// 绂佺敤tun锛岀鐢ㄥ悗鍙兘鍏呭綋娴侀噺鍑哄彛鎴栬€呰繘琛岀鍙f槧灏勶紝鏃犻渶绠$悊鍛樻潈闄? #[clap(long)] pub no_tun: bool, - /// 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址 + /// 绔彛鏄犲皠锛屾牸寮忎负锛氬崗璁?//鏈湴鐩戝惉鍦板潃-鐩爣铏氭嫙IP-鐩爣鏄犲皠鍦板潃 #[clap(long)] pub port_mapping: Vec, - /// 是否允许作为端口映射出口,开启后其他设备才可使用本设备的ip为"目标虚拟IP" + /// 鏄惁鍏佽浣滀负绔彛鏄犲皠鍑哄彛锛屽紑鍚悗鍏朵粬璁惧鎵嶅彲浣跨敤鏈澶囩殑ip涓?鐩爣铏氭嫙IP" #[clap(long)] pub allow_mapping: bool, - /// 设置mtu + /// 璁剧疆mtu #[clap(long)] pub mtu: Option, - /// 控制端口,设置0时禁用控制服务 + /// 鎺у埗绔彛锛岃缃?鏃剁鐢ㄦ帶鍒舵湇鍔? #[clap(long)] pub ctrl_port: Option, - /// 隧道端口,用于P2P通信 + /// 闅ч亾绔彛锛岀敤浜嶱2P閫氫俊 #[clap(long)] pub tunnel_port: Option, - /// 读取配置文件 + /// 璇诲彇閰嶇疆鏂囦欢 #[arg(long)] pub conf: Option, - /// 输出配置文件示例 + /// 杈撳嚭閰嶇疆鏂囦欢绀轰緥 #[clap(long)] pub conf_example: bool, } @@ -382,107 +382,121 @@ impl FileConfig { let example = format!( r#"# ================================== -# VNT 配置文件示例(程序版本 v{version}) +# VNT example config (v{version}) +# Fields marked as Required have no default value. # ================================== -# --- 网络配置 --- -# 网络编号,相同网络编号的会组在同一个虚拟网 (必填) +# --- Required --- + +# Virtual network identifier. Required. Default: none. network_code = "your_network_code" -# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填) -# dynamic 协议使用dns txt解析记录值 +# Server addresses. Required. Default: none. +# Supported schemes: quic://, tcp://, wss://, dynamic:// +# If the scheme is omitted, tcp:// is used. server = ["quic://1.2.3.4:29872"] -# ===简单使用以下参数可以不动=== +# --- Common optional fields --- -# 自定义虚拟 IP (可选) +# Virtual IP. Default: auto-assigned by the server. # ip = "10.10.0.2" -# 是否启用quic优化传输 (默认 false,设置为true时开启) +# Enable QUIC optimized transport. Default: false. # rtx = false -# 是否启用 FEC 前向纠错,损失一定带宽来提升网络稳定性(默认 false,设置为true时开启) +# Enable forward error correction. Default: false. # fec = false -# 是否关闭 P2P 打洞 (默认 false,设置为true时关闭) +# Disable P2P hole punching. Default: false. # no_punch = false -# 是否启用 LZ4 压缩 (默认 false,设置为true时开启) +# Enable LZ4 compression. Default: false. # compress = false -# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点 +# Route subnet traffic to a virtual peer. Default: []. +# Format: "CIDR,target_virtual_ip" # input = ["192.168.0.0/24,10.26.0.2", "192.168.1.0/24,10.26.0.3"] -# 出栈允许网段,用于点对网,允许指定网段的转发 +# Allow this node to forward traffic to these destination subnets. Default: []. # output = ["0.0.0.0/0"] -# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好 +# Disable built-in subnet NAT. Default: false. # no_nat = false -# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限 +# Disable TUN device creation. Default: false. # no_tun = false -# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址 -# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问 -# 例如 port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"] -# tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 则表示将本地tcp的81端口的数据转发到10.0.0.2:80 -# tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80 -# tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80 +# Port mapping rules. Default: []. +# Format: "tcp://listen_addr-virtual_target_ip-dst_host:dst_port" +# Example: +# port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"] +# port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80"] +# port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-anyonehost:80"] # port_mapping = [] -# 是否允许作为端口映射出口,开启(设置为true)后其他设备才可使用本设备的ip为"目标虚拟IP" -# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络 +# Allow other peers to use this node as a port-mapping egress. Default: false. # allow_mapping = false -# 控制服务的 tcp 端口 +# Local IPC control port for CLI mode. Default: 11233. +# If 11233 is occupied, a random free port is chosen automatically. +# Set to 0 to disable the local IPC server. # ctrl_port = 11233 -# 隧道端口,用于P2P通信 (默认为0,自动分配) +# Local P2P tunnel port. Default: 0 (auto-assign). # tunnel_port = 0 -# MTU 设置 -# mtu = 1400 +# MTU. Default: 1380. Maximum: 1500. +# mtu = 1380 -# --- 设备配置 --- +# --- Device identity --- -# 设备名称 (可选,默认读取本机 hostname) +# Device name. Default: local hostname. # device_name = "my-device" -# 设备 ID (可选,不填自动生成,不同设备ID不能相同) +# Device ID. Default: auto-generated from the local machine. # device_id = "device-id-xxxx" -# 虚拟网卡名称 +# TUN interface name. Default: OS/runtime chosen. # tun_name = "vnt-tun" -# --- 安全配置 --- +# --- Security --- -# 加密密码 (可选) -# Join secret for server-side admission control (optional) +# Join secret used for server-side admission control. Default: none. # network_secret = "replace_with_a_long_random_secret" -# Data-plane packet encryption password (optional) +# Payload encryption password. Default: none. # password = "123456" -# 证书校验方式: -# skip 跳过验证(默认) -# standard 使用系统证书验证 -# finger 使用证书指纹验证,服务端启动时日志会输出指纹, -# 例如 finger:3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1 +# Server certificate validation mode. Default: "skip". +# Options: +# "skip" - no certificate validation +# "standard" - validate against system root CAs +# "finger:" - pin by certificate fingerprint # cert_mode = "skip" -# --- 其他配置 --- -# 自定义stun地址,分别用于udp打洞和tcp打洞,需要单独配置,不设置则用默认stun +# --- STUN --- + +# UDP STUN servers. Default: built-in list. +# Built-in default: +# ["stun.miwifi.com:3478", "stun.chat.bilibili.com:3478", "stun.l.google.com:19302"] +# If a host has no port, :3478 is appended automatically. # udp_stun = ["stun.chat.bilibili.com"] + +# TCP STUN servers. Default: built-in list. +# Built-in default: +# ["stun.flashdance.cx:3478", "stun.sipnet.net:3478", "stun.nextcloud.com:443"] +# If a host has no port, :3478 is appended automatically. # tcp_stun = ["stun.nextcloud.com:443"] "#, version = VERSION ); - println!("--- 示例配置文件内容 ---\n{}", example); + println!("--- 绀轰緥閰嶇疆鏂囦欢鍐呭 ---\n{}", example); if let Some(p) = path { std::fs::write(p, &example)?; - println!("示例配置文件已写入 {}", p.display()); + println!("绀轰緥閰嶇疆鏂囦欢宸插啓鍏?{}", p.display()); } Ok(()) } } + diff --git a/vnt-web/static/index.html b/vnt-web/static/index.html index 8c626958..f4e50831 100644 --- a/vnt-web/static/index.html +++ b/vnt-web/static/index.html @@ -1,4 +1,4 @@ - + @@ -48,7 +48,7 @@ ring: 2px; } - /* Tooltip CSS (保留用于表格内的静态 tooltip) */ + /* Tooltip CSS (淇濈暀鐢ㄤ簬琛ㄦ牸鍐呯殑闈欐€?tooltip) */ .tooltip { position: relative; display: inline-flex; @@ -92,7 +92,7 @@ opacity: 1; } - /* 路由切换动画 */ + /* 璺敱鍒囨崲鍔ㄧ敾 */ .fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease; @@ -103,7 +103,7 @@ opacity: 0; } - /* 自定义滚动条样式 */ + /* 鑷畾涔夋粴鍔ㄦ潯鏍峰紡 */ .custom-scrollbar::-webkit-scrollbar { width: 8px; height: 8px; @@ -124,7 +124,7 @@ background: rgba(100, 116, 139, 0.9); } - /* Firefox 滚动条样式 */ + /* Firefox 婊氬姩鏉℃牱寮?*/ .custom-scrollbar { scrollbar-width: thin; scrollbar-color: rgba(71, 85, 105, 0.8) rgba(15, 23, 42, 0.5); @@ -133,7 +133,7 @@
- +
@@ -283,9 +283,9 @@

:class="info.status === 'running' ? 'bg-green-500 animate-pulse' : (info.status === 'starting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')" > {{ info.status === 'running' ? '已运行' : - (info.status === 'starting' ? '启动中...' : - '未启动') }}{{ info.status === 'running' ? '宸茶繍琛? : + (info.status === 'starting' ? '鍚姩涓?..' : + '鏈惎鍔?) }}

@@ -311,8 +311,8 @@

服务器: {{ isServerConnected ? '已连接' : - '未连接' }}鏈嶅姟鍣? {{ isServerConnected ? '宸茶繛鎺? : + '鏈繛鎺? }}

@@ -328,7 +328,7 @@

- 设备: + 璁惧: {{ info.name || '' }} @@ -349,7 +349,7 @@

- +
>

{{ startStatus === 'starting' ? - '正在启动组网...' : (startStatus === - 'running' ? '启动成功' : '启动失败') }} + '姝e湪鍚姩缁勭綉...' : (startStatus === + 'running' ? '鍚姩鎴愬姛' : '鍚姩澶辫触') }}

v-if="startStatus === 'starting'" class="text-blue-400 animate-pulse italic mt-4" > - 等待后续步骤... + 绛夊緟鍚庣画姝ラ...
- 启动失败: - 请检查配置或网络连接。 + 鍚姩澶辫触锛?/strong> + 璇锋鏌ラ厤缃垨缃戠粶杩炴帴銆?
@click="cancelStart" class="px-6 py-2 bg-slate-700 hover:bg-red-600 text-white rounded-lg transition-colors font-medium" > - 取消组网 + 鍙栨秷缁勭綉
@@ -497,27 +497,27 @@

- + - + - + - +