From 2221b6160c26158267900a78bb82c2df344df1f3 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 Jan 2020 13:56:57 +0100 Subject: [PATCH 01/13] sign: add expiration jitter (#3588) * add expiration jitter Signed-off-by: Miek Gieben * sign: add expiration jitter This PR adds a expiration jitter to spread out zone re-signing even more. The max is 5 extra days added when creating the signer for a specific zone. Also make the duration* constants private to clean up the godoc for this plugin. Signed-off-by: Miek Gieben --- plugin/sign/README.md | 2 +- plugin/sign/setup.go | 15 ++++++++------- plugin/sign/sign.go | 13 +++++++------ plugin/sign/signer.go | 31 ++++++++++++++++--------------- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/plugin/sign/README.md b/plugin/sign/README.md index 6f528c49c..957fd3406 100644 --- a/plugin/sign/README.md +++ b/plugin/sign/README.md @@ -32,7 +32,7 @@ it do key or algorithm rollovers - it just signs. Both these dates are only checked on the SOA's signature(s). * Create RRSIGs that have an inception of -3 hours (minus a jitter between 0 and 18 hours) - and a expiration of +32 days for every given DNSKEY. + and a expiration of +32 (plus a jitter between 0 and 5 days) days for every given DNSKEY. * Add NSEC records for all names in the zone. The TTL for these is the negative cache TTL from the SOA record. diff --git a/plugin/sign/setup.go b/plugin/sign/setup.go index 8f4d4abd5..c830eb930 100644 --- a/plugin/sign/setup.go +++ b/plugin/sign/setup.go @@ -23,7 +23,7 @@ func setup(c *caddy.Controller) error { c.OnStartup(sign.OnStartup) c.OnStartup(func() error { for _, signer := range sign.signers { - go signer.refresh(DurationRefreshHours) + go signer.refresh(durationRefreshHours) } return nil }) @@ -64,12 +64,13 @@ func parse(c *caddy.Controller) (*Sign, error) { signers := make([]*Signer, len(origins)) for i := range origins { signers[i] = &Signer{ - dbfile: dbfile, - origin: plugin.Host(origins[i]).Normalize(), - jitter: time.Duration(float32(DurationJitter) * rand.Float32()), - directory: "/var/lib/coredns", - stop: make(chan struct{}), - signedfile: fmt.Sprintf("db.%ssigned", origins[i]), // origins[i] is a fqdn, so it ends with a dot, hence %ssigned. + dbfile: dbfile, + origin: plugin.Host(origins[i]).Normalize(), + jitterIncep: time.Duration(float32(durationInceptionJitter) * rand.Float32()), + jitterExpir: time.Duration(float32(durationExpirationDayJitter) * rand.Float32()), + directory: "/var/lib/coredns", + stop: make(chan struct{}), + signedfile: fmt.Sprintf("db.%ssigned", origins[i]), // origins[i] is a fqdn, so it ends with a dot, hence %ssigned. } } diff --git a/plugin/sign/sign.go b/plugin/sign/sign.go index f5eb9afac..e74b1e40b 100644 --- a/plugin/sign/sign.go +++ b/plugin/sign/sign.go @@ -26,12 +26,13 @@ func (s *Sign) OnStartup() error { // Various duration constants for signing of the zones. const ( - DurationExpireDays = 7 * 24 * time.Hour // max time allowed before expiration - DurationResignDays = 6 * 24 * time.Hour // if the last sign happenend this long ago, sign again - DurationSignatureExpireDays = 32 * 24 * time.Hour // sign for 32 days - DurationRefreshHours = 5 * time.Hour // check zones every 5 hours - DurationJitter = -18 * time.Hour // default max jitter - DurationSignatureInceptionHours = -3 * time.Hour // -(2+1) hours, be sure to catch daylight saving time and such, jitter is subtracted + durationExpireDays = 7 * 24 * time.Hour // max time allowed before expiration + durationResignDays = 6 * 24 * time.Hour // if the last sign happenend this long ago, sign again + durationSignatureExpireDays = 32 * 24 * time.Hour // sign for 32 days + durationRefreshHours = 5 * time.Hour // check zones every 5 hours + durationInceptionJitter = -18 * time.Hour // default max jitter for the inception + durationExpirationDayJitter = 5 * 24 * time.Hour // default max jitter for the expiration + durationSignatureInceptionHours = -3 * time.Hour // -(2+1) hours, be sure to catch daylight saving time and such, jitter is subtracted ) const timeFmt = "2006-01-02T15:04:05.000Z07:00" diff --git a/plugin/sign/signer.go b/plugin/sign/signer.go index b0e2f02fe..d1ea22356 100644 --- a/plugin/sign/signer.go +++ b/plugin/sign/signer.go @@ -18,11 +18,12 @@ var log = clog.NewWithPlugin("sign") // Signer holds the data needed to sign a zone file. type Signer struct { - keys []Pair - origin string - dbfile string - directory string - jitter time.Duration + keys []Pair + origin string + dbfile string + directory string + jitterIncep time.Duration + jitterExpir time.Duration signedfile string stop chan struct{} @@ -42,7 +43,7 @@ func (s *Signer) Sign(now time.Time) (*file.Zone, error) { mttl := z.Apex.SOA.Minttl ttl := z.Apex.SOA.Header().Ttl - inception, expiration := lifetime(now, s.jitter) + inception, expiration := lifetime(now, s.jitterIncep, s.jitterExpir) z.Apex.SOA.Serial = uint32(now.Unix()) for _, pair := range s.keys { @@ -143,8 +144,8 @@ func resign(rd io.Reader, now time.Time) (why error) { } incep, _ := time.Parse("20060102150405", dns.TimeToString(x.Inception)) // If too long ago, resign. - if now.Sub(incep) >= 0 && now.Sub(incep) > DurationResignDays { - return fmt.Errorf("inception %q was more than: %s ago from %s: %s", incep.Format(timeFmt), DurationResignDays, now.Format(timeFmt), now.Sub(incep)) + if now.Sub(incep) >= 0 && now.Sub(incep) > durationResignDays { + return fmt.Errorf("inception %q was more than: %s ago from %s: %s", incep.Format(timeFmt), durationResignDays, now.Format(timeFmt), now.Sub(incep)) } // Inception hasn't even start yet. if now.Sub(incep) < 0 { @@ -152,8 +153,8 @@ func resign(rd io.Reader, now time.Time) (why error) { } expire, _ := time.Parse("20060102150405", dns.TimeToString(x.Expiration)) - if expire.Sub(now) < DurationExpireDays { - return fmt.Errorf("expiration %q is less than: %s away from %s: %s", expire.Format(timeFmt), DurationExpireDays, now.Format(timeFmt), expire.Sub(now)) + if expire.Sub(now) < durationExpireDays { + return fmt.Errorf("expiration %q is less than: %s away from %s: %s", expire.Format(timeFmt), durationExpireDays, now.Format(timeFmt), expire.Sub(now)) } } i++ @@ -173,7 +174,7 @@ func signAndLog(s *Signer, why error) { z, err := s.Sign(now) log.Infof("Signing %q because %s", s.origin, why) if err != nil { - log.Warningf("Error signing %q with key tags %q in %s: %s, next: %s", s.origin, keyTag(s.keys), time.Since(now), err, now.Add(DurationRefreshHours).Format(timeFmt)) + log.Warningf("Error signing %q with key tags %q in %s: %s, next: %s", s.origin, keyTag(s.keys), time.Since(now), err, now.Add(durationRefreshHours).Format(timeFmt)) return } @@ -181,7 +182,7 @@ func signAndLog(s *Signer, why error) { log.Warningf("Error signing %q: failed to move zone file into place: %s", s.origin, err) return } - log.Infof("Successfully signed zone %q in %q with key tags %q and %d SOA serial, elapsed %f, next: %s", s.origin, filepath.Join(s.directory, s.signedfile), keyTag(s.keys), z.Apex.SOA.Serial, time.Since(now).Seconds(), now.Add(DurationRefreshHours).Format(timeFmt)) + log.Infof("Successfully signed zone %q in %q with key tags %q and %d SOA serial, elapsed %f, next: %s", s.origin, filepath.Join(s.directory, s.signedfile), keyTag(s.keys), z.Apex.SOA.Serial, time.Since(now).Seconds(), now.Add(durationRefreshHours).Format(timeFmt)) } // refresh checks every val if some zones need to be resigned. @@ -202,8 +203,8 @@ func (s *Signer) refresh(val time.Duration) { } } -func lifetime(now time.Time, jitter time.Duration) (uint32, uint32) { - incep := uint32(now.Add(DurationSignatureInceptionHours).Add(jitter).Unix()) - expir := uint32(now.Add(DurationSignatureExpireDays).Unix()) +func lifetime(now time.Time, jitterInception, jitterExpiration time.Duration) (uint32, uint32) { + incep := uint32(now.Add(durationSignatureInceptionHours).Add(jitterInception).Unix()) + expir := uint32(now.Add(durationSignatureExpireDays).Add(jitterExpiration).Unix()) return incep, expir } From 81a54faaeb36ebe3374490ed8800f92bd308a011 Mon Sep 17 00:00:00 2001 From: Dominic Yin Date: Mon, 13 Jan 2020 14:19:49 +0800 Subject: [PATCH 02/13] add mips64le to released ARCH (#3589) Signed-off-by: Dominic Yin --- Makefile.release | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile.release b/Makefile.release index eec8cf2b1..b346216c7 100644 --- a/Makefile.release +++ b/Makefile.release @@ -88,6 +88,8 @@ build: mkdir -p build/windows/amd64 && $(MAKE) coredns BINARY=build/windows/amd64/$(NAME).exe SYSTEM="GOOS=windows GOARCH=amd64" CHECKS="" BUILDOPTS="" @echo Building: linux/mips - $(VERSION) mkdir -p build/linux/mips && $(MAKE) coredns BINARY=build/linux/mips/$(NAME) SYSTEM="GOOS=linux GOARCH=mips" CHECKS="" BUILDOPTS="" + @echo Building: linux/mips64le - $(VERSION) + mkdir -p build/linux/mips64le && $(MAKE) coredns BINARY=build/linux/mips64le/$(NAME) SYSTEM="GOOS=linux GOARCH=mips64le" CHECKS="" BUILDOPTS="" @echo Building: linux/$(LINUX_ARCH) - $(VERSION) ;\ for arch in $(LINUX_ARCH); do \ mkdir -p build/linux/$$arch && $(MAKE) coredns BINARY=build/linux/$$arch/$(NAME) SYSTEM="GOOS=linux GOARCH=$$arch" CHECKS="" BUILDOPTS="" ;\ @@ -100,6 +102,7 @@ tar: tar -zcf release/$(NAME)_$(VERSION)_darwin_amd64.tgz -C build/darwin/amd64 $(NAME) tar -zcf release/$(NAME)_$(VERSION)_windows_amd64.tgz -C build/windows/amd64 $(NAME).exe tar -zcf release/$(NAME)_$(VERSION)_linux_mips.tgz -C build/linux/mips $(NAME) + tar -zcf release/$(NAME)_$(VERSION)_linux_mips64le.tgz -C build/linux/mips64le $(NAME) for arch in $(LINUX_ARCH); do \ tar -zcf release/$(NAME)_$(VERSION)_linux_$$arch.tgz -C build/linux/$$arch $(NAME) ;\ done From dcff271480780f90a5703e9b4ee25cf2a40c4ba8 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 13 Jan 2020 15:31:42 +0100 Subject: [PATCH 03/13] doc: run make -f Makefile.doc (#3595) Update the docs (mechanical change). Also run: go generate (no changes, good!) Signed-off-by: Miek Gieben --- man/coredns-acl.7 | 2 +- man/coredns-autopath.7 | 2 +- man/coredns-bind.7 | 2 +- man/coredns-bufsize.7 | 2 +- man/coredns-cache.7 | 2 +- man/coredns-clouddns.7 | 2 +- man/coredns-dnssec.7 | 2 +- man/coredns-file.7 | 2 +- man/coredns-forward.7 | 2 +- man/coredns-grpc.7 | 2 +- man/coredns-health.7 | 2 +- man/coredns-hosts.7 | 2 +- man/coredns-import.7 | 2 +- man/coredns-kubernetes.7 | 2 +- man/coredns-reload.7 | 2 +- man/coredns-rewrite.7 | 2 +- man/coredns-route53.7 | 2 +- man/coredns-secondary.7 | 2 +- man/coredns-sign.7 | 4 ++-- man/coredns-template.7 | 2 +- man/coredns-tls.7 | 2 +- man/coredns-transfer.7 | 2 +- 22 files changed, 23 insertions(+), 23 deletions(-) diff --git a/man/coredns-acl.7 b/man/coredns-acl.7 index d92594171..21f65e79f 100644 --- a/man/coredns-acl.7 +++ b/man/coredns-acl.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-ACL" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-ACL" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .PP \fIacl\fP - enforces access control policies on source ip and prevents unauthorized access to DNS servers. diff --git a/man/coredns-autopath.7 b/man/coredns-autopath.7 index 397921047..4bacee73f 100644 --- a/man/coredns-autopath.7 +++ b/man/coredns-autopath.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-AUTOPATH" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-AUTOPATH" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-bind.7 b/man/coredns-bind.7 index 2cfc34a8c..ff3e7c939 100644 --- a/man/coredns-bind.7 +++ b/man/coredns-bind.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-BIND" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-BIND" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-bufsize.7 b/man/coredns-bufsize.7 index e3e4e0e97..96c46f8cf 100644 --- a/man/coredns-bufsize.7 +++ b/man/coredns-bufsize.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-BUFSIZE" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-BUFSIZE" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-cache.7 b/man/coredns-cache.7 index 0349af0f6..bf9f738df 100644 --- a/man/coredns-cache.7 +++ b/man/coredns-cache.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-CACHE" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-CACHE" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-clouddns.7 b/man/coredns-clouddns.7 index 7265aecc2..ee90516fb 100644 --- a/man/coredns-clouddns.7 +++ b/man/coredns-clouddns.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-CLOUDDNS" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-CLOUDDNS" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-dnssec.7 b/man/coredns-dnssec.7 index c9d944808..bb06c829d 100644 --- a/man/coredns-dnssec.7 +++ b/man/coredns-dnssec.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-DNSSEC" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-DNSSEC" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-file.7 b/man/coredns-file.7 index bdc4e1b04..a04290d50 100644 --- a/man/coredns-file.7 +++ b/man/coredns-file.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-FILE" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-FILE" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-forward.7 b/man/coredns-forward.7 index 40cbd9b85..5d3c022aa 100644 --- a/man/coredns-forward.7 +++ b/man/coredns-forward.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-FORWARD" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-FORWARD" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-grpc.7 b/man/coredns-grpc.7 index c0b68bba0..27376e152 100644 --- a/man/coredns-grpc.7 +++ b/man/coredns-grpc.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-GRPC" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-GRPC" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-health.7 b/man/coredns-health.7 index 17b535bc5..d1b85dabb 100644 --- a/man/coredns-health.7 +++ b/man/coredns-health.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-HEALTH" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-HEALTH" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-hosts.7 b/man/coredns-hosts.7 index 2f7b6168f..64a37bd9f 100644 --- a/man/coredns-hosts.7 +++ b/man/coredns-hosts.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-HOSTS" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-HOSTS" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-import.7 b/man/coredns-import.7 index 215a86b21..fc65bd74c 100644 --- a/man/coredns-import.7 +++ b/man/coredns-import.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-IMPORT" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-IMPORT" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-kubernetes.7 b/man/coredns-kubernetes.7 index c494b3068..ffd3f3563 100644 --- a/man/coredns-kubernetes.7 +++ b/man/coredns-kubernetes.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-KUBERNETES" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-KUBERNETES" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-reload.7 b/man/coredns-reload.7 index 8265abc65..c7cbfbfda 100644 --- a/man/coredns-reload.7 +++ b/man/coredns-reload.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-RELOAD" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-RELOAD" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-rewrite.7 b/man/coredns-rewrite.7 index 661136c9e..0d0ebff7e 100644 --- a/man/coredns-rewrite.7 +++ b/man/coredns-rewrite.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-REWRITE" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-REWRITE" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-route53.7 b/man/coredns-route53.7 index 71ef617bc..c8408e16d 100644 --- a/man/coredns-route53.7 +++ b/man/coredns-route53.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-ROUTE53" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-ROUTE53" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-secondary.7 b/man/coredns-secondary.7 index 373b889f3..4b01b9a22 100644 --- a/man/coredns-secondary.7 +++ b/man/coredns-secondary.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-SECONDARY" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-SECONDARY" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-sign.7 b/man/coredns-sign.7 index 8c37db1e3..1d9800560 100644 --- a/man/coredns-sign.7 +++ b/man/coredns-sign.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-SIGN" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-SIGN" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP @@ -43,7 +43,7 @@ the signature only has 14 days left before expiring. Both these dates are only checked on the SOA's signature(s). .IP \(bu 4 Create RRSIGs that have an inception of -3 hours (minus a jitter between 0 and 18 hours) -and a expiration of +32 days for every given DNSKEY. +and a expiration of +32 (plus a jitter between 0 and 5 days) days for every given DNSKEY. .IP \(bu 4 Add NSEC records for all names in the zone. The TTL for these is the negative cache TTL from the SOA record. diff --git a/man/coredns-template.7 b/man/coredns-template.7 index 9f18b7496..046c68e83 100644 --- a/man/coredns-template.7 +++ b/man/coredns-template.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-TEMPLATE" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-TEMPLATE" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-tls.7 b/man/coredns-tls.7 index 0ba8769f1..120adf4f7 100644 --- a/man/coredns-tls.7 +++ b/man/coredns-tls.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-TLS" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-TLS" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP diff --git a/man/coredns-transfer.7 b/man/coredns-transfer.7 index 6bb473642..c1635a781 100644 --- a/man/coredns-transfer.7 +++ b/man/coredns-transfer.7 @@ -1,5 +1,5 @@ .\" Generated by Mmark Markdown Processer - mmark.miek.nl -.TH "COREDNS-TRANSFER" 7 "December 2019" "CoreDNS" "CoreDNS Plugins" +.TH "COREDNS-TRANSFER" 7 "January 2020" "CoreDNS" "CoreDNS Plugins" .SH "NAME" .PP From 6f940cb3224cc11cfd80d4855b475f3aa2f7bc5e Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 13 Jan 2020 15:39:05 +0100 Subject: [PATCH 04/13] Remove replace in go.mod (#3596) Seems these are absolete now? /cc @yongtang Signed-off-by: Miek Gieben --- go.mod | 5 ----- 1 file changed, 5 deletions(-) diff --git a/go.mod b/go.mod index 20d42bbd6..897eedf2f 100644 --- a/go.mod +++ b/go.mod @@ -46,8 +46,3 @@ require ( k8s.io/client-go v0.17.0 k8s.io/klog v1.0.0 ) - -replace ( - github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.0.0+incompatible - github.com/miekg/dns v1.1.3 => github.com/miekg/dns v1.1.22 -) From d024014251bda0f681e27b106bd9101de987564d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2020 06:39:24 -0800 Subject: [PATCH 05/13] build(deps): bump github.com/aws/aws-sdk-go from 1.27.0 to 1.28.0 (#3591) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.27.0 to 1.28.0. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.27.0...v1.28.0) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 897eedf2f..be845ba85 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/DataDog/datadog-go v2.2.0+incompatible // indirect github.com/Shopify/sarama v1.21.0 // indirect github.com/apache/thrift v0.13.0 // indirect - github.com/aws/aws-sdk-go v1.27.0 + github.com/aws/aws-sdk-go v1.28.0 github.com/caddyserver/caddy v1.0.4 github.com/coredns/federation v0.0.0-20190818181423-e032b096babe github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect diff --git a/go.sum b/go.sum index 25df394c5..326c70c3f 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.28.0 h1:NkmnHFVEMTRYTleRLm5xUaL1mHKKkYQl4rCd+jzD58c= +github.com/aws/aws-sdk-go v1.28.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= From 8a4f3c3701c8b631306afb00312d7b1233d3eade Mon Sep 17 00:00:00 2001 From: "coredns-auto-go-mod-tidy[bot]" Date: Mon, 13 Jan 2020 14:41:09 +0000 Subject: [PATCH 06/13] auto go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 326c70c3f..6b8cfa23e 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,6 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.28.0 h1:NkmnHFVEMTRYTleRLm5xUaL1mHKKkYQl4rCd+jzD58c= github.com/aws/aws-sdk-go v1.28.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= From b7977402d6c3ffd2a7e9b7da310db0579d8447a0 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 15 Jan 2020 08:30:27 -0800 Subject: [PATCH 07/13] Update both DataDog/dd-trace-go and DataDog/datadog-go (#3597) This is a PR that supersede #3592. In PR #3592 the build failed because DataDog/dd-trace-go and DataDog/datadog-go have to be updated at the same time. (dependabot failed to detect that). This PR fixes the error. This PR closes #3592 Signed-off-by: Yong Tang --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index be845ba85..7e4178785 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Azure/azure-sdk-for-go v32.6.0+incompatible github.com/Azure/go-autorest/autorest v0.9.3 github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 - github.com/DataDog/datadog-go v2.2.0+incompatible // indirect + github.com/DataDog/datadog-go v3.3.1+incompatible // indirect github.com/Shopify/sarama v1.21.0 // indirect github.com/apache/thrift v0.13.0 // indirect github.com/aws/aws-sdk-go v1.28.0 @@ -40,7 +40,7 @@ require ( golang.org/x/sys v0.0.0-20191220142924-d4481acd189f google.golang.org/api v0.15.0 google.golang.org/grpc v1.26.0 - gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 + gopkg.in/DataDog/dd-trace-go.v1 v1.20.0 k8s.io/api v0.17.0 k8s.io/apimachinery v0.17.0 k8s.io/client-go v0.17.0 diff --git a/go.sum b/go.sum index 6b8cfa23e..0bcb3c4ab 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= -github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.3.1+incompatible h1:NT/ghvYzqIzTJGiqvc3n4t9cZy8waO+I2O3I8Cok6/k= +github.com/DataDog/datadog-go v3.3.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -386,6 +386,7 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -564,8 +565,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 h1:aFSFd6oDMdvPYiToGqTv7/ERA6QrPhGaXSuueRCaM88= -gopkg.in/DataDog/dd-trace-go.v1 v1.19.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/DataDog/dd-trace-go.v1 v1.20.0 h1:OUvLkkEtg2HpDS9g+GeNKDnJtx9zVbqCh2hGH7jHHfg= +gopkg.in/DataDog/dd-trace-go.v1 v1.20.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= From aa8c325d4a0fc7ac35b4a9b58f984ef6ee0bf3d1 Mon Sep 17 00:00:00 2001 From: "Brad P. Crochet" Date: Thu, 16 Jan 2020 14:47:39 -0500 Subject: [PATCH 08/13] Fix HostPortOrFile to support IPv6 addresses with zone (#3527) 1. The HostPortOrFile tests don't have any IPv6 tests. This adds some. 2. The HostPortOrFile breaks if any of the addresses have IPv6 zone defined. ParseIP does not handle %zone anymore. Signed-off-by: Brad P. Crochet --- plugin/pkg/parse/host.go | 19 +++++++++++++++---- plugin/pkg/parse/host_test.go | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/plugin/pkg/parse/host.go b/plugin/pkg/parse/host.go index 87177125f..c1b7d23e0 100644 --- a/plugin/pkg/parse/host.go +++ b/plugin/pkg/parse/host.go @@ -4,12 +4,23 @@ import ( "fmt" "net" "os" + "strings" "github.com/coredns/coredns/plugin/pkg/transport" "github.com/miekg/dns" ) +// Strips the zone, but preserves any port that comes after the zone +func stripZone(host string) string { + if strings.Contains(host, "%") { + lastPercent := strings.LastIndex(host, "%") + newHost := host[:lastPercent] + return newHost + } + return host +} + // HostPortOrFile parses the strings in s, each string can either be a // address, [scheme://]address:port or a filename. The address part is checked // and in case of filename a resolv.conf like file is (assumed) and parsed and @@ -21,10 +32,11 @@ func HostPortOrFile(s ...string) ([]string, error) { trans, host := Transport(h) addr, _, err := net.SplitHostPort(host) + if err != nil { // Parse didn't work, it is not a addr:port combo - if net.ParseIP(host) == nil { - // Not an IP address. + hostNoZone := stripZone(host) + if net.ParseIP(hostNoZone) == nil { ss, err := tryFile(host) if err == nil { servers = append(servers, ss...) @@ -47,8 +59,7 @@ func HostPortOrFile(s ...string) ([]string, error) { continue } - if net.ParseIP(addr) == nil { - // Not an IP address. + if net.ParseIP(stripZone(addr)) == nil { ss, err := tryFile(host) if err == nil { servers = append(servers, ss...) diff --git a/plugin/pkg/parse/host_test.go b/plugin/pkg/parse/host_test.go index f6e771f29..1c23c5bee 100644 --- a/plugin/pkg/parse/host_test.go +++ b/plugin/pkg/parse/host_test.go @@ -34,6 +34,26 @@ func TestHostPortOrFile(t *testing.T) { "127.0.0.1:53", false, }, + { + "fe80::1", + "[fe80::1]:53", + false, + }, + { + "fe80::1%ens3", + "[fe80::1%ens3]:53", + false, + }, + { + "[fd01::1]:153", + "[fd01::1]:153", + false, + }, + { + "[fd01::1%ens3]:153", + "[fd01::1%ens3]:153", + false, + }, } err := ioutil.WriteFile("resolv.conf", []byte("nameserver 127.0.0.1\n"), 0600) From c95faea6241de662f730d35c3368fc0fdb1ae1c0 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 17 Jan 2020 16:16:29 +0100 Subject: [PATCH 09/13] docs: update README and log plugin (#3602) README: remove the logo thing as we stopped doing that log: remote the lines about the clock output as that's gone as well and discuss the query log vs other logging a bit. Signed-off-by: Miek Gieben --- README.md | 26 +++++++++++--------------- plugin/log/README.md | 13 ++++++------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 72f2ef2c9..956e0dcb4 100644 --- a/README.md +++ b/README.md @@ -77,25 +77,20 @@ The above command alone will have `coredns` binary generated. ## Examples When starting CoreDNS without any configuration, it loads the -[*whoami*](https://coredns.io/plugins/whoami) plugin and starts listening on port 53 (override with -`-dns.port`), it should show the following: +[*whoami*](https://coredns.io/plugins/whoami) and [*log*](https://coredns.io/plugins/log) plugins +and starts listening on port 53 (override with `-dns.port`), it should show the following: ~~~ txt .:53 - ______ ____ _ _______ - / ____/___ ________ / __ \/ | / / ___/ ~ CoreDNS-1.6.3 - / / / __ \/ ___/ _ \/ / / / |/ /\__ \ ~ linux/amd64, go1.13, -/ /___/ /_/ / / / __/ /_/ / /| /___/ / -\____/\____/_/ \___/_____/_/ |_//____/ +CoreDNS-1.6.6 +linux/amd64, go1.13.5, aa8c32 ~~~ Any query sent to port 53 should return some information; your sending address, port and protocol -used. +used. The query should also be logged to standard output. If you have a Corefile without a port number specified it will, by default, use port 53, but you can -override the port with the `-dns.port` flag: - -`./coredns -dns.port 1053`, runs the server on port 1053. +override the port with the `-dns.port` flag: `coredns -dns.port 1053`, runs the server on port 1053. Start a simple proxy. You'll need to be root to start listening on port 53. @@ -108,11 +103,11 @@ Start a simple proxy. You'll need to be root to start listening on port 53. } ~~~ -Just start CoreDNS: `./coredns`. Then just query on that port (53). The query should be forwarded -to 8.8.8.8 and the response will be returned. Each query should also show up in the log which is -printed on standard output. +Start CoreDNS and then query on that port (53). The query should be forwarded to 8.8.8.8 and the +response will be returned. Each query should also show up in the log which is printed on standard +output. -Serve the (NSEC) DNSSEC-signed `example.org` on port 1053, with errors and logging sent to standard +To serve the (NSEC) DNSSEC-signed `example.org` on port 1053, with errors and logging sent to standard output. Allow zone transfers to everybody, but specifically mention 1 IP address so that CoreDNS can send notifies to it. @@ -139,6 +134,7 @@ example.org:1053 { errors log } + . { any forward . 8.8.8.8:53 diff --git a/plugin/log/README.md b/plugin/log/README.md index 8b397bffa..5dcbcde47 100644 --- a/plugin/log/README.md +++ b/plugin/log/README.md @@ -7,10 +7,10 @@ ## Description By just using *log* you dump all queries (and parts for the reply) on standard output. Options exist -to tweak the output a little. The date/time prefix on log lines is RFC3339 formatted with -milliseconds. +to tweak the output a little. Note that for busy servers logging will incur a performance hit. -Note that for busy servers logging will incur a performance hit. +Enabling or disabling the *log* plugin only affects the query logging, any other logging from +CoreDNS will show up regardless. ## Syntax @@ -18,8 +18,7 @@ Note that for busy servers logging will incur a performance hit. log ~~~ -* With no arguments, a query log entry is written to *stdout* in the common log format for all requests - +With no arguments, a query log entry is written to *stdout* in the common log format for all requests. Or if you want/need slightly more control: ~~~ txt @@ -47,11 +46,11 @@ The classes of responses have the following meaning: * `denial`: either NXDOMAIN or nodata responses (Name exists, type does not). A nodata response sets the return code to NOERROR. * `error`: SERVFAIL, NOTIMP, REFUSED, etc. Anything that indicates the remote server is not willing to - resolve the request. + resolve the request. * `all`: the default - nothing is specified. Using of this class means that all messages will be logged whatever we mix together with "all". -If no class is specified, it defaults to *all*. +If no class is specified, it defaults to `all`. ## Log Format From 5f159ca4649ebd125b4718d02367be10f402ed74 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 17 Jan 2020 16:47:45 +0100 Subject: [PATCH 10/13] gofmt -w -s **/*.go (#3603) format and remove trailing white space; makes 'make presubmit' pass again. Signed-off-by: Miek Gieben --- plugin/etcd/etcd.go | 2 +- plugin/forward/forward.go | 2 +- plugin/forward/setup.go | 2 +- plugin/ready/ready.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/etcd/etcd.go b/plugin/etcd/etcd.go index d8e9e054e..5a497d57b 100644 --- a/plugin/etcd/etcd.go +++ b/plugin/etcd/etcd.go @@ -178,7 +178,7 @@ func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 { // shouldInclude returns true if the service should be included in a list of records, given the qType. For all the // currently supported lookup types, the only one to allow for an empty Host field in the service are TXT records -// which resolve directly. If a TXT record is being resolved by CNAME, then we expect the Host field to have a +// which resolve directly. If a TXT record is being resolved by CNAME, then we expect the Host field to have a // value while the TXT field will be empty. func shouldInclude(serv *msg.Service, qType uint16) bool { return (qType == dns.TypeTXT && serv.Text != "") || serv.Host != "" diff --git a/plugin/forward/forward.go b/plugin/forward/forward.go index 897d9e7eb..6631b7bab 100644 --- a/plugin/forward/forward.go +++ b/plugin/forward/forward.go @@ -12,8 +12,8 @@ import ( "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/debug" - "github.com/coredns/coredns/plugin/pkg/policy" clog "github.com/coredns/coredns/plugin/pkg/log" + "github.com/coredns/coredns/plugin/pkg/policy" "github.com/coredns/coredns/request" "github.com/miekg/dns" diff --git a/plugin/forward/setup.go b/plugin/forward/setup.go index 73c9cf943..fa35639f2 100644 --- a/plugin/forward/setup.go +++ b/plugin/forward/setup.go @@ -8,8 +8,8 @@ import ( "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/metrics" - "github.com/coredns/coredns/plugin/pkg/policy" "github.com/coredns/coredns/plugin/pkg/parse" + "github.com/coredns/coredns/plugin/pkg/policy" pkgtls "github.com/coredns/coredns/plugin/pkg/tls" "github.com/coredns/coredns/plugin/pkg/transport" diff --git a/plugin/ready/ready.go b/plugin/ready/ready.go index 326d39955..a76a20084 100644 --- a/plugin/ready/ready.go +++ b/plugin/ready/ready.go @@ -11,8 +11,8 @@ import ( "sync" clog "github.com/coredns/coredns/plugin/pkg/log" - "github.com/coredns/coredns/plugin/pkg/uniq" "github.com/coredns/coredns/plugin/pkg/reuseport" + "github.com/coredns/coredns/plugin/pkg/uniq" ) var ( From 9433da1a67d75dccb63502b0c20e9b9c7bf8be6e Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sat, 5 Oct 2019 11:45:45 +0100 Subject: [PATCH 11/13] Add new plugin: traffic Traffic is a plugin that communicates via the xDS protocol to an Envoy control plane. Using the data from this control plane it hands out IP addresses. This allows you (via controlling the data in the control plane) to drain or send more traffic to specific endpoints. The plugin itself only acts upon this data; it doesn't do anything fancy by itself. Code used here is copied from grpc-go and other places, this is clearly marked in the source files. Signed-off-by: Miek Gieben --- core/dnsserver/zdirectives.go | 1 + core/plugin/zplugin.go | 1 + go.mod | 2 + go.sum | 2 + plugin.cfg | 1 + plugin/traffic/HACKING.md | 58 ++++++++ plugin/traffic/README.md | 129 ++++++++++++++++++ plugin/traffic/setup.go | 122 +++++++++++++++++ plugin/traffic/setup_test.go | 53 ++++++++ plugin/traffic/traffic.go | 93 +++++++++++++ plugin/traffic/traffic_test.go | 130 ++++++++++++++++++ plugin/traffic/xds/assignment.go | 116 ++++++++++++++++ plugin/traffic/xds/client.go | 227 +++++++++++++++++++++++++++++++ plugin/traffic/xds/fields.go | 37 +++++ 14 files changed, 972 insertions(+) create mode 100644 plugin/traffic/HACKING.md create mode 100644 plugin/traffic/README.md create mode 100644 plugin/traffic/setup.go create mode 100644 plugin/traffic/setup_test.go create mode 100644 plugin/traffic/traffic.go create mode 100644 plugin/traffic/traffic_test.go create mode 100644 plugin/traffic/xds/assignment.go create mode 100644 plugin/traffic/xds/client.go create mode 100644 plugin/traffic/xds/fields.go diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go index 61d96c633..08b36475c 100644 --- a/core/dnsserver/zdirectives.go +++ b/core/dnsserver/zdirectives.go @@ -30,6 +30,7 @@ var Directives = []string{ "acl", "any", "chaos", + "traffic", "loadbalance", "cache", "rewrite", diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go index 90267f29b..374d0b8fe 100644 --- a/core/plugin/zplugin.go +++ b/core/plugin/zplugin.go @@ -46,6 +46,7 @@ import ( _ "github.com/coredns/coredns/plugin/template" _ "github.com/coredns/coredns/plugin/tls" _ "github.com/coredns/coredns/plugin/trace" + _ "github.com/coredns/coredns/plugin/traffic" _ "github.com/coredns/coredns/plugin/transfer" _ "github.com/coredns/coredns/plugin/whoami" _ "github.com/coredns/federation" diff --git a/go.mod b/go.mod index 7e4178785..60b97b1e8 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,11 @@ require ( github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11 + github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/golang/protobuf v1.3.2 + github.com/google/go-cmp v0.3.1 github.com/googleapis/gnostic v0.2.0 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/imdario/mergo v0.3.7 // indirect diff --git a/go.sum b/go.sum index 0bcb3c4ab..be128c24a 100644 --- a/go.sum +++ b/go.sum @@ -124,7 +124,9 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= diff --git a/plugin.cfg b/plugin.cfg index 3f3dd85fd..f8e00a4e2 100644 --- a/plugin.cfg +++ b/plugin.cfg @@ -39,6 +39,7 @@ dnstap:dnstap acl:acl any:any chaos:chaos +traffic:traffic loadbalance:loadbalance cache:cache rewrite:rewrite diff --git a/plugin/traffic/HACKING.md b/plugin/traffic/HACKING.md new file mode 100644 index 000000000..5fd2faf0d --- /dev/null +++ b/plugin/traffic/HACKING.md @@ -0,0 +1,58 @@ +# Hacking on *traffic* + +Repos used: + + +: implements control plane, has testing stuff in pkg/test/main (iirc). + + +: implements client for xDS - much of this code has been reused here. + +I found these website useful while working on this. + +* https://github.com/envoyproxy/envoy/blob/master/api/API_OVERVIEW.md +* https://github.com/envoyproxy/learnenvoy/blob/master/_articles/service-discovery.md +* This was *really* helpful: https://www.envoyproxy.io/docs/envoy/v1.11.2/api-docs/xds_protocol to + show the flow of the protocol. + +# Testing + +Assuming you have envoyproxy/go-control-plane checked out somewhere, then: + +~~~ sh +% cd ~/src/github.com/envoyproxy/go-control-plane/pkg/test/main +% go build +% ./main --xds=ads --runtimes=2 -debug +~~~ + +This runs a binary from pkg/test/main. Now we're testing aDS. Everything is using gRPC with TLS +disabled: `grpc.WithInsecure()`. The test binary runs on port 18000 on localhost; all these things +are currently hardcoded in the *traffic* plugin. This will be factored out into config as some +point. Another thing that is hardcoded is the use of the "example.org" domain. + +Then for CoreDNS, check out the `traffic` branch, create a Corefile: + +~~~ Corefile +example.org { + traffic grpc://127.0.0.1:18000 { + id test-id + } + debug +} +~~~ + +Start CoreDNS (`coredns -conf Corefile -dns.port=1053`), and see logging/debugging flow by; the +test binary should also spew out a bunch of things. CoreDNS willl build up a list of cluster and +endpoints. Next you can query it: + +~~~ sh +% dig @localhost -p 1053 cluster-v0-0.example.org A +;; QUESTION SECTION: +;cluster-v0-0.example.org. IN A + +;; ANSWER SECTION: +cluster-v0-0.example.org. 5 IN A 127.0.0.1 +~~~ + +Note: the xds/test binary is a go-control-plane binary with added debugging that I'm using for +testing. diff --git a/plugin/traffic/README.md b/plugin/traffic/README.md new file mode 100644 index 000000000..94f0a99bc --- /dev/null +++ b/plugin/traffic/README.md @@ -0,0 +1,129 @@ +# traffic + +## Name + +*traffic* - handout addresses according to assignments from Envoy's xDS. + +## Description + +The *traffic* plugin is a balancer that allows traffic steering, weighted responses +and draining of clusters. The cluster information is retrieved from a service +discovery manager that implements the service discovery protocols that Envoy +[implements](https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol). + +A Cluster is defined as: "A group of logically similar endpoints that Envoy connects to." Each +cluster has a name, which *traffic* extends to be a domain name. See "Naming Clusters" below. + +The use case for this plugin is when a cluster has endpoints running in multiple (Kubernetes?) +clusters and you need to steer traffic to (or away) from these endpoints, i.e. endpoint A needs to +be upgraded, so all traffic to it is drained. Or the entire Kubernetes needs to upgraded, and *all* +endpoints need to be drained from it. + +*Traffic* discovers the endpoints via Envoy's xDS protocol. Endpoints and clusters are discovered +every 10 seconds. The plugin hands out responses that adhere to these assignments. Only endpoints +that are *healthy* are handed out. + +Each DNS response contains a single IP address that's considered the best one. *Traffic* will load +balance A and AAAA queries. The TTL on these answer is set to 5s. It will only return successful +responses either with an answer or otherwise a NODATA response. Queries for non-existent clusters +get a NXDOMAIN. + +The *traffic* plugin has no notion of draining, drop overload and anything that advanced, *it just +acts upon assignments*. This is means that if a endpoint goes down and *traffic* has not seen a new +assignment yet, it will still include this endpoint address in responses. + +## Syntax + +~~~ +traffic TO... +~~~ + +This enabled the *traffic* plugin, with a default node id of `coredns` and no TLS. + +* **TO...** are the Envoy control plane endpoint to connect to. This must start with `grpc://`. + +The extended syntax is available is you want more control. + +~~~ +traffic TO... { + server SERVER [SERVER]... + node ID + tls CERT KEY CA + tls_servername NAME +} +~~~ + +* node **ID** is how *traffic* identifies itself to the control plane. This defaults to `coredns`. +* `tls` **CERT** **KEY** **CA** define the TLS properties for gRPC connection. If this is omitted an + insecure connection is attempted. From 0 to 3 arguments can be provided with the meaning as described below + + * `tls` - no client authentication is used, and the system CAs are used to verify the server certificate + * `tls` **CA** - no client authentication is used, and the file CA is used to verify the server certificate + * `tls` **CERT** **KEY** - client authentication is used with the specified cert/key pair. + The server certificate is verified with the system CAs. + * `tls` **CERT** **KEY** **CA** - client authentication is used with the specified cert/key pair. + The server certificate is verified using the specified CA file. + +* `tls_servername` **NAME** allows you to set a server name in the TLS configuration. This is needed + because *traffic* connects to an IP address, so it can't infer the server name from it. + +## Naming Clusters + +When a cluster is named this usually consists out of a single word, i.e. "cluster-v0", or "web". +The *traffic* plugins uses the name(s) specified in the Server Block to create fully qualified +domain names. For example if the Server Block specifies `lb.example.org` as one of the names, +and "cluster-v0" is one of the load balanced cluster, *traffic* will respond to query asking for +`cluster-v0.lb.example.org.` and the same goes for `web`; `web.lb.example.org`. + +## Metrics + +What metrics should we do? If any? Number of clusters? Number of endpoints and health? + +## Ready + +Should this plugin implement readiness? + +## Examples + +~~~ +lb.example.org { + traffic grpc://127.0.0.1:18000 { + node test-id + } + debug + log +} +~~~ + +This will load balance any names under `lb.example.org` using the data from the manager running on +localhost on port 18000. The node ID will be `test-id` and no TLS will be used. + +## Also See + +The following documents provide some background on Envoy's control plane. + + * + + * + + * + +## Bugs + +Priority and locality information from ClusterLoadAssignments is not used. + +Load reporting via xDS is not supported; this can be implemented, but there are some things that +make this difficult. A single (DNS) query is done by a resolver. Behind this resolver there may be +many clients that will use this reply, the responding server (CoreDNS) has no idea how many clients +use this resolver. So reporting a load of +1 on the CoreDNS side can be anything from 1 to 1000+, +making the load reporting highly inaccurate. + +Multiple **TO** addresses is not implemented. + +## TODO + +* metrics? +* more and better testing +* credentials (other than TLS) - how/what? +* is the protocol correctly implemented? Should we not have a 10s tick, but wait for responses from + the control plane? diff --git a/plugin/traffic/setup.go b/plugin/traffic/setup.go new file mode 100644 index 000000000..bfd9683a4 --- /dev/null +++ b/plugin/traffic/setup.go @@ -0,0 +1,122 @@ +package traffic + +import ( + "crypto/tls" + "fmt" + "math/rand" + "strings" + "time" + + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/plugin" + clog "github.com/coredns/coredns/plugin/pkg/log" + "github.com/coredns/coredns/plugin/pkg/parse" + pkgtls "github.com/coredns/coredns/plugin/pkg/tls" + "github.com/coredns/coredns/plugin/pkg/transport" + "github.com/coredns/coredns/plugin/traffic/xds" + + "github.com/caddyserver/caddy" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +var log = clog.NewWithPlugin("traffic") + +func init() { plugin.Register("traffic", setup) } + +func setup(c *caddy.Controller) error { + rand.Seed(int64(time.Now().Nanosecond())) + t, err := parseTraffic(c) + if err != nil { + return plugin.Error("traffic", err) + } + + dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { + t.Next = next + return t + }) + + c.OnStartup(func() error { + go t.c.Run() + return nil + }) + c.OnShutdown(func() error { return t.c.Stop() }) + return nil +} + +func parseTraffic(c *caddy.Controller) (*Traffic, error) { + node := "coredns" + toHosts := []string{} + t := &Traffic{} + var ( + err error + tlsConfig *tls.Config + tlsServerName string + ) + + t.origins = make([]string, len(c.ServerBlockKeys)) + for i := range c.ServerBlockKeys { + t.origins[i] = plugin.Host(c.ServerBlockKeys[i]).Normalize() + } + + for c.Next() { + args := c.RemainingArgs() + if len(args) < 1 { + return nil, c.ArgErr() + } + toHosts, err = parse.HostPortOrFile(args...) + if err != nil { + return nil, err + } + for i := range toHosts { + if !strings.HasPrefix(toHosts[i], transport.GRPC+"://") { + return nil, fmt.Errorf("not a %s scheme: %s", transport.GRPC, toHosts[i]) + } + // now cut the prefix off again, because the dialler needs to see normal address strings. All this + // grpc:// stuff is to enforce uniform across plugins and future proofing for other protocols. + toHosts[i] = toHosts[i][len(transport.GRPC+"://"):] + } + for c.NextBlock() { + switch c.Val() { + case "id": + args := c.RemainingArgs() + if len(args) != 1 { + return nil, c.ArgErr() + } + node = args[0] + case "tls": + args := c.RemainingArgs() + if len(args) > 3 { + return nil, c.ArgErr() + } + + tlsConfig, err = pkgtls.NewTLSConfigFromArgs(args...) + if err != nil { + return nil, err + } + case "tls_servername": + if !c.NextArg() { + return nil, c.ArgErr() + } + tlsServerName = c.Val() + default: + return nil, c.Errf("unknown property '%s'", c.Val()) + } + } + } + + opts := []grpc.DialOption{grpc.WithInsecure()} + if tlsConfig != nil { + if tlsServerName != "" { + tlsConfig.ServerName = tlsServerName + } + opts = []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} + } + + // TODO: only the first host is used, need to figure out how to reconcile multiple upstream providers. + if t.c, err = xds.New(toHosts[0], node, opts...); err != nil { + return nil, err + } + + return t, nil +} diff --git a/plugin/traffic/setup_test.go b/plugin/traffic/setup_test.go new file mode 100644 index 000000000..99403fbcd --- /dev/null +++ b/plugin/traffic/setup_test.go @@ -0,0 +1,53 @@ +package traffic + +import ( + "testing" + + "github.com/caddyserver/caddy" +) + +func TestSetup(t *testing.T) { + /* + c := caddy.NewTestController("dns", `traffic grpc://bla`) + if err := setup(c); err != nil { + t.Fatalf("Test 1, expected no errors, but got: %q", err) + } + */ +} + +func TestParseTraffic(t *testing.T) { + tests := []struct { + input string + shouldErr bool + }{ + // ok + {`traffic grpc://127.0.0.1:18000 { + id test-id + }`, false}, + + // fail + {`traffic`, true}, + {`traffic tls://1.1.1.1`, true}, + {`traffic { + id bla bla + }`, true}, + {`traffic { + node + }`, true}, + } + for i, test := range tests { + c := caddy.NewTestController("dns", test.input) + _, err := parseTraffic(c) + if test.shouldErr && err == nil { + t.Errorf("Test %v: Expected error but found nil", i) + continue + } else if !test.shouldErr && err != nil { + t.Errorf("Test %v: Expected no error but found error: %v", i, err) + continue + } + + if test.shouldErr { + continue + } + } +} diff --git a/plugin/traffic/traffic.go b/plugin/traffic/traffic.go new file mode 100644 index 000000000..0090bd780 --- /dev/null +++ b/plugin/traffic/traffic.go @@ -0,0 +1,93 @@ +package traffic + +import ( + "context" + "strings" + "time" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/pkg/dnsutil" + "github.com/coredns/coredns/plugin/traffic/xds" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +// Traffic is a plugin that load balances according to assignments. +type Traffic struct { + c *xds.Client + id string + origins []string + + Next plugin.Handler +} + +// ServeDNS implements the plugin.Handler interface. +func (t *Traffic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := request.Request{Req: r, W: w} + + cluster := "" + for _, o := range t.origins { + if strings.HasSuffix(state.Name(), o) { + cluster, _ = dnsutil.TrimZone(state.Name(), o) + state.Zone = o + break + } + } + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + + addr, ok := t.c.Select(cluster) + if !ok { + m.Ns = soa(state.Zone) + m.Rcode = dns.RcodeNameError + w.WriteMsg(m) + return 0, nil + } + + if addr == nil { + log.Debugf("No (healthy) endpoints found for %q", cluster) + m.Ns = soa(state.Zone) + w.WriteMsg(m) + return 0, nil + } + + switch state.QType() { + case dns.TypeA: + if addr.To4() == nil { // it's an IPv6 address, return nodata in that case. + m.Ns = soa(state.Zone) + break + } + m.Answer = []dns.RR{&dns.A{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, A: addr}} + + case dns.TypeAAAA: + if addr.To4() != nil { // it's an IPv4 address, return nodata in that case. + m.Ns = soa(state.Zone) + break + } + m.Answer = []dns.RR{&dns.AAAA{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, AAAA: addr}} + default: + m.Ns = soa(state.Zone) + } + + w.WriteMsg(m) + return 0, nil +} + +// soa returns a synthetic so for this zone. +func soa(z string) []dns.RR { + return []dns.RR{&dns.SOA{ + Hdr: dns.RR_Header{Name: z, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 5}, + Ns: dnsutil.Join("ns", z), + Mbox: dnsutil.Join("coredns", z), + Serial: uint32(time.Now().UTC().Unix()), + Refresh: 14400, + Retry: 3600, + Expire: 604800, + Minttl: 5, + }} +} + +// Name implements the plugin.Handler interface. +func (t *Traffic) Name() string { return "traffic" } diff --git a/plugin/traffic/traffic_test.go b/plugin/traffic/traffic_test.go new file mode 100644 index 000000000..5db7cc451 --- /dev/null +++ b/plugin/traffic/traffic_test.go @@ -0,0 +1,130 @@ +package traffic + +import ( + "context" + "testing" + + "github.com/coredns/coredns/plugin/pkg/dnstest" + "github.com/coredns/coredns/plugin/pkg/dnsutil" + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/plugin/traffic/xds" + + xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" + corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint" + "github.com/miekg/dns" + "google.golang.org/grpc" +) + +func TestTraffic(t *testing.T) { + c, err := xds.New("127.0.0.1:0", "test-id", grpc.WithInsecure()) + if err != nil { + t.Fatal(err) + } + tr := &Traffic{c: c, origins: []string{"lb.example.org."}} + + tests := []struct { + cla *xdspb.ClusterLoadAssignment + cluster string + qtype uint16 + rcode int + answer string // address value of the A/AAAA record. + ns bool // should there be a ns section. + }{ + { + cla: &xdspb.ClusterLoadAssignment{}, + cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true, + }, + { + cla: &xdspb.ClusterLoadAssignment{}, + cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, ns: true, + }, + { + cla: &xdspb.ClusterLoadAssignment{}, + cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true}, + { + cla: &xdspb.ClusterLoadAssignment{ + ClusterName: "web", + Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", corepb.HealthStatus_HEALTHY}}), + }, + cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.1", + }, + { + cla: &xdspb.ClusterLoadAssignment{ + ClusterName: "web", + Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", corepb.HealthStatus_UNKNOWN}}), + }, + cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true, + }, + } + + ctx := context.TODO() + + for i, tc := range tests { + a := xds.NewAssignment() + a.SetClusterLoadAssignment("web", tc.cla) // web is our cluster + c.SetAssignments(a) + + m := new(dns.Msg) + cl := dnsutil.Join(tc.cluster, tr.origins[0]) + m.SetQuestion(cl, tc.qtype) + + rec := dnstest.NewRecorder(&test.ResponseWriter{}) + _, err := tr.ServeDNS(ctx, rec, m) + if err != nil { + t.Errorf("Test %d: Expected no error, but got %q", i, err) + } + if rec.Msg.Rcode != tc.rcode { + t.Errorf("Test %d: Expected no rcode %d, but got %d", i, tc.rcode, rec.Msg.Rcode) + } + if tc.ns && len(rec.Msg.Ns) == 0 { + t.Errorf("Test %d: Expected authority section, but got none", i) + } + if tc.answer != "" && len(rec.Msg.Answer) == 0 { + t.Fatalf("Test %d: Expected answer section, but got none", i) + } + if tc.answer != "" { + record := rec.Msg.Answer[0] + addr := "" + switch x := record.(type) { + case *dns.A: + addr = x.A.String() + case *dns.AAAA: + addr = x.AAAA.String() + } + if tc.answer != addr { + t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.answer, addr) + } + + } + + } +} + +type EndpointHealth struct { + Address string + Health corepb.HealthStatus +} + +func endpoints(e []EndpointHealth) []*endpointpb.LocalityLbEndpoints { + ep := make([]*endpointpb.LocalityLbEndpoints, len(e)) + for i := range e { + ep[i] = &endpointpb.LocalityLbEndpoints{ + LbEndpoints: []*endpointpb.LbEndpoint{{ + HostIdentifier: &endpointpb.LbEndpoint_Endpoint{ + Endpoint: &endpointpb.Endpoint{ + Address: &corepb.Address{ + Address: &corepb.Address_SocketAddress{ + SocketAddress: &corepb.SocketAddress{ + Address: e[i].Address, + }, + }, + }, + }, + }, + HealthStatus: e[i].Health, + }}, + } + } + return ep +} diff --git a/plugin/traffic/xds/assignment.go b/plugin/traffic/xds/assignment.go new file mode 100644 index 000000000..c8d1a5798 --- /dev/null +++ b/plugin/traffic/xds/assignment.go @@ -0,0 +1,116 @@ +package xds + +import ( + "math/rand" + "net" + "sync" + + xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" + corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" +) + +type assignment struct { + mu sync.RWMutex + cla map[string]*xdspb.ClusterLoadAssignment +} + +// NewAssignment returns a pointer to an assignment. +func NewAssignment() *assignment { + return &assignment{cla: make(map[string]*xdspb.ClusterLoadAssignment)} +} + +// SetClusterLoadAssignment sets the assignment for the cluster to cla. +func (a *assignment) SetClusterLoadAssignment(cluster string, cla *xdspb.ClusterLoadAssignment) { + // If cla is nil we just found a cluster, check if we already know about it, or if we need to make a new entry. + a.mu.Lock() + defer a.mu.Unlock() + _, ok := a.cla[cluster] + if !ok { + a.cla[cluster] = cla + return + } + if cla == nil { + return + } + a.cla[cluster] = cla + +} + +// ClusterLoadAssignment returns the assignment for the cluster or nil if there is none. +func (a *assignment) ClusterLoadAssignment(cluster string) *xdspb.ClusterLoadAssignment { + a.mu.RLock() + cla, ok := a.cla[cluster] + a.mu.RUnlock() + if !ok { + return nil + } + return cla +} + +func (a *assignment) clusters() []string { + a.mu.RLock() + defer a.mu.RUnlock() + clusters := make([]string, len(a.cla)) + i := 0 + for k := range a.cla { + clusters[i] = k + i++ + } + return clusters +} + +// Select selects a backend from cluster load assignments, using weighted random selection. It only selects +// backends that are reporting healthy. +func (a *assignment) Select(cluster string) (net.IP, bool) { + cla := a.ClusterLoadAssignment(cluster) + if cla == nil { + return nil, false + } + + total := 0 + healthy := 0 + for _, ep := range cla.Endpoints { + for _, lb := range ep.GetLbEndpoints() { + if lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY { + continue + } + total += int(lb.GetLoadBalancingWeight().GetValue()) + healthy++ + } + } + if healthy == 0 { + return nil, true + } + + if total == 0 { + // all weights are 0, randomly select one of the endpoints. + r := rand.Intn(healthy) + i := 0 + for _, ep := range cla.Endpoints { + for _, lb := range ep.GetLbEndpoints() { + if lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY { + continue + } + if r == i { + return net.ParseIP(lb.GetEndpoint().GetAddress().GetSocketAddress().GetAddress()), true + } + i++ + } + } + return nil, true + } + + r := rand.Intn(total) + 1 + for _, ep := range cla.Endpoints { + for _, lb := range ep.GetLbEndpoints() { + if lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY { + continue + } + r -= int(lb.GetLoadBalancingWeight().GetValue()) + if r <= 0 { + return net.ParseIP(lb.GetEndpoint().GetAddress().GetSocketAddress().GetAddress()), true + } + } + } + return nil, true +} diff --git a/plugin/traffic/xds/client.go b/plugin/traffic/xds/client.go new file mode 100644 index 000000000..5b3d736a5 --- /dev/null +++ b/plugin/traffic/xds/client.go @@ -0,0 +1,227 @@ +/* +This package contains code copied from github.com/grpc/grpc-co. The license for that code is: + +Copyright 2019 gRPC authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package xds implements a bidirectional stream to an envoy ADS management endpoint. It will stream +// updates (CDS and EDS) from there to help load balance responses to DNS clients. +package xds + +import ( + "context" + "fmt" + "net" + "os" + "sync" + "time" + + "github.com/coredns/coredns/coremain" + clog "github.com/coredns/coredns/plugin/pkg/log" + + xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" + corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" + "github.com/golang/protobuf/ptypes" + structpb "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/grpc" +) + +var log = clog.NewWithPlugin("traffic: xds") + +const ( + cdsURL = "type.googleapis.com/envoy.api.v2.Cluster" + edsURL = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment" +) + +type adsStream adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient + +// Client talks to the grpc manager's endpoint to get load assignments. +type Client struct { + cc *grpc.ClientConn + ctx context.Context + assignments *assignment // assignments contains the current clusters and endpoints. + node *corepb.Node + cancel context.CancelFunc + stop chan struct{} + mu sync.RWMutex + + version map[string]string + nonce map[string]string +} + +// New returns a new client that's dialed to addr using node as the local identifier. +func New(addr, node string, opts ...grpc.DialOption) (*Client, error) { + cc, err := grpc.Dial(addr, opts...) + if err != nil { + return nil, err + } + hostname, _ := os.Hostname() + c := &Client{cc: cc, node: &corepb.Node{Id: node, + Metadata: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "HOSTNAME": { + Kind: &structpb.Value_StringValue{StringValue: hostname}, + }, + }, + }, + BuildVersion: coremain.CoreVersion, + }, + } + c.assignments = &assignment{cla: make(map[string]*xdspb.ClusterLoadAssignment)} + c.version, c.nonce = make(map[string]string), make(map[string]string) + c.ctx, c.cancel = context.WithCancel(context.Background()) + + return c, nil +} + +// Stop stops all goroutines and closes the connection to the upstream manager. +func (c *Client) Stop() error { c.cancel(); return c.cc.Close() } + +// Run starts all goroutines and gathers the clusters and endpoint information from the upstream manager. +func (c *Client) Run() { + for { + select { + case <-c.ctx.Done(): + return + default: + } + + cli := adsgrpc.NewAggregatedDiscoveryServiceClient(c.cc) + stream, err := cli.StreamAggregatedResources(c.ctx) + if err != nil { + log.Debug(err) + time.Sleep(2 * time.Second) // grpc's client.go does more spiffy exp. backoff, do we really need that? + continue + } + + done := make(chan struct{}) + go func() { + if err := c.clusterDiscovery(stream, c.Version(cdsURL), c.Nonce(cdsURL), []string{}); err != nil { + log.Debug(err) + } + tick := time.NewTicker(10 * time.Second) + for { + select { + case <-tick.C: + // send empty list for cluster discovery every 10 seconds + if err := c.clusterDiscovery(stream, c.Version(cdsURL), c.Nonce(cdsURL), []string{}); err != nil { + log.Debug(err) + } + + case <-done: + tick.Stop() + return + } + } + }() + + if err := c.Receive(stream); err != nil { + log.Warning(err) + } + close(done) + } +} + +// clusterDiscovery sends a cluster DiscoveryRequest on the stream. +func (c *Client) clusterDiscovery(stream adsStream, version, nonce string, clusters []string) error { + req := &xdspb.DiscoveryRequest{ + Node: c.node, + TypeUrl: cdsURL, + ResourceNames: clusters, // empty for all + VersionInfo: version, + ResponseNonce: nonce, + } + return stream.Send(req) +} + +// endpointDiscovery sends a endpoint DiscoveryRequest on the stream. +func (c *Client) endpointDiscovery(stream adsStream, version, nonce string, clusters []string) error { + req := &xdspb.DiscoveryRequest{ + Node: c.node, + TypeUrl: edsURL, + ResourceNames: clusters, + VersionInfo: version, + ResponseNonce: nonce, + } + return stream.Send(req) +} + +// Receive receives from the stream, it handled both cluster and endpoint DiscoveryResponses. +func (c *Client) Receive(stream adsStream) error { + for { + resp, err := stream.Recv() + if err != nil { + return err + } + + switch resp.GetTypeUrl() { + case cdsURL: + a := NewAssignment() + for _, r := range resp.GetResources() { + var any ptypes.DynamicAny + if err := ptypes.UnmarshalAny(r, &any); err != nil { + log.Debugf("Failed to unmarshal cluster discovery: %s", err) + continue + } + cluster, ok := any.Message.(*xdspb.Cluster) + if !ok { + continue + } + a.SetClusterLoadAssignment(cluster.GetName(), nil) + } + log.Debugf("Cluster discovery processed with %d resources, version %q and nonce %q, clusters: %v", len(resp.GetResources()), c.Version(cdsURL), c.Nonce(cdsURL), a.clusters()) + // set our local administration and ack the reply. Empty version would signal NACK. + c.SetNonce(cdsURL, resp.GetNonce()) + c.SetVersion(cdsURL, resp.GetVersionInfo()) + c.SetAssignments(a) + c.clusterDiscovery(stream, resp.GetVersionInfo(), resp.GetNonce(), a.clusters()) + + // now kick off discovery for endpoints + if err := c.endpointDiscovery(stream, c.Version(edsURL), c.Nonce(edsURL), a.clusters()); err != nil { + log.Debug(err) + } + case edsURL: + for _, r := range resp.GetResources() { + var any ptypes.DynamicAny + if err := ptypes.UnmarshalAny(r, &any); err != nil { + log.Debugf("Failed to unmarshal endpoint discovery: %s", err) + continue + } + cla, ok := any.Message.(*xdspb.ClusterLoadAssignment) + if !ok { + continue + } + c.assignments.SetClusterLoadAssignment(cla.GetClusterName(), cla) + } + log.Debugf("Endpoint discovery processed with %d resources, version %q and nonce %q, clusters: %v", len(resp.GetResources()), c.Version(edsURL), c.Nonce(edsURL), c.assignments.clusters()) + // set our local administration and ack the reply. Empty version would signal NACK. + c.SetNonce(edsURL, resp.GetNonce()) + c.SetVersion(edsURL, resp.GetVersionInfo()) + + default: + return fmt.Errorf("unknown response URL for discovery: %q", resp.GetTypeUrl()) + } + } +} + +// Select returns an address that is deemed to be the correct one for this cluster. The returned +// boolean indicates if the cluster exists. +func (c *Client) Select(cluster string) (net.IP, bool) { + if cluster == "" { + return nil, false + } + return c.assignments.Select(cluster) +} diff --git a/plugin/traffic/xds/fields.go b/plugin/traffic/xds/fields.go new file mode 100644 index 000000000..9dac60cbe --- /dev/null +++ b/plugin/traffic/xds/fields.go @@ -0,0 +1,37 @@ +package xds + +func (c *Client) Assignments() *assignment { + c.mu.RLock() + defer c.mu.RUnlock() + return c.assignments +} + +func (c *Client) SetAssignments(a *assignment) { + c.mu.Lock() + defer c.mu.Unlock() + c.assignments = a +} + +func (c *Client) Version(typeURL string) string { + c.mu.RLock() + defer c.mu.RUnlock() + return c.version[typeURL] +} + +func (c *Client) SetVersion(typeURL, a string) { + c.mu.Lock() + defer c.mu.Unlock() + c.version[typeURL] = a +} + +func (c *Client) Nonce(typeURL string) string { + c.mu.RLock() + defer c.mu.RUnlock() + return c.nonce[typeURL] +} + +func (c *Client) SetNonce(typeURL, n string) { + c.mu.Lock() + defer c.mu.Unlock() + c.nonce[typeURL] = n +} From c7dcd633e02ac8f700d252d9c4acb693a1f35844 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 17 Jan 2020 16:51:19 +0100 Subject: [PATCH 12/13] more tests Signed-off-by: Miek Gieben --- plugin/traffic/traffic_test.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugin/traffic/traffic_test.go b/plugin/traffic/traffic_test.go index 5db7cc451..7f1fb2648 100644 --- a/plugin/traffic/traffic_test.go +++ b/plugin/traffic/traffic_test.go @@ -41,7 +41,9 @@ func TestTraffic(t *testing.T) { }, { cla: &xdspb.ClusterLoadAssignment{}, - cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true}, + cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true, + }, + // healthy backend { cla: &xdspb.ClusterLoadAssignment{ ClusterName: "web", @@ -49,6 +51,7 @@ func TestTraffic(t *testing.T) { }, cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.1", }, + // unknown backend { cla: &xdspb.ClusterLoadAssignment{ ClusterName: "web", @@ -56,6 +59,17 @@ func TestTraffic(t *testing.T) { }, cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true, }, + // unknown backend and healthy backend + { + cla: &xdspb.ClusterLoadAssignment{ + ClusterName: "web", + Endpoints: endpoints([]EndpointHealth{ + {"127.0.0.1", corepb.HealthStatus_UNKNOWN}, + {"127.0.0.2", corepb.HealthStatus_HEALTHY}, + }), + }, + cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2", + }, } ctx := context.TODO() From d21efd17cb4c61f281782681cd4167baa7ca1e11 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 17 Jan 2020 17:24:35 +0100 Subject: [PATCH 13/13] update hacking text Signed-off-by: Miek Gieben --- plugin/traffic/HACKING.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/plugin/traffic/HACKING.md b/plugin/traffic/HACKING.md index 5fd2faf0d..d17a52259 100644 --- a/plugin/traffic/HACKING.md +++ b/plugin/traffic/HACKING.md @@ -43,16 +43,8 @@ example.org { Start CoreDNS (`coredns -conf Corefile -dns.port=1053`), and see logging/debugging flow by; the test binary should also spew out a bunch of things. CoreDNS willl build up a list of cluster and -endpoints. Next you can query it: - -~~~ sh -% dig @localhost -p 1053 cluster-v0-0.example.org A -;; QUESTION SECTION: -;cluster-v0-0.example.org. IN A - -;; ANSWER SECTION: -cluster-v0-0.example.org. 5 IN A 127.0.0.1 -~~~ +endpoints. Next you can query it. Note none of the endpoints are HEALTHY so you'll mostly get NODATA +responses, instead of actual records. Note: the xds/test binary is a go-control-plane binary with added debugging that I'm using for testing.