85、SOCKS5代理:SOCKS5协议、代理服务器实现、客户端使用

说到网络编程,代理是个绕不开的话题。我个人觉得,SOCKS5 是其中最实用、也最优雅的一种代理协议。它不像 HTTP 代理那样只懂 HTTP 请求,SOCKS5 工作在更底层,说白了,它就是个“网络数据搬运工”——不管你是 TCP 还是 UDP,它都能帮你转发。

我记得刚入行那会儿,公司有个项目需要让内网机器访问外网数据库。防火墙只开放了 1080 端口,怎么办?我第一反应就是搭个 SOCKS5 代理。嗯,从那以后,我对这个协议就特别上心。

什么是 SOCKS5 协议?

SOCKS5 是 SOCKS 协议的第五个版本。它工作在 OSI 模型的会话层,介于传输层和应用层之间。你想想看,它不关心你传的是什么数据,只管把数据包从客户端送到目标服务器。

相比 SOCKS4,SOCKS5 多了三个重要能力:

  • 支持 UDP 代理——不仅仅是 TCP
  • 支持多种认证方式——无认证、用户名/密码、GSSAPI
  • 支持 IPv6——这个在现在越来越重要

核心要点: SOCKS5 不解析应用层数据,它只做“透传”。所以 HTTPS、FTP、SSH 这些协议都能完美通过,不需要额外配置。

SOCKS5 协议握手流程

这个流程其实不复杂,我画了一张图帮你理解。整个握手分三步走:

客户端 SOCKS5 代理 目标服务器 ① 协商认证方式 ② 返回选中的认证方式 ③ 发送连接请求(目标地址+端口) ④ 返回连接结果(成功/失败) ⑤ 开始传输数据(TCP/UDP) ⑥ 转发响应数据 代理转发 图:SOCKS5 代理握手与数据传输流程

具体来说,握手过程是这样的:

  1. 协商阶段:客户端发一个包,告诉代理“我支持这些认证方式”。代理选一个,回复客户端。
  2. 请求阶段:客户端告诉代理“我要连这个目标地址和端口”。代理去尝试连接,然后返回结果。
  3. 传输阶段:连接建立后,双方开始透传数据。代理就是个“二传手”。

小技巧: 我在调试 SOCKS5 代理时,经常用 tcpdump 抓包看握手过程。你只要看前三个字节就能判断是不是 SOCKS5——第一个字节是 0x05 就对了。

代理服务器实现要点

实现一个 SOCKS5 代理服务器,核心就是处理上面说的握手流程。我挑几个关键点说说:

1. 认证处理

最简单的场景是“无认证”。客户端发 0x05 0x01 0x00,服务器回 0x05 0x00 就完事了。但如果你要加用户名密码认证,就得按 RFC 1929 来:

// 客户端认证请求格式
// +----+------+----------+------+----------+
// |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
// +----+------+----------+------+----------+
// | 1  |  1   | 1~255    |  1   | 1~255    |
// +----+------+----------+------+----------+

// 服务器响应
// +----+--------+
// |VER | STATUS |
// +----+--------+
// | 1  |   1    |
// +----+--------+
// STATUS: 0x00 成功, 0x01 失败

我曾经踩过一个坑:用户名或密码长度字段是 无符号字节,最大 255。有人传了个 0 长度的用户名,结果我代码里没做校验,直接 memcpy 把栈给搞崩了。嗯,从那以后我对边界检查特别敏感。

2. 地址解析

SOCKS5 支持三种地址类型:

类型 ATYP 值 地址长度 说明
IPv4 0x01 4 字节 最常见的类型
域名 0x03 1+可变 第一个字节是域名长度
IPv6 0x04 16 字节 现在越来越常用

注意: 处理域名类型时,一定要先读长度字节,再读域名。我见过有人直接假设域名是固定长度的,结果解析出来的地址完全不对。

3. 数据转发

数据转发这块,说白了就是两个 socket 之间互相拷贝数据。但有个细节要注意——非阻塞 I/O。如果你用阻塞方式,一个方向堵住了,另一个方向也会卡死。

我个人习惯用 select()epoll 来做多路复用。核心逻辑就是:

// 伪代码:双向数据转发
while (1) {
    FD_ZERO(&readfds);
    FD_SET(client_fd, &readfds);
    FD_SET(remote_fd, &readfds);
    
    select(max_fd + 1, &readfds, NULL, NULL, NULL);
    
    if (FD_ISSET(client_fd, &readfds)) {
        n = read(client_fd, buf, BUFSIZ);
        if (n <= 0) break;  // 连接关闭
        write(remote_fd, buf, n);
    }
    
    if (FD_ISSET(remote_fd, &readfds)) {
        n = read(remote_fd, buf, BUFSIZ);
        if (n <= 0) break;
        write(client_fd, buf, n);
    }
}

性能提示: 缓冲区大小建议用 8192 或 16384 字节。太小了频繁系统调用,太大了浪费内存。我一般用 8192,实测在大多数场景下表现不错。

客户端如何使用 SOCKS5 代理

客户端使用 SOCKS5 代理,有两种常见方式:

方式一:应用层配置

很多软件直接支持 SOCKS5 代理设置。比如 curl:

# 使用 SOCKS5 代理访问网站
curl --socks5 127.0.0.1:1080 https://www.example.com

# 使用 SOCKS5 代理,且用户名密码认证
curl --socks5 127.0.0.1:1080 --proxy-user user:pass https://www.example.com

再比如 SSH 的 -D 参数,其实就是内置了一个 SOCKS5 代理:

# 在本地 1080 端口启动 SOCKS5 代理
ssh -D 1080 user@your-server.com

方式二:代码中集成

如果你要在自己的 C 程序里用 SOCKS5 代理,流程是这样的:

// 1. 先连接代理服务器
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, proxy_addr, proxy_addr_len);

// 2. 发送握手包
unsigned char handshake[] = {0x05, 0x01, 0x00};
send(sock, handshake, 3, 0);

// 3. 接收响应
unsigned char response[2];
recv(sock, response, 2, 0);
if (response[0] != 0x05 || response[1] != 0x00) {
    // 认证失败
    return -1;
}

// 4. 发送连接请求(以 IPv4 为例)
// VER=0x05, CMD=0x01(连接), RSV=0x00, ATYP=0x01
unsigned char request[10] = {
    0x05, 0x01, 0x00, 0x01,
    // 目标 IP(4字节)
    (target_ip >> 24) & 0xFF,
    (target_ip >> 16) & 0xFF,
    (target_ip >> 8) & 0xFF,
    target_ip & 0xFF,
    // 目标端口(2字节,网络字节序)
    (target_port >> 8) & 0xFF,
    target_port & 0xFF
};
send(sock, request, 10, 0);

// 5. 接收代理响应
unsigned char reply[10];
recv(sock, reply, 10, 0);
if (reply[1] != 0x00) {
    // 连接失败,reply[1] 是错误码
    return -1;
}

// 6. 现在 sock 已经连接到目标服务器了
// 可以直接收发数据

关键点: 代理连接成功后,你手里的 socket 就相当于直接连到了目标服务器。后续的 read/write 操作完全不需要关心代理的存在——代理在中间默默帮你转发数据。

避坑指南

做 SOCKS5 代理开发,有几个坑我帮你提前踩了:

  • UDP 关联要小心: UDP 代理需要先发一个 UDP ASSOCIATE 请求,代理会给你分配一个 UDP 端口。这个端口是动态的,别写死。
  • DNS 解析问题: 如果客户端发的是域名,代理服务器要自己去做 DNS 解析。我建议用 getaddrinfo(),它同时支持 IPv4 和 IPv6。
  • 连接超时: 代理连接目标服务器时,一定要设超时。我一般设 10 秒,超过就断开,避免资源泄漏。
  • 并发处理: 别用单线程处理多个连接。用线程池或者事件驱动模型,不然一个慢连接会拖垮整个代理。

好了,SOCKS5 代理的核心内容就这些。从协议握手到服务器实现,再到客户端使用,整个链路其实很清晰。你只要把握手流程吃透,剩下的就是 socket 编程的基本功了。


公众号:蓝海资料掘金营,微信deep3321