如何在不丢失原始目标地址的前提下将TCP包重定向至代理?
解决TCP流量重定向时丢失原始目标IP的问题
你遇到的核心问题很典型:普通的端口转发会把原始目标地址替换成代理本地端口,导致代理B没法知道要连接到C。下面我给你拆解解决方案,从网络配置到代理实现一步一步来:
一、先搞定网络层:让流量转发时保留原始目标信息
首先要在A或B上配置iptables,确保流量被导向代理的同时,原始目标C的IP和端口不会丢失。分两种场景:
场景1:B是A的网关(A流量先经过B)
如果能把B设为A的默认网关,直接在B上配置PREROUTING规则:
# 假设C用TLS默认端口443,代理B监听8080端口 iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
这种规则下,代理程序可以通过系统socket选项直接拿到原始目标地址。
场景2:A直接连B,但需要指向C
如果没法把B设为网关,就在A的安卓模拟器里(需要root权限)配置DNAT规则,把到C的流量重定向到B:
# 替换[C的IP]和[B的IP]为实际地址 iptables -t nat -A OUTPUT -p tcp -d [C的IP] --dport 443 -j DNAT --to-destination [B的IP]:8080
这种方式下,代理拿到的连接目标是自己的8080,但可以通过后续的代码逻辑获取原始目标。
二、实现支持透明转发的代理程序
接下来需要一个能读取原始目标地址、并完成双向流量转发的代理。这里给你一个Python示例,专门处理这种纯TCP的透明转发:
import socket import threading import os # 代理监听端口 PROXY_PORT = 8080 def handle_client(client_socket): try: # 获取原始目标地址(仅在iptables REDIRECT规则下生效) original_dst = client_socket.getsockopt(socket.SOL_IP, 80, 16) # 解析IP和端口 dst_ip = socket.inet_ntoa(original_dst[:4]) dst_port = int.from_bytes(original_dst[4:6], byteorder='big') print(f"收到连接,原始目标: {dst_ip}:{dst_port}") # 连接到原始目标C target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) target_socket.connect((dst_ip, dst_port)) # 双向流量转发函数 def forward(src, dst): while True: data = src.recv(4096) if not data: break dst.sendall(data) src.close() dst.close() # 启动两个线程分别处理客户端→目标、目标→客户端的流量 threading.Thread(target=forward, args=(client_socket, target_socket)).start() threading.Thread(target=forward, args=(target_socket, client_socket)).start() except Exception as e: print(f"处理连接出错: {e}") client_socket.close() def main(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', PROXY_PORT)) server_socket.listen(5) print(f"代理已启动,监听0.0.0.0:{PROXY_PORT}") while True: client_sock, addr = server_socket.accept() print(f"来自{addr}的新连接") threading.Thread(target=handle_client, args=(client_sock,)).start() if __name__ == "__main__": # 获取SO_ORIGINAL_DST需要root权限 if os.geteuid() != 0: print("请以root身份运行此程序") exit(1) main()
代码关键点:
- 必须用root权限运行,否则无法读取原始目标地址
- 自动解析iptables转发过来的原始目标,无需手动配置C的地址
- 双向线程确保TLS数据能在A和C之间正常传输
三、验证流程
- 在B上以root身份运行上述代理程序
- 配置好对应的iptables规则(A或B上的)
- 让A发起到C的TLS连接,此时代理会打印原始目标地址,自动建立到C的连接并转发流量
额外小贴士
- 如果需要做TLS中间人解密(比如查看流量内容),可以在代理程序中加入
ssl模块的逻辑,分别与A、C建立TLS连接,再转发解密后的数据 - 不想自己写代码的话,也可以用
socat工具实现透明转发:
同样需要配合iptables规则和root权限socat TCP-LISTEN:8080,reuseaddr,fork PROXY:localhost:[C的IP]:443,proxyport=8080
内容的提问来源于stack exchange,提问作者Sixkay
相关产品推荐
相关产品推荐

