package internal import ( "log" "strings" "time" "github.com/miekg/dns" ) type DomainManager struct { Cache Storage RuleEvaluator Recursors Recursor } func (dm *DomainManager) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { // only grab first question: https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071 start := time.Now() q := r.Question[0] responseMessage := new(dns.Msg) ql := QueryLog{ Started: start.UTC(), Protocol: w.RemoteAddr().Network(), ClientIP: w.RemoteAddr().String()[:strings.LastIndex(w.RemoteAddr().String(), ":")], Domain: q.Name, Status: NoAnswer, } var resolved Resolved var err error // lookup in cache if dest := dm.LookupRecord(q.Name); dest != nil { responseMessage = new(dns.Msg) responseMessage.Answer = dest ql.Status = CacheHit } else if rule, ok := dm.Evaluate(q.Name); ok { // evaluate domain in rules engine responseMessage = rule.CreateAnswer(q.Name) responseMessage.Authoritative = true ql.Status = CustomRule } else if resolved, err = dm.Recursors.Resolve(r); err == nil { dm.SaveAnswers(q.Name, resolved.Message.Answer) responseMessage = resolved.Message ql.RecurseUpstreamIP = resolved.UpstreamUsed ql.RecurseRoundTripTimeMs = int(resolved.RoundtripTime.Milliseconds()) ql.Status = RecursedUpstream } else if err != nil { ql.Error = err.Error() } responseMessage.SetReply(r) responseMessage.RecursionAvailable = true responseMessage.Compress = true ql.TotalTimeMs = int(time.Since(start).Milliseconds()) log.Printf("%+v", ql) go func(q QueryLog) { if err := dm.Storage.Log(q); err != nil { log.Printf("ERROR WRITING LOG: %v", err) } }(ql) if err := w.WriteMsg(responseMessage); err != nil { log.Println(err) } } type ResponseStatus string const ( CacheMiss = ResponseStatus("CACHE MISS") CacheHit = ResponseStatus("CACHE HIT") CustomRule = ResponseStatus("CUSTOM RULE") RecursedUpstream = ResponseStatus("RECURSED") NoAnswer = ResponseStatus("NO ANSWER") ) type QueryLog struct { Started time.Time ClientIP string Protocol string Domain string TotalTimeMs int RecurseRoundTripTimeMs int RecurseUpstreamIP string Error string Status ResponseStatus } func GetAggregateColumnHeader(ql QueryLog, h LogAggregateColumn) string { switch h { case ClientIP: return ql.ClientIP case Status: return string(ql.Status) case Protocol: return ql.Protocol } return ql.Domain }