AnyLink 部署指南 · 群晖 DSM 7.2.2

基于 Docker 容器部署 AnyLink VPN 服务,支持 Cisco AnyConnect 协议客户端接入。

环境信息

项目
系统 群晖 DSM 7.2.2
机型 SA6400
群晖 IP 192.168.18.8
物理出口网卡 ovs_eth4
VPN 客户端网段 192.168.90.0/24
VPN 网关 192.168.90.1
客户端 IP 范围 192.168.90.100 ~ 192.168.90.200
对外端口 4436(TCP/UDP)
管理面板端口 8809

前置准备

1. 启用 SSH

控制面板 → 终端机与 SNMP → 启用 SSH 功能

2. 安装 Container Manager

套件中心 → 搜索 Container Manager → 安装

3. 确认内核模块

SSH 登录群晖后执行:

lsmod | grep tun

如无输出则手动加载:

sudo modprobe tun

目录结构

mkdir -p /volume1/docker/anylink/{conf,log}
cd /volume1/docker/anylink

最终结构:

/volume1/docker/anylink/
├── docker-compose.yml
├── conf/
│   ├── server.toml
│   ├── anylink.db
│   ├── profile.xml
│   ├── client_ca.pem
│   ├── client_ca.key
│   ├── files/
│   └── cert/
│       ├── anylink.ddns.vanjay.cn.pem
│       └── anylink.ddns.vanjay.cn.key
└── log/

docker-compose.yml

services:
  anylink:
    image: bjdgyc/anylink:latest
    container_name: anylink
    restart: unless-stopped
    privileged: true
    # cpus: 2
    # mem_limit: 4g
    ports:
      - 4436:443
      - 8809:8800
      - 4436:443/udp
    environment:
      LINK_LOG_LEVEL: info
    command:
      - --conf=/app/conf/server.toml
    volumes:
      - ./conf:/app/conf
      - ./log:/app/log
    dns_search: .

server.toml 关键配置

# 其他配置文件,可以使用绝对路径
# 或者相对于 anylink 二进制文件的路径

# 数据文件
db_type = "sqlite3"
db_source = "./conf/anylink.db"
# 证书文件
cert_file = "./conf/cert/anylink.ddns.vanjay.cn.pem"
cert_key = "./conf/cert/anylink.ddns.vanjay.cn.key"
profile = "./conf/profile.xml"
files_path = "./conf/files"
# 日志目录,为空写入标准输出
log_path = "./log" # 添加日志文件路径
log_level = "info" # 修改日志等级为info

# 系统名称
issuer = "VanJay's Home VPN"
# 后台管理用户
admin_user = "VanJay"
admin_pass = "$2a$10$Jkfxxxxxxxx"
# 留空表示不开启 otp, 开启otp后密码为  pass + 6位otp
# 生成 ./anylink tool -o
admin_otp = ""
jwt_secret = "njKSeDiwKnypaFjepvVBUxxxxxxx"

# 服务监听地址
# server_addr = "443"
# 开启 DTLS, 默认关闭
# server_dtls = false
# server_dtls_addr = "443"
# 后台服务监听地址
# admin_addr = ":8800"
# 开启tcp proxy protocol协议
proxy_protocol = true

# 网络模式,只演示该模式,其他模式请自行参考github
link_mode = "tun"

# 客户端分配的ip地址池,docker 部署不需要
# ipv4_master = "eth0"
# ipv4_cidr = "192.168.100.0/24"
# pv4_gateway = "192.168.100.1"
# ipv4_start = "192.168.100.100"
# ipv4_end = "192.168.100.200"

# 最大客户端数量
max_client = 100
# 单个用户同时在线数量
max_user_client = 8
# IP租期(秒)
ip_lease = 86400

# 默认选择的组
default_group = "vip"

# 客户端失效检测时间(秒) dpd > keepalive
cstp_keepalive = 25
cstp_dpd = 30
mobile_keepalive = 85
mobile_dpd = 90

# 设置最大传输单元
mtu = 1460

# 要发布的默认域,可以多个,空格隔开
default_domain = "anylink.ddns.vanjay.cn"

# session过期时间,用于断线重连,0永不过期
session_timeout = 0
auth_timeout = 0
# 审计去重间隔(秒),-1代表关闭审计日志
audit_interval = 600

show_sql = false

# 是否自动添加nat
iptables_nat = true

# 启用压缩
compression = false
# 低于及等于多少字节不压缩
no_compress_limit = 256

# 客户端显示详细错误信息(线上环境慎开启)
display_error = false

HAProxy 配置(可选)

如果群晖上运行了 HAProxy 做前置代理,backend 配置如下:

backend anylink
    mode tcp
    option ssl-hello-chk
    server anylink 127.0.0.1:4436 send-proxy-v2 check-send-proxy

使用 send-proxy-v2 时,server.toml 中必须设置 proxy_protocol = true,否则客户端无法连接。


启动服务

cd /volume3/MyDocker/anylink
docker compose up -d

# 查看日志
docker logs -f anylink

网络配置(关键步骤)

群晖 Docker 环境下,VPN 流量需要手动处理两层 NAT:

问题分析

手机 → HAProxy → anylink容器(tun0) → 容器eth0 → 宿主机网桥 → ovs_eth4 → 公网
  • 容器内:需要 MASQUERADE,将 VPN 客户端 IP(192.168.90.x)转换为容器 eth0 IP
  • 宿主机:需要 FORWARD 放行 + MASQUERADE,将容器 IP 转换为群晖物理 IP 出公网

宿主机 iptables 规则

# 放行 VPN 客户端流量转发
iptables -I FORWARD -s 192.168.90.0/24 -j ACCEPT
iptables -I FORWARD -d 192.168.90.0/24 -j ACCEPT

# 出口 NAT(注意:出口网卡是 ovs_eth4,不是 eth0)
iptables -t nat -A POSTROUTING -s 192.168.90.0/24 -o ovs_eth4 -j MASQUERADE

容器内 iptables 规则(关键)

anylink 的 iptables_nat = true 在群晖环境下不会自动生效(legacy/nftables 冲突),必须手动添加:

docker exec anylink iptables-legacy -t nat -A POSTROUTING \
  -s 192.168.90.0/24 -o eth0 -j MASQUERADE

开机自启任务

群晖重启后内核模块和 iptables 规则会全部丢失,需要设置开机任务。

路径: 控制面板 → 任务计划 → 新增 → 触发的任务 → 用户定义的脚本

  • 事件:开机
  • 用户:root

确定网口

root@SA6400 ~$ ip route | grep default
default via 192.168.18.1 dev ovs_eth4 src 192.168.18.8

脚本内容:

#!/bin/bash

# 加载内核模块
modprobe tun
modprobe iptable_filter
modprobe iptable_nat

# 等待 Docker 和容器启动
sleep 30

# 宿主机 iptables 规则
iptables -I FORWARD -s 192.168.90.0/24 -j ACCEPT
iptables -I FORWARD -d 192.168.90.0/24 -j ACCEPT
iptables -t nat -A POSTROUTING -s 192.168.90.0/24 -o ovs_eth4 -j MASQUERADE

# 等容器完全就绪
sleep 10

# 容器内 NAT 规则(VPN 客户端流量从容器 eth0 出去)
docker exec anylink iptables-legacy -t nat -A POSTROUTING \
  -s 192.168.90.0/24 -o eth0 -j MASQUERADE

验证

检查服务状态

docker ps | grep anylink
docker logs anylink --tail 20

检查规则是否生效

# 宿主机 NAT
iptables -t nat -L POSTROUTING -n -v | grep 192.168.90

# 宿主机 FORWARD
iptables -L FORWARD -n -v | grep 192.168.90

# 容器内 NAT(关键)
docker exec anylink iptables-legacy -t nat -L POSTROUTING -n -v | grep 192.168.90

三条都有输出则配置正确。

网络连通性测试

# 抓包确认 VPN 客户端流量
tcpdump -i any -n host 192.168.90.100

# 确认 NAT 出口
tcpdump -i ovs_eth4 -n src 192.168.18.8

客户端连接

平台 客户端
iOS App Store 搜索 Cisco Secure Client
Android Play Store 搜索 Cisco Secure Client
macOS App Store 搜索 Cisco Secure Client
Windows 下载 Cisco AnyConnect 客户端

连接地址:anylink.ddns.vanjay.cn:4436


管理面板

http://192.168.18.8:8809

可查看在线用户、连接状态、IP 分配情况。


常见问题

tun 模块未加载

[error] Linux module tun is not loaded, please run `modprobe tun`

解决:

sudo modprobe tun
sudo modprobe iptable_filter
sudo modprobe iptable_nat

连接成功但无法上网

最常见原因:容器内缺少 MASQUERADE 规则。执行:

docker exec anylink iptables-legacy -t nat -A POSTROUTING \
  -s 192.168.90.0/24 -o eth0 -j MASQUERADE

iptables nf_tables 警告

iptables v1.8.11 (nf_tables)
Warning: iptables-legacy tables present

这是正常现象,只要 docker-compose.yml 中设置了 IPTABLES_LEGACY: "on" 即可忽略。

proxy_protocol 导致连接失败

  • 有 HAProxy 前置:proxy_protocol = true
  • 直接暴露端口:proxy_protocol = false

两者不能混用。


防火墙放行

控制面板 → 安全性 → 防火墙 → 新增规则:

端口 协议 用途
4436 TCP AnyConnect TLS 连接
4436 UDP DTLS 加速
8809 TCP Web 管理面板