0%

关于 shadowsocks 的零碎回忆

纪念被我弃用的方案。
文中提到的使用方法可能不正确或已过时,脚本也可能有错误,因为仅是回忆,所以也不会再验证可用性。

服务端

服务端在 Linux VPS 搭建,使用的服务端:shadowsocks-rust
所需的 /opt/ss-rust/v2ray-pluginv2ray-plugin

ss-rust 配置文件

位置:/opt/ss-rust/server-config.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
{
"servers": [
{
"disabled": false,
"address": "127.0.0.1",
"port": 12345,
"method": "none",
"_password": "", // 使用 HTTPS 加密流量后,不需要再次加密
"plugin": "/opt/ss-rust/v2ray-plugin",
"plugin_opts": "server;loglevel=none;path=/",
"timeout": 1000
},
{
"disabled": true, // 已禁用
"server": "::",
"server_port": 3390,
"method": "chacha20-ietf-poly1305",
"password": "RZekaxdbowp9P8wnNwWOyH8GkLfW7rsaJdnhZvL31s",
"timeout": 1000
}
],
"_mode": "tcp_and_udp", // websocket 无法传输 UDP 流量,此项未启用
"_runtime": {
"mode": "single_thread"
}
}

systemd 配置文件

用于配置 ss-rust 服务的开机自启,位置:/etc/systemd/system/ss-server.service

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Shadowsocks-rust Service
After=network.target

[Service]
ExecStart=/opt/ss-rust/ssservice server -c /opt/ss-rust/server-config.json --acl /opt/ss-rust/server_block_local.acl
DynamicUser=yes

[Install]
WantedBy=multi-user.target

参考:
[email protected]
Systemd 入门教程:命令篇 - 阮一峰的网络日志

使用的 ACL 文件:/opt/ss-rust/server_block_local.acl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[outbound_block_list]
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.0.2.0/24
192.88.99.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4
255.255.255.255/32
::1/128
::ffff:127.0.0.1/104
fc00::/7
fe80::/10

Nginx 配置文件

Nginx 仅监听 443 端口,连接使用 HTTPS 加密,在提供 websocket 流量转发的同时,还提供了正常的 HTML 网页。
位置:/etc/nginx/conf.d/default.conf

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
#
server{
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name foo.bar; # 域名
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/conf.d/.tls/ssl.pem;
ssl_certificate_key /etc/nginx/conf.d/.tls/ssl.key;
ssl_stapling on;
ssl_stapling_verify on;
location ^~/Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg { # 客户端需要填写正确的path
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:12345/;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /204 {
return 204;
}
location /swap {
alias /var/www/swap/;
}
location / {
root /var/www/hexo;
error_page 404 /404.html;
try_files $uri $uri/ =404;
}
}

客户端

客户端通过 v2ray-plugin 使用指定的 path 建立 websocket 连接访问 Nginx。
如果 path 正确,Nginx 将流量转发到服务端的 v2ray-plugin,然后通过 ss-rust 建立网络连接。
如果 path 不正确,则流量由 Nginx 处理。

Windows

在 Windows 使用客户端,主要是用于网页访问、git 代码拉取、使用 socks5 连接的应用等,并不需要全部软件都使用代理。
客户端同样选择了 shadowsocks-rust,并配合 WinSW 创建了开机启动服务。
服务启动后,会在本地 1080 端口提供 SOCKS5 服务,8080 端口提供 HTTP Proxy 服务,53 端口提供 DNS 服务。
如果只是想要简单的使用的话,Shadowsocks for Windows 会是更好的选择。

ss-rust 配置文件

位置:D:\Software\ss_rust\client-config.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"no_delay": true,
"_keep_alive": 5,
"ipv6_first": true,
"runtime": {
"mode": "single_thread"
},
"_log": { // 调试用日志(未启用)
"level": 1,
"config_path": "log4rs.yaml"
},
"_balancer": { // 多服务器时负载均衡设置(未启用)
"max_server_rtt": 3,
"check_interval": 5,
"check_best_interval": 1
},
"locals": [
{
"protocol": "dns",
"local_address": "127.0.0.1",
"local_port": 53,
"local_dns_address": "223.5.5.5",
"local_dns_port": 53,
"remote_dns_address": "8.8.8.8",
"remote_dns_port": 53
},
{
"protocol": "http",
"local_address": "127.0.0.1",
"local_port": 8080
},
{
"protocol": "socks",
"local_address": "127.0.0.1",
"local_port": 1080
}
],
"servers": [
{
"disabled": true,
"address": "foo.bar", // 域名或 CDN IP
"port": 443,
"method": "none",
"plugin": "v2ray-plugin",
"plugin_opts": "loglevel=none;tls;host=foo.bar;path=/Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg?UserFlag=PC"
// path 要和 Nginx 配置里面的一致,后面的参数 UserFlag 不会用于 path 判断,但会在 Nginx 日志中记录
}
]
}

日志服务配置文件 log4rs.yaml(未使用)

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
refresh_rate: 30 seconds
appenders:
stdout:
kind: console
encoder:
pattern: "{d} {h({l}):<5} {m}{n}"
file:
kind: rolling_file
path: shadowsocks.log
encoder:
kind: pattern
pattern: "{d} {h({l}):<5} {m}{n}"
policy:
trigger:
kind: size
limit: 10 mb
roller:
kind: fixed_window
pattern: shadowsocks.{}.log
count: 5
root:
level: debug
appenders:
- stdout
- file
WinSW 配置文件

WinSW 创建开机自启服务。
位置:D:\Software\ss_rust\WinSW\ss-rust.xml

1
2
3
4
5
6
7
8
9
10
11
12
<service>
<id>xs</id>
<executable>D:\Software\ss_rust\ssservice.exe</executable>
<name>Shadowsocks Service</name>
<description>Shadowsocks rust</description>
<arguments>local --config client-config.json --acl bypass.acl</arguments>
<workingdirectory>D:\Software\ss_rust</workingdirectory>
<priority>Normal</priority>
<stoptimeout>15 sec</stoptimeout>
<startmode>Automatic</startmode>
<log mode="none"/>
</service>

这里使用的 bypass.acl 是使用来自 pmkol/easymosdns 的规则文件生成,生成脚本后面会提到。

本地无污染 DNS 服务

ss-rust 客户端使用 ACL 文件进行了简单的国内外分流,也开启了本地无污染 DNS 服务,但不清楚这个 DNS 查询是否也会根据 ACL 规则分流。
写了简单的批处理来切换是否使用本地 DNS,其中本地连接的名字是 eth0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@echo off
pushd %~dp0
%1 start "" mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c ""%~s0"" ::","","runas",1)(window.close)&&exit
echo "判断当前状态..."
netsh interface ip show dns eth0 | find /I "DHCP" > nul
if %errorlevel% == 0 (
echo "从 DHCP 切换到 静态 ..."
netsh interface ip add dns eth0 127.0.0.1
netsh interface ip add dns eth0 223.5.5.5
) else (
echo "从 静态 切换到 DHCP ..."
netsh interface ip set dns eth0 dhcp
)
ipconfig /flushdns
pause
使用代理

支持使用 socks/http 代理的软件,可以直接配置使用本地 1080 或 8080 端口的服务。
如果不支持配置代理,也不使用系统的代理设置,可以使用 proxifier 配置强制走代理。

浏览器的设置参考 entr0pia/SwitchyOmega-Whitelist,可以实现分流和实时切换是否使用代理。

git 或者 ssh 使用代理,需要在 %USERPROFILE%\.ssh\config 内添加以下内容:

1
2
3
4
5
6
Host github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/git_key
ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe" -S 127.0.0.1:1080 %h %p # Windows 使用 socks 的方式
# ProxyCommand nc -X connect -x 127.0.0.1:28080 %h %p # Linux 使用 http proxy 的方式

这里使用的 git 是 git-scm

命令行软件使用代理,可以添加环境变量:
Windows:
set https_proxy=http://127.0.0.1:8080
Linux:
export https_proxy=http://127.0.0.1:8080

Android

Android 客户端没什么配置的,安装填写相关信息就可以用。
所需文件:
Shadowsocks for Android
v2ray-plugin for shadowsocks-android

使用 Cloudflare CDN 加速

Cloudflare CDN 其实并不能算是加速,除非直连线路特别烂,CDN 更真实的用途是防止服务器 IP 被封后失联。
要启用 Cloudflare CDN,只需要将域名托管在 Cloudflare,设置 DNS 记录的时候启用 Cloudflare 的代理即可。

另一种方式

使用 Cloudflare 代理后,整个域名都无法直连了。
为了实现直连和 CDN 共存,我选择了用 Cloudflare Workers 做代理,原域名不启用代理的方法。

Cloudflare Workers 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/%E8%87%AA%E5%AE%9A%E4%B9%89/")) {
const UserFlag = pathname.split("/")[2];
const UserIp = request.headers.get("CF-Connecting-IP")
var req_url = "https://foo.bar/" +
"Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg" +
"?UserFlag=" + UserFlag +
"&IP=" + UserIp
return await fetch(new Request(req_url, request)
);

} else if (pathname.startsWith("/status")) {
return new Response("It works!", { status: 200 })
} else {
return new Response("", { status: 444 })
}
}

上面提到过,URL 后面的 UserFlag 参数其实不参与 path 匹配,但是经过 Cloudflare Workers 的话,可以在脚本内对不同的 UserFlag 做一些处理。
同时,通过 request.headers.get("CF-Connecting-IP") 获取访客的外网 IP,这部分参数会通过 URL 传递到服务器,记录在 Nginx 的日志里。

在之前的文章 Cloudflare Workers 添加域名路由 提到过给 Cloudflare Workers 添加自定义域名,客户端可以使用原域名直连,使用另一个域名走 Cloudflare CDN。
假设这个 Workers 的域名是 cf.foo.bar
完整的 URL 是 https://cf.foo.bar/自定义/PC
会被转发到服务器的 https://foo.bar/Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg?UserFlag=PC&IP=x.x.x.x

这个方法并没有什么特别的意义,只是展示曾经做过的东西而已。

自选 IP 测速、更新 DNS 和订阅

启用了 Cloudflare CDN 后,有时候获得的 IP 并非最优,这时候就出现了自选 IP 的方法。

测速

使用的工具:CloudflareSpeedTest
测速用批处理:

1
@CloudflareST.exe -sl 10 -dn 10 -tl 300 -t 10 -f cfip.txt

cfip.txt 里面同时包含了 ipv4 和 ipv6 地址。

使用测速结果生成配置文件

生成配置文件的 python 脚本:

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
from random import randint
from csv import DictReader
from random import randint, sample
from string import ascii_letters, digits
from json import dump
from urllib import request
from datetime import datetime

# https://docs.rs/shadowsocks-rust/1.8.23/shadowsocks/acl/struct.AccessControl.html
# ss-rust
# GFW_TRANSLATED_URL = "https://raw.githubusercontent.com/NateScarlet/gfwlist.acl/master/gfwlist.acl.json"
# CHINA_IP_LIST_URL = "https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt"
# https://github.com/pmkol/easymosdns/tree/rules
GFW_URL_LIST = "https://raw.githubusercontent.com/pmkol/easymosdns/rules/gfw_domain_list.txt"
CHINA_IP_LIST = "https://raw.githubusercontent.com/pmkol/easymosdns/rules/china_ip_list.txt"
CHINA_URL_LIST = "https://raw.githubusercontent.com/pmkol/easymosdns/rules/china_domain_list.txt"
CUSTOM_BYPASS = [
"127.0.0.1",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
]
CUSTOM_PROXY = []
try:
now = datetime.now()
with open("./bypass.acl", 'wb') as fp:
fp.write("# Time: {}\n".format(now.isoformat()).encode("utf-8"))
fp.write(b"\n")
fp.write(b"[proxy_all]\n")
fp.write(b"\n[bypass_list]\n")
# fp.write(request.urlopen(CHINA_URL_LIST).read())
fp.write(request.urlopen(CHINA_IP_LIST).read())
fp.write(b"\n")

if len(CUSTOM_BYPASS) > 0:
for a in CUSTOM_BYPASS:
fp.write(a.encode("utf-8"))
fp.write(b"\n")
print("ACL更新完成。")
except:
print("ACL更新失败。")
cfg = {
"no_delay": True,
"dns": "google",
"mode": "tcp_only",
"timeout": 2000,
"runtime": {
"mode": "single_thread"
},
"locals": [],
"servers": []
}
cfg['locals'] = [
{
"protocol": "dns",
"local_address": "127.0.0.1",
"local_port": 53,
"local_dns_address": "223.5.5.5",
"local_dns_port": 53,
"remote_dns_address": "8.8.8.8",
"remote_dns_port": 53
},
{
"protocol": "http",
"local_address": "127.0.0.1",
"local_port": 8080
},
{
"protocol": "socks",
"local_address": "127.0.0.1",
"local_port": 1080
}
]
ip_list = []
with open('./result.csv', 'r', encoding='utf-8') as csvfile:
csv_ip = [row['IP 地址'] for row in DictReader(csvfile)]
for i in range(0, 4):
ip_list.append(csv_ip[i])
for ip in ip_list: # 根据 IP 列表生成的多个配置,使用 Cloudflare CDN
cfg['servers'].append(
{
"address": ip,
"port": 443,
# "password": ''.join(sample(ascii_letters + digits, randint(5, 15))),
"method": "none",
"plugin": "v2ray-plugin",
"plugin_opts": "loglevel=none;tls;host=cf.foo.bar;path=/自定义/PC/"+"".join(sample(ascii_letters + digits, randint(5, 15)))
}
)
cfg['servers'].append( # 直连配置
{
"address": "foo.bar",
"port": 443,
# "password": ''.join(sample(ascii_letters + digits, randint(5, 15))),
"method": "none",
"plugin": "v2ray-plugin",
"plugin_opts": "loglevel=none;tls;host=foo.bar;path=/Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg?UserFlag=PC"
}
)
with open('./client-config.json', 'w', encoding='utf-8') as f:
dump(cfg, f, indent=4, ensure_ascii=False)
print("配置更新完成。")

配套的批处理:

1
2
3
4
5
6
7
8
@echo off
set https_proxy=http://127.0.0.1:8080
python MakeCfg.py
cd ../WinSW
WinSW.exe restart ss-rust.xml
pushd %~dp0
python DNS_Update.py
pause

根据测速结果文件,取出前 4 个 IP,作为 Cloudflare CDN 的自选 IP,然后在最后添加直连的配置。
顺便生成了分流用的 bypass.acl 文件。
这个脚本生成的 client-config.json 和上面配置的有点不同,因为是不同时期写的。

更新 DNS 和订阅

上面的批处理提到了 DNS_Update.py,这个是使用测速结果更新特定 DNS 记录的脚本,对于不方便修改配置文件的客户端,使用这种方式也可以方便的使用自选的 IP。
脚本内容如下:

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
from csv import DictReader
from random import randint, sample
from string import ascii_letters, digits
from json import dump, loads
from requests import patch, put

raw = {"servers": []}
ip_list = []
with open('result.csv', 'r', encoding='utf-8') as csvfile:
csv_ip = [row['IP 地址'] for row in DictReader(csvfile)]
for i in range(0, 4):
ip_list.append(csv_ip[i])
raw['servers'].append( # 直连配置
{
"id": 0,
"remarks": "Server",
"server": "foo.bar",
"server_port": 443,
"password": ''.join(sample(ascii_letters + digits, randint(5, 15))),
"method": "none",
"plugin": "v2ray-plugin",
"plugin_opts": "tls;host=foo.bar;path=/Hs6lnsh9ym32frrrE66XMmzi4wWNtBeIUANSZUM7lfg?UserFlag=PC"
}
)
for ip in ip_list:
id = len(raw['servers'])
raw['servers'].append(
{
"id": id,
"remarks": "Server_"+str(id),
"server": ip,
"server_port": 443,
"password": ''.join(sample(ascii_letters + digits, randint(5, 15))),
"method": "none",
"plugin": "v2ray-plugin",
"plugin_opts": 'tls;host=ip.foo.bar;path=/自定义/PC/' + ''.join(sample(ascii_letters + digits, randint(5, 15)))
}
)
with open("rss.txt", "w", encoding='UTF-8') as f:
dump(raw, f, indent=4, ensure_ascii=False)
print("文件写入完成。")
uid = {
"server1": "9685407e0c22de5ed2bec4dadfc628a8",
"server2": "49dc852cf5d4db7458c6e542d5e931af",
"server3": "8a2e332f46ba1532cabe50dffd999c40",
"server4": "7fdc4bc9b2f294a52e76789d7986be7c",
}
for ip in ip_list:
name = "server" + str(ip_list.index(ip)+1)
id = uid[name]
data = {"type": "A", "name": "ip.foo.bar", "content": ip, "ttl": 1}
res = put("https://api.cloudflare.com/client/v4/zones/9f801d8df31e3ce8438617995ceca76d/dns_records/"+id,
json=data, headers={
'Authorization': 'Bearer H8rsBn4tv5FNRv7V8ypwsvfZMqsL2Te7AS7aS17QI3g',
'Content-Type': 'application/json'
})
# assert loads(res.content)['success'], print(res.content)
if not loads(res.content)['success']:
print(res.content)
print(ip)
else:
print(name + " 更新完成。")

这个脚本会生成 rss.txt,用于订阅文件,放到 Nginx 指定的 web 目录,订阅地址填写这个文件的访问 URL。
同时使用 Cloudflare API 将 IP 更新到 ip.foo.bar 的 DNS 记录,在客户端将服务器填写为 ip.foo.bar 就可以使用选择的 IP。
生成的订阅文件并没有进行 base64 处理,可能不适宜直接公开到公网。
更新 DNS 部分,只会更新 A 记录,并未对 IP 进行判断以区分 ipv4 或者 ipv6。
关于 DNS 更新的方法,在之前的文章 AWS Lightsail 修改 IP 并更新 DNS 记录 有更为详细的介绍。

其他

之前还用过 老毛子 Padavan 固件,还写过生成配置文件的脚本,不过因为太久远了已经没有保留,这里就不讨论了。
关于放弃的原因,以及新的方案,以后有机会再补充吧。
最后放一个链接:clowwindy