newdns

package
v0.1.4-beta Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2021 License: Apache-2.0, MIT Imports: 7 Imported by: 0

README

newdns

Build Status Coverage Status GoDoc Release Go Report Card

A library for building custom DNS servers in Go.

The newdns library wraps the widely used, but low-level github.com/miekg/dns package with a simple interface to quickly build custom DNS servers. The implemented server only supports a subset of record types (A, AAAA, CNAME, MX, TXT) and is intended to be used as a leaf authoritative name server only. It supports UDP and TCP as transport protocols and implements EDNS0. Conformance is tested by issuing a corpus of tests against a zone in AWS Route53 and comparing the response and behavior.

The intention of this project is not to build a feature-complete alternative to "managed zone" offerings by major cloud platforms. However, some projects may require frequent synchronization of many records between a custom database and a cloud-hosted "managed zone". In this scenario, a custom DNS server that queries the own database might be a lot simpler to manage and operate. Also, the distributed nature of the DNS system offers interesting qualities that could be leveraged by future applications.

Example

// create zone
zone := &newdns.Zone{
    Name:             "example.com.",
    MasterNameServer: "ns1.hostmaster.com.",
    AllNameServers: []string{
        "ns1.hostmaster.com.",
        "ns2.hostmaster.com.",
        "ns3.hostmaster.com.",
    },
    Handler: func(name string) ([]newdns.Set, error) {
        // return apex records
        if name == "" {
            return []newdns.Set{
                {
                    Name: "example.com.",
                    Type: newdns.A,
                    Records: []newdns.Record{
                        {Address: "1.2.3.4"},
                    },
                },
                {
                    Name: "example.com.",
                    Type: newdns.AAAA,
                    Records: []newdns.Record{
                        {Address: "1:2:3:4::"},
                    },
                },
            }, nil
        }

        // return sub records
        if name == "foo" {
            return []newdns.Set{
                {
                    Name: "foo.example.com.",
                    Type: newdns.CNAME,
                    Records: []newdns.Record{
                        {Address: "bar.example.com."},
                    },
                },
            }, nil
        }

        return nil, nil
    },
}

// create server
server := newdns.NewServer(newdns.Config{
    Handler: func(name string) (*newdns.Zone, error) {
        // check name
        if newdns.InZone("example.com.", name) {
            return zone, nil
        }

        return nil, nil
    },
    Logger: func(e newdns.Event, msg *dns.Msg, err error, reason string) {
        fmt.Println(e, err, reason)
    },
})

// run server
go func() {
    err := server.Run(":1337")
    if err != nil {
        panic(err)
    }
}()

// print info
fmt.Println("Query apex: dig example.com @0.0.0.0 -p 1337")
fmt.Println("Query other: dig foo.example.com @0.0.0.0 -p 1337")

// wait forever
select {}

Credits

Documentation

Index

Constants

View Source
const (
	// A records return IPV4 addresses.
	A = Type(dns.TypeA)

	// AAAA records return IPV6 addresses.
	AAAA = Type(dns.TypeAAAA)

	// CNAME records return other DNS names.
	CNAME = Type(dns.TypeCNAME)

	// MX records return mails servers with their priorities. The target mail
	// servers must itself be returned with an A or AAAA record.
	MX = Type(dns.TypeMX)

	// TXT records return arbitrary text data.
	TXT = Type(dns.TypeTXT)

	// NS records delegate names to other name servers.
	NS = Type(dns.TypeNS)

	// REBINDING records return different ip when the same client requests twice.
	REBINDING = Type(99)
)

Variables

This section is empty.

Functions

func Accept

func Accept(logger Logger) dns.MsgAcceptFunc

Accept will return a dns.MsgAcceptFunc that only accepts normal queries.

func InZone

func InZone(zone, name string) bool

InZone returns whether the provided name is part of the provided zone. Will always return false if the provided domains are not valid.

func IsDomain

func IsDomain(name string, fqdn bool) bool

IsDomain returns whether the name is a valid domain and if requested also fully qualified.

func NormalizeDomain

func NormalizeDomain(name string, lower, makeFQDN, removeFQDN bool) string

NormalizeDomain will normalize the provided domain name by removing space around the name and lowercase it if request.

func Proxy

func Proxy(addr string, logger Logger) dns.Handler

Proxy returns a handler that proxies requests to the provided DNS server. The optional logger is called with events about the processing of requests.

func Query

func Query(proto, addr, name, typ string, fn func(*dns.Msg)) (*dns.Msg, error)

Query can be used to query a DNS server over the provided protocol on its address for the specified name and type. The supplied function can be set to mutate the sent request.

func Resolver

func Resolver(handler dns.Handler) dns.Handler

Resolver returns a very primitive recursive resolver that uses the provided handler to resolve all names.

func Run

func Run(addr string, handler dns.Handler, accept dns.MsgAcceptFunc, close <-chan struct{}) error

Run will start a UDP and TCP listener to serve the specified handler with the specified accept function until the provided close channel is closed. It will return the first error of a listener.

func SplitDomain

func SplitDomain(name string, hierarchical bool) []string

SplitDomain will split the provided domain either in separate labels or hierarchical labels. The later allows walking a domain up to the root.

func TransferCase

func TransferCase(source, destination string) string

TransferCase will transfer the case from the source name to the destination. For the source "foo.AAA.com." and destination "aaa.com" the function will return "AAA.com". The source must be either a child or the same as the destination.

func TrimZone

func TrimZone(zone, name string) string

TrimZone will remove the zone from the specified name.

Types

type Config

type Config struct {
	// The buffer size used if EDNS is enabled by a client.
	//
	// Default: 1220.
	BufferSize int

	// The list of zones handled by this server.
	//
	// Default: ["."].
	Zones []string

	// Handler is the callback that returns a zone for the specified name.
	// The returned zone must not be altered going forward.
	Handler func(name string) (*Zone, error)

	// The fallback DNS server to be used if the zones is not matched. Exact
	// zones must be provided above for this to work.
	Fallback string

	// Reporter is the callback called with request errors.
	Logger Logger
}

Config provides configuration for a DNS server.

type Event

type Event int

Event denotes an event type emitted to the logger.

const (
	// Ignored are requests that haven been dropped by leaving the connection
	// hanging to mitigate attacks. Inspect the reason for more information.
	Ignored Event = iota

	// Request is emitted for every accepted request. For every request event
	// a finish event fill follow. You can inspect the message to see the
	// complete request sent by the client.
	Request Event = iota

	// Refused are requests that received an error due to some incompatibility.
	// Inspect the reason for more information.
	Refused Event = iota

	// BackendError is emitted with errors returned by the callback and
	// validation functions. Inspect the error for more information.
	BackendError Event = iota

	// NetworkError is emitted with errors returned by the connection. Inspect
	// the error for more information.
	NetworkError Event = iota

	// Response is emitted with the final response to the client. You can inspect
	// the message to see the complete response to the client.
	Response Event = iota

	// Finish is emitted when a request has been processed.
	Finish Event = iota

	// ProxyRequest is emitted with every request forwarded to the fallback
	// DNS server.
	ProxyRequest Event = iota

	// ProxyResponse is emitted with ever response received from the fallback
	// DNS server.
	ProxyResponse Event = iota

	// ProxyError is emitted with errors returned by the fallback DNS server.
	// Inspect the error for more information.
	ProxyError Event = iota
)

func (Event) String

func (e Event) String() string

String will return the name of the event.

type Logger

type Logger func(e Event, msg *dns.Msg, err error, reason string)

Logger is function that accepts logging events.

type Record

type Record struct {
	// The target address for A, AAAA, CNAME and MX records.
	Address string

	// The priority for MX records.
	Priority int

	// The data for TXT records.
	Data []string
}

Record holds a single DNS record.

func (*Record) Validate

func (r *Record) Validate(typ Type) error

Validate will validate the record.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a DNS server.

func NewServer

func NewServer(config Config) *Server

NewServer creates and returns a new DNS server.

func (*Server) Close

func (s *Server) Close()

Close will close the server.

func (*Server) Run

func (s *Server) Run(addr string) error

Run will run a udp and tcp server on the specified address. It will return on the first accept error and close all servers.

func (*Server) ServeDNS

func (s *Server) ServeDNS(w dns.ResponseWriter, req *dns.Msg)

ServeDNS implements the dns.Handler interface.

type Set

type Set struct {
	// The FQDN of the set.
	Name string

	// The type of the record.
	Type Type

	// The records in the set.
	Records []Record

	// The TTL of the set.
	//
	// Default: 5m.
	TTL time.Duration
}

Set is a set of records.

func (*Set) Validate

func (s *Set) Validate() error

Validate will validate the set and ensure defaults.

type Type

type Type uint16

Type denotes the DNS record type.

type Zone

type Zone struct {
	// The FQDN of the zone e.g. "example.com.".
	Name string

	// The FQDN of the master mame server responsible for this zone. The FQDN
	// must be returned as A and AAAA record by the parent zone.
	MasterNameServer string

	// A list of FQDNs to all authoritative name servers for this zone. The
	// FQDNs must be returned as A and AAAA records by the parent zone. It is
	// required to announce at least two distinct name servers per zone.
	AllNameServers []string

	// The email address of the administrator e.g. "hostmaster@example.com".
	//
	// Default: "hostmaster@NAME".
	AdminEmail string

	// The refresh interval.
	//
	// Default: 6h.
	Refresh time.Duration

	// The retry interval for the zone.
	//
	// Default: 1h.
	Retry time.Duration

	// The expiration interval of the zone.
	//
	// Default: 72h.
	Expire time.Duration

	// The TTL for the SOA record.
	//
	// Default: 15m.
	SOATTL time.Duration

	// The TTL for NS records.
	//
	// Default: 48h.
	NSTTL time.Duration

	// The minimum TTL for all records. Either this value, or the SOATTL if lower,
	// is used to determine the "negative caching TTL" which is the duration
	// caches are allowed to cache missing records (NXDOMAIN).
	//
	// Default: 5min.
	MinTTL time.Duration

	// The handler that responds to requests for this zone. The returned sets
	// must not be altered going forward.
	Handler func(name, remoteAddr string) ([]Set, error)
}

Zone describes a single authoritative DNS zone.

func (*Zone) Lookup

func (z *Zone) Lookup(name, remoteAddr string, needle ...Type) ([]Set, bool, error)

Lookup will lookup the specified name in the zone and return results for the specified record types. If no results are returned, the second return value indicates if there are other results for the specified name.

func (*Zone) Validate

func (z *Zone) Validate() error

Validate will validate the zone and ensure the documented defaults.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL