如何在 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
# 在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

# 检测并获取服务器的出口网卡名称
# 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

开启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
# /etc/systemd/system/shadowsocks-rust.service
[Unit]
Description=Shadowsocks-Rust Service
After=network.target

[Service]
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
# 开启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

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
# 先把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

内核参数配置

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

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

配置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检测很严格,混淆经常失效,目前还在调查原因