Redo settings a bit, move a lot of init logic to main.go
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Tyler 2020-01-25 13:48:26 -05:00
parent 991ae3ecb5
commit f726a5d5ae
12 changed files with 152 additions and 394 deletions

View File

@ -7,7 +7,7 @@ import (
"time" "time"
) )
func NewRedisCache(c settings.RedisSettings, expire int32) *RedisCache { func NewRedisCache(c settings.RedisSettings, expire time.Duration) *RedisCache {
rc := redis.NewClient(&redis.Options{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{
@ -20,7 +20,7 @@ type RedisCache struct {
Cache Cache
backend *redis.Client backend *redis.Client
expire int32 expire time.Duration
} }
func (m *RedisCache) Set(key string, msg *dns.Msg) error { func (m *RedisCache) Set(key string, msg *dns.Msg) error {
@ -38,7 +38,7 @@ 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.Set(key, val, time.Duration(m.expire) * time.Second).Err() return m.backend.Set(key, val, m.expire).Err()
} }
func (m *RedisCache) Get(key string) (*dns.Msg, error) { func (m *RedisCache) Get(key string) (*dns.Msg, error) {

View File

@ -1,46 +0,0 @@
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

View File

@ -1,58 +1,48 @@
#Toml config file #Toml config file
debug = false
Title = "GODNS"
Version = "0.2.0"
Author = "kenshin, tystuyfzand"
Debug = false
[server] [server]
host = "0.0.0.0" host = "0.0.0.0"
nets = ["tcp:53", "udp:53"] nets = ["tcp:53", "udp:53"]
[resolv] [resolv]
# Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq file = ""
# Semicolon separate multiple files.
resolv-file = ""
timeout = 5 # 5 seconds timeout = 5 # 5 seconds
# The concurrency interval request upstream recursive server
# Match the PR15, https://github.com/kenshinx/godns/pull/15
interval = 200 # 200 milliseconds interval = 200 # 200 milliseconds
setedns0 = false #Support for larger UDP DNS responses edns0 = false #Support for larger UDP DNS responses
server-list-file = "etc/serverlist.conf" # Domain-specific nameservers configuration, formatting keep compatible with Dnsmasq
server-list = "etc/serverlist.conf"
[redis]
enable = true
host = "192.168.1.71"
port = 6379
db = 0
password =""
[memcache]
servers = ["127.0.0.1:11211"]
[log] [log]
stdout = true stdout = true
file = "./godns.log" file = "./godns.log"
level = "DEBUG" #DEBUG | INFO |NOTICE | WARN | ERROR level = "DEBUG" #DEBUG | INFO |NOTICE | WARN | ERROR
[cache] [cache]
# backend option [memory|memcache|redis] # backend option [memory|memcache|redis]
# redis backend not implemented yet
backend = "memory" backend = "memory"
expire = 600 # 10 minutes expire = 600 # 10 minutes
maxcount = 0 #If set zero. The Sum of cache itmes will be unlimit. maxcount = 0 #If set zero. The Sum of cache itmes will be unlimit.
[cache.memcached]
servers = ["127.0.0.1:11211"]
# Redis cache backend config
[cache.redis]
host = "192.168.1.71"
port = 6379
db = 0
password =""
[hosts] [hosts]
#If set false, will not query hosts file and redis hosts record # File provider, supports inotify hot-reload
enable = true [hosts.file]
host-file = "/etc/hosts" enable = true
redis-enable = true file = "/etc/hosts"
redis-key = "godns:hosts" ttl = 600
ttl = 600
refresh-interval = 60 # 5 seconds
# Redis provider
[hosts.redis]
enable = true
key = "godns:hosts"
ttl = 600

View File

@ -1,42 +0,0 @@
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

View File

@ -3,16 +3,14 @@ package main
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"github.com/miekg/dns"
"meow.tf/joker/godns/cache" "meow.tf/joker/godns/cache"
"meow.tf/joker/godns/hosts" "meow.tf/joker/godns/hosts"
"meow.tf/joker/godns/log" "meow.tf/joker/godns/log"
"meow.tf/joker/godns/resolver" "meow.tf/joker/godns/resolver"
"meow.tf/joker/godns/settings"
"meow.tf/joker/godns/utils" "meow.tf/joker/godns/utils"
"net" "net"
"time" "time"
"github.com/miekg/dns"
) )
const ( const (
@ -21,54 +19,17 @@ const (
_IP6Query = 6 _IP6Query = 6
) )
type GODNSHandler struct { type Handler struct {
resolver *resolver.Resolver resolver *resolver.Resolver
cache, negCache cache.Cache cache, negCache cache.Cache
hosts hosts.Hosts hosts hosts.Hosts
} }
func NewHandler() *GODNSHandler { func NewHandler(r *resolver.Resolver, resolverCache, negCache cache.Cache, h hosts.Hosts) *Handler {
return &Handler{r, resolverCache, negCache, h}
var (
cacheConfig settings.CacheSettings
r *resolver.Resolver
resolverCache, negCache cache.Cache
)
r = resolver.NewResolver(settings.Resolver())
cacheConfig = settings.Cache()
switch cacheConfig.Backend {
case "memory":
cacheDuration := time.Duration(cacheConfig.Expire) * time.Second
negCache = cache.NewMemoryCache(cacheDuration/2, cacheConfig.Maxcount)
resolverCache = cache.NewMemoryCache(time.Duration(cacheConfig.Expire)*time.Second, cacheConfig.Maxcount)
case "memcache":
resolverCache = cache.NewMemcachedCache(
settings.Memcache().Servers,
int32(cacheConfig.Expire))
negCache = cache.NewMemcachedCache(
settings.Memcache().Servers,
int32(cacheConfig.Expire/2))
case "redis":
resolverCache = cache.NewRedisCache(
settings.Redis(),
int32(cacheConfig.Expire))
negCache = cache.NewRedisCache(
settings.Redis(),
int32(cacheConfig.Expire/2))
default:
log.Error("Invalid cache backend %s", cacheConfig.Backend)
panic("Invalid cache backend")
}
h := hosts.NewHosts(settings.Hosts(), settings.Redis())
return &GODNSHandler{r, resolverCache, negCache, h}
} }
func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) { func (h *Handler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
q := req.Question[0] q := req.Question[0]
question := resolver.Question{Name: utils.UnFqdn(q.Name), Type: dns.TypeToString[q.Qtype], Class: dns.ClassToString[q.Qclass]} question := resolver.Question{Name: utils.UnFqdn(q.Name), Type: dns.TypeToString[q.Qtype], Class: dns.ClassToString[q.Qclass]}
@ -84,7 +45,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
// Query hosts // Query hosts
if h.hosts != nil && IPQuery > 0 { if h.hosts != nil && IPQuery > 0 {
if ips, ok := h.hosts.Get(question.Name, IPQuery); ok { if ips, ttl, ok := h.hosts.Get(question.Name, IPQuery); ok {
m := new(dns.Msg) m := new(dns.Msg)
m.SetReply(req) m.SetReply(req)
@ -94,7 +55,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
Name: q.Name, Name: q.Name,
Rrtype: dns.TypeA, Rrtype: dns.TypeA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: h.hosts.TTL(), Ttl: uint32(ttl / time.Second),
} }
for _, ip := range ips { for _, ip := range ips {
m.Answer = append(m.Answer, &dns.A{Hdr: hdr, A: ip}) m.Answer = append(m.Answer, &dns.A{Hdr: hdr, A: ip})
@ -104,7 +65,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
Name: q.Name, Name: q.Name,
Rrtype: dns.TypeAAAA, Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: h.hosts.TTL(), Ttl: uint32(ttl / time.Second),
} }
for _, ip := range ips { for _, ip := range ips {
m.Answer = append(m.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip}) m.Answer = append(m.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip})
@ -165,13 +126,13 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
} }
} }
func (h *GODNSHandler) Bind(net string) func(w dns.ResponseWriter, req *dns.Msg) { func (h *Handler) Bind(net string) func(w dns.ResponseWriter, req *dns.Msg) {
return func(w dns.ResponseWriter, req *dns.Msg) { return func(w dns.ResponseWriter, req *dns.Msg) {
h.do(net, w, req) h.do(net, w, req)
} }
} }
func (h *GODNSHandler) isIPQuery(q dns.Question) int { func (h *Handler) isIPQuery(q dns.Question) int {
if q.Qclass != dns.ClassINET { if q.Qclass != dns.ClassINET {
return notIPQuery return notIPQuery
} }

View File

@ -1,12 +1,8 @@
package hosts package hosts
import ( import (
"meow.tf/joker/godns/log"
"meow.tf/joker/godns/settings"
"net" "net"
"time" "time"
"github.com/hoisie/redis"
) )
const ( const (
@ -15,70 +11,38 @@ const (
_IP6Query = 6 _IP6Query = 6
) )
var (
zeroDuration = time.Duration(0)
)
type Hosts interface { type Hosts interface {
Get(domain string, family int) ([]net.IP, bool) Get(domain string, family int) ([]net.IP, time.Duration, bool)
TTL() uint32
} }
type ProviderList struct { type ProviderList struct {
settings settings.HostsSettings providers []Provider
providers []HostProvider
refreshInterval time.Duration
} }
type HostProvider interface { type Provider interface {
Get(domain string) ([]string, bool) Get(domain string) ([]string, time.Duration, bool)
Refresh()
} }
func NewHosts(hs settings.HostsSettings, rs settings.RedisSettings) Hosts { func NewHosts(providers []Provider) Hosts {
providers := []HostProvider{ return &ProviderList{providers}
NewFileProvider(hs.HostsFile),
}
if hs.RedisEnable {
log.Info("Redis is enabled: %s", rs.Addr())
rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password}
providers = append(providers, NewRedisProvider(rc, hs.RedisKey))
}
h := &ProviderList{hs, providers, time.Second * time.Duration(hs.RefreshInterval)}
if h.refreshInterval > 0 {
h.refresh()
}
return h
}
func (h *ProviderList) refresh() {
ticker := time.NewTicker(h.refreshInterval)
go func() {
for {
// Force a refresh every refreshInterval
for _, provider := range h.providers {
provider.Refresh()
}
<-ticker.C
}
}()
} }
/* /*
Match local /etc/hosts file first, remote redis records second Match local /etc/hosts file first, remote redis records second
*/ */
func (h *ProviderList) Get(domain string, family int) ([]net.IP, bool) { func (h *ProviderList) Get(domain string, family int) ([]net.IP, time.Duration, bool) {
var sips []string var sips []string
var ok bool var ok bool
var ip net.IP var ip net.IP
var ips []net.IP var ips []net.IP
var ttl time.Duration
for _, provider := range h.providers { for _, provider := range h.providers {
sips, ok = provider.Get(domain) sips, ttl, ok = provider.Get(domain)
if ok { if ok {
break break
@ -86,7 +50,7 @@ func (h *ProviderList) Get(domain string, family int) ([]net.IP, bool) {
} }
if sips == nil { if sips == nil {
return nil, false return nil, zeroDuration, false
} }
for _, sip := range sips { for _, sip := range sips {
@ -103,9 +67,5 @@ func (h *ProviderList) Get(domain string, family int) ([]net.IP, bool) {
} }
} }
return ips, ips != nil return ips, ttl, ips != nil
}
func (h *ProviderList) TTL() uint32 {
return h.settings.TTL
} }

View File

@ -10,20 +10,23 @@ import (
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"time"
) )
type FileHosts struct { type FileHosts struct {
HostProvider Provider
file string file string
hosts map[string]string hosts map[string]string
mu sync.RWMutex mu sync.RWMutex
ttl time.Duration
} }
func NewFileProvider(file string) HostProvider { func NewFileProvider(file string, ttl time.Duration) Provider {
fp := &FileHosts{ fp := &FileHosts{
file: file, file: file,
hosts: make(map[string]string), hosts: make(map[string]string),
ttl: ttl,
} }
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
@ -46,7 +49,7 @@ func NewFileProvider(file string) HostProvider {
return fp return fp
} }
func (f *FileHosts) Get(domain string) ([]string, bool) { func (f *FileHosts) Get(domain string) ([]string, time.Duration, bool) {
log.Debug("Checking file provider for %s", domain) log.Debug("Checking file provider for %s", domain)
f.mu.RLock() f.mu.RLock()
@ -54,24 +57,24 @@ func (f *FileHosts) Get(domain string) ([]string, bool) {
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
if ip, ok := f.hosts[domain]; ok { if ip, ok := f.hosts[domain]; ok {
return strings.Split(ip, ","), true return strings.Split(ip, ","), f.ttl, true
} }
if idx := strings.Index(domain, "."); idx != -1 { if idx := strings.Index(domain, "."); idx != -1 {
wildcard := "*." + domain[strings.Index(domain, ".") + 1:] wildcard := "*." + domain[strings.Index(domain, ".") + 1:]
if ip, ok := f.hosts[wildcard]; ok { if ip, ok := f.hosts[wildcard]; ok {
return strings.Split(ip, ","), true return strings.Split(ip, ","), f.ttl, true
} }
} }
for host, ip := range f.hosts { for host, ip := range f.hosts {
if glob.Glob(host, domain) { if glob.Glob(host, domain) {
return strings.Split(ip, ","), true return strings.Split(ip, ","), f.ttl, true
} }
} }
return nil, false return nil, time.Duration(0), false
} }
var ( var (

View File

@ -1,83 +1,39 @@
package hosts package hosts
import ( import (
"github.com/hoisie/redis" "github.com/go-redis/redis/v7"
"github.com/ryanuber/go-glob" "github.com/ryanuber/go-glob"
"meow.tf/joker/godns/log" "meow.tf/joker/godns/log"
"strings" "strings"
"sync" "sync"
"time"
) )
type RedisHosts struct { type RedisHosts struct {
HostProvider Provider
redis *redis.Client redis *redis.Client
key string key string
hosts map[string]string hosts map[string]string
mu sync.RWMutex mu sync.RWMutex
ttl time.Duration
} }
func NewRedisProvider(rc *redis.Client, key string) HostProvider { func NewRedisProvider(rc *redis.Client, key string, ttl time.Duration) Provider {
rh := &RedisHosts{ rh := &RedisHosts{
redis: rc, redis: rc,
key: key, key: key,
hosts: make(map[string]string), hosts: make(map[string]string),
ttl: ttl,
} }
// Force an initial refresh // Force an initial refresh
rh.Refresh() rh.Refresh()
// Use pubsub to listen for key update events
go func() {
keyspaceEvent := "__keyspace@0__:" + key
sub := make(chan string, 3)
sub <- keyspaceEvent
sub <- "godns:update"
sub <- "godns:update_record"
messages := make(chan redis.Message, 0)
go rc.Subscribe(sub, nil, nil, nil, messages)
for {
msg := <-messages
if msg.Channel == "godns:update" {
log.Debug("Refreshing redis records due to update")
rh.Refresh()
} else if msg.Channel == "godns:update_record" {
recordName := string(msg.Message)
b, err := rc.Hget(key, recordName)
if err != nil {
log.Warn("Record %s does not exist, but was updated", recordName)
continue
}
log.Debug("Record %s was updated to %s", recordName, string(b))
rh.mu.Lock()
rh.hosts[recordName] = string(b)
rh.mu.Unlock()
} else if msg.Channel == "godns:remove_record" {
log.Debug("Record %s was removed", msg.Message)
recordName := string(msg.Message)
rh.mu.Lock()
delete(rh.hosts, recordName)
rh.mu.Unlock()
} else if msg.Channel == keyspaceEvent {
log.Debug("Refreshing redis records due to update")
rh.Refresh()
}
}
}()
return rh return rh
} }
func (r *RedisHosts) Get(domain string) ([]string, bool) { func (r *RedisHosts) Get(domain string) ([]string, time.Duration, bool) {
log.Debug("Checking redis provider for %s", domain) log.Debug("Checking redis provider for %s", domain)
r.mu.RLock() r.mu.RLock()
@ -86,37 +42,39 @@ func (r *RedisHosts) Get(domain string) ([]string, bool) {
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
if ip, ok := r.hosts[domain]; ok { if ip, ok := r.hosts[domain]; ok {
return strings.Split(ip, ","), true return strings.Split(ip, ","), r.ttl, true
} }
if idx := strings.Index(domain, "."); idx != -1 { if idx := strings.Index(domain, "."); idx != -1 {
wildcard := "*." + domain[strings.Index(domain, ".")+1:] wildcard := "*." + domain[strings.Index(domain, ".")+1:]
if ip, ok := r.hosts[wildcard]; ok { if ip, ok := r.hosts[wildcard]; ok {
return strings.Split(ip, ","), true return strings.Split(ip, ","), r.ttl, true
} }
} }
for host, ip := range r.hosts { for host, ip := range r.hosts {
if glob.Glob(host, domain) { if glob.Glob(host, domain) {
return strings.Split(ip, ","), true return strings.Split(ip, ","), r.ttl, true
} }
} }
return nil, false return nil, time.Duration(0), false
} }
func (r *RedisHosts) Set(domain, ip string) (bool, error) { func (r *RedisHosts) Set(domain, ip string) (bool, error) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
return r.redis.Hset(r.key, strings.ToLower(domain), []byte(ip)) return r.redis.HSet(r.key, strings.ToLower(domain), []byte(ip)).Result()
} }
func (r *RedisHosts) Refresh() { func (r *RedisHosts) Refresh() {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
r.clear() r.clear()
err := r.redis.Hgetall(r.key, r.hosts)
var err error
r.hosts, err = r.redis.HGetAll(r.key).Result()
if err != nil { if err != nil {
log.Warn("Update hosts records from redis failed %s", err) log.Warn("Update hosts records from redis failed %s", err)
} else { } else {

80
main.go
View File

@ -3,8 +3,12 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/go-redis/redis/v7"
"github.com/spf13/viper" "github.com/spf13/viper"
"meow.tf/joker/godns/cache"
"meow.tf/joker/godns/hosts"
"meow.tf/joker/godns/log" "meow.tf/joker/godns/log"
"meow.tf/joker/godns/resolver"
"meow.tf/joker/godns/settings" "meow.tf/joker/godns/settings"
"os" "os"
"os/signal" "os/signal"
@ -36,29 +40,79 @@ func main() {
fmt.Println("Using config file:", viper.ConfigFileUsed()) fmt.Println("Using config file:", viper.ConfigFileUsed())
} }
serverSettings := settings.Server()
server := &Server{ server := &Server{
host: serverSettings.Host, host: viper.GetString("server.host"),
port: serverSettings.Port, networks: viper.GetStringSlice("server.nets"),
rTimeout: 5 * time.Second, rTimeout: 5 * time.Second,
wTimeout: 5 * time.Second, wTimeout: 5 * time.Second,
} }
server.Run() var resolverCache, negCache cache.Cache
r := resolver.NewResolver(settings.ResolvSettings{
Timeout: viper.GetInt("resolv.timeout"),
Interval: viper.GetInt("resolv.interval"),
SetEDNS0: viper.GetBool("resolv.edns0"),
ServerListFile: viper.GetStringSlice("resolv.server-list"),
ResolvFile: viper.GetString("resolv.file"),
})
log.Info("godns %s (%s) start", Version, runtime.Version()) cacheDuration := viper.GetDuration("cache.expire")
if settings.Debug() { redisConfig := settings.RedisSettings{
Host: viper.GetString("cache.redis.host"),
Port: viper.GetInt("cache.redis.port"),
DB: viper.GetInt("cache.redis.db"),
Password: viper.GetString("cache.redis.password"),
}
backend := viper.GetString("cache.backend")
switch backend {
case "memory":
cacheMaxCount := viper.GetInt("cache.memory.maxCount")
negCache = cache.NewMemoryCache(cacheDuration/2, cacheMaxCount)
resolverCache = cache.NewMemoryCache(cacheDuration, cacheMaxCount)
case "memcached":
servers := viper.GetStringSlice("cache.memcached.servers")
resolverCache = cache.NewMemcachedCache(servers, int32(cacheDuration.Seconds()))
negCache = cache.NewMemcachedCache(servers, int32(cacheDuration.Seconds()/2))
case "redis":
resolverCache = cache.NewRedisCache(redisConfig, cacheDuration)
negCache = cache.NewRedisCache(redisConfig, cacheDuration/2)
default:
log.Error("Invalid cache backend %s", backend)
panic("Invalid cache backend")
}
providers := make([]hosts.Provider, 0)
if viper.GetBool("hosts.file.enable") {
providers = append(providers, hosts.NewFileProvider(viper.GetString("hosts.file.file"), viper.GetDuration("hosts.file.ttl")))
}
if viper.GetBool("hosts.redis.enable") {
rc := redis.NewClient(&redis.Options{Addr: redisConfig.Addr(), DB: redisConfig.DB, Password: redisConfig.Password})
providers = append(providers, hosts.NewRedisProvider(rc, viper.GetString("hosts.redis.key"), viper.GetDuration("hosts.redis.ttl")))
}
h := hosts.NewHosts(providers)
server.Run(NewHandler(r, resolverCache, negCache, h))
log.Info("godns %s (%s)", Version, runtime.Version())
if viper.GetBool("debug") {
go profileCPU() go profileCPU()
go profileMEM() go profileMEM()
} }
sig := make(chan os.Signal) sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt) signal.Notify(sig, os.Interrupt)
<- sig <- sig
log.Info("signal received, stopping") log.Info("signal received, stopping")
} }
@ -93,8 +147,6 @@ func profileMEM() {
} }
func initLogger() { func initLogger() {
logSettings := settings.Log()
if viper.GetBool("log.stdout") { if viper.GetBool("log.stdout") {
log.SetLogger("console", nil) log.SetLogger("console", nil)
} }
@ -103,9 +155,5 @@ func initLogger() {
log.SetLogger("file", map[string]interface{}{"file": file}) log.SetLogger("file", map[string]interface{}{"file": file})
} }
log.SetLevel(logSettings.LogLevel()) log.SetLevel(settings.LogLevelFor(viper.GetString("log.level")))
}
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
} }

View File

@ -68,10 +68,6 @@ func NewResolver(c settings.ResolvSettings) *Resolver {
} }
} }
if len(c.DOHServer) > 0 {
r.servers = append([]string{c.DOHServer}, r.servers...)
}
return r return r
} }
@ -141,8 +137,7 @@ func (r *Resolver) parseServerListFile(buf *os.File) {
} }
func (r *Resolver) ReadServerListFile(path string) { func (r *Resolver) ReadServerListFile(files []string) {
files := strings.Split(path, ";")
for _, file := range files { for _, file := range files {
buf, err := os.Open(file) buf, err := os.Open(file)
if err != nil { if err != nil {

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"github.com/spf13/viper"
"meow.tf/joker/godns/log" "meow.tf/joker/godns/log"
"strings" "strings"
"time" "time"
@ -11,21 +10,17 @@ import (
type Server struct { type Server struct {
host string host string
port int networks []string
rTimeout time.Duration rTimeout time.Duration
wTimeout time.Duration wTimeout time.Duration
} }
func (s *Server) Run() { func (s *Server) Run(handler *Handler) {
handler := NewHandler()
nets := viper.GetStringSlice("networks")
var addr string var addr string
var split []string var split []string
// Defaults: tcp, udp // Defaults: tcp, udp
for _, net := range nets { for _, net := range s.networks {
split = strings.Split(net, ":") split = strings.Split(net, ":")
net = split[0] net = split[0]

View File

@ -1,18 +1,8 @@
package settings package settings
import ( import (
"flag"
"fmt"
"meow.tf/joker/godns/log" "meow.tf/joker/godns/log"
"os"
"strconv" "strconv"
"github.com/BurntSushi/toml"
"github.com/caarlos0/env"
)
var (
settings Settings
) )
var LogLevelMap = map[string]int{ var LogLevelMap = map[string]int{
@ -24,7 +14,6 @@ var LogLevelMap = map[string]int{
} }
type HostsSettings struct { type HostsSettings struct {
Enable bool `toml:"enable" env:"HOSTS_ENABLE"`
HostsFile string `toml:"host-file" env:"HOSTS_FILE"` HostsFile string `toml:"host-file" env:"HOSTS_FILE"`
RedisEnable bool `toml:"redis-enable" env:"REDIS_HOSTS_ENABLE"` RedisEnable bool `toml:"redis-enable" env:"REDIS_HOSTS_ENABLE"`
RedisKey string `toml:"redis-key" env:"REDIS_HOSTS_KEY"` RedisKey string `toml:"redis-key" env:"REDIS_HOSTS_KEY"`
@ -48,9 +37,8 @@ type ResolvSettings struct {
Timeout int `toml:"timeout" env:"RESOLV_TIMEOUT"` Timeout int `toml:"timeout" env:"RESOLV_TIMEOUT"`
Interval int `toml:"interval" env:"RESOLV_INTERVAL"` Interval int `toml:"interval" env:"RESOLV_INTERVAL"`
SetEDNS0 bool `toml:"setedns0" env:"RESOLV_EDNS0"` SetEDNS0 bool `toml:"setedns0" env:"RESOLV_EDNS0"`
ServerListFile string `toml:"server-list-file" env:"SERVER_LIST_FILE"` ServerListFile []string `toml:"server-list-file" env:"SERVER_LIST_FILE"`
ResolvFile string `toml:"resolv-file" env:"RESOLV_FILE"` ResolvFile string `toml:"resolv-file" env:"RESOLV_FILE"`
DOHServer string `toml:"dns-over-https" env:"DNS_HTTPS_SERVER"`
} }
type DNSServerSettings struct { type DNSServerSettings struct {
@ -79,10 +67,10 @@ type LogSettings struct {
Level string `toml:"level" env:"LOG_LEVEL"` Level string `toml:"level" env:"LOG_LEVEL"`
} }
func (ls LogSettings) LogLevel() int { func LogLevelFor(level string) int {
l, ok := LogLevelMap[ls.Level] l, ok := LogLevelMap[level]
if !ok { if !ok {
panic("Config error: invalid log level: " + ls.Level) panic("Config error: invalid log level: " + level)
} }
return l return l
} }
@ -92,55 +80,3 @@ type CacheSettings struct {
Expire int `toml:"expire" env:"CACHE_EXPIRE"` Expire int `toml:"expire" env:"CACHE_EXPIRE"`
Maxcount int `toml:"maxcount" env:"CACHE_MAX_COUNT"` Maxcount int `toml:"maxcount" env:"CACHE_MAX_COUNT"`
} }
func init() {
var configFile string
flag.StringVar(&configFile, "c", "/etc/godns.conf", "Look for godns toml-formatting config file in this directory")
flag.Parse()
if _, err := toml.DecodeFile(configFile, &settings); err != nil {
fmt.Printf("%s is not a valid toml config file\n", configFile)
fmt.Println(err)
os.Exit(1)
}
env.Parse(&settings.ResolvConfig)
env.Parse(&settings.Redis)
env.Parse(&settings.Memcache)
env.Parse(&settings.Log)
env.Parse(&settings.Cache)
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
}