如何在 Ubuntu 服务器上安装 Shadowsocks-Rust

由于Unbinilium的Twist脚本长时间不更新且已删库,想自己维护但确实不太会写shell脚本,只好把他脚本里的内容一步步写下来,当作日记,也方便自己日后使用。

前置准备

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 在tmp目录操作
cd /tmp

# 创建备份目录
sudo mkdir /etc/twist
# 文件格式:filename.old-$(date +%Y%m%d%H%M%S)

sudo apt update
sudo apt upgrade

# wget、curl下载可执行文件,git下载仓库
# nginx用于伪装,fail2ban用来ban恶意IP
# 其他都是一些编译、运行依赖的,或者是一些实用工具
sudo apt install -y wget gawk grep curl sed git gcc swig gettext autoconf \
automake make libtool perl cpio xmlto asciidoc cron net-tools dnsutils \
rng-tools libc-ares-dev libev-dev openssl libssl-dev zlib1g-dev libpcre3-dev \
libevent-dev build-essential python3-dev python3-pip python3-setuptools \
python3-qrcode nginx fail2ban certbot python3-certbot-nginx tmux btop tree

# 检测并获取服务器的出口网卡名称
# ETH="$(route | grep '^default' | grep -o '[^ ]*$')"
route | grep '^default' | grep -o '[^ ]*$'
# eth0

# 没有就再执行
# ETH="$(ip -4 route list 0/0 | grep -Po '(?<=dev )(\S+)')"
ip -4 route list 0/0 | grep -Po '(?<=dev )(\S+)'
# eth0

# 查看eth0网卡当前状态
cat /sys/class/net/eth0/operstate
# up/down

# 获取服务器公网IPv4地址
dig @resolver1.opendns.com -t A -4 myip.opendns.com +short

# 查看是否有IPv6地址
ip -6 addr show eth0

# 获取服务器公网IPv6地址
curl -s diagnostic.opendns.com/myip

# 获取mtu数值
cat /sys/class/net/eth0/mtu
# 一般是1500,如果没有输出,后面就把mtu设置为1492

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 添加用户
sudo useradd shadowsocks

# 禁用swap
# 找到swap文件
sudo swapon
# 关闭swap文件
sudo swapoff /tmp/swapfile
# 删除swap文件
sudo rm /tmp/swapfile
# 将swap文件注释掉
sudo vi /etc/fstab
# 注意,不同的系统/vps服务商的swap文件不太一样,debian把swap交给systemd管理,有些则是放到rc.local手动管理的

# 设置默认编辑器
select-editor

开启bbr算法

如果内核大于等于4.8,可以直接开启bbr算法,否则可以升级内核后开启

1
2
3
4
5
6
# 如果存在 /proc/user_beancounters 这个文件,说明当前系统运行在 OpenVZ 虚拟化环境(通常是老版本 OpenVZ),这种环境下内核功能受限,不支持 BBR 拥塞控制算法
[ -e /proc/user_beancounters ] && echo "存在" || echo "不存在"

# BBR 拥塞控制算法需要 Linux 4.9 及以上版本支持。这里用 4.8 做判断,说明 4.8 及以下的内核不支持 BBR,必须升级内核并重启服务器
uname -r | grep -oE '[0-9]+\.[0-9]+'
# 6.8

内核版本大于等于4.8或者内核已经升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 备份
cp /etc/sysctl.conf /etc/twist/sysctl.conf.old-$(date +%Y%m%d%H%M%S)

# 查看当前正在使用的算法,即使是新的内核也不一定有bbr,这取决于VPS运营商
sudo sysctl net.ipv4.tcp_available_congestion_control
# net.ipv4.tcp_available_congestion_control = reno cubic bbr

# 如果上面输出没有bbr,检查下下面的文件是否存在
ls /lib/modules/$(uname -r)/kernel/net/ipv4/tcp_bbr.ko*
# /lib/modules/6.8.0-31-generic/kernel/net/ipv4/tcp_bbr.ko.zst

# 存在则加载模块
sudo modprobe tcp_bbr

# 如果上面发现支持bbr算法,直接改为bbr
sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.conf
# 如果不支持,就更新内核

更新内核(我没尝试过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 最新内核版本
KERNELURL="https://kernel.ubuntu.com/~kernel-ppa/mainline/"
wget -qO- "$KERNELURL" | awk -F'\"v' '/v[4-9]./{print $2}' | cut -d/ -f1 | grep -v - | sort -V | tail -1
# 6.19.11

# 架构
dpkg --print-architecture
# amd64

# 下载、安装
KERNELVER="$(wget -qO- ${KERNELURL} | awk -F'\"v' '/v[4-9]./{print $2}' | cut -d/ -f1 | grep -v - | sort -V | tail -1)"
for pkg in linux-headers linux-headers-all linux-modules linux-image-unsigned; do
FILE=$(wget -qO- "${KERNELURL}v${KERNELVER}/" | grep "$pkg" | grep "generic" | grep "amd64.deb" | awk -F'\">' '{print $2}' | cut -d'<' -f1 | head -1)
[ -n "$FILE" ] && wget -c "${KERNELURL}v${KERNELVER}/${FILE}"
done
sudo dpkg -i *.deb
sudo update-grub
sudo reboot

安装shadowsocks-rust

shadowsocks-rust跟shadowsocks-libev不同,不支持xchacha20-ietf-poly1305算法

1
2
3
4
5
6
7
8
9
10
# 下载编译好的文件,rust编译太夸张,8GB内存都不一定够用
# https://github.com/shadowsocks/shadowsocks-rust/releases/tag/v1.24.0
curl -LO https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.24.0/shadowsocks-v1.24.0.x86_64-unknown-linux-gnu.tar.xz

# 对比hash值
# sha256:5f528efb4e51e732352f5c69538dcc76e8cf8f6d1a240dfb5b748a67f0b05f65
sha256sum shadowsocks-v1.24.0.x86_64-unknown-linux-gnu.tar.xz

# 解压缩,要指定目录,都是些可执行文件,直接解压乱七八糟的
tar Jxf shadowsocks-v1.24.0.x86_64-unknown-linux-gnu.tar.xz -C /usr/local/bin

创建systemd服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/systemd/system/shadowsocks-rust.service
[Unit]
Description=Shadowsocks-Rust Service
After=network.target

[Service]
User=shadowsocks
Group=shadowsocks
Type=simple
ExecStart=/usr/local/bin/ssserver -c /etc/shadowsocks-rust/config.json
Restart=on-failure
LimitNOFILE=512000

[Install]
WantedBy=multi-user.target

安装simple-obfs:simple-obfs已经不更新了,各大发行版也移除了,但目前还是需要用到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 下载仓库
git clone https://github.com/shadowsocks/simple-obfs.git

#
cd simple-obfs

# 拉取依赖项
git submodule update --init --recursive

# 编译
./autogen.sh
./configure --prefix=/usr/local/simple-obfs && make

# 安装
sudo make install
# /usr/local/simple-obfs/bin/obfs-server

配置shadowsocks-rust

确定监听地址

1
2
3
4
5
# 如果支持IPv6 
# ["[::0]","0.0.0.0"]

# 只有IPv4
# "0.0.0.0"

DNS

1
2
3
4
5
# 如果支持IPv6 
# 8.8.8.8,8.8.4.4,2001:4860:4860::8888,2001:4860:4860::8844

# 只有IPv4
# 8.8.8.8,8.8.4.4

密码生成

1
2
3
4
5
# shadowsocks-libev可以用这种方法
PASSWORD="$(< /dev/urandom tr -dc 'A-HJ-NPR-Za-km-z2-9-._+?%^&*()' | head -c 8)"

# shadowsocks-rust的ssservice可以根据算法生成一个密码
PASSWORD="$(ssservice genkey -m "aes-256-gcm")"

生成json文件

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
29
30
# 确保目录存在
mkdir -p /etc/shadowsocks-rust

# 下面是IPv4的配置
# 替换掉${PASSWORD}
# 模拟microsoft.com的服务器,让gfw去祸害他们吧
# 如果支持ipv6,就把ipv6_first设置为true
cat > /etc/shadowsocks-rust/config.json <<-EOF
{
"server":"0.0.0.0",
"server_port":443,
"password":"${PASSWORD}",
"method":"aes-256-gcm",
"timeout":1800,
"udp_timeout":1800,
"plugin":"/usr/local/simple-obfs/bin/obfs-server",
"plugin_opts":"obfs=tls;obfs-host=microsoft.com;obfs-uri=/",
"fast_open":true,
"reuse_port":true,
"nofile":512000,
"nameserver":"8.8.8.8,8.8.4.4",
"dscp":"EF",
"mode":"tcp_and_udp",
"mtu":1500,
"mptcp":false,
"ipv6_first":false,
"use_syslog":true,
"no_delay":true,
}
EOF

配置内核参数

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 开启bbr已经备份了,可以忽略
# cp /etc/sysctl.conf /etc/twist/sysctl.conf.old-$(date +%Y%m%d%H%M%S)

# Twist这个字是用来识别是否已安装的,不想改了
# tcp_keepalive_time这个值要跟timeout保持一致
cat >> /etc/sysctl.conf <<-EOF

# Twist
fs.file-max = 512000
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.netdev_max_backlog = 256000
net.core.somaxconn = 4096
net.ipv4.udp_mem = 25600 51200 102400
net.ipv4.tcp_mem = 25600 51200 102400
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.ip_local_port_range = 49152 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 4096
net.core.default_qdisc = fq
net.ipv4.ip_forward = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fack = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_dsack = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fwmark_accept = 1
net.ipv4.tcp_stdurg = 1
net.ipv4.tcp_synack_retries = 30
net.ipv4.tcp_syn_retries = 30
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_mtu_probing = 2
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_low_latency = 1
net.ipv4.udp_l3mdev_accept = 1
net.ipv4.fib_multipath_hash_policy = 1
net.ipv4.fib_multipath_use_neigh = 1
net.ipv4.cipso_rbm_optfmt = 1
net.ipv4.fwmark_reflect = 1
net.ipv4.conf.all.accept_source_route = 1
net.ipv4.conf.all.accept_redirects = 1
net.ipv4.conf.all.send_redirects = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.arp_accept = 1
net.ipv4.conf.all.arp_announce = 1
net.ipv4.conf.all.proxy_arp = 1
net.ipv4.conf.all.proxy_arp_pvlan = 1
net.ipv4.conf.all.mc_forwarding = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.all.accept_source_route = 1
net.ipv6.conf.all.accept_redirects = 1
net.ipv6.conf.all.autoconf = 1
net.ipv6.conf.all.accept_ra = 2
net.ipv6.conf.all.seg6_enabled = 1

# conntrack accounting
# net.netfilter.nf_conntrack_acct=1

EOF

配置用户或进程的资源限制

1
2
3
4
5
6
7
# 备份
cp /etc/security/limits.conf /etc/security/limits.conf.old-$(date +%Y%m%d%H%M%S)

#
echo "* soft nofile 512000" >> /etc/security/limits.conf
echo "* hard nofile 512000" >> /etc/security/limits.conf
echo "" >> /etc/security/limits.conf

DNS 配置

1
2
3
4
5
6
7
8
9
# 覆盖
echo "nameserver 8.8.8.8" > /etc/resolv.conf
# 追加
echo "nameserver 8.8.8.4" >> /etc/resolv.conf

# 如果支持IPv6
# echo "nameserver 2001:4860:4860::8888" >> /etc/resolv.conf
# echo "nameserver 2001:4860:4860::8844" >> /etc/resolv.conf
echo "" >> /etc/resolv.conf

配置防火墙

ufw

twist用的是iptables,比较专业,但我用ufw比较多,下面是ufw的同等配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 先把ssh的访问开放
sudo ufw allow 22/tcp

sudo ufw allow 80/tcp
sudo ufw allow 80/udp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

# 禁止本机访问内部网络
sudo ufw deny out to 10.0.0.0/8
sudo ufw deny out to 172.16.0.0/12
sudo ufw deny out to 192.168.0.0/16
sudo ufw deny out to 169.254.0.0/16
sudo ufw deny out to 100.64.0.0/10
sudo ufw deny out to 198.18.0.0/15

# IPv6
sudo ufw deny out to fc00::/7
sudo ufw deny out to fe80::/10

内核参数配置

1
2
3
4
# 修改文件/etc/ufw/sysctl.conf
# 当你执行 ufw enable、ufw reload、或系统启动并自动加载 UFW 时,UFW 会主动读取 /etc/ufw/sysctl.conf 并应用里面的内核参数
net/ipv4/ip_forward=1
net/ipv6/conf/all/forwarding=1

细粒度的iptables修改

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
29
30
# 修改/etc/ufw/before.rules
# 添加自定义的 iptables 规则(如 NAT、TCPMSS、特殊转发等),这些规则会在 UFW 的主规则之前被应用

# 在开头添加(*filter 前)
# NAT 规则(放在最前面)
# 对所有经过指定网卡(${ETH},比如 eth0)的出站流量,做源地址伪装(MASQUERADE),实现 NAT(网络地址转换
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o ${ETH} -j MASQUERADE
COMMIT

# 验证
# sudo ufw reload
# sudo iptables -t nat -L -n -v

# ufw 不直接支持 TCPMSS 规则。如需添加,放在 *filter 里,COMMIT 前
# *filter
# :ufw-before-input - [0:0]
# :ufw-before-output - [0:0]
# :ufw-before-forward - [0:0]
# :ufw-not-local - [0:0]
# :ufw-user-input - [0:0]
# :ufw-user-output - [0:0]
# :ufw-user-forward - [0:0]
-A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# COMMIT

# 验证
# sudo ufw reload
# sudo iptables -t filter -L FORWARD -n --line-numbers

启动ufw并设置开机启动

1
2
3
4
5
6
# 开启防火墙
sudo ufw enable
# 设置开机启动
sudo systemctl enable --now ufw
# 验证
sudo ufw status verbose

nftables

注意:nftables跟iptables一样,很危险,不要随意操作

临时修改

使用 nft 命令

rule 的顺序很重要。add 默认将规则插入到最后,insert 默认插入到最前面,replace 直接替换指定 rule。操作前先用 nft -a list ruleset 查看 handle

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 创建表
nft add table inet filter
nft add table ip nat
nft add table ip6 nat

# filter 表
nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
nft add chain inet filter before-input
nft add chain inet filter main-input
nft add chain inet filter after-input
nft add chain inet filter forward '{ type filter hook forward priority 0; policy drop; }'
nft add chain inet filter output '{ type filter hook output priority 0; policy accept; }'
nft add chain inet filter before-output
nft add chain inet filter main-output

# input 链跳转
nft add rule inet filter input jump before-input
nft add rule inet filter input jump main-input
nft add rule inet filter input jump after-input
nft add rule inet filter input drop

# before-input 链
nft add rule inet filter before-input iifname "lo" counter accept
nft add rule inet filter before-input ct state established,related counter accept
nft add rule inet filter before-input ct state invalid counter drop
nft add rule inet filter before-input ip protocol icmp icmp type echo-request limit rate 10/second burst 20 packets counter accept
nft add rule inet filter before-input ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } counter accept
nft add rule inet filter before-input meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 10/second burst 20 packets counter accept
nft add rule inet filter before-input meta l4proto ipv6-icmp icmpv6 type { echo-reply, destination-unreachable, packet-too-big, time-exceeded, parameter-problem } counter accept

# main-input 链
nft add rule inet filter main-input tcp dport 22 ct state new limit rate 5/minute burst 5 packets counter accept comment "input-tcp-22-limited"
nft add rule inet filter main-input tcp dport 80 counter accept comment "input-tcp-80"
nft add rule inet filter main-input tcp dport 443 counter accept comment "input-tcp-443"
nft add rule inet filter main-input udp dport 443 counter accept comment "input-udp-443"
nft add rule inet filter main-input tcp dport 30001 meta mark set 1 counter accept comment "input-tcp-30001"
nft add rule inet filter main-input udp dport 30001 meta mark set 1 counter accept comment "input-udp-30001"
nft add rule inet filter main-input tcp dport 30002 meta mark set 2 counter accept comment "input-tcp-30002"
nft add rule inet filter main-input udp dport 30002 meta mark set 2 counter accept comment "input-udp-30002"
nft add rule inet filter main-input tcp dport 30003 meta mark set 3 counter accept comment "input-tcp-30003"
nft add rule inet filter main-input udp dport 30003 meta mark set 3 counter accept comment "input-udp-30003"

# after-input 链
nft add rule inet filter after-input limit rate 3/minute burst 10 packets counter log prefix "[nft BLOCK INPUT] "

# forward 链
nft add rule inet filter forward tcp flags syn tcp option maxseg size set rt mtu
nft add rule inet filter forward ct state established,related counter accept

# output 链跳转
nft add rule inet filter output jump before-output
nft add rule inet filter output jump main-output

# before-output 链
nft add rule inet filter before-output oifname != "eth0" counter accept
nft add rule inet filter before-output udp dport 53 counter accept comment "Allow outbound DNS"
nft add rule inet filter before-output tcp dport 53 counter accept comment "Allow outbound DNS"
nft add rule inet filter before-output udp sport 68 udp dport 67 counter accept comment "Allow outbound DHCP"
nft add rule inet filter before-output ip daddr 169.254.169.254 limit rate 3/minute burst 5 packets log prefix "[nft BLOCK METADATA] "
nft add rule inet filter before-output ip daddr 169.254.169.254 counter reject comment "Block Cloud Metadata API"
nft add rule inet filter before-output ip daddr 10.0.0.0/8 counter reject comment "Block Class A (Cloud VPCs & Large Enterprise Intranets)"
nft add rule inet filter before-output ip daddr 172.16.0.0/12 counter reject comment "Block Class B (Docker bridge & Container networks)"
nft add rule inet filter before-output ip daddr 192.168.0.0/16 counter reject comment "Block Class C (Local & Management networks)"
nft add rule inet filter before-output ip daddr 100.64.0.0/10 counter reject comment "Block CGNAT (Cloud infrastructure routing)"
nft add rule inet filter before-output ip6 daddr fc00::/7 counter reject comment "Block IPv6 ULA (Unique Local Addresses)"
nft add rule inet filter before-output ip6 daddr fe80::/10 counter reject comment "Block IPv6 Link-Local (Subnet-only addresses)"

# main-output 链
nft add rule inet filter main-output tcp sport 30001 meta mark set 1 counter accept comment "output-tcp-30001"
nft add rule inet filter main-output udp sport 30001 meta mark set 1 counter accept comment "output-udp-30001"
nft add rule inet filter main-output tcp sport 30002 meta mark set 2 counter accept comment "output-tcp-30002"
nft add rule inet filter main-output udp sport 30002 meta mark set 2 counter accept comment "output-udp-30002"
nft add rule inet filter main-output tcp sport 30003 meta mark set 3 counter accept comment "output-tcp-30003"
nft add rule inet filter main-output udp sport 30003 meta mark set 3 counter accept comment "output-udp-30003"

# NAT表
nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; policy accept; }'
nft add rule ip nat postrouting oifname "eth0" masquerade

nft add chain ip6 nat postrouting '{ type nat hook postrouting priority 100; policy accept; }'
nft add rule ip6 nat postrouting oifname "eth0" masquerade

验证

1
2
3
4
5
6
7
8
9
10
11
# 列出所有table
sudo nft list tables

# 输出如下
# table inet filter
# table ip nat
# table ip6 nat
# table inet f2b-table

# 列出所有完整规则集,包括handle
sudo nft -a list ruleset

永久修改

备份

1
sudo cp /etc/nftables.conf /etc/twist/nftables.conf.old-$(date +%Y%m%d%H%M%S)

编辑 /etc/nftables.conf 配置文件

一定要小心使用 flush 命令,flush ruleset会清空其他table,如fail2ban的table。flush table inet filter 会导致当前连接直接中断,无法再连接上。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/sbin/nft -f

# WARNING: Dangerous flush commands. Do not run interactively via nft CLI.
flush ruleset
# sudo nft -c -f /etc/nftables.conf
# sudo systemctl restart nftables fail2ban

# IPv4/IPv6 Filter Table
table inet filter {
# Input Chain
chain input {
type filter hook input priority 0; policy drop;

# Split into 3 stages for sequential processing and easier dynamic updates
jump before-input
jump main-input
jump after-input

# Drop all other traffic
drop
}

# Stage 1: Essential rules (Do not modify)
chain before-input {
# Allow loopback
iifname "lo" counter accept

# Allow established/related connections
ct state established,related counter accept

# Drop invalid packets
ct state invalid counter drop

# Allow essential IPv4 ICMP (ping, traceroute, MTU discovery, etc.)
ip protocol icmp icmp type echo-request limit rate 10/second burst 20 packets counter accept
ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } counter accept

# Allow essential IPv6 ICMP
meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 10/second burst 20 packets counter accept
meta l4proto ipv6-icmp icmpv6 type { echo-reply, destination-unreachable, packet-too-big, time-exceeded, parameter-problem } counter accept
}

# Stage 2: Main custom rules (Modify ports here)
chain main-input {
# SSH with Rate Limiting (Protects against brute force alongside fail2ban)
tcp dport 22 ct state new limit rate 5/minute burst 5 packets counter accept comment "input-tcp-22-limited"

# Standard Web Ports
tcp dport 80 counter accept comment "input-tcp-80"
tcp dport 443 counter accept comment "input-tcp-443"
udp dport 443 counter accept comment "input-udp-443"

# Custom proxy ports
tcp dport 30001 meta mark set 1 counter accept comment "input-tcp-30001"
udp dport 30001 meta mark set 1 counter accept comment "input-udp-30001"
tcp dport 30002 meta mark set 2 counter accept comment "input-tcp-30002"
udp dport 30002 meta mark set 2 counter accept comment "input-udp-30002"
tcp dport 30003 meta mark set 3 counter accept comment "input-tcp-30003"
udp dport 30003 meta mark set 3 counter accept comment "input-udp-30003"

# Rustdesk-server
# ip protocol tcp tcp dport 8114-8119 counter accept comment "input-tcp-8114-8119"
# udp dport 8116 counter accept

# WireGuard (Uncomment when deploying WG)
# udp dport 51820 counter accept comment "input-udp-wireguard"
}

# Stage 3: Fallback and Logging
chain after-input {
# Log dropped packets with rate limiting
limit rate 3/minute burst 10 packets counter log prefix "[nft BLOCK INPUT] "
}

# Forward Chain: Generally unused on standard VPS (typically for transparent proxies)
chain forward {
type filter hook forward priority 0; policy drop;

# MSS clamping (avoid fragmentation)
tcp flags syn tcp option maxseg size set rt mtu

# Allow Established/Related forwarded traffic
ct state established,related counter accept

# WireGuard Forwarding (Uncomment when deploying WG)
# iifname "wg0" oifname "eth0" counter accept comment "Allow WG to WAN"

# Docker Forwarding (Uncomment if using Docker on this machine)
# iifname "docker0" oifname "eth0" counter accept comment "Allow Docker to WAN"
}

# Output Chain
chain output {
type filter hook output priority 0; policy accept;

jump before-output
jump main-output
}

chain before-output {
# Allow loopback and local virtual interfaces (Docker/WG) freely
oifname != "eth0" counter accept

# Whitelist essential network services (DNS & DHCP) for the VPS itself
# This is REQUIRED before the reject rules below, because cloud DNS/DHCP
# servers are often located within the 10.x.x.x or 100.64.x.x private ranges.
udp dport 53 counter accept comment "Allow outbound DNS"
tcp dport 53 counter accept comment "Allow outbound DNS"
udp sport 68 udp dport 67 counter accept comment "Allow outbound DHCP"

# CRITICAL: Block Cloud Metadata API (Prevents stealing cloud tokens)
# Added rate-limited logging to track malicious probes without flooding the disk
ip daddr 169.254.169.254 limit rate 3/minute burst 5 packets log prefix "[nft BLOCK METADATA] "
ip daddr 169.254.169.254 counter reject comment "Block Cloud Metadata API"

# Block access to private IP ranges from the proxy/VPS via the WAN interface
# These rules prevent shadowsocks clients from scanning your cloud provider's internal VPC
ip daddr 10.0.0.0/8 counter reject comment "Block Class A (Cloud VPCs & Large Enterprise Intranets)"
ip daddr 172.16.0.0/12 counter reject comment "Block Class B (Docker bridge & Container networks)"
ip daddr 192.168.0.0/16 counter reject comment "Block Class C (Local & Management networks)"
ip daddr 100.64.0.0/10 counter reject comment "Block CGNAT (Cloud infrastructure routing)"

ip6 daddr fc00::/7 counter reject comment "Block IPv6 ULA (Unique Local Addresses)"
ip6 daddr fe80::/10 counter reject comment "Block IPv6 Link-Local (Subnet-only addresses)"
}

chain main-output {
# Count output traffic for specific ports (tcp/udp)
tcp sport 30001 meta mark set 1 counter accept comment "output-tcp-30001"
udp sport 30001 meta mark set 1 counter accept comment "output-udp-30001"
tcp sport 30002 meta mark set 2 counter accept comment "output-tcp-30002"
udp sport 30002 meta mark set 2 counter accept comment "output-udp-30002"
tcp sport 30003 meta mark set 3 counter accept comment "output-tcp-30003"
udp sport 30003 meta mark set 3 counter accept comment "output-udp-30003"
}
}

# IPv4 NAT Table (Source Address Masquerading)
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Automatically handles NAT for WireGuard (wg0) or Docker (docker0) routing out via eth0
oifname "eth0" masquerade
}
}

# IPv6 NAT Table (Source Address Masquerading)
table ip6 nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "eth0" masquerade
}
}

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 执行查看是否有异常,不加载仅校验,dry-run
sudo nft -c -f /etc/nftables.conf

# 重启服务
sudo systemctl restart nftables fail2ban

# 列出所有table
sudo nft list tables

# 输出如下
# table inet filter
# table ip nat
# table ip6 nat
# table inet f2b-table

# 列出所有完整规则集,包括handle
sudo nft -a list ruleset

# 设置开机启动
sudo systemctl enable --now nftables.service

iptables

下面是iptables的配置,有点老了,最新的是使用nftables,目前还是兼容的

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 备份当前配置
iptables-save > /etc/twist/iptables.rules
iptables-save > /etc/twist/iptables.rules.old-$(date +%Y%m%d%H%M%S)

# 下面的$PORT改为443或服务监听端口,$ETH改为eth0或网卡名
# 丢弃所有无效(INVALID)状态的入站数据包,提升安全性,防止异常包攻击
iptables -I INPUT -m conntrack --ctstate INVALID -j DROP
# 允许所有已建立和相关的入站连接(如返回包),保证正常通信不被阻断
iptables -I INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# 允许所有目标端口为 $PORT 的 TCP 入站流量
iptables -I INPUT -p tcp -m multiport --dports $PORT -j ACCEPT
# 允许所有目标端口为 $PORT 的 UDP 入站流量
iptables -I INPUT -p udp -m multiport --dports $PORT -j ACCEPT
# 允许新建的 TCP 连接到 $PORT 端口
iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport $PORT -j ACCEPT
# 允许新建的 UDP 连接到 $PORT 端口
iptables -I INPUT -m state --state NEW -m udp -p udp --dport $PORT -j ACCEPT
# 丢弃所有无效的转发数据包,防止异常流量穿透服务器
iptables -I FORWARD -m conntrack --ctstate INVALID -j DROP
# 对所有转发的 TCP SYN 包自动调整 MSS,防止 MTU 不匹配导致的丢包或卡顿
iptables -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# 对所有通过 $ETH 网卡出网的流量做源地址伪装(NAT),实现端口转发、内网穿透等功能
iptables -t nat -A POSTROUTING -o $ETH -j MASQUERADE
# 保存用于重启后恢复
iptables-save > /etc/iptables.rules

# 备份
cp /etc/ip6tables.rules /etc/twist/ip6tables.rules.old-$(date +%Y%m%d%H%M%S)
# 把 /etc/iptables.rules 的全部内容复制到 /etc/ip6tables.rules,原有内容会被完全覆盖
cp -f /etc/iptables.rules /etc/ip6tables.rules

# 没有目录就创建
mkdir -p /etc/network/if-pre-up.d

# 备份
cp /etc/network/if-pre-up.d/iptablesload /etc/twist/iptablesload.old-$(date +%Y%m%d%H%M%S)
# 每次网络接口启动前,把你保存的防火墙规则重新导入系统
cat > /etc/network/if-pre-up.d/iptablesload <<-EOF
#!/bin/sh

iptables-restore < /etc/iptables.rules
exit 0

EOF

# 备份
cp /etc/network/if-pre-up.d/ip6tablesload /etc/twist/ip6tablesload.old-$(date +%Y%m%d%H%M%S)
# 每次网络接口启动前,把你保存的防火墙规则重新导入系统
cat > /etc/network/if-pre-up.d/ip6tablesload <<-EOF
#!/bin/sh

ip6tables-restore < /etc/ip6tables.rules
exit 0

EOF

开机启动

原版

可以忽略,现在用systemd,没怎么用rc.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 备份
cp /etc/rc.local /etc/twist/rc.local.old-$(date +%Y%m%d%H%M%S)

# 配置开机启动
cat >> /etc/rc.local <<-EOF

# 但有些发行版或特殊场景(比如 rc.local 早于 sysctl 服务执行),可能会导致参数未及时生效,所以脚本里加 sysctl -q -p 是保险做法,确保参数一定被加载
sysctl -q -p

# 只有在你直接用 iptables 命令自定义规则时,才需要 iptables-save/iptables-restore 来持久化和恢复
iptables-restore < /etc/iptables.rules
ip6tables-restore < /etc/ip6tables.rules

# 启动服务
systemctl restart fail2ban cron nginx shadowsocks-rust

exit 0

EOF

systemd

1
2
3
4
sudo systemctl enable --now shadowsocks-rust
sudo systemctl enable --now nginx
sudo systemctl enable --now fail2ban
sudo systemctl enable --now cron

打印输出

以下仅供参考,相关变量请改成实际的变量值。注意:如果是http方式的混淆,echo部分也需要做相应修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 依赖变量
METHOD=aes-256-gcm
PASSWORD=abcdefg
PUBLICIP=1.2.3.4
PORT=443
OBFSHOST=microsoft.com
OBFSURI=/
OBFS=tls
IPV6ENABLE=false
PUBLICIPv6=

# 模板
BASE64=$(echo -n "${METHOD}:${PASSWORD}" | base64 -w 0)
# 生成二维码
echo "ss://${BASE64}@${PUBLICIP}:${PORT}?plugin=obfs-local;obfs-host=${OBFSHOST};obfs-uri=${OBFSURI};obfs=${OBFS}#Twist" | qr
# 打印ss链接
echo -e "# [\033[32;1mss://\033[0m\033[34;1m$(echo "${BASE64}@${PUBLICIP}:${PORT}?plugin=obfs-local;obfs-host=${OBFSHOST};obfs-uri=${OBFSURI};obfs=${OBFS}#Twist")\033[0m]"
# 打印各项参数
echo -e "# [\033[32;1mServer IP:\033[0m \033[34;1m${PUBLICIP}\033[0m\c"
[ ! "$IPV6ENABLE" = "false" ] && echo -e "(\033[34;1m${PUBLICIPv6}\033[0m)\c"
echo -e " \033[32;1mPassWord:\033[0m \033[34;1m${PASSWORD}\033[0m \033[32;1mEncryption:\033[0m \033[34;1m${METHOD}\033[0m \033[32;1mOBFS:\033[0m \033[34;1m${OBFS}\033[0m \033[32;1mOBFS-HOST:\033[0m \033[34;1m${OBFSHOST}\033[0m \033[32;1mOBFS-URI:\033[0m \033[34;1m${OBFSURI}\033[0m]"

配置Nginx

伪装为微软的服务器

1
2
3
4
5
6
7
8
# /etc/nginx/sites-enabled/default 
server {
listen 80;
server_name _;
location / {
return 301 http://microsoft.com$request_uri;
}
}

重启nginx

1
2
nginx -t
sudo systemctl restart nginx

letsencrypt如果有通过软链共享证书给其他app,那么最好创建一个certs组,全部加入这个组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 共同组
sudo groupadd certs

# 修改组
sudo chown -R root:certs /etc/letsencrypt

# www-data添加certs组
sudo usermod -aG certs www-data

# 修改文件夹、文件权限
sudo find /etc/letsencrypt -type d -exec chmod 750 {} \;
sudo find /etc/letsencrypt -type f -exec chmod 640 {} \;

# 测试看www-data是否有权限
su - www-data cat /etc/letsencrypt/live/you_domain.com/fullchain.pem

配置fail2ban

fail2ban自带了很多规则,自己可以根据nginx的日志添加一些,放在/etc/fail2ban/filter.d/下面不过默认的也够了

添加过滤器

1
2
3
4
# 仅用作参考,默认的够用了
# /etc/fail2ban/filter.d/nginx-badurl.conf
[Definition]
failregex = <HOST> -.*"(GET|POST|HEAD) (/admin.*)

添加jail

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
29
30
31
32
33
34
35
36
37
38
39
40
# /etc/fail2ban/jail.d/nginx-all.local
[nginx-bad-request]
enabled = true
filter = nginx-bad-request
port = http,https
logpath = /var/log/nginx/access.log
backend = auto
maxretry = 1
bantime = 86400
findtime = 600

[nginx-botsearch]
enabled = true
filter = nginx-botsearch
port = http,https
logpath = /var/log/nginx/access.log
backend = auto
maxretry = 1
bantime = 86400
findtime = 600

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
backend = auto
maxretry = 1
bantime = 86400
findtime = 600

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/error.log
backend = auto
maxretry = 1
bantime = 86400
findtime = 600

重启验证

1
2
3
4
sudo systemctl restart fail2ban

fail2ban-client status
fail2ban-client status nginx-bad-request

重启下服务器,完成

注意:国内电信/联通网络的DPI检测很严格,混淆经常失效,目前还在调查原因

其他

定时任务

1
2
# 添加定时任务
crontab -e

内容如下

1
2
3
# 定时更新,避免系统漏洞,时区只影响一条
CRON_TZ=Asia/Shanghai
0 3 * * 0 /usr/bin/apt-get update && DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get upgrade -y && /usr/bin/apt-get autoremove -y >> /var/log/apt-cron.log 2>&1

融合怪测评项目 - GO版本

ecs 这个项目很有趣,用于测试你的vps各项指标,包括服务器性能、三网路由测试、流媒体解锁测试等