From 607c7a3d1aeb2ec8a0c36036ffc10f97cd25c47c Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Sat, 13 Feb 2016 00:13:35 +0100 Subject: [PATCH 01/13] Bump v0.9.9 --- README.md | 6 +++--- main.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 727cba1..a0dc059 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-dnsmasq -*Version 0.9.8* +*Version 0.9.9* go-dnsmasq is a light weight (1.2 MB) DNS caching server/forwarder with minimal filesystem and runtime overhead. @@ -37,10 +37,10 @@ DNS queries are resolved in the style of the GNU libc resolver: | --listen, -l | Address to listen on `host[:port]` | 127.0.0.1:53 | $DNSMASQ_LISTEN | | --default-resolver, -d | Update resolv.conf and make go-dnsmasq the host's primary nameserver | False | $DNSMASQ_DEFAULT | | --nameservers, -n | Comma-separated list of nameservers `host[:port]` | - | $DNSMASQ_SERVERS | -| --stubzones, -z | Use different nameservers for specific domains `fqdn[,fqdn]/host[:port]` | - | $DNSMASQ_STUB | +| --stubzones, -z | Use different nameservers for specific domain `domain[,domain]/host[:port]` | - | $DNSMASQ_STUB | | --hostsfile, -f | Full path to a hostsfile | - | $DNSMASQ_HOSTSFILE | | --hostsfile-poll, -p | How frequently to check hostsfile for changes (seconds, ‘0‘ to disable) | 0 | $DNSMASQ_POLL | -| --search-domains, -s | Specify SEARCH domains (takes precedence over /etc/resolv.conf) `fqdn[,fqdn]` | - | $DNSMASQ_SEARCH | +| --search-domains, -s | Specify SEARCH domains (in lieu of /etc/resolv.conf) `domain[,domain]` | - | $DNSMASQ_SEARCH | | --append-search-domains, -a | Qualify queries with SEARCH domains | False | $DNSMASQ_APPEND | | --rcache, -r | Capacity of the response cache (‘0‘ to disable cache) | 0 | $DNSMASQ_RCACHE | | --rcache-ttl | TTL for entries in the response cache | 60 | $DNSMASQ_RCACHE_TTL | diff --git a/main.go b/main.go index dd019d9..4679694 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( ) // var Version string -const Version = "0.9.8" +const Version = "0.9.9" var ( nameservers = []string{} From 4765fbb8f44a64759655269cf784d169e2a1dc9e Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Wed, 17 Feb 2016 07:47:22 +0100 Subject: [PATCH 02/13] Changes: Log to Stdout, CLI usage instructions --- Dockerfile.run | 2 +- main.go | 16 ++++++++-------- wercker.yml | 35 ----------------------------------- 3 files changed, 9 insertions(+), 44 deletions(-) delete mode 100644 wercker.yml diff --git a/Dockerfile.run b/Dockerfile.run index fd3d689..c1b2372 100644 --- a/Dockerfile.run +++ b/Dockerfile.run @@ -1,7 +1,7 @@ FROM alpine:3.2 MAINTAINER Jan Broer -ADD https://github.com/janeczku/go-dnsmasq/releases/download/0.9.8/go-dnsmasq_linux-amd64 /go-dnsmasq +ADD https://github.com/janeczku/go-dnsmasq/releases/download/1.0.0/go-dnsmasq_linux-amd64 /go-dnsmasq RUN chmod +x /go-dnsmasq EXPOSE 53 53/udp diff --git a/main.go b/main.go index 4679694..69e1fbf 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( ) // var Version string -const Version = "0.9.9" +const Version = "1.0.0" var ( nameservers = []string{} @@ -40,13 +40,13 @@ var ( var exitErr error func init() { - log.SetOutput(os.Stderr) + log.SetOutput(os.Stdout) } func main() { app := cli.NewApp() app.Name = "go-dnsmasq" - app.Usage = "Lightweight caching DNS proxy for Docker containers" + app.Usage = "Lightweight caching DNS server/forwarder" app.Version = Version app.Author, app.Email = "", "" app.Flags = []cli.Flag{ @@ -58,19 +58,19 @@ func main() { }, cli.BoolFlag{ Name: "default-resolver, d", - Usage: "make go-dnsmasq the local primary nameserver (updates /etc/resolv.conf)", + Usage: "make go-dnsmasq the host's primary nameserver (updates resolv.conf)", EnvVar: "DNSMASQ_DEFAULT", }, cli.StringFlag{ Name: "nameservers, n", Value: "", - Usage: "comma-separated list of name servers: ‘host[:port]‘", + Usage: "comma-separated list of nameservers: ‘host[:port]‘", EnvVar: "DNSMASQ_SERVERS", }, cli.StringFlag{ Name: "stubzones, z", Value: "", - Usage: "domains to resolve using a specific nameserver: ‘fqdn[,fqdn]/host[:port]‘", + Usage: "domains to resolve using a specific nameserver: ‘domain[,domain]/host[:port]‘", EnvVar: "DNSMASQ_STUB", }, cli.StringFlag{ @@ -88,12 +88,12 @@ func main() { cli.StringFlag{ Name: "search-domains, s", Value: "", - Usage: "specify SEARCH domains taking precedence over /etc/resolv.conf: ‘fqdn[,fqdn]‘", + Usage: "specify SEARCH domains (takes precedence over resolv.conf): ‘domain[,domain]‘", EnvVar: "DNSMASQ_SEARCH", }, cli.BoolFlag{ Name: "append-search-domains, a", - Usage: "enable suffixing single-label queries with SEARCH domains", + Usage: "resolve queries by qualifying them with the search domains", EnvVar: "DNSMASQ_APPEND", }, cli.IntFlag{ diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 60ec6dd..0000000 --- a/wercker.yml +++ /dev/null @@ -1,35 +0,0 @@ -box: wercker/golang -build: - steps: - - setup-go-workspace - - - script: - name: get dependencies - code: | - go get github.com/golang/lint/golint - go get -t ./... - - - script: - name: go build - code: | - go build ./... - - - script: - name: go install - code: | - go install ./... - - - script: - name: go vet - code: | - go vet ./... - - - script: - name: go test - code: | - go test -race ./... - - - script: - name: golint - code: | - golint . From f74fb974fe947f4c1dd1c307e54005d20d44a091 Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Wed, 17 Feb 2016 08:00:12 +0100 Subject: [PATCH 03/13] Prepare for Docker Hub trusted builds --- Dockerfile.run => Dockerfile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Dockerfile.run => Dockerfile (100%) diff --git a/Dockerfile.run b/Dockerfile similarity index 100% rename from Dockerfile.run rename to Dockerfile From c4cc51a7a3199beb56d303d3590b18484407597a Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Wed, 17 Feb 2016 08:08:14 +0100 Subject: [PATCH 04/13] Release v1.0.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0dc059..a703031 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-dnsmasq -*Version 0.9.9* +*Version 1.0.0* go-dnsmasq is a light weight (1.2 MB) DNS caching server/forwarder with minimal filesystem and runtime overhead. From 9f8c6b644c4478297f5be09f1afe776651f5f42d Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Wed, 17 Feb 2016 08:13:29 +0100 Subject: [PATCH 05/13] Bind to 0.0.0.0 by default --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index c1b2372..eea6bd7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,5 +4,6 @@ MAINTAINER Jan Broer ADD https://github.com/janeczku/go-dnsmasq/releases/download/1.0.0/go-dnsmasq_linux-amd64 /go-dnsmasq RUN chmod +x /go-dnsmasq +ENV DNSMASQ_LISTEN=0.0.0.0 EXPOSE 53 53/udp ENTRYPOINT ["/go-dnsmasq"] From 73cc34f92b5555f84cf50363cbb46dca452eacd8 Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Wed, 17 Feb 2016 08:45:11 +0100 Subject: [PATCH 06/13] Add shields.io badges --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a703031..0014f0f 100644 --- a/README.md +++ b/README.md @@ -80,10 +80,12 @@ go-dnsmasq is available in two versions. The minimal version (`go-dnsmasq-min`) ``` #### Run as a Docker container +[![ImageLayers Size](https://img.shields.io/imagelayers/image-size/janeczku/go-dnsmasq/latest.svg)]() [![Docker Pulls](https://img.shields.io/docker/pulls/janeczku/go-dnsmasq.svg)]() + +Docker Hub trusted builds [available](https://hub.docker.com/r/janeczku/go-dnsmasq/). ```sh -docker run -d -e DNSMASQ_LISTEN=0.0.0.0 -p 53:53/udp -p 53:53 \ - janeczku/go-dnsmasq +docker run -d -p 53:53/udp -p 53:53 janeczku/go-dnsmasq:latest ``` -You can configure go-dnsmasq by passing the corresponding environmental variables with docker run `--env` flag. +You can configure the container by passing the corresponding environmental variables with docker run's `--env` flag. From 9c6b7d5946f91da80a0a911af427dc8bcd1f4804 Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Sun, 6 Mar 2016 00:54:16 +0100 Subject: [PATCH 07/13] Minor fixes: * Correct spelling in command line help strings * Make error messages more informative * When forwarding is prohibited return REFUSED instead of SERVFAIL --- README.md | 20 ++++++++++---------- main.go | 34 +++++++++++++++++----------------- server/forwarding.go | 44 +++++++++++++++++++++----------------------- 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 0014f0f..52faa96 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-dnsmasq -*Version 1.0.0* +*Version 1.0.1* go-dnsmasq is a light weight (1.2 MB) DNS caching server/forwarder with minimal filesystem and runtime overhead. @@ -35,20 +35,20 @@ DNS queries are resolved in the style of the GNU libc resolver: | Flag | Description | Default | Environment vars | | ------------------------------ | ----------------------------------------------------------------------------- | ------------- | -------------------- | | --listen, -l | Address to listen on `host[:port]` | 127.0.0.1:53 | $DNSMASQ_LISTEN | -| --default-resolver, -d | Update resolv.conf and make go-dnsmasq the host's primary nameserver | False | $DNSMASQ_DEFAULT | -| --nameservers, -n | Comma-separated list of nameservers `host[:port]` | - | $DNSMASQ_SERVERS | -| --stubzones, -z | Use different nameservers for specific domain `domain[,domain]/host[:port]` | - | $DNSMASQ_STUB | -| --hostsfile, -f | Full path to a hostsfile | - | $DNSMASQ_HOSTSFILE | -| --hostsfile-poll, -p | How frequently to check hostsfile for changes (seconds, ‘0‘ to disable) | 0 | $DNSMASQ_POLL | -| --search-domains, -s | Specify SEARCH domains (in lieu of /etc/resolv.conf) `domain[,domain]` | - | $DNSMASQ_SEARCH | -| --append-search-domains, -a | Qualify queries with SEARCH domains | False | $DNSMASQ_APPEND | +| --default-resolver, -d | Update resolv.conf to make go-dnsmasq the host's nameserver | False | $DNSMASQ_DEFAULT | +| --nameservers, -n | Comma separated list of nameservers `host[:port]` | - | $DNSMASQ_SERVERS | +| --stubzones, -z | Use different nameservers for specific domains `domain[,domain]/host[:port]` | - | $DNSMASQ_STUB | +| --hostsfile, -f | Path to a hostsfile (e.g. ‘/etc/hosts‘) | - | $DNSMASQ_HOSTSFILE | +| --hostsfile-poll, -p | How frequently to poll hostsfile for changes (seconds, ‘0‘ to disable) | 0 | $DNSMASQ_POLL | +| --search-domains, -s | Specify search domains (overrides /etc/resolv.conf) `domain[,domain]` | - | $DNSMASQ_SEARCH | +| --append-search-domains, -a | Resolve queries using search domains | False | $DNSMASQ_APPEND | | --rcache, -r | Capacity of the response cache (‘0‘ to disable cache) | 0 | $DNSMASQ_RCACHE | | --rcache-ttl | TTL for entries in the response cache | 60 | $DNSMASQ_RCACHE_TTL | | --no-rec | Disable recursion | False | $DNSMASQ_NOREC | -| --round-robin | enable round robin of A/AAAA records | False | $DNSMASQ_RR | +| --round-robin | Enable round robin of A/AAAA records | False | $DNSMASQ_RR | | --systemd | Bind to socket(s) activated by Systemd (ignores --listen) | False | $DNSMASQ_SYSTEMD | | --verbose | Enable verbose logging | False | $DNSMASQ_VERBOSE | -| --syslog | Log to syslog | False | $DNSMASQ_SYSLOG | +| --syslog | Enable syslog logging | False | $DNSMASQ_SYSLOG | | --multithreading | Enable multithreading | False | | | --help, -h | Show help | | | | --version, -v | Print the version | | | diff --git a/main.go b/main.go index 69e1fbf..a56c39c 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( ) // var Version string -const Version = "1.0.0" +const Version = "1.0.1" var ( nameservers = []string{} @@ -53,89 +53,89 @@ func main() { cli.StringFlag{ Name: "listen, l", Value: "127.0.0.1:53", - Usage: "listen address: ‘host[:port]‘", + Usage: "Address to listen on `host[:port]`", EnvVar: "DNSMASQ_LISTEN", }, cli.BoolFlag{ Name: "default-resolver, d", - Usage: "make go-dnsmasq the host's primary nameserver (updates resolv.conf)", + Usage: "Update resolv.conf to make go-dnsmasq the host's nameserver", EnvVar: "DNSMASQ_DEFAULT", }, cli.StringFlag{ Name: "nameservers, n", Value: "", - Usage: "comma-separated list of nameservers: ‘host[:port]‘", + Usage: "Comma separated list of nameservers `host[:port]`", EnvVar: "DNSMASQ_SERVERS", }, cli.StringFlag{ Name: "stubzones, z", Value: "", - Usage: "domains to resolve using a specific nameserver: ‘domain[,domain]/host[:port]‘", + Usage: "Use different nameservers for specific domains `domain[,domain]/host[:port]`", EnvVar: "DNSMASQ_STUB", }, cli.StringFlag{ Name: "hostsfile, f", Value: "", - Usage: "full path to hostsfile (e.g. ‘/etc/hosts‘)", + Usage: "Path to a hostsfile (e.g. ‘/etc/hosts‘)", EnvVar: "DNSMASQ_HOSTSFILE", }, cli.IntFlag{ Name: "hostsfile-poll, p", Value: 0, - Usage: "how frequently to poll hostsfile (in seconds, ‘0‘ to disable)", + Usage: "How frequently to poll hostsfile for changes (seconds, ‘0‘ to disable)", EnvVar: "DNSMASQ_POLL", }, cli.StringFlag{ Name: "search-domains, s", Value: "", - Usage: "specify SEARCH domains (takes precedence over resolv.conf): ‘domain[,domain]‘", + Usage: "Specify search domains (overrides /etc/resolv.conf) `domain[,domain]`", EnvVar: "DNSMASQ_SEARCH", }, cli.BoolFlag{ Name: "append-search-domains, a", - Usage: "resolve queries by qualifying them with the search domains", + Usage: "Resolve queries using search domains", EnvVar: "DNSMASQ_APPEND", }, cli.IntFlag{ Name: "rcache, r", Value: 0, - Usage: "capacity of the response cache (‘0‘ to disable caching)", + Usage: "Capacity of the response cache (‘0‘ to disable cache)", EnvVar: "DNSMASQ_RCACHE", }, cli.IntFlag{ Name: "rcache-ttl", Value: server.RCacheTtl, - Usage: "TTL of entries in the response cache", + Usage: "TTL for entries in the response cache", EnvVar: "DNSMASQ_RCACHE_TTL", }, cli.BoolFlag{ Name: "no-rec", - Usage: "disable recursion", + Usage: "Disable recursion", EnvVar: "DNSMASQ_NOREC", }, cli.BoolFlag{ Name: "round-robin", - Usage: "enable round robin of A/AAAA replies", + Usage: "Enable round robin of A/AAAA records", EnvVar: "DNSMASQ_RR", }, cli.BoolFlag{ Name: "systemd", - Usage: "bind to socket(s) activated by systemd (ignores --listen)", + Usage: "Bind to socket(s) activated by Systemd (ignores --listen)", EnvVar: "DNSMASQ_SYSTEMD", }, cli.BoolFlag{ Name: "verbose", - Usage: "enable verbose logging", + Usage: "Enable verbose logging", EnvVar: "DNSMASQ_VERBOSE", }, cli.BoolFlag{ Name: "syslog", - Usage: "enable syslog logging", + Usage: "Enable syslog logging", EnvVar: "DNSMASQ_SYSLOG", }, cli.BoolFlag{ Name: "multithreading", - Usage: "enable multithreading (num physical CPU cores)", + Usage: "Enable multithreading", EnvVar: "DNSMASQ_MULTITHREADING", }, } diff --git a/server/forwarding.go b/server/forwarding.go index 0a1e087..6522be8 100644 --- a/server/forwarding.go +++ b/server/forwarding.go @@ -12,19 +12,18 @@ import ( "github.com/miekg/dns" ) -// ServeDNSForward forwards a request to a nameservers and returns the response. +// ServeDNSForward forwards a request to the nameserver and returns the response. func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { if s.config.NoRec || len(s.config.Nameservers) == 0 { m := new(dns.Msg) m.SetReply(req) - m.SetRcode(req, dns.RcodeServerFailure) + m.SetRcode(req, dns.RcodeRefused) m.Authoritative = false m.RecursionAvailable = false if len(s.config.Nameservers) == 0 { - log.Debug("Can not forward query, no nameservers defined") - m.RecursionAvailable = true + log.Debug("Not forwarding query, no nameservers configured") } else { - m.RecursionAvailable = false + log.Debug("Not forwarding query, recursive mode disabled") } w.WriteMsg(m) @@ -33,15 +32,14 @@ func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { name := req.Question[0].Name - if dns.CountLabel(name) < 2 || dns.CountLabel(name) < s.config.Ndots { - // Don't process single-label queries when searching is not enabled + if dns.CountLabel(name) < s.config.Ndots { if !s.config.AppendDomain || len(s.config.SearchDomains) == 0 { - log.Debugf("Can not forward query, name too short: `%s'", name) + log.Debugf("Not forwarding query, name too short: `%s'", name) m := new(dns.Msg) m.SetReply(req) - m.SetRcode(req, dns.RcodeServerFailure) + m.SetRcode(req, dns.RcodeRefused) m.Authoritative = false - m.RecursionAvailable = true + m.RecursionAvailable = false w.WriteMsg(m) return m } @@ -53,10 +51,10 @@ func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { r *dns.Msg err error nsList []string - nsIndex int // nameserver list index - sdIndex int // search list index - sdName string // QNAME with search path - sdCname = new(dns.CNAME) // CNAME record returned when query resolved by searching + nsIndex int // Nameserver list index + sdIndex int // Search list index + sdName string // QNAME suffixed with search path + sdCname = new(dns.CNAME) // CNAME record we include in replies for queries resolved by searching ) tcp := isTCP(w) @@ -70,7 +68,7 @@ func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { Redo: if dns.CountLabel(name) < 2 { - // always qualify single-label names + // Always qualify single-label names if !doingSearch && canSearch { doingSearch = true sdIndex = 0 @@ -104,7 +102,7 @@ Redo: } if err == nil { if canSearch { - // replicate libc's getaddrinfo.c search logic + // Replicate libc's getaddrinfo.c search logic switch { case r.Rcode == dns.RcodeSuccess && len(r.Answer) == 0 && !r.MsgHdr.Truncated: // NODATA !Truncated fallthrough @@ -112,12 +110,12 @@ Redo: fallthrough case r.Rcode == dns.RcodeServerFailure: // SERVFAIL if doingSearch && (sdIndex + 1) < len(s.config.SearchDomains) { - // continue searching + // Continue searching sdIndex++ goto Redo } if !doingSearch { - // start searching + // Start searching doingSearch = true sdIndex = 0 goto Redo @@ -126,7 +124,7 @@ Redo: } if r.Rcode == dns.RcodeServerFailure || r.Rcode == dns.RcodeRefused { - // continue with next available nameserver + // Continue with next available nameserver if (nsIndex + 1) < len(nsList) { nsIndex++ doingSearch = false @@ -134,7 +132,7 @@ Redo: } } - // We are done querying. Process the reply to return to the client. + // We are done querying. process the reply to return to the client. if doingSearch { // Insert cname record pointing name to name.searchdomain @@ -154,7 +152,7 @@ Redo: w.WriteMsg(r) return r } else { - log.Debugf("Error querying nameserver %s: %q", nsList[nsIndex], err) + log.Debugf("Error querying nameserver %s for qname %s: %q", nsList[nsIndex], name, err) // Got an error, this usually means the server did not respond // Continue with next available nameserver if (nsIndex + 1) < len(nsList) { @@ -165,7 +163,7 @@ Redo: } // If we got here it means forwarding failed - log.Errorf("Failure forwarding request %q", err) + log.Errorf("Failed to forward query for qname %s: %q", name, err) m := new(dns.Msg) m.SetReply(reqCopy) m.SetRcode(reqCopy, dns.RcodeServerFailure) @@ -184,7 +182,7 @@ func (s *server) ServeDNSReverse(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { if records, err := s.PTRRecords(req.Question[0]); err == nil && len(records) > 0 { m.Answer = records if err := w.WriteMsg(m); err != nil { - log.Errorf("Failure returning reply %q", err) + log.Errorf("Failed to send reply: %q", err) } return m } From 14d4ebfcaff280d0688c0b0d5f009d186c675ea5 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Fri, 11 Mar 2016 13:34:18 +0100 Subject: [PATCH 08/13] - extended hostname struct with wildcard flag, fixes for wildcard detection - split newHostlist to newHostlistString for multi-line testing - added Equal method to hostname struct - added FindHost and FindHosts methods in hostlist struct --- hostsfile/utils.go | 75 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/hostsfile/utils.go b/hostsfile/utils.go index db3d59f..64b2042 100644 --- a/hostsfile/utils.go +++ b/hostsfile/utils.go @@ -17,15 +17,20 @@ import ( type hostlist []*hostname type hostname struct { - domain string - ip net.IP - ipv6 bool + domain string + ip net.IP + ipv6 bool + wildcard bool } // newHostlist creates a hostlist by parsing a file func newHostlist(data []byte) *hostlist { + return newHostlistString(string(data)); +} + +func newHostlistString(data string) *hostlist { hostlist := hostlist{} - for _, v := range strings.Split(string(data), "\n") { + for _, v := range strings.Split(data, "\n") { for _, hostname := range parseLine(v) { err := hostlist.add(hostname) if err != nil { @@ -36,8 +41,56 @@ func newHostlist(data []byte) *hostlist { return &hostlist } +func (h *hostname) Equal(hostnamev *hostname) bool { + if (h.wildcard != hostnamev.wildcard || h.ipv6 != hostnamev.ipv6) { + return false + } + if (!h.ip.Equal(hostnamev.ip)) { + return false + } + if (h.domain != hostnamev.domain) { + return false + } + return true +} + +// return first match +func (h *hostlist) FindHost(name string) (addr net.IP) { + var ips []net.IP; + ips = h.FindHosts(name) + if len(ips) > 0 { + addr = ips[0]; + } + return +} + +// return exact matches, if existing -> else, return wildcard +func (h *hostlist) FindHosts(name string) (addrs []net.IP) { + for _, hostname := range *h { + if hostname.wildcard == false && hostname.domain == name { + addrs = append(addrs, hostname.ip) + } + } + + if len(addrs) == 0 { + for _, hostname := range *h { + if hostname.wildcard == true && len(hostname.domain) < len(name) { + if name[len(name)-len(hostname.domain):] == hostname.domain { + var left string; + left = name[0:len(name)-len(hostname.domain)] + if !strings.Contains(left, ".") { + addrs = append(addrs, hostname.ip) + } + } + } + } + } + + return +} + func (h *hostlist) add(hostnamev *hostname) error { - hostname := newHostname(hostnamev.domain, hostnamev.ip, hostnamev.ipv6) + hostname := newHostname(hostnamev.domain, hostnamev.ip, hostnamev.ipv6, hostnamev.wildcard) for _, found := range *h { if found.domain == hostname.domain && found.ip.Equal(hostname.ip) { return fmt.Errorf("Duplicate hostname entry for %s -> %s", @@ -49,9 +102,9 @@ func (h *hostlist) add(hostnamev *hostname) error { } // newHostname creates a new Hostname struct -func newHostname(domain string, ip net.IP, ipv6 bool) (host *hostname) { +func newHostname(domain string, ip net.IP, ipv6 bool, wildcard bool) (host *hostname) { domain = strings.ToLower(domain) - host = &hostname{domain, ip, ipv6} + host = &hostname{domain, ip, ipv6, wildcard} return } @@ -114,8 +167,14 @@ func parseLine(line string) hostlist { return hostnames } + var isWildcard bool for _, v := range domains { - hostname := newHostname(v, ip, isIPv6) + isWildcard = false + if v[0:1] == "*" { + v = v[1:] + isWildcard = true + } + hostname := newHostname(v, ip, isIPv6, isWildcard) hostnames = append(hostnames, hostname) } From 11bb00798d4b0e06d6e4bcc5fdcee9b0364fe693 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Fri, 11 Mar 2016 13:35:36 +0100 Subject: [PATCH 09/13] use FindHosts method in hostlist --- hostsfile/hostsfile.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/hostsfile/hostsfile.go b/hostsfile/hostsfile.go index 608da00..ecf718c 100644 --- a/hostsfile/hostsfile.go +++ b/hostsfile/hostsfile.go @@ -67,13 +67,7 @@ func (h *Hostsfile) FindHosts(name string) (addrs []net.IP, err error) { name = strings.TrimSuffix(name, ".") h.hostMutex.RLock() defer h.hostMutex.RUnlock() - - for _, hostname := range *h.hosts { - if hostname.domain == name { - addrs = append(addrs, hostname.ip) - } - } - + addrs = h.hosts.FindHosts(name); return } From 0b134e8aea7eddde0b5faf2106159156715edc71 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Fri, 11 Mar 2016 13:35:50 +0100 Subject: [PATCH 10/13] updated tests --- hostsfile/hostsfile_test.go | 112 +++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 15 deletions(-) diff --git a/hostsfile/hostsfile_test.go b/hostsfile/hostsfile_test.go index fc59582..3443d3d 100644 --- a/hostsfile/hostsfile_test.go +++ b/hostsfile/hostsfile_test.go @@ -3,8 +3,6 @@ package hosts import ( "fmt" "net" - "runtime" - "strings" "testing" ) @@ -27,6 +25,7 @@ const ipv4Fail = ` const domain = "localhost" const ip = "127.0.0.1" const ipv6 = false +const wildcard = false func Diff(expected, actual string) string { return fmt.Sprintf(` @@ -47,6 +46,38 @@ func (h *hostlist) Contains(b *hostname) bool { return false } +func TestEquality(t *testing.T) { + var host1 *hostname + var host2 *hostname + + host1 = newHostname("hello", net.ParseIP("255.255.255.255"), false, false); + host2 = newHostname("hello", net.ParseIP("255.255.255.255"), false, false); + if !host1.Equal(host2) { + t.Error("Hosts are expected equal, got: ", host1, host2); + } + + host2 = newHostname("hello2", net.ParseIP("255.255.255.255"), false, false); + if host1.Equal(host2) { + t.Error("Hosts are expected different, got: ", host1, host2); + } + + host2 = newHostname("hello1", net.ParseIP("255.255.255.254"), false, false); + if host1.Equal(host2) { + t.Error("Hosts are expected different, got: ", host1, host2); + } + + host2 = newHostname("hello1", net.ParseIP("255.255.255.255"), true, false); + if host1.Equal(host2) { + t.Error("Hosts are expected different, got: ", host1, host2); + } + + host2 = newHostname("hello1", net.ParseIP("255.255.255.255"), false, true); + if host1.Equal(host2) { + t.Error("Hosts are expected different, got: ", host1, host2); + } + +} + func TestParseLine(t *testing.T) { var hosts hostlist @@ -74,30 +105,78 @@ func TestParseLine(t *testing.T) { } // Not Commented stuff - hosts = parseLine("255.255.255.255 broadcasthost test.domain.com domain.com") - if !hosts.Contains(newHostname("broadcasthost", net.ParseIP("255.255.255.255"), false)) || - !hosts.Contains(newHostname("test.domain.com", net.ParseIP("255.255.255.255"), false)) || - !hosts.Contains(newHostname("domain.com", net.ParseIP("255.255.255.255"), false)) || + hosts = parseLine("192.168.0.1 broadcasthost test.domain.com domain.com") + if !hosts.Contains(newHostname("broadcasthost", net.ParseIP("192.168.0.1"), false, false)) || + !hosts.Contains(newHostname("test.domain.com", net.ParseIP("192.168.0.1"), false, false)) || + !hosts.Contains(newHostname("domain.com", net.ParseIP("192.168.0.1"), false, false)) || len(hosts) != 3 { t.Error("Expected to find broadcasthost, domain.com, and test.domain.com") } + // Wildcard stuff + hosts = parseLine("192.168.0.1 *.domain.com mail.domain.com serenity") + if !hosts.Contains(newHostname(".domain.com", net.ParseIP("192.168.0.1"), false, true)) || + !hosts.Contains(newHostname("mail.domain.com", net.ParseIP("192.168.0.1"), false, false)) || + !hosts.Contains(newHostname("serenity", net.ParseIP("192.168.0.1"), false, false)) || + len(hosts) != 3 { + t.Error("Expected to find *.domain.com, mail.domain.com and serenity.") + } + + var ip net.IP; + + ip = hosts.FindHost("api.domain.com"); + if !net.ParseIP("192.168.0.1").Equal(ip) { + t.Error("Can't match wildcard host api.domain.com"); + } + + ip = hosts.FindHost("google.com") + if ip != nil { + t.Error("We shouldn't resolve google.com"); + } + + hosts = *newHostlistString(`192.168.0.1 *.domain.com mail.domain.com serenity + 192.168.0.2 api.domain.com`); + + if (!net.ParseIP("192.168.0.2").Equal(hosts.FindHost("api.domain.com"))) { + t.Error("Failed matching api.domain.com explicitly"); + } + if (!net.ParseIP("192.168.0.1").Equal(hosts.FindHost("mail.domain.com"))) { + t.Error("Failed matching api.domain.com explicitly"); + } + if (!net.ParseIP("192.168.0.1").Equal(hosts.FindHost("wildcard.domain.com"))) { + t.Error("Failed matching wildcard.domain.com explicitly"); + } + if (net.ParseIP("192.168.0.1").Equal(hosts.FindHost("sub.wildcard.domain.com"))) { + t.Error("Failed not matching sub.wildcard.domain.com explicitly"); + } + + // IPv6 (not link-local) + hosts = parseLine("2a02:7a8:1:250::80:1 rtvslo.si img.rtvslo.si") + if !hosts.Contains(newHostname("img.rtvslo.si", net.ParseIP("2a02:7a8:1:250::80:1"), true, false)) || + len(hosts) != 2 { + t.Error("Expected to find rtvslo.si ipv6, two hosts") + } + + /* the following all fails since the addressses are link-local */ + + /* // Ipv6 stuff - hosts = hostess.parseLine("::1 localhost") - if !hosts.Contains(newHostname("localhost", net.ParseIP("::1"), true)) || + hosts = parseLine("::1 localhost") + if !hosts.Contains(newHostname("localhost", net.ParseIP("::1"), true, false)) || len(hosts) != 1 { - t.Error("Expected to find localhost ipv6 (enabled)") + t.Error("Expected to find localhost ipv6") } - hosts = hostess.parseLine("ff02::1 ip6-allnodes") - if !hosts.Contains(newHostname("ip6-allnodes", net.ParseIP("ff02::1"), true)) || + hosts = parseLine("ff02::1 ip6-allnodes") + if !hosts.Contains(newHostname("ip6-allnodes", net.ParseIP("ff02::1"), true, false)) || len(hosts) != 1 { - t.Error("Expected to find ip6-allnodes ipv6 (enabled)") + t.Error("Expected to find ip6-allnodes ipv6") } + */ } func TestHostname(t *testing.T) { - h := newHostname(domain, net.ParseIP(ip), ipv6) + h := newHostname(domain, net.ParseIP(ip), ipv6, wildcard) if h.domain != domain { t.Errorf("Domain should be %s", domain) @@ -105,7 +184,10 @@ func TestHostname(t *testing.T) { if !h.ip.Equal(net.ParseIP(ip)) { t.Errorf("IP should be %s", ip) } - if h.ipv6 != enabled { - t.Errorf("Enabled should be %t", enabled) + if h.ipv6 != ipv6 { + t.Errorf("IPv6 should be %t", ipv6) + } + if h.wildcard != wildcard { + t.Errorf("Wildcard should be %t", wildcard) } } From e60fd9c50f9eece11e331cfe43c82e6e2cbae47c Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Sat, 12 Mar 2016 12:30:13 +0100 Subject: [PATCH 11/13] test for equality of host entry when adding it to list, move leading . out of wildcard entries, update tests with equality errors --- hostsfile/hostsfile_test.go | 13 ++++++++++++- hostsfile/utils.go | 15 ++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/hostsfile/hostsfile_test.go b/hostsfile/hostsfile_test.go index 3443d3d..3b507e4 100644 --- a/hostsfile/hostsfile_test.go +++ b/hostsfile/hostsfile_test.go @@ -104,6 +104,17 @@ func TestParseLine(t *testing.T) { t.Error("Expected to find zero hostnames when line is commented out") } + var err error; + err = hosts.add(newHostname("aaa", net.ParseIP("192.168.0.1"), false, false)); + if err != nil { + t.Error("Did not expect error on first hostname"); + } + err = hosts.add(newHostname("aaa", net.ParseIP("192.168.0.1"), false, false)); + if err == nil { + t.Error("Expected error on duplicate host"); + } + fmt.Println(err); + // Not Commented stuff hosts = parseLine("192.168.0.1 broadcasthost test.domain.com domain.com") if !hosts.Contains(newHostname("broadcasthost", net.ParseIP("192.168.0.1"), false, false)) || @@ -115,7 +126,7 @@ func TestParseLine(t *testing.T) { // Wildcard stuff hosts = parseLine("192.168.0.1 *.domain.com mail.domain.com serenity") - if !hosts.Contains(newHostname(".domain.com", net.ParseIP("192.168.0.1"), false, true)) || + if !hosts.Contains(newHostname("domain.com", net.ParseIP("192.168.0.1"), false, true)) || !hosts.Contains(newHostname("mail.domain.com", net.ParseIP("192.168.0.1"), false, false)) || !hosts.Contains(newHostname("serenity", net.ParseIP("192.168.0.1"), false, false)) || len(hosts) != 3 { diff --git a/hostsfile/utils.go b/hostsfile/utils.go index 64b2042..590b071 100644 --- a/hostsfile/utils.go +++ b/hostsfile/utils.go @@ -73,11 +73,13 @@ func (h *hostlist) FindHosts(name string) (addrs []net.IP) { } if len(addrs) == 0 { + var domain_match string; for _, hostname := range *h { if hostname.wildcard == true && len(hostname.domain) < len(name) { - if name[len(name)-len(hostname.domain):] == hostname.domain { + domain_match = strings.Join([]string{".", hostname.domain}, ""); + if name[len(name)-len(domain_match):] == domain_match { var left string; - left = name[0:len(name)-len(hostname.domain)] + left = name[0:len(name)-len(domain_match)] if !strings.Contains(left, ".") { addrs = append(addrs, hostname.ip) } @@ -92,9 +94,8 @@ func (h *hostlist) FindHosts(name string) (addrs []net.IP) { func (h *hostlist) add(hostnamev *hostname) error { hostname := newHostname(hostnamev.domain, hostnamev.ip, hostnamev.ipv6, hostnamev.wildcard) for _, found := range *h { - if found.domain == hostname.domain && found.ip.Equal(hostname.ip) { - return fmt.Errorf("Duplicate hostname entry for %s -> %s", - hostname.domain, hostname.ip) + if found.Equal(hostname) { + return fmt.Errorf("Duplicate hostname entry for %#v", hostname) } } *h = append(*h, hostname) @@ -170,8 +171,8 @@ func parseLine(line string) hostlist { var isWildcard bool for _, v := range domains { isWildcard = false - if v[0:1] == "*" { - v = v[1:] + if v[0:2] == "*." { + v = v[2:] isWildcard = true } hostname := newHostname(v, ip, isIPv6, isWildcard) From 017a75fcedc0b15d029a0778242a117bd96dca62 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Sat, 12 Mar 2016 12:35:13 +0100 Subject: [PATCH 12/13] remove debug output with error returned --- hostsfile/hostsfile_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hostsfile/hostsfile_test.go b/hostsfile/hostsfile_test.go index 3b507e4..2fba33f 100644 --- a/hostsfile/hostsfile_test.go +++ b/hostsfile/hostsfile_test.go @@ -113,7 +113,6 @@ func TestParseLine(t *testing.T) { if err == nil { t.Error("Expected error on duplicate host"); } - fmt.Println(err); // Not Commented stuff hosts = parseLine("192.168.0.1 broadcasthost test.domain.com domain.com") From 2da0269767a5a27fd36d88059a6e8f5ed85108ab Mon Sep 17 00:00:00 2001 From: Jan Broer Date: Sat, 12 Mar 2016 17:02:41 +0100 Subject: [PATCH 13/13] Document hosts file usage --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52faa96..ba1edbb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-dnsmasq -*Version 1.0.1* +*Version 1.0.2* go-dnsmasq is a light weight (1.2 MB) DNS caching server/forwarder with minimal filesystem and runtime overhead. @@ -89,3 +89,14 @@ docker run -d -p 53:53/udp -p 53:53 janeczku/go-dnsmasq:latest ``` You can configure the container by passing the corresponding environmental variables with docker run's `--env` flag. + +#### Serving A/AAAA records from a hosts file +The `--hostsfile` parameter expects a standard plain text [hosts file](https://en.wikipedia.org/wiki/Hosts_(file)) with the only difference being that a wildcard `*` in the left-most label of hostnames is allowed. Wildcard entries will match any subdomain that is not explicitely defined. +For example, given a hosts file with the following content: + +``` +192.168.0.1 db1.db.local +192.168.0.2 *.db.local +``` + +Queries for `db2.db.local` would be answered with an A record pointing to 192.168.0.2, while queries for `db1.db.local` would yield an A record pointing to 192.168.0.1.