Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,9 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
default:
err = fmt.Errorf("unsupported RCode type: %s", addr)
}
case "records":
dnsNetType = "records"
addr, err = checkRecords(u.Host)
default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
}
Expand Down Expand Up @@ -1263,6 +1266,31 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
return nameservers, nil
}

// checkRecords check host is '[ip4],[ip6]', ip4 is required
func checkRecords(host string) (string, error) {
ips := strings.Split(host+",", ",")
ip4 := ips[0]
ip6 := ips[1]

log.Debugln("ns-policy records check format => [%s]", host)

// check ip4 format
ip4Addr, err := netip.ParseAddr(ip4)
if err != nil || !ip4Addr.Is4() {
return "", fmt.Errorf("ns-policy records[0] format error: records://%s", host)
}

// if ip6 not empty, check ip6
if len(ip6) > 0 {
ip6Addr, err := netip.ParseAddr(ip6)
if err != nil || !ip6Addr.Is6() {
return "", fmt.Errorf("ns-policy records[1] format error: records://%s", host)
}
}

return host, nil
}

func init() {
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
return parseNameServer(servers, false, false)
Expand Down
69 changes: 69 additions & 0 deletions dns/records.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dns

import (
"context"
"fmt"
"net"
"strings"

"github.com/metacubex/mihomo/log"
D "github.com/miekg/dns"
)

func newRecordsClient(addr string) recordsClient {
return recordsClient{
rcode: D.RcodeSuccess,
addr: "records://" + addr,
}
}

type recordsClient struct {
rcode int
addr string
}

var _ dnsClient = (*recordsClient)(nil)

func (r recordsClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
m.Response = true
m.Rcode = r.rcode

// split ip
ips := strings.Split(r.addr[len("records://"):]+",", ",")
ip4 := ips[0]
ip6 := ips[1]

var q *D.Question
if len(m.Question) > 0 {
q = &m.Question[0]
} else {
return nil, fmt.Errorf("[DNS] ns-policy records resolve failed: no Question")
}

// queryType A/AAAA return ip4/6 records
if q.Qtype == D.TypeA {
rr := &D.A{
Hdr: D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 60},
A: net.ParseIP(ip4),
}
m.Answer = append(m.Answer, rr)
}

if q.Qtype == D.TypeAAAA {
rr := &D.AAAA{
Hdr: D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 60},
AAAA: net.ParseIP(ip6),
}
m.Answer = append(m.Answer, rr)
}

log.Debugln("[DNS] ns-policy %s --> %s from %s", msgToDomain(m), msgToLogString(m), r.Address())

return m, nil
}

func (r recordsClient) Address() string {
return r.addr
}

func (r recordsClient) ResetConnection() {}
61 changes: 61 additions & 0 deletions dns/records_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package dns

import (
"net"
"testing"

D "github.com/miekg/dns"
"github.com/stretchr/testify/assert"
)

func TestExchangeContextTypeA(t *testing.T) {
// new recordsClient
ip4 := "127.0.0.1"
recordsCli := newRecordsClient(ip4 + ",2001:0db8:85a3:0000:0000:8a2e:0370:7334")

// new D.Msg
question := D.Question{
Name: "android-gateway.com.cn",
Qtype: D.TypeA,
Qclass: D.ClassANY,
}

msg := &D.Msg{
Question: []D.Question{question},
}

// test get answer [A]
replyMsg, _ := recordsCli.ExchangeContext(nil, msg)

a, ok := replyMsg.Answer[0].(*D.A)

assert.Equal(t, true, ok)
assert.Equal(t, ip4, a.A.String())
assert.Implements(t, (*D.RR)(nil), replyMsg.Answer[0])
}

func TestExchangeContextTypeAAAA(t *testing.T) {
// new recordsClient
ip6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
recordsCli := newRecordsClient("127.0.0.1," + ip6)

// new D.Msg
question := D.Question{
Name: "android-gateway.com.cn",
Qtype: D.TypeAAAA,
Qclass: D.ClassANY,
}

msg := &D.Msg{
Question: []D.Question{question},
}

// test get answer [AAAA]
replyMsg, _ := recordsCli.ExchangeContext(nil, msg)

reply, ok := replyMsg.Answer[0].(*D.AAAA)

assert.Equal(t, true, ok)
assert.Equal(t, net.ParseIP(ip6).String(), reply.AAAA.String())
assert.Implements(t, (*D.RR)(nil), replyMsg.Answer[0])
}
2 changes: 2 additions & 0 deletions dns/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
c = newRCodeClient(s.Addr)
case "quic":
c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
case "records":
c = newRecordsClient(s.Addr)
default:
c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
}
Expand Down
2 changes: 2 additions & 0 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ dns:
## global,dns 为 rule-providers 中的名为 global 和 dns 规则订阅,
## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
# "rule-set:global,dns": 8.8.8.8
# 配置自定义返回记录, 通过配合fake-ip可透明访问代理网关, 格式(ip4必须): records://{ip4},{ip6}
"+.android-gateway.com.cn": records://127.0.0.1,::1

proxies: # socks5
- name: "socks"
Expand Down