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

如何让goproxy将指定域名解析为自定义IP(支持HTTP/HTTPS)

基于goproxy实现域名自定义IP解析(支持HTTP/HTTPS)

问题背景

本地运行基于github.com/elazarl/goproxy的代理服务器,初始代码如下:

proxy := goproxy.NewProxyHttpServer()
http.ListenAndServe(":4242", proxy)

需求是通过该代理将指定域名解析为自定义IP,同时兼容HTTP和HTTPS请求。

尝试过的方案及问题

方案1:修改HandleConnectFunc替换Host

实现代码:

proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
    // Allow the connection to proceed for specific hosts
    if host == "mydomain.com" {
        host = "myip:443"
        ctx.Req.URL.Host = host
        ctx.Req.Host = host
    }
    return goproxy.MitmConnect, host
})

遇到的问题:

  • 使用curl(不带--insecure参数)时,触发证书验证失败错误:
curl failed to verify the legitimacy of the server and therefore could not 
establish a secure connection to it. To learn more about this situation and 
how to fix it, please visit the web page mentioned above.
  • 浏览器无法正常工作,测试证书已被本地信任的服务器时,出现证书不匹配错误。

方案2:修改net.DefaultResolver切换自定义DNS

实现代码:

net.DefaultResolver = &net.Resolver{
    PreferGo: true,
    Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        // Use the default address resolution for other hosts
        fmt.Println("domaIN", network, address)
        return (&net.Dialer{}).DialContext(ctx, network, "127.0.0.1:53")
    },
}

遇到的问题:仅对HTTP请求有效;修改代理Transport的DialContext同样只支持HTTP,DialTLSContext对代理请求无效。

可行解决方案

核心思路

HTTPS请求的证书验证依赖正确的SNI(Server Name Indication),之前的方案直接将Host替换为IP,导致SNI与服务器证书绑定的域名不匹配,引发证书错误。正确做法是:保持SNI为原域名,仅替换连接的目标IP。

实现代码

package main

import (
	"context"
	"crypto/tls"
	"net"
	"net/http"
	"strings"

	"github.com/elazarl/goproxy"
)

func main() {
	proxy := goproxy.NewProxyHttpServer()

	// 配置需要自定义解析的域名-IP映射
	customDNSMap := map[string]string{
		"mydomain.com": "192.168.1.100", // 替换为你的目标IP
	}

	// 处理HTTPS请求:替换连接IP,保留原SNI
	originalDialTLS := proxy.Tr.DialTLSContext
	proxy.Tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
		hostPort := strings.Split(addr, ":")
		if len(hostPort) != 2 {
			return originalDialTLS(ctx, network, addr)
		}
		originalHost, port := hostPort[0], hostPort[1]

		// 替换为自定义IP
		if targetIP, exists := customDNSMap[originalHost]; exists {
			addr = net.JoinHostPort(targetIP, port)
		}

		// 先建立TCP连接到目标IP
		dialer := &net.Dialer{}
		tcpConn, err := dialer.DialContext(ctx, network, addr)
		if err != nil {
			return nil, err
		}

		// 手动发起TLS握手,指定SNI为原域名
		tlsConn := tls.Client(tcpConn, &tls.Config{
			ServerName: originalHost,
			// 仅测试场景可开启:InsecureSkipVerify: true
		})
		if err := tlsConn.HandshakeContext(ctx); err != nil {
			tcpConn.Close()
			return nil, err
		}
		return tlsConn, nil
	}

	// 处理HTTP请求:直接替换目标IP
	originalDial := proxy.Tr.DialContext
	proxy.Tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
		hostPort := strings.Split(addr, ":")
		if len(hostPort) != 2 {
			return originalDial(ctx, network, addr)
		}
		host, port := hostPort[0], hostPort[1]

		if targetIP, exists := customDNSMap[host]; exists {
			addr = net.JoinHostPort(targetIP, port)
		}
		return originalDial(ctx, network, addr)
	}

	// 启动代理服务器
	http.ListenAndServe(":4242", proxy)
}

方案优势

  • HTTPS请求:代理连接到自定义IP,但TLS握手时发送原域名作为SNI,服务器返回对应域名的证书,客户端验证时不会出现不匹配问题。
  • HTTP请求:直接替换目标IP,保持原有逻辑正常运行。
  • 无需使用中间人代理(MitmConnect),避免证书信任问题。

注意事项

  • 若目标服务器使用自签名证书,需将证书添加到客户端的信任列表,禁止在生产环境开启InsecureSkipVerify: true。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 20:42:03