jni
This commit is contained in:
Generated
+121
@@ -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"
|
||||
|
||||
+1
-1
@@ -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'
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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<ClientInfo> getClientList() throws VntException {
|
||||
try {
|
||||
String json = nativeGetClientList(nativeHandle);
|
||||
JSONArray array = new JSONArray(json);
|
||||
List<ClientInfo> 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<ServerInfo> getServerList() throws VntException {
|
||||
try {
|
||||
String json = nativeGetServerList(nativeHandle);
|
||||
JSONArray array = new JSONArray(json);
|
||||
List<ServerInfo> 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<RouteInfo> getRouteTable() throws VntException {
|
||||
try {
|
||||
String json = nativeGetRouteTable(nativeHandle);
|
||||
JSONArray array = new JSONArray(json);
|
||||
List<RouteInfo> 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<RouteDetail> 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<String> publicIps;
|
||||
private final String ipv6;
|
||||
|
||||
private NatInfo(String natType, List<String> 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<String> 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<String> 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<RouteDetail> routes;
|
||||
|
||||
public RouteInfo(String ip, List<RouteDetail> routes) {
|
||||
this.ip = ip;
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public List<RouteDetail> 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 + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> portMapping;
|
||||
private final List<String> udpStun;
|
||||
private final List<String> 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<String> 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<String> portMapping = new ArrayList<>();
|
||||
private List<String> udpStun = new ArrayList<>();
|
||||
private List<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Runtime>,
|
||||
/// 网络管理器实例
|
||||
network_managers: HashMap<i64, Arc<Mutex<Option<NetworkManager>>>>,
|
||||
/// API实例
|
||||
vnt_apis: HashMap<i64, VntApi>,
|
||||
/// 任务组管理器
|
||||
task_group_managers: HashMap<i64, TaskGroupManager>,
|
||||
/// 下一个实例ID
|
||||
next_id: i64,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
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<Option<GlobalState>> = 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<i64> = (|| {
|
||||
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<String> = (|| {
|
||||
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<i64> = (|| {
|
||||
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<bool> = (|| {
|
||||
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<String> = (|| {
|
||||
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<String> = (|| {
|
||||
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<String> = (|| {
|
||||
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::<Vec<_>>(),
|
||||
"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<String> = (|| {
|
||||
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<String> = (|| {
|
||||
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<bool> = (|| {
|
||||
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<String> = (|| {
|
||||
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::<Vec<_>>(),
|
||||
"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<String> = (|| {
|
||||
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<String> = (|| {
|
||||
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<Config> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ConfigJson {
|
||||
server: Vec<String>,
|
||||
network_code: String,
|
||||
#[serde(default)]
|
||||
device_id: Option<String>,
|
||||
#[serde(default)]
|
||||
device_name: Option<String>,
|
||||
#[serde(default)]
|
||||
tun_name: Option<String>,
|
||||
#[serde(default)]
|
||||
ip: Option<Ipv4Addr>,
|
||||
#[serde(default)]
|
||||
password: Option<String>,
|
||||
#[serde(default)]
|
||||
cert_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
no_punch: bool,
|
||||
#[serde(default)]
|
||||
compress: bool,
|
||||
#[serde(default)]
|
||||
rtx: bool,
|
||||
#[serde(default)]
|
||||
fec: bool,
|
||||
#[serde(default)]
|
||||
input: Vec<NetInput>,
|
||||
#[serde(default)]
|
||||
output: Vec<ipnet::Ipv4Net>,
|
||||
#[serde(default)]
|
||||
no_nat: bool,
|
||||
#[serde(default)]
|
||||
no_tun: bool,
|
||||
#[serde(default)]
|
||||
mtu: Option<u16>,
|
||||
#[serde(default)]
|
||||
port_mapping: Vec<String>,
|
||||
#[serde(default)]
|
||||
allow_mapping: bool,
|
||||
#[serde(default)]
|
||||
udp_stun: Vec<String>,
|
||||
#[serde(default)]
|
||||
tcp_stun: Vec<String>,
|
||||
}
|
||||
|
||||
let cfg: ConfigJson = serde_json::from_str(json_str)?;
|
||||
|
||||
let server_addrs: Vec<ProtocolAddress> = cfg
|
||||
.server
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.parse()
|
||||
.map_err(|e| anyhow::anyhow!("invalid server address '{}': {}", s, e))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let port_mapping: Vec<PortMapping> = cfg
|
||||
.port_mapping
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.parse()
|
||||
.map_err(|e| anyhow::anyhow!("invalid port_mapping '{}': {}", s, e))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -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 成功");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user