Skip to content
Permalink
Browse files

Fix dnsflagday issues

Also:
- NSID support
- Support for DNS Cookies

(closes #115)
  • Loading branch information
abh committed Dec 29, 2019
1 parent 5847436 commit b3ca7047d2ed6af6aabc47d189afb669a4cb74d7
Showing with 213 additions and 46 deletions.
  1. +6 −0 CHANGES.md
  2. +2 −1 build
  3. +3 −0 edns/README.md
  4. +137 −0 edns/edns.go
  5. +4 −4 geodns-logs/process-stats.go
  6. +2 −2 go.sum
  7. +49 −37 server/serve.go
  8. +10 −2 server/server.go
@@ -1,5 +1,11 @@
# GeoDNS Changelog

## 3.1.0 - in development

* NSID support
* Support for DNS Cookies
* dnsflagday cleanups

## 3.0.2 December 2019

* Better test errors when geoip2 files aren't found
3 build
@@ -14,10 +14,11 @@ ARCH=${GOARCH:-`go env GOARCH`}
set -ex

go build -o dist/geodns-$OS-$ARCH \
-mod=vendor \
-trimpath \
-ldflags "-X main.gitVersion=$REVISION -X main.buildTime=$BUILDTIME" \
-v && \
(cd geodns-logs && go build -v -o ../dist/geodns-logs-$OS-$ARCH && cd ..) && \
(cd geodns-logs && go build -trimpath -mod=vendor -v -o ../dist/geodns-logs-$OS-$ARCH && cd ..) && \
cd dist && \
rm -f service service-logs && \
ln -s ../service . && \
@@ -0,0 +1,3 @@


This is from github.com/coredns/coredns/plugin/pkg/edns/
@@ -0,0 +1,137 @@
// Package edns provides function useful for adding/inspecting OPT records to/in messages.
package edns

import (
"errors"
"sync"

"github.com/miekg/dns"
)

var sup = &supported{m: make(map[uint16]struct{})}

type supported struct {
m map[uint16]struct{}
sync.RWMutex
}

// SetSupportedOption adds a new supported option the set of EDNS0 options that we support. Plugins typically call
// this in their setup code to signal support for a new option.
// By default we support:
// dns.EDNS0NSID, dns.EDNS0EXPIRE, dns.EDNS0COOKIE, dns.EDNS0TCPKEEPALIVE, dns.EDNS0PADDING. These
// values are not in this map and checked directly in the server.
func SetSupportedOption(option uint16) {
sup.Lock()
sup.m[option] = struct{}{}
sup.Unlock()
}

// SupportedOption returns true if the option code is supported as an extra EDNS0 option.
func SupportedOption(option uint16) bool {
sup.RLock()
_, ok := sup.m[option]
sup.RUnlock()
return ok
}

// Version checks the EDNS version in the request. If error
// is nil everything is OK and we can invoke the plugin. If non-nil, the
// returned Msg is valid to be returned to the client (and should). For some
// reason this response should not contain a question RR in the question section.
func Version(req *dns.Msg) (*dns.Msg, error) {
opt := req.IsEdns0()
if opt == nil {
return nil, nil
}
if opt.Version() == 0 {
return nil, nil
}
m := new(dns.Msg)
m.SetReply(req)
// zero out question section, wtf.
m.Question = nil

o := new(dns.OPT)
o.Hdr.Name = "."
o.Hdr.Rrtype = dns.TypeOPT
o.SetVersion(0)
m.Rcode = dns.RcodeBadVers
o.SetExtendedRcode(dns.RcodeBadVers)
m.Extra = []dns.RR{o}

return m, errors.New("EDNS0 BADVERS")
}

// Size returns a normalized size based on proto.
func Size(proto string, size uint16) uint16 {
if proto == "tcp" {
return dns.MaxMsgSize
}
if size < dns.MinMsgSize {
return dns.MinMsgSize
}
return size
}

/*
The below wasn't from the edns package
*/

// SetSizeAndDo adds an OPT record that the reflects the intent from request.
func SetSizeAndDo(req, m *dns.Msg) *dns.OPT {
o := req.IsEdns0()
if o == nil {
return nil
}

if mo := m.IsEdns0(); mo != nil {
mo.Hdr.Name = "."
mo.Hdr.Rrtype = dns.TypeOPT
mo.SetVersion(0)
mo.SetUDPSize(o.UDPSize())
mo.Hdr.Ttl &= 0xff00 // clear flags

// Assume if the message m has options set, they are OK and represent what an upstream can do.

if o.Do() {
mo.SetDo()
}
return mo
}

// Reuse the request's OPT record and tack it to m.
o.Hdr.Name = "."
o.Hdr.Rrtype = dns.TypeOPT
o.SetVersion(0)
o.Hdr.Ttl &= 0xff00 // clear flags

if len(o.Option) > 0 {
o.Option = SupportedOptions(o.Option)
}

m.Extra = append(m.Extra, o)
return o
}

func SupportedOptions(o []dns.EDNS0) []dns.EDNS0 {
var supported = make([]dns.EDNS0, 0, 3)
// For as long as possible try avoid looking up in the map, because that need an Rlock.
for _, opt := range o {
switch code := opt.Option(); code {
case dns.EDNS0NSID:
fallthrough
case dns.EDNS0COOKIE:
fallthrough
case dns.EDNS0SUBNET:
supported = append(supported, opt)
default:
if SupportedOption(code) {
supported = append(supported, opt)

}
}
}
return supported
}
@@ -10,8 +10,8 @@ import (
"strings"
"sync"

"github.com/nxadm/tail"
"github.com/miekg/dns"
"github.com/nxadm/tail"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"

@@ -23,11 +23,11 @@ import (
// Add vendor yes/no
// add server region tag (identifier)?

const UserAgent = "geodns-logs/2.0"
const userAgent = "geodns-logs/2.0"

func main() {

log.Printf("Starting %q", UserAgent)
log.Printf("Starting %q", userAgent)

identifierFlag := flag.String("identifier", "", "identifier (hostname, pop name or similar)")
// verboseFlag := flag.Bool("verbose", false, "verbose output")
@@ -70,7 +70,7 @@ func main() {
[]string{"Version"},
)
prometheus.MustRegister(buildInfo)
buildInfo.WithLabelValues(UserAgent).Set(1)
buildInfo.WithLabelValues(userAgent).Set(1)

http.Handle("/metrics", promhttp.Handler())
go func() {
4 go.sum
@@ -1,5 +1,7 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/abh/dns v1.1.26-1 h1:Q5xZ912xwwCbcqHVwEM4ZDyukU86CsPl0Mf9g2X79vs=
github.com/abh/dns v1.1.26-1/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/abh/errorutil v0.0.0-20130729183701-f9bd360d00b9 h1:xwBQqR2Uq0q7FDzd1z9lr/sB6NrvMBvOBW5xzDn+DcM=
github.com/abh/errorutil v0.0.0-20130729183701-f9bd360d00b9/go.mod h1:L6nuAnQTJ2ZqobKWmzIcnTvpoOSWV8LulEOCk2rdhmM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -47,8 +49,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -1,6 +1,7 @@
package server

import (
"encoding/hex"
"encoding/json"
"fmt"
"net"
@@ -10,6 +11,7 @@ import (
"time"

"github.com/abh/geodns/applog"
"github.com/abh/geodns/edns"
"github.com/abh/geodns/querylog"
"github.com/abh/geodns/zones"

@@ -66,36 +68,28 @@ func (srv *Server) serve(w dns.ResponseWriter, req *dns.Msg, z *zones.Zone) {

z.Metrics.ClientStats.Add(realIP.String())

var ip net.IP // EDNS or real IP
var edns *dns.EDNS0_SUBNET
var opt_rr *dns.OPT

for _, extra := range req.Extra {

switch extra.(type) {
case *dns.OPT:
for _, o := range extra.(*dns.OPT).Option {
opt_rr = extra.(*dns.OPT)
switch e := o.(type) {
case *dns.EDNS0_NSID:
// do stuff with e.Nsid
case *dns.EDNS0_SUBNET:
applog.Println("Got edns", e.Address, e.Family, e.SourceNetmask, e.SourceScope)
if e.Address != nil {
edns = e
ip = e.Address

if qle != nil {
qle.HasECS = true
qle.ClientAddr = fmt.Sprintf("%s/%d", ip, e.SourceNetmask)
}
var ip net.IP // EDNS CLIENT SUBNET or real IP
var ecs *dns.EDNS0_SUBNET

if option := req.IsEdns0(); option != nil {
for _, s := range option.Option {
switch e := s.(type) {
case *dns.EDNS0_SUBNET:
applog.Println("Got edns-client-subnet", e.Address, e.Family, e.SourceNetmask, e.SourceScope)
if e.Address != nil {
ecs = e
ip = e.Address

if qle != nil {
qle.HasECS = true
qle.ClientAddr = fmt.Sprintf("%s/%d", ip, e.SourceNetmask)
}
}
}
}
}

if len(ip) == 0 { // no edns subnet
if len(ip) == 0 { // no edns client subnet
ip = realIP
if qle != nil {
qle.ClientAddr = fmt.Sprintf("%s/%d", ip, len(ip)*8)
@@ -104,8 +98,9 @@ func (srv *Server) serve(w dns.ResponseWriter, req *dns.Msg, z *zones.Zone) {

targets, netmask, location := z.Options.Targeting.GetTargets(ip, z.HasClosest)

m := new(dns.Msg)
m := &dns.Msg{}

// setup logging of answers and rcode
if qle != nil {
qle.Targets = targets
defer func() {
@@ -114,23 +109,40 @@ func (srv *Server) serve(w dns.ResponseWriter, req *dns.Msg, z *zones.Zone) {
}()
}

m.SetReply(req)
if e := m.IsEdns0(); e != nil {
m.SetEdns0(4096, e.Do())
mv, err := edns.Version(req)
if err != nil {
m = mv
err := w.WriteMsg(m)
if err != nil {
applog.Printf("could not write response: %s", err)
}
return
}
m.Authoritative = true

// TODO: set scope to 0 if there are no alternate responses
if edns != nil {
if edns.Family != 0 {
if netmask < 16 {
netmask = 16
m.SetReply(req)

if option := edns.SetSizeAndDo(req, m); option != nil {

for _, s := range option.Option {
switch e := s.(type) {
case *dns.EDNS0_NSID:
e.Code = dns.EDNS0NSID
e.Nsid = hex.EncodeToString([]byte(srv.info.ID))
case *dns.EDNS0_SUBNET:
// access e.Family, e.Address, etc.
// TODO: set scope to 0 if there are no alternate responses
if ecs.Family != 0 {
if netmask < 16 {
netmask = 16
}
e.SourceScope = uint8(netmask)
}
}
edns.SourceScope = uint8(netmask)
m.Extra = append(m.Extra, opt_rr)
}
}

m.Authoritative = true

labelMatches := z.FindLabels(qlabel, targets, []uint16{dns.TypeMF, dns.TypeCNAME, qtype})

if len(labelMatches) == 0 {
@@ -267,7 +279,7 @@ func (srv *Server) serve(w dns.ResponseWriter, req *dns.Msg, z *zones.Zone) {
// should this be in the match loop above?
qle.Rcode = m.Rcode
}
err := w.WriteMsg(m)
err = w.WriteMsg(m)
if err != nil {
// if Pack'ing fails the Write fails. Return SERVFAIL.
applog.Printf("Error writing packet: %q, %s", err, m)

0 comments on commit b3ca704

Please sign in to comment.
You can’t perform that action at this time.