├── dnsproxy_test.go ├── README.md ├── dnsproxy.conf ├── server.go ├── main.go ├── LICENSE ├── resolver.go ├── settings.go ├── hosts.go ├── cache.go └── handler.go /dnsproxy_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/miekg/dns" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | nameserver = "127.0.0.1:53" 10 | domain = "ur.gd" 11 | ) 12 | 13 | func BenchmarkDig(b *testing.B) { 14 | m := new(dns.Msg) 15 | m.SetQuestion(dns.Fqdn(domain), dns.TypeA) 16 | 17 | c := new(dns.Client) 18 | 19 | b.ResetTimer() 20 | 21 | for i := 0; i < b.N; i++ { 22 | c.Exchange(m, nameserver) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dnsproxy 2 | DNS Filtering proxy and minimalistic nameserver in GO 3 | 4 | - Recurse only A, AAAA, CNAME records (onlyipq) 5 | - Filter on domain suffix (suffixes) 6 | - Filter on returned IP (ipfilter) 7 | - Swap NXDOMAIN with static A response and set TTL (swapnxdip, swapnxdttl) 8 | - Hosts file lookup for static (override) entries 9 | 10 | Based on DNS library (github.com/miekg/dns) 11 | 12 | # Installation 13 | 14 | - Install golang, set GOPATH, get dependent libraries 15 | -- go get github.com/miekg/dns 16 | -- go get github.com/BurntSushi/toml 17 | - Build application 18 | -- go build -o dnsproxy *.go 19 | 20 | See dnsproxy.conf for examples 21 | -------------------------------------------------------------------------------- /dnsproxy.conf: -------------------------------------------------------------------------------- 1 | #Toml config file 2 | 3 | Title = "DNSProxy" 4 | Version = "0.0.1" 5 | Author = "DM" 6 | 7 | Debug = true 8 | 9 | [filters] 10 | onlyipq=true 11 | 12 | # if suffix starts with a non dot (ur.gd.) then both ^ur.gd.$ and ^.*\.ur.gd.$ is matched 13 | suffixes = [ ".amazonaws.com.", ".ge.com.", "ur.gd.", ".cnn.com." ] 14 | ipfilter = [ "10.", "94." ] 15 | swapnxdip = "223.255.255.255" 16 | swapnxttl = 600 17 | 18 | [server] 19 | host = "127.0.0.1" 20 | port = 53 21 | 22 | [resolv] 23 | resolv-file = "/etc/resolv.conf" 24 | timeout = 5 # 5 seconds 25 | 26 | [log] 27 | #If didn't set the log file,log will be redirected to console. 28 | file = "" 29 | 30 | [cache] 31 | backend = "memory" 32 | expire = 600 # 10 minutes 33 | maxcount = 0 #If set zero. The Sum of cache itmes will be unlimit. 34 | 35 | [hosts] 36 | enable = false 37 | host-file = "/etc/hosts" 38 | ttl = 600 39 | 40 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/miekg/dns" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | type Server struct { 10 | host string 11 | port int 12 | rTimeout time.Duration 13 | wTimeout time.Duration 14 | } 15 | 16 | func (s *Server) Addr() string { 17 | return s.host + ":" + strconv.Itoa(s.port) 18 | } 19 | 20 | func (s *Server) Run() { 21 | 22 | Handler := NewHandler() 23 | 24 | udpHandler := dns.NewServeMux() 25 | udpHandler.HandleFunc(".", Handler.DoUDP) 26 | 27 | udpServer := &dns.Server{Addr: s.Addr(), 28 | Net: "udp", 29 | Handler: udpHandler, 30 | UDPSize: 65535, 31 | ReadTimeout: s.rTimeout, 32 | WriteTimeout: s.wTimeout} 33 | 34 | go s.start(udpServer) 35 | } 36 | 37 | func (s *Server) start(ds *dns.Server) { 38 | 39 | logger.Printf("Start %s listener on %s\n", ds.Net, s.Addr()) 40 | err := ds.ListenAndServe() 41 | if err != nil { 42 | logger.Fatalf("Start %s listener on %s failed:%s", ds.Net, s.Addr(), err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "runtime" 8 | "time" 9 | ) 10 | 11 | var ( 12 | logger *log.Logger 13 | ) 14 | 15 | func main() { 16 | 17 | logger = initLogger(settings.Log.File) 18 | server := &Server{ 19 | host: settings.Server.Host, 20 | port: settings.Server.Port, 21 | rTimeout: 5 * time.Second, 22 | wTimeout: 5 * time.Second, 23 | } 24 | 25 | server.Run() 26 | logger.Printf("dnsproxy %s start", settings.Version) 27 | 28 | sig := make(chan os.Signal) 29 | signal.Notify(sig, os.Interrupt) 30 | 31 | forever: 32 | for { 33 | select { 34 | case <-sig: 35 | logger.Printf("signal received, stopping") 36 | break forever 37 | } 38 | } 39 | 40 | } 41 | 42 | func Debug(format string, v ...interface{}) { 43 | if settings.Debug { 44 | logger.Printf(format, v...) 45 | } 46 | } 47 | 48 | func initLogger(log_file string) (logger *log.Logger) { 49 | if log_file != "" { 50 | f, err := os.Create(log_file) 51 | if err != nil { 52 | os.Exit(1) 53 | } 54 | logger = log.New(f, "dnsproxy [=] ", log.Ldate|log.Ltime) 55 | } else { 56 | logger = log.New(os.Stdout, "dnsproxy [=]", log.Ldate|log.Ltime) 57 | } 58 | return logger 59 | 60 | } 61 | 62 | func init() { 63 | runtime.GOMAXPROCS(runtime.NumCPU()) 64 | } 65 | 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, dmagyar 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /resolver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/miekg/dns" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type ResolvError struct { 11 | qname string 12 | nameservers []string 13 | } 14 | 15 | func (e ResolvError) Error() string { 16 | errmsg := fmt.Sprintf("%s resolv failed on %s", e.qname, strings.Join(e.nameservers, "; ")) 17 | return errmsg 18 | } 19 | 20 | type Resolver struct { 21 | config *dns.ClientConfig 22 | } 23 | 24 | func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) { 25 | c := &dns.Client{ 26 | Net: net, 27 | ReadTimeout: r.Timeout(), 28 | WriteTimeout: r.Timeout(), 29 | } 30 | 31 | qname := req.Question[0].Name 32 | 33 | for _, nameserver := range r.Nameservers() { 34 | r, rtt, err := c.Exchange(req, nameserver) 35 | if err != nil { 36 | Debug("%s socket error on %s", qname, nameserver) 37 | Debug("error:%s", err.Error()) 38 | continue 39 | } 40 | if r != nil && r.Rcode != dns.RcodeSuccess { 41 | Debug("%s failed to get an valid answer on %s", qname, nameserver) 42 | continue 43 | } 44 | Debug("%s resolv on %s ttl: %d", UnFqdn(qname), nameserver, rtt) 45 | return r, nil 46 | } 47 | return nil, ResolvError{qname, r.Nameservers()} 48 | 49 | } 50 | 51 | func (r *Resolver) Nameservers() (ns []string) { 52 | for _, server := range r.config.Servers { 53 | nameserver := server + ":" + r.config.Port 54 | ns = append(ns, nameserver) 55 | } 56 | return 57 | } 58 | 59 | func (r *Resolver) Timeout() time.Duration { 60 | return time.Duration(r.config.Timeout) * time.Second 61 | } 62 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/BurntSushi/toml" 10 | ) 11 | 12 | var ( 13 | settings Settings 14 | ) 15 | 16 | type Settings struct { 17 | Version string 18 | Debug bool 19 | Server DNSServerSettings `toml:"server"` 20 | ResolvConfig ResolvSettings `toml:"resolv"` 21 | Log LogSettings `toml:"log"` 22 | Cache CacheSettings `toml:"cache"` 23 | Hosts HostsSettings `toml:"hosts"` 24 | Filters FilterSettings `toml:"filters"` 25 | } 26 | 27 | type ResolvSettings struct { 28 | ResolvFile string `toml:"resolv-file"` 29 | Timeout int 30 | } 31 | 32 | type DNSServerSettings struct { 33 | Host string 34 | Port int 35 | } 36 | 37 | type DBSettings struct { 38 | Host string 39 | Port int 40 | DB int 41 | Password string 42 | } 43 | 44 | func (s DBSettings) Addr() string { 45 | return s.Host + ":" + strconv.Itoa(s.Port) 46 | } 47 | 48 | type LogSettings struct { 49 | File string 50 | } 51 | 52 | type CacheSettings struct { 53 | Backend string 54 | Expire int 55 | Maxcount int 56 | } 57 | 58 | type HostsSettings struct { 59 | Enable bool 60 | HostsFile string `toml:"host-file"` 61 | TTL uint32 `toml:"ttl"` 62 | } 63 | 64 | type FilterSettings struct { 65 | OnlyIPQ bool `toml:"onlyipq"` 66 | Suffixes []string `toml:"suffixes"` 67 | IPFilter []string `toml:"ipfilter"` 68 | SwapNXDIP string `toml:"swapnxdip"` 69 | SwapNXDTTL uint32 `toml:"swapnxdttl"` 70 | } 71 | 72 | func init() { 73 | 74 | var configFile string 75 | 76 | flag.StringVar(&configFile, "c", "dnsproxy.conf", "Look for dnsproxy toml-formatting config file in this directory") 77 | flag.Parse() 78 | 79 | if _, err := toml.DecodeFile(configFile, &settings); err != nil { 80 | fmt.Printf("%s is not a valid toml config file\n", configFile) 81 | fmt.Println(err) 82 | os.Exit(1) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /hosts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "os" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | type Hosts struct { 12 | FileHosts map[string]string 13 | } 14 | 15 | func NewHosts(hs HostsSettings) Hosts { 16 | fileHosts := &FileHosts{hs.HostsFile} 17 | hosts := Hosts{fileHosts.GetAll()} 18 | return hosts 19 | 20 | } 21 | 22 | /* 23 | 1. Resolve hosts file only one times 24 | 2. Match local /etc/hosts file first 25 | */ 26 | 27 | func (h *Hosts) Get(domain string, family int) (ip net.IP, ok bool) { 28 | var sip string 29 | 30 | if sip, ok = h.FileHosts[strings.ToLower(domain)]; !ok { 31 | return nil, false 32 | } 33 | 34 | switch family { 35 | case _IP4Query: 36 | ip = net.ParseIP(sip).To4() 37 | return ip, (ip != nil) 38 | case _IP6Query: 39 | ip = net.ParseIP(sip).To16() 40 | return ip, (ip != nil) 41 | } 42 | return nil, false 43 | } 44 | 45 | func (h *Hosts) GetAll() map[string]string { 46 | 47 | m := make(map[string]string) 48 | for domain, ip := range h.FileHosts { 49 | m[domain] = ip 50 | } 51 | return m 52 | } 53 | 54 | type FileHosts struct { 55 | file string 56 | } 57 | 58 | func (f *FileHosts) GetAll() map[string]string { 59 | var hosts = make(map[string]string) 60 | 61 | buf, err := os.Open(f.file) 62 | if err != nil { 63 | panic("Can't open " + f.file) 64 | } 65 | 66 | scanner := bufio.NewScanner(buf) 67 | for scanner.Scan() { 68 | 69 | line := scanner.Text() 70 | line = strings.TrimSpace(strings.ToLower(line)) 71 | 72 | if strings.HasPrefix(line, "#") || line == "" { 73 | continue 74 | } 75 | 76 | sli := strings.Split(line, " ") 77 | if len(sli) == 1 { 78 | sli = strings.Split(line, "\t") 79 | } 80 | 81 | if len(sli) < 2 { 82 | continue 83 | } 84 | 85 | domain := sli[len(sli)-1] 86 | ip := sli[0] 87 | if !f.isDomain(domain) || !f.isIP(ip) { 88 | continue 89 | } 90 | 91 | hosts[domain] = ip 92 | } 93 | return hosts 94 | } 95 | 96 | func (f *FileHosts) isDomain(domain string) bool { 97 | if f.isIP(domain) { 98 | return false 99 | } 100 | match, _ := regexp.MatchString("^[a-zA-Z0-9][a-zA-Z0-9-]", domain) 101 | return match 102 | } 103 | 104 | func (f *FileHosts) isIP(ip string) bool { 105 | return (net.ParseIP(ip) != nil) 106 | } 107 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/miekg/dns" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type KeyNotFound struct { 13 | key string 14 | } 15 | 16 | func (e KeyNotFound) Error() string { 17 | return e.key + " " + "not found" 18 | } 19 | 20 | type KeyExpired struct { 21 | Key string 22 | } 23 | 24 | func (e KeyExpired) Error() string { 25 | return e.Key + " " + "expired" 26 | } 27 | 28 | type CacheIsFull struct { 29 | } 30 | 31 | func (e CacheIsFull) Error() string { 32 | return "Cache is Full" 33 | } 34 | 35 | type SerializerError struct { 36 | } 37 | 38 | func (e SerializerError) Error() string { 39 | return "Serializer error" 40 | } 41 | 42 | type Mesg struct { 43 | Msg *dns.Msg 44 | Expire time.Time 45 | } 46 | 47 | type Cache interface { 48 | Get(key string) (Msg *dns.Msg, err error) 49 | Set(key string, Msg *dns.Msg) error 50 | Exists(key string) bool 51 | Remove(key string) 52 | Length() int 53 | } 54 | 55 | type MemoryCache struct { 56 | Backend map[string]Mesg 57 | Expire time.Duration 58 | Maxcount int 59 | mu *sync.RWMutex 60 | } 61 | 62 | func (c *MemoryCache) Get(key string) (*dns.Msg, error) { 63 | c.mu.RLock() 64 | mesg, ok := c.Backend[key] 65 | c.mu.RUnlock() 66 | if !ok { 67 | return nil, KeyNotFound{key} 68 | } 69 | 70 | if mesg.Expire.Before(time.Now()) { 71 | c.Remove(key) 72 | return nil, KeyExpired{key} 73 | } 74 | 75 | return mesg.Msg, nil 76 | 77 | } 78 | 79 | func (c *MemoryCache) Set(key string, msg *dns.Msg) error { 80 | if c.Full() && !c.Exists(key) { 81 | return CacheIsFull{} 82 | } 83 | 84 | expire := time.Now().Add(c.Expire) 85 | mesg := Mesg{msg, expire} 86 | c.mu.Lock() 87 | c.Backend[key] = mesg 88 | c.mu.Unlock() 89 | return nil 90 | } 91 | 92 | func (c *MemoryCache) Remove(key string) { 93 | c.mu.RLock() 94 | delete(c.Backend, key) 95 | c.mu.RUnlock() 96 | } 97 | 98 | func (c *MemoryCache) Exists(key string) bool { 99 | c.mu.RLock() 100 | _, ok := c.Backend[key] 101 | c.mu.RUnlock() 102 | return ok 103 | } 104 | 105 | func (c *MemoryCache) Length() int { 106 | c.mu.RLock() 107 | defer c.mu.RUnlock() 108 | return len(c.Backend) 109 | } 110 | 111 | func (c *MemoryCache) Full() bool { 112 | // if Maxcount is zero. the cache will never be full. 113 | if c.Maxcount == 0 { 114 | return false 115 | } 116 | return c.Length() >= c.Maxcount 117 | } 118 | 119 | func KeyGen(q Question) string { 120 | h := md5.New() 121 | h.Write([]byte(q.String())) 122 | x := h.Sum(nil) 123 | key := fmt.Sprintf("%x", x) 124 | return key 125 | } 126 | 127 | type JsonSerializer struct { 128 | } 129 | 130 | func (*JsonSerializer) Dumps(mesg *dns.Msg) (encoded []byte, err error) { 131 | encoded, err = json.Marshal(mesg) 132 | return 133 | } 134 | 135 | func (*JsonSerializer) Loads(data []byte, mesg **dns.Msg) error { 136 | err := json.Unmarshal(data, mesg) 137 | return err 138 | 139 | } 140 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | "net" 7 | "github.com/miekg/dns" 8 | "strings" 9 | ) 10 | 11 | type Question struct { 12 | qname string 13 | qtype string 14 | qclass string 15 | } 16 | 17 | const ( 18 | notIPQuery = 0 19 | _IP4Query = 4 20 | _IP6Query = 6 21 | ) 22 | 23 | func (q *Question) String() string { 24 | return q.qname + " " + q.qclass + " " + q.qtype 25 | } 26 | 27 | type DNSProxyHandler struct { 28 | resolver *Resolver 29 | cache Cache 30 | hosts Hosts 31 | mu *sync.Mutex 32 | } 33 | 34 | func NewHandler() *DNSProxyHandler { 35 | 36 | var ( 37 | clientConfig *dns.ClientConfig 38 | cacheConfig CacheSettings 39 | resolver *Resolver 40 | cache Cache 41 | ) 42 | 43 | resolvConfig := settings.ResolvConfig 44 | clientConfig, err := dns.ClientConfigFromFile(resolvConfig.ResolvFile) 45 | if err != nil { 46 | logger.Printf(":%s is not a valid resolv.conf file\n", resolvConfig.ResolvFile) 47 | logger.Println(err) 48 | panic(err) 49 | } 50 | clientConfig.Timeout = resolvConfig.Timeout 51 | resolver = &Resolver{clientConfig} 52 | 53 | cacheConfig = settings.Cache 54 | switch cacheConfig.Backend { 55 | case "memory": 56 | cache = &MemoryCache{ 57 | Backend: make(map[string]Mesg), 58 | Expire: time.Duration(cacheConfig.Expire) * time.Second, 59 | Maxcount: cacheConfig.Maxcount, 60 | mu: new(sync.RWMutex), 61 | } 62 | default: 63 | logger.Printf("Invalid cache backend %s", cacheConfig.Backend) 64 | panic("Invalid cache backend") 65 | } 66 | 67 | hosts := NewHosts(settings.Hosts) 68 | 69 | return &DNSProxyHandler{resolver, cache, hosts, new(sync.Mutex)} 70 | } 71 | 72 | func (h *DNSProxyHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) { 73 | q := req.Question[0] 74 | 75 | if (settings.Filters.OnlyIPQ)&&(q.Qtype == dns.TypeANY) { 76 | q.Qtype = dns.TypeA 77 | } 78 | 79 | Q := Question{UnFqdn(q.Name), dns.TypeToString[q.Qtype], dns.ClassToString[q.Qclass]} 80 | Debug("Question: %s", Q.String()) 81 | 82 | IPQuery := h.isIPQuery(q) 83 | if ((IPQuery == 0)&&(settings.Filters.OnlyIPQ)) { 84 | Debug("Only IP queries allowed (A,AAAA)") 85 | m := new(dns.Msg) 86 | m.SetReply(req) 87 | // return servfail 88 | m.MsgHdr.Rcode = 2 89 | w.WriteMsg(m) 90 | return 91 | } 92 | 93 | if (len(settings.Filters.Suffixes) > 0) { 94 | j := -1; 95 | for i := range settings.Filters.Suffixes { 96 | Debug("Suffix: %s", settings.Filters.Suffixes[i]) 97 | if (strings.HasSuffix(q.Name,settings.Filters.Suffixes[i])) { 98 | dot := "." 99 | dot += settings.Filters.Suffixes[i] 100 | if (settings.Filters.Suffixes[i][0] == '.') { 101 | j = i 102 | break 103 | } else if (len(q.Name) == len(settings.Filters.Suffixes[i])) { 104 | // exact match 105 | j = i 106 | break 107 | } else if (strings.HasSuffix(q.Name,dot)) { 108 | // matches '.'+suffix 109 | j = i 110 | break 111 | } 112 | } 113 | } 114 | if (j == -1) { 115 | Debug("Request %s does not match any suffix filter -> SRVFAIL", q.Name) 116 | m := new(dns.Msg) 117 | m.SetReply(req) 118 | // return servfail 119 | m.MsgHdr.Rcode = 2 120 | w.WriteMsg(m) 121 | return 122 | } else { 123 | Debug("Request %s matches suffix filter %s", q.Name, settings.Filters.Suffixes[j]) 124 | } 125 | } 126 | 127 | // Query hosts 128 | if settings.Hosts.Enable && IPQuery > 0 { 129 | if ip, ok := h.hosts.Get(Q.qname, IPQuery); ok { 130 | m := new(dns.Msg) 131 | m.SetReply(req) 132 | 133 | switch IPQuery { 134 | case _IP4Query: 135 | rr_header := dns.RR_Header{ 136 | Name: q.Name, 137 | Rrtype: dns.TypeA, 138 | Class: dns.ClassINET, 139 | Ttl: settings.Hosts.TTL, 140 | } 141 | a := &dns.A{rr_header, ip} 142 | m.Answer = append(m.Answer, a) 143 | case _IP6Query: 144 | rr_header := dns.RR_Header{ 145 | Name: q.Name, 146 | Rrtype: dns.TypeAAAA, 147 | Class: dns.ClassINET, 148 | Ttl: settings.Hosts.TTL, 149 | } 150 | aaaa := &dns.AAAA{rr_header, ip} 151 | m.Answer = append(m.Answer, aaaa) 152 | } 153 | 154 | w.WriteMsg(m) 155 | Debug("%s found in hosts file", Q.qname) 156 | return 157 | } else { 158 | Debug("%s didn't found in hosts file", Q.qname) 159 | } 160 | 161 | } 162 | 163 | // Only query cache when qtype == 'A' , qclass == 'IN' 164 | key := KeyGen(Q) 165 | if IPQuery > 0 { 166 | mesg, err := h.cache.Get(key) 167 | if err != nil { 168 | Debug("%s didn't hit cache: %s", Q.String(), err) 169 | } else { 170 | Debug("%s hit cache", Q.String()) 171 | h.mu.Lock() 172 | mesg.Id = req.Id 173 | w.WriteMsg(mesg) 174 | h.mu.Unlock() 175 | return 176 | } 177 | 178 | } 179 | 180 | mesg, err := h.resolver.Lookup(Net, req) 181 | 182 | if (((settings.Filters.OnlyIPQ) || (len(settings.Filters.IPFilter)>0)) && (err == nil)) { 183 | var newans []dns.RR 184 | for a := range mesg.Answer { 185 | ah := mesg.Answer[a].Header() 186 | Debug("ANS[%d]: %s",a,mesg.Answer[a]) 187 | if ((ah.Rrtype == dns.TypeA)&&(len(settings.Filters.IPFilter)>0)) { 188 | t, _ := mesg.Answer[a].(*dns.A) 189 | j := 0 190 | for i := range settings.Filters.IPFilter { 191 | if (strings.HasPrefix(t.A.String(),settings.Filters.IPFilter[i])) { 192 | Debug("A answer %s matches IPFilter %s", t.A.String(),settings.Filters.IPFilter[i]) 193 | j = 1 194 | break 195 | } 196 | } 197 | if (j == 0) { 198 | Debug("A answer %s does not match any IPFilter -> NXD", q.Name) 199 | err = dns.ErrId 200 | break 201 | } 202 | } 203 | if (settings.Filters.OnlyIPQ) { 204 | if (ah.Rrtype == dns.TypeA)||(ah.Rrtype == dns.TypeAAAA)||(ah.Rrtype == dns.TypeCNAME) { 205 | newans = append(newans, mesg.Answer[a]) 206 | } 207 | } 208 | } 209 | if (settings.Filters.OnlyIPQ) { 210 | mesg.Answer = newans 211 | } 212 | } 213 | 214 | if err != nil { 215 | if (len(settings.Filters.SwapNXDIP)>0) { 216 | rr_header := dns.RR_Header { 217 | Name: q.Name, 218 | Rrtype: dns.TypeA, 219 | Class: dns.ClassINET, 220 | Ttl: settings.Filters.SwapNXDTTL, 221 | } 222 | m := new(dns.Msg) 223 | m.SetReply(req) 224 | ip := net.ParseIP(settings.Filters.SwapNXDIP).To4() 225 | a := &dns.A{rr_header, ip} 226 | m.Answer = append(m.Answer, a) 227 | m.MsgHdr.Authoritative = true 228 | w.WriteMsg(m) 229 | return 230 | 231 | } else { 232 | Debug("%s", err) 233 | dns.HandleFailed(w, req) 234 | return 235 | } 236 | } 237 | w.WriteMsg(mesg) 238 | 239 | if IPQuery > 0 && len(mesg.Answer) > 0 { 240 | err = h.cache.Set(key, mesg) 241 | 242 | if err != nil { 243 | Debug("Set %s cache failed: %s", Q.String(), err.Error()) 244 | } 245 | 246 | Debug("Insert %s into cache", Q.String()) 247 | } 248 | } 249 | 250 | func (h *DNSProxyHandler) DoTCP(w dns.ResponseWriter, req *dns.Msg) { 251 | h.do("tcp", w, req) 252 | } 253 | 254 | func (h *DNSProxyHandler) DoUDP(w dns.ResponseWriter, req *dns.Msg) { 255 | h.do("udp", w, req) 256 | } 257 | 258 | func (h *DNSProxyHandler) isIPQuery(q dns.Question) int { 259 | if q.Qclass != dns.ClassINET { 260 | return notIPQuery 261 | } 262 | 263 | switch q.Qtype { 264 | case dns.TypeA: 265 | return _IP4Query 266 | case dns.TypeAAAA: 267 | return _IP6Query 268 | default: 269 | return notIPQuery 270 | } 271 | } 272 | 273 | func UnFqdn(s string) string { 274 | if dns.IsFqdn(s) { 275 | return s[:len(s)-1] 276 | } 277 | return s 278 | } 279 | --------------------------------------------------------------------------------