mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-01-30 04:36:54 +00:00
Add hosts transport & Remove bad linkname usages
This commit is contained in:
parent
314b25bccf
commit
3fa417f283
|
@ -32,7 +32,6 @@ run:
|
||||||
- with_reality_server
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- badlinkname
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|
|
@ -9,7 +9,6 @@ builds:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
- -checklinkname=0
|
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|
|
@ -11,7 +11,6 @@ builds:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
- -checklinkname=0
|
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|
|
@ -15,7 +15,7 @@ RUN set -ex \
|
||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -2,15 +2,14 @@ NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS_GO123 = badlinkname
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121),$(TAGS_GO123)
|
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
|
@ -55,7 +55,7 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
DNSTypeHTTPS = "https"
|
DNSTypeHTTPS = "https"
|
||||||
DNSTypeQUIC = "quic"
|
DNSTypeQUIC = "quic"
|
||||||
DNSTypeHTTP3 = "h3"
|
DNSTypeHTTP3 = "h3"
|
||||||
|
DNSTypeHosts = "hosts"
|
||||||
DNSTypeLocal = "local"
|
DNSTypeLocal = "local"
|
||||||
DNSTypePreDefined = "predefined"
|
DNSTypePreDefined = "predefined"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
|
|
63
dns/transport/hosts/hosts.go
Normal file
63
dns/transport/hosts/hosts.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package hosts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.HostsDNSServerOptions](registry, C.DNSTypeHosts, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
files []*File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
var files []*File
|
||||||
|
if len(options.Path) == 0 {
|
||||||
|
files = append(files, NewFile(DefaultPath))
|
||||||
|
} else {
|
||||||
|
for _, path := range options.Path {
|
||||||
|
files = append(files, NewFile(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
|
||||||
|
files: files,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
for _, file := range t.files {
|
||||||
|
addresses := file.Lookup(domain)
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeNameError,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
}, nil
|
||||||
|
}
|
102
dns/transport/hosts/hosts_file.go
Normal file
102
dns/transport/hosts/hosts_file.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package hosts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cacheMaxAge = 5 * time.Second
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
path string
|
||||||
|
access sync.Mutex
|
||||||
|
byName map[string][]netip.Addr
|
||||||
|
expire time.Time
|
||||||
|
modTime time.Time
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFile(path string) *File {
|
||||||
|
return &File{
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Lookup(name string) []netip.Addr {
|
||||||
|
f.access.Lock()
|
||||||
|
defer f.access.Unlock()
|
||||||
|
f.update()
|
||||||
|
return f.byName[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) update() {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Before(f.expire) && len(f.byName) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f.modTime.Equal(stat.ModTime()) && f.size == stat.Size() {
|
||||||
|
f.expire = now.Add(cacheMaxAge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byName := make(map[string][]netip.Addr)
|
||||||
|
file, err := os.Open(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
var (
|
||||||
|
prefix []byte
|
||||||
|
line []byte
|
||||||
|
isPrefix bool
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
line, isPrefix, err = reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isPrefix {
|
||||||
|
prefix = append(prefix, line...)
|
||||||
|
continue
|
||||||
|
} else if len(prefix) > 0 {
|
||||||
|
line = append(prefix, line...)
|
||||||
|
prefix = nil
|
||||||
|
}
|
||||||
|
commentIndex := strings.IndexRune(string(line), '#')
|
||||||
|
if commentIndex != -1 {
|
||||||
|
line = line[:commentIndex]
|
||||||
|
}
|
||||||
|
fields := strings.Fields(string(line))
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var addr netip.Addr
|
||||||
|
addr, err = netip.ParseAddr(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for index := 1; index < len(fields); index++ {
|
||||||
|
canonicalName := dns.CanonicalName(fields[index])
|
||||||
|
byName[canonicalName] = append(byName[canonicalName], addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.expire = now.Add(cacheMaxAge)
|
||||||
|
f.modTime = stat.ModTime()
|
||||||
|
f.size = stat.Size()
|
||||||
|
f.byName = byName
|
||||||
|
}
|
15
dns/transport/hosts/hosts_test.go
Normal file
15
dns/transport/hosts/hosts_test.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package hosts_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHosts(t *testing.T) {
|
||||||
|
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
||||||
|
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
||||||
|
}
|
5
dns/transport/hosts/hosts_unix.go
Normal file
5
dns/transport/hosts/hosts_unix.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package hosts
|
||||||
|
|
||||||
|
var DefaultPath = "/etc/hosts"
|
8
dns/transport/hosts/hosts_windows.go
Normal file
8
dns/transport/hosts/hosts_windows.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package hosts
|
||||||
|
|
||||||
|
import _ "unsafe"
|
||||||
|
|
||||||
|
var DefaultPath = getSystemDirectory() + "/Drivers/etc/hosts"
|
||||||
|
|
||||||
|
//go:linkname getSystemDirectory internal/syscall/windows.GetSystemDirectory
|
||||||
|
func getSystemDirectory() string
|
2
dns/transport/hosts/testdata/hosts
vendored
Normal file
2
dns/transport/hosts/testdata/hosts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
127.0.0.1 localhost
|
||||||
|
::1 localhost
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
@ -26,6 +26,7 @@ var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
|
hosts *hosts.File
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +36,8 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeTCP, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -47,9 +49,9 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
addressStrings, _ := lookupStaticHost(domain)
|
addresses := t.hosts.Lookup(domain)
|
||||||
if len(addressStrings) > 0 {
|
if len(addresses) > 0 {
|
||||||
return dns.FixedResponse(message.Id, question, common.Map(addressStrings, M.ParseAddr), C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig()
|
systemConfig := getSystemDNSConfig()
|
||||||
|
@ -62,7 +64,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
||||||
|
|
||||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for _, fqdn := range nameList(systemConfig, domain) {
|
for _, fqdn := range systemConfig.nameList(domain) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
|
@ -90,7 +92,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
||||||
}
|
}
|
||||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
defer queryCancel()
|
defer queryCancel()
|
||||||
for _, fqdn := range nameList(systemConfig, domain) {
|
for _, fqdn := range systemConfig.nameList(domain) {
|
||||||
go startRacer(queryCtx, fqdn)
|
go startRacer(queryCtx, fqdn)
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
//go:build badlinkname
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname getSystemDNSConfig net.getSystemDNSConfig
|
|
||||||
func getSystemDNSConfig() *dnsConfig
|
|
||||||
|
|
||||||
//go:linkname nameList net.(*dnsConfig).nameList
|
|
||||||
func nameList(c *dnsConfig, name string) []string
|
|
||||||
|
|
||||||
//go:linkname lookupStaticHost net.lookupStaticHost
|
|
||||||
func lookupStaticHost(host string) ([]string, string)
|
|
||||||
|
|
||||||
//go:linkname splitHostZone net.splitHostZone
|
|
||||||
func splitHostZone(s string) (host, zone string)
|
|
|
@ -1,44 +0,0 @@
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// net.maxDNSPacketSize
|
|
||||||
maxDNSPacketSize = 1232
|
|
||||||
)
|
|
||||||
|
|
||||||
type dnsConfig struct {
|
|
||||||
servers []string // server addresses (in host:port form) to use
|
|
||||||
search []string // rooted suffixes to append to local name
|
|
||||||
ndots int // number of dots in name to trigger absolute lookup
|
|
||||||
timeout time.Duration // wait before giving up on a query, including retries
|
|
||||||
attempts int // lost packets before giving up on server
|
|
||||||
rotate bool // round robin among servers
|
|
||||||
unknownOpt bool // anything unknown was encountered
|
|
||||||
lookup []string // OpenBSD top-level database "lookup" order
|
|
||||||
err error // any error that occurs during open of resolv.conf
|
|
||||||
mtime time.Time // time of resolv.conf modification
|
|
||||||
soffset uint32 // used by serverOffset
|
|
||||||
singleRequest bool // use sequential A and AAAA queries instead of parallel queries
|
|
||||||
useTCP bool // force usage of TCP for DNS resolutions
|
|
||||||
trustAD bool // add AD flag to queries
|
|
||||||
noReload bool // do not check for config file updates
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *dnsConfig) serverOffset() uint32 {
|
|
||||||
if c.rotate {
|
|
||||||
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname runtime_rand runtime.rand
|
|
||||||
func runtime_rand() uint64
|
|
||||||
|
|
||||||
func randInt() int {
|
|
||||||
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//go:build !badlinkname
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
func getSystemDNSConfig() *dnsConfig {
|
|
||||||
panic("stub")
|
|
||||||
}
|
|
||||||
|
|
||||||
func nameList(c *dnsConfig, name string) []string {
|
|
||||||
panic("stub")
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupStaticHost(host string) ([]string, string) {
|
|
||||||
panic("stub")
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitHostZone(s string) (host, zone string) {
|
|
||||||
panic("stub")
|
|
||||||
}
|
|
154
dns/transport/local/resolv.go
Normal file
154
dns/transport/local/resolv.go
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// net.maxDNSPacketSize
|
||||||
|
maxDNSPacketSize = 1232
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolverConfig struct {
|
||||||
|
initOnce sync.Once
|
||||||
|
ch chan struct{}
|
||||||
|
lastChecked time.Time
|
||||||
|
dnsConfig atomic.Pointer[dnsConfig]
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvConf resolverConfig
|
||||||
|
|
||||||
|
func getSystemDNSConfig() *dnsConfig {
|
||||||
|
resolvConf.tryUpdate("/etc/resolv.conf")
|
||||||
|
return resolvConf.dnsConfig.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conf *resolverConfig) init() {
|
||||||
|
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
||||||
|
conf.lastChecked = time.Now()
|
||||||
|
conf.ch = make(chan struct{}, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conf *resolverConfig) tryUpdate(name string) {
|
||||||
|
conf.initOnce.Do(conf.init)
|
||||||
|
|
||||||
|
if conf.dnsConfig.Load().noReload {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !conf.tryAcquireSema() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conf.releaseSema()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if conf.lastChecked.After(now.Add(-5 * time.Second)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conf.lastChecked = now
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
var mtime time.Time
|
||||||
|
if fi, err := os.Stat(name); err == nil {
|
||||||
|
mtime = fi.ModTime()
|
||||||
|
}
|
||||||
|
if mtime.Equal(conf.dnsConfig.Load().mtime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsConf := dnsReadConfig(name)
|
||||||
|
conf.dnsConfig.Store(dnsConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conf *resolverConfig) tryAcquireSema() bool {
|
||||||
|
select {
|
||||||
|
case conf.ch <- struct{}{}:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conf *resolverConfig) releaseSema() {
|
||||||
|
<-conf.ch
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsConfig struct {
|
||||||
|
servers []string
|
||||||
|
search []string
|
||||||
|
ndots int
|
||||||
|
timeout time.Duration
|
||||||
|
attempts int
|
||||||
|
rotate bool
|
||||||
|
unknownOpt bool
|
||||||
|
lookup []string
|
||||||
|
err error
|
||||||
|
mtime time.Time
|
||||||
|
soffset uint32
|
||||||
|
singleRequest bool
|
||||||
|
useTCP bool
|
||||||
|
trustAD bool
|
||||||
|
noReload bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsConfig) serverOffset() uint32 {
|
||||||
|
if c.rotate {
|
||||||
|
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conf *dnsConfig) nameList(name string) []string {
|
||||||
|
l := len(name)
|
||||||
|
rooted := l > 0 && name[l-1] == '.'
|
||||||
|
if l > 254 || l == 254 && !rooted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rooted {
|
||||||
|
if avoidDNS(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||||
|
name += "."
|
||||||
|
l++
|
||||||
|
|
||||||
|
names := make([]string, 0, 1+len(conf.search))
|
||||||
|
if hasNdots && !avoidDNS(name) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
for _, suffix := range conf.search {
|
||||||
|
fqdn := name + suffix
|
||||||
|
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
||||||
|
names = append(names, fqdn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasNdots && !avoidDNS(name) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_rand runtime.rand
|
||||||
|
func runtime_rand() uint64
|
||||||
|
|
||||||
|
func randInt() int {
|
||||||
|
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
||||||
|
}
|
||||||
|
|
||||||
|
func avoidDNS(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if name[len(name)-1] == '.' {
|
||||||
|
name = name[:len(name)-1]
|
||||||
|
}
|
||||||
|
return strings.HasSuffix(name, ".onion")
|
||||||
|
}
|
175
dns/transport/local/resolv_unix.go
Normal file
175
dns/transport/local/resolv_unix.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dnsReadConfig(name string) *dnsConfig {
|
||||||
|
conf := &dnsConfig{
|
||||||
|
ndots: 1,
|
||||||
|
timeout: 5 * time.Second,
|
||||||
|
attempts: 2,
|
||||||
|
}
|
||||||
|
file, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
conf.servers = defaultNS
|
||||||
|
conf.search = dnsDefaultSearch()
|
||||||
|
conf.err = err
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
fi, err := file.Stat()
|
||||||
|
if err == nil {
|
||||||
|
conf.mtime = fi.ModTime()
|
||||||
|
} else {
|
||||||
|
conf.servers = defaultNS
|
||||||
|
conf.search = dnsDefaultSearch()
|
||||||
|
conf.err = err
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
var (
|
||||||
|
prefix []byte
|
||||||
|
line []byte
|
||||||
|
isPrefix bool
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
line, isPrefix, err = reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if isPrefix {
|
||||||
|
prefix = append(prefix, line...)
|
||||||
|
continue
|
||||||
|
} else if len(prefix) > 0 {
|
||||||
|
line = append(prefix, line...)
|
||||||
|
prefix = nil
|
||||||
|
}
|
||||||
|
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := strings.Fields(string(line))
|
||||||
|
if len(f) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch f[0] {
|
||||||
|
case "nameserver":
|
||||||
|
if len(f) > 1 && len(conf.servers) < 3 {
|
||||||
|
if _, err := netip.ParseAddr(f[1]); err == nil {
|
||||||
|
conf.servers = append(conf.servers, net.JoinHostPort(f[1], "53"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "domain":
|
||||||
|
if len(f) > 1 {
|
||||||
|
conf.search = []string{ensureRooted(f[1])}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search":
|
||||||
|
conf.search = make([]string, 0, len(f)-1)
|
||||||
|
for i := 1; i < len(f); i++ {
|
||||||
|
name := ensureRooted(f[i])
|
||||||
|
if name == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conf.search = append(conf.search, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "options":
|
||||||
|
for _, s := range f[1:] {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(s, "ndots:"):
|
||||||
|
n, _, _ := dtoi(s[6:])
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
} else if n > 15 {
|
||||||
|
n = 15
|
||||||
|
}
|
||||||
|
conf.ndots = n
|
||||||
|
case strings.HasPrefix(s, "timeout:"):
|
||||||
|
n, _, _ := dtoi(s[8:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
conf.timeout = time.Duration(n) * time.Second
|
||||||
|
case strings.HasPrefix(s, "attempts:"):
|
||||||
|
n, _, _ := dtoi(s[9:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
conf.attempts = n
|
||||||
|
case s == "rotate":
|
||||||
|
conf.rotate = true
|
||||||
|
case s == "single-request" || s == "single-request-reopen":
|
||||||
|
conf.singleRequest = true
|
||||||
|
case s == "use-vc" || s == "usevc" || s == "tcp":
|
||||||
|
conf.useTCP = true
|
||||||
|
case s == "trust-ad":
|
||||||
|
conf.trustAD = true
|
||||||
|
case s == "edns0":
|
||||||
|
case s == "no-reload":
|
||||||
|
conf.noReload = true
|
||||||
|
default:
|
||||||
|
conf.unknownOpt = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "lookup":
|
||||||
|
conf.lookup = f[1:]
|
||||||
|
|
||||||
|
default:
|
||||||
|
conf.unknownOpt = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(conf.servers) == 0 {
|
||||||
|
conf.servers = defaultNS
|
||||||
|
}
|
||||||
|
if len(conf.search) == 0 {
|
||||||
|
conf.search = dnsDefaultSearch()
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname defaultNS net.defaultNS
|
||||||
|
var defaultNS []string
|
||||||
|
|
||||||
|
func dnsDefaultSearch() []string {
|
||||||
|
hn, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 {
|
||||||
|
return []string{ensureRooted(hn[i+1:])}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureRooted(s string) string {
|
||||||
|
if len(s) > 0 && s[len(s)-1] == '.' {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
const big = 0xFFFFFF
|
||||||
|
|
||||||
|
func dtoi(s string) (n int, i int, ok bool) {
|
||||||
|
n = 0
|
||||||
|
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
|
||||||
|
n = n*10 + int(s[i]-'0')
|
||||||
|
if n >= big {
|
||||||
|
return big, i, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return n, i, true
|
||||||
|
}
|
100
dns/transport/local/resolv_windows.go
Normal file
100
dns/transport/local/resolv_windows.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dnsReadConfig(_ string) *dnsConfig {
|
||||||
|
conf := &dnsConfig{
|
||||||
|
ndots: 1,
|
||||||
|
timeout: 5 * time.Second,
|
||||||
|
attempts: 2,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if len(conf.servers) == 0 {
|
||||||
|
conf.servers = defaultNS
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
aas, err := adapterAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, aa := range aas {
|
||||||
|
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
||||||
|
if aa.OperStatus != windows.IfOperStatusUp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only take interfaces which have at least one gateway
|
||||||
|
if aa.FirstGatewayAddress == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
||||||
|
sa, err := dns.Address.Sockaddr.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ip netip.Addr
|
||||||
|
switch sa := sa.(type) {
|
||||||
|
case *syscall.SockaddrInet4:
|
||||||
|
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
||||||
|
case *syscall.SockaddrInet6:
|
||||||
|
var addr16 [16]byte
|
||||||
|
copy(addr16[:], sa.Addr[:])
|
||||||
|
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
||||||
|
// fec0/10 IPv6 addresses are site local anycast DNS
|
||||||
|
// addresses Microsoft sets by default if no other
|
||||||
|
// IPv6 DNS address is set. Site local anycast is
|
||||||
|
// deprecated since 2004, see
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc3879
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip = netip.AddrFrom16(addr16)
|
||||||
|
default:
|
||||||
|
// Unexpected type.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname defaultNS net.defaultNS
|
||||||
|
var defaultNS []string
|
||||||
|
|
||||||
|
func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
|
||||||
|
var b []byte
|
||||||
|
l := uint32(15000) // recommended initial size
|
||||||
|
for {
|
||||||
|
b = make([]byte, l)
|
||||||
|
const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS
|
||||||
|
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
|
||||||
|
if err == nil {
|
||||||
|
if l == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW {
|
||||||
|
return nil, os.NewSyscallError("getadaptersaddresses", err)
|
||||||
|
}
|
||||||
|
if l <= uint32(len(b)) {
|
||||||
|
return nil, os.NewSyscallError("getadaptersaddresses", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var aas []*windows.IpAdapterAddresses
|
||||||
|
for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next {
|
||||||
|
aas = append(aas, aa)
|
||||||
|
}
|
||||||
|
return aas, nil
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport"
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
"github.com/sagernet/sing-box/dns/transport/fakeip"
|
"github.com/sagernet/sing-box/dns/transport/fakeip"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
@ -103,6 +104,7 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
||||||
transport.RegisterTLS(registry)
|
transport.RegisterTLS(registry)
|
||||||
transport.RegisterHTTPS(registry)
|
transport.RegisterHTTPS(registry)
|
||||||
transport.RegisterPredefined(registry)
|
transport.RegisterPredefined(registry)
|
||||||
|
hosts.RegisterTransport(registry)
|
||||||
local.RegisterTransport(registry)
|
local.RegisterTransport(registry)
|
||||||
fakeip.RegisterTransport(registry)
|
fakeip.RegisterTransport(registry)
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,11 @@ type LegacyDNSServerOptions struct {
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HostsDNSServerOptions struct {
|
||||||
|
Path badoption.Listable[string] `json:"path,omitempty"`
|
||||||
|
Predefined badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type LocalDNSServerOptions struct {
|
type LocalDNSServerOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
LegacyStrategy DomainStrategy `json:"-"`
|
LegacyStrategy DomainStrategy `json:"-"`
|
||||||
|
|
Loading…
Reference in a new issue