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

Golang实现HTTP/HTTPS代理时遭遇SSL_ERROR_BAD_MAC_READ错误求助

HTTPS代理出现SSL_ERROR_BAD_MAC_READ错误的解决方法

我参考教程用Golang实现了HTTP/HTTPS代理,核心逻辑为:接收浏览器发送的指令,若为CONNECT请求则建立TCP套接字,让浏览器与目标服务器直接通信并双向转发数据;若为GET请求则处理HTTP转发。目前HTTP请求可正常工作,但在Firefox中访问HTTPS网站时,出现以下错误:

Secure Connection Failed

An error occurred during a connection to www.google.com. SSL received a record with an incorrect Message Authentication Code.

Error code: SSL_ERROR_BAD_MAC_READ

问题代码

package main

import (
    "bufio"
    "fmt"
    "net"
    "strings"
)

func main() {
    fmt.Println("Start server...")

    ln, _ := net.Listen("tcp", ":8000")

    conn, _ := ln.Accept()
    handleSocket(conn)

}

func handleSocket(client_to_proxy net.Conn) {
    message, e := bufio.NewReader(client_to_proxy).ReadString('\n')
    message = strings.ReplaceAll(message, "\r\n", "")
    if e != nil {
        fmt.Println("ERROR1 ", e)
        return
    }

    splited := strings.Split(message, " ")
    host := strings.Split(splited[1], ":")
    if splited[0] == "CONNECT" {
        proxy_to_server, e := net.Dial("tcp", splited[1])
        if e != nil {
            fmt.Println("ERROR2 ", e)
            return
        }
        lenn, e := client_to_proxy.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
        if e != nil {
            fmt.Println("ERROR8 ", e)
            return
        }
        fmt.Println(lenn)

        readAll(client_to_proxy, proxy_to_server)

    } else if splited[0] == "GET" {
        remote_conn, e := net.Dial("tcp", strings.Replace(splited[1][:len(splited[1])-1], "http://", "", 2)+":80")
        if e != nil {
            fmt.Println("ERROR7 ", e)
            return
        }
        _, e = remote_conn.Write([]byte("GET / " + splited[2] + "\r\n" + "Host: " + host[0] + "\r\n\r\n"))
        if e != nil {
            fmt.Println("ERROR6 ", e)
            return
        }
        writeAll(client_to_proxy, remote_conn)
    }
}

func writeAll(client_to_proxy net.Conn, proxy_to_server net.Conn) {
    buffer := make([]byte, 32*1024)
    for {
        readLeng, err := proxy_to_server.Read(buffer)
        if err != nil {
            fmt.Println("ERROR9 ", err)
            return
        }
        if readLeng > 0 {
            _, err := client_to_proxy.Write(buffer)
            if err != nil {
                fmt.Println("ERR4 ", err)
                return
            }
        }
    }
}

func readAll(client_to_proxy net.Conn, proxy_to_server net.Conn) {
    buffer := make([]byte, 32*1024)
    go writeAll(client_to_proxy, proxy_to_server)

    for {
        read, err := client_to_proxy.Read(buffer)
        if err != nil {
            return
        }
        if read > 0 {
            _, err := proxy_to_server.Write(buffer)
            if err != nil {
                fmt.Println("ERR5 ", err)
                return
            }
        }
    }
}

问题分析

  1. 转发数据长度错误:在writeAll和readAll函数中,读取到数据后直接写入整个buffer,而非实际读取的字节长度。SSL是二进制协议,多余的未初始化字节会被当作有效数据转发,破坏SSL记录的MAC校验,导致SSL_ERROR_BAD_MAC_READ。
  2. 仅处理单个连接:main函数中只调用一次ln.Accept(),处理完第一个连接后程序就会退出,无法处理后续请求。
  3. 未读完CONNECT请求的完整头部:浏览器发送CONNECT请求时会附带完整的HTTP头部(直到\r\n\r\n),当前代码只读取了第一行,剩余头部数据会留在缓冲区中,后续被当作SSL数据转发,干扰正常加密通信。

修复后的代码

package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
    "strings"
)

func main() {
    fmt.Println("Start server...")

    ln, err := net.Listen("tcp", ":8000")
    if err != nil {
        fmt.Println("Listen error:", err)
        return
    }
    defer ln.Close()

    // 循环接受连接,用goroutine处理每个请求
    for {
        conn, err := ln.Accept()
        if err != nil {
            fmt.Println("Accept error:", err)
            continue
        }
        go handleSocket(conn)
    }
}

func handleSocket(clientToProxy net.Conn) {
    defer clientToProxy.Close()

    reader := bufio.NewReader(clientToProxy)
    // 读取请求行
    requestLine, err := reader.ReadString('\n')
    if err != nil {
        fmt.Println("Read request line error:", err)
        return
    }
    requestLine = strings.TrimSpace(requestLine)
    splited := strings.Split(requestLine, " ")
    if len(splited) < 3 {
        fmt.Println("Invalid request line:", requestLine)
        return
    }

    if splited[0] == "CONNECT" {
        // 读完剩余的HTTP头部,直到\r\n\r\n
        for {
            line, err := reader.ReadString('\n')
            if err != nil {
                fmt.Println("Read header error:", err)
                return
            }
            if strings.TrimSpace(line) == "" {
                break
            }
        }

        // 建立到目标服务器的连接
        proxyToServer, err := net.Dial("tcp", splited[1])
        if err != nil {
            fmt.Println("Dial to server error:", err)
            // 返回503错误
            clientToProxy.Write([]byte("HTTP/1.1 503 Service Unavailable\r\n\r\n"))
            return
        }
        defer proxyToServer.Close()

        // 回复浏览器连接建立成功
        _, err = clientToProxy.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
        if err != nil {
            fmt.Println("Write 200 OK error:", err)
            return
        }

        // 双向转发数据
        go io.Copy(proxyToServer, clientToProxy)
        io.Copy(clientToProxy, proxyToServer)

    } else if splited[0] == "GET" {
        // 处理HTTP GET请求
        hostPort := strings.TrimPrefix(splited[1], "http://")
        // 去掉路径部分,只保留主机和端口
        hostPort = strings.Split(hostPort, "/")[0]
        if !strings.Contains(hostPort, ":") {
            hostPort += ":80"
        }

        remoteConn, err := net.Dial("tcp", hostPort)
        if err != nil {
            fmt.Println("Dial to HTTP server error:", err)
            clientToProxy.Write([]byte("HTTP/1.1 503 Service Unavailable\r\n\r\n"))
            return
        }
        defer remoteConn.Close()

        // 构造转发的GET请求
        forwardRequest := fmt.Sprintf("GET %s %s\r\nHost: %s\r\n\r\n", splited[1], splited[2], strings.Split(hostPort, ":")[0])
        _, err = remoteConn.Write([]byte(forwardRequest))
        if err != nil {
            fmt.Println("Write to HTTP server error:", err)
            return
        }

        // 转发响应
        io.Copy(clientToProxy, remoteConn)
    }
}

修复说明

  1. 修正数据转发长度:直接使用标准库io.Copy函数处理双向转发,它会自动处理读取和写入的字节长度,避免手动buffer操作的错误。
  2. 循环处理连接:main函数中通过for循环持续接受新连接,并使用goroutine异步处理每个连接,支持多客户端请求。
  3. 完整读取CONNECT请求头部:在处理CONNECT请求时,读取所有头部直到\r\n\r\n,确保缓冲区中没有残留的HTTP头部数据,避免干扰后续的SSL通信。
  4. 完善错误处理:添加了更多错误场景的处理,比如无效请求、连接失败时返回合适的HTTP错误码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:05:21