Skip to content

SOCKS5 和 HTTP 代理

Xray-core 实现了 SOCKS4/4a/5 和 HTTP/HTTPS 代理协议,同时作为入站(本地代理服务器)和出站(上游代理客户端)处理器。SOCKS 入站还可以自动检测并将 HTTP 请求转发给内置的 HTTP 处理器。

SOCKS5 协议

概述

  • 方向:入站 + 出站
  • 传输:TCP + UDP
  • 认证:无或用户名/密码(RFC 1929)
  • 命令:CONNECT(TCP)、UDP ASSOCIATE
  • 标准:RFC 1928(SOCKS5)、RFC 1929(用户名/密码认证)

线路格式

SOCKS5 握手

mermaid
sequenceDiagram
    participant C as 客户端
    participant S as 服务端

    C->>S: Version(1B) + NMethods(1B) + Methods(NB)
    S->>C: Version(1B) + ChosenMethod(1B)

    alt 用户名/密码认证
        C->>S: AuthVer(1B) + ULen(1B) + User + PLen(1B) + Pass
        S->>C: AuthVer(1B) + Status(1B)
    end

    C->>S: Version(1B) + Cmd(1B) + Rsv(1B) + Address
    S->>C: Version(1B) + Rep(1B) + Rsv(1B) + BndAddress

认证协商

Client -> Server:
+-----+----------+----------+
| VER | NMETHODS | METHODS  |
| 1B  | 1B       | 1-255B   |
+-----+----------+----------+
  0x05

Server -> Client:
+-----+--------+
| VER | METHOD |
| 1B  | 1B     |
+-----+--------+
  0x05

方法值

方法
0x00无需认证
0x02用户名/密码
0xFF无可接受的方法

源码:proxy/socks/protocol.go:27-33

用户名/密码认证(RFC 1929)

Client -> Server:
+-----+------+----------+------+----------+
| VER | ULEN | UNAME    | PLEN | PASSWD   |
| 1B  | 1B   | 1-255B   | 1B   | 1-255B   |
+-----+------+----------+------+----------+
  0x01

Server -> Client:
+-----+--------+
| VER | STATUS |
| 1B  | 1B     |
+-----+--------+
  0x01  0x00=ok

源码:proxy/socks/protocol.go:232-265

SOCKS5 请求

+-----+-----+------+----------+----------+------+
| VER | CMD | RSV  | ATYP     | DST.ADDR | PORT |
| 1B  | 1B  | 1B   | 1B       | variable | 2B   |
+-----+-----+------+----------+----------+------+
  0x05              0x00

命令

命令支持状态
0x01TCP CONNECT
0x02TCP BIND否(返回 0x07)
0x03UDP ASSOCIATE是(如果启用)
0xF0Tor Resolve按 CONNECT 处理
0xF1Tor Resolve PTR按 CONNECT 处理

源码:proxy/socks/protocol.go:15-22proxy/socks/protocol.go:164-180

SOCKS5 响应

+-----+-----+------+----------+----------+------+
| VER | REP | RSV  | ATYP     | BND.ADDR | PORT |
| 1B  | 1B  | 1B   | 1B       | variable | 2B   |
+-----+-----+------+----------+----------+------+
  0x05       0x00

对于 UDP ASSOCIATE,BND.ADDRBND.PORT 指示 UDP 中继端点。

源码:proxy/socks/protocol.go:300-310

SOCKS5 UDP 数据报

+------+------+------+----------+----------+------+---------+
| RSV  | RSV  | FRAG | ATYP     | DST.ADDR | PORT | DATA    |
| 1B   | 1B   | 1B   | 1B       | variable | 2B   | ...     |
+------+------+------+----------+----------+------+---------+
  0x00   0x00

源码:proxy/socks/protocol.go:324-363

分片字节必须为 0x00 — 不支持分片的 UDP:

go
if packet.Byte(2) != 0 /* fragments */ {
    return nil, errors.New("discarding fragmented payload.")
}

源码:proxy/socks/protocol.go:334-336

SOCKS4/4a 支持

入站处理器还根据版本字节处理 SOCKS4/4a:

go
switch version {
case socks4Version:  // 0x04
    return s.handshake4(cmd, reader, writer)
case socks5Version:  // 0x05
    return s.handshake5(cmd, reader, writer)
}

源码:proxy/socks/protocol.go:222-230

SOCKS4a 通过域名支持扩展了 SOCKS4:如果 IP 以 0x00 开头,则在用户 ID 空终止符之后跟随域名。

源码:proxy/socks/protocol.go:72-78

入站(服务端)

文件:proxy/socks/server.go

自动检测:服务端读取第一个字节以区分 SOCKS 和 HTTP:

go
if firstbyte[0] != 5 && firstbyte[0] != 4 {
    // Not SOCKS, try HTTP
    return s.httpServer.ProcessWithFirstbyte(ctx, network, conn, dispatcher, firstbyte...)
}

源码:proxy/socks/server.go:92-95

UDP Associate 流程

  1. 客户端发送命令 0x03 的 CONNECT 请求
  2. 服务端响应其 UDP 中继地址/端口
  3. 客户端向该地址发送 UDP 数据报
  4. 服务端在 UDP 网络上接收,解码 SOCKS5 UDP 头部,进行分发
  5. TCP 连接保持打开作为保活信号
go
func (*Server) handleUDP(c io.Reader) error {
    // Wait until client closes the TCP connection
    return common.Error2(io.Copy(buf.DiscardBytes, c))
}

源码:proxy/socks/server.go:182-186

UDP 过滤器:当启用认证时,只有已建立 TCP UDP ASSOCIATE 连接的客户端才被允许发送 UDP 数据包:

go
if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {
    return nil
}

源码:proxy/socks/server.go:189-192

出站(客户端)

文件:proxy/socks/client.go

客户端与上游服务器执行完整的 SOCKS5 握手:

go
func ClientHandshake(request *RequestHeader, reader io.Reader, writer io.Writer) (*RequestHeader, error) {
    // 1. Send auth method selection
    // 2. Read server's chosen method
    // 3. If password auth, send credentials
    // 4. Send CONNECT or UDP ASSOCIATE request
    // 5. Read response
}

源码:proxy/socks/protocol.go:421-515

对于 UDP,客户端打开一个独立的 UDP 连接到 SOCKS5 UDP ASSOCIATE 响应返回的地址。

源码:proxy/socks/client.go:146-163


HTTP 代理协议

概述

  • 方向:入站 + 出站
  • 传输:TCP、UNIX 套接字
  • 认证:HTTP Basic Auth(Proxy-Authorization 头)
  • 方法:CONNECT(隧道)、普通 HTTP(代理)
  • UDP:不支持

HTTP CONNECT(隧道)

mermaid
sequenceDiagram
    participant C as 客户端
    participant P as 代理
    participant S as 服务端

    C->>P: CONNECT host:port HTTP/1.1
    P->>S: TCP 连接到 host:port
    S->>P: 已连接
    P->>C: HTTP/1.1 200 Connection established
    C->>P: [原始字节]
    P->>S: [原始字节]
    S->>P: [原始字节]
    P->>C: [原始字节]

源码:proxy/http/server.go:176-202

普通 HTTP 代理

对于非 CONNECT 请求,服务端:

  1. 使用 Go 的 http.ReadRequest() 解析完整的 HTTP 请求
  2. Host 头或 URL 中提取目标地址
  3. 将请求分发到出站
  4. 支持同一连接上的 keep-alive 序列请求
go
keepAlive := strings.TrimSpace(strings.ToLower(
    request.Header.Get("Proxy-Connection"))) == "keep-alive"

源码:proxy/http/server.go:163

认证

通过 Proxy-Authorization 头进行 Basic 认证检查:

go
func parseBasicAuth(auth string) (username, password string, ok bool) {
    const prefix = "Basic "
    c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
    cs := string(c)
    s := strings.IndexByte(cs, ':')
    return cs[:s], cs[s+1:], true
}

源码:proxy/http/server.go:63-78

认证失败返回 407 Proxy Authentication Required,并附带 Proxy-Authenticate: Basic realm="proxy" 头。

源码:proxy/http/server.go:128

HTTP/2 支持(出站)

HTTP 出站客户端在通过上游 HTTP 代理连接时支持 HTTP/1.1 和 HTTP/2:

go
switch nextProto {
case "", "http/1.1":
    return connectHTTP1(rawConn)
case "h2":
    t := http2.Transport{}
    h2clientConn, err := t.NewClientConn(rawConn)
    return connectHTTP2(rawConn, h2clientConn)
}

源码:proxy/http/client.go:320-332

H2 连接按目标地址缓存以实现多路复用:

go
var cachedH2Conns map[net.Destination]h2Conn

源码:proxy/http/client.go:47-48

1xx 响应处理

服务端正确处理 HTTP 1xx 中间响应(如 100 Continue),在读取实际响应之前将它们转发给客户端:

go
if strings.HasPrefix(status, "1") {
    // Read until \r\n\r\n, forward to client
    writer.Write(ResponseHeader1xx)
}
return http.ReadResponse(r, req)

源码:proxy/http/server.go:316-344

入站(服务端)

文件:proxy/http/server.go

关键行为:

  • 支持 AllowTransparent 模式用于透明 HTTP 代理(不需要绝对 URL)
  • 按 HTTP 规范移除逐跳头
  • 如果未提供 User-Agent 则设置为空字符串(防止 Go 默认值)
  • 通过循环处理 keep-alive 连接(goto Start

出站(客户端)

文件:proxy/http/client.go

客户端的工作流程:

  1. 从 link reader 读取第一个载荷
  2. 与上游代理建立 HTTP CONNECT 隧道
  3. 在隧道建立后立即发送第一个载荷
  4. 支持通过配置自定义头部(支持 Go 模板用于 Source/Target
go
data := struct {
    Source net.Destination
    Target net.Destination
}{...}
tmpl.Execute(&buf, data)

源码:proxy/http/client.go:180-203

实现说明

  1. SOCKS/HTTP 自动检测:SOCKS 入站读取第一个字节。如果不是 0x040x05,则将连接转发给嵌入的 HTTP 服务器。这允许单个端口同时提供 SOCKS5 和 HTTP 代理服务。

源码:proxy/socks/server.go:92-95

  1. CanSpliceCopy 行为:SOCKS 和 HTTP 初始设置 CanSpliceCopy = 2。在协议握手完成后(对于 TCP CONNECT),升级为 1,在原始 TCP 流上启用零拷贝。如果在传输层检测到 TLS,则从 3 开始。

  2. SOCKS4 认证拒绝:SOCKS4 不支持密码认证。如果服务端要求认证,SOCKS4 连接会被立即拒绝。

源码:proxy/socks/protocol.go:50-53

  1. HTTP 代理不支持 UDP:HTTP 出站明确拒绝 UDP 目标。

源码:proxy/http/client.go:80-82

  1. DispatchLink 与 Dispatch:对于 TCP CONNECT 和 SOCKS TCP,入站使用 dispatcher.DispatchLink() 阻塞直到连接完成,而不是使用返回 Link 进行手动拷贝的 Dispatch()。这简化了生命周期管理。

源码:proxy/socks/server.go:163-169

用于重新实现目的的技术分析。