diff --git a/zh-cn/application-dev/connectivity/net-vpn.md b/zh-cn/application-dev/connectivity/net-vpn.md index 59020190a1c13c6b614be1b8eaf189b60639f084..496986e03e63b65034c4e059a3543fb13a08009b 100644 --- a/zh-cn/application-dev/connectivity/net-vpn.md +++ b/zh-cn/application-dev/connectivity/net-vpn.md @@ -26,102 +26,326 @@ VPN即虚拟专网(VPN-Virtual Private Network)在公用网络上建立专 3. 建立一个VPN网络。 4. 处理虚拟网卡的数据,如:读写操作。 -```js -import vpn from '@ohos.net.vpn' +本示例通过 Native C++ 的方式开发应用程序,Native C++ 可参考: [简易Native C++ 示例(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo) + -// FA模型获取context -import featureAbility from '@ohos.ability.featureAbility'; -let context = featureAbility.getContext(); +### js 相关的代码 -// Stage模型获取context +```js +import hilog from '@ohos.hilog'; +import vpn from '@ohos.net.vpn'; import UIAbility from '@ohos.app.ability.UIAbility'; +import vpn_client from "libvpn_client.so" + class EntryAbility extends UIAbility { - onWindowStageCreate(windowStage){ + onWindowStageCreate(windowStage) { globalThis.context = this.context; } } -let context = globalThis.context; - -// 获取VpnConnection对象 -VpnConnection = vpn.createVpnConnection(context); -console.info("vpn onInit: " + JSON.stringify(VpnConnection)); - -// 建立TCP 隧道 -import socket from "@ohos.net.socket"; -var tcp = socket.constructTCPSocketInstance(); -tcp.bind({ - address: "0.0.0.0", - family: 1 -}) -let connectAddress = { - address: "192.168.1.11", - port: 8888, - family: 1 -}; -tcp.connect({ - address: connectAddress, timeout: 6000 -}) -tcp.getSocketFd().then((tunnelfd) => { - console.info("tunenlfd: " + tunnelfd); - VpnConnection.protect(tunnelfd, (error, data) => { - console.info(JSON.stringify(error)); - console.info(JSON.stringify(data)); - }) -}) - -// 建立 VPN 网络 -let config = { - addresses: [{ - address: { - address: "10.0.0.5", - family: 1 - }, - prefixLength: 24, - }], - routes: [], - mtu: 1400, - dnsAddresses:[ - "114.114.114.114" - ], - acceptedApplications:[], - refusedApplications:[] -} -let tunfd = 0 -VpnConnection.setUp(config).then((data) => { - console.info("setUp success, tunfd: " + JSON.stringify(data)) - tunfd = data - StartReadFromTun(data) -}).catch(err => { - console.info("setUp fail" + JSON.stringify(err)) -}) - -// 处理虚拟网卡的数据 -import fs from '@ohos.file.fs'; -async function StartReadFromTun(tunFd) { - let buf = new ArrayBuffer(4096); - while (tunFd) { - await sleep(1) - fs.read(tunFd, buf).then((readLen) => { - console.info("read file data succeed, len: ", readLen); - await tcp.send({ data: buffer.slice(0, readLen) }) - }).catch((err) => { - console.info("read file data failed with error message: " + err.message + ", error code: " + err.code); - }); + +let TunnelFd = -1 +let VpnConnection = vpn.createVpnConnection(globalThis.context) + +@Entry +@Component +struct Index { + @State message: string = 'Toy VPN' + + CreateTunnel() { + TunnelFd = vpn_client.tcpConnect("192.168.43.208", 8888) } -} -tcp.on('message', (data) => { - console.log("on message:" + JSON.stringify(data.message) + ", remoteInfo:" + JSON.stringify(data.remoteInfo)); - if (data.message) { - if (!tunfd) { - console.info("tunfd is null") - return; + Protect() { + console.info("vpn Protect") + VpnConnection.protect(TunnelFd).then(function () { + console.info("vpn Protect Success ") + }).catch(function (err) { + console.info("vpn Protect Failed " + JSON.stringify(err)) + }) + } + + SetupVpn() { + console.info("vpn SetupVpn") + let config = { + addresses: [{ + address: { + address: "10.0.0.5", + family: 1 + }, + prefixLength: 24, + }], + routes: [], + mtu: 1400, + dnsAddresses: [ + "114.114.114.114" + ], + acceptedApplications: [], + refusedApplications: [] + } + + try { + VpnConnection.setUp(config, (error, data) => { + console.info("tunfd: " + JSON.stringify(data)); + vpn_client.startVpn(data, TunnelFd) + }) + } catch (error) { + console.info("vpn setUp fail " + JSON.stringify(error)); } - fs.write(tunfd, buffer).then(function (number) { - console.info("write data to file succeed and size is:" + number); + } + + Destroy() { + console.info("vpn Destroy") + vpn_client.stopVpn(TunnelFd) + VpnConnection.destroy().then(function () { + console.info("vpn Destroy Success ") }).catch(function (err) { - console.info("write data to file failed with error:" + err); - }); + console.info("vpn Destroy Failed " + JSON.stringify(err)) + }) + } + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + .onClick(() => { + console.info("vpn Client") + }) + Button('CreateTunnel').onClick(() => { + this.CreateTunnel() + }).fontSize(50) + Button('Protect').onClick(() => { + this.Protect() + }).fontSize(50) + Button('SetupVpn').onClick(() => { + this.SetupVpn() + }).fontSize(50) + Button('Destroy').onClick(() => { + this.Destroy() + }).fontSize(50) + } + .width('100%') + } + .height('100%') } -}) +} +``` + +### C++ 相关的代码 + +```c++ +#include "napi/native_api.h" +#include "hilog/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define BUFFER_SIZE 2048 + +#define VPN_LOG_TAG "NetMgrVpn" +#define VPN_LOG_DOMAIN 0x15b0 +#define MAKE_FILE_NAME (strrchr(__FILE__, '/') + 1) + +#define NETMANAGER_VPN_LOGE(fmt, ...) \ + OH_LOG_Print(LOG_APP, LOG_ERROR, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \ + __LINE__, ##__VA_ARGS__) + +#define NETMANAGER_VPN_LOGI(fmt, ...) \ + OH_LOG_Print(LOG_APP, LOG_INFO, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \ + __LINE__, ##__VA_ARGS__) + +#define NETMANAGER_VPN_LOGD(fmt, ...) \ + OH_LOG_Print(LOG_APP, LOG_DEBUG, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \ + __LINE__, ##__VA_ARGS__) + +struct FdInfo { + int32_t tunFd = 0; + int32_t tunnelFd = 0; + struct sockaddr_in serverAddr; +}; + +static FdInfo fdInfo; +static bool threadRunF = false; +static std::thread threadt1; +static std::thread threadt2; + +static constexpr const int MAX_STRING_LENGTH = 1024; +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; +} + +void HandleReadTunfd(FdInfo fdInfo) { + uint8_t buffer[BUFFER_SIZE] = {0}; + while (threadRunF) { + int ret = read(fdInfo.tunFd, buffer, sizeof(buffer)); + if (ret <= 0) { + if (errno != 11) { + NETMANAGER_VPN_LOGE("read tun device error: %{public}d, tunfd: %{public}d", errno, fdInfo.tunFd); + } + continue; + } + + // 读取到虚拟网卡的数据,通过udp隧道,发送给服务器 + NETMANAGER_VPN_LOGD("buffer: %{public}s, len: %{public}d", buffer, ret); + ret = sendto(fdInfo.tunnelFd, buffer, ret, 0, (struct sockaddr *)&fdInfo.serverAddr, sizeof(fdInfo.serverAddr)); + if (ret <= 0) { + NETMANAGER_VPN_LOGE("send to server[%{public}s:%{public}d] failed, ret: %{public}d, error: %{public}s", + inet_ntoa(fdInfo.serverAddr.sin_addr), ntohs(fdInfo.serverAddr.sin_port), ret, + strerror(errno)); + continue; + } + } +} + +void HandleTcpReceived(FdInfo fdInfo) { + int addrlen = sizeof(struct sockaddr_in); + uint8_t buffer[BUFFER_SIZE] = {0}; + while (threadRunF) { + int length = recvfrom(fdInfo.tunnelFd, buffer, sizeof(buffer), 0, (struct sockaddr *)&fdInfo.serverAddr, + (socklen_t *)&addrlen); + if (length < 0) { + if (errno != 11) { + NETMANAGER_VPN_LOGE("read tun device error: %{public}d,tunnelfd: %{public}d", errno, fdInfo.tunnelFd); + } + continue; + } + + NETMANAGER_VPN_LOGD("from [%{public}s:%{public}d] data: %{public}s, len: %{public}d", + inet_ntoa(fdInfo.serverAddr.sin_addr), ntohs(fdInfo.serverAddr.sin_port), buffer, length); + int ret = write(fdInfo.tunFd, buffer, length); + if (ret <= 0) { + NETMANAGER_VPN_LOGE("error Write To Tunfd, errno: %{public}d", errno); + } + } +} + +static napi_value TcpConnect(napi_env env, napi_callback_info info) { + size_t argc = 2; + 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]); + + NETMANAGER_VPN_LOGI("ip: %{public}s port: %{public}d", ipAddr.c_str(), port); + + int32_t sockFd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockFd == -1) { + NETMANAGER_VPN_LOGE("socket() error"); + return 0; + } + + struct timeval timeout = {1, 0}; + setsockopt(sockFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval)); + + memset(&fdInfo.serverAddr, 0, sizeof(fdInfo.serverAddr)); + fdInfo.serverAddr.sin_family = AF_INET; + fdInfo.serverAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str()); // server's IP addr + fdInfo.serverAddr.sin_port = htons(port); // port + + NETMANAGER_VPN_LOGI("Connection successful"); + + napi_value tunnelFd; + napi_create_int32(env, sockFd, &tunnelFd); + return tunnelFd; +} + +static napi_value StartVpn(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + napi_get_value_int32(env, args[0], &fdInfo.tunFd); + napi_get_value_int32(env, args[1], &fdInfo.tunnelFd); + + if (threadRunF) { + threadRunF = false; + threadt1.join(); + threadt2.join(); + } + + threadRunF = true; + std::thread tt1(HandleReadTunfd, fdInfo); + std::thread tt2(HandleTcpReceived, fdInfo); + + threadt1 = std::move(tt1); + threadt2 = std::move(tt2); + + NETMANAGER_VPN_LOGI("StartVpn successful"); + + napi_value retValue; + napi_create_int32(env, 0, &retValue); + return retValue; +} + +static napi_value StopVpn(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + int32_t tunnelFd; + napi_get_value_int32(env, args[0], &tunnelFd); + if (tunnelFd) { + close(tunnelFd); + tunnelFd = 0; + } + + if (threadRunF) { + threadRunF = false; + threadt1.join(); + threadt2.join(); + } + + NETMANAGER_VPN_LOGI("StopVpn successful"); + + napi_value retValue; + napi_create_int32(env, 0, &retValue); + return retValue; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[] = { + {"tcpConnect", nullptr, TcpConnect, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"startVpn", nullptr, StartVpn, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"stopVpn", nullptr, StopVpn, 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); +} ```