From 624a46fddcf41f180883224aa44a48b7d02f3fa1 Mon Sep 17 00:00:00 2001 From: bigeagle Date: Tue, 3 Feb 2015 22:53:57 +0800 Subject: [PATCH 01/10] dnsmasq style domain specific server supported --- godns.conf | 1 + handler.go | 17 +++------- hosts.go | 8 ++--- hosts_test.go | 18 +++++----- resolver.go | 87 +++++++++++++++++++++++++++++++++++++++++++----- settings.go | 8 +++-- sfx_tree.go | 65 ++++++++++++++++++++++++++++++++++++ sfx_tree_test.go | 49 +++++++++++++++++++++++++++ 8 files changed, 214 insertions(+), 39 deletions(-) create mode 100644 sfx_tree.go create mode 100644 sfx_tree_test.go diff --git a/godns.conf b/godns.conf index 7ce4b6e..5234a62 100644 --- a/godns.conf +++ b/godns.conf @@ -14,6 +14,7 @@ port = 53 [resolv] resolv-file = "/etc/resolv.conf" timeout = 5 # 5 seconds +domain-server-file = "/etc/godns.d/servers" [redis] host = "127.0.0.1" diff --git a/handler.go b/handler.go index 91467c3..ad6a268 100644 --- a/handler.go +++ b/handler.go @@ -33,21 +33,12 @@ type GODNSHandler struct { func NewHandler() *GODNSHandler { var ( - clientConfig *dns.ClientConfig - cacheConfig CacheSettings - resolver *Resolver - cache Cache + cacheConfig CacheSettings + resolver *Resolver + cache Cache ) - resolvConfig := settings.ResolvConfig - clientConfig, err := dns.ClientConfigFromFile(resolvConfig.ResolvFile) - if err != nil { - logger.Printf(":%s is not a valid resolv.conf file\n", resolvConfig.ResolvFile) - logger.Println(err) - panic(err) - } - clientConfig.Timeout = resolvConfig.Timeout - resolver = &Resolver{clientConfig} + resolver = NewResolver(settings.ResolvConfig) cacheConfig = settings.Cache switch cacheConfig.Backend { diff --git a/hosts.go b/hosts.go index 17d6445..7390e76 100644 --- a/hosts.go +++ b/hosts.go @@ -116,7 +116,7 @@ func (f *FileHosts) GetAll() map[string]string { domain := sli[len(sli)-1] ip := sli[0] - if !f.isDomain(domain) || !f.isIP(ip) { + if !isDomain(domain) || !isIP(ip) { continue } @@ -125,14 +125,14 @@ func (f *FileHosts) GetAll() map[string]string { return hosts } -func (f *FileHosts) isDomain(domain string) bool { - if f.isIP(domain) { +func isDomain(domain string) bool { + if isIP(domain) { return false } match, _ := regexp.MatchString("^[a-zA-Z0-9][a-zA-Z0-9-]", domain) return match } -func (f *FileHosts) isIP(ip string) bool { +func isIP(ip string) bool { return (net.ParseIP(ip) != nil) } diff --git a/hosts_test.go b/hosts_test.go index af70747..4499e8a 100644 --- a/hosts_test.go +++ b/hosts_test.go @@ -8,26 +8,24 @@ import ( func TestHostDomainAndIP(t *testing.T) { Convey("Test Host File Domain and IP regex", t, func() { - f := &FileHosts{} - Convey("1.1.1.1 should be IP and not domain", func() { - So(f.isIP("1.1.1.1"), ShouldEqual, true) - So(f.isDomain("1.1.1.1"), ShouldEqual, false) + So(isIP("1.1.1.1"), ShouldEqual, true) + So(isDomain("1.1.1.1"), ShouldEqual, false) }) Convey("2001:470:20::2 should be IP and not domain", func() { - So(f.isIP("2001:470:20::2"), ShouldEqual, true) - So(f.isDomain("2001:470:20::2"), ShouldEqual, false) + So(isIP("2001:470:20::2"), ShouldEqual, true) + So(isDomain("2001:470:20::2"), ShouldEqual, false) }) Convey("`host` should be domain and not IP", func() { - So(f.isDomain("host"), ShouldEqual, true) - So(f.isIP("host"), ShouldEqual, false) + So(isDomain("host"), ShouldEqual, true) + So(isIP("host"), ShouldEqual, false) }) Convey("`123.test` should be domain and not IP", func() { - So(f.isDomain("123.test"), ShouldEqual, true) - So(f.isIP("123.test"), ShouldEqual, false) + So(isDomain("123.test"), ShouldEqual, true) + So(isIP("123.test"), ShouldEqual, false) }) }) diff --git a/resolver.go b/resolver.go index 11756c1..438ed4c 100644 --- a/resolver.go +++ b/resolver.go @@ -1,10 +1,13 @@ package main import ( + "bufio" "fmt" - "github.com/miekg/dns" + "os" "strings" "time" + + "github.com/miekg/dns" ) type ResolvError struct { @@ -18,7 +21,62 @@ func (e ResolvError) Error() string { } type Resolver struct { - config *dns.ClientConfig + config *dns.ClientConfig + domain_server *suffixTreeNode +} + +func NewResolver(c ResolvSettings) *Resolver { + var clientConfig *dns.ClientConfig + clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile) + if err != nil { + logger.Printf(":%s is not a valid resolv.conf file\n", c.ResolvFile) + logger.Println(err) + panic(err) + } + clientConfig.Timeout = c.Timeout + + domain_server := newSuffixTreeRoot() + r := &Resolver{clientConfig, domain_server} + + if len(c.DomainServerFile) > 0 { + r.ReadDomainServerFile(c.DomainServerFile) + } + return r +} + +func (r *Resolver) ReadDomainServerFile(file string) { + buf, err := os.Open(file) + if err != nil { + panic("Can't open " + file) + } + scanner := bufio.NewScanner(buf) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + + if !strings.HasPrefix(line, "server") { + continue + } + + sli := strings.Split(line, "=") + if len(sli) != 2 { + continue + } + + line = strings.TrimSpace(sli[1]) + + tokens := strings.Split(line, "/") + if len(tokens) != 3 { + continue + } + domain := tokens[1] + ip := tokens[2] + if !isDomain(domain) || !isIP(ip) { + continue + } + r.domain_server.sinsert(strings.Split(domain, "."), ip) + } + } func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) { @@ -29,8 +87,8 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error } qname := req.Question[0].Name - - for _, nameserver := range r.Nameservers() { + nameservers := r.Nameservers(qname) + for _, nameserver := range nameservers { r, rtt, err := c.Exchange(req, nameserver) if err != nil { Debug("%s socket error on %s", qname, nameserver) @@ -41,19 +99,30 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error Debug("%s failed to get an valid answer on %s", qname, nameserver) continue } - Debug("%s resolv on %s ttl: %d", UnFqdn(qname), nameserver, rtt) + Debug("%s resolv on %s rtt: %v", UnFqdn(qname), nameserver, rtt) return r, nil } - return nil, ResolvError{qname, r.Nameservers()} - + return nil, ResolvError{qname, nameservers} } -func (r *Resolver) Nameservers() (ns []string) { +func (r *Resolver) Nameservers(qname string) []string { + + queryKeys := strings.Split(qname, ".") + queryKeys = queryKeys[:len(queryKeys)-1] // ignore last '.' + + ns := []string{} + if v, found := r.domain_server.search(queryKeys); found { + Debug("found upstream: %v", v) + server := v + nameserver := server + ":53" + ns = append(ns, nameserver) + } + for _, server := range r.config.Servers { nameserver := server + ":" + r.config.Port ns = append(ns, nameserver) } - return + return ns } func (r *Resolver) Timeout() time.Duration { diff --git a/settings.go b/settings.go index c729b8d..0660532 100644 --- a/settings.go +++ b/settings.go @@ -3,9 +3,10 @@ package main import ( "flag" "fmt" - "github.com/BurntSushi/toml" "os" "strconv" + + "github.com/BurntSushi/toml" ) var ( @@ -24,8 +25,9 @@ type Settings struct { } type ResolvSettings struct { - ResolvFile string `toml:"resolv-file"` - Timeout int + ResolvFile string `toml:"resolv-file"` + DomainServerFile string `toml:"domain-server-file"` + Timeout int } type DNSServerSettings struct { diff --git a/sfx_tree.go b/sfx_tree.go new file mode 100644 index 0000000..bfaca0e --- /dev/null +++ b/sfx_tree.go @@ -0,0 +1,65 @@ +package main + +type suffixTreeNode struct { + key string + value string + children map[string]*suffixTreeNode +} + +func newSuffixTreeRoot() *suffixTreeNode { + return newSuffixTree("", "") +} + +func newSuffixTree(key string, value string) *suffixTreeNode { + root := &suffixTreeNode{ + key: key, + value: value, + children: map[string]*suffixTreeNode{}, + } + return root +} + +func (node *suffixTreeNode) ensureSubTree(key string) { + if _, ok := node.children[key]; !ok { + node.children[key] = newSuffixTree(key, "") + } +} + +func (node *suffixTreeNode) insert(key string, value string) { + if c, ok := node.children[key]; ok { + c.value = value + } else { + node.children[key] = newSuffixTree(key, value) + } +} + +func (node *suffixTreeNode) sinsert(keys []string, value string) { + if len(keys) == 0 { + return + } + + key := keys[len(keys)-1] + if len(keys) > 1 { + node.ensureSubTree(key) + node.children[key].sinsert(keys[:len(keys)-1], value) + return + } + + node.insert(key, value) +} + +func (node *suffixTreeNode) search(keys []string) (string, bool) { + if len(keys) == 0 { + return "", false + } + + key := keys[len(keys)-1] + if n, ok := node.children[key]; ok { + if nextValue, found := n.search(keys[:len(keys)-1]); found { + return nextValue, found + } + return n.value, (n.value != "") + } + + return "", false +} diff --git a/sfx_tree_test.go b/sfx_tree_test.go new file mode 100644 index 0000000..47471dd --- /dev/null +++ b/sfx_tree_test.go @@ -0,0 +1,49 @@ +package main + +import ( + "strings" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_Suffix_Tree(t *testing.T) { + root := newSuffixTreeRoot() + + Convey("Google should not be found", t, func() { + root.insert("cn", "114.114.114.114") + root.sinsert([]string{"baidu", "cn"}, "166.111.8.28") + root.sinsert([]string{"sina", "cn"}, "114.114.114.114") + + v, found := root.search(strings.Split("google.com", ".")) + So(found, ShouldEqual, false) + + v, found = root.search(strings.Split("baidu.cn", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "166.111.8.28") + }) + + Convey("Google should be found", t, func() { + root.sinsert(strings.Split("com", "."), "") + root.sinsert(strings.Split("google.com", "."), "8.8.8.8") + root.sinsert(strings.Split("twitter.com", "."), "8.8.8.8") + root.sinsert(strings.Split("scholar.google.com", "."), "208.67.222.222") + + v, found := root.search(strings.Split("google.com", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "8.8.8.8") + + v, found = root.search(strings.Split("scholar.google.com", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "208.67.222.222") + + v, found = root.search(strings.Split("twitter.com", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "8.8.8.8") + + v, found = root.search(strings.Split("baidu.cn", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "166.111.8.28") + }) + +} From 952f39ddf25fae5ea302df9b8a101ceaaaf25e88 Mon Sep 17 00:00:00 2001 From: bigeagle Date: Tue, 3 Feb 2015 23:03:47 +0800 Subject: [PATCH 02/10] add option to disable redis --- godns.conf | 1 + hosts.go | 20 ++++++++++++++++++-- settings.go | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/godns.conf b/godns.conf index 5234a62..ec06255 100644 --- a/godns.conf +++ b/godns.conf @@ -17,6 +17,7 @@ timeout = 5 # 5 seconds domain-server-file = "/etc/godns.d/servers" [redis] +enable = true host = "127.0.0.1" port = 6379 db = 0 diff --git a/hosts.go b/hosts.go index 7390e76..2af165c 100644 --- a/hosts.go +++ b/hosts.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "errors" "net" "os" "regexp" @@ -17,8 +18,14 @@ type Hosts struct { func NewHosts(hs HostsSettings, rs RedisSettings) Hosts { fileHosts := &FileHosts{hs.HostsFile} - redis := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password} - redisHosts := &RedisHosts{redis, hs.RedisKey} + + var rc *redis.Client + if rs.Enable { + rc = &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password} + } else { + rc = nil + } + redisHosts := &RedisHosts{rc, hs.RedisKey} hosts := Hosts{fileHosts.GetAll(), redisHosts} return hosts @@ -69,17 +76,26 @@ type RedisHosts struct { } func (r *RedisHosts) GetAll() map[string]string { + if r.redis == nil { + return map[string]string{} + } var hosts = make(map[string]string) r.redis.Hgetall(r.key, hosts) return hosts } func (r *RedisHosts) Get(domain string) (ip string, ok bool) { + if r.redis == nil { + return "", false + } b, err := r.redis.Hget(r.key, domain) return string(b), err == nil } func (r *RedisHosts) Set(domain, ip string) (bool, error) { + if r.redis == nil { + return false, errors.New("Redis not enabled") + } return r.redis.Hset(r.key, domain, []byte(ip)) } diff --git a/settings.go b/settings.go index 0660532..1481146 100644 --- a/settings.go +++ b/settings.go @@ -36,6 +36,7 @@ type DNSServerSettings struct { } type RedisSettings struct { + Enable bool Host string Port int DB int From 07283594890426adf8495a921909d513cd0fdc05 Mon Sep 17 00:00:00 2001 From: bigeagle Date: Tue, 3 Feb 2015 23:09:20 +0800 Subject: [PATCH 03/10] redis-enable is now in hosts --- hosts.go | 2 +- settings.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hosts.go b/hosts.go index 2af165c..87ebb01 100644 --- a/hosts.go +++ b/hosts.go @@ -20,7 +20,7 @@ func NewHosts(hs HostsSettings, rs RedisSettings) Hosts { fileHosts := &FileHosts{hs.HostsFile} var rc *redis.Client - if rs.Enable { + if hs.RedisEnable { rc = &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password} } else { rc = nil diff --git a/settings.go b/settings.go index 1481146..4c5b153 100644 --- a/settings.go +++ b/settings.go @@ -36,7 +36,6 @@ type DNSServerSettings struct { } type RedisSettings struct { - Enable bool Host string Port int DB int @@ -58,10 +57,11 @@ type CacheSettings struct { } type HostsSettings struct { - Enable bool - HostsFile string `toml:"host-file"` - RedisKey string `toml:"redis-key"` - TTL uint32 `toml:"ttl"` + Enable bool + HostsFile string `toml:"host-file"` + RedisEnable bool `toml:"redis-enable"` + RedisKey string `toml:"redis-key"` + TTL uint32 `toml:"ttl"` } func init() { From b3b393912280dec8412a2a934401f76c98376ec1 Mon Sep 17 00:00:00 2001 From: bigeagle Date: Tue, 3 Feb 2015 23:20:36 +0800 Subject: [PATCH 04/10] only cache successful lookups --- handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler.go b/handler.go index ad6a268..82102c0 100644 --- a/handler.go +++ b/handler.go @@ -136,7 +136,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) { w.WriteMsg(mesg) - if IPQuery > 0 { + if IPQuery > 0 && len(mesg.Answer) > 0 { err = h.cache.Set(key, mesg) if err != nil { From 3df6ec09f30d9bcf9e1225d7922d228513ba904d Mon Sep 17 00:00:00 2001 From: bigeagle Date: Wed, 4 Feb 2015 16:21:08 +0800 Subject: [PATCH 05/10] Specify server in server-list-file --- examples/china.conf | 5 ++ godns.conf => examples/godns.conf | 2 +- resolver.go | 78 +++++++++++++++++++++---------- settings.go | 6 +-- 4 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 examples/china.conf rename godns.conf => examples/godns.conf (94%) diff --git a/examples/china.conf b/examples/china.conf new file mode 100644 index 0000000..e886276 --- /dev/null +++ b/examples/china.conf @@ -0,0 +1,5 @@ +server=8.8.8.8#53 +server=127.0.0.1#5553 + +server=/baidu.com/114.114.114.114 +# refer https://github.com/felixonmars/dnsmasq-china-list diff --git a/godns.conf b/examples/godns.conf similarity index 94% rename from godns.conf rename to examples/godns.conf index ec06255..ea6f535 100644 --- a/godns.conf +++ b/examples/godns.conf @@ -12,9 +12,9 @@ host = "127.0.0.1" port = 53 [resolv] +server-list-file = "/etc/godns.d/servers" resolv-file = "/etc/resolv.conf" timeout = 5 # 5 seconds -domain-server-file = "/etc/godns.d/servers" [redis] enable = true diff --git a/resolver.go b/resolver.go index 438ed4c..41a93da 100644 --- a/resolver.go +++ b/resolver.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "strconv" "strings" "time" @@ -21,30 +22,40 @@ func (e ResolvError) Error() string { } type Resolver struct { - config *dns.ClientConfig + servers []string domain_server *suffixTreeNode + config *ResolvSettings } func NewResolver(c ResolvSettings) *Resolver { - var clientConfig *dns.ClientConfig - clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile) - if err != nil { - logger.Printf(":%s is not a valid resolv.conf file\n", c.ResolvFile) - logger.Println(err) - panic(err) + r := &Resolver{ + servers: []string{}, + domain_server: newSuffixTreeRoot(), + config: &c, } - clientConfig.Timeout = c.Timeout - domain_server := newSuffixTreeRoot() - r := &Resolver{clientConfig, domain_server} - - if len(c.DomainServerFile) > 0 { - r.ReadDomainServerFile(c.DomainServerFile) + if len(c.ServerListFile) > 0 { + r.ReadServerListFile(c.ServerListFile) + // Debug("%v", r.servers) } + + if len(c.ResolvFile) > 0 { + clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile) + if err != nil { + logger.Printf(":%s is not a valid resolv.conf file\n", c.ResolvFile) + logger.Println(err) + panic(err) + } + for _, server := range clientConfig.Servers { + nameserver := server + ":" + clientConfig.Port + r.servers = append(r.servers, nameserver) + } + } + return r } -func (r *Resolver) ReadDomainServerFile(file string) { +func (r *Resolver) ReadServerListFile(file string) { buf, err := os.Open(file) if err != nil { panic("Can't open " + file) @@ -66,15 +77,35 @@ func (r *Resolver) ReadDomainServerFile(file string) { line = strings.TrimSpace(sli[1]) tokens := strings.Split(line, "/") - if len(tokens) != 3 { - continue + switch len(tokens) { + case 3: + domain := tokens[1] + ip := tokens[2] + if !isDomain(domain) || !isIP(ip) { + continue + } + r.domain_server.sinsert(strings.Split(domain, "."), ip) + case 1: + srv_port := strings.Split(line, "#") + if len(srv_port) > 2 { + continue + } + + ip := "" + if ip = srv_port[0]; !isIP(ip) { + continue + } + + port := "53" + if len(srv_port) == 2 { + if _, err := strconv.Atoi(srv_port[1]); err != nil { + continue + } + port = srv_port[1] + } + r.servers = append(r.servers, ip+":"+port) + } - domain := tokens[1] - ip := tokens[2] - if !isDomain(domain) || !isIP(ip) { - continue - } - r.domain_server.sinsert(strings.Split(domain, "."), ip) } } @@ -118,8 +149,7 @@ func (r *Resolver) Nameservers(qname string) []string { ns = append(ns, nameserver) } - for _, server := range r.config.Servers { - nameserver := server + ":" + r.config.Port + for _, nameserver := range r.servers { ns = append(ns, nameserver) } return ns diff --git a/settings.go b/settings.go index 4c5b153..c4282ad 100644 --- a/settings.go +++ b/settings.go @@ -25,9 +25,9 @@ type Settings struct { } type ResolvSettings struct { - ResolvFile string `toml:"resolv-file"` - DomainServerFile string `toml:"domain-server-file"` - Timeout int + ServerListFile string `toml:"server-list-file"` + ResolvFile string `toml:"resolv-file"` + Timeout int } type DNSServerSettings struct { From 43d8d65438a610f8cef1a1f056ea34b8e7450816 Mon Sep 17 00:00:00 2001 From: kenshinx Date: Thu, 1 Feb 2018 17:09:02 +0800 Subject: [PATCH 06/10] Extract public functions into utils --- hosts.go | 13 ------------- utils.go | 18 ++++++++++++++++++ hosts_test.go => utils_test.go | 0 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 utils.go rename hosts_test.go => utils_test.go (100%) diff --git a/hosts.go b/hosts.go index 360efe8..c234ef7 100644 --- a/hosts.go +++ b/hosts.go @@ -4,7 +4,6 @@ import ( "bufio" "net" "os" - "regexp" "strings" "sync" "time" @@ -240,15 +239,3 @@ func (f *FileHosts) Refresh() { func (f *FileHosts) clear() { f.hosts = make(map[string]string) } - -func isDomain(domain string) bool { - if isIP(domain) { - return false - } - match, _ := regexp.MatchString(`^([a-zA-Z0-9\*]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$`, domain) - return match -} - -func isIP(ip string) bool { - return (net.ParseIP(ip) != nil) -} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..1993d59 --- /dev/null +++ b/utils.go @@ -0,0 +1,18 @@ +package main + +import ( + "net" + "regexp" +) + +func isDomain(domain string) bool { + if isIP(domain) { + return false + } + match, _ := regexp.MatchString(`^([a-zA-Z0-9\*]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$`, domain) + return match +} + +func isIP(ip string) bool { + return (net.ParseIP(ip) != nil) +} diff --git a/hosts_test.go b/utils_test.go similarity index 100% rename from hosts_test.go rename to utils_test.go From 063182e0bdcc3c08d50455010973e8ac5a7685e0 Mon Sep 17 00:00:00 2001 From: kenshinx Date: Thu, 1 Feb 2018 17:43:28 +0800 Subject: [PATCH 07/10] use net.JoinHostPort instead of string join --- resolver.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/resolver.go b/resolver.go index 0789453..555def0 100644 --- a/resolver.go +++ b/resolver.go @@ -3,6 +3,7 @@ package main import ( "bufio" "fmt" + "net" "os" "strconv" "strings" @@ -37,7 +38,6 @@ func NewResolver(c ResolvSettings) *Resolver { if len(c.ServerListFile) > 0 { r.ReadServerListFile(c.ServerListFile) - // Debug("%v", r.servers) } if len(c.ResolvFile) > 0 { @@ -48,7 +48,7 @@ func NewResolver(c ResolvSettings) *Resolver { panic(err) } for _, server := range clientConfig.Servers { - nameserver := server + ":" + clientConfig.Port + nameserver := net.JoinHostPort(server, clientConfig.Port) r.servers = append(r.servers, nameserver) } } @@ -104,8 +104,7 @@ func (r *Resolver) ReadServerListFile(file string) { } port = srv_port[1] } - r.servers = append(r.servers, ip+":"+port) - + r.servers = append(r.servers, net.JoinHostPort(ip, port)) } } From 4b1d61a600a567240114ddb3ed25f0a3bbeb2f17 Mon Sep 17 00:00:00 2001 From: kenshinx Date: Thu, 1 Feb 2018 18:30:41 +0800 Subject: [PATCH 08/10] more pretty logging and ensure query the specific upstream nameserver in async Lookup() function. --- resolver.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/resolver.go b/resolver.go index 555def0..689ab19 100644 --- a/resolver.go +++ b/resolver.go @@ -23,6 +23,12 @@ func (e ResolvError) Error() string { return errmsg } +type RResp struct { + msg *dns.Msg + nameserver string + rtt time.Duration +} + type Resolver struct { servers []string domain_server *suffixTreeNode @@ -126,7 +132,7 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error qname := req.Question[0].Name - res := make(chan *dns.Msg, 1) + res := make(chan *RResp, 1) var wg sync.WaitGroup L := func(nameserver string) { defer wg.Done() @@ -145,11 +151,10 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error if r.Rcode == dns.RcodeServerFailure { return } - } else { - logger.Debug("%s resolv on %s (%s) ttl: %v", UnFqdn(qname), nameserver, net, rtt) } + re := &RResp{r, nameserver, rtt} select { - case res <- r: + case res <- re: default: } } @@ -163,9 +168,9 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error go L(nameserver) // but exit early, if we have an answer select { - case r := <-res: - // logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), nameserver, rtt) - return r, nil + case re := <-res: + logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt) + return re.msg, nil case <-ticker.C: continue } @@ -173,9 +178,9 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error // wait for all the namservers to finish wg.Wait() select { - case r := <-res: - // logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), nameserver, rtt) - return r, nil + case re := <-res: + logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt) + return re.msg, nil default: return nil, ResolvError{qname, net, nameservers} } @@ -190,10 +195,12 @@ func (r *Resolver) Nameservers(qname string) []string { ns := []string{} if v, found := r.domain_server.search(queryKeys); found { - logger.Debug("found upstream: %v", v) + logger.Debug("%s be found in domain server list, upstream: %v", qname, v) server := v - nameserver := server + ":53" + nameserver := net.JoinHostPort(server, "53") ns = append(ns, nameserver) + //Ensure query the specific upstream nameserver in async Lookup() function. + return ns } for _, nameserver := range r.servers { From dc60b4c9a2ce5b699ad07676e94d69f7be0e6384 Mon Sep 17 00:00:00 2001 From: kenshinx Date: Thu, 1 Feb 2018 18:39:40 +0800 Subject: [PATCH 09/10] Supplement test case, www.google.com should match /google.com/ --- sfx_tree_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sfx_tree_test.go b/sfx_tree_test.go index 47471dd..b2f0ae1 100644 --- a/sfx_tree_test.go +++ b/sfx_tree_test.go @@ -33,6 +33,10 @@ func Test_Suffix_Tree(t *testing.T) { So(found, ShouldEqual, true) So(v, ShouldEqual, "8.8.8.8") + v, found = root.search(strings.Split("www.google.com", ".")) + So(found, ShouldEqual, true) + So(v, ShouldEqual, "8.8.8.8") + v, found = root.search(strings.Split("scholar.google.com", ".")) So(found, ShouldEqual, true) So(v, ShouldEqual, "208.67.222.222") From ea21c87183889fbef40f23183cc91c0a7a845f1b Mon Sep 17 00:00:00 2001 From: kenshinx Date: Thu, 1 Feb 2018 19:22:45 +0800 Subject: [PATCH 10/10] Semicolon separate multiple domain list files and update examples --- README.md | 9 +++++-- etc/apple.china.conf | 46 ++++++++++++++++++++++++++++++++++++ etc/china.conf | 5 ---- etc/godns.conf | 5 ++-- etc/google.china.conf | 42 ++++++++++++++++++++++++++++++++ etc/server_list.example.conf | 7 ++++++ resolver.go | 19 +++++++++++---- 7 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 etc/apple.china.conf delete mode 100644 etc/china.conf create mode 100644 etc/google.china.conf create mode 100644 etc/server_list.example.conf diff --git a/README.md b/README.md index 3c801c3..b1b5b01 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,12 @@ Similar to [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html), but support 3. Running - $ sudo ./godns -c godns.conf + $ sudo ./godns -c ./etc/godns.conf 4. Test $ dig www.github.com @127.0.0.1 -More details about how to install and running godns can reference my [blog (Chinese)](http://blog.kenshinx.me/blog/compile-godns/) ## Use godns @@ -58,6 +57,12 @@ resolv-file = "/etc/resolv.conf" If multiple `namerservers` are set in resolv.conf, the upsteam server will try in a top to bottom order +#### server-list-file +Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq. +>server=/google.com/8.8.8.8 + +More cases please refererence [dnsmasq-china-list](https://github.com/felixonmars/dnsmasq-china-list) + #### cache diff --git a/etc/apple.china.conf b/etc/apple.china.conf new file mode 100644 index 0000000..820bf08 --- /dev/null +++ b/etc/apple.china.conf @@ -0,0 +1,46 @@ +server=/adcdownload.apple.com/114.114.114.114 +server=/appldnld.apple.com/114.114.114.114 +server=/cdn-cn1.apple-mapkit.com/114.114.114.114 +server=/cdn-cn2.apple-mapkit.com/114.114.114.114 +server=/cdn-cn3.apple-mapkit.com/114.114.114.114 +server=/cdn-cn4.apple-mapkit.com/114.114.114.114 +server=/cdn.apple-mapkit.com/114.114.114.114 +server=/cdn1.apple-mapkit.com/114.114.114.114 +server=/cdn2.apple-mapkit.com/114.114.114.114 +server=/cdn3.apple-mapkit.com/114.114.114.114 +server=/cdn4.apple-mapkit.com/114.114.114.114 +server=/cds.apple.com/114.114.114.114 +server=/cl1.apple.com/114.114.114.114 +server=/cl2.apple.com.edgekey.net.globalredir.akadns.net/114.114.114.114 +server=/cl2.apple.com.edgekey.net/114.114.114.114 +server=/cl2.apple.com/114.114.114.114 +server=/cl3.apple.com/114.114.114.114 +server=/cl4.apple.com/114.114.114.114 +server=/cl5.apple.com/114.114.114.114 +server=/gsp11-cn.ls.apple.com/114.114.114.114 +server=/gsp12-cn.ls.apple.com/114.114.114.114 +server=/gsp13-cn.ls.apple.com/114.114.114.114 +server=/gsp4-cn.ls.apple.com.edgekey.net.globalredir.akadns.net/114.114.114.114 +server=/gsp4-cn.ls.apple.com.edgekey.net/114.114.114.114 +server=/gsp4-cn.ls.apple.com/114.114.114.114 +server=/gsp5-cn.ls.apple.com/114.114.114.114 +server=/gspe19-cn.ls-apple.com.akadns.net/114.114.114.114 +server=/gspe19-cn.ls.apple.com/114.114.114.114 +server=/gspe21.ls.apple.com/114.114.114.114 +server=/gspe21-ssl.ls.apple.com/114.114.114.114 +server=/gspe35-ssl.ls.apple.com/114.114.114.114 +server=/icloud.cdn-apple.com/114.114.114.114 +server=/images.apple.com/114.114.114.114 +server=/itunes-apple.com.akadns.net/114.114.114.114 +server=/itunes.apple.com/114.114.114.114 +server=/itunesconnect.apple.com/114.114.114.114 +server=/mesu.apple.com/114.114.114.114 +server=/mesu-china.apple.com.akadns.net/114.114.114.114 +server=/phobos-apple.com.akadns.net/114.114.114.114 +server=/phobos.apple.com/114.114.114.114 +server=/store.apple.com/114.114.114.114 +server=/store.storeimages.cdn-apple.com/114.114.114.114 +server=/support.apple.com/114.114.114.114 +server=/swcdn.apple.com/114.114.114.114 +server=/swdist.apple.com/114.114.114.114 +server=/www.apple.com/114.114.114.114 diff --git a/etc/china.conf b/etc/china.conf deleted file mode 100644 index e886276..0000000 --- a/etc/china.conf +++ /dev/null @@ -1,5 +0,0 @@ -server=8.8.8.8#53 -server=127.0.0.1#5553 - -server=/baidu.com/114.114.114.114 -# refer https://github.com/felixonmars/dnsmasq-china-list diff --git a/etc/godns.conf b/etc/godns.conf index 50429f2..a94be7a 100644 --- a/etc/godns.conf +++ b/etc/godns.conf @@ -12,8 +12,9 @@ host = "127.0.0.1" port = 53 [resolv] -#Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq -server-list-file = "./etc/china.conf" +# Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq +# Semicolon separate multiple files. +server-list-file = "./etc/apple.china.conf;./etc/google.china.conf" resolv-file = "/etc/resolv.conf" timeout = 5 # 5 seconds # The concurrency interval request upstream recursive server diff --git a/etc/google.china.conf b/etc/google.china.conf new file mode 100644 index 0000000..0928c70 --- /dev/null +++ b/etc/google.china.conf @@ -0,0 +1,42 @@ +server=/265.com/114.114.114.114 +server=/2mdn.net/114.114.114.114 +server=/app-measurement.com/114.114.114.114 +server=/beacons.gcp.gvt2.com/114.114.114.114 +server=/beacons.gvt2.com/114.114.114.114 +server=/beacons3.gvt2.com/114.114.114.114 +server=/c.admob.com/114.114.114.114 +server=/c.android.clients.google.com/114.114.114.114 +server=/cache.pack.google.com/114.114.114.114 +server=/clientservices.googleapis.com/114.114.114.114 +server=/connectivitycheck.gstatic.com/114.114.114.114 +server=/csi.gstatic.com/114.114.114.114 +server=/dl.google.com/114.114.114.114 +server=/doubleclick.net/114.114.114.114 +server=/e.admob.com/114.114.114.114 +server=/fonts.googleapis.com/114.114.114.114 +server=/fonts.gstatic.com/114.114.114.114 +server=/google-analytics.com/114.114.114.114 +server=/googleadservices.com/114.114.114.114 +server=/googleanalytics.com/114.114.114.114 +server=/googlesyndication.com/114.114.114.114 +server=/googletagmanager.com/114.114.114.114 +server=/googletagservices.com/114.114.114.114 +server=/imasdk.googleapis.com/114.114.114.114 +server=/kh.google.com/114.114.114.114 +server=/khm.google.com/114.114.114.114 +server=/khm.googleapis.com/114.114.114.114 +server=/khm0.google.com/114.114.114.114 +server=/khm0.googleapis.com/114.114.114.114 +server=/khm1.google.com/114.114.114.114 +server=/khm1.googleapis.com/114.114.114.114 +server=/khm2.google.com/114.114.114.114 +server=/khm2.googleapis.com/114.114.114.114 +server=/khm3.google.com/114.114.114.114 +server=/khm3.googleapis.com/114.114.114.114 +server=/khmdb.google.com/114.114.114.114 +server=/khmdb.googleapis.com/114.114.114.114 +server=/media.admob.com/114.114.114.114 +server=/mediavisor.doubleclick.com/114.114.114.114 +server=/redirector.gvt1.com/114.114.114.114 +server=/toolbarqueries.google.com/114.114.114.114 +server=/update.googleapis.com/114.114.114.114 diff --git a/etc/server_list.example.conf b/etc/server_list.example.conf new file mode 100644 index 0000000..c0847fe --- /dev/null +++ b/etc/server_list.example.conf @@ -0,0 +1,7 @@ +# Default upstream servers which have higher priority than the nameserver +# that configuration in `/etc/resolv.conf` +server=8.8.8.8#53 + +server=/google.com/8.8.8.8 +server=/baidu.com/114.114.114.114 +# refer https://github.com/felixonmars/dnsmasq-china-list diff --git a/resolver.go b/resolver.go index 689ab19..1afe8b8 100644 --- a/resolver.go +++ b/resolver.go @@ -62,11 +62,7 @@ func NewResolver(c ResolvSettings) *Resolver { return r } -func (r *Resolver) ReadServerListFile(file string) { - buf, err := os.Open(file) - if err != nil { - panic("Can't open " + file) - } +func (r *Resolver) parseServerListFile(buf *os.File) { scanner := bufio.NewScanner(buf) for scanner.Scan() { line := scanner.Text() @@ -88,6 +84,7 @@ func (r *Resolver) ReadServerListFile(file string) { case 3: domain := tokens[1] ip := tokens[2] + if !isDomain(domain) || !isIP(ip) { continue } @@ -116,6 +113,18 @@ func (r *Resolver) ReadServerListFile(file string) { } +func (r *Resolver) ReadServerListFile(path string) { + files := strings.Split(path, ";") + for _, file := range files { + buf, err := os.Open(file) + if err != nil { + panic("Can't open " + file) + } + defer buf.Close() + r.parseServerListFile(buf) + } +} + // Lookup will ask each nameserver in top-to-bottom fashion, starting a new request // in every second, and return as early as possbile (have an answer). // It returns an error if no request has succeeded.