移植一个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 踩坑过程记录:

  1. 准备VB Ubuntu 20.23环境,并安装交叉编译工具和各类所需环境
  2. 编译openvpn3 使用HarmonyOS lyium交叉编译工具
  3. thirdparty 包含许多参与交叉编译的第三方库,如果有缺失,按官方流程添加
  4. 编译指定arm64-8a的.so文件
  5. openvpn3的MakeFiles文件修改,优化成打包.so文件
  6. 修改 cli.cpp 的对外封装方法,暴露C方法,dlopen 可以打开的方式
  7. 新建 HarmonyOS 工程,定义cpp方法和libentry的.so文件和CmakeList.txt
  8. HarmonyOS 通过napi_init.cpp中的dlopen调用libovpn.so文件
  9. 处理数据传输问题,通过Node API的方式传参转换
  10. 新增 VpnExtensionAbility 来开启VPN,并通过vpnExtension.createVpnConnection创建虚拟网卡(TUN),并把tunFD值传给openvpn3
  11. 处理 vpnExtension.createVpnConnection 代理地址添加
  12. 解决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

3 回复

问题收到,正在分析中,有进展跟您同步

更多关于移植一个openvpn3的c++库到HarmonyOS 鸿蒙Next,最后一步虚拟网卡(TUN)卡流程了,大佬们该怎么解决?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


运行日志文件在附件中:

在鸿蒙Next中实现虚拟网卡(TUN)功能,可以使用鸿蒙的NetDeviceNetIf接口来进行网络设备的创建和管理。首先需要确保你的应用有相应的权限,例如ohos.permission.INTERNETohos.permission.MODIFY_NETWORK_STATE

  1. 创建虚拟网络设备:使用NetDevice接口创建一个虚拟网络设备,类似于Linux中的/dev/net/tun。你可以通过NetDeviceManager来管理这些设备。

  2. 配置网络接口:使用NetIf接口配置网络接口,设置IP地址、子网掩码、网关等参数。鸿蒙提供了NetIfManager来管理网络接口。

  3. 数据包处理:实现数据包的接收和发送逻辑。通常需要实现一个循环,从虚拟网络设备读取数据包,处理后再写回设备。

  4. 权限和安全性:确保应用在运行时具有足够的权限,并且符合鸿蒙的安全规范。

以下是一个简化的代码示例:

#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规范,并处理可能出现的异常情况。

回到顶部