Initial restructure of server
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
764e326a4b
commit
3122096982
|
@ -1,10 +1,8 @@
|
||||||
package main
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"crypto/md5"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,23 +37,11 @@ func (e SerializerError) Error() string {
|
||||||
return fmt.Sprintf("Serializer error: got %v", e.err)
|
return fmt.Sprintf("Serializer error: got %v", e.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mesg struct {
|
|
||||||
Msg *dns.Msg
|
|
||||||
Expire time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cache interface {
|
type Cache interface {
|
||||||
Get(key string) (Msg *dns.Msg, err error)
|
Get(key string) (Msg *dns.Msg, err error)
|
||||||
Set(key string, Msg *dns.Msg) error
|
Set(key string, Msg *dns.Msg) error
|
||||||
Exists(key string) bool
|
Exists(key string) bool
|
||||||
Remove(key string) error
|
Remove(key string) error
|
||||||
Full() bool
|
Full() bool
|
||||||
}
|
Purge() error
|
||||||
|
|
||||||
func KeyGen(q Question) string {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(q.String()))
|
|
||||||
x := h.Sum(nil)
|
|
||||||
key := fmt.Sprintf("%x", x)
|
|
||||||
return key
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bradfitz/gomemcache/memcache"
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
@ -64,3 +64,7 @@ func (m *MemcachedCache) Full() bool {
|
||||||
// memcache is never full (LRU)
|
// memcache is never full (LRU)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MemcachedCache) Purge() error {
|
||||||
|
return m.backend.DeleteAll()
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -6,18 +6,35 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewMemoryCache(expire time.Duration, maxCount int) *MemoryCache {
|
||||||
|
return &MemoryCache{
|
||||||
|
backend: make(map[string]Mesg),
|
||||||
|
Expire: expire,
|
||||||
|
Maxcount: maxCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mesg struct {
|
||||||
|
Msg *dns.Msg
|
||||||
|
Expire time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type MemoryCache struct {
|
type MemoryCache struct {
|
||||||
Cache
|
Cache
|
||||||
|
|
||||||
Backend map[string]Mesg
|
backend map[string]Mesg
|
||||||
Expire time.Duration
|
Expire time.Duration
|
||||||
Maxcount int
|
Maxcount int
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) initialize() {
|
||||||
|
c.backend = make(map[string]Mesg)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *MemoryCache) Get(key string) (*dns.Msg, error) {
|
func (c *MemoryCache) Get(key string) (*dns.Msg, error) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
mesg, ok := c.Backend[key]
|
mesg, ok := c.backend[key]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, KeyNotFound{key}
|
return nil, KeyNotFound{key}
|
||||||
|
@ -40,21 +57,21 @@ func (c *MemoryCache) Set(key string, msg *dns.Msg) error {
|
||||||
expire := time.Now().Add(c.Expire)
|
expire := time.Now().Add(c.Expire)
|
||||||
mesg := Mesg{msg, expire}
|
mesg := Mesg{msg, expire}
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.Backend[key] = mesg
|
c.backend[key] = mesg
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MemoryCache) Remove(key string) error {
|
func (c *MemoryCache) Remove(key string) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
delete(c.Backend, key)
|
delete(c.backend, key)
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MemoryCache) Exists(key string) bool {
|
func (c *MemoryCache) Exists(key string) bool {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
_, ok := c.Backend[key]
|
_, ok := c.backend[key]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
@ -62,7 +79,7 @@ func (c *MemoryCache) Exists(key string) bool {
|
||||||
func (c *MemoryCache) Length() int {
|
func (c *MemoryCache) Length() int {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
return len(c.Backend)
|
return len(c.backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MemoryCache) Full() bool {
|
func (c *MemoryCache) Full() bool {
|
||||||
|
@ -72,3 +89,12 @@ func (c *MemoryCache) Full() bool {
|
||||||
}
|
}
|
||||||
return c.Length() >= c.Maxcount
|
return c.Length() >= c.Maxcount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *MemoryCache) Purge() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.initialize()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
package main
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hoisie/redis"
|
"github.com/go-redis/redis/v7"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"meow.tf/joker/godns/settings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRedisCache(c RedisSettings, expire int32) *RedisCache {
|
func NewRedisCache(c settings.RedisSettings, expire int32) *RedisCache {
|
||||||
rc := &redis.Client{Addr: c.Addr(), Db: c.DB, Password: c.Password}
|
rc := redis.NewClient(&redis.Options{Addr: c.Addr(), DB: c.DB, Password: c.Password})
|
||||||
|
|
||||||
return &RedisCache{
|
return &RedisCache{
|
||||||
backend: rc,
|
backend: rc,
|
||||||
|
@ -25,6 +27,8 @@ func (m *RedisCache) Set(key string, msg *dns.Msg) error {
|
||||||
var val []byte
|
var val []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
key = "cache:" + key
|
||||||
|
|
||||||
// handle cases for negacache where it sets nil values
|
// handle cases for negacache where it sets nil values
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
val = []byte("nil")
|
val = []byte("nil")
|
||||||
|
@ -34,16 +38,21 @@ func (m *RedisCache) Set(key string, msg *dns.Msg) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = SerializerError{err}
|
err = SerializerError{err}
|
||||||
}
|
}
|
||||||
return m.backend.Setex(key, int64(m.expire), val)
|
return m.backend.Set(key, val, time.Duration(m.expire) * time.Second).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RedisCache) Get(key string) (*dns.Msg, error) {
|
func (m *RedisCache) Get(key string) (*dns.Msg, error) {
|
||||||
var msg dns.Msg
|
var msg dns.Msg
|
||||||
item, err := m.backend.Get(key)
|
var err error
|
||||||
|
key = "cache:" + key
|
||||||
|
|
||||||
|
item, err := m.backend.Get(key).Bytes()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = KeyNotFound{key}
|
err = KeyNotFound{key}
|
||||||
return &msg, err
|
return &msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = msg.Unpack(item)
|
err = msg.Unpack(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = SerializerError{err}
|
err = SerializerError{err}
|
||||||
|
@ -52,19 +61,40 @@ func (m *RedisCache) Get(key string) (*dns.Msg, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RedisCache) Exists(key string) bool {
|
func (m *RedisCache) Exists(key string) bool {
|
||||||
exists, err := m.backend.Exists(key)
|
res, err := m.backend.Exists(key).Result()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return exists
|
|
||||||
|
return res == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RedisCache) Remove(key string) error {
|
func (m *RedisCache) Remove(key string) error {
|
||||||
_, err := m.backend.Del(key)
|
return m.backend.Del(key).Err()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RedisCache) Full() bool {
|
func (m *RedisCache) Full() bool {
|
||||||
// redis is never full (LRU)
|
// redis is never full (LRU)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *RedisCache) Purge() error {
|
||||||
|
iter := m.backend.Scan(0, "cache:*", 0).Iterator()
|
||||||
|
|
||||||
|
if iter.Err() != nil {
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
err = m.backend.Del(iter.Val()).Err()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ Debug = false
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
port = 53
|
nets = ["tcp:53", "udp:53"]
|
||||||
|
|
||||||
[resolv]
|
[resolv]
|
||||||
# Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq
|
# Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq
|
||||||
|
|
19
go.mod
19
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module godns
|
module meow.tf/joker/godns
|
||||||
|
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
|
@ -7,18 +7,19 @@ require (
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
github.com/caarlos0/env v3.5.0+incompatible
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
|
github.com/go-redis/redis/v7 v7.0.0-beta.5
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f // indirect
|
||||||
github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0
|
github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0
|
||||||
github.com/miekg/dns v1.1.18
|
github.com/kr/pretty v0.2.0 // indirect
|
||||||
|
github.com/miekg/dns v1.1.27
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/ryanuber/go-glob v1.0.0
|
github.com/ryanuber/go-glob v1.0.0
|
||||||
github.com/smartystreets/assertions v1.0.1 // indirect
|
github.com/smartystreets/assertions v1.0.1 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
|
github.com/smartystreets/goconvey v1.6.4
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/spf13/viper v1.6.2
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect
|
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad // indirect
|
||||||
golang.org/x/net v0.0.0-20190926025831-c00fd9afed17 // indirect
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe // indirect
|
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190925230517-ea99b82c7b93 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
136
handler.go
136
handler.go
|
@ -1,6 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"meow.tf/joker/godns/cache"
|
||||||
|
"meow.tf/joker/godns/hosts"
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
|
"meow.tf/joker/godns/resolver"
|
||||||
|
"meow.tf/joker/godns/settings"
|
||||||
|
"meow.tf/joker/godns/utils"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,75 +21,56 @@ const (
|
||||||
_IP6Query = 6
|
_IP6Query = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
type Question struct {
|
|
||||||
qname string
|
|
||||||
qtype string
|
|
||||||
qclass string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Question) String() string {
|
|
||||||
return q.qname + " " + q.qclass + " " + q.qtype
|
|
||||||
}
|
|
||||||
|
|
||||||
type GODNSHandler struct {
|
type GODNSHandler struct {
|
||||||
resolver *Resolver
|
resolver *resolver.Resolver
|
||||||
cache, negCache Cache
|
cache, negCache cache.Cache
|
||||||
hosts Hosts
|
hosts hosts.Hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler() *GODNSHandler {
|
func NewHandler() *GODNSHandler {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cacheConfig CacheSettings
|
cacheConfig settings.CacheSettings
|
||||||
resolver *Resolver
|
r *resolver.Resolver
|
||||||
cache, negCache Cache
|
resolverCache, negCache cache.Cache
|
||||||
)
|
)
|
||||||
|
|
||||||
resolver = NewResolver(settings.ResolvConfig)
|
r = resolver.NewResolver(settings.Resolver())
|
||||||
|
|
||||||
cacheConfig = settings.Cache
|
cacheConfig = settings.Cache()
|
||||||
switch cacheConfig.Backend {
|
switch cacheConfig.Backend {
|
||||||
case "memory":
|
case "memory":
|
||||||
cache = &MemoryCache{
|
cacheDuration := time.Duration(cacheConfig.Expire) * time.Second
|
||||||
Backend: make(map[string]Mesg, cacheConfig.Maxcount),
|
|
||||||
Expire: time.Duration(cacheConfig.Expire) * time.Second,
|
negCache = cache.NewMemoryCache(cacheDuration/2, cacheConfig.Maxcount)
|
||||||
Maxcount: cacheConfig.Maxcount,
|
resolverCache = cache.NewMemoryCache(time.Duration(cacheConfig.Expire)*time.Second, cacheConfig.Maxcount)
|
||||||
}
|
|
||||||
negCache = &MemoryCache{
|
|
||||||
Backend: make(map[string]Mesg),
|
|
||||||
Expire: time.Duration(cacheConfig.Expire) * time.Second / 2,
|
|
||||||
Maxcount: cacheConfig.Maxcount,
|
|
||||||
}
|
|
||||||
case "memcache":
|
case "memcache":
|
||||||
cache = NewMemcachedCache(
|
resolverCache = cache.NewMemcachedCache(
|
||||||
settings.Memcache.Servers,
|
settings.Memcache().Servers,
|
||||||
int32(cacheConfig.Expire))
|
int32(cacheConfig.Expire))
|
||||||
negCache = NewMemcachedCache(
|
negCache = cache.NewMemcachedCache(
|
||||||
settings.Memcache.Servers,
|
settings.Memcache().Servers,
|
||||||
int32(cacheConfig.Expire/2))
|
int32(cacheConfig.Expire/2))
|
||||||
case "redis":
|
case "redis":
|
||||||
cache = NewRedisCache(
|
resolverCache = cache.NewRedisCache(
|
||||||
settings.Redis,
|
settings.Redis(),
|
||||||
int32(cacheConfig.Expire))
|
int32(cacheConfig.Expire))
|
||||||
negCache = NewRedisCache(
|
negCache = cache.NewRedisCache(
|
||||||
settings.Redis,
|
settings.Redis(),
|
||||||
int32(cacheConfig.Expire/2))
|
int32(cacheConfig.Expire/2))
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid cache backend %s", cacheConfig.Backend)
|
log.Error("Invalid cache backend %s", cacheConfig.Backend)
|
||||||
panic("Invalid cache backend")
|
panic("Invalid cache backend")
|
||||||
}
|
}
|
||||||
|
|
||||||
var hosts Hosts
|
h := hosts.NewHosts(settings.Hosts(), settings.Redis())
|
||||||
if settings.Hosts.Enable {
|
|
||||||
hosts = NewHosts(settings.Hosts, settings.Redis)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GODNSHandler{resolver, cache, negCache, hosts}
|
return &GODNSHandler{r, resolverCache, negCache, h}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
|
func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
|
||||||
q := req.Question[0]
|
q := req.Question[0]
|
||||||
Q := Question{UnFqdn(q.Name), dns.TypeToString[q.Qtype], dns.ClassToString[q.Qclass]}
|
question := resolver.Question{Name: utils.UnFqdn(q.Name), Type: dns.TypeToString[q.Qtype], Class: dns.ClassToString[q.Qclass]}
|
||||||
|
|
||||||
var remote net.IP
|
var remote net.IP
|
||||||
if Net == "tcp" {
|
if Net == "tcp" {
|
||||||
|
@ -89,63 +78,61 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
|
||||||
} else {
|
} else {
|
||||||
remote = w.RemoteAddr().(*net.UDPAddr).IP
|
remote = w.RemoteAddr().(*net.UDPAddr).IP
|
||||||
}
|
}
|
||||||
logger.Info("%s lookup %s", remote, Q.String())
|
log.Info("%s lookup %s", remote, question.String())
|
||||||
|
|
||||||
IPQuery := h.isIPQuery(q)
|
IPQuery := h.isIPQuery(q)
|
||||||
|
|
||||||
// Query hosts
|
// Query hosts
|
||||||
if settings.Hosts.Enable && IPQuery > 0 {
|
if h.hosts != nil && IPQuery > 0 {
|
||||||
if ips, ok := h.hosts.Get(Q.qname, IPQuery); ok {
|
if ips, ok := h.hosts.Get(question.Name, IPQuery); ok {
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetReply(req)
|
m.SetReply(req)
|
||||||
|
|
||||||
switch IPQuery {
|
switch IPQuery {
|
||||||
case _IP4Query:
|
case _IP4Query:
|
||||||
rr_header := dns.RR_Header{
|
hdr := dns.RR_Header{
|
||||||
Name: q.Name,
|
Name: q.Name,
|
||||||
Rrtype: dns.TypeA,
|
Rrtype: dns.TypeA,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: settings.Hosts.TTL,
|
Ttl: h.hosts.TTL(),
|
||||||
}
|
}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
a := &dns.A{rr_header, ip}
|
m.Answer = append(m.Answer, &dns.A{Hdr: hdr, A: ip})
|
||||||
m.Answer = append(m.Answer, a)
|
|
||||||
}
|
}
|
||||||
case _IP6Query:
|
case _IP6Query:
|
||||||
rr_header := dns.RR_Header{
|
hdr := dns.RR_Header{
|
||||||
Name: q.Name,
|
Name: q.Name,
|
||||||
Rrtype: dns.TypeAAAA,
|
Rrtype: dns.TypeAAAA,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: settings.Hosts.TTL,
|
Ttl: h.hosts.TTL(),
|
||||||
}
|
}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
aaaa := &dns.AAAA{rr_header, ip}
|
m.Answer = append(m.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip})
|
||||||
m.Answer = append(m.Answer, aaaa)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
logger.Debug("%s found in hosts file", Q.qname)
|
log.Debug("%s found in hosts file", question.Name)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("%s didn't found in hosts file", Q.qname)
|
log.Debug("%s didn't found in hosts file", question.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only query cache when qtype == 'A'|'AAAA' , qclass == 'IN'
|
// Only query cache when qtype == 'A'|'AAAA' , qclass == 'IN'
|
||||||
key := KeyGen(Q)
|
key := KeyGen(question)
|
||||||
if IPQuery > 0 {
|
if IPQuery > 0 {
|
||||||
mesg, err := h.cache.Get(key)
|
mesg, err := h.cache.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if mesg, err = h.negCache.Get(key); err != nil {
|
if mesg, err = h.negCache.Get(key); err != nil {
|
||||||
logger.Debug("%s didn't hit cache", Q.String())
|
log.Debug("%s didn't hit cache", question.String())
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("%s hit negative cache", Q.String())
|
log.Debug("%s hit negative cache", question.String())
|
||||||
dns.HandleFailed(w, req)
|
dns.HandleFailed(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("%s hit cache", Q.String())
|
log.Debug("%s hit cache", question.String())
|
||||||
// we need this copy against concurrent modification of Id
|
// we need this copy against concurrent modification of Id
|
||||||
msg := *mesg
|
msg := *mesg
|
||||||
msg.Id = req.Id
|
msg.Id = req.Id
|
||||||
|
@ -157,12 +144,12 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
|
||||||
mesg, err := h.resolver.Lookup(Net, req)
|
mesg, err := h.resolver.Lookup(Net, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Resolve query error %s", err)
|
log.Warn("Resolve query error %s", err)
|
||||||
dns.HandleFailed(w, req)
|
dns.HandleFailed(w, req)
|
||||||
|
|
||||||
// cache the failure, too!
|
// cache the failure, too!
|
||||||
if err = h.negCache.Set(key, nil); err != nil {
|
if err = h.negCache.Set(key, nil); err != nil {
|
||||||
logger.Warn("Set %s negative cache failed: %v", Q.String(), err)
|
log.Warn("Set %s negative cache failed: %v", question.String(), err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -172,18 +159,16 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
|
||||||
if IPQuery > 0 && len(mesg.Answer) > 0 {
|
if IPQuery > 0 && len(mesg.Answer) > 0 {
|
||||||
err = h.cache.Set(key, mesg)
|
err = h.cache.Set(key, mesg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Set %s cache failed: %s", Q.String(), err.Error())
|
log.Warn("Set %s cache failed: %s", question.String(), err.Error())
|
||||||
}
|
}
|
||||||
logger.Debug("Insert %s into cache", Q.String())
|
log.Debug("Insert %s into cache", question.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *GODNSHandler) DoTCP(w dns.ResponseWriter, req *dns.Msg) {
|
func (h *GODNSHandler) Bind(net string) func(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
h.do("tcp", w, req)
|
return func(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
}
|
h.do(net, w, req)
|
||||||
|
}
|
||||||
func (h *GODNSHandler) DoUDP(w dns.ResponseWriter, req *dns.Msg) {
|
|
||||||
h.do("udp", w, req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *GODNSHandler) isIPQuery(q dns.Question) int {
|
func (h *GODNSHandler) isIPQuery(q dns.Question) int {
|
||||||
|
@ -201,9 +186,10 @@ func (h *GODNSHandler) isIPQuery(q dns.Question) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnFqdn(s string) string {
|
func KeyGen(q resolver.Question) string {
|
||||||
if dns.IsFqdn(s) {
|
h := md5.New()
|
||||||
return s[:len(s)-1]
|
h.Write([]byte(q.String()))
|
||||||
}
|
x := h.Sum(nil)
|
||||||
return s
|
key := fmt.Sprintf("%x", x)
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
package main
|
package hosts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
|
"meow.tf/joker/godns/settings"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hoisie/redis"
|
"github.com/hoisie/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hosts struct {
|
const (
|
||||||
|
notIPQuery = 0
|
||||||
|
_IP4Query = 4
|
||||||
|
_IP6Query = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hosts interface {
|
||||||
|
Get(domain string, family int) ([]net.IP, bool)
|
||||||
|
TTL() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderList struct {
|
||||||
|
settings settings.HostsSettings
|
||||||
providers []HostProvider
|
providers []HostProvider
|
||||||
refreshInterval time.Duration
|
refreshInterval time.Duration
|
||||||
}
|
}
|
||||||
|
@ -17,20 +31,20 @@ type HostProvider interface {
|
||||||
Refresh()
|
Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHosts(hs HostsSettings, rs RedisSettings) Hosts {
|
func NewHosts(hs settings.HostsSettings, rs settings.RedisSettings) Hosts {
|
||||||
providers := []HostProvider{
|
providers := []HostProvider{
|
||||||
NewFileProvider(hs.HostsFile),
|
NewFileProvider(hs.HostsFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
if hs.RedisEnable {
|
if hs.RedisEnable {
|
||||||
logger.Info("Redis is enabled: %s", rs.Addr())
|
log.Info("Redis is enabled: %s", rs.Addr())
|
||||||
|
|
||||||
rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password}
|
rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password}
|
||||||
|
|
||||||
providers = append(providers, NewRedisProvider(rc, hs.RedisKey))
|
providers = append(providers, NewRedisProvider(rc, hs.RedisKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
h := Hosts{providers, time.Second * time.Duration(hs.RefreshInterval)}
|
h := &ProviderList{hs, providers, time.Second * time.Duration(hs.RefreshInterval)}
|
||||||
|
|
||||||
if h.refreshInterval > 0 {
|
if h.refreshInterval > 0 {
|
||||||
h.refresh()
|
h.refresh()
|
||||||
|
@ -39,7 +53,7 @@ func NewHosts(hs HostsSettings, rs RedisSettings) Hosts {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hosts) refresh() {
|
func (h *ProviderList) refresh() {
|
||||||
ticker := time.NewTicker(h.refreshInterval)
|
ticker := time.NewTicker(h.refreshInterval)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -57,7 +71,7 @@ func (h *Hosts) refresh() {
|
||||||
/*
|
/*
|
||||||
Match local /etc/hosts file first, remote redis records second
|
Match local /etc/hosts file first, remote redis records second
|
||||||
*/
|
*/
|
||||||
func (h *Hosts) Get(domain string, family int) ([]net.IP, bool) {
|
func (h *ProviderList) Get(domain string, family int) ([]net.IP, bool) {
|
||||||
var sips []string
|
var sips []string
|
||||||
var ok bool
|
var ok bool
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
|
@ -91,3 +105,7 @@ func (h *Hosts) Get(domain string, family int) ([]net.IP, bool) {
|
||||||
|
|
||||||
return ips, ips != nil
|
return ips, ips != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ProviderList) TTL() uint32 {
|
||||||
|
return h.settings.TTL
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package main
|
package hosts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/ryanuber/go-glob"
|
"github.com/ryanuber/go-glob"
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -45,7 +46,7 @@ func NewFileProvider(file string) HostProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileHosts) Get(domain string) ([]string, bool) {
|
func (f *FileHosts) Get(domain string) ([]string, bool) {
|
||||||
logger.Debug("Checking file provider for %s", domain)
|
log.Debug("Checking file provider for %s", domain)
|
||||||
|
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
defer f.mu.RUnlock()
|
defer f.mu.RUnlock()
|
||||||
|
@ -80,7 +81,7 @@ func (f *FileHosts) Refresh() {
|
||||||
buf, err := os.Open(f.file)
|
buf, err := os.Open(f.file)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Update hosts records from file failed %s", err)
|
log.Warn("Update hosts records from file failed %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ func (f *FileHosts) Refresh() {
|
||||||
f.hosts[strings.ToLower(domain)] = ip
|
f.hosts[strings.ToLower(domain)] = ip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug("update hosts records from %s, total %d records.", f.file, len(f.hosts))
|
log.Debug("update hosts records from %s, total %d records.", f.file, len(f.hosts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileHosts) clear() {
|
func (f *FileHosts) clear() {
|
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package hosts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hoisie/redis"
|
"github.com/hoisie/redis"
|
||||||
"github.com/ryanuber/go-glob"
|
"github.com/ryanuber/go-glob"
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +42,7 @@ func NewRedisProvider(rc *redis.Client, key string) HostProvider {
|
||||||
msg := <-messages
|
msg := <-messages
|
||||||
|
|
||||||
if msg.Channel == "godns:update" {
|
if msg.Channel == "godns:update" {
|
||||||
logger.Debug("Refreshing redis records due to update")
|
log.Debug("Refreshing redis records due to update")
|
||||||
rh.Refresh()
|
rh.Refresh()
|
||||||
} else if msg.Channel == "godns:update_record" {
|
} else if msg.Channel == "godns:update_record" {
|
||||||
recordName := string(msg.Message)
|
recordName := string(msg.Message)
|
||||||
|
@ -49,17 +50,17 @@ func NewRedisProvider(rc *redis.Client, key string) HostProvider {
|
||||||
b, err := rc.Hget(key, recordName)
|
b, err := rc.Hget(key, recordName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Record %s does not exist, but was updated", recordName)
|
log.Warn("Record %s does not exist, but was updated", recordName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Record %s was updated to %s", recordName, string(b))
|
log.Debug("Record %s was updated to %s", recordName, string(b))
|
||||||
|
|
||||||
rh.mu.Lock()
|
rh.mu.Lock()
|
||||||
rh.hosts[recordName] = string(b)
|
rh.hosts[recordName] = string(b)
|
||||||
rh.mu.Unlock()
|
rh.mu.Unlock()
|
||||||
} else if msg.Channel == "godns:remove_record" {
|
} else if msg.Channel == "godns:remove_record" {
|
||||||
logger.Debug("Record %s was removed", msg.Message)
|
log.Debug("Record %s was removed", msg.Message)
|
||||||
|
|
||||||
recordName := string(msg.Message)
|
recordName := string(msg.Message)
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ func NewRedisProvider(rc *redis.Client, key string) HostProvider {
|
||||||
delete(rh.hosts, recordName)
|
delete(rh.hosts, recordName)
|
||||||
rh.mu.Unlock()
|
rh.mu.Unlock()
|
||||||
} else if msg.Channel == keyspaceEvent {
|
} else if msg.Channel == keyspaceEvent {
|
||||||
logger.Debug("Refreshing redis records due to update")
|
log.Debug("Refreshing redis records due to update")
|
||||||
rh.Refresh()
|
rh.Refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +78,7 @@ func NewRedisProvider(rc *redis.Client, key string) HostProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RedisHosts) Get(domain string) ([]string, bool) {
|
func (r *RedisHosts) Get(domain string) ([]string, bool) {
|
||||||
logger.Debug("Checking redis provider for %s", domain)
|
log.Debug("Checking redis provider for %s", domain)
|
||||||
|
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
@ -117,9 +118,9 @@ func (r *RedisHosts) Refresh() {
|
||||||
r.clear()
|
r.clear()
|
||||||
err := r.redis.Hgetall(r.key, r.hosts)
|
err := r.redis.Hgetall(r.key, r.hosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Update hosts records from redis failed %s", err)
|
log.Warn("Update hosts records from redis failed %s", err)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Update hosts records from redis")
|
log.Debug("Update hosts records from redis")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -16,6 +16,10 @@ const (
|
||||||
LevelError
|
LevelError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger *GoDNSLogger
|
||||||
|
)
|
||||||
|
|
||||||
type logMesg struct {
|
type logMesg struct {
|
||||||
Level int
|
Level int
|
||||||
Mesg string
|
Mesg string
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
|
@ -0,0 +1,33 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logger = NewLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(format string, v ...interface{}) {
|
||||||
|
logger.Debug(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(format string, v ...interface{}) {
|
||||||
|
logger.Info(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Notice(format string, v ...interface{}) {
|
||||||
|
logger.Notice(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(format string, v ...interface{}) {
|
||||||
|
logger.Warn(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(format string, v ...interface{}) {
|
||||||
|
logger.Error(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLogger(handlerType string, config map[string]interface{}) {
|
||||||
|
logger.SetLogger(handlerType, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLevel(level int) {
|
||||||
|
logger.SetLevel(level)
|
||||||
|
}
|
64
main.go
64
main.go
|
@ -1,6 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
|
"meow.tf/joker/godns/settings"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -8,26 +13,43 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
logger *GoDNSLogger
|
Version = "0.3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
var (
|
||||||
|
cfgFile string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&cfgFile, "config", "/etc/godns/godns.conf", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
initLogger()
|
initLogger()
|
||||||
|
|
||||||
|
viper.SetConfigFile(cfgFile)
|
||||||
|
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
|
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
|
||||||
|
serverSettings := settings.Server()
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
host: settings.Server.Host,
|
host: serverSettings.Host,
|
||||||
port: settings.Server.Port,
|
port: serverSettings.Port,
|
||||||
rTimeout: 5 * time.Second,
|
rTimeout: 5 * time.Second,
|
||||||
wTimeout: 5 * time.Second,
|
wTimeout: 5 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Run()
|
server.Run()
|
||||||
|
|
||||||
logger.Info("godns %s start", settings.Version)
|
log.Info("godns %s (%s) start", Version, runtime.Version())
|
||||||
|
|
||||||
if settings.Debug {
|
if settings.Debug() {
|
||||||
go profileCPU()
|
go profileCPU()
|
||||||
go profileMEM()
|
go profileMEM()
|
||||||
}
|
}
|
||||||
|
@ -35,21 +57,15 @@ func main() {
|
||||||
sig := make(chan os.Signal)
|
sig := make(chan os.Signal)
|
||||||
signal.Notify(sig, os.Interrupt)
|
signal.Notify(sig, os.Interrupt)
|
||||||
|
|
||||||
forever:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-sig:
|
|
||||||
logger.Info("signal received, stopping")
|
|
||||||
break forever
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<- sig
|
||||||
|
log.Info("signal received, stopping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileCPU() {
|
func profileCPU() {
|
||||||
f, err := os.Create("godns.cprof")
|
f, err := os.Create("godns.cprof")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("%s", err)
|
log.Error("%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +79,9 @@ func profileCPU() {
|
||||||
|
|
||||||
func profileMEM() {
|
func profileMEM() {
|
||||||
f, err := os.Create("godns.mprof")
|
f, err := os.Create("godns.mprof")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("%s", err)
|
log.Error("%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,18 +93,17 @@ func profileMEM() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initLogger() {
|
func initLogger() {
|
||||||
logger = NewLogger()
|
logSettings := settings.Log()
|
||||||
|
|
||||||
if settings.Log.Stdout {
|
if viper.GetBool("log.stdout") {
|
||||||
logger.SetLogger("console", nil)
|
log.SetLogger("console", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.Log.File != "" {
|
if file := viper.GetString("log.file"); file != "" {
|
||||||
config := map[string]interface{}{"file": settings.Log.File}
|
log.SetLogger("file", map[string]interface{}{"file": file})
|
||||||
logger.SetLogger("file", config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.SetLevel(settings.Log.LogLevel())
|
log.SetLevel(logSettings.LogLevel())
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
type Nameserver struct {
|
||||||
|
address string
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
type Question struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Class string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Question) String() string {
|
||||||
|
return q.Name + " " + q.Class + " " + q.Type
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package main
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"meow.tf/joker/godns/log"
|
||||||
|
"meow.tf/joker/godns/settings"
|
||||||
|
"meow.tf/joker/godns/utils"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -11,9 +14,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"errors"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResolvError struct {
|
type ResolvError struct {
|
||||||
|
@ -35,14 +37,13 @@ type RResp struct {
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
servers []string
|
servers []string
|
||||||
domain_server *suffixTreeNode
|
domain_server *suffixTreeNode
|
||||||
config *ResolvSettings
|
config *settings.ResolvSettings
|
||||||
|
|
||||||
tcpClient *dns.Client
|
clients map[string]*dns.Client
|
||||||
udpClient *dns.Client
|
clientLock sync.RWMutex
|
||||||
httpsClient *dns.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(c ResolvSettings) *Resolver {
|
func NewResolver(c settings.ResolvSettings) *Resolver {
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
servers: []string{},
|
servers: []string{},
|
||||||
domain_server: newSuffixTreeRoot(),
|
domain_server: newSuffixTreeRoot(),
|
||||||
|
@ -51,16 +52,14 @@ func NewResolver(c ResolvSettings) *Resolver {
|
||||||
|
|
||||||
if len(c.ServerListFile) > 0 {
|
if len(c.ServerListFile) > 0 {
|
||||||
r.ReadServerListFile(c.ServerListFile)
|
r.ReadServerListFile(c.ServerListFile)
|
||||||
|
|
||||||
log.Println("Read servers", strings.Join(r.servers, ", "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.ResolvFile) > 0 {
|
if len(c.ResolvFile) > 0 {
|
||||||
clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile)
|
clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(":%s is not a valid resolv.conf file\n", c.ResolvFile)
|
log.Error(":%s is not a valid resolv.conf file\n", c.ResolvFile)
|
||||||
logger.Error("%s", err)
|
log.Error("%s", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,26 +72,6 @@ func NewResolver(c ResolvSettings) *Resolver {
|
||||||
r.servers = append([]string{c.DOHServer}, r.servers...)
|
r.servers = append([]string{c.DOHServer}, r.servers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := r.Timeout()
|
|
||||||
|
|
||||||
r.udpClient = &dns.Client{
|
|
||||||
Net: "udp",
|
|
||||||
ReadTimeout: timeout,
|
|
||||||
WriteTimeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.tcpClient = &dns.Client{
|
|
||||||
Net: "tcp",
|
|
||||||
ReadTimeout: timeout,
|
|
||||||
WriteTimeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.httpsClient = &dns.Client{
|
|
||||||
Net: "https",
|
|
||||||
ReadTimeout: timeout,
|
|
||||||
WriteTimeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +107,7 @@ func (r *Resolver) parseServerListFile(buf *os.File) {
|
||||||
domain := tokens[1]
|
domain := tokens[1]
|
||||||
ip := tokens[2]
|
ip := tokens[2]
|
||||||
|
|
||||||
if !isDomain(domain) || !isIP(ip) {
|
if !utils.IsDomain(domain) || !utils.IsIP(ip) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +121,7 @@ func (r *Resolver) parseServerListFile(buf *os.File) {
|
||||||
|
|
||||||
ip := ""
|
ip := ""
|
||||||
|
|
||||||
if ip = srv_port[0]; !isIP(ip) {
|
if ip = srv_port[0]; !utils.IsIP(ip) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,8 +148,8 @@ func (r *Resolver) ReadServerListFile(path string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Can't open " + file)
|
panic("Can't open " + file)
|
||||||
}
|
}
|
||||||
defer buf.Close()
|
|
||||||
r.parseServerListFile(buf)
|
r.parseServerListFile(buf)
|
||||||
|
buf.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +157,7 @@ func (r *Resolver) ReadServerListFile(path string) {
|
||||||
// in every second, and return as early as possbile (have an answer).
|
// in every second, and return as early as possbile (have an answer).
|
||||||
// It returns an error if no request has succeeded.
|
// It returns an error if no request has succeeded.
|
||||||
func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) {
|
func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) {
|
||||||
if net == "udp" && settings.ResolvConfig.SetEDNS0 {
|
if net == "udp" && r.config.SetEDNS0 {
|
||||||
req = req.SetEdns0(65535, true)
|
req = req.SetEdns0(65535, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,15 +171,15 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
c, err := resolver.resolverFor(net, nameserver)
|
c, err := resolver.resolverFor(net, nameserver)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("error:%s", err.Error())
|
log.Warn("error:%s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r, rtt, err := c.Exchange(req, nameserver)
|
r, rtt, err := c.Exchange(req, nameserver)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("%s socket error on %s", qname, nameserver)
|
log.Warn("%s socket error on %s", qname, nameserver)
|
||||||
logger.Warn("error:%s", err.Error())
|
log.Warn("error:%s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If SERVFAIL happen, should return immediately and try another upstream resolver.
|
// If SERVFAIL happen, should return immediately and try another upstream resolver.
|
||||||
|
@ -208,7 +187,7 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
// that it has been verified no such domain existas and ask other resolvers
|
// that it has been verified no such domain existas and ask other resolvers
|
||||||
// would make no sense. See more about #20
|
// would make no sense. See more about #20
|
||||||
if r != nil && r.Rcode != dns.RcodeSuccess {
|
if r != nil && r.Rcode != dns.RcodeSuccess {
|
||||||
logger.Warn("%s failed to get an valid answer on %s", qname, nameserver)
|
log.Warn("%s failed to get an valid answer on %s", qname, nameserver)
|
||||||
if r.Rcode == dns.RcodeServerFailure {
|
if r.Rcode == dns.RcodeServerFailure {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -220,7 +199,7 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(settings.ResolvConfig.Interval) * time.Millisecond)
|
ticker := time.NewTicker(time.Duration(r.config.Interval) * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
// Start lookup on each nameserver top-down, in every second
|
// Start lookup on each nameserver top-down, in every second
|
||||||
nameservers := r.Nameservers(qname)
|
nameservers := r.Nameservers(qname)
|
||||||
|
@ -230,7 +209,7 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
// but exit early, if we have an answer
|
// but exit early, if we have an answer
|
||||||
select {
|
select {
|
||||||
case re := <-res:
|
case re := <-res:
|
||||||
logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt)
|
log.Debug("%s resolv on %s rtt: %v", utils.UnFqdn(qname), re.nameserver, re.rtt)
|
||||||
return re.msg, nil
|
return re.msg, nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
continue
|
continue
|
||||||
|
@ -240,7 +219,7 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
select {
|
select {
|
||||||
case re := <-res:
|
case re := <-res:
|
||||||
logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt)
|
log.Debug("%s resolv on %s rtt: %v", utils.UnFqdn(qname), re.nameserver, re.rtt)
|
||||||
return re.msg, nil
|
return re.msg, nil
|
||||||
default:
|
default:
|
||||||
return nil, ResolvError{qname, net, nameservers}
|
return nil, ResolvError{qname, net, nameservers}
|
||||||
|
@ -248,25 +227,37 @@ func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) resolverFor(net, nameserver string) (*dns.Client, error) {
|
func (r *Resolver) resolverFor(net, nameserver string) (*dns.Client, error) {
|
||||||
if strings.HasPrefix(nameserver, "https") {
|
r.clientLock.RLock()
|
||||||
return r.httpsClient, nil
|
client, exists := r.clients[net]
|
||||||
} else if strings.HasSuffix(nameserver, ":853") {
|
r.clientLock.RUnlock()
|
||||||
// TODO We need to set the server name so we can confirm the TLS connection. This may require a rewrite of storing nameservers.
|
|
||||||
return &dns.Client{
|
if exists {
|
||||||
Net: "tcp-tls",
|
return client, nil
|
||||||
ReadTimeout: r.Timeout(),
|
|
||||||
WriteTimeout: r.Timeout(),
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
ServerName: "",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
} else if net == "udp" {
|
|
||||||
return r.udpClient, nil
|
|
||||||
} else if net == "tcp" {
|
|
||||||
return r.tcpClient, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("no client for nameserver")
|
if net != "tcp" && net != "tcp-tls" && net != "https" && net != "udp" {
|
||||||
|
return nil, errors.New("unknown network type")
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := r.Timeout()
|
||||||
|
|
||||||
|
client = &dns.Client{
|
||||||
|
Net: net,
|
||||||
|
ReadTimeout: timeout,
|
||||||
|
WriteTimeout: timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(nameserver, ":853") {
|
||||||
|
client.TLSConfig = &tls.Config{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.clientLock.Lock()
|
||||||
|
r.clients[net] = client
|
||||||
|
r.clientLock.Lock()
|
||||||
|
|
||||||
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namservers return the array of nameservers, with port number appended.
|
// Namservers return the array of nameservers, with port number appended.
|
||||||
|
@ -278,7 +269,7 @@ func (r *Resolver) Nameservers(qname string) []string {
|
||||||
|
|
||||||
ns := []string{}
|
ns := []string{}
|
||||||
if v, found := r.domain_server.search(queryKeys); found {
|
if v, found := r.domain_server.search(queryKeys); found {
|
||||||
logger.Debug("%s be found in domain server list, upstream: %v", qname, v)
|
log.Debug("%s found in domain server list, upstream: %v", qname, v)
|
||||||
|
|
||||||
ns = append(ns, net.JoinHostPort(v, "53"))
|
ns = append(ns, net.JoinHostPort(v, "53"))
|
||||||
//Ensure query the specific upstream nameserver in async Lookup() function.
|
//Ensure query the specific upstream nameserver in async Lookup() function.
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package resolver
|
||||||
|
|
||||||
type suffixTreeNode struct {
|
type suffixTreeNode struct {
|
||||||
key string
|
key string
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
57
server.go
57
server.go
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"github.com/spf13/viper"
|
||||||
"strconv"
|
"meow.tf/joker/godns/log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -15,45 +16,49 @@ type Server struct {
|
||||||
wTimeout time.Duration
|
wTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Addr() string {
|
|
||||||
return net.JoinHostPort(s.host, strconv.Itoa(s.port))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
func (s *Server) Run() {
|
||||||
handler := NewHandler()
|
handler := NewHandler()
|
||||||
|
|
||||||
tcpHandler := dns.NewServeMux()
|
nets := viper.GetStringSlice("networks")
|
||||||
tcpHandler.HandleFunc(".", handler.DoTCP)
|
|
||||||
|
|
||||||
udpHandler := dns.NewServeMux()
|
var addr string
|
||||||
udpHandler.HandleFunc(".", handler.DoUDP)
|
var split []string
|
||||||
|
|
||||||
tcpServer := &dns.Server{
|
// Defaults: tcp, udp
|
||||||
Addr: s.Addr(),
|
for _, net := range nets {
|
||||||
Net: "tcp",
|
split = strings.Split(net, ":")
|
||||||
Handler: tcpHandler,
|
|
||||||
|
net = split[0]
|
||||||
|
|
||||||
|
addr = s.host
|
||||||
|
|
||||||
|
if len(split) == 1 {
|
||||||
|
addr += ":53"
|
||||||
|
} else {
|
||||||
|
addr += ":" + split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
h := dns.NewServeMux()
|
||||||
|
h.HandleFunc(".", handler.Bind(net))
|
||||||
|
|
||||||
|
server := &dns.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Net: net,
|
||||||
|
Handler: h,
|
||||||
ReadTimeout: s.rTimeout,
|
ReadTimeout: s.rTimeout,
|
||||||
WriteTimeout: s.wTimeout,
|
WriteTimeout: s.wTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
udpServer := &dns.Server{
|
go s.start(server)
|
||||||
Addr: s.Addr(),
|
|
||||||
Net: "udp",
|
|
||||||
Handler: udpHandler,
|
|
||||||
UDPSize: 65535,
|
|
||||||
ReadTimeout: s.rTimeout,
|
|
||||||
WriteTimeout: s.wTimeout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.start(udpServer)
|
|
||||||
go s.start(tcpServer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) start(ds *dns.Server) {
|
func (s *Server) start(ds *dns.Server) {
|
||||||
logger.Info("Start %s listener on %s", ds.Net, s.Addr())
|
log.Info("Start %s listener on %s", ds.Net, ds.Addr)
|
||||||
|
|
||||||
err := ds.ListenAndServe()
|
err := ds.ListenAndServe()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Start %s listener on %s failed:%s", ds.Net, s.Addr(), err.Error())
|
log.Error("Start %s listener on %s failed:%s", ds.Net, ds.Addr, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"meow.tf/joker/godns/log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -15,11 +16,20 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var LogLevelMap = map[string]int{
|
var LogLevelMap = map[string]int{
|
||||||
"DEBUG": LevelDebug,
|
"DEBUG": log.LevelDebug,
|
||||||
"INFO": LevelInfo,
|
"INFO": log.LevelInfo,
|
||||||
"NOTICE": LevelNotice,
|
"NOTICE": log.LevelNotice,
|
||||||
"WARN": LevelWarn,
|
"WARN": log.LevelWarn,
|
||||||
"ERROR": LevelError,
|
"ERROR": log.LevelError,
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostsSettings struct {
|
||||||
|
Enable bool `toml:"enable" env:"HOSTS_ENABLE"`
|
||||||
|
HostsFile string `toml:"host-file" env:"HOSTS_FILE"`
|
||||||
|
RedisEnable bool `toml:"redis-enable" env:"REDIS_HOSTS_ENABLE"`
|
||||||
|
RedisKey string `toml:"redis-key" env:"REDIS_HOSTS_KEY"`
|
||||||
|
TTL uint32 `toml:"ttl" env:"HOSTS_TTL"`
|
||||||
|
RefreshInterval uint32 `toml:"refresh-interval" env:"HOSTS_REFRESH_INTERVAL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
@ -83,15 +93,6 @@ type CacheSettings struct {
|
||||||
Maxcount int `toml:"maxcount" env:"CACHE_MAX_COUNT"`
|
Maxcount int `toml:"maxcount" env:"CACHE_MAX_COUNT"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostsSettings struct {
|
|
||||||
Enable bool `toml:"enable" env:"HOSTS_ENABLE"`
|
|
||||||
HostsFile string `toml:"host-file" env:"HOSTS_FILE"`
|
|
||||||
RedisEnable bool `toml:"redis-enable" env:"REDIS_HOSTS_ENABLE"`
|
|
||||||
RedisKey string `toml:"redis-key" env:"REDIS_HOSTS_KEY"`
|
|
||||||
TTL uint32 `toml:"ttl" env:"HOSTS_TTL"`
|
|
||||||
RefreshInterval uint32 `toml:"refresh-interval" env:"HOSTS_REFRESH_INTERVAL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var configFile string
|
var configFile string
|
||||||
|
|
||||||
|
@ -111,3 +112,35 @@ func init() {
|
||||||
env.Parse(&settings.Cache)
|
env.Parse(&settings.Cache)
|
||||||
env.Parse(&settings.Hosts)
|
env.Parse(&settings.Hosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Resolver() ResolvSettings {
|
||||||
|
return settings.ResolvConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cache() CacheSettings {
|
||||||
|
return settings.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func Server() DNSServerSettings {
|
||||||
|
return settings.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hosts() HostsSettings {
|
||||||
|
return settings.Hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
func Redis() RedisSettings {
|
||||||
|
return settings.Redis
|
||||||
|
}
|
||||||
|
|
||||||
|
func Memcache() MemcacheSettings {
|
||||||
|
return settings.Memcache
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug() bool {
|
||||||
|
return settings.Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func Log() LogSettings {
|
||||||
|
return settings.Log
|
||||||
|
}
|
18
utils.go
18
utils.go
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnFqdn(s string) string {
|
||||||
|
if dns.IsFqdn(s) {
|
||||||
|
return s[:len(s)-1]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
Loading…
Reference in New Issue