VPN 管理

简介

VPN 即虚拟专网(VPN-Virtual Private Network)在公用网络上建立专用网络的技术。整个 VPN 网络的任意两个节点之间的连接并没有传统专网所需的端到端的物理链路,而是架构在公用网络服务商所提供的网络平台(如 Internet)之上的逻辑网络,用户数据在逻辑链路中传输。

说明: 为了保证应用的运行效率,大部分 API 调用都是异步的,对于异步调用的 API 均提供了 callback 和 Promise 两种方式,以下示例均采用 callback 函数,更多方式可以查阅API 参考

以下分别介绍具体开发方式。

接口说明

完整的 JS API 说明以及实例代码请参考:VPN API 参考

接口名 描述
setUp(config: VpnConfig, callback: AsyncCallback<number>): void 建立一个 VPN 网络,使用 callback 方式作为异步方法。
protect(socketFd: number, callback: AsyncCallback<void>): void 保护 VPN 的隧道,使用 callback 方式作为异步方法。
destroy(callback: AsyncCallback<void>): void 销毁一个 VPN 网络,使用 callback 方式作为异步方法。

启动 VPN 的流程

  1. 建立一个 VPN 的网络隧道,下面以 UDP 隧道为例。
  2. 保护前一步建立的 UDP 隧道。
  3. 建立一个 VPN 网络。
  4. 处理虚拟网卡的数据,如:读写操作。
  5. 销毁 VPN 网络。

本示例通过 Native C++ 的方式开发应用程序,Native C++ 可参考: 简易 Native C++ 示例(ArkTS)(API9)

示例程序主要包含两个部分:js 功能代码和 C++功能代码

VPN 示例源码(js 部分)

主要功能:实现业务逻辑,如:创建隧道、建立 VPN 网络、保护 VPN 网络、销毁 VPN 网络

import vpn from '@ohos.net.vpn';
import common from '@ohos.app.ability.common';
import vpn_client from "libvpn_client.so";
import { BusinessError } from '@ohos.base';

let TunnelFd: number = -1;

@Entry
@Component
struct Index {
  @State message: string = 'Test VPN';

  private context = getContext(this) as common.UIAbilityContext;
  private VpnConnection: vpn.VpnConnection = vpn.createVpnConnection(this.context);

  //1. 建立一个VPN的网络隧道,下面以UDP隧道为例。
  CreateTunnel() {
    TunnelFd = vpn_client.udpConnect("192.168.43.208", 8888);
  }

  //2. 保护前一步建立的UDP隧道。
  Protect() {
    this.VpnConnection.protect(TunnelFd).then(() => {
      console.info("vpn Protect Success.");
    }).catch((err: BusinessError) => {
      console.info("vpn Protect Failed " + JSON.stringify(err));
    })
  }

  SetupVpn() {
    let tunAddr : vpn.LinkAddress = {} as vpn.LinkAddress;
    tunAddr.address.address = "10.0.0.5";
    tunAddr.address.family = 1;

    let config : vpn.VpnConfig = {} as vpn.VpnConfig;
    config.addresses.push(tunAddr);
    config.mtu = 1400;
    config.dnsAddresses = ["114.114.114.114"];

    try {
      //3. 建立一个VPN网络。
      this.VpnConnection.setUp(config, (error: BusinessError, data: number) => {
        console.info("tunfd: " + JSON.stringify(data));
        //4. 处理虚拟网卡的数据,如:读写操作。
        vpn_client.startVpn(data, TunnelFd)
      })
    } catch (error) {
      console.info("vpn setUp fail " + JSON.stringify(error));
    }
  }

  //5.销毁VPN网络。
  Destroy() {
    vpn_client.stopVpn(TunnelFd);
    this.VpnConnection.destroy().then(() => {
      console.info("vpn Destroy Success.");
    }).catch((err: BusinessError) => {
      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%')
  }
}

VPN 示例源码(c++部分)

主要功能:具体业务的底层实现,如:UDP 隧道 Client 端的实现、虚拟网卡读写数据的实现

#include "napi/native_api.h"
#include "hilog/log.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>

#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;

//获取对应字符串数据, 用于获取udp server 的IP地址
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;
        }

        // 接收到udp server的数据,写入到虚拟网卡中
        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 UdpConnect(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);

    // 建立udp隧道
    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[] = {
        {"udpConnect", nullptr, UdpConnect, 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);
}