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..5d5820c3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +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"] + # Share the host network namespace so the TUN device and routes live on the host. + # This is required if remote VNT peers should reach services running on the host + # instead of only services inside the container namespace. + network_mode: host + 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 + # With host networking, do not publish ports here. + # 19099 is now bound directly on the host by vnt2_web itself. + # If you enable TUN mode in /app/data/config.toml, the VNT interface also lives on the host. diff --git a/src/args_config.rs b/src/args_config.rs index 507a722a..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}; @@ -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, @@ -83,75 +84,78 @@ 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)] pub token: Option, - /// 自定义虚拟IP + /// Network join secret used for registration validation + #[clap(long)] + pub network_secret: Option, + /// 鑷畾涔夎櫄鎷烮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, } @@ -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), @@ -375,103 +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 used for server-side admission control. Default: none. +# network_secret = "replace_with_a_long_random_secret" -# 加密密码 (可选) +# 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/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/compression/mod.rs b/vnt-core/src/compression/mod.rs index 92c19b69..d792c1fa 100644 --- a/vnt-core/src/compression/mod.rs +++ b/vnt-core/src/compression/mod.rs @@ -40,6 +40,6 @@ impl PacketCompression { if let Some(compression) = self.compression.as_ref() { return compression.decompress(pkt); } - Ok(pkt) + LZ4Compression::new().decompress(pkt) } } 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-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 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); 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..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 @@

- + - + - + - +