From abef9b7f0a957811a452885bee1070e989dc2d91 Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Thu, 26 Dec 2019 19:51:58 +0100 Subject: Include rcode in API responses --- cache/cache.go | 56 ++++++++++++++++++++++++++--------------------------- cache/cache_test.go | 30 ++++++---------------------- dns/proxy.go | 12 ++++++++++-- http/http.go | 24 +++++++---------------- http/http_test.go | 4 ++-- 5 files changed, 53 insertions(+), 73 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index 47db598..d168098 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -51,13 +51,37 @@ func (m *maintainer) run(cache *Cache) { // Value represents a value stored in the cache. type Value struct { - Question string - Qtype uint16 - Answers []string CreatedAt time.Time msg *dns.Msg } +// Rcode returns the response code of this cached value. +func (v *Value) Rcode() int { return v.msg.Rcode } + +// Question returns the question of this cached value. +func (v *Value) Question() string { return v.msg.Question[0].Name } + +// Qtype returns the DNS request type of this cached value. +func (v *Value) Qtype() uint16 { return v.msg.Question[0].Qtype } + +// Answers returns the DNS responses of this cached value. +func (v *Value) Answers() []string { + var answers []string + for _, answer := range v.msg.Answer { + switch v := answer.(type) { + case *dns.A: + answers = append(answers, v.A.String()) + case *dns.AAAA: + answers = append(answers, v.AAAA.String()) + case *dns.MX: + answers = append(answers, v.Mx) + case *dns.PTR: + answers = append(answers, v.Ptr) + } + } + return answers +} + // TTL returns the TTL of this cache value. func (v *Value) TTL() time.Duration { return minTTL(v.msg) } @@ -147,35 +171,11 @@ func (c *Cache) Set(k uint32, msg *dns.Msg) { delete(c.values, c.keys[0]) c.keys = c.keys[1:] } - c.values[k] = &Value{ - Question: question(msg), - Answers: answers(msg), - Qtype: qtype(msg), - CreatedAt: now, - msg: msg, - } + c.values[k] = &Value{CreatedAt: now, msg: msg} c.keys = append(c.keys, k) c.mu.Unlock() } -func qtype(msg *dns.Msg) uint16 { return msg.Question[0].Qtype } - -func question(msg *dns.Msg) string { return msg.Question[0].Name } - -func answers(msg *dns.Msg) []string { - var answers []string - for _, answer := range msg.Answer { - switch v := answer.(type) { - case *dns.A: - answers = append(answers, v.A.String()) - case *dns.AAAA: - answers = append(answers, v.AAAA.String()) - case *dns.MX: - answers = append(answers, v.Mx) - } - } - return answers -} func (c *Cache) deleteExpired() { c.mu.Lock() for k, v := range c.values { diff --git a/cache/cache_test.go b/cache/cache_test.go index 71e5e58..9c13e66 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -92,30 +92,12 @@ func TestCache(t *testing.T) { ok bool value *Value }{ - {msg, createdAt, createdAt, true, &Value{ - CreatedAt: createdAt, - Question: "foo.", - Qtype: 1, - Answers: []string{"192.0.2.1", "192.0.2.2"}, - msg: msg}, - }, // Not expired when query time == create time - {msg, createdAt, createdAt.Add(30 * time.Second), true, &Value{ - CreatedAt: createdAt, - Question: "foo.", - Qtype: 1, - Answers: []string{"192.0.2.1", "192.0.2.2"}, - msg: msg}, - }, // Not expired when below TTL - {msg, createdAt, createdAt.Add(60 * time.Second), true, &Value{ - CreatedAt: createdAt, - Question: "foo.", - Qtype: 1, - Answers: []string{"192.0.2.1", "192.0.2.2"}, - msg: msg}, - }, //, Not expired until TTL exceeds - {msg, createdAt, createdAt.Add(61 * time.Second), false, nil}, // Expired - {msgWithZeroTTL, createdAt, createdAt, false, nil}, // 0 TTL is not cached - {msgFailure, createdAt, createdAt, false, nil}, // Non-cacheable rcode + {msg, createdAt, createdAt, true, &Value{CreatedAt: createdAt, msg: msg}}, // Not expired when query time == create time + {msg, createdAt, createdAt.Add(30 * time.Second), true, &Value{CreatedAt: createdAt, msg: msg}}, // Not expired when below TTL + {msg, createdAt, createdAt.Add(60 * time.Second), true, &Value{CreatedAt: createdAt, msg: msg}}, //, Not expired until TTL exceeds + {msg, createdAt, createdAt.Add(61 * time.Second), false, nil}, // Expired + {msgWithZeroTTL, createdAt, createdAt, false, nil}, // 0 TTL is not cached + {msgFailure, createdAt, createdAt, false, nil}, // Non-cacheable rcode } for i, tt := range tests { c.now = func() time.Time { return tt.createdAt } diff --git a/dns/proxy.go b/dns/proxy.go index a159b26..9553be3 100644 --- a/dns/proxy.go +++ b/dns/proxy.go @@ -14,8 +14,6 @@ const ( TypeA = dns.TypeA // TypeAAAA represents the resource record type AAAA, an IPv6 address. TypeAAAA = dns.TypeAAAA - // TypeMX represents the resource record type MX, a mail exchange address. - TypeMX = dns.TypeMX // LogDiscard disables logging of DNS requests LogDiscard = iota // LogAll logs all DNS requests @@ -24,6 +22,14 @@ const ( LogHijacked ) +var ( + // TypeToString contains a mapping of DNS request type to string. + TypeToString = dns.TypeToString + + // RcodeToString contains a mapping of Mapping DNS return code to string. + RcodeToString = dns.RcodeToString +) + // Request represents a simplified DNS request. type Request struct { Type uint16 @@ -149,6 +155,8 @@ func answers(msg *dns.Msg) []string { answers = append(answers, v.AAAA.String()) case *dns.MX: answers = append(answers, v.Mx) + case *dns.PTR: + answers = append(answers, v.Ptr) } } return answers diff --git a/http/http.go b/http/http.go index 98ca90d..c2c6c5c 100644 --- a/http/http.go +++ b/http/http.go @@ -26,7 +26,8 @@ type entry struct { RemoteAddr net.IP `json:"remote_addr,omitempty"` Qtype string `json:"type"` Question string `json:"question"` - Answers []string `json:"answers"` + Answers []string `json:"answers,omitempty"` + Rcode string `json:"rcode,omitempty"` } type httpError struct { @@ -99,26 +100,15 @@ func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) (interface entries = append(entries, entry{ Time: v.CreatedAt.UTC().Format(time.RFC3339), TTL: int64(v.TTL().Truncate(time.Second).Seconds()), - Qtype: qtype(v.Qtype), - Question: v.Question, - Answers: v.Answers, + Qtype: dns.TypeToString[v.Qtype()], + Question: v.Question(), + Answers: v.Answers(), + Rcode: dns.RcodeToString[v.Rcode()], }) } return entries, nil } -func qtype(qtype uint16) string { - switch qtype { - case dns.TypeA: - return "A" - case dns.TypeAAAA: - return "AAAA" - case dns.TypeMX: - return "MX" - } - return "" -} - func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { logEntries, err := s.logger.Get(100) if err != nil { @@ -132,7 +122,7 @@ func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{} entries = append(entries, entry{ Time: le.Time.UTC().Format(time.RFC3339), RemoteAddr: le.RemoteAddr, - Qtype: qtype(le.Qtype), + Qtype: dns.TypeToString[le.Qtype], Question: le.Question, Answers: le.Answers, }) diff --git a/http/http_test.go b/http/http_test.go index fc100aa..c98577b 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -61,8 +61,8 @@ func TestRequests(t *testing.T) { srv.cache.Set(1, newA("1.example.com.", 60, net.IPv4(192, 0, 2, 200))) srv.cache.Set(2, newA("2.example.com.", 30, net.IPv4(192, 0, 2, 201))) - var cacheResponse = "[{\"time\":\"RFC3339\",\"ttl\":30,\"type\":\"A\",\"question\":\"2.example.com.\",\"answers\":[\"192.0.2.201\"]}," + - "{\"time\":\"RFC3339\",\"ttl\":60,\"type\":\"A\",\"question\":\"1.example.com.\",\"answers\":[\"192.0.2.200\"]}]" + var cacheResponse = "[{\"time\":\"RFC3339\",\"ttl\":30,\"type\":\"A\",\"question\":\"2.example.com.\",\"answers\":[\"192.0.2.201\"],\"rcode\":\"NOERROR\"}," + + "{\"time\":\"RFC3339\",\"ttl\":60,\"type\":\"A\",\"question\":\"1.example.com.\",\"answers\":[\"192.0.2.200\"],\"rcode\":\"NOERROR\"}]" var logResponse = "[{\"time\":\"RFC3339\",\"remote_addr\":\"127.0.0.254\",\"type\":\"AAAA\",\"question\":\"example.com.\",\"answers\":[\"2001:db8::1\"]}," + "{\"time\":\"RFC3339\",\"remote_addr\":\"127.0.0.42\",\"type\":\"A\",\"question\":\"example.com.\",\"answers\":[\"192.0.2.101\",\"192.0.2.100\"]}]" -- cgit v1.2.3