Appearance
socat IPv6 转发实用指南
socat 是一个功能强大的多用途网络工具,被誉为“网络工具中的瑞士军刀”。它可以在两个数据流之间建立双向通道,这些数据流可以是文件、管道、设备或网络套接字。本文档将全面介绍如何使用 socat 进行 IPv6 流量转发,从基本命令到高级的自动化管理脚本。
1. 基本用法
socat 的核心语法是将两个地址连接起来:
bash
socat [选项] <地址1> <地址2>对于 IPv6,我们主要使用 TCP6 和 UDP6 相关的地址类型。
TCP 转发
将一个 IPv6 地址的 TCP 端口流量转发到另一个地址。
场景: 将本机 8080 端口的 TCP 流量,转发到 [2001:db8::1]:80。
bash
socat TCP6-LISTEN:8080,fork,reuseaddr TCP6:[2001:db8::1]:80TCP6-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]:53UDP6-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.target2. 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.target3. 管理命令
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.service4. 高级管理:基于配置文件的批量转发
对于需要管理大量转发规则的场景,最佳实践是创建一个中央配置文件和一个管理脚本。
第 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 步:如何使用
添加执行权限
bashsudo chmod +x /usr/local/bin/socat_manager.sh管理命令
bash# 启动配置文件中定义的所有转发 sudo socat_manager.sh start # 列出所有规则的当前状态 sudo socat_manager.sh status # 停止所有正在运行的转发 sudo socat_manager.sh stop # 重启所有转发 sudo socat_manager.sh restart