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 握手
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命令:
| 值 | 命令 | 支持状态 |
|---|---|---|
0x01 | TCP CONNECT | 是 |
0x02 | TCP BIND | 否(返回 0x07) |
0x03 | UDP ASSOCIATE | 是(如果启用) |
0xF0 | Tor Resolve | 按 CONNECT 处理 |
0xF1 | Tor Resolve PTR | 按 CONNECT 处理 |
源码:proxy/socks/protocol.go:15-22、proxy/socks/protocol.go:164-180
SOCKS5 响应
+-----+-----+------+----------+----------+------+
| VER | REP | RSV | ATYP | BND.ADDR | PORT |
| 1B | 1B | 1B | 1B | variable | 2B |
+-----+-----+------+----------+----------+------+
0x05 0x00对于 UDP ASSOCIATE,BND.ADDR 和 BND.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:
if packet.Byte(2) != 0 /* fragments */ {
return nil, errors.New("discarding fragmented payload.")
}源码:proxy/socks/protocol.go:334-336
SOCKS4/4a 支持
入站处理器还根据版本字节处理 SOCKS4/4a:
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:
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 流程:
- 客户端发送命令 0x03 的 CONNECT 请求
- 服务端响应其 UDP 中继地址/端口
- 客户端向该地址发送 UDP 数据报
- 服务端在 UDP 网络上接收,解码 SOCKS5 UDP 头部,进行分发
- TCP 连接保持打开作为保活信号
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 数据包:
if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {
return nil
}源码:proxy/socks/server.go:189-192
出站(客户端)
文件:proxy/socks/client.go
客户端与上游服务器执行完整的 SOCKS5 握手:
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(隧道)
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 请求,服务端:
- 使用 Go 的
http.ReadRequest()解析完整的 HTTP 请求 - 从
Host头或 URL 中提取目标地址 - 将请求分发到出站
- 支持同一连接上的 keep-alive 序列请求
keepAlive := strings.TrimSpace(strings.ToLower(
request.Header.Get("Proxy-Connection"))) == "keep-alive"源码:proxy/http/server.go:163
认证
通过 Proxy-Authorization 头进行 Basic 认证检查:
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:
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 连接按目标地址缓存以实现多路复用:
var cachedH2Conns map[net.Destination]h2Conn源码:proxy/http/client.go:47-48
1xx 响应处理
服务端正确处理 HTTP 1xx 中间响应(如 100 Continue),在读取实际响应之前将它们转发给客户端:
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
客户端的工作流程:
- 从 link reader 读取第一个载荷
- 与上游代理建立 HTTP CONNECT 隧道
- 在隧道建立后立即发送第一个载荷
- 支持通过配置自定义头部(支持 Go 模板用于
Source/Target)
data := struct {
Source net.Destination
Target net.Destination
}{...}
tmpl.Execute(&buf, data)源码:proxy/http/client.go:180-203
实现说明
- SOCKS/HTTP 自动检测:SOCKS 入站读取第一个字节。如果不是
0x04或0x05,则将连接转发给嵌入的 HTTP 服务器。这允许单个端口同时提供 SOCKS5 和 HTTP 代理服务。
源码:proxy/socks/server.go:92-95
CanSpliceCopy 行为:SOCKS 和 HTTP 初始设置
CanSpliceCopy = 2。在协议握手完成后(对于 TCP CONNECT),升级为1,在原始 TCP 流上启用零拷贝。如果在传输层检测到 TLS,则从3开始。SOCKS4 认证拒绝:SOCKS4 不支持密码认证。如果服务端要求认证,SOCKS4 连接会被立即拒绝。
源码:proxy/socks/protocol.go:50-53
- HTTP 代理不支持 UDP:HTTP 出站明确拒绝 UDP 目标。
源码:proxy/http/client.go:80-82
- DispatchLink 与 Dispatch:对于 TCP CONNECT 和 SOCKS TCP,入站使用
dispatcher.DispatchLink()阻塞直到连接完成,而不是使用返回Link进行手动拷贝的Dispatch()。这简化了生命周期管理。
源码:proxy/socks/server.go:163-169