7 min read

内网通过v2ray和docker保持深信服VPN始终连接

背景

  • 公司用的深信服作为内网VPN服务, 要使用服务器做一些测试或者上传下载一些内网资源, 总需要先打开一下VPN;
  • 而且VPN账号一段时间不用就会临时封禁;
  • 而且VPN服务器并发有限制, 因此连接中的VPN超过半小时不使用就会被踢掉;

怪麻烦的. 本人的电脑又是一台运行在私有服务器上的windows虚拟机, 常年不关机的, 最近总需要去VPN上拿东西, 因此希望弄一个长连的不断开的VPN.

主要问题

  • 深信服的VPN是私有化的AnyConnect协议, 叫EasyConnect, 不能用通用的OpenConnect进行连接. 然而官方有没有提供openwrt甚至linux下的客户端或连接方案;
  • 公司内为了管理账号安全, 防止被盗用借用滥用, 做了机器码验证, 最多允许5个以内的设备连接. 超过了新设备便无法建立连接, 需要提工单给管理员删除既有设备;

方案

第一步, docker跑easyconnect

搜索方案的时候偶然间看到了 使用 docker 封印 EasyConnect , 感觉挺有趣的, 于是便去 原github 看了下, 感觉还挺靠谱的. 便在本地的docker环境中部署试了试;

我本地是无图形的CoreOS, 跑带图形界面的始终不成功, 做不了x11转发. 于是便转而尝试cli纯命令行的方案;

设置了 -d, -u, -p等信息后, 一把跑通. 很开心o(* ̄▽ ̄*)ブ

以下是我的 docker-compose.yml

version: '3.8'
# REF: https://github.com/Hagb/docker-easyconnect
services:
  easyconnect:
    hostname: easyconnect
    image: hagb/docker-easyconnect:cli
    ports:
      - 1080:1080
      - 8888:8888
    cap_add: 
      - NET_ADMIN
    devices:
      - /dev/net/tun
    environment:
      CLI_OPTS: "-d example.com:443 -u username -p password"
      EC_VER: 7.6.7

第二步, 搞定机器码

然而, 好戏不长, 在做后续的socks5和http连通性测试的时候, 多重启了几次容器. 结果就开始疯狂报错

Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N
Is it submitted?[Y/N]: Please enter Y or N

到容器里手动执行了下easyconn命令, 看了下报错, 没想到原因是hardid的原因

Authenticating user "leoyuan" by HardId ...
Authenticating failed, reason: submit hid ,not allow login

This account can only be logged in on an authorized terminal. If you need to log in on this terminal, please try to submit an application to the administrator.
Is it submitted?[Y/N]:

这下凉了呀, 跑docker容器的话, 机器信息都是虚拟的, 机器码一定会变, 这肯定不行.

接下来的方向:

  • 找个机器信息不变的虚拟机, 直接安装deb包, 然后配个systemd任务, 手动跑easyconn;
  • 或者找源码, 看HardID取了哪些信息, 试着用docker来固定机器码;

填坑:

  1. 找VPN管理员清空了目前已提交的硬件ID信息;
  2. 翻github搜hard的时候发现了一个 fake-hwaddr 方法, 仔细一看说明 这不正是所需要的功能嘛.

于是加了一个环境变量 FAKE_HWADDR: '11-22-33-44-55-66'  , 再次重启, 看日志

root@easyconnect:/usr/share/sangfor/EasyConnect/resources/logs# cat HardId.log
[2022-10-09 16:34:59][ERROR][hardid] getDiskHardId err = [hardid] open /dev/hda failed
[2022-10-09 16:34:59][ERROR][hardid] getMotherBordId err = [hardid] getMotherbordId hs not been implemented for Linux yet
[2022-10-09 16:34:59][INFO][hardid] fetch network adapter mac ok, id = 11-22-33-44-55-66.
[2022-10-09 16:34:59][INFO][hardid] hardid = 3468D27594F608BE0115954CE810929C|11-22-33-44-55-66|ZWFzeWNvbm5lY3Q=.
[2022-10-09 16:35:01][ERROR][hardid] getDiskHardId err = [hardid] open /dev/hda failed
[2022-10-09 16:35:01][ERROR][hardid] getMotherBordId err = [hardid] getMotherbordId hs not been implemented for Linux yet
[2022-10-09 16:35:01][INFO][hardid] fetch network adapter mac ok, id = 11-22-33-44-55-66.
[2022-10-09 16:35:01][INFO][hardid] hardid = 3468D27594F608BE0115954CE810929C|11-22-33-44-55-66|ZWFzeWNvbm5lY3Q=.

可真是太开心了, 看来是绑定成功了

第三步, 配v2ray走这个代理

未完待续

第四步, 解决docker swarm不支持映射/dev/tun的问题

填docker swarm的坑, 直接运行docker或docker compose的无视本节

之前跑起来的容器发现好像还是不能用, 在日志里一通翻才发现是没创建 /dev/tun0设备, 后来又翻了翻docker文档才发现 docker swarm是不支持device映射的

不过好在, 万能的网友还是找到了利用docker swarm来管理docker容器, 从而实现device映射的办法. yaml文件如下

version: '3.8'
services:
  easyconnect_runner:
    hostname: easyconnect_runner
    image: docker
    command: 
      docker run
      --name easyconnect 
      --hostname easyconnect 
      --rm 
      --cap-add NET_ADMIN 
      --device /dev/net/tun 
      -p 1080:1080 
      -p 8888:8888 
      -e EC_VER=7.6.7 
      -e CLI_OPTS='-d secure.example.org -u abc -p 123' 
      hagb/docker-easyconnect:cli
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro


docker中跑docker, 便成功实现了device的映射

这样配置的好处是, 这个service的日志中, 能够看到子容器的日志输出. 并且因为是前台运行的, 这个service也不会自动结束. 如果子容器异常退出的话, 服务也会跟着退出并自动重启. 挺好的

但缺点是, 因为子容器不在服务下面, 直接杀掉服务, 子容器依旧会继续运行, 只能手动去杀掉它. 删除容器也不行.

第五步, 全局域网透明代理

参考 官方ip forward的说明 可以通过 ip routeip rule 实现透明代理.

但官方的实例是本地跑docker容器, 本地访问的方式. 只需要本地配一条路由就行. 但我想实现的是, easyconnect跑在docker专用机上, 在路由器openwrt上配路由规则, 实现全局域网的透明代理.

一番摸索后测试如下配置可以跑通.

先在docker专用机上跑通
## 1. 先查到easyconnect的容器ip
docker inspect easyconnect --format '{{ .NetworkSettings.IPAddress }}'

## 2. ping一下确认能通
ping IP地址

## 3. ip route 添加路由
ip route add 192.168.5.0/24 via 172.17.0.2 mtu 1400 table 3
### 解释 添加 网段地址       via  路由网关   mtu 数据包大小 表id
### 网段地址自行通过VPN客户端确认; 路由网关就是容器IP, mtu大小参考easyconnect文档查询; 表id自定义, 1~253
### ip route list table 3 可列出table3下的规则; ip route delete 可以删除指定规则

## 4. ip rule 添加规则
ip rule add iif lo table 3
### 指定lo(localhost)下的所有流量都进入table 3
### 同ip rule list table 3可以列出所有规则; ip rule delete 可以删除规则
### ip rule add table 3指定所有流量进入table 3

## 5. 通过ping测试效果
ping 192.168.5.80 
### 顺利跑通
为docker专用机开启forward转发

一般的linux机器不是作为路由器使用的, 因此默认都是屏蔽ip forward的, 需要开启, 可以参考 https://linuxconfig.org/how-to-turn-on-off-ip-forwarding-in-linux

开启后还需用 iptables -P FORWARD ACCEPT 默认接受所有的forward请求

或者用 iptables -A FORWARD -s 内网段 -j ACCEPT 来接受来自指定ip的请求

为openwrt配置路由规则
## 添加路由转发指定网段到docker专用机
ip route add 192.168.5.0/24 via 10.2.3.4 mtu 1400 table 3

## 添加规则转发指定网卡的流量
ip rule add table 3
### 转发所有流量

然后应该就可以通啦

要调试的话可以在docker专用机上用 watch -n1 iptables -vnL FORWARD 命令观察FORWARD链的流量情况, 配合其他机器发请求来判断.