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

