mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-01 19:07:35 +03:00
Move predefined DNS server to rule action
This commit is contained in:
parent
6d6789a4c5
commit
7473db9515
15 changed files with 289 additions and 282 deletions
|
@ -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 {
|
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{
|
return &resolveDialer{
|
||||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
|
@ -60,21 +74,6 @@ type resolveParallelNetworkDialer struct {
|
||||||
dialer ParallelInterfaceDialer
|
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 {
|
func (d *resolveDialer) initialize() error {
|
||||||
d.initOnce.Do(d.initServer)
|
d.initOnce.Do(d.initServer)
|
||||||
return d.initErr
|
return d.initErr
|
||||||
|
|
|
@ -15,19 +15,19 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DNSTypeLegacy = "legacy"
|
DNSTypeLegacy = "legacy"
|
||||||
DNSTypeUDP = "udp"
|
DNSTypeLegacyRcode = "legacy_rcode"
|
||||||
DNSTypeTCP = "tcp"
|
DNSTypeUDP = "udp"
|
||||||
DNSTypeTLS = "tls"
|
DNSTypeTCP = "tcp"
|
||||||
DNSTypeHTTPS = "https"
|
DNSTypeTLS = "tls"
|
||||||
DNSTypeQUIC = "quic"
|
DNSTypeHTTPS = "https"
|
||||||
DNSTypeHTTP3 = "h3"
|
DNSTypeQUIC = "quic"
|
||||||
DNSTypeHosts = "hosts"
|
DNSTypeHTTP3 = "h3"
|
||||||
DNSTypeLocal = "local"
|
DNSTypeLocal = "local"
|
||||||
DNSTypePreDefined = "predefined"
|
DNSTypeHosts = "hosts"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -33,6 +33,7 @@ const (
|
||||||
RuleActionTypeHijackDNS = "hijack-dns"
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
RuleActionTypeSniff = "sniff"
|
RuleActionTypeSniff = "sniff"
|
||||||
RuleActionTypeResolve = "resolve"
|
RuleActionTypeResolve = "resolve"
|
||||||
|
RuleActionTypePredefined = "predefined"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -190,6 +190,8 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||||
}
|
}
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
return nil, currentRule, currentRuleIndex
|
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:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
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
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
|
@ -376,6 +393,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
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
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
|
@ -395,6 +426,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||||
printResult()
|
printResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
response:
|
||||||
printResult()
|
printResult()
|
||||||
if len(responseAddrs) > 0 {
|
if len(responseAddrs) > 0 {
|
||||||
r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
|
r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -4,7 +4,8 @@ icon: material/new-box
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! 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"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
|
@ -31,6 +32,8 @@ Tag of target server.
|
||||||
|
|
||||||
#### strategy
|
#### strategy
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
Set domain strategy for this query.
|
Set domain strategy for this query.
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
@ -69,7 +72,7 @@ Will overrides `dns.client_subnet`.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "reject",
|
"action": "reject",
|
||||||
"method": "default", // default
|
"method": "",
|
||||||
"no_drop": false
|
"no_drop": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -81,8 +84,61 @@ Will overrides `dns.client_subnet`.
|
||||||
- `default`: Reply with NXDOMAIN.
|
- `default`: Reply with NXDOMAIN.
|
||||||
- `drop`: Drop the request.
|
- `drop`: Drop the request.
|
||||||
|
|
||||||
|
`default` will be used by default.
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||||
|
|
||||||
Not available when `method` is set to drop.
|
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 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
:material-plus: [strategy](#strategy)
|
||||||
|
:material-plus: [predefined](#predefined)
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
|
@ -12,9 +13,9 @@ icon: material/new-box
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "route", // 默认
|
"action": "route",
|
||||||
|
// 默认
|
||||||
"server": "",
|
"server": "",
|
||||||
|
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
|
@ -32,6 +33,8 @@ icon: material/new-box
|
||||||
|
|
||||||
#### strategy
|
#### strategy
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
为此查询设置域名策略。
|
为此查询设置域名策略。
|
||||||
|
|
||||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
@ -70,7 +73,7 @@ icon: material/new-box
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "reject",
|
"action": "reject",
|
||||||
"method": "default", // default
|
"method": "",
|
||||||
"no_drop": false
|
"no_drop": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -82,8 +85,61 @@ icon: material/new-box
|
||||||
- `default`: 返回 NXDOMAIN。
|
- `default`: 返回 NXDOMAIN。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
|
默认使用 `defualt`。
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
||||||
|
|
||||||
当 `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 记录列表。
|
||||||
|
|
|
@ -67,4 +67,30 @@ Example:
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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.
|
|
|
@ -107,7 +107,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
||||||
transport.RegisterUDP(registry)
|
transport.RegisterUDP(registry)
|
||||||
transport.RegisterTLS(registry)
|
transport.RegisterTLS(registry)
|
||||||
transport.RegisterHTTPS(registry)
|
transport.RegisterHTTPS(registry)
|
||||||
transport.RegisterPredefined(registry)
|
|
||||||
hosts.RegisterTransport(registry)
|
hosts.RegisterTransport(registry)
|
||||||
local.RegisterTransport(registry)
|
local.RegisterTransport(registry)
|
||||||
fakeip.RegisterTransport(registry)
|
fakeip.RegisterTransport(registry)
|
||||||
|
|
|
@ -91,7 +91,6 @@ nav:
|
||||||
- QUIC: configuration/dns/server/quic.md
|
- QUIC: configuration/dns/server/quic.md
|
||||||
- HTTPS: configuration/dns/server/https.md
|
- HTTPS: configuration/dns/server/https.md
|
||||||
- HTTP3: configuration/dns/server/http3.md
|
- HTTP3: configuration/dns/server/http3.md
|
||||||
- Predefined: configuration/dns/server/predefined.md
|
|
||||||
- DHCP: configuration/dns/server/dhcp.md
|
- DHCP: configuration/dns/server/dhcp.md
|
||||||
- FakeIP: configuration/dns/server/fakeip.md
|
- FakeIP: configuration/dns/server/fakeip.md
|
||||||
- Tailscale: configuration/dns/server/tailscale.md
|
- Tailscale: configuration/dns/server/tailscale.md
|
||||||
|
|
|
@ -46,7 +46,46 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e
|
||||||
}
|
}
|
||||||
legacyOptions := o.LegacyDNSOptions
|
legacyOptions := o.LegacyDNSOptions
|
||||||
o.LegacyDNSOptions = 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 {
|
type DNSClientOptions struct {
|
||||||
|
@ -243,14 +282,8 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
|
||||||
default:
|
default:
|
||||||
return E.New("unknown rcode: ", serverURL.Host)
|
return E.New("unknown rcode: ", serverURL.Host)
|
||||||
}
|
}
|
||||||
o.Type = C.DNSTypePreDefined
|
o.Type = C.DNSTypeLegacyRcode
|
||||||
o.Options = &PredefinedDNSServerOptions{
|
o.Options = rcode
|
||||||
Responses: []DNSResponseOptions{
|
|
||||||
{
|
|
||||||
RCode: common.Ptr(DNSRCode(rcode)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case C.DNSTypeDHCP:
|
case C.DNSTypeDHCP:
|
||||||
o.Type = C.DNSTypeDHCP
|
o.Type = C.DNSTypeDHCP
|
||||||
dhcpOptions := DHCPDNSServerOptions{}
|
dhcpOptions := DHCPDNSServerOptions{}
|
||||||
|
|
|
@ -3,30 +3,14 @@ package option
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"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
|
type DNSRCode int
|
||||||
|
|
||||||
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -64,49 +48,6 @@ func (r *DNSRCode) Build() int {
|
||||||
return int(*r)
|
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 {
|
type DNSRecordOptions struct {
|
||||||
dns.RR
|
dns.RR
|
||||||
fromBase64 bool
|
fromBase64 bool
|
||||||
|
@ -156,6 +97,6 @@ func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o DNSRecordOptions) build() dns.RR {
|
func (o DNSRecordOptions) Build() dns.RR {
|
||||||
return o.RR
|
return o.RR
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ type _DNSRuleAction struct {
|
||||||
RouteOptions DNSRouteActionOptions `json:"-"`
|
RouteOptions DNSRouteActionOptions `json:"-"`
|
||||||
RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"`
|
RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"`
|
||||||
RejectOptions RejectActionOptions `json:"-"`
|
RejectOptions RejectActionOptions `json:"-"`
|
||||||
|
PredefinedOptions DNSRouteActionPredefined `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRuleAction _DNSRuleAction
|
type DNSRuleAction _DNSRuleAction
|
||||||
|
@ -109,6 +110,8 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
||||||
v = r.RouteOptionsOptions
|
v = r.RouteOptionsOptions
|
||||||
case C.RuleActionTypeReject:
|
case C.RuleActionTypeReject:
|
||||||
v = r.RejectOptions
|
v = r.RejectOptions
|
||||||
|
case C.RuleActionTypePredefined:
|
||||||
|
v = r.PredefinedOptions
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown DNS rule action: " + r.Action)
|
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
|
v = &r.RouteOptionsOptions
|
||||||
case C.RuleActionTypeReject:
|
case C.RuleActionTypeReject:
|
||||||
v = &r.RejectOptions
|
v = &r.RejectOptions
|
||||||
|
case C.RuleActionTypePredefined:
|
||||||
|
v = &r.PredefinedOptions
|
||||||
default:
|
default:
|
||||||
return E.New("unknown DNS rule action: " + r.Action)
|
return E.New("unknown DNS rule action: " + r.Action)
|
||||||
}
|
}
|
||||||
|
@ -294,3 +299,10 @@ type RouteActionResolve struct {
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,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"`
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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) {
|
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,
|
NoDrop: action.RejectOptions.NoDrop,
|
||||||
logger: logger,
|
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:
|
default:
|
||||||
panic(F.ToString("unknown rule action: ", action.Action))
|
panic(F.ToString("unknown rule action: ", action.Action))
|
||||||
}
|
}
|
||||||
|
@ -413,3 +422,23 @@ func (r *RuleActionResolve) String() string {
|
||||||
return F.ToString("resolve(", strings.Join(options, ","), ")")
|
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, ","), ")")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue