切换为默认的dns解析;增加resolver=system的支持

This commit is contained in:
wenyifan
2026-07-02 20:50:24 +08:00
parent a24d79fd45
commit bf85966600
6 changed files with 312 additions and 24 deletions
+4 -1
View File
@@ -95,7 +95,9 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
resolverCfg.Nameservers = append( resolverCfg.Nameservers = append(
resolverCfg.Nameservers, resolverCfg.Nameservers,
&config.NameserverConfig{ &config.NameserverConfig{
Addr: rs, Addr: rs,
Prefer: mdutil.GetString(md, "prefer"),
Only: mdutil.GetString(md, "only"),
}, },
) )
} }
@@ -272,6 +274,7 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
&config.NameserverConfig{ &config.NameserverConfig{
Addr: rs, Addr: rs,
Prefer: mdutil.GetString(md, "prefer"), Prefer: mdutil.GetString(md, "prefer"),
Only: mdutil.GetString(md, "only"),
}, },
) )
} }
+71 -19
View File
@@ -211,13 +211,13 @@ func TestNorm(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{ {
name: "empty string", name: "empty string",
input: "", input: "",
wantErr: true, wantErr: true,
}, },
{ {
name: "whitespace only", name: "whitespace only",
input: " ", input: " ",
wantErr: true, wantErr: true,
}, },
{ {
@@ -326,12 +326,12 @@ func TestCutHost(t *testing.T) {
func TestParseSelector(t *testing.T) { func TestParseSelector(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input map[string]any input map[string]any
wantNil bool wantNil bool
wantStrat string wantStrat string
wantFails int wantFails int
wantDurGt bool // failTimeout > 0 wantDurGt bool // failTimeout > 0
}{ }{
{ {
name: "no params", name: "no params",
@@ -359,9 +359,9 @@ func TestParseSelector(t *testing.T) {
{ {
name: "all params with failTimeout", name: "all params with failTimeout",
input: map[string]any{ input: map[string]any{
"strategy": "hash", "strategy": "hash",
"maxFails": 5, "maxFails": 5,
"failTimeout": "10s", "failTimeout": "10s",
}, },
wantStrat: "hash", wantStrat: "hash",
wantFails: 5, wantFails: 5,
@@ -455,8 +455,8 @@ func TestCopyConnectorConfig(t *testing.T) {
t.Run("non-nil deep copy", func(t *testing.T) { t.Run("non-nil deep copy", func(t *testing.T) {
orig := &config.ConnectorConfig{ orig := &config.ConnectorConfig{
Type: "http", Type: "http",
Auth: &config.AuthConfig{Username: "u", Password: "p"}, Auth: &config.AuthConfig{Username: "u", Password: "p"},
Metadata: map[string]any{"k": "v"}, Metadata: map[string]any{"k": "v"},
} }
c := copyConnectorConfig(orig) c := copyConnectorConfig(orig)
@@ -487,9 +487,9 @@ func TestCopyDialerConfig(t *testing.T) {
t.Run("non-nil deep copy", func(t *testing.T) { t.Run("non-nil deep copy", func(t *testing.T) {
orig := &config.DialerConfig{ orig := &config.DialerConfig{
Type: "tls", Type: "tls",
TLS: &config.TLSConfig{ServerName: "example.com"}, TLS: &config.TLSConfig{ServerName: "example.com"},
Auth: &config.AuthConfig{Username: "u"}, Auth: &config.AuthConfig{Username: "u"},
Metadata: map[string]any{"k": "v"}, Metadata: map[string]any{"k": "v"},
} }
c := copyDialerConfig(orig) c := copyDialerConfig(orig)
@@ -1068,6 +1068,31 @@ func TestBuildConfigFromCmd_NodeWithResolver(t *testing.T) {
} }
} }
func TestBuildConfigFromCmd_NodeResolverFamilyOptions(t *testing.T) {
cfg, err := BuildConfigFromCmd(
[]string{":8080"},
[]string{"http://proxy:3128?resolver=auto&prefer=ipv6&only=ipv4"},
)
if err != nil {
t.Fatalf("BuildConfigFromCmd: %v", err)
}
rs := cfg.Resolvers[0]
if len(rs.Nameservers) != 1 {
t.Fatalf("len(Nameservers) = %d, want 1", len(rs.Nameservers))
}
ns := rs.Nameservers[0]
if ns.Addr != "auto" {
t.Errorf("Addr = %q, want auto", ns.Addr)
}
if ns.Prefer != "ipv6" {
t.Errorf("Prefer = %q, want ipv6", ns.Prefer)
}
if ns.Only != "ipv4" {
t.Errorf("Only = %q, want ipv4", ns.Only)
}
}
func TestBuildConfigFromCmd_NodeWithHosts(t *testing.T) { func TestBuildConfigFromCmd_NodeWithHosts(t *testing.T) {
cfg, err := BuildConfigFromCmd( cfg, err := BuildConfigFromCmd(
[]string{":8080"}, []string{":8080"},
@@ -1171,6 +1196,34 @@ func TestBuildConfigFromCmd_ServiceWithResolver(t *testing.T) {
} }
} }
func TestBuildConfigFromCmd_ServiceSystemResolverOnlyIPv4(t *testing.T) {
cfg, err := BuildConfigFromCmd(
[]string{"http://:8080?resolver=system&only=ipv4"},
nil,
)
if err != nil {
t.Fatalf("BuildConfigFromCmd: %v", err)
}
if len(cfg.Resolvers) != 1 {
t.Fatalf("len(Resolvers) = %d, want 1", len(cfg.Resolvers))
}
rs := cfg.Resolvers[0]
if len(rs.Nameservers) != 1 {
t.Fatalf("len(Nameservers) = %d, want 1", len(rs.Nameservers))
}
ns := rs.Nameservers[0]
if ns.Addr != "system" {
t.Errorf("Addr = %q, want system", ns.Addr)
}
if ns.Only != "ipv4" {
t.Errorf("Only = %q, want ipv4", ns.Only)
}
if cfg.Services[0].Resolver != rs.Name {
t.Errorf("Service.Resolver = %q, want %q", cfg.Services[0].Resolver, rs.Name)
}
}
func TestBuildConfigFromCmd_ServiceWithHosts(t *testing.T) { func TestBuildConfigFromCmd_ServiceWithHosts(t *testing.T) {
cfg, err := BuildConfigFromCmd( cfg, err := BuildConfigFromCmd(
[]string{":8080?hosts=example.com:1.2.3.4"}, []string{":8080?hosts=example.com:1.2.3.4"},
@@ -1896,4 +1949,3 @@ func TestBuildNodeConfig_TLSOnlySecure(t *testing.T) {
t.Error("TLS.Secure should be true") t.Error("TLS.Secure should be true")
} }
} }
+3 -4
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@@ -147,9 +146,9 @@ func (d *Dialer) dialOnce(ctx context.Context, network, addr, ifceName string, i
return nil, fmt.Errorf("dial: unsupported network %s", network) return nil, fmt.Errorf("dial: unsupported network %s", network)
} }
var resolver *net.Resolver var resolver *net.Resolver
if runtime.GOOS != "android" { //if runtime.GOOS != "android" {
resolver = &net.Resolver{PreferGo: true} // resolver = &net.Resolver{PreferGo: true}
} //}
netd := net.Dialer{ netd := net.Dialer{
Resolver: resolver, Resolver: resolver,
LocalAddr: ifAddr, LocalAddr: ifAddr,
+14
View File
@@ -82,6 +82,11 @@ func NewExchanger(addr string, opts ...Option) (Exchanger, error) {
opt(&options) opt(&options)
} }
addr = strings.TrimSpace(addr)
if isSystemResolverAddr(addr) {
return newSystemExchanger(addr, options), nil
}
if !strings.Contains(addr, "://") { if !strings.Contains(addr, "://") {
addr = "udp://" + addr addr = "udp://" + addr
} }
@@ -143,6 +148,15 @@ func NewExchanger(addr string, opts ...Option) (Exchanger, error) {
return ex, nil return ex, nil
} }
func isSystemResolverAddr(addr string) bool {
switch strings.ToLower(strings.TrimSpace(addr)) {
case "system", "auto", "system://", "auto://":
return true
default:
return false
}
}
func (ex *exchanger) Exchange(ctx context.Context, msg []byte) ([]byte, error) { func (ex *exchanger) Exchange(ctx context.Context, msg []byte) ([]byte, error) {
if ex.network == "https" { if ex.network == "https" {
return ex.dohExchange(ctx, msg) return ex.dohExchange(ctx, msg)
+112
View File
@@ -10,6 +10,7 @@ import (
"github.com/go-gost/core/chain" "github.com/go-gost/core/chain"
xlogger "github.com/go-gost/x/logger" xlogger "github.com/go-gost/x/logger"
"github.com/miekg/dns"
) )
func TestNewExchanger_UDP(t *testing.T) { func TestNewExchanger_UDP(t *testing.T) {
@@ -175,6 +176,117 @@ func TestNewExchanger_String(t *testing.T) {
} }
} }
func TestNewExchanger_System(t *testing.T) {
ex, err := NewExchanger("system", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, ok := ex.(*systemExchanger); !ok {
t.Fatalf("NewExchanger(system) = %T, want *systemExchanger", ex)
}
if ex.String() != "system" {
t.Errorf("String() = %q, want system", ex.String())
}
}
func TestNewExchanger_AutoSystem(t *testing.T) {
ex, err := NewExchanger("auto", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, ok := ex.(*systemExchanger); !ok {
t.Fatalf("NewExchanger(auto) = %T, want *systemExchanger", ex)
}
}
func TestSystemExchanger_ExchangeA(t *testing.T) {
ex := &systemExchanger{
rawAddr: "system",
lookupIP: func(_ context.Context, network, host string) ([]net.IP, error) {
if network != "ip4" {
t.Errorf("network = %q, want ip4", network)
}
if host != "example.com" {
t.Errorf("host = %q, want example.com", host)
}
return []net.IP{
net.ParseIP("192.0.2.1"),
net.ParseIP("2001:db8::1"),
}, nil
},
}
mq := new(dns.Msg)
mq.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
packed, err := mq.Pack()
if err != nil {
t.Fatal(err)
}
reply, err := ex.Exchange(context.Background(), packed)
if err != nil {
t.Fatalf("Exchange: %v", err)
}
mr := new(dns.Msg)
if err := mr.Unpack(reply); err != nil {
t.Fatalf("Unpack: %v", err)
}
if len(mr.Answer) != 1 {
t.Fatalf("len(Answer) = %d, want 1", len(mr.Answer))
}
a, ok := mr.Answer[0].(*dns.A)
if !ok {
t.Fatalf("Answer[0] = %T, want *dns.A", mr.Answer[0])
}
if !a.A.Equal(net.ParseIP("192.0.2.1")) {
t.Errorf("A = %s, want 192.0.2.1", a.A)
}
}
func TestSystemExchanger_ExchangeAAAA(t *testing.T) {
ex := &systemExchanger{
rawAddr: "system",
lookupIP: func(_ context.Context, network, host string) ([]net.IP, error) {
if network != "ip6" {
t.Errorf("network = %q, want ip6", network)
}
if host != "example.com" {
t.Errorf("host = %q, want example.com", host)
}
return []net.IP{
net.ParseIP("192.0.2.1"),
net.ParseIP("2001:db8::1"),
}, nil
},
}
mq := new(dns.Msg)
mq.SetQuestion(dns.Fqdn("example.com"), dns.TypeAAAA)
packed, err := mq.Pack()
if err != nil {
t.Fatal(err)
}
reply, err := ex.Exchange(context.Background(), packed)
if err != nil {
t.Fatalf("Exchange: %v", err)
}
mr := new(dns.Msg)
if err := mr.Unpack(reply); err != nil {
t.Fatalf("Unpack: %v", err)
}
if len(mr.Answer) != 1 {
t.Fatalf("len(Answer) = %d, want 1", len(mr.Answer))
}
aaaa, ok := mr.Answer[0].(*dns.AAAA)
if !ok {
t.Fatalf("Answer[0] = %T, want *dns.AAAA", mr.Answer[0])
}
if !aaaa.AAAA.Equal(net.ParseIP("2001:db8::1")) {
t.Errorf("AAAA = %s, want 2001:db8::1", aaaa.AAAA)
}
}
func TestExchange_DialFailure(t *testing.T) { func TestExchange_DialFailure(t *testing.T) {
ex, err := NewExchanger("udp://127.0.0.1:1", TimeoutOption(100*time.Millisecond), LoggerOption(xlogger.Nop())) ex, err := NewExchanger("udp://127.0.0.1:1", TimeoutOption(100*time.Millisecond), LoggerOption(xlogger.Nop()))
if err != nil { if err != nil {
+108
View File
@@ -0,0 +1,108 @@
package exchanger
import (
"context"
"errors"
"net"
"strings"
"time"
"github.com/miekg/dns"
)
type systemExchanger struct {
rawAddr string
timeout time.Duration
lookupIP func(ctx context.Context, network, host string) ([]net.IP, error)
}
func newSystemExchanger(rawAddr string, opts Options) Exchanger {
if opts.timeout <= 0 {
opts.timeout = 5 * time.Second
}
ex := &systemExchanger{
rawAddr: rawAddr,
timeout: opts.timeout,
}
ex.lookupIP = net.DefaultResolver.LookupIP
return ex
}
func (ex *systemExchanger) Exchange(ctx context.Context, msg []byte) ([]byte, error) {
mq := new(dns.Msg)
if err := mq.Unpack(msg); err != nil {
return nil, err
}
mr := new(dns.Msg)
mr.SetReply(mq)
mr.RecursionAvailable = true
if len(mq.Question) == 0 {
return mr.Pack()
}
q := mq.Question[0]
network := "ip"
switch q.Qtype {
case dns.TypeA:
network = "ip4"
case dns.TypeAAAA:
network = "ip6"
default:
return mr.Pack()
}
if ex.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, ex.timeout)
defer cancel()
}
host := strings.TrimSuffix(q.Name, ".")
ips, err := ex.lookupIP(ctx, network, host)
if err != nil {
var dnsErr *net.DNSError
if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
return nil, err
}
}
for _, ip := range ips {
switch q.Qtype {
case dns.TypeA:
if ip4 := ip.To4(); ip4 != nil {
mr.Answer = append(mr.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: q.Qclass,
},
A: ip4,
})
}
case dns.TypeAAAA:
if ip.To4() == nil {
if ip16 := ip.To16(); ip16 != nil {
mr.Answer = append(mr.Answer, &dns.AAAA{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeAAAA,
Class: q.Qclass,
},
AAAA: ip16,
})
}
}
}
}
return mr.Pack()
}
func (ex *systemExchanger) String() string {
if ex.rawAddr == "" {
return "system"
}
return ex.rawAddr
}