对 https 代理和 v2ray 做简单的分析。

基本网络代理

  • HTTP 的明文代理,比较简单,且一般仅需请求一次即可
  • HTTPS 代理,由于有 TLS 加密,有密钥、密文等交互,有多次交互

HTTPS 代理

一个基本的 HTTPS 代理如下:

import http from 'http'
import net from 'node:net'
import dns from 'node:dns'

const PORT = 3001

const resolver = new dns.promises.Resolver()
resolver.setServers(['223.5.5.5'])

// 创建 HTTP 服务器
const server = http.createServer()

// https 协议代理
server.on('connect', async (req, socket) => {
  const [host, port] = req.url.split(':')

  // 解析域名
  const addresses = await resolver.resolve4(host)

  // 当前服务器,连接远程服务器
  const client = net.createConnection({
    host: addresses[0],
    port: Number(port)
  })

  // 远程服务器返回的数据和结束,转发到当前 socket
  client.on('data', data => socket.write(data))
  client.on('end', () => socket.end())

  // 当前 socket 数据,发送到远程服务器
  socket.on('data', data => client.write(data))

  // HTTP Connect 协议交互
  socket.write(`HTTP/1.1 200 Connection established\r\n\r\n`)
})

// 启动服务器
server.listen(PORT)

HTTPS 代理测试

# 设置代理
$ export https_proxy=http://127.0.0.1:3001

# 测试
$ curl -v https://httpbin.org/get
* Uses proxy env variable https_proxy == 'http://127.0.0.1:3001'
*   Trying 127.0.0.1:3001...
* Connected to 127.0.0.1 (127.0.0.1) port 3001
* CONNECT tunnel: HTTP/1.1 negotiated
* allocate connect buffer
* Establish HTTP proxy tunnel to httpbin.org:443
> CONNECT httpbin.org:443 HTTP/1.1
> Host: httpbin.org:443
> User-Agent: curl/8.4.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection established
<
* CONNECT phase completed
* CONNECT tunnel established, response 200
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=httpbin.org
*  start date: Aug 20 00:00:00 2024 GMT
*  expire date: Sep 17 23:59:59 2025 GMT
*  subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://httpbin.org/get
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: httpbin.org]
* [HTTP/2] [1] [:path: /get]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
> GET /get HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 200
< date: Sun, 13 Jul 2025 12:14:40 GMT
< content-type: application/json
< content-length: 254
< server: gunicorn/19.9.0
< access-control-allow-origin: *
< access-control-allow-credentials: true
<
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
  },
  "url": "https://httpbin.org/get"
}

整体流程如下:

使用 wireshark 抓包时,记得选 Loopback: lo0,选 en0 可能抓不到包。

常用的 wireshark 筛选:tcp.port == 3001 && ip.addr == 18.136.185.16

v2ray 基本架构

参考 v2ray overview

+------------+               +------------+
|  inbounds  |               |  outbounds |
|            |               |            |
+------------+               +------------+
       |                            ^
       v                            |
+------------+               +------------+
|  outbounds |               |  inbounds  |
|            |               |            |
+------------+               +------------+
       |                            ^
       v                            |
+------------+               +------------+
|  transport |-------------->|  transport |
|            |               |            |
+------------+               +------------+

Debug v2ray

参考 如何编译 v2ray

$ git clone https://github.com/v2fly/v2ray-core
$ go mod download

本文使用 zed 编译器做 debug

// .zed/debug.json
[
  {
    "label": "Debug 开始",
    "adapter": "Delve",
    "program": "main/main.go",
    "request": "launch",
    "args": [
      "run",
      "--config=$HOME/Downloads/v2ray-core-master/v2ray-config.json"
    ],
    "mode": "debug"
  },
]

打上断点,按 F4 即可

一个最基础 vless 服务器

vless 格式

版本:00
UUID:72 a1 77 35 31 c6 49 6e b9 e5 76 a5 b6 bd d4 b5
附加长度:00
指南:01 (TCP)
端口:00 50 (http 80)
地址类型:02 (
地址:0b 68 74 74
数据:70 62 69 6e 2e 6f 72 67 47 45 54 20 2f 67 65 74 20 48 54 54 50 2f 31 2e ... 80 more bytes>

一个最简单的服务器,只返回 hello

// server.mjs
import net from 'node:net'

const server = net.createServer(socket => {
  socket.on('data', data => {
    // 从 socket data 中可获取到相关的远程地址和端口,然后连接即可。
    console.log(`Received data from client: `, data)
  })

  // vless 版本号 和 protobuf 大小,直接选择设置为 0
  var arr = [0x00, 0x00]
  var content = `HTTP/1.1 200 OK\r\na:b\r\ncontent-length:10\r\n\r\nhello`
    .split('')
    .map(t => t.charCodeAt(0))
  arr = arr.concat(content)

  socket.end(new Uint8Array(arr))
})

// Grab an arbitrary unused port.
server.listen(3000, () => {
  console.log('opened server on', server.address())
})

v2ray 启动配置文件:

{
  "log": {
    "error": "~/Downloads/v2ray-core-master/v2ray-error.log",
    "loglevel": "info",
    "access": "~/Downloads/v2ray-core-master/v2ray-access.log"
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "protocol": "http",
      "port": "1089"
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "streamSettings": {
        "network": "tcp"
      },
      "settings": {
        "vnext": [
          {
            "address": "127.0.0.1",
            "port": 3000,
            "users": [
              {
                "encryption": "none",
                "id": "72a17735-31c6-496e-b9e5-76a5b6bdd4b5",
                "level": 0
              }
            ]
          }
        ]
      }
    }
  ],
  "dns": {},
  "routing": {
    "domainStrategy": "AsIs",
    "rules": []
  }
}

测试记录:


# 代理服务器
$ node server.mjs
# 执行测试后,则打印出以下内容
Received data from client:  <Buffer 00 72 a1 77 35 31 c6 49 6e b9 e5 76 a5 b6 bd d4 b5 00 01 00 50 02 0b 68 74 74 70 62 69 6e 2e 6f 72 67 47 45 54 20 2f 67 65 74 20 48 54 54 50 2f 31 2e ... 80 more bytes>

# 启动服务器
$ v2ray run -c ./v2ray.json

# 测试
$ export http_proxy=http://127.0.0.1:1089
$ curl -v http://httpbin.org/get
* Uses proxy env variable http_proxy == 'http://127.0.0.1:1089'
*   Trying 127.0.0.1:1089...
* Connected to 127.0.0.1 (127.0.0.1) port 1089
> GET http://httpbin.org/get HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/8.4.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Content-Length: 10
< A: b
< Connection: keep-alive
< Keep-Alive: timeout=4
< Proxy-Connection: keep-alive
<
* transfer closed with 5 bytes remaining to read
* Closing connection
curl: (18) transfer closed with 5 bytes remaining to read
hello%
# 最终返回的 hello

参考