Feature(dns): support custom hosts

This commit is contained in:
Dreamacro
2019-07-14 19:29:58 +08:00
parent f867f02546
commit 1a21c8ebfd
10 changed files with 359 additions and 79 deletions

121
dns/middleware.go Normal file
View File

@ -0,0 +1,121 @@
package dns
import (
"fmt"
"net"
"strings"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
type handler func(w D.ResponseWriter, r *D.Msg)
func withFakeIP(cache *cache.Cache, pool *fakeip.Pool) handler {
return func(w D.ResponseWriter, r *D.Msg) {
q := r.Question[0]
cacheItem := cache.Get("fakeip:" + q.String())
if cache != nil {
msg := cacheItem.(*D.Msg).Copy()
setMsgTTL(msg, 1)
msg.SetReply(r)
w.WriteMsg(msg)
return
}
rr := &D.A{}
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
ip := pool.Get()
rr.A = ip
msg := r.Copy()
msg.Answer = []D.RR{rr}
putMsgToCache(cache, "fakeip:"+q.String(), msg)
putMsgToCache(cache, ip.String(), msg)
setMsgTTL(msg, 1)
return
}
}
func withResolver(resolver *Resolver) handler {
return func(w D.ResponseWriter, r *D.Msg) {
msg, err := resolver.Exchange(r)
if err != nil {
q := r.Question[0]
qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String())
log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err)
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
w.WriteMsg(msg)
return
}
}
func withHost(resolver *Resolver, next handler) handler {
hosts := resolver.hosts
if hosts == nil {
panic("dns/withHost: hosts should not be nil")
}
return func(w D.ResponseWriter, r *D.Msg) {
q := r.Question[0]
if q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA {
next(w, r)
return
}
domain := strings.TrimRight(q.Name, ".")
host := hosts.Search(domain)
if host == nil {
next(w, r)
return
}
ip := host.Data.(net.IP)
if q.Qtype == D.TypeAAAA && ip.To16() == nil {
next(w, r)
return
} else if q.Qtype == D.TypeA && ip.To4() == nil {
next(w, r)
return
}
var rr D.RR
if q.Qtype == D.TypeAAAA {
record := &D.AAAA{}
record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
record.AAAA = ip
rr = record
} else {
record := &D.A{}
record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
record.A = ip
rr = record
}
msg := r.Copy()
msg.Answer = []D.RR{rr}
msg.SetReply(r)
w.WriteMsg(msg)
return
}
}
func newHandler(resolver *Resolver) handler {
if resolver.IsFakeIP() {
return withFakeIP(resolver.cache, resolver.pool)
}
if resolver.hosts != nil {
return withHost(resolver, withResolver(resolver))
}
return withResolver(resolver)
}

View File

@ -11,11 +11,13 @@ import (
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
"golang.org/x/sync/singleflight"
)
var (
@ -44,9 +46,11 @@ type Resolver struct {
ipv6 bool
mapping bool
fakeip bool
hosts *trie.Trie
pool *fakeip.Pool
fallback []resolver
main []resolver
group singleflight.Group
cache *cache.Cache
}
@ -134,13 +138,20 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
msg, err = r.fallbackExchange(m)
return
ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) {
isIPReq := isIPRequest(q)
if isIPReq {
msg, err := r.fallbackExchange(m)
return msg, err
}
return r.batchExchange(r.main, m)
})
if err == nil {
msg = ret.(*D.Msg)
}
msg, err = r.batchExchange(r.main, m)
return
}
@ -266,6 +277,7 @@ type Config struct {
Main, Fallback []NameServer
IPv6 bool
EnhancedMode EnhancedMode
Hosts *trie.Trie
Pool *fakeip.Pool
}
@ -280,6 +292,7 @@ func New(config Config) *Resolver {
cache: cache.New(time.Second * 60),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
hosts: config.Hosts,
pool: config.Pool,
}
if len(config.Fallback) != 0 {

View File

@ -1,12 +1,8 @@
package dns
import (
"errors"
"fmt"
"net"
"github.com/Dreamacro/clash/log"
"github.com/miekg/dns"
D "github.com/miekg/dns"
)
@ -19,79 +15,26 @@ var (
type Server struct {
*D.Server
r *Resolver
handler handler
}
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
if s.r.IsFakeIP() {
msg, err := s.handleFakeIP(r)
if err != nil {
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
w.WriteMsg(msg)
return
}
msg, err := s.r.Exchange(r)
if err != nil {
if len(r.Question) > 0 {
q := r.Question[0]
qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String())
log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err)
}
if len(r.Question) == 0 {
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
w.WriteMsg(msg)
s.handler(w, r)
}
func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
if len(r.Question) == 0 {
err = errors.New("should have one question at least")
return
}
q := r.Question[0]
cache := s.r.cache.Get("fakeip:" + q.String())
if cache != nil {
msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, 1)
return
}
var ip net.IP
defer func() {
if msg == nil {
return
}
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
putMsgToCache(s.r.cache, ip.String(), msg)
setMsgTTL(msg, 1)
}()
rr := &D.A{}
rr.Hdr = dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: dnsDefaultTTL}
ip = s.r.pool.Get()
rr.A = ip
msg = r.Copy()
msg.Answer = []D.RR{rr}
return
}
func (s *Server) setReslover(r *Resolver) {
s.r = r
func (s *Server) setHandler(handler handler) {
s.handler = handler
}
func ReCreateServer(addr string, resolver *Resolver) error {
if addr == address {
server.setReslover(resolver)
handler := newHandler(resolver)
server.setHandler(handler)
return nil
}
@ -116,7 +59,8 @@ func ReCreateServer(addr string, resolver *Resolver) error {
}
address = addr
server = &Server{r: resolver}
handler := newHandler(resolver)
server = &Server{handler: handler}
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
go func() {