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
相关产品推荐
相关产品推荐

