Wankupi's Website

PPCA-Network 代理折腾浅记

在 2023 年暑假的PPCA中,我与7位ACM班同学一起参加了 Network组,收获颇丰。 特写此文,记录为期六周的学习成果。

我们的基础任务是 Socks5 协议服务端, 此后有若干选题,写6分可以得到PPCA的100%分数,写9分可以得到110%的分数。

  • 透明代理 4’
  • 代理客户端 1’
    • 分流规则
      • 按 socks 地址分流 1’
      • 按域名分流
        • HTTP 分流 1’
        • TLS 分流 1’
      • 按程序分流 1’
    • 多级代理 2’
  • UDP 代理 1’
  • TLS 劫持 2’
  • HTTP 捕获/修改/重放 3’
  • 反向代理
    • TCP 反向代理 1’
    • HTTP 反向代理 1’

由于不能使用c系列语言的要求,以及考虑到网络对于并行处理的需要,我选择了使用 go 语言。 如果能使用c++的话, 我或许会考虑使用epoll模型, 可惜不行, 我又懒得拿go去写如此贴近系统的事情(贴近底层为什么不用c呢...)。 事实上很多时候我都是先拿c++写个demo再换成go...

Socks5 : tcp 服务端

比较简单,照着 RFC1928 写就好。这个时候的主要难点在于学习 go 语言。

文档有中文翻译。

socks5 可以代理 tcp, udp 和反向tcp代理。

代理tcp只是在tcp字节流前面加了一个包头。 客户端向代理服务端发送自己想连谁, 然后服务端自己去连接, 连接好后,服务端直接交换tcp的字节流, 此后客户端相当于直接与远端通信。

比较坑的点是tcp可能有分包和粘包。

Socks5 : udp 服务端

udp中有些部分在 RFC 文档中语焉不详。

首先客户端通过tcp告诉代理需要建立udp, 同时告诉代理自己以后发送udp时会使用什么端口。

然后服务端需要开始监听两个udp端口。 这里就体现出了udp是无连接的性质。 监听之后,可以直接收数据或向某个地址发送数据。 tcp是做不到这一点的,必须有 acceptconnect 的过程来建立连接。

这里有一个有意思的问题。 我们知道,比如tcp connect 时,内核会帮我们找到一个可用的端口,udp也是如此。 那么,如何在发送数据前知道自己发送数据所使用的端口? 我这才发现原来 connect 或者 sendto 之前,也是可以调用 bind 的。 对于udp而言,如果 bind 时指定的端口为 0,仍是由系统分配端口,但是可以起到固定发送端口的作用。

一个坑点是代理服务器收到远端的回信时,应该将该信息转发给客户端。 在转发时,需要加上一个报头。 在这个报头中,填入的地址是远端的地址。 文档上对这点没有说清楚,不过想一想这样也是很自然的, 这样客户端就可以在同一个代理通道内, 对多个远端地址发送udp信息,同时收取他们的信息, 收取时为了知道是谁发来的,自然需要代理服务端告诉自己远端的地址。

Socks5: tcp 客户端

tcp连接后发送下报头就可以了。

透明代理

我早先是研究过透明代理的,只是一直没有搞明白,也没配成功。

正好开始写网络时,我原来使用的前端 qv2ray 滚挂了, 换成了 v2raya, 正好发现 v2raya 在linux上支持透明代理(但是不支持系统代理qwq)。

言归正传,目前linux上的透明代理有两个实现方式。

  • tproxy模式: 路由表 + 防火墙 + 代理软件
  • tun模式: 路由表 + tun设备 + 代理软件

还有一个 redirect 方式,它和tproxy模式相似,但是不支持udp。

在PPCA中,对于两个方法都有所探索, 也都写过一些代码, 但只实现了一个鲁棒性不足的 tun设备代理。

Tproxy 模式

这一模式的需要写的代码会相对较少,但需要探索的内容比较多。

tproxy模式的基本原理是:

  • 启用内核模块 nft_tproxy, 防火墙能将tcp/udp数据包转发给某个指定的地址,而保持数据包的来源地址和目的地址,使得可以通过一些方式查询。

  • 代理程序监听某地址,它会直接收到tcp连接或udp报文。 通过系统调用获取原本的目标地址后, 进行代理或直连。

  • 需要路由表配合,才能让数据包被防火墙转发以及防止回环。

具体还有一些细节问题:

  • 代理程序监听时设置相关选项需要相应权限。
  • iptables / nftables 学习成本较高。
  • 通过判断 mark 或 gid 等方式标记代理程序发出的数据,防止回环。

参考:

Tun模式

tun/tap 是 linux 上的虚拟网络设备。

tun模式的原理是:

  • 代理程序打开某个tun设备,读取的数据是原始ip数据包。

  • 对于直连,可以直接通过原始套接字原样发出。

  • 对于非直连,拆包后走代理发出。

  • 路由表配合

这里也有一些问题:

  • 对于tcp流量,需要手动管理连接!! 比如三次握手四次挥手,比如回复ACK,由程序自己完成。 要手写或者用别人的库。 当然,由于在同一台电脑上,我们可以假设不会有丢包的事情发生……