平台支持:在 RHEL / CentOS / Rocky / Alma 上部署¶
本项目最初只支持 Debian/Ubuntu。经过在 Rocky Linux 10 上的完整适配,现在 安装脚本会自动识别发行版并走对应逻辑。本文记录平台差异、原理与踩坑。
平台矩阵¶
| 项目 | Debian 12/13、Ubuntu 22.04+ | RHEL / CentOS / Rocky / Alma / Fedora |
|---|---|---|
| 包管理器 | apt-get |
dnf |
redsocks |
发行版软件包(自带 systemd unit) | 仓库无包 → 源码编译 release-0.5 |
redsocks 运行方式 |
daemon = on(fork 后台) |
daemon = off(前台)+ Type=simple |
| 数据泵 | splice 默认开 |
splice = off(buffer pump) |
| unbound 配置目录 | /etc/unbound/unbound.conf.d/ |
/etc/unbound/conf.d/ |
| DNS 接管 | systemd-resolved stub / nmcli | NetworkManager (nmcli) |
| SELinux | 一般不启用 | 常为 Enforcing,需处理端口标签 |
nft |
/usr/sbin/nft |
/usr/sbin/nft |
unbound/dnsmasq在 RHEL 系的 AppStream 仓库中都有,无需 EPEL; 只有redsocks需要编译。
安装(一行)¶
git clone https://github.com/chenxianlong/redsocks-transparent-proxy
sudo bash redsocks-transparent-proxy/scripts/install.sh --proxy HOST:PORT --type http-connect
脚本会自动:dnf 安装依赖 → 编译 redsocks → 生成 systemd unit → 处理 SELinux →
启动 redsocks/nftables/unbound/dnsmasq → 切换系统 DNS。
三个必须知道的坑¶
1) redsocks 的 daemon = on 在 systemd 下会“假死”¶
redsocks 在 daemon = on 时会 先 setuid 再 fork。在 systemd 下子进程可能
在 daemonize 过程中退出,于是 redsocks.service 卡在 activating,没有进程、没有
PID 文件:
解法:前台运行 + 生成的 unit:
配置里对应 daemon = off;。脚本在 RHEL 系会自动这样生成。
2) redsocks_evbuffer_readline() 吞掉 CONNECT 响应的空行(致命)¶
redsocks 0.5 的代码:
#if _EVENT_NUMERIC_VERSION >= 0x02000000
return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF);
#else
return evbuffer_readline(buf); // 旧的、已废弃
#endif
在部分 libevent 2.1 头文件里 _EVENT_NUMERIC_VERSION 未定义,于是走了旧的
evbuffer_readline()。它会把 HTTP/1.0 200 Connection established\r\n\r\n 里的
两个 CRLF 一次性吃掉,导致 http-connect 永远等不到“空行”而卡死:
- 现象:
redsocks日志只有accepted,之后 20s 超时断开;curl得到HTTP 000。 strace可见:已向代理发出CONNECT、已收到200,但客户端先发的 TLS ClientHello 一直没被转发。
解法:强制使用 evbuffer_readln(EVBUFFER_EOL_CRLF)。补丁见
scripts/patches/redsocks-0.5-evbuffer-readline.patch,
scripts/redsocks-build.sh 在编译前自动应用。
同时 RHEL 系默认 splice = off(用 buffer pump),进一步规避握手期数据处理差异。
3) SELinux 默认禁止 unbound 绑定非 53 端口¶
分流设计里 unbound 监听 5353,但 unbound 以 named_t 运行,只被允许绑定
dns_port_t(53、853):
error: can't bind socket: Permission denied for 127.0.0.1 port 5353
avc: denied { name_bind } for ... scontext=system_u:system_r:named_t:s0
tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket
解法(持久化):
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t dns_port_t -p tcp 5353
sudo semanage port -a -t dns_port_t -p udp 5353
脚本在 SELinux 为 Enforcing 时自动执行。
手动部署步骤(Rocky 10 实战)¶
想手工复现或用其它初始化系统时,按此顺序:
# 1. 依赖
dnf install -y gcc make libevent-devel patch tar gzip unbound dnsmasq bind-utils
# 2. 编译 redsocks(带 CRLF 补丁)
curl -L https://github.com/darkk/redsocks/archive/refs/tags/release-0.5.tar.gz | tar xz
cd redsocks-release-0.5
patch -p1 < /path/to/scripts/patches/redsocks-0.5-evbuffer-readline.patch
make -j"$(nproc)" && install -m0755 redsocks /usr/sbin/redsocks
groupadd -r redsocks; useradd -r -g redsocks -s /sbin/nologin -d /nonexistent redsocks
# 3. redsocks 配置(注意 daemon=off、splice=off、type=http-connect)
cat >/etc/redsocks.conf <<'EOF'
base { log_info=on; log="file:/var/log/redsocks.log"; daemon=off;
user=redsocks; group=redsocks; redirector=iptables;
rlimit_nofile=65536; redsocks_conn_max=8192; }
redsocks { local_ip=127.0.0.1; local_port=12345;
ip=PROXY_IP; port=PROXY_PORT; type=http-connect; splice=off; }
EOF
# 4. systemd unit(Type=simple)
# 5. unbound 配置写入 /etc/unbound/conf.d/redsocks.conf(端口 5353,forward-tcp-upstream)
# 6. SELinux 打 5353 标签
# 7. nftables 规则(scripts/redsocks-nft.sh)
# 8. dnsmasq(53) → unbound(5353),并把系统 DNS 指向 127.0.0.1(nmcli)
完整参数化实现见 scripts/install.sh。
验证¶
# 绕过任何 env 代理,纯透明链路
curl --noproxy '*' -s -o /dev/null -w '%{http_code} %{time_total}s\n' https://www.google.com
curl --noproxy '*' -s https://ipinfo.io/ip # 应显示代理出口 IP
# DNS 分流
dig +short @127.0.0.1 www.baidu.com # 国内直连解析
dig +short @127.0.0.1 www.google.com # 真实 IP(未被污染)
# 服务与规则
systemctl status redsocks redsocks-nft unbound dnsmasq
sudo redsocks-nft show
常见附加问题¶
- 不能
ping墙外:redsocks只处理 TCP,ICMP 不代理。 - 本机无全局 IPv6:AAAA 能解析但连不通,应用会自动回落 IPv4(被代理)。
systemctl stop redsocks-nft会连带 DNS 失效:unbound 的 TCP 上游依赖该重定向; 要停就整套停,或先把/etc/resolv.conf改回国内 DNS。- 只删除
/etc/profile.d/proxy.sh之类环境变量代理不够:已在运行的进程仍持有旧变量, 需重新登录/重启才会彻底干净。