2289 lines
77 KiB
Bash
2289 lines
77 KiB
Bash
#!/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
|