Update hosts records as a regular interval

1. /etc/hosts will be update cycle instead of update once at the progress run
2. Avoid access redis in each requests
This commit is contained in:
kenshinx 2015-02-12 14:09:49 +08:00
parent a5a8615b32
commit 81450a3983
2 changed files with 55 additions and 69 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"net"
"sync" "sync"
"time" "time"
@ -86,7 +87,8 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
// Query hosts // Query hosts
if settings.Hosts.Enable && IPQuery > 0 { if settings.Hosts.Enable && IPQuery > 0 {
if ip, ok := h.hosts.Get(Q.qname, IPQuery); ok { if sip, ok := h.hosts.Get(Q.qname); ok {
var ip net.IP
m := new(dns.Msg) m := new(dns.Msg)
m.SetReply(req) m.SetReply(req)
@ -98,6 +100,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: settings.Hosts.TTL, Ttl: settings.Hosts.TTL,
} }
ip = net.ParseIP(sip).To4()
a := &dns.A{rr_header, ip} a := &dns.A{rr_header, ip}
m.Answer = append(m.Answer, a) m.Answer = append(m.Answer, a)
case _IP6Query: case _IP6Query:
@ -107,6 +110,7 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: settings.Hosts.TTL, Ttl: settings.Hosts.TTL,
} }
ip = net.ParseIP(sip).To16()
aaaa := &dns.AAAA{rr_header, ip} aaaa := &dns.AAAA{rr_header, ip}
m.Answer = append(m.Answer, aaaa) m.Answer = append(m.Answer, aaaa)
} }

View File

@ -2,116 +2,98 @@ package main
import ( import (
"bufio" "bufio"
"errors"
"net" "net"
"os" "os"
"regexp" "regexp"
"strings" "strings"
"time"
"github.com/hoisie/redis" "github.com/hoisie/redis"
) )
type Hosts struct { type Hosts struct {
FileHosts *FileHosts fileHosts *FileHosts
RedisHosts *RedisHosts redisHosts *RedisHosts
} }
func NewHosts(hs HostsSettings, rs RedisSettings) Hosts { func NewHosts(hs HostsSettings, rs RedisSettings) Hosts {
fileHosts := &FileHosts{hs.HostsFile} fileHosts := &FileHosts{hs.HostsFile, make(map[string]string)}
var redisHosts *RedisHosts var redisHosts *RedisHosts
if hs.RedisEnable { if hs.RedisEnable {
rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password} rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password}
redisHosts = &RedisHosts{rc, hs.RedisKey} redisHosts = &RedisHosts{rc, hs.RedisKey, make(map[string]string)}
} else {
redisHosts = new(RedisHosts)
} }
hosts := Hosts{fileHosts, redisHosts} hosts := Hosts{fileHosts, redisHosts}
hosts.refresh()
return hosts return hosts
} }
/* /*
1. Resolve hosts file only one times 1. Match local /etc/hosts file first, remote redis records second
2. Request redis on every query called, not found performance lose serious yet. 2. Fetch hosts records from /etc/hosts file and redis per minute
3. Match local /etc/hosts file first, remote redis records second
*/ */
func (h *Hosts) Get(domain string, family int) (ip net.IP, ok bool) { func (h *Hosts) Get(domain string) (ip string, ok bool) {
var sip string
if sip, ok = h.FileHosts.Get(domain); !ok { if ip, ok = h.fileHosts.Get(domain); ok {
if sip, ok = h.RedisHosts.Get(domain); !ok { return
return nil, false
}
} }
switch family { if h.redisHosts != nil {
case _IP4Query: ip, ok = h.redisHosts.Get(domain)
ip = net.ParseIP(sip).To4() return
return ip, (ip != nil)
case _IP6Query:
ip = net.ParseIP(sip).To16()
return ip, (ip != nil)
}
return nil, false
} }
func (h *Hosts) GetAll() map[string]string { return ip, false
}
m := make(map[string]string) func (h *Hosts) refresh() {
for domain, ip := range h.RedisHosts.GetAll() { ticker := time.NewTicker(time.Minute)
m[domain] = ip go func() {
for {
h.fileHosts.Refresh()
if h.redisHosts != nil {
h.redisHosts.Refresh()
} }
for domain, ip := range h.FileHosts.GetAll() { <-ticker.C
m[domain] = ip
} }
return m }()
} }
type RedisHosts struct { type RedisHosts struct {
redis *redis.Client redis *redis.Client
key string key string
} hosts map[string]string
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) { func (r *RedisHosts) Get(domain string) (ip string, ok bool) {
if r.redis == nil { ip, ok = r.hosts[domain]
return "", false return
}
b, err := r.redis.Hget(r.key, domain)
return string(b), err == nil
} }
func (r *RedisHosts) Set(domain, ip string) (bool, error) { 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)) return r.redis.Hset(r.key, domain, []byte(ip))
} }
func (r *RedisHosts) Refresh() {
r.redis.Hgetall(r.key, r.hosts)
Debug("update hosts records from redis")
}
type FileHosts struct { type FileHosts struct {
file string file string
hosts map[string]string
} }
func (f *FileHosts) Get(domain string) (ip string, ok bool) { func (f *FileHosts) Get(domain string) (ip string, ok bool) {
hosts := f.GetAll() ip, ok = f.hosts[domain]
ip, ok = hosts[domain]
return return
} }
func (f *FileHosts) GetAll() map[string]string { func (f *FileHosts) Refresh() {
var hosts = make(map[string]string)
buf, err := os.Open(f.file) buf, err := os.Open(f.file)
if err != nil { if err != nil {
panic("Can't open " + f.file) panic("Can't open " + f.file)
@ -142,9 +124,9 @@ func (f *FileHosts) GetAll() map[string]string {
continue continue
} }
hosts[domain] = ip f.hosts[domain] = ip
} }
return hosts Debug("update hosts records from %s", f.file)
} }
func (f *FileHosts) isDomain(domain string) bool { func (f *FileHosts) isDomain(domain string) bool {