编码地址 · 2023年2月18日 0

利用SoftEther VPN实现群晖NAS公网访问

零、背景

之前的几篇博文中提到过,相较于使用frp等进行端口转发或反向代理,我更倾向于通过VPN访问位于家中的设备和服务。理由主要有三:1)公网不暴露端口,安全性更佳;2)常用平台均可使用VPN客户端或L2TP接入,可以分帐号进行权限/访问控制,进一步提升安全性;3)可以通过VPN将位于不同地点的设备联系起来实现万物互联,便于进行异地备份和数据共享等操作。

经过试用选型后最终放弃了以ZeroTier、N2N等支持P2P的VPN(主要是考虑到客户端兼容有限以及P2P穿透效果不佳),选择SoftEther作为实际部署的VPN。虽然这意味着所有流量都需要经过服务器,但6Mbps的带宽对于简单服务而言还算够用,且SoftEther提供多种接入方式,可以兼容我使用的所有平台;在过去数年间它也重复展现出了其易用性。

但一直存在的问题是,我没有在Linux上成功接入过SoftEther VPN。这一问题在我购入群晖NAS之后变得更加急需解决:群晖DSM只能通过L2TP或OpenVPN方式接入SoftEther VPN,但两种方式均不能自定义虚拟网络适配器的IP地址。不能固定IP最终让我放弃了直接在群晖NAS上接入VPN的想法,转而考虑用Linux虚拟机接入VPN并做转发/反向代理的方式。于是乎经过一番研究和实操,成功将一台Ubuntu虚拟机接入SoftEther VPN并实现群晖服务转发,以下是实现过程和遇到的一些问题。

一、Ubuntu Sever接入SoftEther VPN

最前面提一句,如无必要,不建议Ubuntu安装使用Network Manager L2TP接入VPN,经我反复尝试均无法成功,且未找到在Ubuntu上使用的详尽文档(当然如果使用的Linux发行版使用了nm,那不妨一试)。

个人建议直接使用SoftEther VPN的Linux客户端,虽然不提供GUI,但提供了与其他平台一致的完整功能。客户端通过源码形式分发,需要自行下载编译。

1.1、编译安装VPN客户端

首先安装编译所需软件:

sudo apt install build-essential gnupg2 gcc

从SoftEther官网下载和解压相应平台SoftEther VPN Client源码后,进入源码目录,进行编译:

make

启动VPN客户端则非常简单:

sudo ./vpnclient start

1.2、配置客户端

由于不提供GUI,对VPN客户端的配置通过编译得到的vpncmd进行,但基本原理和Windows端的客户端配置相同。启动vpncmd:

./vpncmd

选择“2. Management of VPN Client”并回车。和Windows客户端一样,先创建虚拟适配器:

# 注意:为了直观展示vpncmd的交互,这里会一同给出提示文字,例如此处的“VPN Client>”
# 提示字符不是命令的一部分,不需要输入
# 通过NicCreate创建适配器
VPN Client>NicCreate 虚拟适配器名称

然后创建VPN账户并选择使用哪个虚拟适配器:

# 通过AccountCreate创建账户
# 此处的账户名仅作为客户端中的标识,不必与登入VPN的用户名一致
VPN Client>AccountCreate 账户名
# 根据提示输入VPN服务器地址和端口
Destination VPN Server Host Name and Port Number: 地址:端口
# 根据提示输入虚拟Hub名称
Destination Virtual Hub Name: 虚拟Hub名
# 此处需要输入登入VPN的用户名
Connecting User Name: 用户名
# 指定使用的虚拟适配器
Used Virtual Network Adapter Name: 虚拟适配器名称

接着为VPN账户配置密码:

# 为刚才创建的账户配置密码,注意这里是账户名而非登入用户名
VPN Client>AccountPassword 账户名
Password: ************
Confirm input: ************
Specify standard or radius: radius

随后可以启动VPN连接,或将VPN连接设置为随客户端启动:

# 连接该账户
VPN Client>AccountConnect 账户名
# 将该账户设为随客户端启动
VPN Client>AccountStartupSet 账户名

如果想对客户端进行更多配置或操作的话,可以在官网文档找到所有命令的使用说明。

1.3、固定虚拟适配器IP

熟悉SoftEther VPN和netplan的话,这一步就相对简单了。VPN客户端在创建虚拟适配器时,会在系统中添加一个名为“vpn_虚拟适配器名”的网络适配器。为这个适配器设定固定IP即可在连接VPN后使用固定IP,那么这里我就简单举个通过netplan实现的例子。

首先在/etc/netplan/下创建配置文件,内容大概如下,注意修改适配器名称和IP地址:

network:
  version: 2
  ethernets:
    适配器名称:
      dhcp4: no
      addresses:
        - 地址/掩码位数

随后应用netplan修改,即完成固定IP设置:

sudo netplan apply

1.4、VPN客户端随系统启动

将VPN客户端作为服务启动的配置这里踩了一下坑:DigitalOceanSoftEther官网提供的文档严重过时,如果参考了类似这两篇中提到的方法,而无法成功启动客户端的话,考虑换一个参考。

我参考的是AskUbuntu上的一篇回答,经过验证在Ubuntu 22.04上可用,注意修改一下路径:

# /etc/systemd/system/softether-vpnclient.service
[Unit]
Description=SoftEther VPN Client
After=network.target

[Service]
Type=forking
ExecStart=/path/to/client/dir/vpnclient start
ExecStop=/path/to/client/dir/vpnclient stop

[Install]
WantedBy=multi-user.target

然后就可以将VPN客户端通过systemctl控制启停和随系统启动了,方法不再赘述。

二、转发群晖NAS服务

最初考虑过用Nginx做反向代理,但反代只能代理HTTP/HTTPS服务,需要使用SMB等不基于HTTP的服务的话则需要另想办法。所以选用了socat直接进行端口转发,管它是HTTP还是SMB,TCP还是UDP,老实转发就完事儿了:

sudo apt install socat

测试通过命令转发SMB的445端口:

sudo socat tcp-listen:445,reuseaddr,fork tcp:群晖NAS的IP:445

在转发用虚拟机和SMB客户机均连接到VPN时,可通过VPN访问虚拟机445端口连接至群晖SMB服务。

接下来参考这篇回答,将其写成服务并设置随系统启动,注意根据情况进行修改:

# /etc/systemd/system/smb-forward.service
[Unit]
Description=SMB port forward service
After=network.target

[Service]
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=smb-forward

ExecStart=/usr/bin/socat -d -d tcp-listen:445,reuseaddr,fork,bind=VPN适配器地址 tcp:NAS地址:445
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

注意:这里配置了bind=VPN适配器地址,socat将绑定并仅监听来自VPN的连接;但系统启动时若VPN未完成连接,绑定地址失败将导致服务启动失败;经测试可以给服务加入自动重启,启动失败后重新尝试,通常可以解决问题。

类似的,转发HTTP/HTTPS端口,即可通过VPN经转发用虚拟机访问群晖服务。如果有意为HTTPS配置证书,同样可以参考前述的回答,为其配置证书和密钥文件,这里便不再赘述,仅引用一下回答中的配置供快速查看。

# /etc/systemd/system/dovecot-auth-bridge.service
[Unit]
Description=Dovecot auth bridge
After=dovecot.service
Requires=dovecot.service

[Service]
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=dovecot-auth-bridge

ExecStart=socat -d -d OPENSSL-LISTEN:9999,reuseaddr,fork,cert=/etc/ssl/certs/dovecotserver.pem,key=/etc/ssl/private/dovecotserver.key,bind=10.10.20.5,cafile=/etc/ssl/certs/eximserver.pem UNIX:/run/dovecot/auth-client
Restart=always

[Install]
WantedBy=multi-user.target
https://unix.stackexchange.com/questions/658312/how-to-run-socat-as-a-systemd-service-to-bridge-two-remote-unix-sockets

三、小结

理清思路后整个配置并不算太麻烦,主要是找错参考资料的话会走不少弯路。文中记录并给出了每个步骤的参考以供未来参考。讲真HTTP和HTTPS以后可能还是会用Nginx反代并配置证书自动续签,免得忘记更新证书导致连不上;socat用于转发SMB确实简单实用,但直接这样转发HTTP服务多少有点简单粗暴了,还是希望通过Nginx提供更精细的配置和管理。