文章

为Ubuntu主机配置Privado VPN

如何为Ubuntu系统的电脑配置Privado VPN,支持域名白名单访问,并支持作为代理服务器

为Ubuntu主机配置Privado VPN

本文是和ChatGPT、Gemini对话后操作的总结,我也不一定完全懂全部配置,我也找到一篇教程,各位也可以去看

起因和基础信息

起初需要是因为N8N中把每日新闻推送到Github,进而Netlify时,国内不能稳定连接Github,于是我就索性把Privado VPN也配置上,有梯子就肯定不会有这个问题。可是配置好之后,N8N中获取国内新闻的RSS订阅就用不了了,可能是因为链接在国内,所以又去配置域名白名单。

我的需求有这些:

  • 梯子能用,且可以白名单访问
  • ZeroTier不影响使用
  • IPV6不影响访问(我需要IPV6公网)

电脑基础信息: -No LSB modules are available.

  • Distributor ID: Ubuntu
  • Description: Ubuntu 24.04.3 LTS
  • Release: 24.04
  • Codename: noble

加装Privado VPN

这里总共有两个方法,我在1月先使用的OpenVPN方法,第一次成功连接后第二次就一定会失败,我分析是被“墙”拦截了,所以更推荐使用WireGuard方法,不容易被“墙”拦截。首先贴出官方配置教程1,我使用的是命令行环境,因为是SSH连接到电脑的。

首先需要去自己账号的管理页面获取WireGurad的配置文件(往下划就看到了),然后转移到Ubuntu主机上(下面假设转移到的路径是/wiregurad_config/<config_name> 。至于这里的<config_name>能不能改,我也不是很清楚,我怀疑它的名字是有意义的,所以我没有更改。

然后是安装WireGuard,执行下面命令

1
sudo apt update && sudo apt install wireguard

随后将前面获取的配置文件移动到WireGuard的配置文件夹下,也就是执行这个命令

1
sudo mv /wireguard_config/<config_name> /etc/wireguard/<config_name>

接下来可以试试配置文件能不能正常加载了,不过要注意,前面的<config_name>是带有.conf后缀的,而下面的这个不需要后缀名,也就是不带.conf

1
sudo wg-quick up <config_name> # 关闭把up改down

见到类似如下输出就是没问题的

1
2
3
4
5
6
7
8
9
[#] wg setconf privado.ams-034 /dev/fd/63
[#] ip -4 address add 100.65.38.223/32 dev privado.ams-034
[#] ip link set mtu 1420 up dev privado.ams-034
[#] wg set privado.ams-034 fwmark 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
[#] ip -4 route add 0.0.0.0/0 dev privado.ams-034 table 51820
[#] sysctl -q net.ipv4.conf.all.src_valid_mark=1
[#] nft -f /dev/fd/63

然后我们就可以配置它开机自启动了,这个也很简单,WireGuard自带Services,直接开启对应Services就可以了,如下,注意这里<config_name>也不带.conf

1
sudo systemctl enable wg-quick@<config_name>.service

这样它就会开机自启动了,我们可以检查一下我们的外部地址,执行下面的命令

1
curl ipinfo.io

比如我连接的是阿姆斯特丹,返回就如下

1
2
3
4
5
6
7
8
9
10
11
{
  "ip": "91.148.245.68",
  "city": "Heerhugowaard",
  "region": "North Holland",
  "country": "NL",
  "loc": "52.6714,4.8486",
  "org": "AS34343 BIP Backbone ASN",
  "postal": "1701",
  "timezone": "Europe/Amsterdam",
  "readme": "https://ipinfo.io/missingauth"
}

配置白名单访问

现在我们就完成了WireGuard的安装,下面就是为其添加一个白名单过滤。原理上,WireGuard支持对静态IP进行配置,但是不支持域名和动态IP,对于我们的需求来说,这很显然不够,你不可能期望Google,ChatGPT,Github这些大网站都是静态IP,所以我们采取的方法是,自建DNS服务维护这些网站的IP,WireGuard根据这些IP控制是不是经过VPN。所以这里最主要的是,我们需要搭建一个本地DNS服务来维护这些IP。

这里ChatGPT给我的建议是

🥇 smartdns + ipset + nftables + WireGuard(白名单模式)

其中smartdns是本地的DNS服务,不过最终我感觉ipset好像没有用。第一步,我们来安装它们,大家可以先不安装ipset,看看后面需不需要再说。

1
2
sudo apt update
sudo apt install smartdns nftables ipset

然后是为Privado创建一个IP表,最初ChatGPT给的命令是

1
sudo ipset create vpnset hash:ip timeout 3600

但是我用了之后后面报错,nft不识别vpnset这个组,不过没关系,我们还可以创建nft自己的组并做一下配置,不过配置前我们需要知道Privado对应的fwmark参数(这个参数就是一个标记,最好是对应上),执行这个命令查询

1
sudo wg show

我得到的输出是这样的

1
2
3
4
5
6
7
8
9
10
11
interface: privado.ams-034
  public key: Ah(中间码掉)=
  private key: (hidden)
  listening port: 58794
  fwmark: 0xca6c # 就是这个

peer: Kg(中间码掉)=
  endpoint: (我码掉了)
  allowed ips: 0.0.0.0/0
  latest handshake: 2 minutes, 20 seconds ago
  transfer: 98.25 KiB received, 133.30 KiB sent

要的就是fwmark参数,然后我们进行配置,执行下面命令2

1
2
3
4
5
6
7
8
9
10
sudo nft add table inet vpn
sudo nft add set inet vpn vpnset '{ type ipv4_addr; flags interval,timeout; timeout 1h; }'

# 给非白名单打标(走直连)
sudo nft add chain inet vpn output '{ type route hook output priority mangle; policy accept; }'
sudo nft add rule inet vpn output ip daddr != @vpnset meta mark set 0xca6c counter

# 保证转发流量(Docker/ZeroTier)也一样
sudo nft add chain inet vpn prerouting '{ type filter hook prerouting priority mangle; policy accept; }'
sudo nft add rule inet vpn prerouting ip daddr != @vpnset meta mark set 0xca6c counter

注意命令里的0xca6c各位替换为自己获取的值就可以了

不过这里我们对nft的配置还没有完成,经过我实践发现,如果就这样到后面连DNS都会直接坏掉,因为DNS解析的流量也会被打标签过VPN,所以我们还得把DNS解析的流量分出来,这部分必须直连,而且应该在全部的最前面,命令如下2

1
2
3
4
5
6
sudo nft add set inet vpn dns_upstream '{ type ipv4_addr; flags interval; }' # 创建子set存放
sudo nft add element inet vpn dns_upstream { 119.29.29.29, 223.5.5.5, 8.8.8.8, 1.1.1.1 } # 这些都是DNS解析地址,可以改成你自己的,不过要对得上机器用的DNS,不然也不对,写哪些后面smartdns就也写哪些

# 直接放行
sudo nft insert rule inet vpn output ip daddr @dns_upstream counter accept
sudo nft insert rule inet vpn prerouting ip daddr @dns_upstream counter accept

验证可以使用这个命令列出电脑全部的nftable规则2

1
sudo nft list ruleset

输出里看到这样就是正常没问题的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
table inet vpn {
        set vpnset {
                type ipv4_addr
                flags interval,timeout
                timeout 1h
        }

        set dns_upstream {
                type ipv4_addr
                flags interval
                elements = { 1.1.1.1, 8.8.8.8,
                             119.29.29.29, 223.5.5.5 }
        }

        chain output {
                type route hook output priority mangle; policy accept;
                ip daddr @dns_upstream counter packets 0 bytes 0 accept
                ip daddr != @vpnset meta mark set 0x0000ca6c counter packets 0 bytes 0
        }

        chain prerouting {
                type filter hook prerouting priority mangle; policy accept;
                ip daddr @dns_upstream counter packets 0 bytes 0 accept
                ip daddr != @vpnset meta mark set 0x0000ca6c counter packets 0 bytes 0
        }
}

smartdns自动写入

不过组创建完了,smartdns还没有配置,所以没有任何域名写入,接下来我们需要配置smartdns,这样就会自动写入IP地址了。配置也很简单,首先进入配置文件按照官网3指示添加设置

1
sudo vi /etc/smartdns/smartdns.conf

比如要github.com通过梯子,就在最后面加上

1
nftset /github.com/#4:inet#vpn#vpnset

其他域名也同理更改前面就可以了。

不过不要着急,为了后面取代系统DNS,我们还需要修改一个地方,找到文件前面(或者新加),不论如何保证有这一行

1
2
bind :53     # IPV4
bind [::]:53 # IPV6

这个的意思是程序启动后自动挂载到电脑的53端口(也就是DNS解析端口,不过目前这个端口还有系统的DNS解析,所以启动也是无用的。配置smartdns开机启动使用的命令是

1
sudo systemctl enable smartdns

不过现在启动smartdns无用,因为正如我前面所说,53端口还占用着的,只有系统DNS下了启动才是成功的

总的来说到现在为止,如果域名符合我们的规则,就会被打上标记,后面我们利用这些标记,无标记的通过WireGuard就好了

nftable持久化

不过现在我们先专注于nft,我们的全部配置都依赖于这样的表,而nftable有一个很麻烦的点,每一次重启你的配置就没有了,我每一次重启后,前面配置的vpn表就消失了,所以我们需要将其配置持久化,每一次都能自动读取。为此,我们需要修改nft的配置,命令如下2

1
sudo sh -c "nft list table inet vpn > /etc/nftables.conf"

这个命令的意思是把查询的结果写入/etc/nftables.conf 文件(覆盖当前内容),查询的命令就是我们前面测试添加成功没有的命令。然后我们还需要修改一点东西,因为这个配置文件默认是有信息的,文件开头通常应该有#!/usr/sbin/nft -fflush ruleset,我也不知道它们什么意思,但是尽量少改,我还是又进入文件把这两行补在了文件的开头,结果就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/sbin/nft -f
flush ruleset

table inet vpn {
        set vpnset {
                type ipv4_addr
                flags interval,timeout
                timeout 1h
        }

        set dns_upstream {
                type ipv4_addr
                flags interval
                elements = { 1.1.1.1, 8.8.8.8, 119.29.29.29, 223.5.5.5 }
        }

        chain output {
                type route hook output priority mangle; policy accept;
                ip daddr @dns_upstream counter packets 0 bytes 0 accept
                ip daddr != @vpnset meta mark set 0x0000ca6c counter packets 0 bytes 0
        }

        chain prerouting {
                type filter hook prerouting priority mangle; policy accept;
                ip daddr @dns_upstream counter packets 0 bytes 0 accept
                ip daddr != @vpnset meta mark set 0x0000ca6c counter packets 0 bytes 0
        }
}

然后配置一下开机自动读取这个文件就可以了,只需要这个命令

1
sudo systemctl enable --now nftables

就可以自动读取了。

smartdns取代系统DNS

现在让我们回到正题,用smartdns替换系统DNS,首先使用这个命令关闭系统DNS

1
sudo systemctl disable --now systemd-resolved

然后启动smartdns

1
sudo systemctl enable --now smartdns

这时建议先检查一下我们前面的配置有没有问题,可以先查询table,然后ping一个网站,然后再看table有没有写入。为此可以像我一样把两个测外部IP的网站配置成一个通过,一个不通过(这里贴几个网站),然后测外部IP就很容易看到有没有问题

1
2
3
4
curl 4.ipw.cn
curl ipinfo.io
curl ipin.io
curl ipconfig.me

如果有问题,可以试一下这个命令,临时解决一下,看看是不是源地址的问题,如果是,那就是网卡看返回和发送网卡不一样直接丢弃了

1
sudo iptables -t nat -A POSTROUTING -o <出口的物理网卡> -s <WireGuard地址> -j MASQUERADE

撤销用这个命令

1
sudo iptables -t nat -D POSTROUTING -o <出口的物理网卡> -s <WireGuard地址> -j MASQUERADE

两个命令里的<WireGuard地址>可以这样得到

1
ip -4 -o addr show dev privado.ams-034

得到的是

1
29: privado.ams-034    inet 100.65.38.223/32 scope global privado.ams-034\       valid_lft forever preferred_lft forever

如果能解决问题,持久化就往/etc/wireguard/<config_name>.conf[Interface]里添加

1
2
PostUp = iptables -t nat -A POSTROUTING -o enp4s0 -s <WG_IP>/32 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o enp4s0 -s <WG_IP>/32 -j MASQUERADE

Docker

在前面配置完成之后,我的电脑就已经能用了,最后的小问题是Docker的DNS坏了,原因未知,但是解决方案也很简单,就是去Docker的配置文件手动设置本机的DNS就可以了,就是去/etc/docker/daemon.json 修改文件,加上这段

1
"dns":["192.168.101.92"]

其中192.168.101.92是主机的LAN IP,可以使用这个命令查看

1
ip -4 -o addr show dev <出口物理网卡,我是enp4s0>

得到

1
3: enp4s0    inet 192.168.101.92/24 brd 192.168.101.255 scope global dynamic noprefixroute enp4s0\       valid_lft 84212sec preferred_lft 84212sec

输出第一个就是主机LAN IP,加上重启Docker就好了

1
sudo systemctl restart docker

长期使用维护

现在我们的配置已经完成了,最后我总结一下长期使用时的维护方法,其实主要是添加删除域名之类的

首先,更改域名直接修改/etc/smartdns/smartdns.conf 在后面添加域名

1
nftset /youtube.com/#4:inet#vpn#vpnset

然后重启smartdns就可以了

1
sudo systemctl restart smartdns

配置网络代理

在给Ubuntu配置网络代理后,我们也可以进一步把它当做代理服务器,下面我们就来配置,这里我没有使用Tinyproxy,因为我最开始配置后使用codex结果一直出问题,感觉它代理HTTPS流量有一点问题,一直连接都报错意外的EOF,AI说可能是COONECT有问题,所以最后选的是Squid

1
sudo apt install squid

然后进入/etc/squid/squid.conf 编辑配置文件(因为这个文件自动生成的例子,太多了,还很长,我直接把它重命名/etc/squid/squid.example.conf 了,后面的配置都是重新写的)。

首先,写http_port,把它设置为你希望它运行的端口,比如我就改成http_port 3128。然后加上允许访问的源IP地址,比如我的电脑的LAN IP是192.168.101.92/24,ZeroTier的IP是172.22.165.129/16,那我就这样写

1
2
3
4
5
6
acl localnet src 192.168.101.0/24 # 0表示这一位随意匹配
acl zerotier src 172.22.0.0/16

http_access allow localnet
http_access allow zerotier
http_access deny all

然后为了支持HTTPS流量,还需要放行CONNECT到443端口的流量,还是配置文件

1
2
3
acl SSL_ports port 443
acl CONNECT method CONNECT
http_access allow CONNECT SSL_ports

含义是放行HTTPS到443端口的流量

然后启动Squid

1
sudo systemctl enable --now squid

需要注意的是,尽管Codex的流量是HTTPS流量,代理配置的环境变量仍然必须写HTTP,也就是这样

1
2
HTTP_PROXY=http://192.168.101.92:3128
HTTPS_PROXY=http://192.168.101.92:3128

因为Squid是HTTP代理,不能处理HTTPS流量,做HTTPS代理是直接转发的流量包,但是对接它本身必须使用HTTP

本文由作者按照 CC BY 4.0 进行授权