移植一个openvpn3的c++库到HarmonyOS 鸿蒙Next,最后一步虚拟网卡(TUN)卡流程了,大佬们该怎么解决?
移植一个openvpn3的c++库到HarmonyOS 鸿蒙Next,最后一步虚拟网卡(TUN)卡流程了,大佬们该怎么解决? 现状: 有提示tun连接问题,但不确定该怎么处理了:
- Error: can’t call NetlinkLinkSet with no interface
- Error: can’t call NetlinkAddr4 with no interface
- Error: can’t call NetlinkRoute4 with no interface
求大佬们指点!!!!
openvpn3 踩坑过程记录:
- 准备VB Ubuntu 20.23环境,并安装交叉编译工具和各类所需环境
- 编译openvpn3 使用HarmonyOS lyium交叉编译工具
- thirdparty 包含许多参与交叉编译的第三方库,如果有缺失,按官方流程添加
- 编译指定arm64-8a的.so文件
- openvpn3的MakeFiles文件修改,优化成打包.so文件
- 修改 cli.cpp 的对外封装方法,暴露C方法,dlopen 可以打开的方式
- 新建 HarmonyOS 工程,定义cpp方法和libentry的.so文件和CmakeList.txt
- HarmonyOS 通过napi_init.cpp中的dlopen调用libovpn.so文件
- 处理数据传输问题,通过Node API的方式传参转换
- 新增 VpnExtensionAbility 来开启VPN,并通过vpnExtension.createVpnConnection创建虚拟网卡(TUN),并把tunFD值传给openvpn3
- 处理 vpnExtension.createVpnConnection 代理地址添加
- 解决TUN虚拟网卡转发问题
核心代码如下: napi_init.cpp
#include "napi/native_api.h"
#include <pthread.h>
#include "dlfcn.h"
#include "hilog/log.h"
#include <cstdio>
#include <cstdlib>
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏,标识业务领域
#define LOG_TAG "OPEN_VPN3" // 全局tag宏,标识模块日志tag
#include "napi/native_api.h"
#include <cstring>
#include <thread>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <thread>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// entry/src/main/cpp/FileAccessMethods.cpp
static napi_value transferSandboxPath(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
// 将沙箱路径和要写入文本的内容通过Node-API接口转换成C侧变量
size_t pathSize, contentsSize;
char pathBuf[1024], contentsBuf[1024];
napi_get_value_string_utf8(env, argv[0], pathBuf, sizeof(pathBuf), &pathSize);
size_t str_length;
// 第一次调用,仅获取字符串长度(不读取内容)
napi_get_value_string_utf8(env, argv[1], nullptr, 0, &str_length);
// 动态分配缓冲区(注意+1为终止符)
char *buffer = (char *)malloc(str_length + 1);
if (buffer == nullptr) {
// 内存分配失败
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "malloc failed!");
return nullptr;
}
// 第二次调用,获取字符串内容
napi_get_value_string_utf8(env, argv[1], buffer, str_length + 1, &str_length);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "openvpn3 .ovpn path : %{public}s", pathBuf);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "openvpn3 .ovpn content : %{public}s", buffer);
// 使用C标准库的文件操作函数写入文件
FILE *fpNew = fopen(pathBuf, "w");
if (fpNew == nullptr) {
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "open file error!");
free(buffer);
return nullptr;
}
fprintf(fpNew, "%s", buffer);
fclose(fpNew);
// 使用完后释放内存
free(buffer);
return nullptr;
}
struct AsyncContext {
napi_deferred deferred;
napi_env env;
};
static napi_value startVPN(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value args[3] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
size_t size = 1024;
char *username = new char[1024];
char *password = new char[1024];
napi_get_value_string_utf8(env, args[0], username, 1024, &size);
napi_get_value_string_utf8(env, args[1], password, 1024, &size);
char *path = new char[1024];
napi_get_value_string_utf8(env, args[2], path, 1024, &size);
void *handle = dlopen(path, RTLD_LAZY); // Open a SO library and get the path
napi_value result;
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "cli_start");
auto cli_start = (int (*)(int, char **))dlsym(handle, "cli_start");
if (!cli_start) {
dlclose(handle);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "cli_start dlclose");
napi_create_double(env, -1, &result);
return result;
}
// 深拷贝参数
char* username_copy = strdup(username);
char* password_copy = strdup(password);
char* config_path = strdup("/data/storage/el2/base/haps/entry/files/config.ovpn");
// 创建Promise
napi_deferred deferred;
napi_value promise;
napi_create_promise(env, &deferred, &promise);
auto ctx = new AsyncContext{deferred, env};
// 启动线程
std::thread([ctx, handle, username_copy, password_copy, config_path, cli_start]() {
const int numArgs = 6;
char* argv[numArgs] = {
strdup("ovpncli.exe"), strdup("-u"), username_copy,
strdup("-p"), password_copy, config_path
};
int returnCode = cli_start(numArgs, argv);
// 释放argv内存
for (int i = 0; i < numArgs; ++i) {
free(argv[i]);
}
// 解析Promise结果
napi_value result;
napi_create_int32(ctx->env, returnCode, &result);
napi_resolve_deferred(ctx->env, ctx->deferred, result);
// 释放资源
free(username_copy);
free(password_copy);
free(config_path);
dlclose(handle);
delete ctx;
}).detach();
return promise;
}
struct FdInfo {
int32_t tunFd = 0;
int32_t tunnelFd = 0;
struct sockaddr_in serverAddr;
};
static FdInfo g_fdInfo;
static bool g_threadRunF = false;
static std::thread g_threadt1;
static std::thread g_threadt2;
static constexpr const int MAX_STRING_LENGTH = 1024;
static std::string GetStringFromValueUtf8(napi_env env, napi_value value)
{
std::string result;
char str[MAX_STRING_LENGTH] = {0};
size_t length = 0;
napi_get_value_string_utf8(env, value, str, MAX_STRING_LENGTH, &length);
if (length > 0) {
return result.append(str, length);
}
return result;
}
static napi_value TcpConnect(napi_env env, napi_callback_info info) {
size_t numArgs = 2;
size_t argc = numArgs;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t port = 0;
napi_get_value_int32(env, args[1], &port);
std::string ipAddr = GetStringFromValueUtf8(env, args[0]);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "ip: %{public}s port: %{public}d", ipAddr.c_str(), port);
int32_t sockFd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd == -1) {
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "socket() error");
return 0;
}
struct timeval timeout = {1, 0};
setsockopt(sockFd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char*>(&timeout), sizeof(struct timeval));
memset(&g_fdInfo.serverAddr, 0, sizeof(g_fdInfo.serverAddr));
g_fdInfo.serverAddr.sin_family = AF_INET;
g_fdInfo.serverAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str()); // server's IP addr
g_fdInfo.serverAddr.sin_port = htons(port); // port
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "Connection successful\n");
napi_value tunnelFd;
napi_create_int32(env, sockFd, &tunnelFd);
return tunnelFd;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"startVPN", nullptr, startVPN, nullptr, nullptr, nullptr, napi_default, nullptr},
{"tcpConnect", nullptr, TcpConnect, nullptr, nullptr, nullptr, napi_default, nullptr},
{"transferSandboxPath", nullptr, transferSandboxPath, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void *)0),
.reserved = {0},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
OpenVpn3Ability.ets
import { connection, vpnExtension, VpnExtensionAbility } from '@kit.NetworkKit';
import { Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { tcpConnect, startVPN } from 'libentry.so';
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';
import { buffer } from '@kit.ArkTS';
const TAG: string = "[OpenVpn3Ability]";
let g_tunFd = -1;
let g_tunnelFd = -1;
export default class OpenVpn3Ability extends VpnExtensionAbility {
private vpnServerIp: string = '119.147.71.231';
private tunIp: string = '172.19.0.1';
private blockedAppName: string = 'com.q1.securevpn';
private VpnConnection: vpnExtension.VpnConnection | undefined
constructor() {
super();
}
async onCreate(want: Want) {
hilog.info(0, "openvpn3", "OpenVpn3Ability onCreate20250226")
this.startVPN3();
this.VpnConnection = vpnExtension.createVpnConnection(this.context);
this.SetupVpn()
}
SetupVpn() {
let vpnConfig: vpnExtension.VpnConfig = {
addresses: [],
routes: [{
interface: "eth0",
destination: {
address: {
address: '10.8.0.23',
family: 1,
port: 8080
},
prefixLength: 16
},
gateway: {
address: '10.8.0.1',
family: 1,
port: 8080
},
hasGateway: true,
isDefaultRoute: true,
}],
mtu: 1500,
dnsAddresses: ["172.16.126.20", "172.16.127.20"],
trustedApplications: [],
blockedApplications: [],
}
try {
this.VpnConnection?.create(vpnConfig).then((data) => {
g_tunFd = data;
hilog.error(0x0000, 'openvpn3', 'tunfd: %{public}s', JSON.stringify(data) ?? '');
})
} catch (error) {
hilog.error(0x0000, 'openvpn3', 'vpn setUp fail: %{public}s', JSON.stringify(error) ?? '');
}
let getNetCapabilitiesSync: connection.NetCapabilities;
connection.getDefaultNet().then((netHandle: connection.NetHandle) => {
if (netHandle.netId == 0) {
console.info("openvpn3 当前没有已连接的网络时,获取的netHandler的netid为0,属于异常场景,此处可以实际情况自行添加一些处理机制");
return;
}
getNetCapabilitiesSync = connection.getNetCapabilitiesSync(netHandle);
console.info("openvpn3 Succeeded to get net capabilities sync: " + JSON.stringify(getNetCapabilitiesSync));
})
let netHandle2 = connection.getAllNetsSync();
for (let index = 0; index < netHandle2.length; index++) {
const element = netHandle2[index];
console.info(element.netId.toString());
let connProp: connection.ConnectionProperties = connection.getConnectionPropertiesSync(element);
console.info("openvpn3 getConnectionPropertiesSync get data: " + JSON.stringify(connProp));
connection.getNetCapabilities(element).then((data: connection.NetCapabilities) => {
console.info("openvpn3 NetCapabilities data: " + JSON.stringify(data));
})
}
}
CreateTunnel() {
g_tunnelFd = tcpConnect(this.vpnServerIp, 443);
}
Protect() {
hilog.info(0x0000, 'openvpn3', '%{public}s', 'vpn Protect');
this.VpnConnection?.protect(g_tunnelFd).then(() => {
hilog.info(0x0000, 'openvpn3', '%{public}s', 'vpn Protect Success');
}).catch((err: Error) => {
hilog.error(0x0000, 'openvpn3', 'vpn Protect Failed %{public}s', JSON.stringify(err) ?? '');
})
}
startVPN3() {
// 获取应用文件路径
let filesDir = this.context.filesDir;
hilog.info(0, "openvpn3", "OpenVpn3Ability 获取应用文件路径")
// 文件不存在时创建并打开文件,文件存在时打开文件
let file = fs.openSync(filesDir + '/q1vpn3.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
hilog.info(0, "openvpn3", "OpenVpn3Ability openSync")
// 创建一个大小为1024字节的ArrayBuffer对象,用于存储从文件中读取的数据
let arrayBuffer = new ArrayBuffer(1024);
hilog.info(0, "openvpn3", "OpenVpn3Ability ArrayBuffer")
// 设置读取的偏移量和长度
let readOptions: ReadOptions = {
offset: 0,
length: arrayBuffer.byteLength
};
hilog.info(0, "openvpn3", "OpenVpn3Ability ReadOptions")
// 读取文件内容到ArrayBuffer对象中,并返回实际读取的字节数
let readLen = fs.readSync(file.fd, arrayBuffer, readOptions);
hilog.info(0, "openvpn3", "OpenVpn3Ability readSync")
// 将ArrayBuffer对象转换为Buffer对象,并转换为字符串输出
let buf = buffer.from(arrayBuffer, 0, readLen);
// 分割字符串
let parts = buf.toString().split('&');
// 解构赋值
let username: string = parts[0];
let password: string = parts[1];
let soLibPath: string = parts[2];
hilog.info(0, "openvpn3", "openvpn3 the content of file: " + buf.toString());
// 关闭文件
fs.closeSync(file);
hilog.info(0, "openvpn3", "OpenVpn3Ability closeSync")
try {
hilog.info(0, "openvpn3",
`startVPN username:${username} password:${password} soLibPath:${soLibPath} `)
let ret: number = startVPN(username, password, soLibPath)
hilog.info(0, "openvpn3", "startVPN ret = " + ret)
} catch (e) {
hilog.error(4, "openvpn3", `启用VPN失败 ${e.message} ${e.stack}`);
}
}
onDestroy() {
}
};
更多关于移植一个openvpn3的c++库到HarmonyOS 鸿蒙Next,最后一步虚拟网卡(TUN)卡流程了,大佬们该怎么解决?的实战教程也可以访问 https://www.itying.com/category-93-b0.html
运行日志文件在附件中:
在鸿蒙Next中实现虚拟网卡(TUN)功能,可以使用鸿蒙的NetDevice
和NetIf
接口来进行网络设备的创建和管理。首先需要确保你的应用有相应的权限,例如ohos.permission.INTERNET
和ohos.permission.MODIFY_NETWORK_STATE
。
-
创建虚拟网络设备:使用
NetDevice
接口创建一个虚拟网络设备,类似于Linux中的/dev/net/tun
。你可以通过NetDeviceManager
来管理这些设备。 -
配置网络接口:使用
NetIf
接口配置网络接口,设置IP地址、子网掩码、网关等参数。鸿蒙提供了NetIfManager
来管理网络接口。 -
数据包处理:实现数据包的接收和发送逻辑。通常需要实现一个循环,从虚拟网络设备读取数据包,处理后再写回设备。
-
权限和安全性:确保应用在运行时具有足够的权限,并且符合鸿蒙的安全规范。
以下是一个简化的代码示例:
#include <netdevice.h>
#include <netif.h>
// 创建虚拟网络设备
NetDevice* tunDevice = NetDeviceManager::createDevice("tun0");
// 配置网络接口
NetIf* tunIf = NetIfManager::createInterface("tun0");
tunIf->setIpAddress("192.168.1.2");
tunIf->setNetmask("255.255.255.0");
tunIf->setGateway("192.168.1.1");
// 启动网络接口
tunIf->up();
// 数据包处理循环
while (true) {
char buffer[1500];
int len = tunDevice->read(buffer, sizeof(buffer));
if (len > 0) {
// 处理数据包
tunDevice->write(buffer, len);
}
}
确保在移植过程中遵循鸿蒙的API规范,并处理可能出现的异常情况。