You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
2.8 KiB
121 lines
2.8 KiB
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().Format(ISO8601),
|
|
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")
|
|
)
|
|
|
|
func (ql QueryLog) StartedTime() time.Time {
|
|
if ql.Started == "" {
|
|
return time.Time{}
|
|
}
|
|
|
|
out, _ := time.Parse(ISO8601, ql.Started)
|
|
return out
|
|
}
|
|
|
|
type QueryLog struct {
|
|
Started string
|
|
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
|
|
case Domain:
|
|
return ql.Domain
|
|
case RecurseIP:
|
|
return ql.RecurseUpstreamIP
|
|
case LookupError:
|
|
return ql.Error
|
|
}
|
|
|
|
return ql.Domain
|
|
}
|