Skip to content

socat IPv6 转发实用指南

socat 是一个功能强大的多用途网络工具,被誉为“网络工具中的瑞士军刀”。它可以在两个数据流之间建立双向通道,这些数据流可以是文件、管道、设备或网络套接字。本文档将全面介绍如何使用 socat 进行 IPv6 流量转发,从基本命令到高级的自动化管理脚本。

1. 基本用法

socat 的核心语法是将两个地址连接起来:

bash
socat [选项] <地址1> <地址2>

对于 IPv6,我们主要使用 TCP6UDP6 相关的地址类型。

TCP 转发

将一个 IPv6 地址的 TCP 端口流量转发到另一个地址。

场景: 将本机 8080 端口的 TCP 流量,转发到 [2001:db8::1]:80

bash
socat TCP6-LISTEN:8080,fork,reuseaddr TCP6:[2001:db8::1]:80
  • TCP6-LISTEN:8080: 在本机所有 IPv6 地址上监听 TCP 8080 端口。
  • fork: 关键选项,为每个新连接创建一个子进程,实现并发处理。
  • reuseaddr: 允许端口快速重用,避免 "Address already in use" 错误。
  • TCP6:[2001:db8::1]:80: 流量的目标地址。IPv6 地址需用 [] 括起来。

UDP 转发

将一个 IPv6 地址的 UDP 端口流量转发到另一个地址。

场景: 将本机 5353 端口的 UDP 流量,转发到 [2001:db8::2]:53

bash
socat UDP6-LISTEN:5353,fork,reuseaddr UDP6:[2001:db8::2]:53
  • UDP6-LISTEN:5353: 监听 UDP 5353 端口。
  • UDP6:[2001:db8::2]:53: 目标地址。

处理链路本地地址

转发到链路本地地址 (fe80::/10) 时,必须指定网络接口。

场景: 将本机 TCP 2222 端口转发到同一链路上的 fe80::... 地址的 eth0 接口。

bash
socat TCP6-LISTEN:2222,fork,reuseaddr TCP6:[fe80::aabb:ccdd:eeff:1122%eth0]:22
  • %eth0: 指定了出站流量的网络接口,对于链路本地地址这是必需的。

2. 同时转发 TCP 和 UDP

socat 一个进程只能处理一种协议。要同时监听和转发同一个端口的 TCP 和 UDP 流量,需要启动两个独立的 socat 进程。

场景: 同时转发本机 8888 端口的 TCP 和 UDP 流量到目标服务器。

bash
# 启动 TCP 转发进程 (后台运行)
nohup socat TCP6-LISTEN:8888,fork,reuseaddr TCP6:[2001:db8:2::2]:9999 &

# 启动 UDP 转发进程 (后台运行)
nohup socat UDP6-LISTEN:8888,fork,reuseaddr UDP6:[2001:db8:2::2]:9999 &

3. 持久化与服务化管理

手动运行 nohup 不利于长期管理。在生产环境中,应将 socat 进程作为系统服务来管理,以便实现开机自启、故障自动重启和标准化的启停操作。

方法一:使用 Systemd (推荐)

这是现代 Linux 发行版的标准做法。我们可以为 TCP 和 UDP 分别创建服务文件。

1. TCP 服务文件 (/etc/systemd/system/socat-tcp-forward.service)

ini
[Unit]
Description=Socat TCP Forwarder
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/socat TCP6-LISTEN:8888,fork,reuseaddr TCP6:[2001:db8:2::2]:9999
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

2. UDP 服务文件 (/etc/systemd/system/socat-udp-forward.service)

ini
[Unit]
Description=Socat UDP Forwarder
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/socat UDP6-LISTEN:8888,fork,reuseaddr UDP6:[2001:db8:2::2]:9999
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

3. 管理命令

bash
# 重新加载 systemd 配置
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start socat-tcp-forward.service
sudo systemctl start socat-udp-forward.service

# 查看状态
sudo systemctl status socat-tcp-forward.service

# 设置开机自启
sudo systemctl enable socat-tcp-forward.service
sudo systemctl enable socat-udp-forward.service

# 停止服务
sudo systemctl stop socat-tcp-forward.service

4. 高级管理:基于配置文件的批量转发

对于需要管理大量转发规则的场景,最佳实践是创建一个中央配置文件和一个管理脚本。

第 1 步:创建配置文件 (/etc/socat.conf)

此文件用于定义所有转发规则,每行一条。

bash
# 使用 sudo 创建文件
sudo nano /etc/socat.conf

文件格式与示例内容:

ini
# 格式: <协议> <监听端口> <目标IPv6> <目标端口>
# 协议可以是 tcp6 或 udp6

# 规则 1: 转发 SSH 流量 (TCP)
tcp6 2222 2001:db8:cafe::1 22

# 规则 2: 转发 Web 流量 (TCP)
tcp6 8080 2001:db8:dead::2 80

# 规则 3: 转发 DNS 流量 (UDP)
udp6 5353 2001:db8:ffff::3 53

# 规则 4: 转发到链路本地地址
tcp6 9000 fe80::aabb:ccdd:eeff:1122%eth0 9000

第 2 步:创建管理脚本 (socat_manager.sh)

这个脚本负责读取配置文件并执行相应的操作。

bash
# 将脚本放在 /usr/local/bin/ 目录下
sudo nano /usr/local/bin/socat_manager.sh

脚本内容:

sh
#!/bin/bash

CONFIG_FILE="/etc/socat.conf"
PID_DIR="/var/run/socat_manager"
SOCAT_CMD=$(which socat)

if [ "$EUID" -ne 0 ]; then echo "错误: 请使用 sudo 或 root 权限运行此脚本。"; exit 1; fi
if [ -z "$SOCAT_CMD" ]; then echo "错误: 未找到 socat 命令。"; exit 1; fi
if [ ! -f "$CONFIG_FILE" ]; then echo "错误: 配置文件 $CONFIG_FILE 不存在。"; exit 1; fi
if ! grep -q -v '^\s*#' "$CONFIG_FILE"; then
    echo "提示: 配置文件 $CONFIG_FILE 为空或只包含注释,没有规则需要处理。"
    exit 0
fi
mkdir -p "$PID_DIR"

process_rules() {
    local callback=$1
    while IFS= read -r line || [[ -n "$line" ]]; do
        [[ -z "$line" || "$line" =~ ^\s*# ]] && continue
        $callback $line
    done < <(grep -v '^\s*#' "$CONFIG_FILE")
}

start_rule() {
    local proto=$1 port=$2 target_ip=$3 target_port=$4
    local proto_upper; proto_upper=$(echo "$proto" | tr '[:lower:]' '[:upper:]')
    local rule_id="${proto}-${port}"
    local pid_file="$PID_DIR/${rule_id}.pid"

    if [ -f "$pid_file" ] && ps -p "$(cat "$pid_file")" > /dev/null; then
        echo "[已运行] 规则 ${rule_id} (PID: $(cat "$pid_file"))"
        return
    fi
    
    local target_address
    if [[ "$target_ip" == *":"* ]]; then
        target_address="[${target_ip}]:${target_port}"
    else
        target_address="${target_ip}:${target_port}"
    fi

    local cmd="$SOCAT_CMD ${proto_upper}-LISTEN:${port},fork,reuseaddr ${proto_upper}:${target_address}"
    
    nohup $cmd > /dev/null 2>&1 &
    local pid=$!

    sleep 0.1
    if ps -p "$pid" > /dev/null; then
        echo "$pid" > "$pid_file"
        echo "[已启动] 规则 ${rule_id} -> ${target_address} (PID: $pid)"
    else
        echo "[启动失败] 规则 ${rule_id} -> ${target_address}。请检查端口是否被占用或配置是否正确。"
    fi
}

stop_rule() {
    local rule_id=$1
    local pid_file="$PID_DIR/${rule_id}.pid"
    if [ ! -f "$pid_file" ]; then
        echo "[已停止] 规则 ${rule_id} (未找到PID文件)"
        return
    fi
    
    local pid; pid=$(cat "$pid_file")
    if [ -z "$pid" ]; then
        rm -f "$pid_file"
        return
    fi

    if ps -p "$pid" > /dev/null; then
        kill "$pid"
        echo "[已发送停止信号] 规则 ${rule_id} (PID: $pid)"
    else
        echo "[已失效] 规则 ${rule_id} 的进程 (PID: $pid) 已不存在, 清理PID文件。"
        rm -f "$pid_file"
    fi
}

list_rule() {
    local proto=$1 port=$2 target_ip=$3 target_port=$4
    local rule_id="${proto}-${port}"
    local pid_file="$PID_DIR/${rule_id}.pid"
    
    local target_address
    if [[ "$target_ip" == *":"* ]]; then
        target_address="[${target_ip}]:${target_port}"
    else
        target_address="${target_ip}:${target_port}"
    fi
    
    if [ -f "$pid_file" ] && ps -p "$(cat "$pid_file")" > /dev/null; then
        echo -e "\e[32m[运行中]\e[0m ${proto} 端口 ${port} -> ${target_address} (PID: $(cat "$pid_file"))"
    else
        echo -e "\e[31m[已停止]\e[0m ${proto} 端口 ${port} -> ${target_address}"
    fi
}

case "$1" in
    start)
        echo "--- 正在启动所有转发规则 ---"
        process_rules start_rule
        echo "--- 启动完成 ---"
        ;;

    stop)
        echo "--- 正在停止所有转发进程 ---"
        pids_to_kill=()
        for pf in "$PID_DIR"/*.pid; do
            [ -e "$pf" ] || continue
            pid=$(cat "$pf")
            if [ -n "$pid" ] && ps -p "$pid" > /dev/null; then
                pids_to_kill+=("$pid")
                kill "$pid"
            fi
        done

        if [ ${#pids_to_kill[@]} -eq 0 ]; then
            echo "没有正在运行的 socat 进程需要停止。"
        else
            echo "已向 ${#pids_to_kill[@]} 个进程发送 SIGTERM 信号,等待它们退出..."
            timeout=10
            while [ $timeout -gt 0 ]; do
                still_running=0
                for pid in "${pids_to_kill[@]}"; do
                    if ps -p "$pid" > /dev/null; then
                        still_running=1
                        break
                    fi
                done
                if [ $still_running -eq 0 ]; then
                    echo "所有进程已成功停止。"
                    break
                fi
                sleep 1
                timeout=$((timeout - 1))
            done

            if [ $still_running -ne 0 ]; then
                echo "警告:部分进程在10秒后仍未退出,将发送 SIGKILL 强制终止。"
                for pid in "${pids_to_kill[@]}"; do
                    if ps -p "$pid" > /dev/null; then
                        kill -9 "$pid"
                    fi
                done
            fi
        fi

        echo "清理所有PID文件..."
        rm -f "$PID_DIR"/*.pid
        echo "--- 停止完成 ---"
        ;;
    
    status|list)
        echo "--- 当前转发规则状态 ---"
        process_rules list_rule
        echo "--- 状态列表结束 ---"
        ;;
    
    restart)
        $0 stop
        echo "等待1秒后重启..."
        sleep 1
        $0 start
        ;;
    
    *)
        echo "用法: sudo $(basename "$0") {start|stop|restart|status|list}"
        exit 1
        ;;
esac

exit 0

第 3 步:如何使用

  1. 添加执行权限

    bash
    sudo chmod +x /usr/local/bin/socat_manager.sh
  2. 管理命令

    bash
    # 启动配置文件中定义的所有转发
    sudo socat_manager.sh start
    
    # 列出所有规则的当前状态
    sudo socat_manager.sh status
    
    # 停止所有正在运行的转发
    sudo socat_manager.sh stop
    
    # 重启所有转发
    sudo socat_manager.sh restart