You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go实现无劫持多级HTTPS代理:双CONNECT隧道问题及方案咨询

问题分析与解决方案

1. 双层CONNECT隧道思路没问题,你的错误出在端口处理

你代码里把targetPort硬设为"80",但客户端发起的是CONNECT www.google.com:443请求,你应该从客户端的CONNECT请求里提取正确的目标端口,而非默认80。这直接导致上游代理去连接目标的80端口,而客户端后续要发送SSL握手数据到该隧道——目标80端口是HTTP服务,不处理SSL协议,自然会出现SEC_E_INVALID_TOKEN错误。

修复端口提取逻辑:

// 解析CONNECT请求的目标地址,正确提取端口
connectLine := strings.Split(strings.TrimSpace(strings.Split(connectRequest, "\r\n")[0]), " ")
if len(connectLine) < 2 {
    fmt.Println("Invalid CONNECT request")
    clientConn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
    return
}
targetAddr := connectLine[1]
// 处理目标地址带端口/不带端口的情况
hostPort := strings.Split(targetAddr, ":")
var targetHost, targetPort string
if len(hostPort) == 2 {
    targetHost = hostPort[0]
    targetPort = hostPort[1]
} else {
    // CONNECT请求默认端口按HTTPS处理
    targetHost = hostPort[0]
    targetPort = "443"
}

2. 类似Proxychains的实现思路

Proxychains本质是通过多层代理转发TCP流量,对应HTTP代理场景就是用CONNECT请求逐层建立隧道,你的核心思路是对的,只需完善以下几点:

(1)正确解析上游代理的认证信息

你手里的上游代理是http://user:pass@ip:port格式,要动态解析账号密码生成Proxy-Authorization头,而非硬编码:

// 解析上游代理URL,提取认证信息
proxyURL, err := url.Parse("http://user:pass@192.168.1.4:3333")
if err != nil {
    clientConn.Write([]byte("HTTP/1.1 500 Internal Server Error\r\n\r\n"))
    return
}
var authHeader string
if proxyURL.User != nil {
    username := proxyURL.User.Username()
    password, _ := proxyURL.User.Password()
    auth := username + ":" + password
    authHeader = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}

(2)校验上游代理的响应状态

不能直接转发上游代理的响应,需先检查是否返回200成功状态,否则要把错误返回给客户端:

// 读取上游代理响应并解析状态
proxyRespBuf := make([]byte, 4096)
n, err := proxyConn.Read(proxyRespBuf)
if err != nil {
    fmt.Println("Error reading proxy response:", err)
    clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
    return
}
proxyRespStr := string(proxyRespBuf[:n])
if !strings.HasPrefix(proxyRespStr, "HTTP/1.1 200") && !strings.HasPrefix(proxyRespStr, "HTTP/1.0 200") {
    // 转发上游代理的错误响应给客户端
    clientConn.Write(proxyRespBuf[:n])
    return
}
// 转发成功响应给客户端
clientConn.Write(proxyRespBuf[:n])

(3)实现响应大小统计

通过包装io.Writer统计转发给客户端的字节数,实现单个用户的流量监控:

// 自定义统计流量的Writer
type StatsWriter struct {
    io.Writer
    BytesWritten int64
}

func (w *StatsWriter) Write(p []byte) (n int, err error) {
    n, err = w.Writer.Write(p)
    w.BytesWritten += int64(n)
    return
}

// 替换原io.Copy,加入流量统计
statsWriter := &StatsWriter{Writer: clientConn}
go func() {
    _, _ = io.Copy(statsWriter, proxyConn)
    // 这里替换为你的用户统计逻辑,比如根据客户端认证信息累加流量
    fmt.Printf("User transferred %d bytes\n", statsWriter.BytesWritten)
}()

_, _ = io.Copy(proxyConn, clientConn)

3. 完整修复后的核心代码示例

func handleClient(clientConn net.Conn) {
    defer clientConn.Close()

    // 读取客户端CONNECT请求
    buf := make([]byte, 4096)
    n, err := clientConn.Read(buf)
    if err != nil {
        fmt.Println("Error reading client request:", err)
        return
    }

    // 解析目标地址与端口
    connectRequest := string(buf[:n])
    connectLine := strings.Split(strings.TrimSpace(strings.Split(connectRequest, "\r\n")[0]), " ")
    if len(connectLine) < 2 {
        clientConn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
        return
    }
    targetAddr := connectLine[1]
    hostPort := strings.Split(targetAddr, ":")
    var targetHost, targetPort string
    if len(hostPort) == 2 {
        targetHost = hostPort[0]
        targetPort = hostPort[1]
    } else {
        targetHost = hostPort[0]
        targetPort = "443"
    }

    // 选择上游代理(可替换为代理列表轮询/随机逻辑)
    proxyURL, err := url.Parse("http://user:pass@192.168.1.4:3333")
    if err != nil {
        clientConn.Write([]byte("HTTP/1.1 500 Internal Server Error\r\n\r\n"))
        return
    }

    // 连接上游代理
    proxyConn, err := net.Dial("tcp", proxyURL.Host)
    if err != nil {
        fmt.Println("Error connecting to proxy:", err)
        clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
        return
    }
    defer proxyConn.Close()

    // 构造上游代理的CONNECT请求
    var connectReq string
    if proxyURL.User != nil {
        username := proxyURL.User.Username()
        password, _ := proxyURL.User.Password()
        auth := username + ":" + password
        authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
        connectReq = fmt.Sprintf("CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\nProxy-Authorization: %s\r\nConnection: close\r\n\r\n", targetHost, targetPort, targetHost, targetPort, authHeader)
    } else {
        connectReq = fmt.Sprintf("CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\nConnection: close\r\n\r\n", targetHost, targetPort, targetHost, targetPort)
    }

    // 发送CONNECT请求到上游代理
    _, err = proxyConn.Write([]byte(connectReq))
    if err != nil {
        fmt.Println("Error sending CONNECT to proxy:", err)
        clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
        return
    }

    // 读取并校验上游代理响应
    proxyRespBuf := make([]byte, 4096)
    n, err = proxyConn.Read(proxyRespBuf)
    if err != nil {
        fmt.Println("Error reading proxy response:", err)
        clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
        return
    }
    if !strings.HasPrefix(string(proxyRespBuf[:n]), "HTTP/1.1 200") && !strings.HasPrefix(string(proxyRespBuf[:n]), "HTTP/1.0 200") {
        clientConn.Write(proxyRespBuf[:n])
        return
    }

    // 返回成功响应给客户端
    _, err = clientConn.Write(proxyRespBuf[:n])
    if err != nil {
        fmt.Println("Error writing to client:", err)
        return
    }

    // 统计流量并转发数据
    type StatsWriter struct {
        io.Writer
        BytesWritten int64
    }
    func (w *StatsWriter) Write(p []byte) (n int, err error) {
        n, err = w.Writer.Write(p)
        w.BytesWritten += int64(n)
        return
    }

    statsWriter := &StatsWriter{Writer: clientConn}
    go func() {
        _, _ = io.Copy(statsWriter, proxyConn)
        fmt.Printf("Transferred %d bytes to client\n", statsWriter.BytesWritten)
    }()
    _, _ = io.Copy(proxyConn, clientConn)
}

内容的提问来源于stack exchange,提问作者Frank Muller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 01:52:05