优化DNS解析逻辑

This commit is contained in:
2025-03-13 17:53:08 +08:00
parent d3b7020065
commit cfcc696bda
7 changed files with 579 additions and 126 deletions

View File

@@ -5,6 +5,7 @@
- 自定义域名解析 - 自定义域名解析
- 动态变更后端服务器IP - 动态变更后端服务器IP
- 自定义后端服务器端口 - 自定义后端服务器端口
- 泛解析(通配符域名)支持
- 负载均衡和故障转移 - 负载均衡和故障转移
- 绕过DNS污染 - 绕过DNS污染
- 高效的DNS缓存 - 高效的DNS缓存
@@ -13,6 +14,8 @@
- **自定义记录**直接设置域名到IP的映射 - **自定义记录**直接设置域名到IP的映射
- **自定义端口**为每个域名指定自定义端口无需在URL中指定 - **自定义端口**为每个域名指定自定义端口无需在URL中指定
- **泛解析**:支持通配符域名(如`*.example.com`)自动匹配多个子域名
- **多级泛解析**:支持复杂的通配符模式(如`api.*.example.com`
- **备用解析**在自定义记录未找到时可选择使用系统DNS - **备用解析**在自定义记录未找到时可选择使用系统DNS
- **DNS缓存**:缓存解析结果以提高性能 - **DNS缓存**:缓存解析结果以提高性能
- **自动重试**:解析失败时可配置重试策略 - **自动重试**:解析失败时可配置重试策略
@@ -39,37 +42,37 @@ go build ./...
go run cmd/custom_dns_proxy/main.go 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 ```bash
# 首先生成自签名证书 go run cmd/custom_port_proxy/main.go
# Linux/MacOS ```
./scripts/generate_cert.sh
# Windows ### 3. 泛解析DNS代理
powershell -ExecutionPolicy Bypass -File .\scripts\generate_cert.ps1
# 然后启动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` - `-listen`: 监听地址,默认 `:8080`
- `-target`: 目标主机名,默认 `www.github.com` - `-target`: 目标主机名,空字符串表示使用请求中的Host头
- `-port`: 目标端口,默认 `443` - `-port`: 默认目标端口,默认 `443`
- `-cert`: 证书文件,默认 `server.crt`
- `-key`: 私钥文件,默认 `server.key`
- `-dns`: DNS配置文件JSON格式 - `-dns`: DNS配置文件JSON格式
- `-hosts`: hosts格式配置文件 - `-hosts`: hosts格式配置文件
@@ -80,12 +83,14 @@ go run cmd/custom_dns_https_proxy/main.go
```json ```json
{ {
"records": { "records": {
"www.github.com": "140.82.121.3", "example.com": "93.184.216.34",
"github.com": "140.82.121.4", "api.example.com": "93.184.216.35:8443",
"api.github.com": "140.82.121.5",
"custom-port-example.com": "192.168.1.10:8080", "*.github.com": "140.82.121.3",
"api.dev.example.com": "127.0.0.1:3001" "github.com": "140.82.121.4",
"*.dev.local": "127.0.0.1:3000",
"api.*.dev.local": "127.0.0.1:3001"
}, },
"use_fallback": true, "use_fallback": true,
"ttl": 300 "ttl": 300
@@ -95,13 +100,14 @@ go run cmd/custom_dns_https_proxy/main.go
### Hosts格式 ### Hosts格式
``` ```
# 标准格式IP 域名 # 精确匹配
140.82.121.3 www.github.com 93.184.216.34 example.com
140.82.121.4 github.com 93.184.216.35:8443 api.example.com
# 带端口格式IP:端口 域名 # 泛解析(通配符域名
192.168.1.10:8080 custom-port-example.com 140.82.121.3 *.github.com
127.0.0.1:3001 api.dev.example.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.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
ip, err := resolver.Resolve("example.com") ip, err := resolver.Resolve("example.com")
if err != nil { if err != nil {
@@ -127,12 +139,19 @@ if err != nil {
} }
fmt.Printf("解析结果IP: %s\n", ip) 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 { if err != nil {
log.Fatalf("解析失败: %v", err) 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拨号器 ### 使用DNS拨号器
@@ -143,17 +162,16 @@ import "github.com/goproxy/internal/dns"
// 创建解析器 // 创建解析器
resolver := dns.NewResolver() resolver := dns.NewResolver()
resolver.Add("example.com", "93.184.216.34") 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) 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 { if err != nil {
log.Fatalf("连接失败: %v", err) log.Fatalf("连接失败: %v", err)
} }
// 注意即使这里指定了443端口实际会连接到8443端口
defer conn.Close() defer conn.Close()
// 或者获取用于http.Transport的拨号上下文函数 // 或者获取用于http.Transport的拨号上下文函数
@@ -165,29 +183,35 @@ client := &http.Client{Transport: transport}
## 高级用法 ## 高级用法
### 负载均衡 ### 泛解析模式
可以通过多次添加同一域名的不同IP地址来实现简单的负载均衡 泛解析支持以下模式
```go 1. **单级通配符**`*.example.com` 匹配 `a.example.com``b.example.com`
resolver := dns.NewResolver()
resolver.Add("api.example.com", "10.0.0.1") 2. **多级通配符**`*.*.example.com` 匹配 `a.b.example.com``c.d.example.com`
resolver.Add("api.example.com", "10.0.0.2")
resolver.Add("api.example.com", "10.0.0.3") 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地址 这样通过修改请求的Host头或使用不同的域名访问代理可以自动路由到不同的后端服务器
### 带端口的负载均衡
可以为不同的后端服务器指定不同的端口:
```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)
```
### 自定义DNS后端 ### 自定义DNS后端
@@ -198,7 +222,7 @@ type MyResolver struct {
// 你的字段 // 你的字段
} }
// 实现Resolver接口 // 实现Resolver接口的所有方法
func (r *MyResolver) Resolve(hostname string) (string, error) { 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 { 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 192.168.1.100 *.test.example.com
10.0.0.2:8080 web.example.com
# 生产环境 # 预发布环境的所有服务
20.0.0.1:443 api.example.com 192.168.1.101 *.staging.example.com
20.0.0.2:443 web.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. 自签名证书会导致浏览器警告,仅用于测试目的 1. 通配符域名只在我们的自定义DNS解析器中有效不会影响系统DNS
2. 某些网站可能拒绝代理连接 2. 泛解析规则的顺序会影响匹配结果,后添加的规则优先级更高
3. 此功能主要用于控制后端连接的IP解析和端口不影响客户端DNS解析 3. 过多的泛解析规则可能会影响性能,建议合理组织规则
4. 在生产环境中使用时建议使用更强大的DNS解析系统 4. 当域名同时匹配多个规则时,精确匹配优先于通配符匹配
5. 端口信息会覆盖请求中的端口即使请求URL中指定了端口也会被替换为自定义端口 5. 自签名证书会导致浏览器警告,仅用于测试目的

View 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)
}
}

View 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
}

View 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

View File

@@ -12,8 +12,8 @@ import (
// DNSConfig DNS配置文件结构 // DNSConfig DNS配置文件结构
type DNSConfig struct { type DNSConfig struct {
Records map[string]string `json:"records"` Records map[string]string `json:"records"` // 普通记录和泛解析记录
Fallback bool `json:"fallback"` Fallback bool `json:"fallback"` // 是否回退到系统DNS
TTL int `json:"ttl"` // 缓存TTL单位为秒 TTL int `json:"ttl"` // 缓存TTL单位为秒
} }
@@ -59,6 +59,11 @@ func (c *DNSConfig) SaveToJSON(filePath string) error {
// 用于解析hosts文件中的IP:端口格式 // 用于解析hosts文件中的IP:端口格式
var ipPortRegex = regexp.MustCompile(`^([0-9.]+)(?::(\d+))?$`) var ipPortRegex = regexp.MustCompile(`^([0-9.]+)(?::(\d+))?$`)
// 检查是否为通配符域名
func isWildcardDomain(domain string) bool {
return strings.Contains(domain, "*")
}
// LoadFromHostsFile 从hosts文件格式加载DNS配置 // LoadFromHostsFile 从hosts文件格式加载DNS配置
func LoadFromHostsFile(filePath string) (*DNSConfig, error) { func LoadFromHostsFile(filePath string) (*DNSConfig, error) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
@@ -112,6 +117,8 @@ func LoadFromHostsFile(filePath string) (*DNSConfig, error) {
if strings.HasPrefix(domain, "#") { if strings.HasPrefix(domain, "#") {
break break
} }
// 支持通配符和普通域名
config.Records[domain] = value config.Records[domain] = value
} }
} }

View File

@@ -3,6 +3,8 @@ package dns
import ( import (
"errors" "errors"
"net" "net"
"sort"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -21,6 +23,12 @@ type Resolver interface {
// AddWithPort 添加带端口的域名解析规则 // AddWithPort 添加带端口的域名解析规则
AddWithPort(host, ip string, port int) error AddWithPort(host, ip string, port int) error
// AddWildcard 添加泛解析规则(通配符域名)
AddWildcard(wildcardDomain, ip string) error
// AddWildcardWithPort 添加带端口的泛解析规则
AddWildcardWithPort(wildcardDomain, ip string, port int) error
// Remove 删除域名解析规则 // Remove 删除域名解析规则
Remove(host string) error Remove(host string) error
@@ -31,12 +39,20 @@ type Resolver interface {
// CustomResolver 自定义DNS解析器 // CustomResolver 自定义DNS解析器
type CustomResolver struct { type CustomResolver struct {
mu sync.RWMutex mu sync.RWMutex
records map[string]*Endpoint // 域名到端点的映射 records map[string]*Endpoint // 精确域名到端点的映射
wildcardRules []wildcardRule // 通配符规则列表
cache map[string]cacheEntry // 外部域名解析缓存 cache map[string]cacheEntry // 外部域名解析缓存
fallback bool // 是否在本地记录找不到时回退到系统DNS fallback bool // 是否在本地记录找不到时回退到系统DNS
ttl time.Duration // 缓存TTL ttl time.Duration // 缓存TTL
} }
// wildcardRule 通配符规则
type wildcardRule struct {
pattern string // 原始通配符模式,如 *.example.com
parts []string // 分解后的模式部分,如 ["*", "example", "com"]
endpoint *Endpoint // 对应的端点
}
// cacheEntry 缓存条目 // cacheEntry 缓存条目
type cacheEntry struct { type cacheEntry struct {
endpoint *Endpoint endpoint *Endpoint
@@ -47,6 +63,7 @@ type cacheEntry struct {
func NewResolver(options ...Option) *CustomResolver { func NewResolver(options ...Option) *CustomResolver {
r := &CustomResolver{ r := &CustomResolver{
records: make(map[string]*Endpoint), records: make(map[string]*Endpoint),
wildcardRules: make([]wildcardRule, 0),
cache: make(map[string]cacheEntry), cache: make(map[string]cacheEntry),
fallback: true, fallback: true,
ttl: 5 * time.Minute, 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) { func (r *CustomResolver) ResolveWithPort(host string, defaultPort int) (*Endpoint, error) {
// 首先检查自定义记录 // 首先检查自定义记录
r.mu.RLock() r.mu.RLock()
// 精确匹配
if endpoint, ok := r.records[host]; ok { if endpoint, ok := r.records[host]; ok {
r.mu.RUnlock() r.mu.RUnlock()
return endpoint, nil return endpoint, nil
} }
// 尝试通配符匹配
if endpoint := r.matchWildcard(host); endpoint != nil {
r.mu.RUnlock()
return endpoint, nil
}
// 检查缓存 // 检查缓存
if entry, ok := r.cache[host]; ok { if entry, ok := r.cache[host]; ok {
if time.Now().Before(entry.expiresAt) { if time.Now().Before(entry.expiresAt) {
@@ -127,6 +152,44 @@ func (r *CustomResolver) ResolveWithPort(host string, defaultPort int) (*Endpoin
return nil, errors.New("未找到域名记录且系统DNS回退被禁用") 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 添加域名解析规则 // Add 添加域名解析规则
func (r *CustomResolver) Add(host, ip string) error { func (r *CustomResolver) Add(host, ip string) error {
return r.AddWithPort(host, ip, 0) return r.AddWithPort(host, ip, 0)
@@ -145,17 +208,62 @@ func (r *CustomResolver) AddWithPort(host, ip string, port int) error {
return nil 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 删除域名解析规则 // Remove 删除域名解析规则
func (r *CustomResolver) Remove(host string) error { func (r *CustomResolver) Remove(host string) error {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
if _, ok := r.records[host]; !ok { // 先尝试删除精确匹配记录
return errors.New("域名记录不存在") if _, ok := r.records[host]; ok {
}
delete(r.records, host) delete(r.records, host)
return nil 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 清除所有解析规则 // Clear 清除所有解析规则
@@ -164,6 +272,7 @@ func (r *CustomResolver) Clear() {
defer r.mu.Unlock() defer r.mu.Unlock()
r.records = make(map[string]*Endpoint) r.records = make(map[string]*Endpoint)
r.wildcardRules = make([]wildcardRule, 0)
r.cache = make(map[string]cacheEntry) r.cache = make(map[string]cacheEntry)
} }
@@ -190,6 +299,27 @@ func (r *CustomResolver) LoadFromMap(records map[string]string) error {
defer r.mu.Unlock() defer r.mu.Unlock()
for host, value := range records { for host, value := range records {
// 判断是否为通配符域名
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 + ")")
}
// 添加通配符规则
rule := wildcardRule{
pattern: host,
parts: strings.Split(host, "."),
endpoint: endpoint,
}
r.wildcardRules = append(r.wildcardRules, rule)
} else {
// 常规记录
endpoint, err := ParseEndpoint(value) endpoint, err := ParseEndpoint(value)
if err != nil { if err != nil {
return err return err
@@ -201,6 +331,42 @@ func (r *CustomResolver) LoadFromMap(records map[string]string) error {
r.records[host] = endpoint r.records[host] = endpoint
} }
}
// 对通配符规则进行排序,确保更具体的规则先匹配
sortWildcardRules(r.wildcardRules)
return nil 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
}

View File

@@ -1,16 +1,12 @@
# PowerShell 脚本:生成自签名证书 # PowerShell 脚本:生成自签名证书
# 默认值
$Days = 365
$Subject = "CN=localhost,OU=Test,O=GoProxy,L=Shanghai,S=Shanghai,C=CN"
# 处理命令行参数 # 处理命令行参数
param( param(
[Parameter(HelpMessage="证书有效期(天数)")] [Parameter(HelpMessage="证书有效期(天数)")]
[int]$days = 365, [int]$days = 365,
[Parameter(HelpMessage="证书主题")] [Parameter(HelpMessage="证书主题")]
[string]$subject = $Subject, [string]$subject = "CN=localhost,OU=Test,O=GoProxy,L=Shanghai,S=Shanghai,C=CN",
[Parameter(HelpMessage="公用名(CN)")] [Parameter(HelpMessage="公用名(CN)")]
[string]$cn = "", [string]$cn = "",
@@ -28,7 +24,7 @@ function Show-Help {
Write-Host "选项:" Write-Host "选项:"
Write-Host " -help 显示此帮助信息" Write-Host " -help 显示此帮助信息"
Write-Host " -days DAYS 证书有效期(天数),默认: 365" Write-Host " -days DAYS 证书有效期(天数),默认: 365"
Write-Host " -subject SUB 证书主题,默认: $Subject" Write-Host " -subject SUB 证书主题,默认: $subject"
Write-Host " -cn CN 公用名(CN)将替换主题中的CN默认: localhost" Write-Host " -cn CN 公用名(CN)将替换主题中的CN默认: localhost"
Write-Host Write-Host
Write-Host "示例:" Write-Host "示例:"
@@ -44,12 +40,12 @@ if ($help) {
# 如果指定了CN替换主题中的CN部分 # 如果指定了CN替换主题中的CN部分
if ($cn -ne "") { if ($cn -ne "") {
$Subject = $Subject -replace "CN=[^,]*", "CN=$cn" $subject = $subject -replace "CN=[^,]*", "CN=$cn"
} }
Write-Host "生成自签名证书..." Write-Host "生成自签名证书..."
Write-Host "有效期: $days" Write-Host "有效期: $days"
Write-Host "主题: $Subject" Write-Host "主题: $subject"
# 检查OpenSSL是否可用 # 检查OpenSSL是否可用
$openssl = Get-Command "openssl" -ErrorAction SilentlyContinue $openssl = Get-Command "openssl" -ErrorAction SilentlyContinue
@@ -66,7 +62,7 @@ try {
# 生成证书请求 # 生成证书请求
Write-Host "正在生成证书请求..." -ForegroundColor Cyan 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 Write-Host "正在生成自签名证书..." -ForegroundColor Cyan