Socat/socat.sh

2289 lines
77 KiB
Bash
Raw Permalink 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
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# ====================================================
# 系统要求: CentOS 7+、Debian 8+、Ubuntu 16+
# 描述: Socat 一键安装管理脚本
# 版本: 5.2
# ====================================================
Green="\033[32m"
Font="\033[0m"
Blue="\033[34m"
Red="\033[31m"
Yellow="\033[33m"
# 创建 socats 目录并定义相关路径
SOCATS_DIR="$HOME/socats"
mkdir -p "$SOCATS_DIR"
# 配置文件路径
CONFIG_FILE="$SOCATS_DIR/socat_forwards.conf"
# JSON格式化开关 (0=不格式化, 1=格式化)
JSON_FORMAT_ENABLED=${JSON_FORMAT_ENABLED:-1}
# 清屏函数
clear_screen() {
clear
}
# 按键继续函数
press_any_key() {
echo
read -n 1 -s -r -p "按任意键继续..."
clear_screen
}
# 检查是否为root用户
check_root(){
if [[ $EUID -ne 0 ]]; then
echo "错误此脚本必须以root身份运行" 1>&2
exit 1
fi
}
# 系统检测逻辑
check_sys(){
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS=$ID
VER=$VERSION_ID
elif [[ -f /etc/redhat-release ]]; then
OS="centos"
VER=$(grep -oE '[0-9]+' /etc/redhat-release | head -1)
elif [[ -f /etc/debian_version ]]; then
OS="debian"
VER=$(cat /etc/debian_version)
else
echo "不支持的操作系统!"
exit 1
fi
# 标准化包管理器检测
if command -v apt-get >/dev/null 2>&1; then
PKG_MANAGER="apt"
elif command -v yum >/dev/null 2>&1; then
PKG_MANAGER="yum"
elif command -v dnf >/dev/null 2>&1; then
PKG_MANAGER="dnf"
elif command -v pacman >/dev/null 2>&1; then
PKG_MANAGER="pacman"
else
echo "未找到支持的包管理器!"
exit 1
fi
}
# IP获取
get_ip(){
local ip=""
# 优先使用ip命令
if command -v ip >/dev/null 2>&1; then
ip=$(ip -4 route get 8.8.8.8 2>/dev/null | awk '{print $7; exit}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
fi
# 备选方案
if [[ -z "$ip" ]] && command -v hostname >/dev/null 2>&1; then
ip=$(hostname -I 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
fi
echo ${ip:-"127.0.0.1"}
}
# IPv6检测和获取
get_ipv6(){
local ipv6=""
# 检查系统是否支持IPv6
if [[ ! -f /proc/sys/net/ipv6/conf/all/disable_ipv6 ]] || [[ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 1 ]]; then
echo ""
return
fi
# 获取可用的全球单播IPv6地址
if command -v ip >/dev/null 2>&1; then
ipv6=$(ip -6 route get 2001:4860:4860::8888 2>/dev/null | grep -oE 'src [0-9a-fA-F:]+' | awk '{print $2}' | grep -v '^fe80' | head -n1)
fi
# 备选方案
if [[ -z "$ipv6" ]] && command -v hostname >/dev/null 2>&1; then
ipv6=$(hostname -I 2>/dev/null | grep -oE '([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}' | head -n1)
fi
echo ${ipv6:-""}
}
# 检查并安装jq
install_jq(){
if ! command -v jq >/dev/null 2>&1; then
echo -e "${Green}检测到 jq 未安装,正在安装 jq...${Font}"
case "$PKG_MANAGER" in
"yum")
yum install -y jq
;;
"dnf")
dnf install -y jq
;;
"apt")
apt-get update -y
apt-get install -y jq
;;
"pacman")
pacman -Sy --noconfirm jq
;;
*)
echo -e "${Yellow}不支持的包管理器: $PKG_MANAGER,跳过 jq 安装${Font}"
return 1
;;
esac
if command -v jq >/dev/null 2>&1; then
echo -e "${Green}jq 安装完成!${Font}"
return 0
else
echo -e "${Red}jq 安装失败,将使用手动 JSON 解析${Font}"
return 1
fi
else
echo -e "${Green}jq 已安装,跳过安装${Font}"
return 0
fi
}
# 卸载jq
uninstall_jq(){
if command -v jq >/dev/null 2>&1; then
echo -e "${Green}正在卸载 jq...${Font}"
case "$PKG_MANAGER" in
"yum")
yum remove -y jq
;;
"dnf")
dnf remove -y jq
;;
"apt")
apt-get remove -y jq
;;
"pacman")
pacman -R --noconfirm jq
;;
*)
echo -e "${Red}不支持的包管理器: $PKG_MANAGER${Font}"
return 1
;;
esac
if ! command -v jq >/dev/null 2>&1; then
echo -e "${Green}jq 卸载完成!${Font}"
else
echo -e "${Red}jq 卸载失败${Font}"
fi
else
echo -e "${Yellow}jq 未安装,无需卸载${Font}"
fi
}
# Socat安装
install_socat(){
if ! command -v socat >/dev/null 2>&1; then
echo -e "${Green}正在安装 Socat...${Font}"
case "$PKG_MANAGER" in
"yum")
yum install -y socat
;;
"dnf")
dnf install -y socat
;;
"apt")
apt-get update -y
apt-get install -y socat
;;
"pacman")
pacman -Sy --noconfirm socat
;;
*)
echo -e "${Red}不支持的包管理器: $PKG_MANAGER${Font}"
exit 1
;;
esac
if command -v socat >/dev/null 2>&1; then
echo -e "${Green}Socat 安装完成!${Font}"
else
echo -e "${Red}Socat 安装失败,请检查网络连接和系统设置。${Font}"
exit 1
fi
fi
}
# 迁移旧格式配置到JSON格式
migrate_old_config() {
if [ ! -f "$CONFIG_FILE" ]; then
echo "[]" > "$CONFIG_FILE"
return
fi
# 检查是否为JSON格式
if command -v jq >/dev/null 2>&1; then
if jq empty "$CONFIG_FILE" 2>/dev/null; then
return # 已经是有效的JSON格式
fi
else
# 简单的JSON格式检查
if head -c 1 "$CONFIG_FILE" | grep -q '^\['; then
return # 看起来是JSON数组
fi
fi
echo -e "${Yellow}检测到旧格式配置文件正在迁移到JSON格式...${Font}"
# 创建备份
cp "$CONFIG_FILE" "$CONFIG_FILE.backup.$(date +%Y%m%d_%H%M%S)"
# 迁移旧格式到JSON
local temp_json=$(mktemp)
echo "[" > "$temp_json"
local first_entry=true
while IFS=' ' read -r type port1 socatip port2; do
if [[ -n "$type" && -n "$port1" && -n "$socatip" && -n "$port2" ]]; then
local forward_type=""
case "$type" in
"ipv4") forward_type="ipv4" ;;
"ipv6") forward_type="ipv6" ;;
"domain") forward_type="domain" ;;
"domain6") forward_type="domain6" ;;
*) forward_type="ipv4" ;;
esac
if [[ "$first_entry" == "false" ]]; then
echo "," >> "$temp_json"
fi
echo -n "{\"type\":\"$forward_type\",\"listen_port\":$port1,\"remote_ip\":\"$socatip\",\"remote_port\":$port2}" >> "$temp_json"
first_entry=false
fi
done < "$CONFIG_FILE"
echo "]" >> "$temp_json"
# 验证并替换
if command -v jq >/dev/null 2>&1; then
if jq empty "$temp_json" 2>/dev/null; then
mv "$temp_json" "$CONFIG_FILE"
echo -e "${Green}配置迁移完成!旧配置已备份${Font}"
else
echo -e "${Red}配置迁移失败,保留原配置${Font}"
rm -f "$temp_json"
fi
else
# 无jq时直接替换
mv "$temp_json" "$CONFIG_FILE"
echo -e "${Green}配置迁移完成!旧配置已备份${Font}"
fi
}
# 统一JSON格式处理函数
format_json_config() {
local input_file="${1:-$CONFIG_FILE}"
local format_type="${2:-$JSON_FORMAT_ENABLED}"
if [ ! -f "$input_file" ]; then
echo "[]" > "$input_file"
return 0
fi
if command -v jq >/dev/null 2>&1; then
if [ "$format_type" -eq 1 ]; then
# 格式化模式 - 完全格式化
jq '.' "$input_file" > "$input_file.tmp" 2>/dev/null && mv "$input_file.tmp" "$input_file"
else
# 紧凑模式但每个对象单独一行,逗号放在对象后面
local temp_file=$(mktemp)
# 使用jq生成紧凑格式逗号放在对象后面
echo "[" > "$temp_file"
local objects=$(jq -c '.[]' "$input_file" 2>/dev/null)
local count=0
local total=$(echo "$objects" | wc -l | tr -d ' ')
while IFS= read -r obj; do
[[ -z "$obj" ]] && continue
count=$((count + 1))
if [[ $count -eq $total ]]; then
echo " $obj" >> "$temp_file"
else
echo " $obj," >> "$temp_file"
fi
done <<< "$objects"
echo "]" >> "$temp_file"
mv "$temp_file" "$input_file"
fi
return 0
fi
# 无jq时的回退处理
if [ "$format_type" -eq 1 ]; then
# 使用回退方案进行格式化
local temp_file=$(mktemp)
local content=$(cat "$input_file")
# 提取所有有效的JSON对象
local objects=()
local current_object=""
local brace_count=0
local in_object=false
# 逐字符解析JSON
while IFS= read -r -n1 char; do
case "$char" in
'{')
if [[ $brace_count -eq 0 ]]; then
in_object=true
current_object="{"
else
current_object="$current_object{"
fi
brace_count=$((brace_count + 1))
;;
'}')
current_object="$current_object}"
brace_count=$((brace_count - 1))
if [[ $brace_count -eq 0 && $in_object == true ]]; then
objects+=("$current_object")
in_object=false
current_object=""
fi
;;
*)
if [[ $in_object == true ]]; then
current_object="$current_object$char"
fi
;;
esac
done <<< "$content"
# 重新构建JSON数组 - 紧凑模式但每个对象单独一行
echo "[" > "$temp_file"
local first=true
for obj in "${objects[@]}"; do
# 确保对象是有效的JSON格式
if [[ -n "$obj" && "$obj" =~ ^\{.*\}$ ]]; then
# 检查是否包含必要的字段
if [[ "$obj" == *"\"type\""* && "$obj" == *"\"listen_port\""* && "$obj" == *"\"remote_ip\""* && "$obj" == *"\"remote_port\""* ]]; then
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> "$temp_file"
fi
# 紧凑模式:每个对象单独一行
echo " $obj" >> "$temp_file"
fi
fi
done
echo "]" >> "$temp_file"
# 确保格式正确:移除多余逗号、空行等
sed -i 's/,,/,/g' "$temp_file"
sed -i 's/\[,\?/[/' "$temp_file"
sed -i 's/\,\?\]/]/' "$temp_file"
sed -i 's/},\s*,/},/g' "$temp_file"
mv "$temp_file" "$input_file"
else
# 紧凑模式但每个对象单独一行逗号放在对象后面无jq回退
local temp_file=$(mktemp)
# 提取现有对象并重新格式化
local content=$(cat "$input_file" | tr -d '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# 使用sed重新格式化紧凑但每个对象一行逗号放在对象后面
echo "[" > "$temp_file"
# 提取对象并格式化
local objects=$(echo "$content" | sed 's/^\[//;s/\]$//' | sed 's/},{/}\n {/g')
local count=0
local total=$(echo "$objects" | grep -c '^' || echo 0)
while IFS= read -r obj; do
[[ -z "$obj" || "$obj" == "{" ]] && continue
count=$((count + 1))
# 确保对象格式正确
if [[ "$obj" != *"}" ]]; then
obj="$obj}"
fi
# 添加对象,根据是否是最后一个决定是否添加逗号
if [[ $count -eq $total ]]; then
echo " $obj" >> "$temp_file"
else
echo " $obj," >> "$temp_file"
fi
done <<< "$objects"
echo "]" >> "$temp_file"
mv "$temp_file" "$input_file"
fi
}
# 修复JSON配置文件格式兼容旧函数名
fix_json_format() {
format_json_config "$CONFIG_FILE" "$JSON_FORMAT_ENABLED"
}
# 初始化配置文件
init_config() {
migrate_old_config
if [ ! -f "$CONFIG_FILE" ]; then
echo "[]" > "$CONFIG_FILE"
echo "Debug: Created new JSON config file: $CONFIG_FILE"
else
echo "Debug: Config file already exists: $CONFIG_FILE"
# 修复可能存在的格式问题
format_json_config "$CONFIG_FILE" "$JSON_FORMAT_ENABLED"
fi
}
# 添加到配置文件 (JSON格式)
add_to_config() {
local forward_type=""
case "$ip_version" in
1) forward_type="ipv4" ;;
2) forward_type="ipv6" ;;
3) forward_type="domain" ;;
4) forward_type="domain6" ;;
esac
# 使用统一格式处理函数添加配置
local new_entry='{"type":"'$forward_type'","listen_port":'$port1',"remote_ip":"'$socatip'","remote_port":'$port2'}'
if command -v jq >/dev/null 2>&1; then
jq ". += [$new_entry]" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
else
# 回退到简单的JSON数组追加 - 修复JSON格式问题
local new_entry='{"type":"'$forward_type'","listen_port":'$port1',"remote_ip":"'$socatip'","remote_port":'$port2'}'
# 确保配置文件存在且为有效的JSON格式
if [ ! -s "$CONFIG_FILE" ]; then
echo "[$new_entry]" > "$CONFIG_FILE"
return
fi
# 清理现有文件中的空元素和多余逗号
local temp_file=$(mktemp)
# 读取并清理现有内容
local content=$(cat "$CONFIG_FILE" | tr -d '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# 移除空数组元素和多余逗号
content=$(echo "$content" | sed 's/\[,\?\s*\[,\?\s*\]/[]/g')
content=$(echo "$content" | sed 's/\[,\?\s*,\?\s*\]/[]/g')
content=$(echo "$content" | sed 's/,,/,/g')
content=$(echo "$content" | sed 's/\[,\?/[/' | sed 's/\,\?\]/]/')
# 如果是空数组,直接创建新数组
if [[ "$content" == "[]" ]]; then
echo "[$new_entry]" > "$CONFIG_FILE"
rm -f "$temp_file"
# 确保格式正确
fix_json_format
return
fi
# 提取现有对象
echo "$content" | sed 's/\[\(.*\)\]/\1/' | sed 's/},{/}\n{/g' | while IFS= read -r obj; do
[[ -n "$obj" && "$obj" != "{" ]] && echo "$obj"
done > "$temp_file"
# 构建新的JSON数组
echo "[" > "$CONFIG_FILE"
local first=true
while IFS= read -r obj; do
[[ -z "$obj" || "$obj" == "{" ]] && continue
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> "$CONFIG_FILE"
fi
# 确保对象格式正确
if [[ "$obj" != *"}" ]]; then
obj="$obj}"
fi
echo -n "$obj" >> "$CONFIG_FILE"
done < "$temp_file"
# 添加新条目
if [[ "$first" == "true" ]]; then
echo "$new_entry]" >> "$CONFIG_FILE"
else
echo "," >> "$CONFIG_FILE"
echo "$new_entry]" >> "$CONFIG_FILE"
fi
rm -f "$temp_file"
fi
# 使用统一格式处理函数确保格式正确
format_json_config "$CONFIG_FILE" "$JSON_FORMAT_ENABLED"
}
# 从配置文件中移除转发 (JSON格式)
remove_from_config() {
local listen_port=$1
if command -v jq >/dev/null 2>&1; then
jq "map(select(.listen_port != $listen_port))" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
else
# 回退到基于行的处理 - 修复JSON格式问题
local temp_file=$(mktemp)
# 读取配置文件内容
local json_content=$(cat "$CONFIG_FILE")
# 提取所有有效的JSON对象
local entries=()
local entry_count=$(echo "$json_content" | grep -o '"type"' | wc -l)
# 收集所有非空且端口不匹配的配置项
local first_entry=true
echo "[" > "$temp_file"
# 使用更可靠的JSON解析
local in_entry=false
local entry=""
local brace_count=0
while IFS= read -r line; do
# 跳过空行和仅包含逗号的行
[[ -z "$line" || "$line" =~ ^[[:space:]]*,[[:space:]]*$ ]] && continue
# 检测JSON对象的开始
if [[ "$line" =~ ^[[:space:]]*\{ ]]; then
in_entry=true
entry=""
brace_count=1
fi
if [[ "$in_entry" == "true" ]]; then
entry="$entry$line"
# 计算大括号匹配
brace_count=$(echo "$line" | sed 's/[^{}]//g' | sed 's/{/\n{/g' | sed 's/}/\n}/g' | grep -c '{')
brace_count=$((brace_count - $(echo "$line" | sed 's/[^{}]//g' | sed 's/{/\n{/g' | sed 's/}/\n}/g' | grep -c '}')))
if [[ $brace_count -eq 0 ]]; then
# 完整的JSON对象
if [[ "$entry" != *"\"listen_port\":$listen_port"* ]]; then
if [[ "$first_entry" == "false" ]]; then
echo "," >> "$temp_file"
fi
echo -n "$entry" >> "$temp_file"
first_entry=false
fi
in_entry=false
entry=""
fi
fi
done < <(cat "$CONFIG_FILE" | tr -d '\n' | sed 's/\[/\[\n/g' | sed 's/\]/\n\]/g' | sed 's/},{/},\n{/g')
# 如果没有找到任何有效条目,创建空数组
if [[ "$first_entry" == "true" ]]; then
echo "]" > "$temp_file"
else
echo "" >> "$temp_file"
echo "]" >> "$temp_file"
fi
# 清理生成的JSON
sed -i 's/,,/,/g' "$temp_file" # 移除重复逗号
sed -i 's/\[,/[/' "$temp_file" # 修复开头
sed -i 's/,\]/]/' "$temp_file" # 修复结尾
mv "$temp_file" "$CONFIG_FILE"
# 使用统一格式处理函数确保格式正确
format_json_config "$CONFIG_FILE" "$JSON_FORMAT_ENABLED"
fi
}
# 端口占用检测
check_port() {
local port=$1
local protocol=${2:-"tcp"}
# 使用ss命令优先
if command -v ss >/dev/null 2>&1; then
if ss -${protocol:0:1}ln | grep -q ":${port} "; then
local process=$(ss -${protocol:0:1}lnp | grep ":${port} " | awk '{print $7}' | cut -d',' -f1 | cut -d'"' -f2 | head -n1)
echo -e "${Red}错误: 端口 $1 已被占用 (${process:-未知进程})${Font}"
return 1
fi
# 备选使用netstat
elif command -v netstat >/dev/null 2>&1; then
if netstat -${protocol:0:1}uln | grep -q ":${port} "; then
local process=$(netstat -${protocol:0:1}ulnp | grep ":${port} " | awk '{print $7}' | cut -d'/' -f2 | head -n1)
echo -e "${Red}错误: 端口 $1 已被占用 (${process:-未知进程})${Font}"
return 1
fi
fi
return 0
}
# 规范化 IPv6 地址
normalize_ipv6() {
local ip=$1
# 使用系统工具标准化IPv6地址
if command -v ipcalc >/dev/null 2>&1; then
local normalized=$(ipcalc -i "$ip" 2>/dev/null | grep -oE '([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}' | head -n1)
[[ -n "$normalized" ]] && echo "$normalized" && return
fi
# 备选方案使用python3
if command -v python3 >/dev/null 2>&1; then
local normalized=$(python3 -c "
import ipaddress
import sys
try:
ip = ipaddress.IPv6Address('$ip')
print(str(ip))
except:
sys.exit(1)
" 2>/dev/null)
[[ -n "$normalized" ]] && echo "$normalized" && return
fi
# 最后备选:简化处理
echo "$ip"
}
# 检查是否支持IPv6
check_ipv6_support() {
# 检查内核IPv6支持
if [[ ! -f /proc/sys/net/ipv6/conf/all/disable_ipv6 ]]; then
echo -e "${Red}错误: 您的内核不支持 IPv6${Font}"
return 1
fi
# 检查IPv6模块是否加载
if ! lsmod | grep -q ipv6; then
echo -e "${Yellow}警告: IPv6 模块未加载,尝试加载...${Font}"
modprobe ipv6 2>/dev/null || {
echo -e "${Red}无法加载 IPv6 模块${Font}"
return 1
}
fi
# 检查并启用IPv6
if [[ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 1 ]]; then
echo -e "${Yellow}警告: IPv6 当前被禁用${Font}"
read -p "是否要启用 IPv6? (y/n): " enable_ipv6
if [[ $enable_ipv6 =~ ^[Yy]$ ]]; then
# 启用IPv6
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1
echo "net.ipv6.conf.all.disable_ipv6=0" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6=0" >> /etc/sysctl.conf
echo -e "${Green}IPv6 已启用${Font}"
else
echo -e "${Red}IPv6 保持禁用状态,无法进行 IPv6 转发${Font}"
return 1
fi
fi
# 检查网络接口IPv6地址
local ipv6_addr=""
if command -v ip >/dev/null 2>&1; then
ipv6_addr=$(ip -6 addr show scope global 2>/dev/null | grep -oE 'inet6 ([0-9a-fA-F:]+)' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^::1' | head -n1)
fi
if [[ -z "$ipv6_addr" ]]; then
echo -e "${Red}错误: 未检测到可用的 IPv6 地址${Font}"
echo -e "${Yellow}请确保您的网络接口已配置 IPv6 地址${Font}"
return 1
else
echo -e "${Green}检测到 IPv6 地址: $ipv6_addr${Font}"
fi
# 检查并启用IPv6转发
if [[ $(cat /proc/sys/net/ipv6/conf/all/forwarding) -eq 0 ]]; then
echo -e "${Yellow}警告: IPv6 转发当前被禁用${Font}"
read -p "是否要启用 IPv6 转发? (y/n): " enable_forwarding
if [[ $enable_forwarding =~ ^[Yy]$ ]]; then
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1
sysctl -w net.ipv6.conf.default.forwarding=1 >/dev/null 2>&1
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.conf
echo -e "${Green}IPv6 转发已启用${Font}"
else
echo -e "${Yellow}IPv6 转发保持禁用状态,可能影响转发功能${Font}"
fi
fi
return 0
}
# 配置Socat
config_socat(){
echo -e "${Green}请选择转发类型:${Font}"
echo "1. IPv4 端口转发"
echo "2. IPv6 端口转发"
echo "3. IPv4 域名(DDNS)端口转发"
echo "4. IPv6 域名(DDNS)端口转发"
read -p "请输入选项 [1-4] (回车返回主菜单): " ip_version
# 检查空输入,回车返回主菜单
if [[ -z "$ip_version" ]]; then
echo -e "${Yellow}已取消操作,返回主菜单...${Font}"
return 1
fi
if [ "$ip_version" == "2" ] || [ "$ip_version" == "4" ]; then
if ! check_ipv6_support; then
echo -e "${Red}无法进行 IPv6 转发,请检查系统配置${Font}"
return 1
fi
fi
echo -e "${Green}请输入Socat配置信息${Font}"
while true; do
read -p "请输入本地端口 (留空随机分配): " port1
if [[ -z "$port1" ]]; then
echo -e "${Yellow}正在为您分配随机端口...${Font}"
port1=$(get_random_unused_port)
if [[ -n "$port1" ]]; then
echo -e "${Green}已分配随机端口: $port1${Font}"
break
else
continue
fi
elif [[ "$port1" =~ ^[0-9]+$ ]] && [[ $port1 -ge 1 ]] && [[ $port1 -le 65535 ]]; then
if check_port $port1; then
break
fi
else
echo -e "${Red}错误: 请输入1-65535之间的有效端口号${Font}"
fi
done
while true; do
read -p "请输入远程端口: " port2
if [[ -z "$port2" ]]; then
echo -e "${Red}错误: 远程端口不能为空${Font}"
continue
elif [[ "$port2" =~ ^[0-9]+$ ]] && [[ $port2 -ge 1 ]] && [[ $port2 -le 65535 ]]; then
break
else
echo -e "${Red}错误: 请输入1-65535之间的有效端口号${Font}"
fi
done
if [ "$ip_version" == "3" ] || [ "$ip_version" == "4" ]; then
while true; do
read -p "请输入远程域名: " socatip
if validate_domain_name "$socatip"; then
break
fi
done
else
while true; do
read -p "请输入远程IP: " socatip
if validate_ip_address "$socatip" "$ip_version"; then
if [ "$ip_version" == "2" ]; then
socatip=$(normalize_ipv6 "$socatip")
fi
break
fi
done
fi
}
# IP地址验证函数
validate_ip_address() {
local ip=$1
local ip_version=$2
case "$ip_version" in
1)
if ! [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e "${Red}错误: 无效的IPv4地址格式${Font}"
return 1
fi
;;
2)
if ! [[ $ip =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]]; then
echo -e "${Red}错误: 无效的IPv6地址格式${Font}"
return 1
fi
;;
esac
return 0
}
# 域名验证函数
validate_domain_name() {
local domain=$1
if ! [[ $domain =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
echo -e "${Red}错误: 无效的域名格式${Font}"
return 1
fi
# 尝试解析域名
if ! host "$domain" >/dev/null 2>&1 && ! nslookup "$domain" >/dev/null 2>&1 && ! dig "$domain" >/dev/null 2>&1; then
echo -e "${Red}错误: 无法解析域名${Font}"
return 1
fi
return 0
}
# 获取随机未使用的端口
get_random_unused_port() {
local min_port=1024
local max_port=65535
local max_attempts=100
local attempts=0
while [[ $attempts -lt $max_attempts ]]; do
local port=$((RANDOM % (max_port - min_port + 1) + min_port))
if check_port $port; then
echo $port
return 0
fi
((attempts++))
done
echo -e "${Red}错误: 无法找到可用的随机端口${Font}"
return 1
}
# 创建 systemd 服务文件
create_systemd_service() {
local name=$1
local command=$2
cat > /etc/systemd/system/${name}.service <<EOF
[Unit]
Description=Socat Forwarding Service
After=network.target
[Service]
Type=simple
ExecStart=$command
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable ${name}.service
systemctl start ${name}.service
}
# 创建单个Socat服务
create_single_socat_service() {
local protocol=$1 # tcp/udp
local ip_version=$2 # 4/6
local listen_port=$3
local target_ip=$4
local target_port=$5
local service_suffix=$6 # 用于服务名
local service_name="socat-${listen_port}-${target_port}-${protocol}"
local socat_cmd=""
# 根据IP版本和协议构建socat命令
if [ "$ip_version" == "4" ]; then
socat_cmd="/usr/bin/socat TCP4-LISTEN:${listen_port},reuseaddr,fork TCP4:${target_ip}:${target_port}"
if [ "$protocol" == "udp" ]; then
socat_cmd="/usr/bin/socat UDP4-LISTEN:${listen_port},reuseaddr,fork UDP4:${target_ip}:${target_port}"
fi
elif [ "$ip_version" == "6" ]; then
socat_cmd="/usr/bin/socat TCP6-LISTEN:${listen_port},reuseaddr,fork TCP6:${target_ip}:${target_port}"
if [ "$protocol" == "udp" ]; then
socat_cmd="/usr/bin/socat UDP6-LISTEN:${listen_port},reuseaddr,fork UDP6:${target_ip}:${target_port}"
fi
elif [ "$ip_version" == "domain" ]; then
socat_cmd="/usr/bin/socat TCP4-LISTEN:${listen_port},reuseaddr,fork TCP:${target_ip}:${target_port}"
if [ "$protocol" == "udp" ]; then
socat_cmd="/usr/bin/socat UDP4-LISTEN:${listen_port},reuseaddr,fork UDP:${target_ip}:${target_port}"
fi
elif [ "$ip_version" == "domain6" ]; then
socat_cmd="/usr/bin/socat TCP6-LISTEN:${listen_port},reuseaddr,fork TCP6:${target_ip}:${target_port}"
if [ "$protocol" == "udp" ]; then
socat_cmd="/usr/bin/socat UDP6-LISTEN:${listen_port},reuseaddr,fork UDP6:${target_ip}:${target_port}"
fi
fi
create_systemd_service "$service_name" "$socat_cmd"
}
# 启动Socat - 重构后的版本
start_socat(){
echo -e "${Green}正在配置Socat...${Font}"
local ip_type_num=""
local firewall_type=""
# 根据IP版本类型设置参数
case "$ip_version" in
1)
ip_type_num="4"
firewall_type="ipv4"
;;
2)
ip_type_num="6"
firewall_type="ipv6"
;;
3)
ip_type_num="domain"
firewall_type="ipv4"
;;
4)
ip_type_num="domain6"
firewall_type="ipv6"
;;
*)
echo -e "${Red}无效的选项,退出配置。${Font}"
return 1
;;
esac
# 统一创建TCP和UDP服务
create_single_socat_service "tcp" "$ip_type_num" "$port1" "$socatip" "$port2"
create_single_socat_service "udp" "$ip_type_num" "$port1" "$socatip" "$port2"
# 如果是域名类型,设置监控
if [ "$ip_version" == "3" ] || [ "$ip_version" == "4" ]; then
setup_domain_monitor "$socatip" "$port1" "$ip_type_num" "$port2"
fi
sleep 2
local service_name_tcp="socat-${port1}-${port2}-tcp"
local service_name_udp="socat-${port1}-${port2}-udp"
if systemctl is-active --quiet "$service_name_tcp" && systemctl is-active --quiet "$service_name_udp"; then
echo -e "${Green}Socat配置成功!${Font}"
echo -e "${Blue}本地端口: ${port1}${Font}"
echo -e "${Blue}远程端口: ${port2}${Font}"
echo -e "${Blue}远程地址: ${socatip}${Font}"
# 统一的显示信息
case "$ip_version" in
1)
echo -e "${Blue}本地服务器IP: ${ip}${Font}"
echo -e "${Blue}IP版本: IPv4${Font}"
;;
2)
echo -e "${Blue}本地服务器IPv6: ${ipv6}${Font}"
echo -e "${Blue}IP版本: IPv6${Font}"
;;
3)
echo -e "${Blue}本地服务器IP: ${ip}${Font}"
echo -e "${Blue}地址类型: 域名 (DDNS, IPv4优先)${Font}"
echo -e "${Blue}域名监控: 已启用 (每5分钟自动检查IP变更)${Font}"
;;
4)
echo -e "${Blue}本地服务器IPv6: ${ipv6}${Font}"
echo -e "${Blue}地址类型: 域名 (DDNS, IPv6优先)${Font}"
echo -e "${Blue}域名监控: 已启用 (每5分钟自动检查IP变更)${Font}"
;;
esac
add_to_config
configure_firewall ${port1} "$firewall_type"
return 0
else
echo -e "${Red}Socat启动失败请检查系统日志。${Font}"
journalctl -u "$service_name_tcp" -u "$service_name_udp"
return 1
fi
}
# 显示和删除转发
view_delete_forward() {
clear_screen
# 检查配置文件是否存在或是否为空
if [ ! -f "$CONFIG_FILE" ]; then
echo -e "${Red}当前没有活动的转发。${Font}"
return
fi
# 检查配置内容是否为空数组
local config_content=$(cat "$CONFIG_FILE" | tr -d '[:space:]')
if [ -z "$config_content" ] || [ "$config_content" = "[]" ]; then
echo -e "${Red}当前没有活动的转发。${Font}"
return
fi
echo -e "${Green}当前转发列表:${Font}"
local i=1
local entries=()
if command -v jq >/dev/null 2>&1; then
# 使用jq解析JSON配置
local configs=$(jq -c '.[]' "$CONFIG_FILE")
while IFS= read -r config; do
local ip_type=$(echo "$config" | jq -r '.type')
local listen_port=$(echo "$config" | jq -r '.listen_port')
local remote_ip=$(echo "$config" | jq -r '.remote_ip')
local remote_port=$(echo "$config" | jq -r '.remote_port')
entries+=("$ip_type $listen_port $remote_ip $remote_port")
case "$ip_type" in
"ipv4")
echo "$i. IPv4: $ip:$listen_port --> $remote_ip:$remote_port (TCP/UDP)"
;;
"ipv6")
echo "$i. IPv6: [$ipv6]:$listen_port --> [$remote_ip]:$remote_port (TCP/UDP)"
;;
"domain")
echo "$i. IPv4 域名: $ip:$listen_port --> $remote_ip:$remote_port (TCP/UDP) [DDNS, IPv4]"
;;
"domain6")
echo "$i. IPv6 域名: [$ipv6]:$listen_port --> $remote_ip:$remote_port (TCP/UDP) [DDNS, IPv6]"
;;
esac
((i++))
done <<< "$configs"
else
# 回退到基于行的JSON解析
local json_content=$(cat "$CONFIG_FILE")
local count=$(echo "$json_content" | grep -o '"type"' | wc -l)
for j in $(seq 0 $(($count-1))); do
local config=$(echo "$json_content" | sed -n 's/.*{\([^}]*\)}.*/\1/p' | sed -n "$((j+1))p")
local ip_type=$(echo "$config" | grep -o '"type":"[^"]*"' | cut -d'"' -f4)
local listen_port=$(echo "$config" | grep -o '"listen_port":[0-9]*' | cut -d':' -f2)
local remote_ip=$(echo "$config" | grep -o '"remote_ip":"[^"]*"' | cut -d'"' -f4)
local remote_port=$(echo "$config" | grep -o '"remote_port":[0-9]*' | cut -d':' -f2)
[ -z "$ip_type" ] && continue
entries+=("$ip_type $listen_port $remote_ip $remote_port")
case "$ip_type" in
"ipv4")
echo "$i. IPv4: $ip:$listen_port --> $remote_ip:$remote_port (TCP/UDP)"
;;
"ipv6")
echo "$i. IPv6: [$ipv6]:$listen_port --> [$remote_ip]:$remote_port (TCP/UDP)"
;;
"domain")
echo "$i. IPv4 域名: $ip:$listen_port --> $remote_ip:$remote_port (TCP/UDP) [DDNS, IPv4]"
;;
"domain6")
echo "$i. IPv6 域名: [$ipv6]:$listen_port --> $remote_ip:$remote_port (TCP/UDP) [DDNS, IPv6]"
;;
esac
((i++))
done
fi
read -p "请输入要删除的转发编号(多个编号用空格分隔,直接回车取消): " numbers
if [ -n "$numbers" ]; then
local nums_to_delete=($(echo "$numbers" | tr ' ' '\n' | sort -rn))
for num in "${nums_to_delete[@]}"; do
if [ $num -ge 1 ] && [ $num -lt $i ]; then
local index=$((num-1))
IFS=' ' read -r ip_type listen_port remote_ip remote_port <<< "${entries[$index]}"
remove_forward "$listen_port" "$ip_type"
# 从JSON配置中删除
if command -v jq >/dev/null 2>&1; then
jq --arg port "$listen_port" 'del(.[] | select(.listen_port == ($port | tonumber)))' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
else
# 回退到基于sed的删除
local json_content=$(cat "$CONFIG_FILE")
local new_content=$(echo "$json_content" | sed 's/{[^}]*"listen_port":'$listen_port'[^}]*},\?//g')
echo "$new_content" > "$CONFIG_FILE"
fi
case "$ip_type" in
"ipv4")
echo -e "${Green}已删除IPv4转发: $ip:$listen_port (TCP/UDP)${Font}"
;;
"ipv6")
echo -e "${Green}已删除IPv6转发: [$ipv6]:$listen_port (TCP/UDP)${Font}"
;;
"domain")
echo -e "${Green}已删除IPv4 域名转发: $ip:$listen_port --> $remote_ip (TCP/UDP) [IPv4]${Font}"
;;
"domain6")
echo -e "${Green}已删除IPv6 域名转发: [$ipv6]:$listen_port --> $remote_ip (TCP/UDP) [IPv6]${Font}"
;;
esac
remove_firewall_rules "$listen_port" "$ip_type"
else
echo -e "${Red}无效的编号: $num${Font}"
fi
done
fi
}
# 移除单个转发
remove_forward() {
local listen_port=$1
local ip_type=$2
local service_name="socat-${listen_port}-*"
# 停止并移除socat服务
systemctl stop ${service_name}
systemctl disable ${service_name}
rm -f /etc/systemd/system/${service_name}.service
systemctl daemon-reload
# 如果是域名类型,移除域名监控服务
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
remove_domain_monitor "$listen_port"
fi
echo -e "${Green}已移除端口 ${listen_port} 的转发${Font}"
}
# 防火墙检测和配置
configure_firewall() {
local port=$1
local ip_version=$2
# 标准化IP版本参数
case "$ip_version" in
domain|ipv4) ip_version="ipv4" ;;
domain6|ipv6) ip_version="ipv6" ;;
esac
# 检测防火墙类型和状态
local firewall_type=$(detect_firewall)
[[ -z "$firewall_type" ]] && {
echo -e "${Yellow}未检测到防火墙工具,端口 ${port} 配置完成。${Font}"
return 0
}
# 统一配置防火墙规则
if configure_firewall_rules "$firewall_type" "$port" "$ip_version"; then
echo -e "${Green}已为 ${ip_version} 端口 ${port} 配置防火墙规则 (TCP/UDP)${Font}"
else
echo -e "${Yellow}防火墙配置失败或无权限,请手动配置端口 ${port}${Font}"
fi
}
# 检测防火墙类型
detect_firewall() {
local firewall_type=""
# 检测防火墙状态
if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
firewall_type="firewalld"
elif command -v ufw >/dev/null 2>&1 && ufw status >/dev/null 2>&1; then
firewall_type="ufw"
elif command -v iptables >/dev/null 2>&1; then
firewall_type="iptables"
fi
echo "$firewall_type"
}
# 统一的防火墙规则配置
configure_firewall_rules() {
local firewall_type=$1
local port=$2
local ip_version=$3
case "$firewall_type" in
firewalld)
local zone=$(firewall-cmd --get-default-zone 2>/dev/null || echo "public")
local ipv6_flag=""
[[ "$ip_version" == "ipv6" ]] && ipv6_flag="--ipv6"
for protocol in tcp udp; do
firewall-cmd --zone="$zone" --add-port="${port}/${protocol}" --permanent $ipv6_flag 2>/dev/null || return 1
done
firewall-cmd --reload 2>/dev/null || return 1
;;
ufw)
for protocol in tcp udp; do
ufw allow "${port}/${protocol}" 2>/dev/null || return 1
done
;;
iptables)
local cmd="iptables"
[[ "$ip_version" == "ipv6" ]] && cmd="ip6tables"
for protocol in tcp udp; do
$cmd -C INPUT -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null || \
$cmd -I INPUT -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null || return 1
done
;;
*)
return 1
;;
esac
return 0
}
# 移除防火墙规则
remove_firewall_rules() {
local port=$1
local ip_type=$2
# 标准化IP版本参数
case "$ip_type" in
domain|ipv4) ip_type="ipv4" ;;
domain6|ipv6) ip_type="ipv6" ;;
esac
# 检测防火墙类型
local firewall_type=$(detect_firewall)
[[ -z "$firewall_type" ]] && {
echo -e "${Yellow}未检测到防火墙工具,跳过防火墙规则移除。${Font}"
return 0
}
# 统一移除防火墙规则
if remove_firewall_rules_by_type "$firewall_type" "$port" "$ip_type"; then
echo -e "${Green}已移除端口 ${port} 的防火墙规则 (TCP/UDP)${Font}"
else
echo -e "${Yellow}防火墙规则移除失败或无权限${Font}"
fi
}
# 统一的防火墙规则移除
remove_firewall_rules_by_type() {
local firewall_type=$1
local port=$2
local ip_type=$3
case "$firewall_type" in
firewalld)
local zone=$(firewall-cmd --get-default-zone 2>/dev/null || echo "public")
local ipv6_flag=""
[[ "$ip_type" == "ipv6" ]] && ipv6_flag="--ipv6"
for protocol in tcp udp; do
firewall-cmd --zone="$zone" --remove-port="${port}/${protocol}" --permanent $ipv6_flag 2>/dev/null || return 1
done
firewall-cmd --reload 2>/dev/null || return 1
;;
ufw)
for protocol in tcp udp; do
ufw delete allow "${port}/${protocol}" 2>/dev/null || return 1
done
;;
iptables)
local cmd="iptables"
[[ "$ip_type" == "ipv6" ]] && cmd="ip6tables"
for protocol in tcp udp; do
$cmd -D INPUT -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null || continue
done
;;
*)
return 1
;;
esac
return 0
}
# 恢复之前的转发
restore_forwards() {
if [ -s "$CONFIG_FILE" ]; then
echo "正在恢复之前的转发..."
if command -v jq >/dev/null 2>&1; then
# 使用jq解析JSON配置
local configs=$(jq -c '.[]' "$CONFIG_FILE")
while IFS= read -r config; do
local ip_type=$(echo "$config" | jq -r '.type')
local listen_port=$(echo "$config" | jq -r '.listen_port')
local remote_ip=$(echo "$config" | jq -r '.remote_ip')
local remote_port=$(echo "$config" | jq -r '.remote_port')
# 将配置类型转换为内部格式
local ip_version=""
case "$ip_type" in
"ipv4") ip_version="4" ;;
"ipv6") ip_version="6" ;;
"domain") ip_version="domain" ;;
"domain6") ip_version="domain6" ;;
*) continue ;; # 跳过无效类型
esac
# 使用统一的函数创建服务
create_single_socat_service "tcp" "$ip_version" "$listen_port" "$remote_ip" "$remote_port"
create_single_socat_service "udp" "$ip_version" "$listen_port" "$remote_ip" "$remote_port"
# 如果是域名类型,恢复监控
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
setup_domain_monitor "$remote_ip" "$listen_port" "$ip_type" "$remote_port"
fi
# 统一的显示信息
case "$ip_type" in
"ipv6"|"domain6")
echo "已恢复IPv6转发${listen_port} -> ${remote_ip}:${remote_port}"
;;
*)
echo "已恢复IPv4转发${listen_port} -> ${remote_ip}:${remote_port}"
;;
esac
# 域名监控恢复信息
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
echo "已恢复域名 ${remote_ip} 的IP监控服务"
fi
done <<< "$configs"
else
# 回退到基于行的JSON解析
local json_content=$(cat "$CONFIG_FILE")
local count=$(echo "$json_content" | grep -o '"type"' | wc -l)
for i in $(seq 0 $(($count-1))); do
local config=$(echo "$json_content" | sed -n 's/.*{\([^}]*\)}.*/\1/p' | sed -n "$((i+1))p")
local ip_type=$(echo "$config" | grep -o '"type":"[^"]*"' | cut -d'"' -f4)
local listen_port=$(echo "$config" | grep -o '"listen_port":[0-9]*' | cut -d':' -f2)
local remote_ip=$(echo "$config" | grep -o '"remote_ip":"[^"]*"' | cut -d'"' -f4)
local remote_port=$(echo "$config" | grep -o '"remote_port":[0-9]*' | cut -d':' -f2)
[ -z "$ip_type" ] && continue
# 将配置类型转换为内部格式
local ip_version=""
case "$ip_type" in
"ipv4") ip_version="4" ;;
"ipv6") ip_version="6" ;;
"domain") ip_version="domain" ;;
"domain6") ip_version="domain6" ;;
*) continue ;;
esac
# 使用统一的函数创建服务
create_single_socat_service "tcp" "$ip_version" "$listen_port" "$remote_ip" "$remote_port"
create_single_socat_service "udp" "$ip_version" "$listen_port" "$remote_ip" "$remote_port"
# 如果是域名类型,恢复监控
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
setup_domain_monitor "$remote_ip" "$listen_port" "$ip_type" "$remote_port"
fi
# 统一的显示信息
case "$ip_type" in
"ipv6"|"domain6")
echo "已恢复IPv6转发${listen_port} -> ${remote_ip}:${remote_port}"
;;
*)
echo "已恢复IPv4转发${listen_port} -> ${remote_ip}:${remote_port}"
;;
esac
# 域名监控恢复信息
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
echo "已恢复域名 ${remote_ip} 的IP监控服务"
fi
done
fi
fi
}
# 检查是否已启用BBR或其变种
check_and_enable_bbr() {
echo -e "${Green}正在检查 BBR 状态...${Font}"
# 获取内核版本 - 更精确的版本检测
kernel_version=$(uname -r | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
major_version=$(echo "$kernel_version" | cut -d. -f1)
minor_version=$(echo "$kernel_version" | cut -d. -f2)
# 检查内核版本是否支持BBR (4.9+ 支持BBR, 4.13+ 支持BBRv2)
if [[ $major_version -lt 4 ]] || [[ $major_version -eq 4 && $minor_version -lt 9 ]]; then
echo -e "${Red}当前内核版本 ($kernel_version) 过低,不支持 BBR。需要 4.9 或更高版本。${Font}"
return 1
fi
# 检查系统是否支持BBR算法
if ! sysctl net.ipv4.tcp_available_congestion_control &>/dev/null; then
echo -e "${Red}系统不支持拥塞控制算法配置${Font}"
return 1
fi
# 获取可用的拥塞控制算法
available_cc=$(sysctl -n net.ipv4.tcp_available_congestion_control 2>/dev/null || echo "")
if [[ -z "$available_cc" ]]; then
echo -e "${Red}无法获取可用的拥塞控制算法${Font}"
return 1
fi
# 检查BBR是否可用 - 按优先级顺序bbrplus -> bbr -> bbr2
bbr_variants=("bbrplus" "bbr" "bbr2")
supported_bbr=""
for variant in "${bbr_variants[@]}"; do
if echo "$available_cc" | grep -q "$variant"; then
supported_bbr="$variant"
break
fi
done
if [[ -z "$supported_bbr" ]]; then
echo -e "${Red}系统不支持BBR算法。可用算法: $available_cc${Font}"
return 1
fi
# 获取当前拥塞控制算法
current_cc=$(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null || echo "")
if [[ -z "$current_cc" ]]; then
echo -e "${Red}无法获取当前拥塞控制算法${Font}"
return 1
fi
# 检查当前算法是否为BBR变体
is_bbr_enabled=false
current_bbr_variant=""
for variant in "${bbr_variants[@]}"; do
if [[ "$current_cc" == "$variant" ]]; then
is_bbr_enabled=true
current_bbr_variant="$variant"
echo -e "${Yellow}检测到系统已启用 ${current_bbr_variant}${Font}"
break
fi
done
# 检查内核是否内置BBR - 不再强制要求模块加载
has_bbr_module=false
if lsmod | grep -q "tcp_bbr" || echo "$available_cc" | grep -q "bbr"; then
has_bbr_module=true
fi
if [[ "$has_bbr_module" == false ]]; then
echo -e "${Yellow}BBR模块未找到尝试加载...${Font}"
if modprobe tcp_bbr 2>/dev/null; then
echo -e "${Green}BBR模块加载成功${Font}"
has_bbr_module=true
else
echo -e "${Yellow}BBR模块加载失败可能是内置内核或已集成${Font}"
# 继续执行,因为可能是内置内核
fi
fi
# 如果未启用BBR尝试启用
if [[ "$is_bbr_enabled" != true ]]; then
echo -e "${Yellow}当前拥塞控制算法为 ${current_cc},正在切换到 ${supported_bbr}...${Font}"
if sysctl -w net.ipv4.tcp_congestion_control="$supported_bbr" 2>/dev/null; then
echo -e "${Green}已切换到 ${supported_bbr}${Font}"
current_cc="$supported_bbr"
else
echo -e "${Red}切换到 ${supported_bbr} 失败${Font}"
return 1
fi
fi
# 检查并设置队列调度算法
current_qdisc=$(sysctl -n net.core.default_qdisc 2>/dev/null || echo "")
if [[ "$current_qdisc" != "fq" ]]; then
echo -e "${Yellow}当前队列调度算法为 ${current_qdisc},正在切换到 fq...${Font}"
if sysctl -w net.core.default_qdisc=fq 2>/dev/null; then
echo -e "${Green}已切换到 fq 队列调度${Font}"
else
echo -e "${Yellow}切换到 fq 失败,可能系统不支持${Font}"
fi
fi
# 持久化配置
local sysctl_file="/etc/sysctl.conf"
if [[ -w "$sysctl_file" ]]; then
# 清理旧的BBR配置
sed -i '/net\.ipv4\.tcp_congestion_control/d' "$sysctl_file"
sed -i '/net\.core\.default_qdisc/d' "$sysctl_file"
# 添加新的BBR配置
echo "net.ipv4.tcp_congestion_control = $current_cc" >> "$sysctl_file"
echo "net.core.default_qdisc = fq" >> "$sysctl_file"
# 重新加载配置
sysctl -p 2>/dev/null || echo -e "${Yellow}sysctl配置重载部分失败${Font}"
else
echo -e "${Yellow}无法写入sysctl配置文件配置将在重启后失效${Font}"
fi
# 最终验证
final_cc=$(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null || echo "")
final_qdisc=$(sysctl -n net.core.default_qdisc 2>/dev/null || echo "")
# 检查是否为BBR变体
is_bbr_final=false
for variant in "${bbr_variants[@]}"; do
if [[ "$final_cc" == "$variant" ]]; then
is_bbr_final=true
break
fi
done
if [[ "$is_bbr_final" == true ]]; then
echo -e "${Green}BBR 已成功启用。当前算法: $final_cc, 队列调度: $final_qdisc${Font}"
return 0
else
echo -e "${Red}BBR 启用失败,当前拥塞控制算法为 ${final_cc}${Font}"
return 1
fi
}
# 网络加速配置管理
manage_network_acceleration() {
local action="$1" # "enable" 或 "disable"
# 定义加速配置(键=值 格式)- 专为端口转发优化
local -A acceleration_configs=(
# 核心转发优化
["net.ipv4.tcp_fastopen"]="3"
["net.ipv4.tcp_slow_start_after_idle"]="0"
["net.ipv4.tcp_mtu_probing"]="1"
# 缓冲区优化 - 大幅提升转发性能
["net.core.rmem_max"]="67108864"
["net.core.wmem_max"]="67108864"
["net.ipv4.tcp_rmem"]="4096 262144 67108864"
["net.ipv4.tcp_wmem"]="4096 262144 67108864"
["net.ipv4.tcp_mem"]="8388608 12582912 16777216"
["net.core.netdev_max_backlog"]="8192"
["net.core.netdev_budget"]="600"
# 连接管理优化
["net.ipv4.tcp_max_syn_backlog"]="8192"
["net.ipv4.tcp_tw_reuse"]="1"
["net.ipv4.tcp_fin_timeout"]="10"
["net.ipv4.tcp_keepalive_time"]="600"
["net.ipv4.tcp_keepalive_intvl"]="30"
["net.ipv4.tcp_keepalive_probes"]="3"
["net.ipv4.tcp_max_tw_buckets"]="5000000"
# 高级TCP特性
["net.ipv4.tcp_syncookies"]="1"
["net.ipv4.tcp_sack"]="1"
["net.ipv4.tcp_fack"]="1"
["net.ipv4.tcp_window_scaling"]="1"
["net.ipv4.tcp_adv_win_scale"]="1"
["net.ipv4.tcp_moderate_rcvbuf"]="1"
["net.ipv4.tcp_no_metrics_save"]="1"
["net.ipv4.tcp_rfc1337"]="1"
["net.ipv4.tcp_timestamps"]="1"
["net.ipv4.tcp_ecn"]="0" # 禁用ECN避免兼容性问题
# 性能调优
["net.core.optmem_max"]="65536"
["net.ipv4.tcp_notsent_lowat"]="32768"
["net.ipv4.ip_local_port_range"]="1024 65535"
["net.ipv4.tcp_max_orphans"]="8192"
["net.ipv4.tcp_abort_on_overflow"]="0"
["net.core.somaxconn"]="8192"
)
# 定义默认配置(键=值 格式)
local -A default_configs=(
["net.ipv4.tcp_fastopen"]="0"
["net.ipv4.tcp_congestion_control"]="cubic"
["net.core.default_qdisc"]="pfifo_fast"
["net.ipv4.tcp_slow_start_after_idle"]="1"
["net.ipv4.tcp_mtu_probing"]="0"
["net.core.rmem_max"]="212992"
["net.core.wmem_max"]="212992"
["net.ipv4.tcp_rmem"]="4096 87380 6291456"
["net.ipv4.tcp_wmem"]="4096 16384 4194304"
["net.ipv4.tcp_mem"]="378651 504868 757299"
["net.core.netdev_max_backlog"]="1000"
["net.ipv4.tcp_max_syn_backlog"]="128"
["net.ipv4.tcp_tw_reuse"]="0"
["net.ipv4.tcp_fin_timeout"]="60"
["net.ipv4.tcp_keepalive_time"]="7200"
["net.ipv4.tcp_max_tw_buckets"]="180000"
["net.ipv4.tcp_syncookies"]="1"
["net.ipv4.tcp_rfc1337"]="0"
["net.ipv4.tcp_sack"]="1"
["net.ipv4.tcp_fack"]="1"
["net.ipv4.tcp_window_scaling"]="1"
["net.ipv4.tcp_adv_win_scale"]="1"
["net.ipv4.tcp_moderate_rcvbuf"]="1"
["net.core.optmem_max"]="20480"
["net.ipv4.tcp_notsent_lowat"]="4294967295"
)
# 定义所有需要清理的配置键 - 包含新增和原有的所有网络参数
local cleanup_keys=(
"net.ipv4.tcp_fastopen"
"net.ipv4.tcp_congestion_control"
"net.core.default_qdisc"
"net.ipv4.tcp_slow_start_after_idle"
"net.ipv4.tcp_mtu_probing"
"net.core.rmem_max"
"net.core.wmem_max"
"net.ipv4.tcp_rmem"
"net.ipv4.tcp_wmem"
"net.ipv4.tcp_mem"
"net.core.netdev_max_backlog"
"net.core.netdev_budget"
"net.ipv4.tcp_max_syn_backlog"
"net.ipv4.tcp_tw_reuse"
"net.ipv4.tcp_fin_timeout"
"net.ipv4.tcp_keepalive_time"
"net.ipv4.tcp_keepalive_intvl"
"net.ipv4.tcp_keepalive_probes"
"net.ipv4.tcp_max_tw_buckets"
"net.ipv4.tcp_syncookies"
"net.ipv4.tcp_rfc1337"
"net.ipv4.tcp_sack"
"net.ipv4.tcp_fack"
"net.ipv4.tcp_window_scaling"
"net.ipv4.tcp_adv_win_scale"
"net.ipv4.tcp_moderate_rcvbuf"
"net.ipv4.tcp_no_metrics_save"
"net.ipv4.tcp_timestamps"
"net.ipv4.tcp_ecn"
"net.core.optmem_max"
"net.ipv4.tcp_notsent_lowat"
"net.ipv4.ip_local_port_range"
"net.ipv4.tcp_max_orphans"
"net.ipv4.tcp_abort_on_overflow"
"net.core.somaxconn"
)
# 清理配置文件中的相关配置
for key in "${cleanup_keys[@]}"; do
sed -i "/${key//\./\\.}/d" /etc/sysctl.conf
done
# 根据action选择配置
local -n configs
local message=""
if [[ "$action" == "enable" ]]; then
configs=acceleration_configs
message="端口转发加速已开启"
# 额外处理BBR
check_and_enable_bbr
echo 3 > /proc/sys/net/ipv4/tcp_fastopen
else
configs=default_configs
message="端口转发加速已关闭"
fi
# 应用配置(静默执行)
for key in "${!configs[@]}"; do
sysctl -w "${key}=${configs[$key]}" >/dev/null 2>&1 #删除”/dev/null 2>&1“输出信息
echo "${key} = ${configs[$key]}" >> /etc/sysctl.conf
done
sysctl -p >/dev/null 2>&1 #删除”/dev/null 2>&1“输出信息
# 输出结果
if [[ "$action" == "enable" ]]; then
echo -e "${Green}${message}${Font}"
else
echo -e "${Yellow}${message}${Font}"
fi
}
# 开启端口转发加速
enable_acceleration() {
echo -e "${Green}正在开启端口转发加速...${Font}"
manage_network_acceleration "enable"
}
# 关闭端口转发加速
disable_acceleration() {
echo -e "${Yellow}正在关闭端口转发加速...${Font}"
manage_network_acceleration "disable"
}
# 设置域名监控服务
setup_domain_monitor() {
local domain=$1
local listen_port=$2
local ip_type=$3
local remote_port=$4
local monitor_script="$SOCATS_DIR/monitor_${listen_port}.sh"
# 创建完整的监控脚本,直接内嵌函数定义
cat > "$monitor_script" <<'EOF'
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 脚本参数
DOMAIN="$1"
LISTEN_PORT="$2"
IP_TYPE="$3"
REMOTE_PORT="$4"
SOCATS_DIR="$5"
# 监控域名IP变更的函数
monitor_domain_ip() {
local domain=$1
local listen_port=$2
local ip_type=$3
local cache_file="${SOCATS_DIR}/dns_cache_${domain//[^a-zA-Z0-9]/_}.txt"
local current_ip=""
# 获取当前IP支持IPv4和IPv6
if [[ "$ip_type" == "ipv4" || "$ip_type" == "domain" ]]; then
# 尝试使用不同的命令解析IPv4
current_ip=$(host -t A "$domain" 2>/dev/null | grep "has address" | head -n1 | awk '{print $NF}')
if [ -z "$current_ip" ]; then
current_ip=$(dig +short A "$domain" 2>/dev/null | head -n1)
fi
if [ -z "$current_ip" ]; then
current_ip=$(nslookup "$domain" 2>/dev/null | grep -A1 "Name:" | grep "Address:" | head -n1 | awk '{print $NF}')
fi
else
# 尝试使用不同的命令解析IPv6
current_ip=$(host -t AAAA "$domain" 2>/dev/null | grep "has IPv6 address" | head -n1 | awk '{print $NF}')
if [ -z "$current_ip" ]; then
current_ip=$(dig +short AAAA "$domain" 2>/dev/null | head -n1)
fi
if [ -z "$current_ip" ]; then
current_ip=$(nslookup -type=AAAA "$domain" 2>/dev/null | grep -A1 "Name:" | grep "Address:" | head -n1 | awk '{print $NF}')
fi
fi
if [ -z "$current_ip" ]; then
echo "无法解析域名 $domain 的IP地址" >> "${SOCATS_DIR}/dns_monitor.log"
return 1
fi
# 如果缓存文件不存在,创建它
if [ ! -f "$cache_file" ]; then
echo "$current_ip" > "$cache_file"
echo "$(date): 初始化域名 $domain 的IP缓存: $current_ip" >> "${SOCATS_DIR}/dns_monitor.log"
return 0
fi
# 读取上次缓存的IP
local cached_ip=$(cat "$cache_file")
# 如果IP变更重启服务
if [ "$current_ip" != "$cached_ip" ]; then
echo "$(date): 检测到域名 $domain 的IP变更: $cached_ip -> $current_ip" >> "${SOCATS_DIR}/dns_monitor.log"
echo "$current_ip" > "$cache_file"
# 重启对应的socat服务
local service_name="socat-${listen_port}-*"
systemctl restart $service_name
echo "$(date): 已重启转发服务 $service_name" >> "${SOCATS_DIR}/dns_monitor.log"
return 0
fi
return 0
}
# 执行监控
monitor_domain_ip "$DOMAIN" "$LISTEN_PORT" "$IP_TYPE"
EOF
chmod +x "$monitor_script"
# 创建systemd定时器服务
local timer_name="domain-monitor-${listen_port}"
cat > /etc/systemd/system/${timer_name}.service <<EOF
[Unit]
Description=Domain IP Monitor Service for ${domain}
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash $monitor_script "${domain}" "${listen_port}" "${ip_type}" "${remote_port}" "${SOCATS_DIR}"
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/${timer_name}.timer <<EOF
[Unit]
Description=Domain IP Monitor Timer for ${domain}
Requires=${timer_name}.service
[Timer]
OnBootSec=60
OnUnitActiveSec=300
AccuracySec=1
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable ${timer_name}.timer
systemctl start ${timer_name}.timer
echo -e "${Green}已启用域名 ${domain} 的IP监控每5分钟检查一次变更${Font}"
}
# 移除域名监控服务
remove_domain_monitor() {
local listen_port=$1
local timer_name="domain-monitor-${listen_port}"
systemctl stop ${timer_name}.timer >/dev/null 2>&1
systemctl disable ${timer_name}.timer >/dev/null 2>&1
rm -f /etc/systemd/system/${timer_name}.service
rm -f /etc/systemd/system/${timer_name}.timer
rm -f "$SOCATS_DIR/monitor_${listen_port}.sh"
# 获取当前端口的域名信息并删除对应的DNS缓存文件
local domain=""
if command -v jq >/dev/null 2>&1; then
domain=$(jq -r --argjson port "$listen_port" '.[] | select(.listen_port == $port and (.type == "domain" or .type == "domain6")) | .remote_ip' "$CONFIG_FILE" 2>/dev/null)
else
# 从配置文件中提取域名
domain=$(grep -o '"listen_port":'$listen_port'[^}]*' "$CONFIG_FILE" | grep -o '"remote_ip":"[^"]*"' | head -n1 | cut -d'"' -f4)
fi
if [[ -n "$domain" ]]; then
# 将域名转换为缓存文件名格式
local cache_filename="dns_cache_${domain//[^a-zA-Z0-9]/_}.txt"
local cache_file="${SOCATS_DIR}/${cache_filename}"
if [ -f "$cache_file" ]; then
rm -f "$cache_file"
echo -e "${Green}已删除DNS缓存文件: ${cache_filename}${Font}"
fi
fi
systemctl daemon-reload
echo -e "${Green}已移除端口 ${listen_port} 的域名监控服务${Font}"
}
# 切换JSON格式化开关
toggle_json_format() {
if [ "$JSON_FORMAT_ENABLED" -eq 1 ]; then
JSON_FORMAT_ENABLED=0
echo -e "${Green}已关闭JSON格式化配置文件将保持紧凑格式${Font}"
else
JSON_FORMAT_ENABLED=1
echo -e "${Green}已开启JSON格式化配置文件将被格式化${Font}"
fi
# 立即应用新的格式化设置到现有配置
if [ -f "$CONFIG_FILE" ]; then
format_json_config "$CONFIG_FILE" "$JSON_FORMAT_ENABLED"
fi
# 保存设置到配置文件
echo "JSON_FORMAT_ENABLED=$JSON_FORMAT_ENABLED" > "$SOCATS_DIR/.json_format"
}
# 修改域名监控频率
change_monitor_interval() {
if [ ! -s "$CONFIG_FILE" ]; then
echo -e "${Red}当前没有活动的转发。${Font}"
return
fi
local has_domain=false
local domain_entries=()
local i=1
if command -v jq >/dev/null 2>&1; then
# 使用jq解析JSON配置
local configs=$(jq -c '.[]' "$CONFIG_FILE")
while IFS= read -r config; do
local ip_type=$(echo "$config" | jq -r '.type')
local listen_port=$(echo "$config" | jq -r '.listen_port')
local remote_ip=$(echo "$config" | jq -r '.remote_ip')
local remote_port=$(echo "$config" | jq -r '.remote_port')
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
has_domain=true
domain_entries+=("$listen_port $remote_ip $ip_type $remote_port")
if [ "$ip_type" == "domain" ]; then
echo "$i. IPv4 域名: $ip:$listen_port --> $remote_ip:$remote_port"
else
echo "$i. IPv6 域名: [$ipv6]:$listen_port --> $remote_ip:$remote_port"
fi
((i++))
fi
done <<< "$configs"
else
# 回退到基于行的JSON解析
local json_content=$(cat "$CONFIG_FILE")
local count=$(echo "$json_content" | grep -o '"type"' | wc -l)
for j in $(seq 0 $(($count-1))); do
local config=$(echo "$json_content" | sed -n 's/.*{\([^}]*\)}.*/\1/p' | sed -n "$((j+1))p")
local ip_type=$(echo "$config" | grep -o '"type":"[^"]*"' | cut -d'"' -f4)
local listen_port=$(echo "$config" | grep -o '"listen_port":[0-9]*' | cut -d':' -f2)
local remote_ip=$(echo "$config" | grep -o '"remote_ip":"[^"]*"' | cut -d'"' -f4)
local remote_port=$(echo "$config" | grep -o '"remote_port":[0-9]*' | cut -d':' -f2)
[ -z "$ip_type" ] && continue
if [ "$ip_type" == "domain" ] || [ "$ip_type" == "domain6" ]; then
has_domain=true
domain_entries+=("$listen_port $remote_ip $ip_type $remote_port")
if [ "$ip_type" == "domain" ]; then
echo "$i. IPv4 域名: $ip:$listen_port --> $remote_ip:$remote_port"
else
echo "$i. IPv6 域名: [$ipv6]:$listen_port --> $remote_ip:$remote_port"
fi
((i++))
fi
done
fi
if [ "$has_domain" == "false" ]; then
echo -e "${Red}当前没有活动的域名转发。${Font}"
return
fi
read -p "请输入要修改监控频率的域名转发编号: " num
if [ -z "$num" ] || ! [[ $num =~ ^[0-9]+$ ]] || [ $num -lt 1 ] || [ $num -gt ${#domain_entries[@]} ]; then
echo -e "${Red}无效的编号。${Font}"
return
fi
local index=$((num-1))
IFS=' ' read -r port domain type remote_port <<< "${domain_entries[$index]}"
local timer_name="domain-monitor-${port}"
local timer_file="/etc/systemd/system/${timer_name}.timer"
if [ ! -f "$timer_file" ]; then
echo -e "${Red}找不到域名 $domain 的监控定时器。${Font}"
return
fi
local current_interval=$(grep "OnUnitActiveSec" "$timer_file" | awk -F= '{print $2}' | tr -d '[:space:]')
echo -e "${Green}当前域名 $domain 的监控频率为 ${current_interval:-300s}${Font}"
echo -e "${Yellow}请选择新的监控频率:${Font}"
echo "1. 1分钟 (适合频繁变更的域名)"
echo "2. 5分钟 (默认)"
echo "3. 15分钟"
echo "4. 30分钟"
echo "5. 1小时"
echo "6. 自定义"
read -p "请选择 [1-6]: " choice
local new_interval=""
case $choice in
1) new_interval="60s" ;;
2) new_interval="300s" ;;
3) new_interval="900s" ;;
4) new_interval="1800s" ;;
5) new_interval="3600s" ;;
6)
read -p "请输入自定义时间间隔 (格式: 数字+单位, 例如 10s, 5m, 1h): " custom_interval
if [[ $custom_interval =~ ^[0-9]+[smhd]$ ]]; then
new_interval=$custom_interval
else
echo -e "${Red}无效的时间格式。使用默认值300s。${Font}"
new_interval="300s"
fi
;;
*)
echo -e "${Red}无效的选择。使用默认值300s。${Font}"
new_interval="300s"
;;
esac
# 更新定时器配置
sed -i "s/OnUnitActiveSec=.*/OnUnitActiveSec=$new_interval/" "$timer_file"
systemctl daemon-reload
systemctl restart ${timer_name}.timer
echo -e "${Green}已将域名 $domain 的监控频率更新为 $new_interval${Font}"
}
# 显示Socat管理子菜单
manage_socat_menu() {
while true; do
clear_screen
echo -e "${Green}
_____ __
/ ___/____ _________ _/ /_
\__ \/ __ \/ ___/ __ \`/ __/
___/ / /_/ / /__/ /_/ / /_
/____/\____/\___/\__,_/\__/ ${Yellow}Socat管理${Font}"
echo -e "${Blue}==========================================${Font}"
echo -e "${Yellow}1.${Font} 开启Socat"
echo -e "${Yellow}2.${Font} 关闭Socat"
echo -e "${Yellow}3.${Font} 重启Socat"
echo -e "${Yellow}4.${Font} 卸载Socat"
echo -e "${Yellow}5.${Font} 返回主菜单"
echo -e "${Blue}==========================================${Font}"
read -p "请输入选项 [1-5]: " choice
case $choice in
1)
echo -e "${Green}开启Socat...${Font}"
open_socat
;;
2)
echo -e "${Green}终止所有 Socat 进程...${Font}"
kill_all_socat
;;
3)
echo -e "${Green}重启Socat...${Font}"
re_socat
;;
4)
echo -e "${Green}卸载Socat...${Font}"
uninstall_socat
return
;;
5)
return
;;
*)
echo -e "${Red}无效的选项,请重新输入。${Font}"
sleep 2
;;
esac
done
}
# 开启/重新加载socat
open_socat(){
restore_forwards
}
# 强制终止所有Socat进程
kill_all_socat() {
echo -e "${Yellow}正在终止所有 Socat 进程...${Font}"
# 停止并禁用所有socat服务
for service_file in /etc/systemd/system/socat-*.service; do
if [ -f "$service_file" ]; then
service_name=$(basename "$service_file" .service)
systemctl stop "$service_name"
systemctl disable "$service_name"
fi
done
rm -f /etc/systemd/system/socat-*.service
systemctl daemon-reload
pkill -9 -f "$(which socat)"
#pkill -9 -f "$0"
sleep 2
if pgrep -f socat > /dev/null; then
echo -e "${Red}警告:某些 Socat 进程可能仍在运行。请考虑手动检查。${Font}"
else
echo -e "${Green}所有 Socat 进程已成功终止。${Font}"
fi
if [[ "$1" == "uninstall" ]]; then
> "$CONFIG_FILE"
echo -e "${Green}已清空转发配置文件${Font}"
elif [[ "$1" != "noconfirm" ]]; then
read -p "是否清除Socat转发配置文件[y/N]: " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
> "$CONFIG_FILE"
echo -e "${Green}已清空转发配置文件${Font}"
else
echo -e "${Yellow}已保留转发配置文件${Font}"
fi
else
echo -e "${Yellow}正在执行重启操作${Font}"
fi
echo -e "${Green}已从配置和开机自启动中移除所有 Socat 转发${Font}"
}
# 重启socat
re_socat(){
kill_all_socat "noconfirm"
sleep 2
restore_forwards
}
# 改进的卸载socat
uninstall_socat() {
if ! command -v socat >/dev/null 2>&1; then
echo -e "${Red}错误系统中未安装Socat无需执行卸载操作${Font}"
return 1
fi
echo -e "${Yellow}正在执行彻底卸载操作...${Font}"
# 停止并清理所有服务
kill_all_socat "uninstall"
# 删除系统服务文件
rm -f /etc/systemd/system/socat-*.{service,timer}
systemctl daemon-reload
# 删除配置目录和文件
rm -rf "$SOCATS_DIR"
[ -f "$CONFIG_FILE" ] && rm -f "$CONFIG_FILE"
# 使用检测到的包管理器卸载
local uninstall_success=0
case "$PKG_MANAGER" in
"apt")
echo -e "${Yellow}正在通过apt卸载Socat...${Font}"
apt-get remove -y socat && apt-get autoremove -y && uninstall_success=1
;;
"yum")
echo -e "${Yellow}正在通过yum卸载Socat...${Font}"
yum remove -y socat && yum autoremove -y && uninstall_success=1
;;
"dnf")
echo -e "${Yellow}正在通过dnf卸载Socat...${Font}"
dnf remove -y socat && dnf autoremove -y && uninstall_success=1
;;
"pacman")
echo -e "${Yellow}正在通过pacman卸载Socat...${Font}"
pacman -Rns --noconfirm socat && uninstall_success=1
;;
*)
# 手动删除
local socat_path=$(command -v socat 2>/dev/null)
if [[ -n "$socat_path" ]]; then
rm -f "$socat_path"
[[ ! -f "$socat_path" ]] && uninstall_success=1
fi
;;
esac
if [[ $uninstall_success -eq 1 ]] && ! command -v socat >/dev/null 2>&1; then
echo -e "${Green}✅ Socat卸载验证通过${Font}"
else
echo -e "${Red}⚠️ Socat卸载未完成请手动检查${Font}"
echo -e "${Yellow}1. which socat\n2. 残留进程: pgrep -f socat${Font}"
fi
echo -e "${Green}已成功卸载Socat及所有相关文件${Font}"
# 询问是否同时卸载jq
read -p "是否同时卸载jq[y/N]: " uninstall_jq_confirm
if [[ $uninstall_jq_confirm =~ ^[Yy]$ ]]; then
echo -e "${Yellow}正在卸载jq...${Font}"
uninstall_jq
fi
}
# 显示菜单
show_menu() {
echo -e "${Green}
_____ __
/ ___/____ _________ _/ /_
\__ \/ __ \/ ___/ __ \`/ __/
___/ / /_/ / /__/ /_/ / /_
/____/\____/\___/\__,_/\__/ ${Yellow}Management Script${Font}"
echo -e "${Blue}==========================================${Font}"
echo -e "${Yellow}1.${Font} 添加新转发"
echo -e "${Yellow}2.${Font} 查看或删除转发"
echo -e "${Yellow}3.${Font} Socat管理"
echo -e "${Yellow}4.${Font} 开启端口转发加速"
echo -e "${Yellow}5.${Font} 关闭端口转发加速"
echo -e "${Yellow}6.${Font} 设置域名监控频率"
echo -e "${Yellow}7.${Font} 配置文件JSON格式化 ($([ "$JSON_FORMAT_ENABLED" -eq 1 ] && echo "开启" || echo "关闭"))"
echo -e "${Yellow}8.${Font} 退出脚本"
echo -e "${Blue}==========================================${Font}"
echo -e "${Green}当前 IPv4: ${ip:-未知}${Font}"
echo -e "${Green}当前 IPv6: ${ipv6:-未知}${Font}"
echo
}
# 主程序
main() {
check_root
check_sys
install_socat
install_jq
ip=$(get_ip)
ipv6=$(get_ipv6)
// 加载JSON格式化设置
if [ -f "$SOCATS_DIR/.json_format" ]; then
source "$SOCATS_DIR/.json_format"
fi
// 初始化标记检查
if [ ! -f "$SOCATS_DIR/.initialized" ]; then
init_config
restore_forwards
clear_screen
touch "$SOCATS_DIR/.initialized"
echo -e "${Green}所有配置和日志文件将保存在: $SOCATS_DIR${Font}"
fi
clear_screen
#echo -e "${Green}所有配置和日志文件将保存在: $SOCATS_DIR${Font}"
while true; do
show_menu
read -p "请输入选项 [1-8]: " choice
clear_screen
case $choice in
1)
if config_socat; then
start_socat
echo -e "${Green}Socat配置完成并成功启动${Font}"
press_any_key
elif [ $? -eq 1 ]; then
# 用户主动取消,不显示错误
echo -e "${Yellow}已取消配置操作${Font}"
clear_screen
else
echo -e "${Red}配置失败,未能启动 Socat${Font}"
press_any_key
fi
;;
2)
view_delete_forward
press_any_key
;;
3)
manage_socat_menu
clear_screen
;;
4)
enable_acceleration
press_any_key
;;
5)
disable_acceleration
press_any_key
;;
6)
change_monitor_interval
press_any_key
;;
7)
toggle_json_format
press_any_key
;;
8)
echo -e "${Green}感谢使用,再见!${Font}"
exit 0
;;
*)
echo -e "${Red}无效选项,请重新选择${Font}"
press_any_key
;;
esac
done
}
# 执行主程序
main