个人觉得需要深入学习的几个编程语言,即 C/C++、Java、Shell、Perl,当然还有 Linux,我还琢磨着什么时候将电脑的系统换为 Linux 呢。
深入学习的几个语言
- C 语言(网络编程)
- Java 语言(吃饭用的)
- Shell(sed、awk、perl)
关于透明代理
Linux 中有两种透明代理方式(只讨论 TCP 和 UDP):REDIRECT、TPROXY。
- REDIRECT:本质还是 DNAT,即修改数据包的 DST_ADDR、DST_PORT。
- TPROXY:不涉及 NAT,但要求监听套接字设置
IP_TRANSPARENT
选项。
REDIRECT 方式注定只能透明代理 TCP,因为 UDP 在经过 DNAT 之后,无法使用 SO_ORIGINAL_DST
套接字选项来获取原始的目的地址和目的端口。目前的解决办法是,通过 TPROXY 来代理 UDP,使用此方法代理 UDP 时,需要先为监听套接字设置 IP_RECVORIGDSTADDR
选项,然后在收到 UDP 包之后(使用 recvmsg()
而不是 recvfrom()
),获取原始目的地址信息,进行透明代理。
解释一下 IP_TRANSPARENT
选项的作用,它的作用有两个:
- 可以让套接字进行透明代理
- 可以让套接字绑定非本地地址
所以要使用 TPROXY 进行透明代理,IP_TRANSPARENT
套接字选项是必不可少的。
REDIRECT TCP 透明代理思路
- 首先,像往常一样,监听一个 TCP 端口(如 60080),然后等待新连接的到来。
- 设置 iptables REDIRECT 规则,将需要代理的 TCP 流量重定向至该监听端口。
- 新连接到来之后,使用
SO_ORIGINAL_DST
socket 选项获取原始目的地址信息。 - 新建 TCP 套接字,地址为刚才的真实目的地址,发起 TCP 连接,然后转发数据。
TPROXY TCP 透明代理思路
- 首先,创建监听套接字,设置
IP_TRANSPARENT
选项,然后再绑定要监听地址。 - 设置 iptables TPROXY 规则,将需要代理的 TCP 流量路由到本地的监听端口。
- 新连接到来之后,通过
getsockname()
获取目的地址(即 self-side 地址)。 - 新建 TCP 套接字,目的地址为刚才获取的目的地址,发起 TCP 连接,转发数据。
TPROXY UDP 透明代理思路
- 创建套接字,设置
IP_TRANSPARENT
、IP_RECVORIGDSTADDR
选项,绑定要监听地址。 - 设置 iptables TPROXY 规则,将需要代理的 UDP 流量重定向或路由到本地监听端口。
- 使用
recvmsg()
接收 UDP 包,获取原始地址信息,然后发送该 UDP 数据给目的主机。 - 收到响应包后创建新套接字并设置
IP_TRANSPARENT
选项,bind 原始地址再发给客户端。
但是,如果按照上面的 TPROXY UDP 实现思路,那么它将只能用于透明代理 request-response 类型的 UDP 上层协议,如 DNS,但是类似 QUIC 这样的非 request-response 类型的 UDP 上层协议会出现问题,因为端口号变了,导致它们依靠端口号来识别用户的策略失效,即 QUIC 协议无法正常工作。
解决方法:在代理程序内部维护一个 hash table,手动维护一个状态信息,防止端口变化。
Full-Cone NAT(完全锥形 NAT)
一旦内部地址 Iaddr:Iport
成功映射到外部地址 Eaddr:Eport
,那么:
所有 src=Iaddr:Iport
的 UDP 包均被 SNAT 替换为 src=Eaddr:Eport
所有 dst=Eaddr:Eport
的 UDP 包均被 DNAT 替换为 dst=Iaddr:Iport
Iaddr:Iport
不需要网关操心,它由内网主机进程自己保持和管理。Eaddr:Eport
须保持打开状态,因为要随时准备接收外部发来的包。
注意,NAT 环境下,必须由 Iaddr:Iport
先发送数据包才能打开 UDP 隧道!
tls-client 内部的 hashtable 数据结构
key(iaddr:iport
) -> value(eport
)
tls-server 内部的 hashtable 数据结构
key(eport
) -> value(esock
)
tls-client 发送的 UDP 请求格式iaddr:iport:raddr:rport:eport:data\r\n
tls-server 发送的 UDP 响应格式iaddr:iport:raddr:rport:eport:data\r\n