Files
tuic_docker/bin/port.sh
T

216 lines
7.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 查找随机空闲端口
# 用途:
# 在指定端口范围内,随机选择一个当前未被监听(TCP/UDP)的端口
# 参数:
# $1: 起始端口(默认 10001)
# $2: 结束端口(默认 65535)
# $3: 最大随机尝试次数(默认 100)
find_free_port() {
local start="${1:-10001}"
local max="${2:-65535}"
local attempts="${3:-100}"
# ------------------------------------------------------------
# 收集当前正在监听的端口(TCP + UDP)
# 优先使用 ss,其次 netstat
# ------------------------------------------------------------
local used_ports raw
if command -v ss >/dev/null 2>&1; then
# ss 输出示例:
# LISTEN 0 128 0.0.0.0:22
raw=$(ss -lntu 2>/dev/null || true)
used_ports=$(printf "%s\n" "$raw" \
| awk '{print $5}' \
| sed -E 's/.*[:]//g')
elif command -v netstat >/dev/null 2>&1; then
# netstat 输出示例:
# tcp 0 0 0.0.0.0:22
raw=$(netstat -lntu 2>/dev/null || true)
used_ports=$(printf "%s\n" "$raw" \
| awk '{print $4}' \
| sed -E 's/.*[:]//g')
else
echo "Error: ss or netstat is required to check listening ports." >&2
return 2
fi
# ------------------------------------------------------------
# 将已占用端口存入关联数组,便于 O(1) 判断
# 需要 bash 4+
# ------------------------------------------------------------
declare -A used_map
local p
for p in $used_ports; do
# 过滤非数字字段(如 *、:::
if [[ $p =~ ^[0-9]+$ ]]; then
used_map["$p"]=1
fi
done
# ------------------------------------------------------------
# 随机尝试若干次
# 每次随机生成一个端口,只要未被监听就立即返回
# ------------------------------------------------------------
local port i
for ((i=0; i<attempts; i++)); do
# 在 [start, max] 范围内生成随机端口
port=$(( RANDOM % (max - start + 1) + start ))
# 如果端口不在已监听表中,则认为可用
if [[ -z "${used_map[$port]}" ]]; then
echo "$port"
return 0
fi
done
# ------------------------------------------------------------
# 多次随机尝试后仍未找到可用端口
# 通常意味着端口范围过小或已被大量占用
# ------------------------------------------------------------
echo "Error: no free port found after $attempts random attempts." >&2
return 1
}
# ==========================================
# 函数名: port_manage
# 功能: 开放端口和关闭端口
# 参数1: allow 或 deny (操作类型)
# 参数2: 端口号
# --- 使用示例 ---
# 开启端口 8888
# port_manage allow 8888
# 关闭端口 8888
# port_manage deny 8888
# ==========================================
port_manage() {
local ACTION=$1
local PORT=$2
if [[ -z "$PORT" ]]; then
echo "错误: 未提供端口号"
return 1
fi
# 统一转换为小写,增强鲁棒性
ACTION=$(echo "$ACTION" | tr '[:upper:]' '[:lower:]')
echo "--- 正在对端口 $PORT 执行 $ACTION 操作 ---"
# 1. 防火墙 (Firewall) 逻辑处理
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
# CentOS/RHEL/Fedora (firewalld)
if [ "$ACTION" == "allow" ]; then
sudo firewall-cmd --zone=public --add-port=${PORT}/tcp --permanent
sudo firewall-cmd --zone=public --add-port=${PORT}/udp --permanent
elif [ "$ACTION" == "deny" ]; then
sudo firewall-cmd --zone=public --remove-port=${PORT}/tcp --permanent
sudo firewall-cmd --zone=public --remove-port=${PORT}/udp --permanent
fi
sudo firewall-cmd --reload
echo "[OK] firewalld 规则已更新 ($ACTION)"
elif command -v ufw >/dev/null 2>&1 && systemctl is-active --quiet ufw; then
# Ubuntu/Debian (ufw)
if [ "$ACTION" == "allow" ]; then
sudo ufw allow ${PORT}/tcp
sudo ufw allow ${PORT}/udp
elif [ "$ACTION" == "deny" ]; then
sudo ufw delete allow ${PORT}/tcp
sudo ufw delete allow ${PORT}/udp
fi
echo "[OK] ufw 规则已更新 ($ACTION)"
else
# 防火墙未启用时,allow 操作直接跳过,deny 操作走 iptables 兜底
if [ "$ACTION" == "allow" ]; then
echo "[!] 防火墙未启用,跳过防火墙规则配置"
else
# 兜底方案 (iptables) —— 仅 deny 时执行
sudo iptables -D INPUT -p tcp --dport ${PORT} -j ACCEPT 2>/dev/null
sudo iptables -D INPUT -p udp --dport ${PORT} -j ACCEPT 2>/dev/null
echo "[OK] iptables 规则已执行 ($ACTION)"
fi
fi
# 2. SELinux 逻辑处理(与防火墙状态无关,始终执行)
# getenforce 不存在说明未安装 SELinux,直接跳过
command -v getenforce >/dev/null 2>&1 || return 0
# Enforcing 模式才会拦截端口绑定,其他模式无需处理
local SELINUX_STATUS=$(getenforce)
[ "$SELINUX_STATUS" == "Enforcing" ] || return 0
# semanage 不存在时的兜底方案: 开启全局网络连接开关(粒度较粗)
if ! command -v semanage >/dev/null 2>&1; then
if [ "$ACTION" == "allow" ]; then
echo "[!] 警告: 未找到 semanage,尝试开启全局网络连接开关..."
sudo setsebool -P httpd_can_network_connect 1
fi
return 0
fi
# 使用 semanage 精确管理端口标签
if [ "$ACTION" == "allow" ]; then
# 先尝试添加 (-a),若标签已存在则改用修改 (-m)
sudo semanage port -a -t http_port_t -p tcp ${PORT} 2>/dev/null || \
sudo semanage port -m -t http_port_t -p tcp ${PORT}
elif [ "$ACTION" == "deny" ]; then
sudo semanage port -d -t http_port_t -p tcp ${PORT} 2>/dev/null
fi
echo "[OK] SELinux 端口权限已更新 ($ACTION)"
}
# ==========================================
# 更新 sing-box 配置文件中的监听端口,并开放对应防火墙规则
# 参数1: 端口号(可选,不传则在指定范围内随机选取空闲端口)
# 参数2: 随机端口起始值(可选,默认 5000)
# 参数3: 随机端口结束值(可选,默认 15000)
# --- 使用示例 ---
# 指定端口
# update_port 8080
#
# 自动随机端口(默认范围 5000-15000)
# update_port
#
# 自动随机端口(自定义范围)
# update_port "" 20000 30000
# ==========================================
update_port(){
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 脚本文件夹绝对路径
local config_dir="$script_dir/../config"
local port=$1
# 如果没有提供端口参数,则自动查找一个随机空闲端口
if [[ -z "$port" ]]; then
port=$(find_free_port 5000 15000)
fi
modify_json_file "$config_dir/config.json" ".inbounds[0].listen_port" "$port"
port_manage allow "$port"
echo "设置端口成功"
}
# 通过交互方式更新端口
ask_update_port(){
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local config_file="$script_dir/../config/config.json"
local current_port=$(get_listen_port)
local new_port=$(ask_update "请输入新的端口号" "$current_port")
update_port "$new_port"
}
# 获取当前配置的监听端口
get_listen_port(){
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local config_file="$script_dir/../config/config.json"
local port=$(jq -r ".inbounds[0].listen_port" "$config_file")
echo $port
}