前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >HarmonyOS 开发实践 —— 基于网络的VPN连接实现

HarmonyOS 开发实践 —— 基于网络的VPN连接实现

原创
作者头像
小帅聊鸿蒙
发布2024-12-11 20:34:08
发布2024-12-11 20:34:08
21000
代码可运行
举报
文章被收录于专栏:鸿蒙开发笔记鸿蒙开发笔记
运行总次数:0
代码可运行

"歪屁恩"全称为虚拟私人网络(Virtual Private Network),是常用于连接中、大型企业或团体间私人网络的通讯方法,利用隧道协议(Tunneling Protocol)来达到发送端认证、消息保密与准确性等功能。

使用过程中,外网的用户可以使用"歪屁恩"client 连接组织搭建的 "歪屁恩" server 以建立通信隧道

随后便建立了虚拟的私人网络,处于外网的 worker 和内网中的 server 可以相互通信。

场景一:手机应用配置"歪屁恩"客户端转发请求到 远程"歪屁恩"服务端访问互联网,实现建立基本 "歪屁恩" 服务能力

效果图

启动界面如下图示:

点击'启动"歪屁恩"Ext'按钮,会弹窗提示是否使用"歪屁恩"权限连接。

方案描述

当前提供三方"歪屁恩"能力主要用于创建虚拟网卡及配置"歪屁恩"路由信息,连接隧道过程及内部连接的协议需要应用内部自行实现,创建过程可参考如下:

1、项目中“jian”立"歪屁恩"Ability.ets文件,继承调用"歪屁恩"ExtensionAbility提供"歪屁恩"创建、销毁等生命周期能力。

2、ability实现后在entry-module.json5中,添加extensionAbilities相关配置。

3、设置want参数指定的启动目标,启用"歪屁恩"服务。

核心代码

1、项目中“jian”立"歪屁恩"Ability.ets文件,继承调用"歪屁恩"ExtensionAbility提供"歪屁恩"创建、销毁等生命周期能力

代码语言:ts
复制
private "歪屁恩"Connection: "歪屁恩"Ext."歪屁恩"Connection;

onCreate(want: Want) {

  console.info(TAG, `onCreate, want: ${want.abilityName}`);

  this."歪屁恩"Connection = "歪屁恩"Ext.create"歪屁恩"Connection(this.context);

  console.info("create"歪屁恩"Connection success");

}

onRequest(want: Want, startId: number) {

  console.info(TAG, `onRequest, want: ${want.abilityName}`);

}

onConnect(want: Want) {

  console.info(TAG, `onConnect, want: ${want.abilityName}`);

  return null;

}

onDisconnect(want: Want) {

  console.info(TAG, `onDisconnect, want: ${want.abilityName}`);

}

onDestroy() {

  this.Destroy();

  console.info(TAG, `onDestroy`);

}

Destroy() {

  hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Destroy');

  "歪屁恩"_client.stop"歪屁恩"(g_tunnelFd);

  this."歪屁恩"Connection.destroy().then(() => {

    hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Destroy Success');

  }).catch((err : Error) => {

    hilog.error(0x0000, 'developTag', '"歪屁恩" Destroy Failed: %{public}s', JSON.stringify(err) ?? '');

  })

}

2、module.json5文件中配置extensionAbilities参数,样例如下:

代码语言:ts
复制
{

  "module": {

    "name": "entry",

    "type": "entry",

    "description": "$string:module_desc",

    "mainElement": "EntryAbility",

    "deviceTypes": [

      "phone",

      "tablet",

      "2in1"

    ],

    "deliveryWithInstall": true,

    "installationFree": false,

    "pages": "$profile:main_pages",

    "abilities": [

      {

        "name": "EntryAbility",

        "srcEntry": "./ets/entryAbility/EntryAbility.ets",

        "description": "$string:EntryAbility_desc",

        "icon": "$media:startIcon",

        "label": "$string:EntryAbility_label",

        "startWindowIcon": "$media:startIcon",

        "startWindowBackground": "$color:start_window_background",

        "exported": true,

        "skills": [

          {

            "entities": [

              "entity.system.home"

            ],

            "actions": [

              "action.system.home"

            ]

          }

        ]

      }

    ],

    "requestPermissions": [

      {

        "name": "ohos.permission.INTERNET"

      },

      {

        "name": "ohos.permission.GET_NETWORK_INFO"

      }

    ],

    "extensionAbilities": [

      {

        "name": "My"歪屁恩"ExtAbility",

        "srcEntry": "./ets/"歪屁恩"Ability/My"歪屁恩"ExtAbility.ets",

        "type": ""歪屁恩""

      }

    ]

  }

}

如首次添加"type": ""歪屁恩""时报红,“ctrl+左键”点击"type",在"enum"中添加“"歪屁恩"”参数

配置修改后界面如下:

3、设置want参数指定的启动目标,启用"歪屁恩"服务。

代码语言:ts
复制
let want: Want = {

  deviceId: "",

  bundleName: "com.example.my"歪屁恩"demo",

  abilityName: "My"歪屁恩"ExtAbility",

};

@Entry

@Component

struct Start"歪屁恩" {

  build() {

    Row() {

      Column() {

        Button($r('app.string.btn_start_"歪屁恩"Ext')).onClick(() => {

          "歪屁恩"ext.start"歪屁恩"ExtensionAbility(want);  //启用"歪屁恩"服务

        }).fontSize(50)

      }

      .width('100%')

    }

    .height('100%')

  }

}

4、创建 "歪屁恩"连接 网络

代码语言:ts
复制
class Config {

  addresses: AddressWithPrefix[];

  mtu: number;

  dnsAddresses: string[];

  trustedApplications: string[];

  blockedApplications: string[];

  constructor(

    tunIp: string,

    blockedAppName: string

  ) {

    this.addresses = [

      new AddressWithPrefix(new Address(tunIp, 1), 24)

    ];

    this.mtu = 1400;

    this.dnsAddresses = ["114.114.114.114"];

    this.trustedApplications = [];

    this.blockedApplications = [blockedAppName];

  }

}

let config = new Config(this.tunIp, this.blockedAppName);

try {

  this."歪屁恩"Connection.create(config).then((data) => {

    g_tunFd = data;

    hilog.error(0x0000, 'developTag', 'tunfd: %{public}s', JSON.stringify(data) ?? '');

    "歪屁恩"_client.start"歪屁恩"(g_tunFd, g_tunnelFd);

  })

} catch (error) {

  hilog.error(0x0000, 'developTag', '"歪屁恩" setUp fail: %{public}s', JSON.stringify(error) ?? '');

}

"歪屁恩"创建成功时日志打印如下图:

5、销毁"歪屁恩"连接。

代码语言:ts
复制
let want: Want = {

  deviceId: "",

  bundleName: "com.example.my"歪屁恩"demo",

  abilityName: "My"歪屁恩"ExtAbility",

};

let g_tunnelFd = -1;

@Entry

@Component

struct Stop"歪屁恩" {

  @State message: string = '"歪屁恩"';

  @State "歪屁恩"ServerIp: string = '192.168.3.49 ';

  @State tunIp: string = '10.0.0.5';

  @State routeAddr: string = '192.168.214.0';

  @State prefix: string = '24';

  @State blockedAppName: string = 'com.example.baidumyapplication';

  private context = getContext(this) as common."歪屁恩"ExtensionContext;

  private "歪屁恩"Connection: "歪屁恩"ext."歪屁恩"Connection = "歪屁恩"ext.create"歪屁恩"Connection(this.context);

  Destroy() {

    hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Destroy');

    "歪屁恩"_client.stop"歪屁恩"(g_tunnelFd);

    this."歪屁恩"Connection.destroy().then(() => {

      hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Destroy Success');

    }).catch((err : Error) => {

      hilog.error(0x0000, 'developTag', '"歪屁恩" Destroy Failed: %{public}s', JSON.stringify(err) ?? '');

    })

  }

  build() {

    Row() {

      Column() {

        Text(this.message)

          .fontSize(35)

          .fontWeight(FontWeight.Bold)

          .onClick(() => {

            hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Client');

          })

        Button('stop "歪屁恩"').onClick(() => {

          this.Destroy();

        }).fontSize(50)

        Button('stop "歪屁恩"Ext').onClick(() => {

          "歪屁恩"ext.stop"歪屁恩"ExtensionAbility(want);

        }).fontSize(50)

      }.width('100%')

    }.height('100%')

  }

}

场景二:使用当前提供 "歪屁恩" 能力建立隧道连接,实现连接过程中 "歪屁恩"  数据包的传递

方案描述

建立隧道有关能力可通过NDK侧代码段实现:

1、建立"歪屁恩"_client.cpp文件,写入"歪屁恩"隧道通信启动、停止有关能力。

2、NDK添加可导出配置的使用接口能力。

3、页面中调用能力import引入。

核心代码

1、建立"歪屁恩"_client.cpp文件,写入"歪屁恩"隧道通信有关能力。

代码语言:cpp
代码运行次数:0
复制
#define MAKE_FILE_NAME (strrchr(__FILE__, '/') + 1)

#define NETMANAGER_"歪屁恩"_LOGE(fmt, ...)                                                                                  \

OH_LOG_Print(LOG_APP, LOG_ERROR, 0x15b0, "NetMgr"歪屁恩"", ""歪屁恩" [%{public}s %{public}d] " fmt, MAKE_FILE_NAME,  \

__LINE__, ##__VA_ARGS__)

#define NETMANAGER_"歪屁恩"_LOGI(fmt, ...)                                                                                  \

OH_LOG_Print(LOG_APP, LOG_INFO, 0x15b0, "NetMgr"歪屁恩"", ""歪屁恩" [%{public}s %{public}d] " fmt, MAKE_FILE_NAME,   \

__LINE__, ##__VA_ARGS__)

#define NETMANAGER_"歪屁恩"_LOGD(fmt, ...)                                                                                  \

OH_LOG_Print(LOG_APP, LOG_DEBUG, 0x15b0, "NetMgr"歪屁恩"", ""歪屁恩" [%{public}s %{public}d] " fmt, MAKE_FILE_NAME,  \

__LINE__, ##__VA_ARGS__)

constexpr int BUFFER_SIZE = 2048;

constexpr int ERRORAGAIN = 11;

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 void HandleReadTunfd(FdInfo fdInfo)

{

  uint8_t buffer[BUFFER_SIZE] = {0};

  while (g_threadRunF) {

    if (fdInfo.tunFd <= 0) {

      sleep(1);

      continue;

    }

    int ret = read(fdInfo.tunFd, buffer, sizeof(buffer));

    if (ret <= 0) {

      if (errno != ERRORAGAIN) {

        sleep(1);

      }

      continue;

    }

    // Read the data from the virtual network interface and send it to the client through a TCP tunnel.

    NETMANAGER_"歪屁恩"_LOGD("buffer: %{public}s, len: %{public}d", buffer, ret);

    ret = sendto(fdInfo.tunnelFd, buffer, ret, 0,

      reinterpret_cast<struct sockaddr *>(&fdInfo.serverAddr), sizeof(fdInfo.serverAddr));

    if (ret <= 0) {

      NETMANAGER_"歪屁恩"_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;

    }

  }

}

static void HandleTcpReceived(FdInfo fdInfo)

{

  int addrlen = sizeof(struct sockaddr_in);

  uint8_t buffer[BUFFER_SIZE] = {0};

  while (g_threadRunF) {

    if (fdInfo.tunnelFd <= 0) {

      sleep(1);

      continue;

    }

    int length = recvfrom(fdInfo.tunnelFd, buffer, sizeof(buffer), 0,

      reinterpret_cast<struct sockaddr *>(&fdInfo.serverAddr)

    reinterpret_cast<socklen_t *>(&addrlen));

    if (length < 0) {

      if (errno != EAGAIN) {

        NETMANAGER_"歪屁恩"_LOGE("read tun device error: %{public}d %{public}d", errno, fdInfo.tunnelFd);

      }

      continue;

    }

    NETMANAGER_"歪屁恩"_LOGI("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_"歪屁恩"_LOGE("error Write To Tunfd, errno: %{public}d", errno);

    }

  }

}

//通信能力创建

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]);

  NETMANAGER_"歪屁恩"_LOGI("ip: %{public}s port: %{public}d", ipAddr.c_str(), port);

  int32_t sockFd = socket(AF_INET, SOCK_DGRAM, 0);

  if (sockFd == -1) {

    NETMANAGER_"歪屁恩"_LOGE("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

NETMANAGER_"歪屁恩"_LOGI("Connection successful\n");

napi_value tunnelFd;

napi_create_int32(env, sockFd, &tunnelFd);

return tunnelFd;

}

//"歪屁恩"启用当前隧道连接

static napi_value Start"歪屁恩"(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);

  napi_get_value_int32(env, args[0], &g_fdInfo.tunFd);

  napi_get_value_int32(env, args[1], &g_fdInfo.tunnelFd);

  if (g_threadRunF) {

    g_threadRunF = false;

    g_threadt1.join();

    g_threadt2.join();

  }

  g_threadRunF = true;

  std::thread tt1(HandleReadTunfd, g_fdInfo);

  std::thread tt2(HandleTcpReceived, g_fdInfo);

  g_threadt1 = std::move(tt1);

  g_threadt2 = std::move(tt2);

  NETMANAGER_"歪屁恩"_LOGI("Start"歪屁恩" successful\n");

  napi_value retValue;

  napi_create_int32(env, 0, &retValue);

  return retValue;

}

//"歪屁恩"停止当前隧道连接

static napi_value Stop"歪屁恩"(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 (g_threadRunF) {

    g_threadRunF = false;

    g_threadt1.join();

    g_threadt2.join();

  }

  NETMANAGER_"歪屁恩"_LOGI("Stop"歪屁恩" successful\n");

  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},

{"start"歪屁恩"", nullptr, Start"歪屁恩", nullptr, nullptr, nullptr, napi_default, nullptr},

{"stop"歪屁恩"", nullptr, Stop"歪屁恩", 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)

{

  NETMANAGER_"歪屁恩"_LOGI(""歪屁恩" 15b0 HELLO ~~~~~~~~~~");

  napi_module_register(&demoModule);

}

2、NDK添加可导出配置的使用接口能力,以当前执行使用项目为例,index.d.ts文件中配置方法:

3、页面中调用能力import引入。

代码语言:ts
复制
import "歪屁恩"_client from 'lib"歪屁恩"_client.so';

let want: Want = {

  deviceId: "",

  bundleName: "com.example.my"歪屁恩"demo",

  abilityName: "My"歪屁恩"ExtAbility",

};

//g_tunFd连接前设置生成的标识符,g_tunnelFd表示连接隧道成功后对应的表示符

let g_tunFd = -1;

let g_tunnelFd = -1;

@Entry

@Component

struct Start"歪屁恩" {

  @State message: string = 'Toy "歪屁恩"';

  @State "歪屁恩"ServerIp: string = '192.168.3.49';

  @State tunIp: string = '10.0.0.5';

  @State prefix: string = '24';

  @State blockedAppName: string = 'com.example.baidumyapplication';

  private context = getContext(this) as common."歪屁恩"ExtensionContext;

  private "歪屁恩"Connection: "歪屁恩"ext."歪屁恩"Connection = "歪屁恩"ext.create"歪屁恩"Connection(this.context);

  //创建隧道连接

  CreateTunnel() {

    g_tunnelFd = "歪屁恩"_client.tcpConnect(this."歪屁恩"ServerIp, 8888);

  }

  Protect() {

    hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Protect');

    this."歪屁恩"Connection.protect(g_tunnelFd).then(() => {

      hilog.info(0x0000, 'developTag', '%{public}s', '"歪屁恩" Protect Success');

    }).catch((err : Error) => {

      hilog.error(0x0000, 'developTag', '"歪屁恩" Protect Failed %{public}s', JSON.stringify(err) ?? '');

    })

  }

}

常见问题

Q:"歪屁恩"连接后如何判断?

A:可使用connection模块中 getNetCapabilities 能力获取,返回 netBearType 参数为4,即当前使用了"歪屁恩"网络。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识;
  • 想要获取更多完整鸿蒙最新学习知识点,可关注B站:码牛课堂;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景一:手机应用配置"歪屁恩"客户端转发请求到 远程"歪屁恩"服务端访问互联网,实现建立基本 "歪屁恩" 服务能力
    • 效果图
    • 方案描述
    • 核心代码
    • 场景二:使用当前提供 "歪屁恩" 能力建立隧道连接,实现连接过程中 "歪屁恩"  数据包的传递
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档