mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
63 Commits
v1.12.0-al
...
v1.12.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32c43a8f87 | ||
|
|
c0ecfedce6 | ||
|
|
0595567594 | ||
|
|
2dbadb8789 | ||
|
|
f5429fdf31 | ||
|
|
7843b7473d | ||
|
|
1d7bf3cbf8 | ||
|
|
06321ec655 | ||
|
|
4e202f8ed7 | ||
|
|
8fa4dfce91 | ||
|
|
ce0036ae56 | ||
|
|
5c85409eb8 | ||
|
|
0bb55be50f | ||
|
|
99776a199e | ||
|
|
ec0902623c | ||
|
|
97e99daeef | ||
|
|
d11143ead8 | ||
|
|
5a1ca90c70 | ||
|
|
bf70b096bf | ||
|
|
46c8bafae5 | ||
|
|
ee8cd25b31 | ||
|
|
587a74311b | ||
|
|
0c5e5f7ab8 | ||
|
|
1908dd0672 | ||
|
|
6e5bddccaa | ||
|
|
b646949449 | ||
|
|
db973cf246 | ||
|
|
6e06ce55ab | ||
|
|
6b29c2e8e3 | ||
|
|
ba0cf49ae3 | ||
|
|
46c743cdee | ||
|
|
7dc1d66857 | ||
|
|
1b1f3a12b2 | ||
|
|
0eee4abdad | ||
|
|
cfcf77bb4f | ||
|
|
7a1f014094 | ||
|
|
45d785c256 | ||
|
|
ab01bbf79e | ||
|
|
d6825fa358 | ||
|
|
9d6cee976e | ||
|
|
476552eeb4 | ||
|
|
f81518ea82 | ||
|
|
3ae036e997 | ||
|
|
5da2d1d470 | ||
|
|
8e2baf40f1 | ||
|
|
c24c40dfee | ||
|
|
32e52ce1ed | ||
|
|
ed46438359 | ||
|
|
0b5490d5a3 | ||
|
|
2d73ef511d | ||
|
|
63e6c85f6f | ||
|
|
8946a6d2d0 | ||
|
|
d3132645fb | ||
|
|
373f158fe0 | ||
|
|
ce36835fab | ||
|
|
619fa671d7 | ||
|
|
eb07c7a79e | ||
|
|
7eb3535094 | ||
|
|
93b68312cf | ||
|
|
97ce666e43 | ||
|
|
4000e1e66d | ||
|
|
270740e859 | ||
|
|
6cad142cfe |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -155,7 +155,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: 2.5.1
|
||||
version: '~> v2'
|
||||
install-only: true
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
@@ -551,7 +551,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: 2.5.1
|
||||
version: '~> v2'
|
||||
install-only: true
|
||||
- name: Cache ghr
|
||||
uses: actions/cache@v4
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
version: '~> v2'
|
||||
args: release -f .goreleaser.fury.yaml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -97,10 +97,12 @@ archives:
|
||||
builds:
|
||||
- main
|
||||
- android
|
||||
format: tar.gz
|
||||
formats:
|
||||
- tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats:
|
||||
- zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
|
||||
@@ -246,8 +246,6 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(outbound, stage)
|
||||
@@ -256,6 +254,8 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
||||
}
|
||||
}
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = common.Close(existsOutbound)
|
||||
|
||||
9
box.go
9
box.go
@@ -216,8 +216,15 @@ func New(options Options) (*Box, error) {
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
endpointCtx := ctx
|
||||
if tag != "" {
|
||||
// TODO: remove this
|
||||
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
|
||||
Outbound: tag,
|
||||
})
|
||||
}
|
||||
err = endpointManager.Create(
|
||||
ctx,
|
||||
endpointCtx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
|
||||
Submodule clients/android updated: 3a2fc9c880...aefe3c0290
Submodule clients/apple updated: 3d5d7343fb...ae5818ee5a
@@ -34,10 +34,13 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
||||
switch options.Store {
|
||||
case C.CertificateStoreSystem, "":
|
||||
systemPool = x509.NewCertPool()
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var systemValid bool
|
||||
for _, cert := range service.FromContext[platform.Interface](ctx).SystemCertificates() {
|
||||
if systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||
systemValid = true
|
||||
if platformInterface != nil {
|
||||
for _, cert := range platformInterface.SystemCertificates() {
|
||||
if systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||
systemValid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !systemValid {
|
||||
|
||||
@@ -210,6 +210,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
} else if address.IsFqdn() {
|
||||
return nil, E.New("domain not resolved")
|
||||
}
|
||||
if d.networkStrategy == nil {
|
||||
switch N.NetworkName(network) {
|
||||
|
||||
@@ -18,6 +18,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
@@ -31,7 +32,9 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
if defaultInterface == nil || iif.Index != defaultInterface.Index {
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
}
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -89,6 +92,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
@@ -103,7 +107,9 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
if defaultInterface == nil || iif.Index != defaultInterface.Index {
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
}
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -149,10 +155,13 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
var errors []error
|
||||
for _, primaryInterface := range primaryInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
}
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
@@ -161,7 +170,9 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
}
|
||||
for _, fallbackInterface := range fallbackInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
}
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
|
||||
@@ -44,6 +44,20 @@ type resolveDialer struct {
|
||||
}
|
||||
|
||||
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
return &resolveParallelNetworkDialer{
|
||||
resolveDialer{
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||
dialer: dialer,
|
||||
parallel: parallel,
|
||||
server: server,
|
||||
queryOptions: queryOptions,
|
||||
fallbackDelay: fallbackDelay,
|
||||
},
|
||||
parallelDialer,
|
||||
}
|
||||
}
|
||||
return &resolveDialer{
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||
@@ -60,21 +74,6 @@ type resolveParallelNetworkDialer struct {
|
||||
dialer ParallelInterfaceDialer
|
||||
}
|
||||
|
||||
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
|
||||
return &resolveParallelNetworkDialer{
|
||||
resolveDialer{
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||
dialer: dialer,
|
||||
parallel: parallel,
|
||||
server: server,
|
||||
queryOptions: queryOptions,
|
||||
fallbackDelay: fallbackDelay,
|
||||
},
|
||||
dialer,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) initialize() error {
|
||||
d.initOnce.Do(d.initServer)
|
||||
return d.initErr
|
||||
|
||||
@@ -15,19 +15,19 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypePreDefined = "predefined"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeLegacyRcode = "legacy_rcode"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -78,6 +78,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "TUIC"
|
||||
case TypeHysteria2:
|
||||
return "Hysteria2"
|
||||
case TypeAnyTLS:
|
||||
return "AnyTLS"
|
||||
case TypeSelector:
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
RuleActionTypeHijackDNS = "hijack-dns"
|
||||
RuleActionTypeSniff = "sniff"
|
||||
RuleActionTypeResolve = "resolve"
|
||||
RuleActionTypePredefined = "predefined"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -537,7 +537,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
||||
Question: []dns.Question{question},
|
||||
}
|
||||
for _, address := range addresses {
|
||||
if address.Is4() {
|
||||
if address.Is4() && question.Qtype == dns.TypeA {
|
||||
response.Answer = append(response.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: question.Name,
|
||||
@@ -547,7 +547,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
||||
},
|
||||
A: address.AsSlice(),
|
||||
})
|
||||
} else {
|
||||
} else if address.Is6() && question.Qtype == dns.TypeAAAA {
|
||||
response.Answer = append(response.Answer, &dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: question.Name,
|
||||
|
||||
@@ -190,6 +190,8 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
}
|
||||
case *R.RuleActionReject:
|
||||
return nil, currentRule, currentRuleIndex
|
||||
case *R.RuleActionPredefined:
|
||||
return nil, currentRule, currentRuleIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +262,21 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: action.Rcode,
|
||||
},
|
||||
Question: message.Question,
|
||||
Answer: action.Answer,
|
||||
Ns: action.Ns,
|
||||
Extra: action.Extra,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||
@@ -366,7 +383,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
ruleIndex = -1
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
@@ -376,6 +394,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
if action.Rcode != mDNS.RcodeSuccess {
|
||||
err = RcodeError(action.Rcode)
|
||||
} else {
|
||||
for _, answer := range action.Answer {
|
||||
switch record := answer.(type) {
|
||||
case *mDNS.A:
|
||||
responseAddrs = append(responseAddrs, M.AddrFromIP(record.A))
|
||||
case *mDNS.AAAA:
|
||||
responseAddrs = append(responseAddrs, M.AddrFromIP(record.AAAA))
|
||||
}
|
||||
}
|
||||
}
|
||||
goto response
|
||||
}
|
||||
}
|
||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||
@@ -385,16 +417,17 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck)
|
||||
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck)
|
||||
if responseCheck == nil || err == nil {
|
||||
break
|
||||
}
|
||||
printResult()
|
||||
}
|
||||
}
|
||||
response:
|
||||
printResult()
|
||||
if len(responseAddrs) > 0 {
|
||||
r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
|
||||
|
||||
@@ -2,12 +2,15 @@ package hosts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
@@ -20,21 +23,31 @@ var _ adapter.DNSTransport = (*Transport)(nil)
|
||||
|
||||
type Transport struct {
|
||||
dns.TransportAdapter
|
||||
files []*File
|
||||
files []*File
|
||||
predefined map[string][]netip.Addr
|
||||
}
|
||||
|
||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
|
||||
var files []*File
|
||||
var (
|
||||
files []*File
|
||||
predefined = make(map[string][]netip.Addr)
|
||||
)
|
||||
if len(options.Path) == 0 {
|
||||
files = append(files, NewFile(DefaultPath))
|
||||
} else {
|
||||
for _, path := range options.Path {
|
||||
files = append(files, NewFile(path))
|
||||
files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path))))
|
||||
}
|
||||
}
|
||||
if options.Predefined != nil {
|
||||
for _, entry := range options.Predefined.Entries() {
|
||||
predefined[mDNS.CanonicalName(entry.Key)] = entry.Value
|
||||
}
|
||||
}
|
||||
return &Transport{
|
||||
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
|
||||
files: files,
|
||||
predefined: predefined,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -43,8 +56,11 @@ 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)
|
||||
domain := mDNS.CanonicalName(question.Name)
|
||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||
if addresses, ok := t.predefined[domain]; ok {
|
||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||
}
|
||||
for _, file := range t.files {
|
||||
addresses := file.Lookup(domain)
|
||||
if len(addresses) > 0 {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (f *File) Lookup(name string) []netip.Addr {
|
||||
f.access.Lock()
|
||||
defer f.access.Unlock()
|
||||
f.update()
|
||||
return f.byName[name]
|
||||
return f.byName[dns.CanonicalName(name)]
|
||||
}
|
||||
|
||||
func (f *File) update() {
|
||||
|
||||
@@ -11,6 +11,6 @@ import (
|
||||
|
||||
func TestHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
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."))
|
||||
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"))
|
||||
}
|
||||
|
||||
@@ -139,6 +139,9 @@ func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn stri
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||
if server.Port == 0 {
|
||||
server.Port = 53
|
||||
}
|
||||
var networks []string
|
||||
if useTCP {
|
||||
networks = []string{N.NetworkTCP}
|
||||
|
||||
@@ -58,8 +58,12 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FallbackTransport) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
if f.fallback {
|
||||
if !f.fallback {
|
||||
return f.DNSTransport.Exchange(ctx, message)
|
||||
}
|
||||
question := message.Question[0]
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package transport
|
||||
|
||||
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"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var _ adapter.DNSTransport = (*PredefinedTransport)(nil)
|
||||
|
||||
func RegisterPredefined(registry *dns.TransportRegistry) {
|
||||
dns.RegisterTransport[option.PredefinedDNSServerOptions](registry, C.DNSTypePreDefined, NewPredefined)
|
||||
}
|
||||
|
||||
type PredefinedTransport struct {
|
||||
dns.TransportAdapter
|
||||
responses []*predefinedResponse
|
||||
}
|
||||
|
||||
type predefinedResponse struct {
|
||||
questions []mDNS.Question
|
||||
answer *mDNS.Msg
|
||||
}
|
||||
|
||||
func NewPredefined(ctx context.Context, logger log.ContextLogger, tag string, options option.PredefinedDNSServerOptions) (adapter.DNSTransport, error) {
|
||||
var responses []*predefinedResponse
|
||||
for _, response := range options.Responses {
|
||||
questions, msg, err := response.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
responses = append(responses, &predefinedResponse{
|
||||
questions: questions,
|
||||
answer: msg,
|
||||
})
|
||||
}
|
||||
if len(responses) == 0 {
|
||||
return nil, E.New("empty predefined responses")
|
||||
}
|
||||
return &PredefinedTransport{
|
||||
TransportAdapter: dns.NewTransportAdapter(C.DNSTypePreDefined, tag, nil),
|
||||
responses: responses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PredefinedTransport) Reset() {
|
||||
}
|
||||
|
||||
func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
for _, response := range t.responses {
|
||||
for _, question := range response.questions {
|
||||
if func() bool {
|
||||
if question.Name == "" && question.Qtype == mDNS.TypeNone {
|
||||
return true
|
||||
} else if question.Name == "" {
|
||||
return common.Any(message.Question, func(it mDNS.Question) bool {
|
||||
return it.Qtype == question.Qtype
|
||||
})
|
||||
} else if question.Qtype == mDNS.TypeNone {
|
||||
return common.Any(message.Question, func(it mDNS.Question) bool {
|
||||
return it.Name == question.Name
|
||||
})
|
||||
} else {
|
||||
return common.Contains(message.Question, question)
|
||||
}
|
||||
}() {
|
||||
copyAnswer := *response.answer
|
||||
copyAnswer.Id = message.Id
|
||||
copyAnswer.Question = message.Question
|
||||
return ©Answer, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, dns.RcodeNameError
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
@@ -101,8 +100,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
headers: headers,
|
||||
transport: &http3.Transport{
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
destinationAddr := M.ParseSocksaddr(addr)
|
||||
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr)
|
||||
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr)
|
||||
if dialErr != nil {
|
||||
return nil, dialErr
|
||||
}
|
||||
|
||||
@@ -110,13 +110,6 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
||||
conn.access.Lock()
|
||||
delete(conn.callbacks, messageId)
|
||||
conn.access.Unlock()
|
||||
callback.access.Lock()
|
||||
select {
|
||||
case <-callback.done:
|
||||
default:
|
||||
close(callback.done)
|
||||
}
|
||||
callback.access.Unlock()
|
||||
}()
|
||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,29 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-alpha.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-alpha.13
|
||||
|
||||
* Move `predefined` DNS server to DNS rule action **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
||||
|
||||
### 1.11.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-alpha.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -7,6 +7,10 @@ icon: material/apple
|
||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||
|
||||
!!! failure ""
|
||||
|
||||
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
|
||||
|
||||
## :material-graph: Requirements
|
||||
|
||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||
|
||||
@@ -4,6 +4,7 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||
:material-delete-clock: [outbound](#outbound)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
@@ -77,15 +78,6 @@ icon: material/alert-decagram
|
||||
"domain_regex": [
|
||||
"^stun\\..+"
|
||||
],
|
||||
"geosite": [
|
||||
"cn"
|
||||
],
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
@@ -96,6 +88,7 @@ icon: material/alert-decagram
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"ip_accept_any": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@@ -147,8 +140,6 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// deprecated
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
@@ -156,7 +147,20 @@ icon: material/alert-decagram
|
||||
"direct"
|
||||
],
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"geosite": [
|
||||
"cn"
|
||||
],
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
@@ -451,7 +455,9 @@ Only takes effect for address requests (A/AAAA/HTTPS). When the query results do
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
!!! failure "Removed in sing-box 1.12.0"
|
||||
|
||||
GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||
|
||||
Match GeoIP with query response.
|
||||
|
||||
@@ -473,6 +479,12 @@ Match private IP with query response.
|
||||
|
||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Match any IP with query response.
|
||||
|
||||
### Logical Fields
|
||||
|
||||
#### type
|
||||
|
||||
@@ -4,6 +4,7 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||
:material-delete-clock: [outbound](#outbound)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
@@ -77,15 +78,6 @@ icon: material/alert-decagram
|
||||
"domain_regex": [
|
||||
"^stun\\..+"
|
||||
],
|
||||
"geosite": [
|
||||
"cn"
|
||||
],
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
@@ -96,6 +88,7 @@ icon: material/alert-decagram
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"ip_accept_any": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@@ -147,8 +140,6 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// 已弃用
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
@@ -156,7 +147,19 @@ icon: material/alert-decagram
|
||||
"direct"
|
||||
],
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
|
||||
// 已弃用
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"geosite": [
|
||||
"cn"
|
||||
],
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
@@ -232,17 +235,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### geosite
|
||||
|
||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
||||
|
||||
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||
GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||
|
||||
匹配 Geosite。
|
||||
|
||||
#### source_geoip
|
||||
|
||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
||||
|
||||
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||
|
||||
匹配源 GeoIP。
|
||||
|
||||
@@ -451,7 +454,10 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
||||
|
||||
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||
|
||||
|
||||
与查询响应匹配 GeoIP。
|
||||
|
||||
@@ -467,6 +473,12 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
匹配任意 IP。
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
@@ -4,7 +4,8 @@ icon: material/new-box
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
:material-plus: [strategy](#strategy)
|
||||
:material-plus: [predefined](#predefined)
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
@@ -31,6 +32,8 @@ Tag of target server.
|
||||
|
||||
#### strategy
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Set domain strategy for this query.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
@@ -69,7 +72,7 @@ Will overrides `dns.client_subnet`.
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"method": "default", // default
|
||||
"method": "",
|
||||
"no_drop": false
|
||||
}
|
||||
```
|
||||
@@ -81,8 +84,61 @@ Will overrides `dns.client_subnet`.
|
||||
- `default`: Reply with NXDOMAIN.
|
||||
- `drop`: Drop the request.
|
||||
|
||||
`default` will be used by default.
|
||||
|
||||
#### no_drop
|
||||
|
||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||
|
||||
Not available when `method` is set to drop.
|
||||
|
||||
### predefined
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "predefined",
|
||||
"rcode": "",
|
||||
"answer": [],
|
||||
"ns": [],
|
||||
"extra": []
|
||||
}
|
||||
```
|
||||
|
||||
`predefined` responds with predefined DNS records.
|
||||
|
||||
#### rcode
|
||||
|
||||
The response code.
|
||||
|
||||
| Value | Value in the legacy rcode server | Description |
|
||||
|------------|----------------------------------|-----------------|
|
||||
| `NOERROR` | `success` | Ok |
|
||||
| `FORMERR` | `format_error` | Bad request |
|
||||
| `SERVFAIL` | `server_failure` | Server failure |
|
||||
| `NXDOMAIN` | `name_error` | Not found |
|
||||
| `NOTIMP` | `not_implemented` | Not implemented |
|
||||
| `REFUSED` | `refused` | Refused |
|
||||
|
||||
`NOERROR` will be used by default.
|
||||
|
||||
#### answer
|
||||
|
||||
List of text DNS record to respond as answers.
|
||||
|
||||
Examples:
|
||||
|
||||
| Record Type | Example |
|
||||
|-------------|-------------------------------|
|
||||
| `A` | `localhost. IN A 127.0.0.1` |
|
||||
| `AAAA` | `localhost. IN AAAA ::1` |
|
||||
| `TXT` | `localhost. IN TXT \"Hello\"` |
|
||||
|
||||
#### ns
|
||||
|
||||
List of text DNS record to respond as name servers.
|
||||
|
||||
#### extra
|
||||
|
||||
List of text DNS record to respond as extra records.
|
||||
|
||||
@@ -4,7 +4,8 @@ icon: material/new-box
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
:material-plus: [strategy](#strategy)
|
||||
:material-plus: [predefined](#predefined)
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
@@ -12,9 +13,8 @@ icon: material/new-box
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route", // 默认
|
||||
"action": "route", // 默认
|
||||
"server": "",
|
||||
|
||||
"strategy": "",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
@@ -32,6 +32,8 @@ icon: material/new-box
|
||||
|
||||
#### strategy
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
为此查询设置域名策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
@@ -70,7 +72,7 @@ icon: material/new-box
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"method": "default", // default
|
||||
"method": "",
|
||||
"no_drop": false
|
||||
}
|
||||
```
|
||||
@@ -82,8 +84,61 @@ icon: material/new-box
|
||||
- `default`: 返回 NXDOMAIN。
|
||||
- `drop`: 丢弃请求。
|
||||
|
||||
默认使用 `defualt`。
|
||||
|
||||
#### no_drop
|
||||
|
||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
||||
|
||||
当 `method` 设为 `drop` 时不可用。
|
||||
|
||||
### predefined
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "predefined",
|
||||
"rcode": "",
|
||||
"answer": [],
|
||||
"ns": [],
|
||||
"extra": []
|
||||
}
|
||||
```
|
||||
|
||||
`predefined` 以预定义的 DNS 记录响应。
|
||||
|
||||
#### rcode
|
||||
|
||||
响应码。
|
||||
|
||||
| 值 | 旧 rcode DNS 服务器中的值 | 描述 |
|
||||
|------------|--------------------|-----------------|
|
||||
| `NOERROR` | `success` | Ok |
|
||||
| `FORMERR` | `format_error` | Bad request |
|
||||
| `SERVFAIL` | `server_failure` | Server failure |
|
||||
| `NXDOMAIN` | `name_error` | Not found |
|
||||
| `NOTIMP` | `not_implemented` | Not implemented |
|
||||
| `REFUSED` | `refused` | Refused |
|
||||
|
||||
默认使用 `NOERROR`。
|
||||
|
||||
#### answer
|
||||
|
||||
用于作为回答响应的文本 DNS 记录列表。
|
||||
|
||||
例子:
|
||||
|
||||
| 记录类型 | 例子 |
|
||||
|--------|-------------------------------|
|
||||
| `A` | `localhost. IN A 127.0.0.1` |
|
||||
| `AAAA` | `localhost. IN AAAA ::1` |
|
||||
| `TXT` | `localhost. IN TXT \"Hello\"` |
|
||||
|
||||
#### ns
|
||||
|
||||
用于作为名称服务器响应的文本 DNS 记录列表。
|
||||
|
||||
#### extra
|
||||
|
||||
用于作为额外记录响应的文本 DNS 记录列表。
|
||||
|
||||
96
docs/configuration/dns/server/hosts.md
Normal file
96
docs/configuration/dns/server/hosts.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Hosts
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "hosts",
|
||||
"tag": "",
|
||||
|
||||
"path": [],
|
||||
"predefined": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### path
|
||||
|
||||
List of paths to hosts files.
|
||||
|
||||
`/etc/hosts` is used by default.
|
||||
|
||||
`C:\Windows\System32\Drivers\etc\hosts` is used by default on Windows.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
// "path": "/etc/hosts"
|
||||
|
||||
"path": [
|
||||
"/etc/hosts",
|
||||
"$HOME/.hosts"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### predefined
|
||||
|
||||
Predefined hosts.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"predefined": {
|
||||
"www.google.com": "127.0.0.1",
|
||||
"localhost": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
=== "Use hosts if available"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
...
|
||||
},
|
||||
{
|
||||
"type": "hosts",
|
||||
"tag": "hosts"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"ip_accept_any": true,
|
||||
"server": "hosts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Predefined
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "predefined",
|
||||
"tag": "",
|
||||
"responses": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### responses
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Response](#response-structure).
|
||||
|
||||
### Response Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"query": [],
|
||||
"query_type": [],
|
||||
"rcode": "",
|
||||
"answer": [],
|
||||
"ns": [],
|
||||
"extra": []
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Response Fields
|
||||
|
||||
#### query
|
||||
|
||||
List of domain name to match.
|
||||
|
||||
#### query_type
|
||||
|
||||
List of query type to match.
|
||||
|
||||
#### rcode
|
||||
|
||||
The response code.
|
||||
|
||||
| Value | Value in the legacy rcode server | Description |
|
||||
|------------|----------------------------------|-----------------|
|
||||
| `NOERROR` | `success` | Ok |
|
||||
| `FORMERR` | `format_error` | Bad request |
|
||||
| `SERVFAIL` | `server_failure` | Server failure |
|
||||
| `NXDOMAIN` | `name_error` | Not found |
|
||||
| `NOTIMP` | `not_implemented` | Not implemented |
|
||||
| `REFUSED` | `refused` | Refused |
|
||||
|
||||
`NOERROR` will be used by default.
|
||||
|
||||
#### answer
|
||||
|
||||
List of text DNS record to respond as answers.
|
||||
|
||||
Examples:
|
||||
|
||||
| Record Type | Example |
|
||||
|-------------|-------------------------------|
|
||||
| `A` | `localhost. IN A 127.0.0.1` |
|
||||
| `AAAA` | `localhost. IN AAAA ::1` |
|
||||
| `TXT` | `localhost. IN TXT \"Hello\"` |
|
||||
|
||||
#### ns
|
||||
|
||||
List of text DNS record to respond as name servers.
|
||||
|
||||
#### extra
|
||||
|
||||
List of text DNS record to respond as extra records.
|
||||
@@ -4,7 +4,7 @@ icon: material/new-box
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# TCP
|
||||
# UDP
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ icon: material/new-box
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"idle_session_check_interval": "30s",
|
||||
"idle_session_timeout": "30s",
|
||||
"min_idle_session": 5,
|
||||
"tls": {},
|
||||
|
||||
... // Dial Fields
|
||||
@@ -50,6 +51,10 @@ Interval checking for idle sessions. Default: 30s.
|
||||
|
||||
In the check, close sessions that have been idle for longer than this. Default: 30s.
|
||||
|
||||
#### min_idle_session
|
||||
|
||||
In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0
|
||||
|
||||
#### tls
|
||||
|
||||
==Required==
|
||||
|
||||
@@ -16,6 +16,7 @@ icon: material/new-box
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"idle_session_check_interval": "30s",
|
||||
"idle_session_timeout": "30s",
|
||||
"min_idle_session": 5,
|
||||
"tls": {},
|
||||
|
||||
... // 拨号字段
|
||||
@@ -50,6 +51,10 @@ AnyTLS 密码。
|
||||
|
||||
在检查中,关闭闲置时间超过此值的会话。默认值:30秒。
|
||||
|
||||
#### min_idle_session
|
||||
|
||||
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
|
||||
|
||||
#### tls
|
||||
|
||||
==必填==
|
||||
|
||||
@@ -31,12 +31,11 @@ The protocol version, `1` or `2`.
|
||||
|
||||
### Application support
|
||||
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|----------------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
|
||||
### Protocol details
|
||||
|
||||
@@ -50,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic
|
||||
|------|----------|-------|--------|----------|
|
||||
| u8 | variable | u16be | u16be | variable |
|
||||
|
||||
**ATYP / address / port**: Uses the SOCKS address format.
|
||||
**ATYP / address / port**: Uses the SOCKS address format, but with different address types:
|
||||
|
||||
| ATYP | Address type |
|
||||
|--------|--------------|
|
||||
| `0x00` | IPv4 Address |
|
||||
| `0x01` | IPv6 Address |
|
||||
| `0x02` | Domain Name |
|
||||
|
||||
#### Protocol version 2
|
||||
|
||||
|
||||
@@ -58,6 +58,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
||||
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
||||
|
||||
It is not recommended to change the default build tag list unless you really know what you are adding.
|
||||
|
||||
@@ -51,7 +51,7 @@ icon: material/package
|
||||
|
||||
=== ":material-linux: Linux"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
| 类型 | 平台 | 命令 | 链接 |
|
||||
|----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| AUR | Arch Linux | `? -S sing-box` | [][aur] |
|
||||
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
||||
@@ -61,13 +61,13 @@ icon: material/package
|
||||
|
||||
=== ":material-apple: macOS"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
| 类型 | 平台 | 命令 | 链接 |
|
||||
|----------|-------|-------------------------|------------------------------------------------------------------------------------------------|
|
||||
| Homebrew | macOS | `brew install sing-box` | [][brew] |
|
||||
|
||||
=== ":material-microsoft-windows: Windows"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
| 类型 | 平台 | 命令 | 链接 |
|
||||
|------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| Scoop | Windows | `scoop install sing-box` | [][scoop] |
|
||||
| Chocolatey | Windows | `choco install sing-box` | [][choco] |
|
||||
@@ -75,13 +75,13 @@ icon: material/package
|
||||
|
||||
=== ":material-android: Android"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
| 类型 | 平台 | 命令 | 链接 |
|
||||
|--------|---------|--------------------|----------------------------------------------------------------------------------------------|
|
||||
| Termux | Android | `pkg add sing-box` | [][termux] |
|
||||
|
||||
=== ":material-freebsd: FreeBSD"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
| 类型 | 平台 | 命令 | 链接 |
|
||||
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||
|
||||
|
||||
@@ -567,7 +567,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
|
||||
"server_port": 2080,
|
||||
"domain_resolver": {
|
||||
"server": "local",
|
||||
"rewrite_tll": 60,
|
||||
"rewrite_ttl": 60,
|
||||
"client_subnet": "1.1.1.1"
|
||||
},
|
||||
// or "domain_resolver": "local",
|
||||
@@ -579,7 +579,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
|
||||
"route": {
|
||||
"default_domain_resolver": {
|
||||
"server": "local",
|
||||
"rewrite_tll": 60,
|
||||
"rewrite_ttl": 60,
|
||||
"client_subnet": "1.1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,13 +565,21 @@ DNS 服务器已经重构。
|
||||
"type": "socks",
|
||||
"server": "example.org",
|
||||
"server_port": 2080,
|
||||
"domain_resolver": "local",
|
||||
"domain_resolver": {
|
||||
"server": "local",
|
||||
"rewrite_ttl": 60,
|
||||
"client_subnet": "1.1.1.1"
|
||||
},
|
||||
// 或 "domain_resolver": "local",
|
||||
}
|
||||
],
|
||||
|
||||
// 或
|
||||
|
||||
"route": {
|
||||
"default_domain_resolver": {
|
||||
"server": "local",
|
||||
"rewrite_tll": 60,
|
||||
"rewrite_ttl": 60,
|
||||
"client_subnet": "1.1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,15 +77,15 @@ func (s *Server) downloadExternalUI() error {
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return E.New("download external ui failed: ", response.Status)
|
||||
}
|
||||
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
|
||||
err = s.downloadZIP(response.Body, s.externalUI)
|
||||
if err != nil {
|
||||
removeAllInDirectory(s.externalUI)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||
tempFile, err := filemanager.CreateTemp(s.ctx, name)
|
||||
func (s *Server) downloadZIP(body io.Reader, output string) error {
|
||||
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -56,7 +56,12 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
||||
if sFixAndroidStack {
|
||||
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
} else {
|
||||
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
}
|
||||
|
||||
4
go.mod
4
go.mod
@@ -3,7 +3,7 @@ module github.com/sagernet/sing-box
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/anytls/sing-anytls v0.0.2
|
||||
github.com/anytls/sing-anytls v0.0.6
|
||||
github.com/caddyserver/certmagic v0.21.7
|
||||
github.com/cloudflare/circl v1.6.0
|
||||
github.com/cretz/bine v0.2.0
|
||||
@@ -26,7 +26,7 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff
|
||||
github.com/sagernet/sing v0.6.4-0.20250309232452-1c3b777fe509
|
||||
github.com/sagernet/sing-mux v0.3.1
|
||||
github.com/sagernet/sing-quic v0.4.1-beta.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
|
||||
10
go.sum
10
go.sum
@@ -8,10 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anytls/sing-anytls v0.0.1 h1:Hex6GFUcgATWMWL2E9YgH/7oPgwdokiIF09UQi5BEC0=
|
||||
github.com/anytls/sing-anytls v0.0.1/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/anytls/sing-anytls v0.0.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o=
|
||||
github.com/anytls/sing-anytls v0.0.2/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/anytls/sing-anytls v0.0.6 h1:UatIjl/OvzWQGXQ1I2bAIkabL9WtihW0fA7G+DXGBUg=
|
||||
github.com/anytls/sing-anytls v0.0.6/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||
@@ -184,8 +182,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff h1:5UGghwx8cI14qFa0ienrLekAYfhdKAiWvJUkY7rHmsI=
|
||||
github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.4-0.20250309232452-1c3b777fe509 h1:rDWToc7O295Xh/uFSLqg67MVPftzXnICH/EUI4NL/a8=
|
||||
github.com/sagernet/sing v0.6.4-0.20250309232452-1c3b777fe509/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
|
||||
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
|
||||
github.com/sagernet/sing-quic v0.4.1-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s=
|
||||
|
||||
@@ -107,7 +107,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
||||
transport.RegisterUDP(registry)
|
||||
transport.RegisterTLS(registry)
|
||||
transport.RegisterHTTPS(registry)
|
||||
transport.RegisterPredefined(registry)
|
||||
hosts.RegisterTransport(registry)
|
||||
local.RegisterTransport(registry)
|
||||
fakeip.RegisterTransport(registry)
|
||||
|
||||
@@ -84,13 +84,13 @@ nav:
|
||||
- configuration/dns/server/index.md
|
||||
- Legacy: configuration/dns/server/legacy.md
|
||||
- Local: configuration/dns/server/local.md
|
||||
- Hosts: configuration/dns/server/hosts.md
|
||||
- TCP: configuration/dns/server/tcp.md
|
||||
- UDP: configuration/dns/server/udp.md
|
||||
- TLS: configuration/dns/server/tls.md
|
||||
- QUIC: configuration/dns/server/quic.md
|
||||
- HTTPS: configuration/dns/server/https.md
|
||||
- HTTP3: configuration/dns/server/http3.md
|
||||
- Predefined: configuration/dns/server/predefined.md
|
||||
- DHCP: configuration/dns/server/dhcp.md
|
||||
- FakeIP: configuration/dns/server/fakeip.md
|
||||
- Tailscale: configuration/dns/server/tailscale.md
|
||||
|
||||
@@ -21,4 +21,5 @@ type AnyTLSOutboundOptions struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"`
|
||||
IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"`
|
||||
MinIdleSession int `json:"min_idle_session,omitempty"`
|
||||
}
|
||||
|
||||
@@ -46,7 +46,46 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e
|
||||
}
|
||||
legacyOptions := o.LegacyDNSOptions
|
||||
o.LegacyDNSOptions = LegacyDNSOptions{}
|
||||
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rcodeMap := make(map[string]int)
|
||||
o.Servers = common.Filter(o.Servers, func(it NewDNSServerOptions) bool {
|
||||
if it.Type == C.DNSTypeLegacyRcode {
|
||||
rcodeMap[it.Tag] = it.Options.(int)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(rcodeMap) > 0 {
|
||||
for i := 0; i < len(o.Rules); i++ {
|
||||
rewriteRcode(rcodeMap, &o.Rules[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
rewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)
|
||||
case C.RuleTypeLogical:
|
||||
rewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
|
||||
if ruleAction.Action != C.RuleActionTypeRoute {
|
||||
return
|
||||
}
|
||||
rcode, loaded := rcodeMap[ruleAction.RouteOptions.Server]
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
ruleAction.Action = C.RuleActionTypePredefined
|
||||
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
|
||||
return
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
@@ -87,7 +126,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content
|
||||
}
|
||||
registry := service.FromContext[DNSTransportOptionsRegistry](ctx)
|
||||
if registry == nil {
|
||||
return E.New("missing outbound options registry in context")
|
||||
return E.New("missing DNS transport options registry in context")
|
||||
}
|
||||
var options any
|
||||
switch o.Type {
|
||||
@@ -102,7 +141,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content
|
||||
return E.New("unknown transport type: ", o.Type)
|
||||
}
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(o), options)
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_NewDNSServerOptions)(o), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,12 +217,10 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.Addr.String()
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 53 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
case C.DNSTypeTCP:
|
||||
o.Type = C.DNSTypeTCP
|
||||
o.Options = &remoteOptions
|
||||
@@ -191,19 +228,17 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.Addr.String()
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 53 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
case C.DNSTypeTLS, C.DNSTypeQUIC:
|
||||
o.Type = serverType
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.Addr.String()
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 853 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
@@ -222,7 +257,7 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
httpsOptions.Server = serverAddr.Addr.String()
|
||||
httpsOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 443 {
|
||||
httpsOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
@@ -247,14 +282,8 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
default:
|
||||
return E.New("unknown rcode: ", serverURL.Host)
|
||||
}
|
||||
o.Type = C.DNSTypePreDefined
|
||||
o.Options = &PredefinedDNSServerOptions{
|
||||
Responses: []DNSResponseOptions{
|
||||
{
|
||||
RCode: common.Ptr(DNSRCode(rcode)),
|
||||
},
|
||||
},
|
||||
}
|
||||
o.Type = C.DNSTypeLegacyRcode
|
||||
o.Options = rcode
|
||||
case C.DNSTypeDHCP:
|
||||
o.Type = C.DNSTypeDHCP
|
||||
dhcpOptions := DHCPDNSServerOptions{}
|
||||
@@ -287,8 +316,8 @@ type LegacyDNSServerOptions struct {
|
||||
}
|
||||
|
||||
type HostsDNSServerOptions struct {
|
||||
Path badoption.Listable[string] `json:"path,omitempty"`
|
||||
Predefined badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
||||
Path badoption.Listable[string] `json:"path,omitempty"`
|
||||
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
||||
}
|
||||
|
||||
type LocalDNSServerOptions struct {
|
||||
|
||||
@@ -3,30 +3,14 @@ package option
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type PredefinedDNSServerOptions struct {
|
||||
Responses []DNSResponseOptions `json:"responses,omitempty"`
|
||||
}
|
||||
|
||||
type DNSResponseOptions struct {
|
||||
Query badoption.Listable[string] `json:"query,omitempty"`
|
||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||
|
||||
RCode *DNSRCode `json:"rcode,omitempty"`
|
||||
Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"`
|
||||
Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"`
|
||||
Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
type DNSRCode int
|
||||
|
||||
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
||||
@@ -64,49 +48,6 @@ func (r *DNSRCode) Build() int {
|
||||
return int(*r)
|
||||
}
|
||||
|
||||
func (o DNSResponseOptions) Build() ([]dns.Question, *dns.Msg, error) {
|
||||
var questions []dns.Question
|
||||
if len(o.Query) == 0 && len(o.QueryType) == 0 {
|
||||
questions = []dns.Question{{Qclass: dns.ClassINET}}
|
||||
} else if len(o.Query) == 0 {
|
||||
for _, queryType := range o.QueryType {
|
||||
questions = append(questions, dns.Question{
|
||||
Qtype: uint16(queryType),
|
||||
Qclass: dns.ClassINET,
|
||||
})
|
||||
}
|
||||
} else if len(o.QueryType) == 0 {
|
||||
for _, domain := range o.Query {
|
||||
questions = append(questions, dns.Question{
|
||||
Name: dns.Fqdn(domain),
|
||||
Qclass: dns.ClassINET,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, queryType := range o.QueryType {
|
||||
for _, domain := range o.Query {
|
||||
questions = append(questions, dns.Question{
|
||||
Name: dns.Fqdn(domain),
|
||||
Qtype: uint16(queryType),
|
||||
Qclass: dns.ClassINET,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return questions, &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Response: true,
|
||||
Rcode: o.RCode.Build(),
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Answer: common.Map(o.Answer, DNSRecordOptions.build),
|
||||
Ns: common.Map(o.Ns, DNSRecordOptions.build),
|
||||
Extra: common.Map(o.Extra, DNSRecordOptions.build),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type DNSRecordOptions struct {
|
||||
dns.RR
|
||||
fromBase64 bool
|
||||
@@ -156,6 +97,6 @@ func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o DNSRecordOptions) build() dns.RR {
|
||||
func (o DNSRecordOptions) Build() dns.RR {
|
||||
return o.RR
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ type _DNSRuleAction struct {
|
||||
RouteOptions DNSRouteActionOptions `json:"-"`
|
||||
RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"`
|
||||
RejectOptions RejectActionOptions `json:"-"`
|
||||
PredefinedOptions DNSRouteActionPredefined `json:"-"`
|
||||
}
|
||||
|
||||
type DNSRuleAction _DNSRuleAction
|
||||
@@ -109,6 +110,8 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
||||
v = r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
v = r.RejectOptions
|
||||
case C.RuleActionTypePredefined:
|
||||
v = r.PredefinedOptions
|
||||
default:
|
||||
return nil, E.New("unknown DNS rule action: " + r.Action)
|
||||
}
|
||||
@@ -129,6 +132,8 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
|
||||
v = &r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
v = &r.RejectOptions
|
||||
case C.RuleActionTypePredefined:
|
||||
v = &r.PredefinedOptions
|
||||
default:
|
||||
return E.New("unknown DNS rule action: " + r.Action)
|
||||
}
|
||||
@@ -294,3 +299,10 @@ type RouteActionResolve struct {
|
||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||
}
|
||||
|
||||
type DNSRouteActionPredefined struct {
|
||||
Rcode *DNSRCode `json:"rcode,omitempty"`
|
||||
Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"`
|
||||
Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"`
|
||||
Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
@@ -51,8 +51,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
outbound.tlsConfig = tlsConfig
|
||||
|
||||
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
RemoteIsDomain: options.ServerIsDomain(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -63,6 +64,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
Password: options.Password,
|
||||
IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(),
|
||||
IdleSessionTimeout: options.IdleSessionTimeout.Build(),
|
||||
MinIdleSession: options.MinIdleSession,
|
||||
DialOut: outbound.dialOut,
|
||||
Logger: logger,
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/udpnat2"
|
||||
@@ -80,7 +81,7 @@ func (i *Inbound) Close() error {
|
||||
}
|
||||
|
||||
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
|
||||
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
|
||||
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)
|
||||
}
|
||||
|
||||
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
@@ -104,7 +105,6 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
||||
|
||||
func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
i.logger.InfoContext(ctx, "inbound packet connection from ", source)
|
||||
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = i.Tag()
|
||||
metadata.InboundType = i.Type()
|
||||
@@ -123,8 +123,11 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
destination.Port = i.overrideDestination.Port
|
||||
default:
|
||||
}
|
||||
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
|
||||
metadata.Destination = destination
|
||||
metadata.OriginDestination = i.listener.UDPAddr()
|
||||
if i.overrideOption != 0 {
|
||||
conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)
|
||||
}
|
||||
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
if options.Detour != "" {
|
||||
return nil, E.New("`detour` is not supported in direct context")
|
||||
}
|
||||
outboundDialer, err := dialer.New(ctx, options.DialerOptions, false)
|
||||
outboundDialer, err := dialer.New(ctx, options.DialerOptions, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
logger: logger,
|
||||
}
|
||||
var err error
|
||||
inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -165,22 +165,17 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
|
||||
} else {
|
||||
originDestination = metadata.Destination
|
||||
}
|
||||
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
} else if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
} else {
|
||||
remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
}
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
|
||||
} else {
|
||||
remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
|
||||
}
|
||||
remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
|
||||
}
|
||||
var udpTimeout time.Duration
|
||||
if metadata.UDPTimeout > 0 {
|
||||
@@ -295,13 +290,17 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
|
||||
func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
|
||||
_, err := bufio.CopyPacket(destination, source)
|
||||
if !direction {
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if err == nil {
|
||||
m.logger.DebugContext(ctx, "packet upload finished")
|
||||
} else if E.IsClosedOrCanceled(err) {
|
||||
m.logger.TraceContext(ctx, "packet upload closed")
|
||||
} else {
|
||||
m.logger.DebugContext(ctx, "packet upload closed: ", err)
|
||||
}
|
||||
} else {
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if err == nil {
|
||||
m.logger.DebugContext(ctx, "packet download finished")
|
||||
} else if E.IsClosedOrCanceled(err) {
|
||||
m.logger.TraceContext(ctx, "packet download closed")
|
||||
} else {
|
||||
m.logger.DebugContext(ctx, "packet download closed: ", err)
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) {
|
||||
@@ -126,6 +128,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
|
||||
NoDrop: action.RejectOptions.NoDrop,
|
||||
logger: logger,
|
||||
}
|
||||
case C.RuleActionTypePredefined:
|
||||
return &RuleActionPredefined{
|
||||
Rcode: action.PredefinedOptions.Rcode.Build(),
|
||||
Answer: common.Map(action.PredefinedOptions.Answer, option.DNSRecordOptions.Build),
|
||||
Ns: common.Map(action.PredefinedOptions.Ns, option.DNSRecordOptions.Build),
|
||||
Extra: common.Map(action.PredefinedOptions.Extra, option.DNSRecordOptions.Build),
|
||||
}
|
||||
default:
|
||||
panic(F.ToString("unknown rule action: ", action.Action))
|
||||
}
|
||||
@@ -413,3 +422,23 @@ func (r *RuleActionResolve) String() string {
|
||||
return F.ToString("resolve(", strings.Join(options, ","), ")")
|
||||
}
|
||||
}
|
||||
|
||||
type RuleActionPredefined struct {
|
||||
Rcode int
|
||||
Answer []dns.RR
|
||||
Ns []dns.RR
|
||||
Extra []dns.RR
|
||||
}
|
||||
|
||||
func (r *RuleActionPredefined) Type() string {
|
||||
return C.RuleActionTypePredefined
|
||||
}
|
||||
|
||||
func (r *RuleActionPredefined) String() string {
|
||||
var options []string
|
||||
options = append(options, dns.RcodeToString[r.Rcode])
|
||||
options = append(options, common.Map(r.Answer, dns.RR.String)...)
|
||||
options = append(options, common.Map(r.Ns, dns.RR.String)...)
|
||||
options = append(options, common.Map(r.Extra, dns.RR.String)...)
|
||||
return F.ToString("predefined(", strings.Join(options, ","), ")")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
var _ net.Conn = (*GunConn)(nil)
|
||||
|
||||
type GunConn struct {
|
||||
rawReader io.Reader
|
||||
reader *std_bufio.Reader
|
||||
writer io.Writer
|
||||
flusher http.Flusher
|
||||
@@ -31,9 +32,10 @@ type GunConn struct {
|
||||
|
||||
func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {
|
||||
return &GunConn{
|
||||
reader: std_bufio.NewReader(reader),
|
||||
writer: writer,
|
||||
flusher: flusher,
|
||||
rawReader: reader,
|
||||
reader: std_bufio.NewReader(reader),
|
||||
writer: writer,
|
||||
flusher: flusher,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ func newLateGunConn(writer io.Writer) *GunConn {
|
||||
|
||||
func (c *GunConn) setup(reader io.Reader, err error) {
|
||||
if reader != nil {
|
||||
c.rawReader = reader
|
||||
c.reader = std_bufio.NewReader(reader)
|
||||
}
|
||||
c.err = err
|
||||
@@ -138,7 +141,7 @@ func (c *GunConn) FrontHeadroom() int {
|
||||
}
|
||||
|
||||
func (c *GunConn) Close() error {
|
||||
return common.Close(c.reader, c.writer)
|
||||
return common.Close(c.rawReader, c.writer)
|
||||
}
|
||||
|
||||
func (c *GunConn) LocalAddr() net.Addr {
|
||||
|
||||
Reference in New Issue
Block a user