feat: add optional UDP relay support
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
一键安装 shadowsocks-rust,自动配置 ss2022 双节点,生成订阅链接 + Surge/Clash 配置。
|
一键安装 shadowsocks-rust,自动配置 ss2022 双节点,生成订阅链接 + Surge/Clash 配置。
|
||||||
|
|
||||||
|
安装时可选择是否启用 UDP(默认启用)。普通节点会使用 TCP+UDP;HTTP obfs 节点保留 obfs TCP 前端,并在相同公网端口额外提供原生 Shadowsocks UDP。
|
||||||
|
|
||||||
## 一键安装
|
## 一键安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -15,6 +17,7 @@ curl -sL https://mjjtop.com/ss -o ss-rust.sh && chmod +x ss-rust.sh && ./ss-rust
|
|||||||
- 🎲 随机端口 + 随机密钥
|
- 🎲 随机端口 + 随机密钥
|
||||||
- ⏰ 自动 NTP 时间同步
|
- ⏰ 自动 NTP 时间同步
|
||||||
- 📋 自动生成 SS 订阅链接 / Surge 配置 / Clash 配置
|
- 📋 自动生成 SS 订阅链接 / Surge 配置 / Clash 配置
|
||||||
|
- 📡 可控 UDP 转发,生成的 Clash 节点会同步标记 UDP 能力
|
||||||
- 🔄 systemd 管理,开机自启
|
- 🔄 systemd 管理,开机自启
|
||||||
|
|
||||||
## 输出文件
|
## 输出文件
|
||||||
|
|||||||
85
ss-rust.sh
85
ss-rust.sh
@@ -29,6 +29,7 @@ METHOD_OBFS=""
|
|||||||
OBFS_BACKEND_PORT=""
|
OBFS_BACKEND_PORT=""
|
||||||
OBFS_HOST=""
|
OBFS_HOST=""
|
||||||
NODE_MODE=""
|
NODE_MODE=""
|
||||||
|
ENABLE_UDP="1"
|
||||||
|
|
||||||
# ============ 基础检测 ============
|
# ============ 基础检测 ============
|
||||||
check_root() {
|
check_root() {
|
||||||
@@ -228,6 +229,7 @@ select_and_configure() {
|
|||||||
PORT_2022="" ; KEY_2022="" ; METHOD_2022=""
|
PORT_2022="" ; KEY_2022="" ; METHOD_2022=""
|
||||||
PORT_RAW="" ; KEY_RAW="" ; METHOD_RAW=""
|
PORT_RAW="" ; KEY_RAW="" ; METHOD_RAW=""
|
||||||
PORT_OBFS="" ; KEY_OBFS="" ; METHOD_OBFS="" ; OBFS_BACKEND_PORT="" ; OBFS_HOST=""
|
PORT_OBFS="" ; KEY_OBFS="" ; METHOD_OBFS="" ; OBFS_BACKEND_PORT="" ; OBFS_HOST=""
|
||||||
|
ENABLE_UDP="1"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}════════════════════════════════════════${NC}"
|
echo -e "${CYAN}════════════════════════════════════════${NC}"
|
||||||
@@ -242,6 +244,14 @@ select_and_configure() {
|
|||||||
read -rp "请选择 [1-4] (默认3): " node_choice
|
read -rp "请选择 [1-4] (默认3): " node_choice
|
||||||
node_choice=${node_choice:-3}
|
node_choice=${node_choice:-3}
|
||||||
|
|
||||||
|
read -rp "启用 UDP 转发? [Y/n]: " udp_choice
|
||||||
|
udp_choice=${udp_choice:-Y}
|
||||||
|
if [[ "$udp_choice" =~ ^[Yy]$ ]]; then
|
||||||
|
ENABLE_UDP="1"
|
||||||
|
else
|
||||||
|
ENABLE_UDP="0"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$node_choice" == "1" || "$node_choice" == "3" ]]; then
|
if [[ "$node_choice" == "1" || "$node_choice" == "3" ]]; then
|
||||||
local dp=$((RANDOM % 10000 + 20000))
|
local dp=$((RANDOM % 10000 + 20000))
|
||||||
local dk=$(openssl rand -base64 16)
|
local dk=$(openssl rand -base64 16)
|
||||||
@@ -288,6 +298,8 @@ select_and_configure() {
|
|||||||
python3 -c "
|
python3 -c "
|
||||||
import json
|
import json
|
||||||
servers = []
|
servers = []
|
||||||
|
udp_enabled = '${ENABLE_UDP}' == '1'
|
||||||
|
direct_mode = 'tcp_and_udp' if udp_enabled else 'tcp_only'
|
||||||
if '${PORT_2022}':
|
if '${PORT_2022}':
|
||||||
servers.append({
|
servers.append({
|
||||||
'server': '0.0.0.0',
|
'server': '0.0.0.0',
|
||||||
@@ -295,7 +307,8 @@ if '${PORT_2022}':
|
|||||||
'method': '${METHOD_2022}',
|
'method': '${METHOD_2022}',
|
||||||
'password': '${KEY_2022}',
|
'password': '${KEY_2022}',
|
||||||
'timeout': 300,
|
'timeout': 300,
|
||||||
'fast_open': True
|
'fast_open': True,
|
||||||
|
'mode': direct_mode
|
||||||
})
|
})
|
||||||
if '${PORT_RAW}':
|
if '${PORT_RAW}':
|
||||||
servers.append({
|
servers.append({
|
||||||
@@ -304,17 +317,30 @@ if '${PORT_RAW}':
|
|||||||
'method': '${METHOD_RAW}',
|
'method': '${METHOD_RAW}',
|
||||||
'password': '${KEY_RAW}',
|
'password': '${KEY_RAW}',
|
||||||
'timeout': 300,
|
'timeout': 300,
|
||||||
'fast_open': True
|
'fast_open': True,
|
||||||
|
'mode': direct_mode
|
||||||
})
|
})
|
||||||
if '${PORT_OBFS}':
|
if '${PORT_OBFS}':
|
||||||
|
# simple-obfs only fronts TCP. Keep the obfuscated TCP backend on
|
||||||
|
# localhost and expose native Shadowsocks UDP on the public port.
|
||||||
servers.append({
|
servers.append({
|
||||||
'server': '127.0.0.1',
|
'server': '127.0.0.1',
|
||||||
'server_port': int('${OBFS_BACKEND_PORT}'),
|
'server_port': int('${OBFS_BACKEND_PORT}'),
|
||||||
'method': '${METHOD_OBFS}',
|
'method': '${METHOD_OBFS}',
|
||||||
'password': '${KEY_OBFS}',
|
'password': '${KEY_OBFS}',
|
||||||
'timeout': 300,
|
'timeout': 300,
|
||||||
'fast_open': True
|
'fast_open': True,
|
||||||
|
'mode': 'tcp_only'
|
||||||
})
|
})
|
||||||
|
if udp_enabled:
|
||||||
|
servers.append({
|
||||||
|
'server': '0.0.0.0',
|
||||||
|
'server_port': int('${PORT_OBFS}'),
|
||||||
|
'method': '${METHOD_OBFS}',
|
||||||
|
'password': '${KEY_OBFS}',
|
||||||
|
'timeout': 300,
|
||||||
|
'mode': 'udp_only'
|
||||||
|
})
|
||||||
with open('/etc/shadowsocks-rust/config.json', 'w') as f:
|
with open('/etc/shadowsocks-rust/config.json', 'w') as f:
|
||||||
json.dump({'servers': servers}, f, indent=4)
|
json.dump({'servers': servers}, f, indent=4)
|
||||||
print('OK')
|
print('OK')
|
||||||
@@ -327,6 +353,7 @@ KEY_OBFS=${KEY_OBFS}
|
|||||||
METHOD_OBFS=${METHOD_OBFS}
|
METHOD_OBFS=${METHOD_OBFS}
|
||||||
OBFS_BACKEND_PORT=${OBFS_BACKEND_PORT}
|
OBFS_BACKEND_PORT=${OBFS_BACKEND_PORT}
|
||||||
OBFS_HOST=${OBFS_HOST}
|
OBFS_HOST=${OBFS_HOST}
|
||||||
|
ENABLE_UDP=${ENABLE_UDP}
|
||||||
EOF
|
EOF
|
||||||
else
|
else
|
||||||
rm -f /etc/shadowsocks-rust/obfs.env
|
rm -f /etc/shadowsocks-rust/obfs.env
|
||||||
@@ -415,12 +442,28 @@ load_config() {
|
|||||||
PORT_2022="" ; KEY_2022="" ; METHOD_2022=""
|
PORT_2022="" ; KEY_2022="" ; METHOD_2022=""
|
||||||
PORT_RAW="" ; KEY_RAW="" ; METHOD_RAW=""
|
PORT_RAW="" ; KEY_RAW="" ; METHOD_RAW=""
|
||||||
PORT_OBFS="" ; KEY_OBFS="" ; METHOD_OBFS="" ; OBFS_BACKEND_PORT="" ; OBFS_HOST=""
|
PORT_OBFS="" ; KEY_OBFS="" ; METHOD_OBFS="" ; OBFS_BACKEND_PORT="" ; OBFS_HOST=""
|
||||||
|
ENABLE_UDP="0"
|
||||||
|
|
||||||
eval $(python3 -c "
|
if [[ -f /etc/shadowsocks-rust/obfs.env ]]; then
|
||||||
import json
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/shadowsocks-rust/obfs.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval $(OBFS_BACKEND_PORT="${OBFS_BACKEND_PORT}" PORT_OBFS="${PORT_OBFS}" python3 -c "
|
||||||
|
import json, os
|
||||||
with open('/etc/shadowsocks-rust/config.json') as f:
|
with open('/etc/shadowsocks-rust/config.json') as f:
|
||||||
c = json.load(f)
|
c = json.load(f)
|
||||||
|
skip_ports = {
|
||||||
|
int(p) for p in (os.environ.get('OBFS_BACKEND_PORT'), os.environ.get('PORT_OBFS'))
|
||||||
|
if p and p.isdigit()
|
||||||
|
}
|
||||||
|
udp_enabled = False
|
||||||
for s in c['servers']:
|
for s in c['servers']:
|
||||||
|
mode = s.get('mode', 'tcp_only')
|
||||||
|
if mode in ('tcp_and_udp', 'udp_only'):
|
||||||
|
udp_enabled = True
|
||||||
|
if s.get('server_port') in skip_ports or mode == 'udp_only':
|
||||||
|
continue
|
||||||
m = s['method']
|
m = s['method']
|
||||||
if '2022' in m:
|
if '2022' in m:
|
||||||
print(f'PORT_2022={s[\"server_port\"]}')
|
print(f'PORT_2022={s[\"server_port\"]}')
|
||||||
@@ -430,14 +473,8 @@ for s in c['servers']:
|
|||||||
print(f'PORT_RAW={s[\"server_port\"]}')
|
print(f'PORT_RAW={s[\"server_port\"]}')
|
||||||
print(f'KEY_RAW={s[\"password\"]}')
|
print(f'KEY_RAW={s[\"password\"]}')
|
||||||
print(f'METHOD_RAW={m}')
|
print(f'METHOD_RAW={m}')
|
||||||
|
print(f'ENABLE_UDP={1 if udp_enabled else 0}')
|
||||||
" 2>/dev/null)
|
" 2>/dev/null)
|
||||||
if [[ -f /etc/shadowsocks-rust/obfs.env ]]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source /etc/shadowsocks-rust/obfs.env
|
|
||||||
if [[ -n "$OBFS_BACKEND_PORT" && "$PORT_2022" == "$OBFS_BACKEND_PORT" ]]; then
|
|
||||||
PORT_2022="" ; KEY_2022="" ; METHOD_2022=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,14 +485,19 @@ gen_subscribe() {
|
|||||||
mkdir -p "$sub_dir"
|
mkdir -p "$sub_dir"
|
||||||
|
|
||||||
local uris="" surge="" clash="" info_txt=""
|
local uris="" surge="" clash="" info_txt=""
|
||||||
|
local udp_clash="false" udp_label="关闭"
|
||||||
|
if [[ "${ENABLE_UDP:-0}" == "1" ]]; then
|
||||||
|
udp_clash="true"
|
||||||
|
udp_label="开启"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$PORT_2022" ]]; then
|
if [[ -n "$PORT_2022" ]]; then
|
||||||
local uri="ss://$(echo -n "${METHOD_2022}:${KEY_2022}" | base64 -w0)@${SERVER_IP}:${PORT_2022}#SS2022-128"
|
local uri="ss://$(echo -n "${METHOD_2022}:${KEY_2022}" | base64 -w0)@${SERVER_IP}:${PORT_2022}#SS2022-128"
|
||||||
URI_2022="$uri"
|
URI_2022="$uri"
|
||||||
uris="${uris}${uri}\n"
|
uris="${uris}${uri}\n"
|
||||||
surge="${surge}SS2022-128 = ss, ${SERVER_IP}, ${PORT_2022}, encrypt-method=${METHOD_2022}, password=${KEY_2022}\n"
|
surge="${surge}SS2022-128 = ss, ${SERVER_IP}, ${PORT_2022}, encrypt-method=${METHOD_2022}, password=${KEY_2022}\n"
|
||||||
clash="${clash} - name: SS2022-128\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_2022}\n cipher: ${METHOD_2022}\n password: \"${KEY_2022}\"\n\n"
|
clash="${clash} - name: SS2022-128\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_2022}\n cipher: ${METHOD_2022}\n password: \"${KEY_2022}\"\n udp: ${udp_clash}\n\n"
|
||||||
info_txt="${info_txt}【SS2022-AES-128】新协议\n 地址: ${SERVER_IP}\n 端口: ${PORT_2022}\n 加密: ${METHOD_2022}\n 密码: ${KEY_2022}\n\n"
|
info_txt="${info_txt}【SS2022-AES-128】新协议\n 地址: ${SERVER_IP}\n 端口: ${PORT_2022}\n 加密: ${METHOD_2022}\n 密码: ${KEY_2022}\n UDP: ${udp_label}\n\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$PORT_RAW" ]]; then
|
if [[ -n "$PORT_RAW" ]]; then
|
||||||
@@ -463,8 +505,8 @@ gen_subscribe() {
|
|||||||
URI_RAW="$uri"
|
URI_RAW="$uri"
|
||||||
uris="${uris}${uri}\n"
|
uris="${uris}${uri}\n"
|
||||||
surge="${surge}SS-AES-128 = ss, ${SERVER_IP}, ${PORT_RAW}, encrypt-method=${METHOD_RAW}, password=${KEY_RAW}\n"
|
surge="${surge}SS-AES-128 = ss, ${SERVER_IP}, ${PORT_RAW}, encrypt-method=${METHOD_RAW}, password=${KEY_RAW}\n"
|
||||||
clash="${clash} - name: SS-AES-128\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_RAW}\n cipher: ${METHOD_RAW}\n password: \"${KEY_RAW}\"\n\n"
|
clash="${clash} - name: SS-AES-128\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_RAW}\n cipher: ${METHOD_RAW}\n password: \"${KEY_RAW}\"\n udp: ${udp_clash}\n\n"
|
||||||
info_txt="${info_txt}【SS-AES-128】传统协议\n 地址: ${SERVER_IP}\n 端口: ${PORT_RAW}\n 加密: ${METHOD_RAW}\n 密码: ${KEY_RAW}\n\n"
|
info_txt="${info_txt}【SS-AES-128】传统协议\n 地址: ${SERVER_IP}\n 端口: ${PORT_RAW}\n 加密: ${METHOD_RAW}\n 密码: ${KEY_RAW}\n UDP: ${udp_label}\n\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$PORT_OBFS" ]]; then
|
if [[ -n "$PORT_OBFS" ]]; then
|
||||||
@@ -475,8 +517,8 @@ gen_subscribe() {
|
|||||||
URI_OBFS="$uri"
|
URI_OBFS="$uri"
|
||||||
uris="${uris}${uri}\n"
|
uris="${uris}${uri}\n"
|
||||||
surge="${surge}SS2022-OBFS-HTTP = ss, ${SERVER_IP}, ${PORT_OBFS}, encrypt-method=${METHOD_OBFS}, password=${KEY_OBFS}, obfs=http, obfs-host=${OBFS_HOST}\n"
|
surge="${surge}SS2022-OBFS-HTTP = ss, ${SERVER_IP}, ${PORT_OBFS}, encrypt-method=${METHOD_OBFS}, password=${KEY_OBFS}, obfs=http, obfs-host=${OBFS_HOST}\n"
|
||||||
clash="${clash} - name: SS2022-OBFS-HTTP\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_OBFS}\n cipher: ${METHOD_OBFS}\n password: \"${KEY_OBFS}\"\n udp: false\n plugin: obfs\n plugin-opts:\n mode: http\n host: ${OBFS_HOST}\n\n"
|
clash="${clash} - name: SS2022-OBFS-HTTP\n type: ss\n server: ${SERVER_IP}\n port: ${PORT_OBFS}\n cipher: ${METHOD_OBFS}\n password: \"${KEY_OBFS}\"\n udp: ${udp_clash}\n plugin: obfs\n plugin-opts:\n mode: http\n host: ${OBFS_HOST}\n\n"
|
||||||
info_txt="${info_txt}【SS2022 + HTTP obfs】simple-obfs 伪装\n 地址: ${SERVER_IP}\n 端口: ${PORT_OBFS}\n 加密: ${METHOD_OBFS}\n 密码: ${KEY_OBFS}\n 插件: obfs/http\n Host: ${OBFS_HOST}\n 后端: 127.0.0.1:${OBFS_BACKEND_PORT}\n\n"
|
info_txt="${info_txt}【SS2022 + HTTP obfs】simple-obfs 伪装\n 地址: ${SERVER_IP}\n 端口: ${PORT_OBFS}\n 加密: ${METHOD_OBFS}\n 密码: ${KEY_OBFS}\n 插件: obfs/http\n Host: ${OBFS_HOST}\n 后端: 127.0.0.1:${OBFS_BACKEND_PORT}\n UDP: ${udp_label}(公网原生 SS UDP)\n\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "$uris" | base64 -w0 > "$sub_dir/subscribe.txt"
|
echo -e "$uris" | base64 -w0 > "$sub_dir/subscribe.txt"
|
||||||
@@ -489,6 +531,8 @@ gen_subscribe() {
|
|||||||
# ============ 显示结果 ============
|
# ============ 显示结果 ============
|
||||||
show_result() {
|
show_result() {
|
||||||
load_config || return
|
load_config || return
|
||||||
|
local udp_label="关闭"
|
||||||
|
[[ "${ENABLE_UDP:-0}" == "1" ]] && udp_label="开启"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}════════════════════════════════════════${NC}"
|
echo -e "${CYAN}════════════════════════════════════════${NC}"
|
||||||
echo -e "${CYAN} 🚀 Shadowsocks-Rust 安装完成${NC}"
|
echo -e "${CYAN} 🚀 Shadowsocks-Rust 安装完成${NC}"
|
||||||
@@ -501,6 +545,7 @@ show_result() {
|
|||||||
echo -e " 端口: ${YELLOW}${PORT_2022}${NC}"
|
echo -e " 端口: ${YELLOW}${PORT_2022}${NC}"
|
||||||
echo -e " 加密: ${METHOD_2022}"
|
echo -e " 加密: ${METHOD_2022}"
|
||||||
echo -e " 密码: ${YELLOW}${KEY_2022}${NC}"
|
echo -e " 密码: ${YELLOW}${KEY_2022}${NC}"
|
||||||
|
echo -e " UDP: ${udp_label}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -510,6 +555,7 @@ show_result() {
|
|||||||
echo -e " 端口: ${YELLOW}${PORT_RAW}${NC}"
|
echo -e " 端口: ${YELLOW}${PORT_RAW}${NC}"
|
||||||
echo -e " 加密: ${METHOD_RAW}"
|
echo -e " 加密: ${METHOD_RAW}"
|
||||||
echo -e " 密码: ${YELLOW}${KEY_RAW}${NC}"
|
echo -e " 密码: ${YELLOW}${KEY_RAW}${NC}"
|
||||||
|
echo -e " UDP: ${udp_label}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -521,6 +567,7 @@ show_result() {
|
|||||||
echo -e " 密码: ${YELLOW}${KEY_OBFS}${NC}"
|
echo -e " 密码: ${YELLOW}${KEY_OBFS}${NC}"
|
||||||
echo -e " 插件: obfs / http"
|
echo -e " 插件: obfs / http"
|
||||||
echo -e " Host: ${OBFS_HOST}"
|
echo -e " Host: ${OBFS_HOST}"
|
||||||
|
echo -e " UDP: ${udp_label}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -607,7 +654,7 @@ import json
|
|||||||
with open('/etc/shadowsocks-rust/config.json') as f:
|
with open('/etc/shadowsocks-rust/config.json') as f:
|
||||||
c = json.load(f)
|
c = json.load(f)
|
||||||
for s in c.get('servers', []):
|
for s in c.get('servers', []):
|
||||||
if str(s.get('server_port')) == '${OBFS_BACKEND_PORT}':
|
if str(s.get('server_port')) in ('${OBFS_BACKEND_PORT}', '${PORT_OBFS}'):
|
||||||
s['password'] = '${KEY_OBFS}'
|
s['password'] = '${KEY_OBFS}'
|
||||||
with open('/etc/shadowsocks-rust/config.json','w') as f:
|
with open('/etc/shadowsocks-rust/config.json','w') as f:
|
||||||
json.dump(c, f, indent=4)
|
json.dump(c, f, indent=4)
|
||||||
|
|||||||
Reference in New Issue
Block a user