优化DNS解析逻辑
This commit is contained in:
228
README_DNS.md
228
README_DNS.md
@@ -5,6 +5,7 @@
|
||||
- 自定义域名解析
|
||||
- 动态变更后端服务器IP
|
||||
- 自定义后端服务器端口
|
||||
- 泛解析(通配符域名)支持
|
||||
- 负载均衡和故障转移
|
||||
- 绕过DNS污染
|
||||
- 高效的DNS缓存
|
||||
@@ -13,6 +14,8 @@
|
||||
|
||||
- **自定义记录**:直接设置域名到IP的映射
|
||||
- **自定义端口**:为每个域名指定自定义端口,无需在URL中指定
|
||||
- **泛解析**:支持通配符域名(如`*.example.com`)自动匹配多个子域名
|
||||
- **多级泛解析**:支持复杂的通配符模式(如`api.*.example.com`)
|
||||
- **备用解析**:在自定义记录未找到时可选择使用系统DNS
|
||||
- **DNS缓存**:缓存解析结果以提高性能
|
||||
- **自动重试**:解析失败时可配置重试策略
|
||||
@@ -39,37 +42,37 @@ go build ./...
|
||||
go run cmd/custom_dns_proxy/main.go
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
### 2. 自定义端口代理
|
||||
|
||||
- `-listen`: 监听地址,默认 `:8080`
|
||||
- `-target`: 目标主机名,默认 `www.github.com`
|
||||
- `-port`: 目标端口,默认 `443`
|
||||
- `-dns`: DNS配置文件(JSON格式)
|
||||
- `-hosts`: hosts格式配置文件
|
||||
|
||||
### 2. HTTPS代理(自定义DNS)
|
||||
|
||||
以下命令会启动一个HTTPS代理(监听端口8443),它使用自定义DNS解析:
|
||||
支持为不同域名指定不同端口的代理:
|
||||
|
||||
```bash
|
||||
# 首先生成自签名证书
|
||||
# Linux/MacOS
|
||||
./scripts/generate_cert.sh
|
||||
go run cmd/custom_port_proxy/main.go
|
||||
```
|
||||
|
||||
# Windows
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\generate_cert.ps1
|
||||
### 3. 泛解析DNS代理
|
||||
|
||||
# 然后启动HTTPS代理
|
||||
go run cmd/custom_dns_https_proxy/main.go
|
||||
支持通配符域名解析的代理:
|
||||
|
||||
```bash
|
||||
# 使用默认的示例泛解析规则
|
||||
go run cmd/wildcard_dns_proxy/main.go
|
||||
|
||||
# 透明代理模式(使用请求中的Host进行匹配)
|
||||
go run cmd/wildcard_dns_proxy/main.go -target ""
|
||||
|
||||
# 使用自定义配置文件
|
||||
go run cmd/wildcard_dns_proxy/main.go -dns examples/wildcard_dns_config.json
|
||||
|
||||
# 使用hosts格式配置文件
|
||||
go run cmd/wildcard_dns_proxy/main.go -hosts examples/wildcard_hosts.txt
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
|
||||
- `-listen`: 监听地址,默认 `:8443`
|
||||
- `-target`: 目标主机名,默认 `www.github.com`
|
||||
- `-port`: 目标端口,默认 `443`
|
||||
- `-cert`: 证书文件,默认 `server.crt`
|
||||
- `-key`: 私钥文件,默认 `server.key`
|
||||
- `-listen`: 监听地址,默认 `:8080`
|
||||
- `-target`: 目标主机名,空字符串表示使用请求中的Host头
|
||||
- `-port`: 默认目标端口,默认 `443`
|
||||
- `-dns`: DNS配置文件(JSON格式)
|
||||
- `-hosts`: hosts格式配置文件
|
||||
|
||||
@@ -80,12 +83,14 @@ go run cmd/custom_dns_https_proxy/main.go
|
||||
```json
|
||||
{
|
||||
"records": {
|
||||
"www.github.com": "140.82.121.3",
|
||||
"github.com": "140.82.121.4",
|
||||
"api.github.com": "140.82.121.5",
|
||||
"example.com": "93.184.216.34",
|
||||
"api.example.com": "93.184.216.35:8443",
|
||||
|
||||
"custom-port-example.com": "192.168.1.10:8080",
|
||||
"api.dev.example.com": "127.0.0.1:3001"
|
||||
"*.github.com": "140.82.121.3",
|
||||
"github.com": "140.82.121.4",
|
||||
|
||||
"*.dev.local": "127.0.0.1:3000",
|
||||
"api.*.dev.local": "127.0.0.1:3001"
|
||||
},
|
||||
"use_fallback": true,
|
||||
"ttl": 300
|
||||
@@ -95,13 +100,14 @@ go run cmd/custom_dns_https_proxy/main.go
|
||||
### Hosts格式
|
||||
|
||||
```
|
||||
# 标准格式:IP 域名
|
||||
140.82.121.3 www.github.com
|
||||
140.82.121.4 github.com
|
||||
# 精确匹配
|
||||
93.184.216.34 example.com
|
||||
93.184.216.35:8443 api.example.com
|
||||
|
||||
# 带端口格式:IP:端口 域名
|
||||
192.168.1.10:8080 custom-port-example.com
|
||||
127.0.0.1:3001 api.dev.example.com
|
||||
# 泛解析(通配符域名)
|
||||
140.82.121.3 *.github.com
|
||||
127.0.0.1:3000 *.dev.local
|
||||
127.0.0.1:3001 api.*.dev.local
|
||||
```
|
||||
|
||||
## 编程接口
|
||||
@@ -120,6 +126,12 @@ resolver.Add("example.com", "93.184.216.34")
|
||||
// 添加带端口的记录
|
||||
resolver.AddWithPort("api.example.com", "93.184.216.35", 8443)
|
||||
|
||||
// 添加泛解析记录(使用默认端口)
|
||||
resolver.AddWildcard("*.example.com", "93.184.216.36")
|
||||
|
||||
// 添加带端口的泛解析记录
|
||||
resolver.AddWildcardWithPort("*.api.example.com", "93.184.216.37", 8444)
|
||||
|
||||
// 解析域名(只获取IP)
|
||||
ip, err := resolver.Resolve("example.com")
|
||||
if err != nil {
|
||||
@@ -127,12 +139,19 @@ if err != nil {
|
||||
}
|
||||
fmt.Printf("解析结果IP: %s\n", ip)
|
||||
|
||||
// 解析域名(获取IP和端口)
|
||||
endpoint, err := resolver.ResolveWithPort("api.example.com", 443) // 443为默认端口
|
||||
// 测试泛解析功能
|
||||
ip, err = resolver.Resolve("sub.example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("解析失败: %v", err)
|
||||
}
|
||||
fmt.Printf("解析结果: IP=%s, 端口=%d\n", endpoint.IP, endpoint.Port)
|
||||
fmt.Printf("泛解析结果IP: %s\n", ip)
|
||||
|
||||
// 测试多级泛解析功能
|
||||
endpoint, err := resolver.ResolveWithPort("test.api.example.com", 443)
|
||||
if err != nil {
|
||||
log.Fatalf("解析失败: %v", err)
|
||||
}
|
||||
fmt.Printf("多级泛解析结果: IP=%s, 端口=%d\n", endpoint.IP, endpoint.Port)
|
||||
```
|
||||
|
||||
### 使用DNS拨号器
|
||||
@@ -143,17 +162,16 @@ import "github.com/goproxy/internal/dns"
|
||||
// 创建解析器
|
||||
resolver := dns.NewResolver()
|
||||
resolver.Add("example.com", "93.184.216.34")
|
||||
resolver.AddWithPort("example-api.com", "93.184.216.35", 8443)
|
||||
resolver.AddWildcard("*.example.com", "93.184.216.36")
|
||||
|
||||
// 创建拨号器
|
||||
dialer := dns.NewDialer(resolver)
|
||||
|
||||
// 使用拨号器连接(会自动应用自定义端口)
|
||||
conn, err := dialer.Dial("tcp", "example-api.com:443")
|
||||
// 使用拨号器连接(会自动应用泛解析)
|
||||
conn, err := dialer.Dial("tcp", "sub.example.com:443")
|
||||
if err != nil {
|
||||
log.Fatalf("连接失败: %v", err)
|
||||
}
|
||||
// 注意:即使这里指定了443端口,实际会连接到8443端口
|
||||
defer conn.Close()
|
||||
|
||||
// 或者获取用于http.Transport的拨号上下文函数
|
||||
@@ -165,29 +183,35 @@ client := &http.Client{Transport: transport}
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 负载均衡
|
||||
### 泛解析模式
|
||||
|
||||
可以通过多次添加同一域名的不同IP地址来实现简单的负载均衡:
|
||||
泛解析支持以下模式:
|
||||
|
||||
```go
|
||||
resolver := dns.NewResolver()
|
||||
resolver.Add("api.example.com", "10.0.0.1")
|
||||
resolver.Add("api.example.com", "10.0.0.2")
|
||||
resolver.Add("api.example.com", "10.0.0.3")
|
||||
1. **单级通配符**:`*.example.com` 匹配 `a.example.com`、`b.example.com` 等
|
||||
|
||||
2. **多级通配符**:`*.*.example.com` 匹配 `a.b.example.com`、`c.d.example.com` 等
|
||||
|
||||
3. **中间通配符**:`api.*.example.com` 匹配 `api.v1.example.com`、`api.beta.example.com` 等
|
||||
|
||||
4. **前缀通配符**:`api-*.example.com` 匹配 `api-v1.example.com`、`api-beta.example.com` 等
|
||||
|
||||
### 域名匹配优先级
|
||||
|
||||
当有多条规则可以匹配同一个域名时,解析器会按照以下优先级选择:
|
||||
|
||||
1. 精确匹配(如 `example.com`)
|
||||
2. 最具体的通配符匹配(如 `*.test.example.com` 比 `*.example.com` 更优先)
|
||||
3. 后添加的通配符规则优先于先添加的规则
|
||||
|
||||
### 透明代理模式
|
||||
|
||||
泛解析代理支持透明模式,这种模式下代理会使用请求中的Host头来进行DNS解析,而不是固定的目标主机:
|
||||
|
||||
```bash
|
||||
go run cmd/wildcard_dns_proxy/main.go -target ""
|
||||
```
|
||||
|
||||
解析器将按轮询方式返回这些IP地址。
|
||||
|
||||
### 带端口的负载均衡
|
||||
|
||||
可以为不同的后端服务器指定不同的端口:
|
||||
|
||||
```go
|
||||
resolver := dns.NewResolver()
|
||||
resolver.AddWithPort("api.example.com", "10.0.0.1", 8080)
|
||||
resolver.AddWithPort("api.example.com", "10.0.0.2", 8081)
|
||||
resolver.AddWithPort("api.example.com", "10.0.0.3", 8082)
|
||||
```
|
||||
这样,通过修改请求的Host头或使用不同的域名访问代理,可以自动路由到不同的后端服务器。
|
||||
|
||||
### 自定义DNS后端
|
||||
|
||||
@@ -198,7 +222,7 @@ type MyResolver struct {
|
||||
// 你的字段
|
||||
}
|
||||
|
||||
// 实现Resolver接口
|
||||
// 实现Resolver接口的所有方法
|
||||
func (r *MyResolver) Resolve(hostname string) (string, error) {
|
||||
// 自定义逻辑
|
||||
}
|
||||
@@ -215,6 +239,14 @@ func (r *MyResolver) AddWithPort(hostname, ip string, port int) error {
|
||||
// 自定义逻辑
|
||||
}
|
||||
|
||||
func (r *MyResolver) AddWildcard(wildcardDomain, ip string) error {
|
||||
// 自定义逻辑
|
||||
}
|
||||
|
||||
func (r *MyResolver) AddWildcardWithPort(wildcardDomain, ip string, port int) error {
|
||||
// 自定义逻辑
|
||||
}
|
||||
|
||||
func (r *MyResolver) Remove(hostname string) error {
|
||||
// 自定义逻辑
|
||||
}
|
||||
@@ -226,44 +258,56 @@ func (r *MyResolver) Clear() {
|
||||
|
||||
## 应用场景
|
||||
|
||||
### 开发环境映射
|
||||
|
||||
在本地开发时,可以将多个服务映射到不同的端口:
|
||||
|
||||
```
|
||||
127.0.0.1:3000 web.local
|
||||
127.0.0.1:3001 api.local
|
||||
127.0.0.1:5432 db.local
|
||||
```
|
||||
|
||||
### 微服务路由
|
||||
|
||||
可以将不同的微服务映射到不同的IP和端口:
|
||||
|
||||
```
|
||||
192.168.1.10:8001 auth-service.internal
|
||||
192.168.1.11:8002 user-service.internal
|
||||
192.168.1.12:8003 payment-service.internal
|
||||
```
|
||||
|
||||
### 多环境测试
|
||||
|
||||
可以创建多个配置文件,针对不同的环境:
|
||||
使用泛解析可以为不同环境的所有服务配置不同的后端:
|
||||
|
||||
```
|
||||
# 测试环境
|
||||
10.0.0.1:8080 api.example.com
|
||||
10.0.0.2:8080 web.example.com
|
||||
# 测试环境的所有服务
|
||||
192.168.1.100 *.test.example.com
|
||||
|
||||
# 生产环境
|
||||
20.0.0.1:443 api.example.com
|
||||
20.0.0.2:443 web.example.com
|
||||
# 预发布环境的所有服务
|
||||
192.168.1.101 *.staging.example.com
|
||||
|
||||
# 生产环境的所有服务
|
||||
192.168.1.102 *.production.example.com
|
||||
```
|
||||
|
||||
### 微服务架构
|
||||
|
||||
为不同类型的微服务提供统一的路由模式:
|
||||
|
||||
```
|
||||
10.0.0.1:8001 *.auth.internal
|
||||
10.0.0.2:8002 *.user.internal
|
||||
10.0.0.3:8003 *.payment.internal
|
||||
```
|
||||
|
||||
### 多租户系统
|
||||
|
||||
在多租户系统中为每个租户路由到不同后端:
|
||||
|
||||
```
|
||||
# 每个租户的子域名指向专用服务器
|
||||
192.168.1.10 tenant1.*.example.com
|
||||
192.168.1.11 tenant2.*.example.com
|
||||
192.168.1.12 tenant3.*.example.com
|
||||
```
|
||||
|
||||
### 本地开发环境
|
||||
|
||||
在本地开发中快速模拟复杂的服务架构:
|
||||
|
||||
```
|
||||
127.0.0.1:3000 *.local
|
||||
127.0.0.1:3001 api.*.local
|
||||
127.0.0.1:5432 db.*.local
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 自签名证书会导致浏览器警告,仅用于测试目的
|
||||
2. 某些网站可能拒绝代理连接
|
||||
3. 此功能主要用于控制后端连接的IP解析和端口,不影响客户端DNS解析
|
||||
4. 在生产环境中使用时,建议使用更强大的DNS解析系统
|
||||
5. 端口信息会覆盖请求中的端口,即使请求URL中指定了端口也会被替换为自定义端口
|
||||
1. 通配符域名只在我们的自定义DNS解析器中有效,不会影响系统DNS
|
||||
2. 泛解析规则的顺序会影响匹配结果,后添加的规则优先级更高
|
||||
3. 过多的泛解析规则可能会影响性能,建议合理组织规则
|
||||
4. 当域名同时匹配多个规则时,精确匹配优先于通配符匹配
|
||||
5. 自签名证书会导致浏览器警告,仅用于测试目的
|
190
cmd/wildcard_dns_proxy/main.go
Normal file
190
cmd/wildcard_dns_proxy/main.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/goproxy/internal/config"
|
||||
"github.com/goproxy/internal/dns"
|
||||
"github.com/goproxy/internal/proxy"
|
||||
)
|
||||
|
||||
// WildcardDNSDelegate 带有泛解析功能的委托
|
||||
type WildcardDNSDelegate struct {
|
||||
proxy.DefaultDelegate
|
||||
targetHost string
|
||||
targetPort string
|
||||
resolver dns.Resolver
|
||||
}
|
||||
|
||||
// ModifyRequest 修改请求头
|
||||
func (d *WildcardDNSDelegate) ModifyRequest(req *http.Request) {
|
||||
log.Printf("收到请求: %s %s", req.Method, req.URL.String())
|
||||
|
||||
// 设置标准浏览器请求头
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/122.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
|
||||
// 保留原始Host头,便于泛解析匹配
|
||||
// 只有当明确指定目标主机时才覆盖
|
||||
if d.targetHost != "" && d.targetHost != "*" {
|
||||
req.Host = d.targetHost
|
||||
}
|
||||
|
||||
// 设置请求的URL方案为HTTPS
|
||||
req.URL.Scheme = "https"
|
||||
|
||||
log.Printf("修改后的请求: %s %s (Host: %s)", req.Method, req.URL.String(), req.Host)
|
||||
}
|
||||
|
||||
// ModifyResponse 修改响应头
|
||||
func (d *WildcardDNSDelegate) ModifyResponse(resp *http.Response) error {
|
||||
log.Printf("收到响应: %d %s", resp.StatusCode, resp.Status)
|
||||
|
||||
// 添加CORS头和代理标识
|
||||
resp.Header.Set("Access-Control-Allow-Origin", "*")
|
||||
resp.Header.Set("X-Proxied-By", "GoProxy-WildcardDNS")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveBackend 解析后端服务器
|
||||
func (d *WildcardDNSDelegate) ResolveBackend(req *http.Request) (string, error) {
|
||||
// 获取要解析的主机名
|
||||
hostToResolve := req.Host
|
||||
if d.targetHost != "" && d.targetHost != "*" {
|
||||
hostToResolve = d.targetHost
|
||||
}
|
||||
|
||||
// 从泛解析获取目标地址
|
||||
endpoint, err := d.resolver.ResolveWithPort(hostToResolve, 0)
|
||||
if err != nil {
|
||||
log.Printf("解析主机 %s 失败: %v, 使用默认端口: %s", hostToResolve, err, d.targetPort)
|
||||
return hostToResolve + ":" + d.targetPort, nil
|
||||
}
|
||||
|
||||
// 优先使用解析得到的端口
|
||||
if endpoint.Port > 0 {
|
||||
address := endpoint.GetAddressWithDefaultPort(0)
|
||||
log.Printf("泛解析主机 %s 到目标服务器(自定义端口): %s", hostToResolve, address)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// 使用默认端口
|
||||
address := endpoint.GetAddressWithDefaultPort(443)
|
||||
log.Printf("泛解析主机 %s 到目标服务器(默认端口): %s", hostToResolve, address)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 命令行参数
|
||||
listenAddr := flag.String("listen", ":8080", "监听地址")
|
||||
targetHost := flag.String("target", "", "目标站点主机名,留空表示使用请求中的Host")
|
||||
targetPort := flag.String("port", "443", "默认目标端口")
|
||||
dnsFile := flag.String("dns", "", "DNS配置文件路径 (JSON格式)")
|
||||
hostsFile := flag.String("hosts", "", "Hosts文件路径")
|
||||
flag.Parse()
|
||||
|
||||
// 创建DNS解析器
|
||||
var resolver dns.Resolver
|
||||
var err error
|
||||
|
||||
if *dnsFile != "" {
|
||||
// 从JSON文件加载DNS配置
|
||||
dnsConfig, err := dns.LoadFromJSON(*dnsFile)
|
||||
if err != nil {
|
||||
log.Printf("加载DNS配置文件失败: %v,将使用默认DNS解析器", err)
|
||||
resolver = dns.NewResolver()
|
||||
} else {
|
||||
resolver = dns.NewResolverFromConfig(dnsConfig)
|
||||
log.Printf("已加载DNS配置,包含 %d 条记录", len(dnsConfig.Records))
|
||||
}
|
||||
} else if *hostsFile != "" {
|
||||
// 从hosts文件加载DNS配置
|
||||
dnsConfig, err := dns.LoadFromHostsFile(*hostsFile)
|
||||
if err != nil {
|
||||
log.Printf("加载hosts文件失败: %v,将使用默认DNS解析器", err)
|
||||
resolver = dns.NewResolver()
|
||||
} else {
|
||||
resolver = dns.NewResolverFromConfig(dnsConfig)
|
||||
log.Printf("已加载hosts文件,包含 %d 条记录", len(dnsConfig.Records))
|
||||
}
|
||||
} else {
|
||||
// 创建默认解析器并添加一些示例泛解析规则
|
||||
resolver = dns.NewResolver()
|
||||
|
||||
// 通配符示例
|
||||
resolver.AddWildcard("*.example.com", "93.184.216.34")
|
||||
resolver.AddWildcardWithPort("*.api.example.com", "93.184.216.35", 8443)
|
||||
resolver.AddWildcard("*.github.com", "140.82.121.3")
|
||||
|
||||
// 多级通配符示例
|
||||
resolver.AddWildcardWithPort("*.dev.local", "127.0.0.1", 3000)
|
||||
resolver.AddWildcardWithPort("api.*.dev.local", "127.0.0.1", 3001)
|
||||
resolver.AddWildcardWithPort("db.*.dev.local", "127.0.0.1", 5432)
|
||||
|
||||
// 常规精确匹配
|
||||
resolver.Add("example.com", "93.184.216.34")
|
||||
resolver.Add("github.com", "140.82.121.4")
|
||||
|
||||
log.Printf("已创建默认解析器并添加示例泛解析规则")
|
||||
}
|
||||
|
||||
// 创建自定义DNS拨号器
|
||||
dnsDialer := dns.NewDialer(resolver)
|
||||
|
||||
// 创建配置
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.ReverseProxy = true // 启用反向代理模式
|
||||
cfg.DecryptHTTPS = false // 不解密HTTPS流量,避免TLS问题
|
||||
cfg.IdleTimeout = 30 * time.Second // 连接空闲超时
|
||||
cfg.AddXForwardedFor = true // 添加X-Forwarded-For头
|
||||
cfg.AddXRealIP = true // 添加X-Real-IP头
|
||||
cfg.SupportWebSocketUpgrade = true // 支持WebSocket升级
|
||||
cfg.EnableCompression = false // 不启用压缩
|
||||
cfg.EnableCORS = true // 启用CORS
|
||||
cfg.EnableRetry = false // 关闭重试功能
|
||||
cfg.EnableConnectionPool = false // 禁用连接池
|
||||
|
||||
// 创建自定义委托
|
||||
delegate := &WildcardDNSDelegate{
|
||||
targetHost: *targetHost,
|
||||
targetPort: *targetPort,
|
||||
resolver: resolver,
|
||||
}
|
||||
|
||||
// 创建代理实例
|
||||
p := proxy.New(&proxy.Options{
|
||||
Config: cfg,
|
||||
Delegate: delegate,
|
||||
})
|
||||
|
||||
// 设置自定义拨号器
|
||||
p.SetDialContext(dnsDialer.DialContext)
|
||||
|
||||
// 创建HTTP服务器
|
||||
server := &http.Server{
|
||||
Addr: *listenAddr,
|
||||
Handler: p,
|
||||
}
|
||||
|
||||
// 启动HTTP服务器
|
||||
log.Printf("泛解析DNS代理启动,监听地址: %s", *listenAddr)
|
||||
|
||||
if *targetHost == "" {
|
||||
log.Printf("透明代理模式:请求的Host将直接用于DNS解析")
|
||||
log.Printf("提示: 尝试访问 http://localhost%s 并设置不同的Host头", *listenAddr)
|
||||
} else {
|
||||
log.Printf("目标主机模式:所有请求将被发送到 %s", *targetHost)
|
||||
log.Printf("提示: 尝试访问 http://localhost%s", *listenAddr)
|
||||
}
|
||||
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Fatalf("服务器启动失败: %v", err)
|
||||
}
|
||||
}
|
23
examples/wildcard_dns_config.json
Normal file
23
examples/wildcard_dns_config.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"records": {
|
||||
"example.com": "93.184.216.34",
|
||||
"api.example.com": "93.184.216.35:8443",
|
||||
|
||||
"*.github.com": "140.82.121.3",
|
||||
"github.com": "140.82.121.4",
|
||||
"api.github.com": "140.82.121.5",
|
||||
|
||||
"*.s3.amazonaws.com": "52.216.162.69",
|
||||
"cdn-*.example.org": "203.0.113.10",
|
||||
|
||||
"*.dev.local": "127.0.0.1:3000",
|
||||
"api.*.dev.local": "127.0.0.1:3001",
|
||||
"db.*.dev.local": "127.0.0.1:5432",
|
||||
|
||||
"*.test.example.com": "192.168.1.100",
|
||||
"*.staging.example.com": "192.168.1.101",
|
||||
"*.production.example.com": "192.168.1.102"
|
||||
},
|
||||
"use_fallback": true,
|
||||
"ttl": 300
|
||||
}
|
27
examples/wildcard_hosts.txt
Normal file
27
examples/wildcard_hosts.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
# 精确匹配记录
|
||||
93.184.216.34 example.com
|
||||
93.184.216.35:8443 api.example.com
|
||||
|
||||
# GitHub相关泛解析
|
||||
140.82.121.3 *.github.com
|
||||
140.82.121.4 github.com
|
||||
140.82.121.5 api.github.com
|
||||
|
||||
# AWS服务泛解析
|
||||
52.216.162.69 *.s3.amazonaws.com
|
||||
203.0.113.10 cdn-*.example.org
|
||||
|
||||
# 本地开发环境泛解析
|
||||
127.0.0.1:3000 *.dev.local
|
||||
127.0.0.1:3001 api.*.dev.local
|
||||
127.0.0.1:5432 db.*.dev.local
|
||||
|
||||
# 多环境测试泛解析
|
||||
192.168.1.100 *.test.example.com
|
||||
192.168.1.101 *.staging.example.com
|
||||
192.168.1.102 *.production.example.com
|
||||
|
||||
# 特定子域名泛解析示例
|
||||
10.0.0.1 *.api.service.com
|
||||
10.0.0.2 *.auth.service.com
|
||||
10.0.0.3 *.cdn.service.com
|
@@ -12,9 +12,9 @@ import (
|
||||
|
||||
// DNSConfig DNS配置文件结构
|
||||
type DNSConfig struct {
|
||||
Records map[string]string `json:"records"`
|
||||
Fallback bool `json:"fallback"`
|
||||
TTL int `json:"ttl"` // 缓存TTL,单位为秒
|
||||
Records map[string]string `json:"records"` // 普通记录和泛解析记录
|
||||
Fallback bool `json:"fallback"` // 是否回退到系统DNS
|
||||
TTL int `json:"ttl"` // 缓存TTL,单位为秒
|
||||
}
|
||||
|
||||
// LoadFromJSON 从JSON文件加载DNS配置
|
||||
@@ -59,6 +59,11 @@ func (c *DNSConfig) SaveToJSON(filePath string) error {
|
||||
// 用于解析hosts文件中的IP:端口格式
|
||||
var ipPortRegex = regexp.MustCompile(`^([0-9.]+)(?::(\d+))?$`)
|
||||
|
||||
// 检查是否为通配符域名
|
||||
func isWildcardDomain(domain string) bool {
|
||||
return strings.Contains(domain, "*")
|
||||
}
|
||||
|
||||
// LoadFromHostsFile 从hosts文件格式加载DNS配置
|
||||
func LoadFromHostsFile(filePath string) (*DNSConfig, error) {
|
||||
file, err := os.Open(filePath)
|
||||
@@ -112,6 +117,8 @@ func LoadFromHostsFile(filePath string) (*DNSConfig, error) {
|
||||
if strings.HasPrefix(domain, "#") {
|
||||
break
|
||||
}
|
||||
|
||||
// 支持通配符和普通域名
|
||||
config.Records[domain] = value
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@ package dns
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -21,6 +23,12 @@ type Resolver interface {
|
||||
// AddWithPort 添加带端口的域名解析规则
|
||||
AddWithPort(host, ip string, port int) error
|
||||
|
||||
// AddWildcard 添加泛解析规则(通配符域名)
|
||||
AddWildcard(wildcardDomain, ip string) error
|
||||
|
||||
// AddWildcardWithPort 添加带端口的泛解析规则
|
||||
AddWildcardWithPort(wildcardDomain, ip string, port int) error
|
||||
|
||||
// Remove 删除域名解析规则
|
||||
Remove(host string) error
|
||||
|
||||
@@ -30,11 +38,19 @@ type Resolver interface {
|
||||
|
||||
// CustomResolver 自定义DNS解析器
|
||||
type CustomResolver struct {
|
||||
mu sync.RWMutex
|
||||
records map[string]*Endpoint // 域名到端点的映射
|
||||
cache map[string]cacheEntry // 外部域名解析缓存
|
||||
fallback bool // 是否在本地记录找不到时回退到系统DNS
|
||||
ttl time.Duration // 缓存TTL
|
||||
mu sync.RWMutex
|
||||
records map[string]*Endpoint // 精确域名到端点的映射
|
||||
wildcardRules []wildcardRule // 通配符规则列表
|
||||
cache map[string]cacheEntry // 外部域名解析缓存
|
||||
fallback bool // 是否在本地记录找不到时回退到系统DNS
|
||||
ttl time.Duration // 缓存TTL
|
||||
}
|
||||
|
||||
// wildcardRule 通配符规则
|
||||
type wildcardRule struct {
|
||||
pattern string // 原始通配符模式,如 *.example.com
|
||||
parts []string // 分解后的模式部分,如 ["*", "example", "com"]
|
||||
endpoint *Endpoint // 对应的端点
|
||||
}
|
||||
|
||||
// cacheEntry 缓存条目
|
||||
@@ -46,10 +62,11 @@ type cacheEntry struct {
|
||||
// NewResolver 创建新的自定义DNS解析器
|
||||
func NewResolver(options ...Option) *CustomResolver {
|
||||
r := &CustomResolver{
|
||||
records: make(map[string]*Endpoint),
|
||||
cache: make(map[string]cacheEntry),
|
||||
fallback: true,
|
||||
ttl: 5 * time.Minute,
|
||||
records: make(map[string]*Endpoint),
|
||||
wildcardRules: make([]wildcardRule, 0),
|
||||
cache: make(map[string]cacheEntry),
|
||||
fallback: true,
|
||||
ttl: 5 * time.Minute,
|
||||
}
|
||||
|
||||
// 应用选项
|
||||
@@ -73,11 +90,19 @@ func (r *CustomResolver) Resolve(host string) (string, error) {
|
||||
func (r *CustomResolver) ResolveWithPort(host string, defaultPort int) (*Endpoint, error) {
|
||||
// 首先检查自定义记录
|
||||
r.mu.RLock()
|
||||
|
||||
// 精确匹配
|
||||
if endpoint, ok := r.records[host]; ok {
|
||||
r.mu.RUnlock()
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// 尝试通配符匹配
|
||||
if endpoint := r.matchWildcard(host); endpoint != nil {
|
||||
r.mu.RUnlock()
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if entry, ok := r.cache[host]; ok {
|
||||
if time.Now().Before(entry.expiresAt) {
|
||||
@@ -127,6 +152,44 @@ func (r *CustomResolver) ResolveWithPort(host string, defaultPort int) (*Endpoin
|
||||
return nil, errors.New("未找到域名记录且系统DNS回退被禁用")
|
||||
}
|
||||
|
||||
// matchWildcard 尝试匹配通配符规则
|
||||
func (r *CustomResolver) matchWildcard(host string) *Endpoint {
|
||||
hostParts := strings.Split(host, ".")
|
||||
|
||||
// 按照通配符规则列表的顺序尝试匹配
|
||||
// 规则顺序应该保证更具体的规则先匹配
|
||||
for _, rule := range r.wildcardRules {
|
||||
if matchDomainPattern(hostParts, rule.parts) {
|
||||
return rule.endpoint
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchDomainPattern 判断域名部分是否匹配通配符模式
|
||||
func matchDomainPattern(hostParts, patternParts []string) bool {
|
||||
// 如果长度不匹配,则不匹配
|
||||
if len(hostParts) != len(patternParts) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 逐部分匹配
|
||||
for i := 0; i < len(hostParts); i++ {
|
||||
// 如果模式部分是星号,则匹配任何内容
|
||||
if patternParts[i] == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 否则必须精确匹配
|
||||
if hostParts[i] != patternParts[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Add 添加域名解析规则
|
||||
func (r *CustomResolver) Add(host, ip string) error {
|
||||
return r.AddWithPort(host, ip, 0)
|
||||
@@ -145,17 +208,62 @@ func (r *CustomResolver) AddWithPort(host, ip string, port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWildcard 添加泛解析规则
|
||||
func (r *CustomResolver) AddWildcard(wildcardDomain, ip string) error {
|
||||
return r.AddWildcardWithPort(wildcardDomain, ip, 0)
|
||||
}
|
||||
|
||||
// AddWildcardWithPort 添加带端口的泛解析规则
|
||||
func (r *CustomResolver) AddWildcardWithPort(wildcardDomain, ip string, port int) error {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return errors.New("无效的IP地址")
|
||||
}
|
||||
|
||||
// 检查通配符格式
|
||||
if !strings.Contains(wildcardDomain, "*") {
|
||||
return errors.New("泛解析域名必须包含通配符'*'")
|
||||
}
|
||||
|
||||
// 分解通配符域名
|
||||
parts := strings.Split(wildcardDomain, ".")
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// 创建新的通配符规则
|
||||
rule := wildcardRule{
|
||||
pattern: wildcardDomain,
|
||||
parts: parts,
|
||||
endpoint: NewEndpointWithPort(ip, port),
|
||||
}
|
||||
|
||||
// 将新规则添加到规则列表头部,确保更新的规则优先匹配
|
||||
r.wildcardRules = append([]wildcardRule{rule}, r.wildcardRules...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除域名解析规则
|
||||
func (r *CustomResolver) Remove(host string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if _, ok := r.records[host]; !ok {
|
||||
return errors.New("域名记录不存在")
|
||||
// 先尝试删除精确匹配记录
|
||||
if _, ok := r.records[host]; ok {
|
||||
delete(r.records, host)
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(r.records, host)
|
||||
return nil
|
||||
// 然后尝试删除通配符记录
|
||||
for i, rule := range r.wildcardRules {
|
||||
if rule.pattern == host {
|
||||
// 删除这条规则
|
||||
r.wildcardRules = append(r.wildcardRules[:i], r.wildcardRules[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("域名记录不存在")
|
||||
}
|
||||
|
||||
// Clear 清除所有解析规则
|
||||
@@ -164,6 +272,7 @@ func (r *CustomResolver) Clear() {
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.records = make(map[string]*Endpoint)
|
||||
r.wildcardRules = make([]wildcardRule, 0)
|
||||
r.cache = make(map[string]cacheEntry)
|
||||
}
|
||||
|
||||
@@ -190,17 +299,74 @@ func (r *CustomResolver) LoadFromMap(records map[string]string) error {
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for host, value := range records {
|
||||
endpoint, err := ParseEndpoint(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 判断是否为通配符域名
|
||||
if strings.Contains(host, "*") {
|
||||
endpoint, err := ParseEndpoint(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if net.ParseIP(endpoint.IP) == nil {
|
||||
return errors.New("无效的IP地址: " + endpoint.IP + " (域名: " + host + ")")
|
||||
}
|
||||
if net.ParseIP(endpoint.IP) == nil {
|
||||
return errors.New("无效的IP地址: " + endpoint.IP + " (域名: " + host + ")")
|
||||
}
|
||||
|
||||
r.records[host] = endpoint
|
||||
// 添加通配符规则
|
||||
rule := wildcardRule{
|
||||
pattern: host,
|
||||
parts: strings.Split(host, "."),
|
||||
endpoint: endpoint,
|
||||
}
|
||||
r.wildcardRules = append(r.wildcardRules, rule)
|
||||
|
||||
} else {
|
||||
// 常规记录
|
||||
endpoint, err := ParseEndpoint(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if net.ParseIP(endpoint.IP) == nil {
|
||||
return errors.New("无效的IP地址: " + endpoint.IP + " (域名: " + host + ")")
|
||||
}
|
||||
|
||||
r.records[host] = endpoint
|
||||
}
|
||||
}
|
||||
|
||||
// 对通配符规则进行排序,确保更具体的规则先匹配
|
||||
sortWildcardRules(r.wildcardRules)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sortWildcardRules 对通配符规则进行排序,使更具体的规则优先匹配
|
||||
func sortWildcardRules(rules []wildcardRule) {
|
||||
// 使用稳定排序,保证相同优先级的规则保持原有顺序(后添加的规则在前面)
|
||||
sort.SliceStable(rules, func(i, j int) bool {
|
||||
ruleI := rules[i]
|
||||
ruleJ := rules[j]
|
||||
|
||||
// 计算每个规则中通配符的数量
|
||||
wildcardCountI := countWildcards(ruleI.parts)
|
||||
wildcardCountJ := countWildcards(ruleJ.parts)
|
||||
|
||||
// 通配符数量少的规则更具体,优先级更高
|
||||
if wildcardCountI != wildcardCountJ {
|
||||
return wildcardCountI < wildcardCountJ
|
||||
}
|
||||
|
||||
// 如果通配符数量相同,域名部分数量多的更具体
|
||||
return len(ruleI.parts) > len(ruleJ.parts)
|
||||
})
|
||||
}
|
||||
|
||||
// countWildcards 计算域名部分中通配符的数量
|
||||
func countWildcards(parts []string) int {
|
||||
count := 0
|
||||
for _, part := range parts {
|
||||
if part == "*" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
@@ -1,16 +1,12 @@
|
||||
# PowerShell 脚本:生成自签名证书
|
||||
|
||||
# 默认值
|
||||
$Days = 365
|
||||
$Subject = "CN=localhost,OU=Test,O=GoProxy,L=Shanghai,S=Shanghai,C=CN"
|
||||
|
||||
# 处理命令行参数
|
||||
param(
|
||||
[Parameter(HelpMessage="证书有效期(天数)")]
|
||||
[int]$days = 365,
|
||||
|
||||
[Parameter(HelpMessage="证书主题")]
|
||||
[string]$subject = $Subject,
|
||||
[string]$subject = "CN=localhost,OU=Test,O=GoProxy,L=Shanghai,S=Shanghai,C=CN",
|
||||
|
||||
[Parameter(HelpMessage="公用名(CN)")]
|
||||
[string]$cn = "",
|
||||
@@ -28,7 +24,7 @@ function Show-Help {
|
||||
Write-Host "选项:"
|
||||
Write-Host " -help 显示此帮助信息"
|
||||
Write-Host " -days DAYS 证书有效期(天数),默认: 365"
|
||||
Write-Host " -subject SUB 证书主题,默认: $Subject"
|
||||
Write-Host " -subject SUB 证书主题,默认: $subject"
|
||||
Write-Host " -cn CN 公用名(CN),将替换主题中的CN,默认: localhost"
|
||||
Write-Host
|
||||
Write-Host "示例:"
|
||||
@@ -44,12 +40,12 @@ if ($help) {
|
||||
|
||||
# 如果指定了CN,替换主题中的CN部分
|
||||
if ($cn -ne "") {
|
||||
$Subject = $Subject -replace "CN=[^,]*", "CN=$cn"
|
||||
$subject = $subject -replace "CN=[^,]*", "CN=$cn"
|
||||
}
|
||||
|
||||
Write-Host "生成自签名证书..."
|
||||
Write-Host "有效期: $days 天"
|
||||
Write-Host "主题: $Subject"
|
||||
Write-Host "主题: $subject"
|
||||
|
||||
# 检查OpenSSL是否可用
|
||||
$openssl = Get-Command "openssl" -ErrorAction SilentlyContinue
|
||||
@@ -66,7 +62,7 @@ try {
|
||||
|
||||
# 生成证书请求
|
||||
Write-Host "正在生成证书请求..." -ForegroundColor Cyan
|
||||
& openssl req -new -key server.key -out server.csr -subj $Subject.Replace(",", "/")
|
||||
& openssl req -new -key server.key -out server.csr -subj $subject.Replace(",", "/")
|
||||
|
||||
# 生成自签名证书
|
||||
Write-Host "正在生成自签名证书..." -ForegroundColor Cyan
|
||||
@@ -85,4 +81,4 @@ try {
|
||||
catch {
|
||||
Write-Host "错误: 生成证书时发生错误: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user