diff --git a/Cargo.lock b/Cargo.lock index fe3cb79..34fcb98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -452,6 +458,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1182,6 +1198,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.85" @@ -3206,6 +3244,23 @@ dependencies = [ "vnt-core", ] +[[package]] +name = "vnt-jni" +version = "2.0.0" +dependencies = [ + "anyhow", + "hostname", + "ipnet", + "jni", + "lazy_static", + "log", + "parking_lot 0.12.5", + "serde", + "serde_json", + "tokio", + "vnt-core", +] + [[package]] name = "vnt-web" version = "2.0.0" @@ -3528,6 +3583,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3564,6 +3628,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3621,6 +3700,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3639,6 +3724,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3657,6 +3748,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3687,6 +3784,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3705,6 +3808,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3723,6 +3832,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3741,6 +3856,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index f00dc16..79dfde1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ path = "src/main_web.rs" required-features = ["vnt-web"] [workspace] -members = ["vnt-web", "vnt-ipc", "vnt-core"] +members = ["vnt-web", "vnt-ipc", "vnt-core", "vnt-jni"] [profile.release] opt-level = 'z' diff --git a/src/main_cli.rs b/src/main_cli.rs index 81223ee..4adf8ce 100644 --- a/src/main_cli.rs +++ b/src/main_cli.rs @@ -108,7 +108,7 @@ async fn main0() -> anyhow::Result<()> { log::info!("启动网络:{}/{}", reg_msg.ip, reg_msg.prefix_len); network_manager.start_tun().await.context("start tun")?; network_manager - .set_network_ip(reg_msg.ip, reg_msg.prefix_len) + .set_tun_network_ip(reg_msg.ip, reg_msg.prefix_len) .await .context("set network ip")?; if !sub_input.is_empty() { diff --git a/vnt-core/src/core/mod.rs b/vnt-core/src/core/mod.rs index 1c8d6b7..de4243d 100644 --- a/vnt-core/src/core/mod.rs +++ b/vnt-core/src/core/mod.rs @@ -324,7 +324,7 @@ impl NetworkManager { .await } #[cfg(not(target_os = "android"))] - pub async fn set_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> { + pub async fn set_tun_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> { self.device_io_manager.set_network(ip, prefix_len).await?; Ok(()) } diff --git a/vnt-jni/Cargo.toml b/vnt-jni/Cargo.toml new file mode 100644 index 0000000..9cc8aae --- /dev/null +++ b/vnt-jni/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "vnt-jni" +version = "2.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +vnt-core = { path = "../vnt-core" } + +jni = "0.21" +tokio = { version = "1", features = ["full"] } +parking_lot = "0.12" +log = "0.4" +anyhow = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +ipnet = { version = "2.11", features = ["serde"] } +lazy_static = "1.5" +hostname = "0.4.2" diff --git a/vnt-jni/java_example/AndroidVpnExample.java b/vnt-jni/java_example/AndroidVpnExample.java new file mode 100644 index 0000000..93b70b0 --- /dev/null +++ b/vnt-jni/java_example/AndroidVpnExample.java @@ -0,0 +1,144 @@ +import android.net.VpnService; +import android.os.ParcelFileDescriptor; +import com.vnt.*; + +/** + * Android VPN服务示例 + * + * 演示如何正确使用VNT JNI接口: + * 1. 创建网络 + * 2. 注册获取IP/掩码 + * 3. 用获取的参数建立Android VPN接口 + * 4. 传入tunFd启动VNT + */ +public class AndroidVpnExample extends VpnService { + + private VntNetwork network; + private ParcelFileDescriptor vpnInterface; + + @Override + public int onStartCommand(android.content.Intent intent, int flags, int startId) { + try { + startVpn(); + return START_STICKY; + } catch (Exception e) { + e.printStackTrace(); + stopSelf(); + return START_NOT_STICKY; + } + } + + private void startVpn() throws Exception { + // 1. 初始化VNT(全局初始化,只需一次) + if (!VntManager.init()) { + throw new VntException("Failed to initialize VNT"); + } + + // 2. 构建配置 + VntConfig config = new VntConfig.Builder() + .addServer("tcp://101.35.230.139:6660") + .setNetworkCode("your_network_code") + .setPassword("123456") + .setDeviceName("AndroidDevice") + .setCompress(true) + .setMtu(1380) + .build(); + + // 3. 创建网络实例 + network = VntManager.createNetwork(config); + if (network == null) { + throw new VntException("Failed to create network"); + } + + // 4. 注册网络(连接服务器,获取分配的IP和掩码) + RegisterResult result = network.register(); + System.out.println("Registration successful: " + result); + + // 5. 使用注册返回的IP和掩码,建立Android VPN接口 + VpnService.Builder builder = new Builder(); + builder.setMtu(1380); + builder.addAddress(result.getIp(), result.getPrefixLen()); + builder.addRoute("0.0.0.0", 0); // 全局路由 + builder.setSession("VNT VPN"); + + // 建立VPN接口,获取文件描述符 + vpnInterface = builder.establish(); + if (vpnInterface == null) { + throw new VntException("Failed to establish VPN interface"); + } + + int tunFd = vpnInterface.getFd(); + System.out.println("VPN interface established, fd: " + tunFd); + + // 6. 将tunFd传给VNT,启动数据转发 + network.startTun(tunFd); + System.out.println("VNT started successfully!"); + + // 7. 获取API用于查询状态 + VntApi api = network.getApi(); + + // 8. 查询网络信息 + VntApi.NetworkInfo networkInfo = api.getNetwork(); + System.out.println("Network info: " + networkInfo); + + // 9. 查询NAT信息 + VntApi.NatInfo natInfo = api.getNatInfo(); + System.out.println("NAT info: " + natInfo); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // 清理资源 + if (network != null) { + network.stop(); + network = null; + } + + if (vpnInterface != null) { + try { + vpnInterface.close(); + } catch (Exception e) { + e.printStackTrace(); + } + vpnInterface = null; + } + + VntManager.destroy(); + } + + /** + * 查询客户端列表(可在UI线程定期调用) + */ + public void queryClients() { + if (network == null) { + return; + } + + try { + VntApi api = network.getApi(); + for (VntApi.ClientInfo client : api.getClientList()) { + System.out.println("Client: " + client); + + // 检查是否直连 + boolean direct = api.isDirect(client.getIp()); + System.out.println(" Direct: " + direct); + + // 获取丢包信息 + VntApi.PacketLossInfo loss = api.getPacketLoss(client.getIp()); + if (loss != null) { + System.out.println(" Packet loss: " + loss); + } + + // 获取流量信息 + VntApi.TrafficInfo traffic = api.getTrafficInfo(client.getIp()); + if (traffic != null) { + System.out.println(" Traffic: " + traffic); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/vnt-jni/java_example/com/vnt/RegisterResult.java b/vnt-jni/java_example/com/vnt/RegisterResult.java new file mode 100644 index 0000000..fe69f86 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/RegisterResult.java @@ -0,0 +1,97 @@ +package com.vnt; + +import org.json.JSONObject; + +/** + * 注册结果 + * + * 包含服务器分配的IP地址、掩码等信息 + * 注意:如果能创建此对象,说明注册一定成功了(失败会抛异常) + */ +public class RegisterResult { + + private final String ip; + private final int prefixLen; + private final String gateway; + private final String broadcast; + + private RegisterResult(String ip, int prefixLen, String gateway, String broadcast) { + this.ip = ip; + this.prefixLen = prefixLen; + this.gateway = gateway; + this.broadcast = broadcast; + } + + /** + * 从JSON字符串解析注册结果 + * @throws VntException 如果注册失败或解析失败 + */ + static RegisterResult fromJson(String json) throws VntException { + try { + JSONObject obj = new JSONObject(json); + boolean success = obj.getBoolean("success"); + + if (success) { + return new RegisterResult( + obj.getString("ip"), + obj.getInt("prefix_len"), + obj.getString("gateway"), + obj.getString("broadcast") + ); + } else { + // 注册失败,抛出异常 + String error = obj.getString("error"); + throw new VntException("Registration failed: " + error); + } + } catch (VntException e) { + throw e; // 重新抛出VntException + } catch (Exception e) { + throw new VntException("Failed to parse register result: " + e.getMessage(), e); + } + } + + /** + * 获取分配的IP地址 + */ + public String getIp() { + return ip; + } + + /** + * 获取前缀长度(掩码位数) + */ + public int getPrefixLen() { + return prefixLen; + } + + /** + * 获取网关地址 + */ + public String getGateway() { + return gateway; + } + + /** + * 获取广播地址 + */ + public String getBroadcast() { + return broadcast; + } + + /** + * 转换为CIDR格式字符串(例如:10.0.0.2/24) + */ + public String toCidr() { + return ip + "/" + prefixLen; + } + + @Override + public String toString() { + return "RegisterResult{" + + "ip='" + ip + '\'' + + ", prefixLen=" + prefixLen + + ", gateway='" + gateway + '\'' + + ", broadcast='" + broadcast + '\'' + + '}'; + } +} diff --git a/vnt-jni/java_example/com/vnt/VntApi.java b/vnt-jni/java_example/com/vnt/VntApi.java new file mode 100644 index 0000000..aebad29 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/VntApi.java @@ -0,0 +1,428 @@ +package com.vnt; + +import org.json.JSONArray; +import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; + +/** + * VNT API接口 - 用于查询网络状态和信息 + * + * 通过VntNetwork.getApi()获取实例 + */ +public class VntApi { + + private final long nativeHandle; + + // 包内构造,只能通过VntNetwork创建 + VntApi(long handle) { + this.nativeHandle = handle; + } + + /** + * 获取客户端列表 + * @return 客户端信息列表 + */ + public List getClientList() throws VntException { + try { + String json = nativeGetClientList(nativeHandle); + JSONArray array = new JSONArray(json); + List clients = new ArrayList<>(); + + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + clients.add(new ClientInfo( + obj.getString("ip"), + obj.getBoolean("online") + )); + } + return clients; + } catch (Exception e) { + throw new VntException("Failed to get client list: " + e.getMessage(), e); + } + } + + /** + * 获取当前网络配置 + * @return 网络信息,未连接返回null + */ + public NetworkInfo getNetwork() throws VntException { + try { + String json = nativeGetNetwork(nativeHandle); + if ("null".equals(json)) { + return null; + } + JSONObject obj = new JSONObject(json); + return new NetworkInfo( + obj.getString("ip"), + obj.getInt("prefix_len"), + obj.getString("gateway"), + obj.getString("broadcast") + ); + } catch (Exception e) { + throw new VntException("Failed to get network info: " + e.getMessage(), e); + } + } + + /** + * 获取本地NAT信息 + * @return NAT信息,未检测到返回null + */ + public NatInfo getNatInfo() throws VntException { + try { + String json = nativeGetNatInfo(nativeHandle); + if ("null".equals(json)) { + return null; + } + return NatInfo.fromJson(json); + } catch (Exception e) { + throw new VntException("Failed to get NAT info: " + e.getMessage(), e); + } + } + + /** + * 获取服务器节点列表 + * @return 服务器信息列表 + */ + public List getServerList() throws VntException { + try { + String json = nativeGetServerList(nativeHandle); + JSONArray array = new JSONArray(json); + List servers = new ArrayList<>(); + + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + servers.add(new ServerInfo( + obj.getInt("server_id"), + obj.getString("server_addr"), + obj.getBoolean("connected"), + obj.isNull("rtt") ? null : obj.getInt("rtt"), + obj.getLong("data_version"), + obj.isNull("server_version") ? null : obj.getString("server_version") + )); + } + return servers; + } catch (Exception e) { + throw new VntException("Failed to get server list: " + e.getMessage(), e); + } + } + + /** + * 获取路由表 + * @return 路由信息列表 + */ + public List getRouteTable() throws VntException { + try { + String json = nativeGetRouteTable(nativeHandle); + JSONArray array = new JSONArray(json); + List routes = new ArrayList<>(); + + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + String ip = obj.getString("ip"); + JSONArray routesArray = obj.getJSONArray("routes"); + + List details = new ArrayList<>(); + for (int j = 0; j < routesArray.length(); j++) { + JSONObject route = routesArray.getJSONObject(j); + details.add(new RouteDetail( + route.getString("route_key"), + route.getString("protocol"), + route.getInt("metric"), + route.getInt("rtt") + )); + } + routes.add(new RouteInfo(ip, details)); + } + return routes; + } catch (Exception e) { + throw new VntException("Failed to get route table: " + e.getMessage(), e); + } + } + + /** + * 检查目标IP是否直连(P2P) + * @param ip 目标IP地址 + * @return true表示直连,false表示通过服务器中转 + */ + public boolean isDirect(String ip) { + return nativeIsDirect(nativeHandle, ip); + } + + /** + * 获取对端NAT信息 + * @param ip 目标IP地址 + * @return NAT信息,未知返回null + */ + public NatInfo getPeerNatInfo(String ip) throws VntException { + try { + String json = nativeGetPeerNatInfo(nativeHandle, ip); + if ("null".equals(json)) { + return null; + } + return NatInfo.fromJson(json); + } catch (Exception e) { + throw new VntException("Failed to get peer NAT info: " + e.getMessage(), e); + } + } + + /** + * 获取对端丢包信息 + * @param ip 目标IP地址 + * @return 丢包信息,未知返回null + */ + public PacketLossInfo getPacketLoss(String ip) throws VntException { + try { + String json = nativeGetPacketLoss(nativeHandle, ip); + if ("null".equals(json)) { + return null; + } + JSONObject obj = new JSONObject(json); + return new PacketLossInfo( + obj.getString("ip"), + obj.getLong("sent"), + obj.getLong("received"), + obj.getDouble("loss_rate") + ); + } catch (Exception e) { + throw new VntException("Failed to get packet loss: " + e.getMessage(), e); + } + } + + /** + * 获取对端流量统计 + * @param ip 目标IP地址 + * @return 流量信息,未知返回null + */ + public TrafficInfo getTrafficInfo(String ip) throws VntException { + try { + String json = nativeGetTrafficInfo(nativeHandle, ip); + if ("null".equals(json)) { + return null; + } + JSONObject obj = new JSONObject(json); + return new TrafficInfo( + obj.getString("ip"), + obj.getLong("tx_bytes"), + obj.getLong("rx_bytes") + ); + } catch (Exception e) { + throw new VntException("Failed to get traffic info: " + e.getMessage(), e); + } + } + + // ========== Native 方法 ========== + + private static native String nativeGetClientList(long apiHandle); + private static native String nativeGetNetwork(long apiHandle); + private static native String nativeGetNatInfo(long apiHandle); + private static native String nativeGetServerList(long apiHandle); + private static native String nativeGetRouteTable(long apiHandle); + private static native boolean nativeIsDirect(long apiHandle, String ip); + private static native String nativeGetPeerNatInfo(long apiHandle, String ip); + private static native String nativeGetPacketLoss(long apiHandle, String ip); + private static native String nativeGetTrafficInfo(long apiHandle, String ip); + + // ========== 数据类 ========== + + public static class ClientInfo { + private final String ip; + private final boolean online; + + public ClientInfo(String ip, boolean online) { + this.ip = ip; + this.online = online; + } + + public String getIp() { return ip; } + public boolean isOnline() { return online; } + + @Override + public String toString() { + return "ClientInfo{ip='" + ip + "', online=" + online + "}"; + } + } + + public static class NetworkInfo { + private final String ip; + private final int prefixLen; + private final String gateway; + private final String broadcast; + + public NetworkInfo(String ip, int prefixLen, String gateway, String broadcast) { + this.ip = ip; + this.prefixLen = prefixLen; + this.gateway = gateway; + this.broadcast = broadcast; + } + + public String getIp() { return ip; } + public int getPrefixLen() { return prefixLen; } + public String getGateway() { return gateway; } + public String getBroadcast() { return broadcast; } + + @Override + public String toString() { + return "NetworkInfo{ip='" + ip + "', prefixLen=" + prefixLen + + ", gateway='" + gateway + "', broadcast='" + broadcast + "'}"; + } + } + + public static class NatInfo { + private final String natType; + private final List publicIps; + private final String ipv6; + + private NatInfo(String natType, List publicIps, String ipv6) { + this.natType = natType; + this.publicIps = publicIps; + this.ipv6 = ipv6; + } + + static NatInfo fromJson(String json) throws Exception { + JSONObject obj = new JSONObject(json); + JSONArray ipsArray = obj.getJSONArray("public_ips"); + List publicIps = new ArrayList<>(); + for (int i = 0; i < ipsArray.length(); i++) { + publicIps.add(ipsArray.getString(i)); + } + return new NatInfo( + obj.getString("nat_type"), + publicIps, + obj.isNull("ipv6") ? null : obj.getString("ipv6") + ); + } + + public String getNatType() { return natType; } + public List getPublicIps() { return publicIps; } + public String getIpv6() { return ipv6; } + + @Override + public String toString() { + return "NatInfo{natType='" + natType + "', publicIps=" + publicIps + + ", ipv6='" + ipv6 + "'}"; + } + } + + public static class ServerInfo { + private final int serverId; + private final String serverAddr; + private final boolean connected; + private final Integer rtt; + private final long dataVersion; + private final String serverVersion; + + public ServerInfo(int serverId, String serverAddr, boolean connected, + Integer rtt, long dataVersion, String serverVersion) { + this.serverId = serverId; + this.serverAddr = serverAddr; + this.connected = connected; + this.rtt = rtt; + this.dataVersion = dataVersion; + this.serverVersion = serverVersion; + } + + public int getServerId() { return serverId; } + public String getServerAddr() { return serverAddr; } + public boolean isConnected() { return connected; } + public Integer getRtt() { return rtt; } + public long getDataVersion() { return dataVersion; } + public String getServerVersion() { return serverVersion; } + + @Override + public String toString() { + return "ServerInfo{serverId=" + serverId + ", serverAddr='" + serverAddr + + "', connected=" + connected + ", rtt=" + rtt + "}"; + } + } + + public static class RouteInfo { + private final String ip; + private final List routes; + + public RouteInfo(String ip, List routes) { + this.ip = ip; + this.routes = routes; + } + + public String getIp() { return ip; } + public List getRoutes() { return routes; } + + @Override + public String toString() { + return "RouteInfo{ip='" + ip + "', routes=" + routes + "}"; + } + } + + public static class RouteDetail { + private final String routeKey; + private final String protocol; + private final int metric; + private final int rtt; + + public RouteDetail(String routeKey, String protocol, int metric, int rtt) { + this.routeKey = routeKey; + this.protocol = protocol; + this.metric = metric; + this.rtt = rtt; + } + + public String getRouteKey() { return routeKey; } + public String getProtocol() { return protocol; } + public int getMetric() { return metric; } + public int getRtt() { return rtt; } + + @Override + public String toString() { + return "RouteDetail{routeKey='" + routeKey + "', protocol='" + protocol + + "', metric=" + metric + ", rtt=" + rtt + "}"; + } + } + + public static class PacketLossInfo { + private final String ip; + private final long sent; + private final long received; + private final double lossRate; + + public PacketLossInfo(String ip, long sent, long received, double lossRate) { + this.ip = ip; + this.sent = sent; + this.received = received; + this.lossRate = lossRate; + } + + public String getIp() { return ip; } + public long getSent() { return sent; } + public long getReceived() { return received; } + public double getLossRate() { return lossRate; } + + @Override + public String toString() { + return "PacketLossInfo{ip='" + ip + "', sent=" + sent + + ", received=" + received + ", lossRate=" + lossRate + "}"; + } + } + + public static class TrafficInfo { + private final String ip; + private final long txBytes; + private final long rxBytes; + + public TrafficInfo(String ip, long txBytes, long rxBytes) { + this.ip = ip; + this.txBytes = txBytes; + this.rxBytes = rxBytes; + } + + public String getIp() { return ip; } + public long getTxBytes() { return txBytes; } + public long getRxBytes() { return rxBytes; } + + @Override + public String toString() { + return "TrafficInfo{ip='" + ip + "', txBytes=" + txBytes + + ", rxBytes=" + rxBytes + "}"; + } + } +} diff --git a/vnt-jni/java_example/com/vnt/VntConfig.java b/vnt-jni/java_example/com/vnt/VntConfig.java new file mode 100644 index 0000000..93cccf5 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/VntConfig.java @@ -0,0 +1,310 @@ +package com.vnt; + +import org.json.JSONArray; +import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; + +/** + * VNT网络配置 + * + * 使用Builder模式构建配置 + */ +public class VntConfig { + + private final List servers; + private final String networkCode; + private final String password; + private final String deviceId; + private final String deviceName; + private final String tunName; + private final String ip; + private final String certMode; + private final boolean noPunch; + private final boolean compress; + private final boolean rtx; + private final boolean fec; + private final boolean noNat; + private final boolean noTun; + private final Integer mtu; + private final boolean allowMapping; + private final List portMapping; + private final List udpStun; + private final List tcpStun; + + private VntConfig(Builder builder) { + this.servers = builder.servers; + this.networkCode = builder.networkCode; + this.password = builder.password; + this.deviceId = builder.deviceId; + this.deviceName = builder.deviceName; + this.tunName = builder.tunName; + this.ip = builder.ip; + this.certMode = builder.certMode; + this.noPunch = builder.noPunch; + this.compress = builder.compress; + this.rtx = builder.rtx; + this.fec = builder.fec; + this.noNat = builder.noNat; + this.noTun = builder.noTun; + this.mtu = builder.mtu; + this.allowMapping = builder.allowMapping; + this.portMapping = builder.portMapping; + this.udpStun = builder.udpStun; + this.tcpStun = builder.tcpStun; + } + + /** + * 转换为JSON字符串 + */ + String toJson() { + JSONObject json = new JSONObject(); + + // 必填项 + JSONArray serverArray = new JSONArray(); + for (String server : servers) { + serverArray.put(server); + } + json.put("server", serverArray); + json.put("network_code", networkCode); + + // 可选项 + if (password != null) json.put("password", password); + if (deviceId != null) json.put("device_id", deviceId); + if (deviceName != null) json.put("device_name", deviceName); + if (tunName != null) json.put("tun_name", tunName); + if (ip != null) json.put("ip", ip); + if (certMode != null) json.put("cert_mode", certMode); + if (mtu != null) json.put("mtu", mtu); + + // 布尔值 + json.put("no_punch", noPunch); + json.put("compress", compress); + json.put("rtx", rtx); + json.put("fec", fec); + json.put("no_nat", noNat); + json.put("no_tun", noTun); + json.put("allow_mapping", allowMapping); + + // 数组 + if (!portMapping.isEmpty()) { + JSONArray mappingArray = new JSONArray(); + for (String mapping : portMapping) { + mappingArray.put(mapping); + } + json.put("port_mapping", mappingArray); + } + + if (!udpStun.isEmpty()) { + JSONArray stunArray = new JSONArray(); + for (String stun : udpStun) { + stunArray.put(stun); + } + json.put("udp_stun", stunArray); + } + + if (!tcpStun.isEmpty()) { + JSONArray stunArray = new JSONArray(); + for (String stun : tcpStun) { + stunArray.put(stun); + } + json.put("tcp_stun", stunArray); + } + + return json.toString(); + } + + /** + * 配置构建器 + */ + public static class Builder { + private List servers = new ArrayList<>(); + private String networkCode; + private String password; + private String deviceId; + private String deviceName; + private String tunName; + private String ip; + private String certMode; + private boolean noPunch = false; + private boolean compress = false; + private boolean rtx = false; + private boolean fec = false; + private boolean noNat = false; + private boolean noTun = false; + private Integer mtu; + private boolean allowMapping = false; + private List portMapping = new ArrayList<>(); + private List udpStun = new ArrayList<>(); + private List tcpStun = new ArrayList<>(); + + /** + * 添加服务器地址(必填) + * @param server 服务器地址,格式:tcp://host:port 或 wss://host:port + */ + public Builder addServer(String server) { + this.servers.add(server); + return this; + } + + /** + * 设置网络代码(必填) + * @param networkCode 组网代码 + */ + public Builder setNetworkCode(String networkCode) { + this.networkCode = networkCode; + return this; + } + + /** + * 设置密码(可选) + */ + public Builder setPassword(String password) { + this.password = password; + return this; + } + + /** + * 设置设备ID(可选,默认自动生成) + */ + public Builder setDeviceId(String deviceId) { + this.deviceId = deviceId; + return this; + } + + /** + * 设置设备名称(可选) + */ + public Builder setDeviceName(String deviceName) { + this.deviceName = deviceName; + return this; + } + + /** + * 设置TUN设备名称(可选) + */ + public Builder setTunName(String tunName) { + this.tunName = tunName; + return this; + } + + /** + * 设置固定IP(可选) + */ + public Builder setIp(String ip) { + this.ip = ip; + return this; + } + + /** + * 设置证书验证模式(可选) + * @param certMode "insecure" | "system" | "embedded" + */ + public Builder setCertMode(String certMode) { + this.certMode = certMode; + return this; + } + + /** + * 禁用打洞(默认false) + */ + public Builder setNoPunch(boolean noPunch) { + this.noPunch = noPunch; + return this; + } + + /** + * 启用压缩(默认false) + */ + public Builder setCompress(boolean compress) { + this.compress = compress; + return this; + } + + /** + * 启用QUIC重传(默认false) + */ + public Builder setRtx(boolean rtx) { + this.rtx = rtx; + return this; + } + + /** + * 启用FEC冗余传输(默认false) + */ + public Builder setFec(boolean fec) { + this.fec = fec; + return this; + } + + /** + * 禁用NAT(默认false) + */ + public Builder setNoNat(boolean noNat) { + this.noNat = noNat; + return this; + } + + /** + * 无TUN模式(默认false) + */ + public Builder setNoTun(boolean noTun) { + this.noTun = noTun; + return this; + } + + /** + * 设置MTU(可选,默认1380) + */ + public Builder setMtu(int mtu) { + this.mtu = mtu; + return this; + } + + /** + * 允许端口映射(默认false) + */ + public Builder setAllowMapping(boolean allowMapping) { + this.allowMapping = allowMapping; + return this; + } + + /** + * 添加端口映射规则(可选) + * @param mapping 格式:tcp:80->192.168.1.100:8080 + */ + public Builder addPortMapping(String mapping) { + this.portMapping.add(mapping); + return this; + } + + /** + * 添加UDP STUN服务器(可选) + */ + public Builder addUdpStun(String stun) { + this.udpStun.add(stun); + return this; + } + + /** + * 添加TCP STUN服务器(可选) + */ + public Builder addTcpStun(String stun) { + this.tcpStun.add(stun); + return this; + } + + /** + * 构建配置对象 + */ + public VntConfig build() { + if (servers.isEmpty()) { + throw new IllegalArgumentException("At least one server must be specified"); + } + if (networkCode == null || networkCode.isEmpty()) { + throw new IllegalArgumentException("Network code must be specified"); + } + return new VntConfig(this); + } + } +} diff --git a/vnt-jni/java_example/com/vnt/VntException.java b/vnt-jni/java_example/com/vnt/VntException.java new file mode 100644 index 0000000..5b3de95 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/VntException.java @@ -0,0 +1,21 @@ +package com.vnt; + +/** + * VNT异常 + * + * VNT操作失败时抛出的异常 + */ +public class VntException extends Exception { + + public VntException(String message) { + super(message); + } + + public VntException(String message, Throwable cause) { + super(message, cause); + } + + public VntException(Throwable cause) { + super(cause); + } +} diff --git a/vnt-jni/java_example/com/vnt/VntManager.java b/vnt-jni/java_example/com/vnt/VntManager.java new file mode 100644 index 0000000..0f12703 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/VntManager.java @@ -0,0 +1,56 @@ +package com.vnt; + +/** + * VNT网络管理器 - 主入口类 + * + * 使用示例: + * 1. 初始化: VntManager.init() + * 2. 创建网络: VntNetwork network = VntManager.createNetwork(config) + * 3. 注册: RegisterResult result = network.register() + * 4. (Android端用result的IP/掩码创建VPN接口,获取tunFd) + * 5. 启动TUN: network.startTun(tunFd) + * 6. 获取API: VntApi api = network.getApi() + * 7. 关闭: network.stop() + */ +public class VntManager { + + static { + // 加载JNI库 + System.loadLibrary("vnt_jni"); + } + + /** + * 初始化VNT模块(全局初始化,只需调用一次) + * @return true表示成功,false表示失败 + */ + public static boolean init() { + return nativeInit(); + } + + /** + * 销毁VNT模块(全局清理) + */ + public static void destroy() { + nativeDestroy(); + } + + /** + * 创建网络实例 + * @param config 网络配置对象 + * @return VntNetwork实例,失败返回null + */ + public static VntNetwork createNetwork(VntConfig config) { + String configJson = config.toJson(); + long handle = nativeCreateNetwork(configJson); + if (handle < 0) { + return null; + } + return new VntNetwork(handle); + } + + // ========== Native 方法 ========== + + private static native boolean nativeInit(); + private static native void nativeDestroy(); + private static native long nativeCreateNetwork(String configJson); +} diff --git a/vnt-jni/java_example/com/vnt/VntNetwork.java b/vnt-jni/java_example/com/vnt/VntNetwork.java new file mode 100644 index 0000000..caedc40 --- /dev/null +++ b/vnt-jni/java_example/com/vnt/VntNetwork.java @@ -0,0 +1,122 @@ +package com.vnt; + +/** + * VNT网络实例 + * + * 代表一个VNT网络连接,持有native资源 + */ +public class VntNetwork { + + private long nativeHandle; + private boolean closed = false; + + // 包内构造,只能通过VntManager创建 + VntNetwork(long handle) { + this.nativeHandle = handle; + } + + /** + * 注册网络(连接服务器) + * @return 注册结果,包含分配的IP、掩码等信息 + * @throws VntException 注册失败时抛出异常 + */ + public RegisterResult register() throws VntException { + checkClosed(); + String resultJson = nativeRegister(nativeHandle); + return RegisterResult.fromJson(resultJson); + } + + /** + * 启动TUN设备 + * @param tunFd TUN设备文件描述符(Android VpnService.Builder.establish()返回的fd) + * 传入-1表示让VNT自动创建(仅非Android平台支持) + * @throws VntException 启动失败时抛出异常 + */ + public void startTun(int tunFd) throws VntException { + checkClosed(); + if (!nativeStartTun(nativeHandle, tunFd)) { + throw new VntException("Failed to start TUN device"); + } + } + + /** + * 设置网络IP(仅非Android平台使用) + * @param ip IP地址 + * @param prefixLen 前缀长度 + * @throws VntException 设置失败时抛出异常 + */ + public void setNetworkIp(String ip, int prefixLen) throws VntException { + checkClosed(); + if (!nativeSetNetworkIp(nativeHandle, ip, prefixLen)) { + throw new VntException("Failed to set network IP"); + } + } + + /** + * 获取VNT API实例 + * @return VntApi实例 + * @throws VntException 获取失败时抛出异常 + */ + public VntApi getApi() throws VntException { + checkClosed(); + long apiHandle = nativeGetApi(nativeHandle); + if (apiHandle < 0) { + throw new VntException("Failed to get VntApi"); + } + return new VntApi(apiHandle); + } + + /** + * 检查是否为无TUN模式 + * @return true表示无TUN模式 + */ + public boolean isNoTun() { + checkClosed(); + return nativeIsNoTun(nativeHandle); + } + + /** + * 停止并关闭网络 + */ + public void stop() { + if (closed) { + return; + } + nativeStop(nativeHandle); + closed = true; + } + + /** + * 获取native句柄(供内部使用) + */ + long getNativeHandle() { + return nativeHandle; + } + + /** + * 检查是否已关闭 + */ + private void checkClosed() { + if (closed) { + throw new IllegalStateException("VntNetwork has been closed"); + } + } + + @Override + protected void finalize() throws Throwable { + try { + stop(); + } finally { + super.finalize(); + } + } + + // ========== Native 方法 ========== + + private static native String nativeRegister(long handle); + private static native boolean nativeStartTun(long handle, int tunFd); + private static native boolean nativeSetNetworkIp(long handle, String ip, int prefixLen); + private static native long nativeGetApi(long handle); + private static native boolean nativeIsNoTun(long handle); + private static native boolean nativeStop(long handle); +} diff --git a/vnt-jni/src/lib.rs b/vnt-jni/src/lib.rs new file mode 100644 index 0000000..c8e47de --- /dev/null +++ b/vnt-jni/src/lib.rs @@ -0,0 +1,966 @@ +use anyhow::Context; +use jni::objects::{JClass, JObject, JString}; +use jni::sys::{jboolean, jint, jlong, jstring}; +use jni::JNIEnv; +use parking_lot::Mutex; +use std::collections::HashMap; +use std::net::Ipv4Addr; +use std::sync::Arc; +use tokio::runtime::Runtime; +use vnt_core::api::VntApi; +use vnt_core::context::config::Config; +use vnt_core::core::{NetworkManager, RegisterResponse}; +use vnt_core::nat::NetInput; +use vnt_core::port_mapping::PortMapping; +use vnt_core::tls::verifier::CertValidationMode; +use vnt_core::tunnel_core::server::transport::config::ProtocolAddress; +use vnt_core::utils::task_control::TaskGroupManager; + +/// 全局状态管理 +struct GlobalState { + /// Tokio运行时(Arc包装以便多线程访问) + runtime: Arc, + /// 网络管理器实例 + network_managers: HashMap>>>, + /// API实例 + vnt_apis: HashMap, + /// 任务组管理器 + task_group_managers: HashMap, + /// 下一个实例ID + next_id: i64, +} + +impl GlobalState { + fn new() -> anyhow::Result { + Ok(Self { + runtime: Arc::new(Runtime::new()?), + network_managers: HashMap::new(), + vnt_apis: HashMap::new(), + task_group_managers: HashMap::new(), + next_id: 1, + }) + } +} + +lazy_static::lazy_static! { + static ref GLOBAL_STATE: Mutex> = Mutex::new(None); +} + +/// 初始化JNI模块 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntManager_nativeInit( + mut env: JNIEnv, + _class: JClass, +) -> jboolean { + let mut state = GLOBAL_STATE.lock(); + if state.is_some() { + return 1; // 已经初始化 + } + + match GlobalState::new() { + Ok(global_state) => { + *state = Some(global_state); + 1 + } + Err(e) => { + let _ = env.throw(format!("Failed to initialize VNT: {:?}", e)); + 0 + } + } +} + +/// 销毁JNI模块 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntManager_nativeDestroy( + _env: JNIEnv, + _class: JClass, +) { + let mut state = GLOBAL_STATE.lock(); + *state = None; +} + +/// 创建网络实例 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntManager_nativeCreateNetwork<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + config_json: JString<'local>, +) -> jlong { + let result: anyhow::Result = (|| { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + // 解析配置JSON + let config_str: String = env + .get_string(&config_json)? + .into(); + let config = parse_config_from_json(&config_str)?; + + // 创建任务组 + let task_group_manager = TaskGroupManager::new(); + let (task_group, _task_group_guard) = task_group_manager + .create_task() + .context("create task group")?; + + // 获取runtime的clone + let runtime = state.runtime.clone(); + + // 创建网络管理器 + let network_manager = runtime.block_on(async { + NetworkManager::create_network(Box::new(config), task_group).await + })?; + + // 分配ID + let id = state.next_id; + state.next_id += 1; + + // 保存实例 + state.network_managers.insert(id, Arc::new(Mutex::new(Some(network_manager)))); + state.task_group_managers.insert(id, task_group_manager); + + Ok(id) + })(); + + match result { + Ok(id) => id, + Err(e) => { + let _ = env.throw(format!("Failed to create network: {:?}", e)); + -1 + } + } +} + +/// 注册网络 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeRegister<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let (network_manager_arc, runtime) = { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + let network_manager_arc = state + .network_managers + .get(&handle) + .context("Invalid handle")? + .clone(); + + let runtime = state.runtime.clone(); + (network_manager_arc, runtime) + }; + + let response = { + let mut manager_lock = network_manager_arc.lock(); + let manager = manager_lock + .as_mut() + .context("Network manager already destroyed")?; + + runtime.block_on(async { + manager.register().await + })? + }; + + match response { + RegisterResponse::Success(network_addr) => { + let response_json = serde_json::json!({ + "success": true, + "ip": network_addr.ip.to_string(), + "prefix_len": network_addr.prefix_len, + "gateway": network_addr.gateway.to_string(), + "broadcast": network_addr.broadcast.to_string(), + }); + Ok(response_json.to_string()) + } + RegisterResponse::Failed(error_msg) => { + let response_json = serde_json::json!({ + "success": false, + "error": error_msg.message, + }); + Ok(response_json.to_string()) + } + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to register: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 启动TUN设备(Android使用,需要传入fd) +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeStartTun( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + tun_fd: jint, +) -> jboolean { + let result: anyhow::Result<()> = (|| { + let (network_manager_arc, runtime) = { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + let network_manager_arc = state + .network_managers + .get(&handle) + .context("Invalid handle")? + .clone(); + + let runtime = state.runtime.clone(); + (network_manager_arc, runtime) + }; + + let mut manager_lock = network_manager_arc.lock(); + let manager = manager_lock + .as_mut() + .context("Network manager already destroyed")?; + + #[cfg(unix)] + { + let tun_fd = if tun_fd < 0 { None } else { Some(tun_fd) }; + runtime.block_on(async { + manager.start_tun_fd(tun_fd).await + })?; + } + + #[cfg(not(unix))] + { + let _ = tun_fd; // 避免未使用警告 + runtime.block_on(async { + manager.start_tun().await + })?; + } + + Ok(()) + })(); + + match result { + Ok(_) => 1, + Err(e) => { + let _ = env.throw(format!("Failed to start TUN: {:?}", e)); + 0 + } + } +} + +/// 设置网络IP(非Android系统) +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeSetNetworkIp<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + handle: jlong, + ip: JString<'local>, + prefix_len: jint, +) -> jboolean { + let result: anyhow::Result<()> = (|| { + let (network_manager_arc, runtime) = { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + let network_manager_arc = state + .network_managers + .get(&handle) + .context("Invalid handle")? + .clone(); + + let runtime = state.runtime.clone(); + (network_manager_arc, runtime) + }; + + let ip_str: String = env.get_string(&ip)?.into(); + let ip_addr: Ipv4Addr = ip_str.parse() + .context("Invalid IP address")?; + + let manager_lock = network_manager_arc.lock(); + let manager = manager_lock + .as_ref() + .context("Network manager already destroyed")?; + + #[cfg(not(target_os = "android"))] + { + runtime.block_on(async { + manager.set_tun_network_ip(ip_addr, prefix_len as u8).await + })?; + } + + #[cfg(target_os = "android")] + { + let _ = (ip_addr, prefix_len); // 避免未使用警告 + anyhow::bail!("set_network_ip is not supported on Android"); + } + + Ok(()) + })(); + + match result { + Ok(_) => 1, + Err(e) => { + let _ = env.throw(format!("Failed to set network IP: {:?}", e)); + 0 + } + } +} + +/// 获取VntApi实例 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeGetApi( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jlong { + let result: anyhow::Result = (|| { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + let network_manager_arc = state + .network_managers + .get(&handle) + .context("Invalid handle")? + .clone(); + + let api = { + let manager_lock = network_manager_arc.lock(); + let manager = manager_lock + .as_ref() + .context("Network manager already destroyed")?; + manager.vnt_api() + }; + + state.vnt_apis.insert(handle, api); + Ok(handle) + })(); + + match result { + Ok(id) => id, + Err(e) => { + let _ = env.throw(format!("Failed to get API: {:?}", e)); + -1 + } + } +} + +/// 检查是否为无TUN模式 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeIsNoTun( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jboolean { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let network_manager_arc = state + .network_managers + .get(&handle) + .context("Invalid handle")? + .clone(); + + let manager_lock = network_manager_arc.lock(); + let manager = manager_lock + .as_ref() + .context("Network manager already destroyed")?; + + Ok(manager.is_no_tun()) + })(); + + match result { + Ok(is_no_tun) => if is_no_tun { 1 } else { 0 }, + Err(e) => { + let _ = env.throw(format!("Failed to check no_tun: {:?}", e)); + 0 + } + } +} + +/// 关闭网络 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntNetwork_nativeStop( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jboolean { + let result: anyhow::Result<()> = (|| { + let mut global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_mut() + .context("VNT not initialized")?; + + // 停止任务组 + if let Some(task_group_manager) = state.task_group_managers.get(&handle) { + task_group_manager.stop(); + } + + // 移除网络管理器 + state.network_managers.remove(&handle); + state.vnt_apis.remove(&handle); + state.task_group_managers.remove(&handle); + + Ok(()) + })(); + + match result { + Ok(_) => 1, + Err(e) => { + let _ = env.throw(format!("Failed to stop network: {:?}", e)); + 0 + } + } +} + +// ==================== VntApi 接口 ==================== + +/// 获取客户端列表 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetClientList<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let client_ips = api.client_ips(); + let json_array: Vec<_> = client_ips + .into_iter() + .map(|client| { + serde_json::json!({ + "ip": client.ip.to_string(), + "online": client.online, + }) + }) + .collect(); + Ok(serde_json::to_string(&json_array)?) + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get client list: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取网络配置信息 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetNetwork<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + if let Some(network) = api.network() { + let json = serde_json::json!({ + "ip": network.ip.to_string(), + "prefix_len": network.prefix_len, + "gateway": network.gateway.to_string(), + "broadcast": network.broadcast.to_string(), + }); + Ok(json.to_string()) + } else { + Ok("null".to_string()) + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get network info: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取NAT信息 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetNatInfo<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + if let Some(nat_info) = api.nat_info() { + let json = serde_json::json!({ + "nat_type": format!("{:?}", nat_info.nat_type), + "public_ips": nat_info.public_ips.iter().map(|ip| ip.to_string()).collect::>(), + "ipv6": nat_info.ipv6.map(|ip| ip.to_string()), + }); + Ok(json.to_string()) + } else { + Ok("null".to_string()) + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get NAT info: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取服务器节点列表 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetServerList<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let servers = api.server_node_list(); + let json_array: Vec<_> = servers + .into_iter() + .map(|server| { + serde_json::json!({ + "server_id": server.server_id, + "server_addr": server.server_addr.to_string(), + "connected": server.connected, + "rtt": server.rtt, + "data_version": server.data_version, + "server_version": server.server_version, + }) + }) + .collect(); + Ok(serde_json::to_string(&json_array)?) + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get server list: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取路由表 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetRouteTable<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let route_table = api.route_table(); + let json_data: Vec<_> = route_table + .into_iter() + .map(|(ip, routes)| { + let routes_json: Vec<_> = routes + .into_iter() + .map(|route| { + serde_json::json!({ + "route_key": route.route_key().to_string(), + "protocol": route.route_key().protocol().to_string(), + "metric": route.metric(), + "rtt": route.rtt(), + }) + }) + .collect(); + serde_json::json!({ + "ip": ip.to_string(), + "routes": routes_json, + }) + }) + .collect(); + + Ok(serde_json::to_string(&json_data)?) + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get route table: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 检查目标IP是否直连 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeIsDirect<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, + ip: JString<'local>, +) -> jboolean { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let ip_str: String = env.get_string(&ip)?.into(); + let ip_addr: Ipv4Addr = ip_str.parse() + .context("Invalid IP address")?; + + Ok(api.is_direct(&ip_addr)) + })(); + + match result { + Ok(is_direct) => if is_direct { 1 } else { 0 }, + Err(e) => { + let _ = env.throw(format!("Failed to check direct: {:?}", e)); + 0 + } + } +} + +/// 获取对端NAT信息 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetPeerNatInfo<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, + ip: JString<'local>, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let ip_str: String = env.get_string(&ip)?.into(); + let ip_addr: Ipv4Addr = ip_str.parse() + .context("Invalid IP address")?; + + if let Some(nat_info) = api.peer_nat_info(&ip_addr) { + let json = serde_json::json!({ + "nat_type": format!("{:?}", nat_info.nat_type), + "public_ips": nat_info.public_ips.iter().map(|ip| ip.to_string()).collect::>(), + "ipv6": nat_info.ipv6.map(|ip| ip.to_string()), + }); + Ok(json.to_string()) + } else { + Ok("null".to_string()) + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get peer NAT info: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取丢包信息 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetPacketLoss<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, + ip: JString<'local>, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let ip_str: String = env.get_string(&ip)?.into(); + let ip_addr: Ipv4Addr = ip_str.parse() + .context("Invalid IP address")?; + + if let Some(loss_info) = api.packet_loss_info(&ip_addr) { + let json = serde_json::json!({ + "ip": loss_info.ip.to_string(), + "sent": loss_info.sent, + "received": loss_info.received, + "loss_rate": loss_info.loss_rate, + }); + Ok(json.to_string()) + } else { + Ok("null".to_string()) + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get packet loss: {:?}", e)); + JObject::null().into_raw() + } + } +} + +/// 获取流量信息 +#[no_mangle] +pub extern "system" fn Java_com_vnt_VntApi_nativeGetTrafficInfo<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + api_handle: jlong, + ip: JString<'local>, +) -> jstring { + let result: anyhow::Result = (|| { + let global_state = GLOBAL_STATE.lock(); + let state = global_state + .as_ref() + .context("VNT not initialized")?; + + let api = state + .vnt_apis + .get(&api_handle) + .context("Invalid API handle")?; + + let ip_str: String = env.get_string(&ip)?.into(); + let ip_addr: Ipv4Addr = ip_str.parse() + .context("Invalid IP address")?; + + if let Some(traffic_info) = api.traffic_info(&ip_addr) { + let json = serde_json::json!({ + "ip": traffic_info.ip.to_string(), + "tx_bytes": traffic_info.tx_bytes, + "rx_bytes": traffic_info.rx_bytes, + }); + Ok(json.to_string()) + } else { + Ok("null".to_string()) + } + })(); + + match result { + Ok(json_str) => env + .new_string(json_str) + .unwrap_or_else(|_| JObject::null().into()) + .into_raw(), + Err(e) => { + let _ = env.throw(format!("Failed to get traffic info: {:?}", e)); + JObject::null().into_raw() + } + } +} + +// ==================== 辅助函数 ==================== + +/// 从JSON字符串解析配置 +fn parse_config_from_json(json_str: &str) -> anyhow::Result { + #[derive(serde::Deserialize)] + struct ConfigJson { + server: Vec, + network_code: String, + #[serde(default)] + device_id: Option, + #[serde(default)] + device_name: Option, + #[serde(default)] + tun_name: Option, + #[serde(default)] + ip: Option, + #[serde(default)] + password: Option, + #[serde(default)] + cert_mode: Option, + #[serde(default)] + no_punch: bool, + #[serde(default)] + compress: bool, + #[serde(default)] + rtx: bool, + #[serde(default)] + fec: bool, + #[serde(default)] + input: Vec, + #[serde(default)] + output: Vec, + #[serde(default)] + no_nat: bool, + #[serde(default)] + no_tun: bool, + #[serde(default)] + mtu: Option, + #[serde(default)] + port_mapping: Vec, + #[serde(default)] + allow_mapping: bool, + #[serde(default)] + udp_stun: Vec, + #[serde(default)] + tcp_stun: Vec, + } + + let cfg: ConfigJson = serde_json::from_str(json_str)?; + + let server_addrs: Vec = cfg + .server + .iter() + .map(|s| { + s.parse() + .map_err(|e| anyhow::anyhow!("invalid server address '{}': {}", s, e)) + }) + .collect::>()?; + + let port_mapping: Vec = cfg + .port_mapping + .iter() + .map(|s| { + s.parse() + .map_err(|e| anyhow::anyhow!("invalid port_mapping '{}': {}", s, e)) + }) + .collect::>()?; + + let cert_mode = match cfg.cert_mode.as_deref() { + Some(s) => s + .parse() + .map_err(|e| anyhow::anyhow!("invalid cert_mode '{}': {}", s, e))?, + None => CertValidationMode::InsecureSkipVerification, + }; + + let device_id = match cfg.device_id { + Some(id) => id, + None => vnt_core::utils::device_id::get_device_id() + .map_err(|e| anyhow::anyhow!("failed to get device_id: {}", e))?, + }; + + let device_name = cfg.device_name.unwrap_or_else(|| { + hostname::get() + .ok() + .and_then(|v| v.into_string().ok()) + .unwrap_or_default() + }); + + let mut udp_stun = cfg.udp_stun; + for x in udp_stun.iter_mut() { + if !x.contains(':') { + x.push_str(":3478"); + } + } + + let mut tcp_stun = cfg.tcp_stun; + for x in tcp_stun.iter_mut() { + if !x.contains(':') { + x.push_str(":3478"); + } + } + + Ok(Config { + server_addr: server_addrs, + network_code: cfg.network_code, + ip: cfg.ip, + no_punch: cfg.no_punch, + rtx: cfg.rtx, + compress: cfg.compress, + device_id, + device_name, + tun_name: cfg.tun_name, + password: cfg.password, + cert_mode, + input: cfg.input, + output: cfg.output, + no_nat: cfg.no_nat, + no_tun: cfg.no_tun, + mtu: cfg.mtu, + port_mapping, + allow_port_mapping: cfg.allow_mapping, + udp_stun, + tcp_stun, + fec: cfg.fec, + }) +} diff --git a/vnt-web/src/service_http.rs b/vnt-web/src/service_http.rs index 460bad8..74a7c2d 100644 --- a/vnt-web/src/service_http.rs +++ b/vnt-web/src/service_http.rs @@ -591,7 +591,7 @@ async fn start_vnt_network( state.record_log("创建 TUN 虚拟网卡成功,设置 IP"); network_manager - .set_network_ip(reg_msg.ip, reg_msg.prefix_len) + .set_tun_network_ip(reg_msg.ip, reg_msg.prefix_len) .await?; state.record_log("设置 IP 成功");