#!/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 </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 < /etc/systemd/system/${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