Merge pull request #2343 from vmware/vsphere-vendor-code

Add vSphere vendor codes
This commit is contained in:
Justin Santa Barbara 2017-04-17 23:22:00 -05:00 committed by GitHub
commit 0294a90f6a
864 changed files with 198795 additions and 0 deletions

12
.gitmodules vendored
View File

@ -274,3 +274,15 @@
[submodule "_vendor/github.com/golang/protobuf"]
path = _vendor/github.com/golang/protobuf
url = https://github.com/golang/protobuf
[submodule "_vendor/github.com/vmware/govmomi"]
path = _vendor/github.com/vmware/govmomi
url = https://github.com/vmware/govmomi.git
[submodule "_vendor/github.com/coreos/go-semver"]
path = _vendor/github.com/coreos/go-semver
url = https://github.com/coreos/go-semver.git
[submodule "_vendor/github.com/miekg/coredns"]
path = _vendor/github.com/miekg/coredns
url = https://github.com/miekg/coredns.git
[submodule "_vendor/github.com/miekg/dns"]
path = _vendor/github.com/miekg/dns
url = https://github.com/miekg/dns.git

1
_vendor/github.com/coreos/go-semver generated Submodule

@ -0,0 +1 @@
Subproject commit 5e3acbb5668c4c3deb4842615c4098eb61fb6b1e

1
_vendor/github.com/miekg/coredns generated Submodule

@ -0,0 +1 @@
Subproject commit 757f49d8ff2687d289468b00835f360614357252

1
_vendor/github.com/miekg/dns generated Submodule

@ -0,0 +1 @@
Subproject commit 25ac7f171497271bc74ad3c6b5e1f86b4bab54fa

1
_vendor/github.com/vmware/govmomi generated Submodule

@ -0,0 +1 @@
Subproject commit 2d7d7b3702fddc23a76ac283c3a3d56bb0375e62

8
vendor/github.com/coreos/go-semver/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,8 @@
language: go
sudo: false
go:
- 1.4
- 1.5
- 1.6
- tip
script: cd semver && go test

202
vendor/github.com/coreos/go-semver/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

28
vendor/github.com/coreos/go-semver/README.md generated vendored Normal file
View File

@ -0,0 +1,28 @@
# go-semver - Semantic Versioning Library
[![Build Status](https://travis-ci.org/coreos/go-semver.svg?branch=master)](https://travis-ci.org/coreos/go-semver)
[![GoDoc](https://godoc.org/github.com/coreos/go-semver/semver?status.svg)](https://godoc.org/github.com/coreos/go-semver/semver)
go-semver is a [semantic versioning][semver] library for Go. It lets you parse
and compare two semantic version strings.
[semver]: http://semver.org/
## Usage
```go
vA := semver.New("1.2.3")
vB := semver.New("3.2.1")
fmt.Printf("%s < %s == %t\n", vA, vB, vA.LessThan(*vB))
```
## Example Application
```
$ go run example.go 1.2.3 3.2.1
1.2.3 < 3.2.1 == true
$ go run example.go 5.2.3 3.2.1
5.2.3 < 3.2.1 == false
```

20
vendor/github.com/coreos/go-semver/example.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"fmt"
"github.com/coreos/go-semver/semver"
"os"
)
func main() {
vA, err := semver.NewVersion(os.Args[1])
if err != nil {
fmt.Println(err.Error())
}
vB, err := semver.NewVersion(os.Args[2])
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%s < %s == %t\n", vA, vB, vA.LessThan(*vB))
}

275
vendor/github.com/coreos/go-semver/semver/semver.go generated vendored Normal file
View File

@ -0,0 +1,275 @@
// Copyright 2013-2015 CoreOS, Inc.
//
// 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.
// Semantic Versions http://semver.org
package semver
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
)
type Version struct {
Major int64
Minor int64
Patch int64
PreRelease PreRelease
Metadata string
}
type PreRelease string
func splitOff(input *string, delim string) (val string) {
parts := strings.SplitN(*input, delim, 2)
if len(parts) == 2 {
*input = parts[0]
val = parts[1]
}
return val
}
func New(version string) *Version {
return Must(NewVersion(version))
}
func NewVersion(version string) (*Version, error) {
v := Version{}
if err := v.Set(version); err != nil {
return nil, err
}
return &v, nil
}
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Set parses and updates v from the given version string. Implements flag.Value
func (v *Version) Set(version string) error {
metadata := splitOff(&version, "+")
preRelease := PreRelease(splitOff(&version, "-"))
dotParts := strings.SplitN(version, ".", 3)
if len(dotParts) != 3 {
return fmt.Errorf("%s is not in dotted-tri format", version)
}
parsed := make([]int64, 3, 3)
for i, v := range dotParts[:3] {
val, err := strconv.ParseInt(v, 10, 64)
parsed[i] = val
if err != nil {
return err
}
}
v.Metadata = metadata
v.PreRelease = preRelease
v.Major = parsed[0]
v.Minor = parsed[1]
v.Patch = parsed[2]
return nil
}
func (v Version) String() string {
var buffer bytes.Buffer
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.PreRelease != "" {
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
}
if v.Metadata != "" {
fmt.Fprintf(&buffer, "+%s", v.Metadata)
}
return buffer.String()
}
func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
var data string
if err := unmarshal(&data); err != nil {
return err
}
return v.Set(data)
}
func (v Version) MarshalJSON() ([]byte, error) {
return []byte(`"` + v.String() + `"`), nil
}
func (v *Version) UnmarshalJSON(data []byte) error {
l := len(data)
if l == 0 || string(data) == `""` {
return nil
}
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("invalid semver string")
}
return v.Set(string(data[1 : l-1]))
}
// Compare tests if v is less than, equal to, or greater than versionB,
// returning -1, 0, or +1 respectively.
func (v Version) Compare(versionB Version) int {
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
return cmp
}
return preReleaseCompare(v, versionB)
}
// Equal tests if v is equal to versionB.
func (v Version) Equal(versionB Version) bool {
return v.Compare(versionB) == 0
}
// LessThan tests if v is less than versionB.
func (v Version) LessThan(versionB Version) bool {
return v.Compare(versionB) < 0
}
// Slice converts the comparable parts of the semver into a slice of integers.
func (v Version) Slice() []int64 {
return []int64{v.Major, v.Minor, v.Patch}
}
func (p PreRelease) Slice() []string {
preRelease := string(p)
return strings.Split(preRelease, ".")
}
func preReleaseCompare(versionA Version, versionB Version) int {
a := versionA.PreRelease
b := versionB.PreRelease
/* Handle the case where if two versions are otherwise equal it is the
* one without a PreRelease that is greater */
if len(a) == 0 && (len(b) > 0) {
return 1
} else if len(b) == 0 && (len(a) > 0) {
return -1
}
// If there is a prerelease, check and compare each part.
return recursivePreReleaseCompare(a.Slice(), b.Slice())
}
func recursiveCompare(versionA []int64, versionB []int64) int {
if len(versionA) == 0 {
return 0
}
a := versionA[0]
b := versionB[0]
if a > b {
return 1
} else if a < b {
return -1
}
return recursiveCompare(versionA[1:], versionB[1:])
}
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
// A larger set of pre-release fields has a higher precedence than a smaller set,
// if all of the preceding identifiers are equal.
if len(versionA) == 0 {
if len(versionB) > 0 {
return -1
}
return 0
} else if len(versionB) == 0 {
// We're longer than versionB so return 1.
return 1
}
a := versionA[0]
b := versionB[0]
aInt := false
bInt := false
aI, err := strconv.Atoi(versionA[0])
if err == nil {
aInt = true
}
bI, err := strconv.Atoi(versionB[0])
if err == nil {
bInt = true
}
// Numeric identifiers always have lower precedence than non-numeric identifiers.
if aInt && !bInt {
return -1
} else if !aInt && bInt {
return 1
}
// Handle Integer Comparison
if aInt && bInt {
if aI > bI {
return 1
} else if aI < bI {
return -1
}
}
// Handle String Comparison
if a > b {
return 1
} else if a < b {
return -1
}
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
}
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
func (v *Version) BumpMajor() {
v.Major += 1
v.Minor = 0
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
func (v *Version) BumpMinor() {
v.Minor += 1
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
func (v *Version) BumpPatch() {
v.Patch += 1
v.PreRelease = PreRelease("")
v.Metadata = ""
}

View File

@ -0,0 +1,371 @@
// Copyright 2013-2015 CoreOS, Inc.
//
// 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 semver
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"math/rand"
"reflect"
"testing"
"time"
"gopkg.in/yaml.v2"
)
type fixture struct {
GreaterVersion string
LesserVersion string
}
var fixtures = []fixture{
fixture{"0.0.0", "0.0.0-foo"},
fixture{"0.0.1", "0.0.0"},
fixture{"1.0.0", "0.9.9"},
fixture{"0.10.0", "0.9.0"},
fixture{"0.99.0", "0.10.0"},
fixture{"2.0.0", "1.2.3"},
fixture{"0.0.0", "0.0.0-foo"},
fixture{"0.0.1", "0.0.0"},
fixture{"1.0.0", "0.9.9"},
fixture{"0.10.0", "0.9.0"},
fixture{"0.99.0", "0.10.0"},
fixture{"2.0.0", "1.2.3"},
fixture{"0.0.0", "0.0.0-foo"},
fixture{"0.0.1", "0.0.0"},
fixture{"1.0.0", "0.9.9"},
fixture{"0.10.0", "0.9.0"},
fixture{"0.99.0", "0.10.0"},
fixture{"2.0.0", "1.2.3"},
fixture{"1.2.3", "1.2.3-asdf"},
fixture{"1.2.3", "1.2.3-4"},
fixture{"1.2.3", "1.2.3-4-foo"},
fixture{"1.2.3-5-foo", "1.2.3-5"},
fixture{"1.2.3-5", "1.2.3-4"},
fixture{"1.2.3-5-foo", "1.2.3-5-Foo"},
fixture{"3.0.0", "2.7.2+asdf"},
fixture{"3.0.0+foobar", "2.7.2"},
fixture{"1.2.3-a.10", "1.2.3-a.5"},
fixture{"1.2.3-a.b", "1.2.3-a.5"},
fixture{"1.2.3-a.b", "1.2.3-a"},
fixture{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"},
fixture{"1.0.0", "1.0.0-rc.1"},
fixture{"1.0.0-rc.2", "1.0.0-rc.1"},
fixture{"1.0.0-rc.1", "1.0.0-beta.11"},
fixture{"1.0.0-beta.11", "1.0.0-beta.2"},
fixture{"1.0.0-beta.2", "1.0.0-beta"},
fixture{"1.0.0-beta", "1.0.0-alpha.beta"},
fixture{"1.0.0-alpha.beta", "1.0.0-alpha.1"},
fixture{"1.0.0-alpha.1", "1.0.0-alpha"},
fixture{"1.2.3-rc.1-1-1hash", "1.2.3-rc.2"},
}
func TestCompare(t *testing.T) {
for _, v := range fixtures {
gt, err := NewVersion(v.GreaterVersion)
if err != nil {
t.Error(err)
}
lt, err := NewVersion(v.LesserVersion)
if err != nil {
t.Error(err)
}
if gt.LessThan(*lt) {
t.Errorf("%s should not be less than %s", gt, lt)
}
if gt.Equal(*lt) {
t.Errorf("%s should not be equal to %s", gt, lt)
}
if gt.Compare(*lt) <= 0 {
t.Errorf("%s should be greater than %s", gt, lt)
}
if !lt.LessThan(*gt) {
t.Errorf("%s should be less than %s", lt, gt)
}
if !lt.Equal(*lt) {
t.Errorf("%s should be equal to %s", lt, lt)
}
if lt.Compare(*gt) > 0 {
t.Errorf("%s should not be greater than %s", lt, gt)
}
}
}
func testString(t *testing.T, orig string, version *Version) {
if orig != version.String() {
t.Errorf("%s != %s", orig, version)
}
}
func TestString(t *testing.T) {
for _, v := range fixtures {
gt, err := NewVersion(v.GreaterVersion)
if err != nil {
t.Error(err)
}
testString(t, v.GreaterVersion, gt)
lt, err := NewVersion(v.LesserVersion)
if err != nil {
t.Error(err)
}
testString(t, v.LesserVersion, lt)
}
}
func shuffleStringSlice(src []string) []string {
dest := make([]string, len(src))
rand.Seed(time.Now().Unix())
perm := rand.Perm(len(src))
for i, v := range perm {
dest[v] = src[i]
}
return dest
}
func TestSort(t *testing.T) {
sortedVersions := []string{"1.0.0", "1.0.2", "1.2.0", "3.1.1"}
unsortedVersions := shuffleStringSlice(sortedVersions)
semvers := []*Version{}
for _, v := range unsortedVersions {
sv, err := NewVersion(v)
if err != nil {
t.Fatal(err)
}
semvers = append(semvers, sv)
}
Sort(semvers)
for idx, sv := range semvers {
if sv.String() != sortedVersions[idx] {
t.Fatalf("incorrect sort at index %v", idx)
}
}
}
func TestBumpMajor(t *testing.T) {
version, _ := NewVersion("1.0.0")
version.BumpMajor()
if version.Major != 2 {
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
}
version, _ = NewVersion("1.5.2")
version.BumpMajor()
if version.Minor != 0 && version.Patch != 0 {
t.Fatalf("bumping major on 1.5.2 resulted in %v", version)
}
version, _ = NewVersion("1.0.0+build.1-alpha.1")
version.BumpMajor()
if version.PreRelease != "" && version.Metadata != "" {
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
}
}
func TestBumpMinor(t *testing.T) {
version, _ := NewVersion("1.0.0")
version.BumpMinor()
if version.Major != 1 {
t.Fatalf("bumping minor on 1.0.0 resulted in %v", version)
}
if version.Minor != 1 {
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
}
version, _ = NewVersion("1.0.0+build.1-alpha.1")
version.BumpMinor()
if version.PreRelease != "" && version.Metadata != "" {
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
}
}
func TestBumpPatch(t *testing.T) {
version, _ := NewVersion("1.0.0")
version.BumpPatch()
if version.Major != 1 {
t.Fatalf("bumping minor on 1.0.0 resulted in %v", version)
}
if version.Minor != 0 {
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
}
if version.Patch != 1 {
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
}
version, _ = NewVersion("1.0.0+build.1-alpha.1")
version.BumpPatch()
if version.PreRelease != "" && version.Metadata != "" {
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
}
}
func TestMust(t *testing.T) {
tests := []struct {
versionStr string
version *Version
recov interface{}
}{
{
versionStr: "1.0.0",
version: &Version{Major: 1},
},
{
versionStr: "version number",
recov: errors.New("version number is not in dotted-tri format"),
},
}
for _, tt := range tests {
func() {
defer func() {
recov := recover()
if !reflect.DeepEqual(tt.recov, recov) {
t.Fatalf("incorrect panic for %q: want %v, got %v", tt.versionStr, tt.recov, recov)
}
}()
version := Must(NewVersion(tt.versionStr))
if !reflect.DeepEqual(tt.version, version) {
t.Fatalf("incorrect version for %q: want %+v, got %+v", tt.versionStr, tt.version, version)
}
}()
}
}
type fixtureJSON struct {
GreaterVersion *Version
LesserVersion *Version
}
func TestJSON(t *testing.T) {
fj := make([]fixtureJSON, len(fixtures))
for i, v := range fixtures {
var err error
fj[i].GreaterVersion, err = NewVersion(v.GreaterVersion)
if err != nil {
t.Fatal(err)
}
fj[i].LesserVersion, err = NewVersion(v.LesserVersion)
if err != nil {
t.Fatal(err)
}
}
fromStrings, err := json.Marshal(fixtures)
if err != nil {
t.Fatal(err)
}
fromVersions, err := json.Marshal(fj)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(fromStrings, fromVersions) {
t.Errorf("Expected: %s", fromStrings)
t.Errorf("Unexpected: %s", fromVersions)
}
fromJson := make([]fixtureJSON, 0, len(fj))
err = json.Unmarshal(fromStrings, &fromJson)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(fromJson, fj) {
t.Error("Expected: ", fj)
t.Error("Unexpected: ", fromJson)
}
}
func TestYAML(t *testing.T) {
document, err := yaml.Marshal(fixtures)
if err != nil {
t.Fatal(err)
}
expected := make([]fixtureJSON, len(fixtures))
for i, v := range fixtures {
var err error
expected[i].GreaterVersion, err = NewVersion(v.GreaterVersion)
if err != nil {
t.Fatal(err)
}
expected[i].LesserVersion, err = NewVersion(v.LesserVersion)
if err != nil {
t.Fatal(err)
}
}
fromYAML := make([]fixtureJSON, 0, len(fixtures))
err = yaml.Unmarshal(document, &fromYAML)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(fromYAML, expected) {
t.Error("Expected: ", expected)
t.Error("Unexpected: ", fromYAML)
}
}
func TestBadInput(t *testing.T) {
bad := []string{
"1.2",
"1.2.3x",
"0x1.3.4",
"-1.2.3",
"1.2.3.4",
}
for _, b := range bad {
if _, err := NewVersion(b); err == nil {
t.Error("Improperly accepted value: ", b)
}
}
}
func TestFlag(t *testing.T) {
v := Version{}
f := flag.NewFlagSet("version", flag.ContinueOnError)
f.Var(&v, "version", "set version")
if err := f.Set("version", "1.2.3"); err != nil {
t.Fatal(err)
}
if v.String() != "1.2.3" {
t.Errorf("Set wrong value %q", v)
}
}
func ExampleVersion_LessThan() {
vA := New("1.2.3")
vB := New("3.2.1")
fmt.Printf("%s < %s == %t\n", vA, vB, vA.LessThan(*vB))
// Output:
// 1.2.3 < 3.2.1 == true
}

38
vendor/github.com/coreos/go-semver/semver/sort.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2013-2015 CoreOS, Inc.
//
// 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 semver
import (
"sort"
)
type Versions []*Version
func (s Versions) Len() int {
return len(s)
}
func (s Versions) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Versions) Less(i, j int) bool {
return s[i].LessThan(*s[j])
}
// Sort sorts the given slice of Version
func Sort(versions []*Version) {
sort.Sort(Versions(versions))
}

7
vendor/github.com/miekg/coredns/.gitignore generated vendored Normal file
View File

@ -0,0 +1,7 @@
query.log
Corefile
*.swp
coredns
kubectl
go-test-tmpfile*
coverage.txt

49
vendor/github.com/miekg/coredns/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,49 @@
sudo: required
# Trusty distribution is much faster when sudo is required
dist: trusty
services:
- docker
language: go
go:
- 1.8
go_import_path: github.com/coredns/coredns
env:
- ETCD_VERSION=2.3.1 K8S_VERSION=1.3.7 KUBECTL="docker exec hyperkube /hyperkube kubectl" DNS_ARGUMENTS=""
# In the Travis VM-based build environment, IPv6 networking is not
# enabled by default. The sysctl operations below enable IPv6.
# IPv6 is needed by some of the CoreDNS test cases. The VM environment
# is needed to have access to sudo in the test environment. Sudo is
# needed to have docker in the test environment. Docker is needed to
# launch a kubernetes instance in the test environment.
# (Dependencies are fun! :) )
before_install:
- cat /proc/net/if_inet6
- uname -a
- sudo bash -c 'if [ `cat /proc/net/if_inet6 | wc -l` = "0" ]; then echo "Enabling IPv6" ; sysctl net.ipv6.conf.all.disable_ipv6=0 ; sysctl net.ipv6.conf.default.disable_ipv6=0 ; sysctl net.ipv6.conf.lo.disable_ipv6=0 ; fi'
- cat /proc/net/if_inet6
- env
before_script:
- docker run -d --net=host --name=etcd quay.io/coreos/etcd:v$ETCD_VERSION
- docker run -d --volume=/:/rootfs:ro --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:rw --volume=/var/lib/kubelet/:/var/lib/kubelet:rw --volume=/var/run:/var/run:rw --volume=`pwd`/.travis:/travis --net=host --pid=host --privileged --name=hyperkube gcr.io/google_containers/hyperkube-amd64:v$K8S_VERSION /hyperkube kubelet --containerized --hostname-override=127.0.0.1 --api-servers=http://localhost:8080 --config=/etc/kubernetes/manifests $DNS_ARGUMENTS --allow-privileged --v=2
# Wait until kubectl is ready
- for i in {1..10}; do $KUBECTL version && break || sleep 5; done
- $KUBECTL version
- $KUBECTL config set-cluster test-doc --server=http://localhost:8080
- $KUBECTL config set-context test-doc --cluster=test-doc
- $KUBECTL config use-context test-doc
# Wait until k8s is ready
- for i in {1..30}; do $KUBECTL get nodes && break || sleep 5; done
- $KUBECTL create -f /travis/kubernetes/dns-test.yaml
- docker ps -a
script:
- make coverage
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -0,0 +1,185 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-1
---
apiVersion: v1
kind: Namespace
metadata:
name: test-2
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: de-1-a
namespace: test-1
spec:
replicas: 1
template:
metadata:
labels:
app: app-1-a
spec:
containers:
- name: app-1-a-c
image: gcr.io/google_containers/pause-amd64:3.0
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: de-1-b
namespace: test-1
spec:
replicas: 1
template:
metadata:
labels:
app: app-1-b
spec:
containers:
- name: app-1-b-c
image: gcr.io/google_containers/pause-amd64:3.0
ports:
- containerPort: 80
name: http
protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: de-c
namespace: test-1
spec:
replicas: 1
template:
metadata:
labels:
app: app-c
spec:
containers:
- name: app-c-c
image: gcr.io/google_containers/pause-amd64:3.0
ports:
- containerPort: 1234
name: c-port
protocol: UDP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: de-c
namespace: test-2
spec:
replicas: 1
template:
metadata:
labels:
app: app-c
spec:
containers:
- name: app-c-c
image: gcr.io/google_containers/pause-amd64:3.0
ports:
- containerPort: 1234
name: c-port
protocol: UDP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: de-d1
namespace: test-1
spec:
replicas: 2
template:
metadata:
labels:
app: app-d
spec:
containers:
- name: app-d-c
image: gcr.io/google_containers/pause-amd64:3.0
ports:
- containerPort: 1234
name: c-port
protocol: UDP
---
apiVersion: v1
kind: Service
metadata:
name: svc-1-a
namespace: test-1
spec:
selector:
app: app-1-a
clusterIP: 10.0.0.100
ports:
- name: http
port: 80
protocol: TCP
- name: https
port: 443
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: svc-1-b
namespace: test-1
spec:
selector:
app: app-1-b
clusterIP: 10.0.0.110
ports:
- name: http
port: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: svc-c
namespace: test-1
spec:
selector:
app: app-c
clusterIP: 10.0.0.115
ports:
- name: c-port
port: 1234
protocol: UDP
---
apiVersion: v1
kind: Service
metadata:
name: svc-c
namespace: test-2
spec:
selector:
app: app-c
clusterIP: 10.0.0.120
ports:
- name: c-port
port: 1234
protocol: UDP
---
apiVersion: v1
kind: Service
metadata:
name: headless-svc
namespace: test-1
spec:
selector:
app: app-d
clusterIP: None
ports:
- name: c-port
port: 1234
protocol: UDP

43
vendor/github.com/miekg/coredns/CODE-OF-CONDUCT.md generated vendored Normal file
View File

@ -0,0 +1,43 @@
CNCF Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering
an open and welcoming community, we pledge to respect all people who contribute
through reporting issues, posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are not
aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers
commit themselves to fairly and consistently applying these principles to every aspect
of managing this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a CNCF project maintainer, Sarah Novotny <sarahnovotny@google.com>, and/or Dan Kohn <dan@linuxfoundation.org>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### CNCF Events Code of Conduct
CNCF events are governed by the Linux Foundation [Code of Conduct](http://events.linuxfoundation.org/events/cloudnativecon/attend/code-of-conduct) available on the event page. This is designed to be compatible with the above policy and also includes more details on responding to incidents.

87
vendor/github.com/miekg/coredns/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,87 @@
## Contributing to CoreDNS
Welcome! Our community focuses on helping others and making CoreDNS the best it
can be. We gladly accept contributions and encourage you to get involved!
### Bug reports
First, please [search this repository](https://github.com/coredns/coredns/search?q=&type=Issues&utf8=%E2%9C%93)
with a variety of keywords to ensure your bug is not already reported.
If not, [open an issue](https://github.com/coredns/coredns/issues) and answer the
questions so we can understand and reproduce the problematic behavior.
The burden is on you to convince us that it is actually a bug in CoreDNS. This is
easiest to do when you write clear, concise instructions so we can reproduce
the behavior (even if it seems obvious). The more detailed and specific you are,
the faster we will be able to help you. Check out
[How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind. :smile: Remember that CoreDNS comes at no cost to you, and you're
getting free help.
### Minor improvements and new tests
Submit [pull requests](https://github.com/coredns/coredns/pulls) at any time. Make
sure to write tests to assert your change is working properly and is thoroughly
covered.
### Proposals, suggestions, ideas, new features
First, please [search](https://github.com/coredns/coredns/search?q=&type=Issues&utf8=%E2%9C%93)
with a variety of keywords to ensure your suggestion/proposal is new.
If so, you may open either an issue or a pull request for discussion and
feedback.
The advantage of issues is that you don't have to spend time actually
implementing your idea, but you should still describe it thoroughly. The
advantage of a pull request is that we can immediately see the impact the change
will have on the project, what the code will look like, and how to improve it.
The disadvantage of pull requests is that they are unlikely to get accepted
without significant changes, or it may be rejected entirely. Don't worry, that
won't happen without an open discussion first.
If you are going to spend significant time implementing code for a pull request,
best to open an issue first and "claim" it and get feedback before you invest
a lot of time.
### Vulnerabilities
If you've found a vulnerability that is serious, please email me: <miek@miek.nl>.
If it's not a big deal, a pull request will probably be faster.
## Thank you
Thanks for your help! CoreDNS would not be what it is today without your contributions.
## Git Hook
We use `golint` and `go vet` as tools to warn use about things (noted golint is obnoxious sometimes,
but still helpful). Add the following script as a git `post-commit` in `.git/hooks/post-commit` and
make it executable.
~~~ sh
#!/bin/bash
# <https://git-scm.com/docs/githooks>:
# The script takes no parameters and its exit status does not affect the commit in any way. You can
# use git # rev-parse HEAD to get the new commits SHA1 hash, or you can use git log -l HEAD to get
# all of its # information.
for d in *; do
if [[ "$d" == "vendor" ]]; then
continue
fi
if [[ "$d" == "logo" ]]; then
continue
fi
if [[ ! -d "$d" ]]; then
continue
fi
golint "$d"/...
done
~~~

9
vendor/github.com/miekg/coredns/Dockerfile generated vendored Normal file
View File

@ -0,0 +1,9 @@
FROM alpine:latest
MAINTAINER Miek Gieben <miek@miek.nl> @miekg
RUN apk --update add bind-tools && rm -rf /var/cache/apk/*
ADD coredns /coredns
EXPOSE 53 53/udp
ENTRYPOINT ["/coredns"]

201
vendor/github.com/miekg/coredns/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

72
vendor/github.com/miekg/coredns/Makefile generated vendored Normal file
View File

@ -0,0 +1,72 @@
BUILD_VERBOSE := -v
TEST_VERBOSE := -v
all: coredns
# Phony this to ensure we always build the binary.
# TODO: Add .go file dependencies.
.PHONY: coredns
coredns: check
go build $(BUILD_VERBOSE) -ldflags="-s -w"
.PHONY: deps
deps: core/zmiddleware.go core/dnsserver/zdirectives.go
go get ${BUILD_VERBOSE}
go get -u github.com/golang/lint/golint
.PHONY: check
check: fmt deps
.PHONY: test
test: check
go test -race $(TEST_VERBOSE) ./test ./middleware/...
.PHONY: testk8s
testk8s: check
go test -race $(TEST_VERBOSE) -tags=k8s -run 'TestKubernetes' ./test ./middleware/kubernetes/...
.PHONY: coverage
coverage: check
set -e -x
echo "" > coverage.txt
for d in `go list ./... | grep -v vendor`; do \
go test $(TEST_VERBOSE) -tags 'etcd k8s' -race -coverprofile=cover.out -covermode=atomic -bench=. $$d || exit 1; \
if [ -f cover.out ]; then \
cat cover.out >> coverage.txt; \
rm cover.out; \
fi; \
done
.PHONY: clean
clean:
go clean
rm -f coredns
core/zmiddleware.go core/dnsserver/zdirectives.go: middleware.cfg
go generate coredns.go
.PHONY: gen
gen:
go generate coredns.go
.PHONY: fmt
fmt:
## run go fmt
@test -z "$$(gofmt -s -l . | grep -v vendor/ | tee /dev/stderr)" || \
(echo "please format Go code with 'gofmt -s -w'" && false)
.PHONY: lint
lint: deps
## run go lint, suggestion only (not enforced)
@test -z "$$(golint ./... | grep -v vendor/ | grep -v ".pb.go:" | grep -vE "context\.Context should be the first parameter of a function" | tee /dev/stderr)"
.PHONY: distclean
distclean: clean
# Clean all dependencies and build artifacts
find $(GOPATH)/pkg -maxdepth 1 -mindepth 1 | xargs rm -rf
find $(GOPATH)/bin -maxdepth 1 -mindepth 1 | xargs rm -rf
find $(GOPATH)/src -maxdepth 1 -mindepth 1 | grep -v github | xargs rm -rf
find $(GOPATH)/src -maxdepth 2 -mindepth 2 | grep -v miekg | xargs rm -rf
find $(GOPATH)/src/github.com/miekg -maxdepth 1 -mindepth 1 \! -name \*coredns\* | xargs rm -rf

89
vendor/github.com/miekg/coredns/Makefile.release generated vendored Normal file
View File

@ -0,0 +1,89 @@
# Makefile for releasing CoreDNS
#
# The release is controlled from coremain/version.go. The version found
# there is used to tag the git repo and to build the assets that are
# uploaded to github (after some sanity checks).
#
# The release should be accompanied by release notes published on
# blog.coredns.io. For example:
# https://blog.coredns.io/2016/09/18/coredns-001-release/
#
# We use https://github.com/progrium/gh-release to automate github stuff
# be sure to have that binary in your path.
#
# Get a list of authors for this release with:
#
# git log --pretty=format:'%an' v001..master | sort -u
# (where v001 is the previous release, obviously you'll need to adjust this)
#
# Steps:
# * Get an access token: https://help.github.com/articles/creating-an-access-token-for-command-line-use/
# * export GITHUB_ACCESS_TOKEN=<token>
# * Up the version in coremain/version.go
# * Run: make -f Makefile.release
# * will commit your change with 'Release $VERSION'
# * push to github
# * build the release and do all that fluff.
#
# Steps for docker
# * Login into docker: docker login (should have push creds for coredns registry)
# * Run: make -f Makefile.release docker
#
# Docker push should happen after you make the new release and uploaded it to Github.
NAME:=coredns
VERSION:=$(shell grep 'coreVersion' coremain/version.go | awk '{ print $$3 }' | tr -d '"')
ARCH:=$(shell uname -m)
GITHUB:=coredns
DOCKER:=coredns
DOCKER_IMAGE_NAME:=$(DOCKER)/$(NAME)
all: commit push build tar release
docker: docker-build docker-release
.PHONY: push
push:
@echo Pushing release to master
git push
.PHONY: commit
commit:
@echo Committing
git commit -am"Release $(VERSION)"
.PHONY: build
build:
@echo Building: $(VERSION)
mkdir -p build/Darwin && CGO_ENABLED=0 GOOS=darwin go build -ldflags="-s -w" -o build/Darwin/$(NAME)
mkdir -p build/Linux/Arm && CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="-s -w" -o build/Linux/Arm/$(NAME)
mkdir -p build/Linux && CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o build/Linux/$(NAME)
.PHONY: tar
tar:
rm -rf release && mkdir release
tar -zcf release/$(NAME)_$(VERSION)_linux_$(ARCH).tgz -C build/Linux $(NAME)
tar -zcf release/$(NAME)_$(VERSION)_linux_armv6l.tgz -C build/Linux/Arm $(NAME)
tar -zcf release/$(NAME)_$(VERSION)_darwin_$(ARCH).tgz -C build/Darwin $(NAME)
.PHONY: release
release:
@echo Releasing: $(VERSION)
gh-release create $(GITHUB)/$(NAME) $(VERSION)
.PHONY: docker-build
docker:
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w"
docker build -t $(DOCKER_IMAGE_NAME) .
docker tag $(DOCKER_IMAGE_NAME):latest $(DOCKER_IMAGE_NAME):$(VERSION)
.PHONY: docker-release
docker-release:
@echo Pushing: $(VERSION)
docker tag $(DOCKER_IMAGE_NAME):latest $(DOCKER_IMAGE_NAME):$(VERSION)
.PHONY: clean
clean:
rm -rf release
rm -rf build

226
vendor/github.com/miekg/coredns/README.md generated vendored Normal file
View File

@ -0,0 +1,226 @@
# CoreDNS
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/coredns/coredns)
[![Build Status](https://img.shields.io/travis/coredns/coredns.svg?style=flat-square&label=build)](https://travis-ci.org/coredns/coredns)
[![Code Coverage](https://img.shields.io/codecov/c/github/coredns/coredns/master.svg?style=flat-square)](https://codecov.io/github/coredns/coredns?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/coredns/coredns?style=flat-square)](https://goreportcard.com/report/coredns/coredns)
CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has
the same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type
plugin for Caddy.
CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception level project.
CoreDNS is the successor to [SkyDNS](https://github.com/skynetservices/skydns). SkyDNS is a thin
layer that exposes services in etcd in the DNS. CoreDNS builds on this idea and is a generic DNS
server that can talk to multiple backends (etcd, kubernetes, etc.).
CoreDNS aims to be a fast and flexible DNS server. The keyword here is *flexible*: with CoreDNS you
are able to do what you want with your DNS data. And if not: write some middleware!
CoreDNS can listen for DNS request coming in over UDP/TCP (go'old DNS), TLS ([RFC
7858](https://tools.ietf.org/html/rfc7858)) and gRPC (not a standard.
Currently CoreDNS is able to:
* Serve zone data from a file; both DNSSEC (NSEC only) and DNS are supported (*file*).
* Retrieve zone data from primaries, i.e., act as a secondary server (AXFR only) (*secondary*).
* Sign zone data on-the-fly (*dnssec*).
* Load balancing of responses (*loadbalance*).
* Allow for zone transfers, i.e., act as a primary server (*file*).
* Automatically load zone files from disk (*auto*)
* Caching (*cache*).
* Health checking endpoint (*health*).
* Use etcd as a backend, i.e., a 101.5% replacement for
[SkyDNS](https://github.com/skynetservices/skydns) (*etcd*).
* Use k8s (kubernetes) as a backend (*kubernetes*).
* Serve as a proxy to forward queries to some other (recursive) nameserver (*proxy*).
* Provide metrics (by using Prometheus) (*metrics*).
* Provide query (*log*) and error (*error*) logging.
* Support the CH class: `version.bind` and friends (*chaos*).
* Profiling support (*pprof*).
* Rewrite queries (qtype, qclass and qname) (*rewrite*).
* Echo back the IP address, transport and port number used (*whoami*).
Each of the middlewares has a README.md of its own.
## Status
CoreDNS can be used as an authoritative nameserver for your domains, and should be stable enough to
provide you with good DNS(SEC) service.
There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing
on making things fast and to reduce the memory usage.
All in all, CoreDNS should be able to provide you with enough functionality to replace parts of BIND
9, Knot, NSD or PowerDNS and SkyDNS. Most documentation is in the source and some blog articles can
be [found here](https://blog.coredns.io). If you do want to use CoreDNS in production, please
let us know and how we can help.
<https://caddyserver.com/> is also full of examples on how to structure a Corefile (renamed from
Caddyfile when forked).
## Compilation
CoreDNS (as a servertype plugin for Caddy) has a dependency on Caddy, but this is not different than
any other Go dependency. If you have the source of CoreDNS, get all dependencies:
go get ./...
And then `go build` as you would normally do:
go build
This should yield a `coredns` binary.
## Examples
When starting CoreDNS without any configuration, it loads the `whoami` middleware and starts
listening on port 53 (override with `-dns.port`), it should show the following:
~~~ txt
.:53
2016/09/18 09:20:50 [INFO] CoreDNS-001
CoreDNS-001
~~~
Any query send to port 53 should return some information; your sending address, port and protocol
used.
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:
~~~ txt
.: {
proxy . 8.8.8.8:53
log stdout
}
~~~
`./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.
`Corefile` contains:
~~~ txt
.:53 {
proxy . 8.8.8.8:53
log stdout
}
~~~
Just start CoreDNS: `./coredns`.
And 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.
Serve the (NSEC) DNSSEC-signed `example.org` on port 1053, with errors and logging sent to stdout.
Allow zone transfers to everybody, but specically mention 1 IP address so that CoreDNS can send
notifies to it.
~~~ txt
example.org:1053 {
file /var/lib/coredns/example.org.signed {
transfer to *
transfer to 2001:500:8f::53
}
errors stdout
log stdout
}
~~~
Serve `example.org` on port 1053, but forward everything that does *not* match `example.org` to a recursive
nameserver *and* rewrite ANY queries to HINFO.
~~~ txt
.:1053 {
rewrite ANY HINFO
proxy . 8.8.8.8:53
file /var/lib/coredns/example.org.signed example.org {
transfer to *
transfer to 2001:500:8f::53
}
errors stdout
log stdout
}
~~~
### Zone Specification
The following Corefile fragment is legal, but does not explicitly define a zone to listen on:
~~~ txt
{
# ...
}
~~~
This defaults to `.:53` (or whatever `-dns.port` is).
The next one only defines a port:
~~~ txt
:123 {
# ...
}
~~~
This defaults to the root zone `.`, but can't be overruled with the `-dns.port` flag.
Just specifying a zone, default to listening on port 53 (can still be overridden with `-dns.port`:
~~~ txt
example.org {
# ...
}
~~~
Listening on TLS and for gRPC? Use:
~~~ txt
tls://example.org grpc://example.org {
# ...
}
~~~
Specifying ports works in the same way:
~~~ txt
grpc://example.org:1443 {
# ...
}
~~~
When no transport protocol is specified the default `dns://` is assumed.
## Blog and Contact
Website: <https://coredns.io>
Twitter: [@corednsio](https://twitter.com/corednsio)
Docs: <https://miek.nl/tags/coredns/>
Github: <https://github.com/coredns/coredns>
## Systemd Service File
Use this as a systemd service file. It defaults to a coredns with a homedir of /home/coredns
and the binary lives in /opt/bin and the config in `/etc/coredns/Corefile`:
~~~ txt
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target
[Service]
PermissionsStartOnly=true
LimitNOFILE=8192
User=coredns
WorkingDirectory=/home/coredns
ExecStartPre=/sbin/setcap cap_net_bind_service=+ep /opt/bin/coredns
ExecStart=/opt/bin/coredns -conf=/etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
~~~

31
vendor/github.com/miekg/coredns/core/coredns.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
// Package core registers the server and all plugins we support.
package core
import (
// plug in the server
_ "github.com/coredns/coredns/core/dnsserver"
// plug in the standard directives (sorted)
_ "github.com/coredns/coredns/middleware/auto"
_ "github.com/coredns/coredns/middleware/bind"
_ "github.com/coredns/coredns/middleware/cache"
_ "github.com/coredns/coredns/middleware/chaos"
_ "github.com/coredns/coredns/middleware/dnssec"
_ "github.com/coredns/coredns/middleware/erratic"
_ "github.com/coredns/coredns/middleware/errors"
_ "github.com/coredns/coredns/middleware/etcd"
_ "github.com/coredns/coredns/middleware/file"
_ "github.com/coredns/coredns/middleware/health"
_ "github.com/coredns/coredns/middleware/kubernetes"
_ "github.com/coredns/coredns/middleware/loadbalance"
_ "github.com/coredns/coredns/middleware/log"
_ "github.com/coredns/coredns/middleware/metrics"
_ "github.com/coredns/coredns/middleware/pprof"
_ "github.com/coredns/coredns/middleware/proxy"
_ "github.com/coredns/coredns/middleware/reverse"
_ "github.com/coredns/coredns/middleware/rewrite"
_ "github.com/coredns/coredns/middleware/root"
_ "github.com/coredns/coredns/middleware/secondary"
_ "github.com/coredns/coredns/middleware/trace"
_ "github.com/coredns/coredns/middleware/whoami"
)

View File

@ -0,0 +1,89 @@
package dnsserver
import (
"fmt"
"net"
"strings"
"github.com/miekg/dns"
)
type zoneAddr struct {
Zone string
Port string
Transport string // dns, tls or grpc
}
// String return the string represenation of z.
func (z zoneAddr) String() string { return z.Transport + "://" + z.Zone + ":" + z.Port }
// Transport returns the protocol of the string s
func Transport(s string) string {
switch {
case strings.HasPrefix(s, TransportTLS+"://"):
return TransportTLS
case strings.HasPrefix(s, TransportDNS+"://"):
return TransportDNS
case strings.HasPrefix(s, TransportGRPC+"://"):
return TransportGRPC
}
return TransportDNS
}
// normalizeZone parses an zone string into a structured format with separate
// host, and port portions, as well as the original input string.
//
// TODO(miek): possibly move this to middleware/normalize.go
func normalizeZone(str string) (zoneAddr, error) {
var err error
// Default to DNS if there isn't a transport protocol prefix.
trans := TransportDNS
switch {
case strings.HasPrefix(str, TransportTLS+"://"):
trans = TransportTLS
str = str[len(TransportTLS+"://"):]
case strings.HasPrefix(str, TransportDNS+"://"):
trans = TransportDNS
str = str[len(TransportDNS+"://"):]
case strings.HasPrefix(str, TransportGRPC+"://"):
trans = TransportGRPC
str = str[len(TransportGRPC+"://"):]
}
host, port, err := net.SplitHostPort(str)
if err != nil {
host, port, err = net.SplitHostPort(str + ":")
// no error check here; return err at end of function
}
if len(host) > 255 { // TODO(miek): this should take escaping into account.
return zoneAddr{}, fmt.Errorf("specified zone is too long: %d > 255", len(host))
}
_, d := dns.IsDomainName(host)
if !d {
return zoneAddr{}, fmt.Errorf("zone is not a valid domain name: %s", host)
}
if port == "" {
if trans == TransportDNS {
port = Port
}
if trans == TransportTLS {
port = TLSPort
}
if trans == TransportGRPC {
port = GRPCPort
}
}
return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port, Transport: trans}, err
}
// Supported transports.
const (
TransportDNS = "dns"
TransportTLS = "tls"
TransportGRPC = "grpc"
)

View File

@ -0,0 +1,28 @@
package dnsserver
import "testing"
func TestNormalizeZone(t *testing.T) {
for i, test := range []struct {
input string
expected string
shouldErr bool
}{
{".", "dns://.:53", false},
{".:54", "dns://.:54", false},
{"..", "://:", true},
{"..", "://:", true},
} {
addr, err := normalizeZone(test.input)
actual := addr.String()
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
}
if actual != test.expected {
t.Errorf("Test %d: Expected %s but got %s", i, test.expected, actual)
}
}
}

View File

@ -0,0 +1,67 @@
package dnsserver
import (
"crypto/tls"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
)
// Config configuration for a single server.
type Config struct {
// The zone of the site.
Zone string
// The hostname to bind listener to, defaults to the wildcard address
ListenHost string
// The port to listen on.
Port string
// Root points to a base directory we we find user defined "things".
// First consumer is the file middleware to looks for zone files in this place.
Root string
// The transport we implement, normally just "dns" over TCP/UDP, but could be
// DNS-over-TLS or DNS-over-gRPC.
Transport string
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config
// Middleware stack.
Middleware []middleware.Middleware
// Compiled middleware stack.
middlewareChain middleware.Handler
}
// GetConfig gets the Config that corresponds to c.
// If none exist nil is returned.
func GetConfig(c *caddy.Controller) *Config {
ctx := c.Context().(*dnsContext)
if cfg, ok := ctx.keysToConfigs[c.Key]; ok {
return cfg
}
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs.
ctx.saveConfig(c.Key, &Config{})
return GetConfig(c)
}
// GetMiddleware returns the middleware handler that has been added to the config under name.
// This is useful to inspect if a certain middleware is active in this server.
// Note that this is order dependent and the order is defined in directives.go, i.e. if your middleware
// comes before the middleware you are checking; it will not be there (yet).
func GetMiddleware(c *caddy.Controller, name string) middleware.Handler {
conf := GetConfig(c)
for _, h := range conf.Middleware {
x := h(nil)
if name == x.Name() {
return x
}
}
return nil
}

View File

@ -0,0 +1,64 @@
package dnsserver
import (
"fmt"
"os"
"strings"
)
// RegisterDevDirective splices name into the list of directives
// immediately before another directive. This function is ONLY
// for plugin development purposes! NEVER use it for a plugin
// that you are not currently building. If before is empty,
// the directive will be appended to the end of the list.
//
// It is imperative that directives execute in the proper
// order, and hard-coding the list of directives guarantees
// a correct, absolute order every time. This function is
// convenient when developing a plugin, but it does not
// guarantee absolute ordering. Multiple plugins registering
// directives with this function will lead to non-
// deterministic builds and buggy software.
//
// Directive names must be lower-cased and unique. Any errors
// here are fatal, and even successful calls print a message
// to stdout as a reminder to use it only in development.
func RegisterDevDirective(name, before string) {
if name == "" {
fmt.Println("[FATAL] Cannot register empty directive name")
os.Exit(1)
}
if strings.ToLower(name) != name {
fmt.Printf("[FATAL] %s: directive name must be lowercase\n", name)
os.Exit(1)
}
for _, dir := range directives {
if dir == name {
fmt.Printf("[FATAL] %s: directive name already exists\n", name)
os.Exit(1)
}
}
if before == "" {
directives = append(directives, name)
} else {
var found bool
for i, dir := range directives {
if dir == before {
directives = append(directives[:i], append([]string{name}, directives[i:]...)...)
found = true
break
}
}
if !found {
fmt.Printf("[FATAL] %s: directive not found\n", before)
os.Exit(1)
}
}
msg := fmt.Sprintf("Registered directive '%s' ", name)
if before == "" {
msg += "at end of list"
} else {
msg += fmt.Sprintf("before '%s'", before)
}
fmt.Printf("[INFO] %s\n", msg)
}

View File

@ -0,0 +1,166 @@
package dnsserver
import (
"flag"
"fmt"
"net"
"time"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
)
const serverType = "dns"
// Any flags defined here, need to be namespaced to the serverType other
// wise they potentially clash with other server types.
func init() {
flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: func() []string { return directives },
DefaultInput: func() caddy.Input {
return caddy.CaddyfileInput{
Filepath: "Corefile",
Contents: []byte(".:" + Port + " {\nwhoami\n}\n"),
ServerTypeName: serverType,
}
},
NewContext: newContext,
})
}
func newContext() caddy.Context {
return &dnsContext{keysToConfigs: make(map[string]*Config)}
}
type dnsContext struct {
keysToConfigs map[string]*Config
// configs is the master list of all site configs.
configs []*Config
}
func (h *dnsContext) saveConfig(key string, cfg *Config) {
h.configs = append(h.configs, cfg)
h.keysToConfigs[key] = cfg
}
// InspectServerBlocks make sure that everything checks out before
// executing directives and otherwise prepares the directives to
// be parsed and executed.
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
// Normalize and check all the zone names and check for duplicates
dups := map[string]string{}
for _, s := range serverBlocks {
for i, k := range s.Keys {
za, err := normalizeZone(k)
if err != nil {
return nil, err
}
s.Keys[i] = za.String()
if v, ok := dups[za.String()]; ok {
return nil, fmt.Errorf("cannot serve %s - zone already defined for %v", za, v)
}
dups[za.String()] = za.String()
// Save the config to our master list, and key it for lookups
cfg := &Config{
Zone: za.Zone,
Port: za.Port,
Transport: za.Transport,
}
h.saveConfig(za.String(), cfg)
}
}
return serverBlocks, nil
}
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
// we must map (group) each config to a bind address
groups, err := groupConfigsByListenAddr(h.configs)
if err != nil {
return nil, err
}
// then we create a server for each group
var servers []caddy.Server
for addr, group := range groups {
// switch on addr
switch Transport(addr) {
case TransportDNS:
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
case TransportTLS:
s, err := NewServerTLS(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
case TransportGRPC:
s, err := NewServergRPC(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
}
}
return servers, nil
}
// AddMiddleware adds a middleware to a site's middleware stack.
func (c *Config) AddMiddleware(m middleware.Middleware) {
c.Middleware = append(c.Middleware, m)
}
// groupSiteConfigsByListenAddr groups site configs by their listen
// (bind) address, so sites that use the same listener can be served
// on the same server instance. The return value maps the listen
// address (what you pass into net.Listen) to the list of site configs.
// This function does NOT vet the configs to ensure they are compatible.
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
groups := make(map[string][]*Config)
for _, conf := range configs {
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Port))
if err != nil {
return nil, err
}
addrstr := conf.Transport + "://" + addr.String()
groups[addrstr] = append(groups[addrstr], conf)
}
return groups, nil
}
const (
// DefaultPort is the default port.
DefaultPort = "53"
// TLSPort is the default port for DNS-over-TLS.
TLSPort = "853"
// GRPCPort is the default port for DNS-over-gRPC.
GRPCPort = "443"
)
// These "soft defaults" are configurable by
// command line flags, etc.
var (
// Port is the port we listen on by default.
Port = DefaultPort
// GracefulTimeout is the maximum duration of a graceful shutdown.
GracefulTimeout time.Duration
)
var _ caddy.GracefulServer = new(Server)

View File

@ -0,0 +1,165 @@
package dnsserver
import (
"crypto/tls"
"errors"
"fmt"
"net"
"github.com/miekg/dns"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/peer"
"github.com/coredns/coredns/pb"
)
// servergRPC represents an instance of a DNS-over-gRPC server.
type servergRPC struct {
*Server
grpcServer *grpc.Server
listenAddr net.Addr
}
// NewGRPCServer returns a new CoreDNS GRPC server and compiles all middleware in to it.
func NewServergRPC(addr string, group []*Config) (*servergRPC, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
gs := &servergRPC{Server: s}
gs.grpcServer = grpc.NewServer()
// trace foo... TODO(miek)
pb.RegisterDnsServiceServer(gs.grpcServer, gs)
return gs, nil
}
// Serve implements caddy.TCPServer interface.
func (s *servergRPC) Serve(l net.Listener) error {
s.m.Lock()
s.listenAddr = l.Addr()
s.m.Unlock()
return s.grpcServer.Serve(l)
}
// ServePacket implements caddy.UDPServer interface.
func (s *servergRPC) ServePacket(p net.PacketConn) error { return nil }
// Listen implements caddy.TCPServer interface.
func (s *servergRPC) Listen() (net.Listener, error) {
// The *tls* middleware must make sure that multiple conflicting
// TLS configuration return an error: it can only be specified once.
tlsConfig := new(tls.Config)
for _, conf := range s.zones {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
var (
l net.Listener
err error
)
if tlsConfig == nil {
l, err = net.Listen("tcp", s.Addr[len(TransportGRPC+"://"):])
} else {
l, err = tls.Listen("tcp", s.Addr[len(TransportGRPC+"://"):], tlsConfig)
}
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *servergRPC) ListenPacket() (net.PacketConn, error) { return nil, nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *servergRPC) OnStartupComplete() {
if Quiet {
return
}
for zone, config := range s.zones {
fmt.Println(TransportGRPC + "://" + zone + ":" + config.Port)
}
}
func (s *servergRPC) Stop() (err error) {
s.m.Lock()
defer s.m.Unlock()
if s.grpcServer != nil {
s.grpcServer.GracefulStop()
}
return
}
// Query is the main entry-point into the gRPC server. From here we call ServeDNS like
// any normal server. We use a custom responseWriter to pick up the bytes we need to write
// back to the client as a protobuf.
func (s *servergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket, error) {
msg := new(dns.Msg)
err := msg.Unpack(in.Msg)
if err != nil {
return nil, err
}
p, ok := peer.FromContext(ctx)
if !ok {
return nil, errors.New("no peer in gRPC context")
}
a, ok := p.Addr.(*net.TCPAddr)
if !ok {
return nil, fmt.Errorf("no TCP peer in gRPC context: %v", p.Addr)
}
r := &net.IPAddr{IP: a.IP}
w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: r, Msg: msg}
s.ServeDNS(ctx, w, msg)
packed, err := w.Msg.Pack()
if err != nil {
return nil, err
}
return &pb.DnsPacket{Msg: packed}, nil
}
func (g *servergRPC) Shutdown() error {
if g.grpcServer != nil {
g.grpcServer.Stop()
}
return nil
}
type gRPCresponse struct {
localAddr net.Addr
remoteAddr net.Addr
Msg *dns.Msg
}
// Write is the hack that makes this work. It does not actually write the message
// but returns the bytes we need to to write in r. We can then pick this up in Query
// and write a proper protobuf back to the client.
func (r *gRPCresponse) Write(b []byte) (int, error) {
r.Msg = new(dns.Msg)
return len(b), r.Msg.Unpack(b)
}
// These methods implement the dns.ResponseWriter interface from Go DNS.
func (r *gRPCresponse) Close() error { return nil }
func (r *gRPCresponse) TsigStatus() error { return nil }
func (r *gRPCresponse) TsigTimersOnly(b bool) { return }
func (r *gRPCresponse) Hijack() { return }
func (r *gRPCresponse) LocalAddr() net.Addr { return r.localAddr }
func (r *gRPCresponse) RemoteAddr() net.Addr { return r.remoteAddr }
func (r *gRPCresponse) WriteMsg(m *dns.Msg) error { r.Msg = m; return nil }

View File

@ -0,0 +1,85 @@
package dnsserver
import (
"context"
"crypto/tls"
"fmt"
"net"
"github.com/miekg/dns"
)
// serverTLS represents an instance of a TLS-over-DNS-server.
type serverTLS struct {
*Server
}
// NewTLSServer returns a new CoreDNS TLS server and compiles all middleware in to it.
func NewServerTLS(addr string, group []*Config) (*serverTLS, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
return &serverTLS{Server: s}, nil
}
// Serve implements caddy.TCPServer interface.
func (s *serverTLS) Serve(l net.Listener) error {
s.m.Lock()
// Only fill out the TCP server for this one.
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp-tls", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.Background()
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
}
// ServePacket implements caddy.UDPServer interface.
func (s *serverTLS) ServePacket(p net.PacketConn) error { return nil }
// Listen implements caddy.TCPServer interface.
func (s *serverTLS) Listen() (net.Listener, error) {
// The *tls* middleware must make sure that multiple conflicting
// TLS configuration return an error: it can only be specified once.
tlsConfig := new(tls.Config)
for _, conf := range s.zones {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
var (
l net.Listener
err error
)
if tlsConfig == nil {
l, err = net.Listen("tcp", s.Addr[len(TransportTLS+"://"):])
} else {
l, err = tls.Listen("tcp", s.Addr[len(TransportTLS+"://"):], tlsConfig)
}
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *serverTLS) ListenPacket() (net.PacketConn, error) { return nil, nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *serverTLS) OnStartupComplete() {
if Quiet {
return
}
for zone, config := range s.zones {
fmt.Println(TransportTLS + "://" + zone + ":" + config.Port)
}
}

View File

@ -0,0 +1,281 @@
// Package dnsserver implements all the interfaces from Caddy, so that CoreDNS can be a servertype plugin.
package dnsserver
import (
"fmt"
"log"
"net"
"runtime"
"sync"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/metrics/vars"
"github.com/coredns/coredns/middleware/pkg/edns"
"github.com/coredns/coredns/middleware/pkg/rcode"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Server represents an instance of a server, which serves
// DNS requests at a particular address (host and port). A
// server is capable of serving numerous zones on
// the same address and the listener may be stopped for
// graceful termination (POSIX only).
type Server struct {
Addr string // Address we listen on
server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
m sync.Mutex // protects the servers
zones map[string]*Config // zones keyed by their address
dnsWg sync.WaitGroup // used to wait on outstanding connections
connTimeout time.Duration // the maximum duration of a graceful shutdown
}
// NewServer returns a new CoreDNS server and compiles all middleware in to it.
func NewServer(addr string, group []*Config) (*Server, error) {
s := &Server{
Addr: addr,
zones: make(map[string]*Config),
connTimeout: 5 * time.Second, // TODO(miek): was configurable
}
// We have to bound our wg with one increment
// to prevent a "race condition" that is hard-coded
// into sync.WaitGroup.Wait() - basically, an add
// with a positive delta must be guaranteed to
// occur before Wait() is called on the wg.
// In a way, this kind of acts as a safety barrier.
s.dnsWg.Add(1)
for _, site := range group {
// set the config per zone
s.zones[site.Zone] = site
// compile custom middleware for everything
var stack middleware.Handler
for i := len(site.Middleware) - 1; i >= 0; i-- {
stack = site.Middleware[i](stack)
}
site.middlewareChain = stack
}
return s, nil
}
// Serve starts the server with an existing listener. It blocks until the server stops.
// This implements caddy.TCPServer interface.
func (s *Server) Serve(l net.Listener) error {
s.m.Lock()
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.Background()
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
}
// ServePacket starts the server with an existing packetconn. It blocks until the server stops.
// This implements caddy.UDPServer interface.
func (s *Server) ServePacket(p net.PacketConn) error {
s.m.Lock()
s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.Background()
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[udp].ActivateAndServe()
}
// Listen implements caddy.TCPServer interface.
func (s *Server) Listen() (net.Listener, error) {
l, err := net.Listen("tcp", s.Addr[len(TransportDNS+"://"):])
if err != nil {
return nil, err
}
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *Server) ListenPacket() (net.PacketConn, error) {
p, err := net.ListenPacket("udp", s.Addr[len(TransportDNS+"://"):])
if err != nil {
return nil, err
}
return p, nil
}
// Stop stops the server. It blocks until the server is
// totally stopped. On POSIX systems, it will wait for
// connections to close (up to a max timeout of a few
// seconds); on Windows it will close the listener
// immediately.
// This implements Caddy.Stopper interface.
func (s *Server) Stop() (err error) {
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
s.dnsWg.Done() // decrement our initial increment used as a barrier
s.dnsWg.Wait()
close(done)
}()
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(s.connTimeout):
case <-done:
}
}
// Close the listener now; this stops the server without delay
s.m.Lock()
for _, s1 := range s.server {
// We might not have started and initialized the full set of servers
if s1 != nil {
err = s1.Shutdown()
}
}
s.m.Unlock()
return
}
// Address together with Stop() implement caddy.GracefulServer.
func (s *Server) Address() string { return s.Addr }
// ServeDNS is the entry point for every request to the address that s
// is bound to. It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and middleware stack) will handle the request.
func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
defer func() {
// In case the user doesn't enable error middleware, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
DefaultErrorFunc(w, r, dns.RcodeServerFailure)
}
}()
if m, err := edns.Version(r); err != nil { // Wrong EDNS version, return at once.
w.WriteMsg(m)
return
}
q := r.Question[0].Name
b := make([]byte, len(q))
off, end := 0, false
var dshandler *Config
for {
l := len(q[off:])
for i := 0; i < l; i++ {
b[i] = q[off+i]
// normalize the name for the lookup
if b[i] >= 'A' && b[i] <= 'Z' {
b[i] |= ('a' - 'A')
}
}
if h, ok := s.zones[string(b[:l])]; ok {
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
if rcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
// The type is DS, keep the handler, but keep on searching as maybe we are serving
// the parent as well and the DS should be routed to it - this will probably *misroute* DS
// queries to a possibly grand parent, but there is no way for us to know at this point
// if there is an actually delegation from grandparent -> parent -> zone.
// In all fairness: direct DS queries should not be needed.
dshandler = h
}
off, end = dns.NextLabel(q, off)
if end {
break
}
}
if dshandler != nil {
// DS request, and we found a zone, use the handler for the query
rcode, _ := dshandler.middlewareChain.ServeDNS(ctx, w, r)
if rcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
// Wildcard match, if we have found nothing try the root zone as a last resort.
if h, ok := s.zones["."]; ok {
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
if rcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
// Still here? Error out with REFUSED and some logging
remoteHost := w.RemoteAddr().String()
DefaultErrorFunc(w, r, dns.RcodeRefused)
log.Printf("[INFO] \"%s %s %s\" - No such zone at %s (Remote: %s)", dns.Type(r.Question[0].Qtype), dns.Class(r.Question[0].Qclass), q, s.Addr, remoteHost)
}
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *Server) OnStartupComplete() {
if Quiet {
return
}
for zone, config := range s.zones {
fmt.Println(zone + ":" + config.Port)
}
}
// DefaultErrorFunc responds to an DNS request with an error.
func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rc int) {
state := request.Request{W: w, Req: r}
answer := new(dns.Msg)
answer.SetRcode(r, rc)
state.SizeAndDo(answer)
vars.Report(state, vars.Dropped, rcode.ToString(rc), answer.Len(), time.Now())
w.WriteMsg(answer)
}
func rcodeNoClientWrite(rcode int) bool {
switch rcode {
case dns.RcodeServerFailure:
fallthrough
case dns.RcodeRefused:
fallthrough
case dns.RcodeFormatError:
fallthrough
case dns.RcodeNotImplemented:
return true
}
return false
}
const (
tcp = 0
udp = 1
)
var (
// Quiet mode will not show any informative output on initialization.
Quiet bool
)

View File

@ -0,0 +1,39 @@
// generated by directives_generate.go; DO NOT EDIT
package dnsserver
// Directives are registered in the order they should be
// executed.
//
// Ordering is VERY important. Every middleware will
// feel the effects of all other middleware below
// (after) them during a request, but they must not
// care what middleware above them are doing.
var directives = []string{
"tls",
"root",
"bind",
"trace",
"health",
"pprof",
"prometheus",
"errors",
"log",
"chaos",
"cache",
"rewrite",
"loadbalance",
"dnssec",
"reverse",
"file",
"auto",
"secondary",
"etcd",
"kubernetes",
"proxy",
"whoami",
"erratic",
"startup",
"shutdown",
}

31
vendor/github.com/miekg/coredns/core/zmiddleware.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
// generated by directives_generate.go; DO NOT EDIT
package core
import (
// Include all middleware.
_ "github.com/coredns/coredns/middleware/auto"
_ "github.com/coredns/coredns/middleware/bind"
_ "github.com/coredns/coredns/middleware/cache"
_ "github.com/coredns/coredns/middleware/chaos"
_ "github.com/coredns/coredns/middleware/dnssec"
_ "github.com/coredns/coredns/middleware/erratic"
_ "github.com/coredns/coredns/middleware/errors"
_ "github.com/coredns/coredns/middleware/etcd"
_ "github.com/coredns/coredns/middleware/file"
_ "github.com/coredns/coredns/middleware/health"
_ "github.com/coredns/coredns/middleware/kubernetes"
_ "github.com/coredns/coredns/middleware/loadbalance"
_ "github.com/coredns/coredns/middleware/log"
_ "github.com/coredns/coredns/middleware/metrics"
_ "github.com/coredns/coredns/middleware/pprof"
_ "github.com/coredns/coredns/middleware/proxy"
_ "github.com/coredns/coredns/middleware/reverse"
_ "github.com/coredns/coredns/middleware/rewrite"
_ "github.com/coredns/coredns/middleware/root"
_ "github.com/coredns/coredns/middleware/secondary"
_ "github.com/coredns/coredns/middleware/tls"
_ "github.com/coredns/coredns/middleware/trace"
_ "github.com/coredns/coredns/middleware/whoami"
_ "github.com/mholt/caddy/startupshutdown"
)

9
vendor/github.com/miekg/coredns/coredns.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package main
//go:generate go run directives_generate.go
import "github.com/coredns/coredns/coremain"
func main() {
coremain.Run()
}

260
vendor/github.com/miekg/coredns/coremain/run.go generated vendored Normal file
View File

@ -0,0 +1,260 @@
// Package coremain contains the functions for starting CoreDNS.
package coremain
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"strconv"
"strings"
"github.com/mholt/caddy"
"github.com/coredns/coredns/core/dnsserver"
// Plug in CoreDNS
_ "github.com/coredns/coredns/core"
)
func init() {
// Reset flag.CommandLine to get rid of unwanted flags for instance from glog (used in kubernetes).
// And readd the once we want to keep.
flag.VisitAll(func(f *flag.Flag) {
if _, ok := flagsBlacklist[f.Name]; ok {
return
}
flagsToKeep = append(flagsToKeep, f)
})
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
for _, f := range flagsToKeep {
flag.Var(f.Value, f.Name, f.Usage)
}
caddy.TrapSignals()
caddy.DefaultConfigFile = "Corefile"
caddy.Quiet = true // don't show init stuff from caddy
setVersion()
flag.StringVar(&conf, "conf", "", "Corefile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&logfile, "log", "", "Process log file")
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
flag.BoolVar(&version, "version", false, "Show version")
flag.BoolVar(&dnsserver.Quiet, "quiet", false, "Quiet mode (no initialization output)")
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
}
// Run is CoreDNS's main() function.
func Run() {
flag.Parse()
caddy.AppName = coreName
caddy.AppVersion = coreVersion
// Set up process log before anything bad happens
switch logfile {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
default:
log.SetOutput(os.Stdout)
}
log.SetFlags(log.LstdFlags)
if version {
showVersion()
os.Exit(0)
}
if plugins {
fmt.Println(caddy.DescribePlugins())
os.Exit(0)
}
// Set CPU cap
if err := setCPU(cpu); err != nil {
mustLogFatal(err)
}
// Get Corefile input
corefile, err := caddy.LoadCaddyfile(serverType)
if err != nil {
mustLogFatal(err)
}
// Start your engines
instance, err := caddy.Start(corefile)
if err != nil {
mustLogFatal(err)
}
logVersion()
if !dnsserver.Quiet {
showVersion()
}
// Twiddle your thumbs
instance.Wait()
}
// mustLogFatal wraps log.Fatal() in a way that ensures the
// output is always printed to stderr so the user can see it
// if the user is still there, even if the process log was not
// enabled. If this process is an upgrade, however, and the user
// might not be there anymore, this just logs to the process
// log and exits.
func mustLogFatal(args ...interface{}) {
if !caddy.IsUpgrade() {
log.SetOutput(os.Stderr)
}
log.Fatal(args...)
}
// confLoader loads the Caddyfile using the -conf flag.
func confLoader(serverType string) (caddy.Input, error) {
if conf == "" {
return nil, nil
}
if conf == "stdin" {
return caddy.CaddyfileFromPipe(os.Stdin, "dns")
}
contents, err := ioutil.ReadFile(conf)
if err != nil {
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: conf,
ServerTypeName: serverType,
}, nil
}
// defaultLoader loads the Corefile from the current working directory.
func defaultLoader(serverType string) (caddy.Input, error) {
contents, err := ioutil.ReadFile(caddy.DefaultConfigFile)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: caddy.DefaultConfigFile,
ServerTypeName: serverType,
}, nil
}
// logVersion logs the version that is starting.
func logVersion() { log.Print("[INFO] " + versionString()) }
// showVersion prints the version that is starting.
func showVersion() {
fmt.Print(versionString())
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
}
}
// versionString returns the CoreDNS version as a string.
func versionString() string {
return fmt.Sprintf("%s-%s\n", caddy.AppName, caddy.AppVersion)
}
// setVersion figures out the version information
// based on variables set by -ldflags.
func setVersion() {
// A development build is one that's not at a tag or has uncommitted changes
devBuild = gitTag == "" || gitShortStat != ""
// Only set the appVersion if -ldflags was used
if gitNearestTag != "" || gitTag != "" {
if devBuild && gitNearestTag != "" {
appVersion = fmt.Sprintf("%s (+%s %s)",
strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate)
} else if gitTag != "" {
appVersion = strings.TrimPrefix(gitTag, "v")
}
}
}
// setCPU parses string cpu and sets GOMAXPROCS
// according to its value. It accepts either
// a number (e.g. 3) or a percent (e.g. 50%).
func setCPU(cpu string) error {
var numCPU int
availCPU := runtime.NumCPU()
if strings.HasSuffix(cpu, "%") {
// Percent
var percent float32
pctStr := cpu[:len(cpu)-1]
pctInt, err := strconv.Atoi(pctStr)
if err != nil || pctInt < 1 || pctInt > 100 {
return errors.New("invalid CPU value: percentage must be between 1-100")
}
percent = float32(pctInt) / 100
numCPU = int(float32(availCPU) * percent)
} else {
// Number
num, err := strconv.Atoi(cpu)
if err != nil || num < 1 {
return errors.New("invalid CPU value: provide a number or percent greater than 0")
}
numCPU = num
}
if numCPU > availCPU {
numCPU = availCPU
}
runtime.GOMAXPROCS(numCPU)
return nil
}
// Flags that control program flow or startup
var (
conf string
cpu string
logfile string
version bool
plugins bool
)
// Build information obtained with the help of -ldflags
var (
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup
buildDate string // date -u
gitTag string // git describe --exact-match HEAD 2> /dev/null
gitNearestTag string // git describe --abbrev=0 --tags HEAD
gitCommit string // git rev-parse HEAD
gitShortStat string // git diff-index --shortstat
gitFilesModified string // git diff-index --name-only HEAD
)
// flagsBlacklist removes flags with these names from our flagset.
var flagsBlacklist = map[string]bool{
"logtostderr": true,
"alsologtostderr": true,
"v": true,
"stderrthreshold": true,
"vmodule": true,
"log_backtrace_at": true,
"log_dir": true,
}
var flagsToKeep []*flag.Flag

44
vendor/github.com/miekg/coredns/coremain/run_test.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
package coremain
import (
"runtime"
"testing"
)
func TestSetCPU(t *testing.T) {
currentCPU := runtime.GOMAXPROCS(-1)
maxCPU := runtime.NumCPU()
halfCPU := int(0.5 * float32(maxCPU))
if halfCPU < 1 {
halfCPU = 1
}
for i, test := range []struct {
input string
output int
shouldErr bool
}{
{"1", 1, false},
{"-1", currentCPU, true},
{"0", currentCPU, true},
{"100%", maxCPU, false},
{"50%", halfCPU, false},
{"110%", currentCPU, true},
{"-10%", currentCPU, true},
{"invalid input", currentCPU, true},
{"invalid input%", currentCPU, true},
{"9999", maxCPU, false}, // over available CPU
} {
err := setCPU(test.input)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
}
if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
}
// teardown
runtime.GOMAXPROCS(currentCPU)
}
}

8
vendor/github.com/miekg/coredns/coremain/version.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package coremain
const (
coreName = "CoreDNS"
coreVersion = "006"
serverType = "dns"
)

119
vendor/github.com/miekg/coredns/directives_generate.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
//+build ignore
package main
import (
"bufio"
"go/format"
"io/ioutil"
"log"
"os"
"sort"
"strconv"
"strings"
)
func main() {
mi := make(map[string]string, 0)
md := make(map[int]string, 0)
file, err := os.Open(middlewareFile)
fatalIfErr(err)
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "#") {
continue
}
items := strings.Split(line, ":")
if len(items) != 3 {
// ignore
continue
}
priority, err := strconv.Atoi(items[0])
fatalIfErr(err)
if v, ok := md[priority]; ok {
log.Fatalf("Duplicate priority '%d', slot already taken by %q", priority, v)
}
md[priority] = items[1]
mi[items[1]] = middlewarePath + items[2] // Default, unless overriden by 3rd arg
if strings.Contains(items[2], "/") { // External package has been given
mi[items[1]] = items[2]
}
}
genImports("core/zmiddleware.go", "core", mi)
genDirectives("core/dnsserver/zdirectives.go", "dnsserver", md)
}
func genImports(file, pack string, mi map[string]string) {
outs := header + "package " + pack + "\n\n" + "import ("
if len(mi) > 0 {
outs += "\n"
}
outs += "// Include all middleware.\n"
for _, v := range mi {
outs += `_ "` + v + `"` + "\n"
}
outs += ")\n"
res, err := format.Source([]byte(outs))
fatalIfErr(err)
err = ioutil.WriteFile(file, res, 0644)
fatalIfErr(err)
}
func genDirectives(file, pack string, md map[int]string) {
outs := header + "package " + pack + "\n\n"
outs += `
// Directives are registered in the order they should be
// executed.
//
// Ordering is VERY important. Every middleware will
// feel the effects of all other middleware below
// (after) them during a request, but they must not
// care what middleware above them are doing.
var directives = []string{
`
var orders []int
for k := range md {
orders = append(orders, k)
}
sort.Ints(orders)
for _, k := range orders {
outs += `"` + md[k] + `",` + "\n"
}
outs += "}\n"
res, err := format.Source([]byte(outs))
fatalIfErr(err)
err = ioutil.WriteFile(file, res, 0644)
fatalIfErr(err)
}
func fatalIfErr(err error) {
if err != nil {
log.Fatal(err)
}
}
const (
middlewarePath = "github.com/coredns/coredns/middleware/"
middlewareFile = "middleware.cfg"
header = "// generated by directives_generate.go; DO NOT EDIT\n\n"
)

46
vendor/github.com/miekg/coredns/middleware.cfg generated vendored Normal file
View File

@ -0,0 +1,46 @@
# Directives are registered in the order they should be
# executed.
#
# Ordering is VERY important. Every middleware will
# feel the effects of all other middleware below
# (after) them during a request, but they must not
# care what middleware above them are doing.
# How to rebuild with updated middleware configurations:
# Modify the list below and run `go gen && go build`
# The parser takes the input format of
# <order>:<middleware-name>:<package-name>
# Or
# <order>:<middleware-name>:<fully-qualified-package-name>
#
# External middleware example:
# 80:log:github.com/coredns/coredns/middleware/log
# Local middleware example:
# 80:log:log
1:tls:tls
10:root:root
20:bind:bind
30:trace:trace
40:health:health
50:pprof:pprof
60:prometheus:metrics
70:errors:errors
80:log:log
90:chaos:chaos
100:cache:cache
110:rewrite:rewrite
120:loadbalance:loadbalance
130:dnssec:dnssec
140:reverse:reverse
150:file:file
160:auto:auto
170:secondary:secondary
180:etcd:etcd
190:kubernetes:kubernetes
200:proxy:proxy
210:whoami:whoami
220:erratic:erratic
500:startup:github.com/mholt/caddy/startupshutdown
510:shutdown:github.com/mholt/caddy/startupshutdown

134
vendor/github.com/miekg/coredns/middleware.md generated vendored Normal file
View File

@ -0,0 +1,134 @@
# Middleware
## Writing Middleware
From the Caddy docs:
> Oh yes, those pesky return values on ServeHTTP(). You read the documentation so you already know
> what they mean. But what does that imply for the behavior of your middleware?
>
> Basically, return a status code only if you did NOT write to the response body. If you DO write to
> the response body, return a status code of 0. Return an error value if your middleware encountered
> an error that you want logged. It is common to return an error status and an error value together,
> so that the error handler up the chain can write the correct error page.
>
> The returned status code is not logged directly; rather, it tells middleware higher up the chain
> what status code to use if/when the response body is written. Again, return a 0 status if you've
> already written a body!
In the DNS status codes are called rcodes and it's slightly harder to return the correct
answer in case of failure.
So CoreDNS treats:
* SERVFAIL (dns.RcodeServerFailure)
* REFUSED (dns.RecodeRefused)
* FORMERR (dns.RcodeFormatError)
* NOTIMP (dns.RcodeNotImplemented)
as special and will then assume nothing has written to the client. In all other cases it is assumes
something has been written to the client (by the middleware).
## Hooking It Up
See a couple of blog posts on how to write and add middleware to CoreDNS:
* <https://blog.coredns.io/#> TO BE PUBLISHED.
* <https://blog.coredns.io/2016/12/19/writing-middleware-for-coredns/>, slightly older, but useful.
## Metrics
When exporting metrics the *Namespace* should be `middleware.Namespace` (="coredns"), and the
*Subsystem* should be the name of the middleware. The README.md for the middleware should then
also contain a *Metrics* section detailing the metrics.
## Documentation
Each middleware should have a README.md explaining what the middleware does and how it is
configured. The file should have the following layout:
* Title: use the middleware's name
* Subsection titled: "Syntax"
* Subsection titled: "Examples"
More sections are of course possible.
### Style
We use the Unix manual page style:
* The name of middleware in the running text should be italic: *middleware*.
* all CAPITAL: user supplied argument, in the running text references this use strong text: `**`:
**EXAMPLE**.
* Optional text: in block quotes: `[optional]`.
* Use three dots to indicate multiple options are allowed: `arg...`.
* Item used literal: `literal`.
### Example Domain Names
Please be sure to use `example.org` or `example.net` in any examples you provide. These are the
standard domain names created for this purpose.
## Fallthrough
In a perfect world the following would be true for middleware: "Either you are responsible for
a zone or not". If the answer is "not", the middleware should call the next middleware in the chain.
If "yes" it should handle *all* names that fall in this zone and the names below - i.e. it should
handle the entire domain.
~~~ txt
. {
file example.org db.example
}
~~~
In this example the *file* middleware is handling all names below (and including) `example.org`. If
a query comes in that is not a subdomain (or equal to) `example.org` the next middleware is called.
Now, the world isn't perfect, and there are good reasons to "fallthrough" to the next middlware,
meaning a middleware is only responsible for a subset of names within the zone. The first of these
to appear was the *reverse* middleware that synthesis PTR and A/AAAA responses (useful with IPv6).
The nature of the *reverse* middleware is such that it only deals with A,AAAA and PTR and then only
for a subset of the names. Ideally you would want to layer *reverse* **in front off** another
middleware such as *file* or *auto* (or even *proxy*). This means *reverse* handles some special
reverse cases and **all other** request are handled by the backing middleware. This is exactly what
"fallthrough" does. To keep things explicit we've opted that middlewares implement such behavior
should implement a `fallthrough` keyword.
### Example Fallthrough Usage
The following Corefile example, sets up the *reverse* middleware, but disables fallthrough. It
also defines a zonefile for use with the *file* middleware for other names in the `compute.internal`.
~~~ txt
arpa compute.internal {
reverse 10.32.0.0/16 {
hostname ip-{ip}.{zone[2]}
#fallthrough
}
file db.compute.internal compute.internal
}
~~~
This works for returning a response to a PTR request:
~~~ sh
% dig +nocmd @localhost +noall +ans -x 10.32.0.1
1.0.32.10.in-addr.arpa. 3600 IN PTR ip-10-32-0-1.compute.internal.
~~~
And for the forward:
~~~ sh
% dig +nocmd @localhost +noall +ans A ip-10-32-0-1.compute.internal
ip-10-32-0-1.compute.internal. 3600 IN A 10.32.0.1
~~~
But a query for `mx compute.internal` will return SERVFAIL. Now when we remove the '#' from
fallthrough and reload (on Unix: `kill -SIGUSR1 $(pidof coredns)`) CoreDNS, we *should* get an
answer for the MX query:
~~~ sh
% dig +nocmd @localhost +noall +ans MX compute.internal
compute.internal. 3600 IN MX 10 mx.compute.internal.
~~~

View File

@ -0,0 +1,67 @@
# auto
*auto* enables serving zone data from an RFC 1035-style master file which is automatically picked
up from disk.
The *auto* middleware is used for an "old-style" DNS server. It serves from a preloaded file that exists
on disk. If the zone file contains signatures (i.e. is signed, i.e. DNSSEC) correct DNSSEC answers
are returned. Only NSEC is supported! If you use this setup *you* are responsible for resigning the
zonefile. New zones or changed zone are automatically picked up from disk.
## Syntax
~~~
auto [ZONES...] {
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
no_reload
upstream ADDRESS...
}
~~~
**ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
are used.
* `directory` loads zones from the speficied **DIR**. If a file name matches **REGEXP** it will be
used to extract the origin. **ORIGIN_TEMPLATE** will be used as a template for the origin. Strings
like `{<number>}` are replaced with the respective matches in the file name, i.e. `{1}` is the
first match, `{2}` is the second, etc.. The default is: `db\.(.*) {1}` e.g. from a file with the
name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often
CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds.
The minimum value is 1 second.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
All directives from the *file* middleware are supported. Note that *auto* will load all zones found,
even though the directive might only receive queries for a specific zone. I.e:
~~~
auto example.org {
directory /etc/coredns/zones
}
~~~
Will happily pick up a zone for `example.COM`, except it will never be queried, because the *auto*
directive only is authoritative for `example.ORG`.
## Examples
Load `org` domains from `/etc/coredns/zones/org` and allow transfers to the internet, but send
notifies to 10.240.1.1
~~~
auto org {
directory /etc/coredns/zones/org
transfer to *
transfer to 10.240.1.1
}
~~~
Load `org` domains from `/etc/coredns/zones/org` and looks for file names as `www.db.example.org`,
where `example.org` is the origin. Scan every 45 seconds.
~~~
auto org {
directory /etc/coredns/zones/org www\.db\.(.*) {1} 45
}
~~~

100
vendor/github.com/miekg/coredns/middleware/auto/auto.go generated vendored Normal file
View File

@ -0,0 +1,100 @@
// Package auto implements an on-the-fly loading file backend.
package auto
import (
"errors"
"regexp"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/file"
"github.com/coredns/coredns/middleware/metrics"
"github.com/coredns/coredns/middleware/proxy"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
type (
// Auto holds the zones and the loader configuration for automatically loading zones.
Auto struct {
Next middleware.Handler
*Zones
metrics *metrics.Metrics
loader
}
loader struct {
directory string
template string
re *regexp.Regexp
// In the future this should be something like ZoneMeta that contains all this stuff.
transferTo []string
noReload bool
proxy proxy.Proxy // Proxy for looking up names during the resolution process
duration time.Duration
}
)
// ServeDNS implements the middleware.Handle interface.
func (a Auto) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
if state.QClass() != dns.ClassINET {
return dns.RcodeServerFailure, middleware.Error(a.Name(), errors.New("can only deal with ClassINET"))
}
qname := state.Name()
// TODO(miek): match the qname better in the map
// Precheck with the origins, i.e. are we allowed to looks here.
zone := middleware.Zones(a.Zones.Origins()).Matches(qname)
if zone == "" {
return middleware.NextOrFailure(a.Name(), a.Next, ctx, w, r)
}
// Now the real zone.
zone = middleware.Zones(a.Zones.Names()).Matches(qname)
a.Zones.RLock()
z, ok := a.Zones.Z[zone]
a.Zones.RUnlock()
if !ok || z == nil {
return dns.RcodeServerFailure, nil
}
if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR {
xfr := file.Xfr{Zone: z}
return xfr.ServeDNS(ctx, w, r)
}
answer, ns, extra, result := z.Lookup(state, qname)
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
m.Answer, m.Ns, m.Extra = answer, ns, extra
switch result {
case file.Success:
case file.NoData:
case file.NameError:
m.Rcode = dns.RcodeNameError
case file.Delegation:
m.Authoritative = false
case file.ServerFailure:
return dns.RcodeServerFailure, nil
}
state.SizeAndDo(m)
m, _ = state.Scrub(m)
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
// Name implements the Handler interface.
func (a Auto) Name() string { return "auto" }

View File

@ -0,0 +1,20 @@
package auto
// rewriteToExpand rewrites our template string to one that we can give to regexp.ExpandString. This basically
// involves prefixing any '{' with a '$'.
func rewriteToExpand(s string) string {
// Pretty dumb at the moment, every { will get a $ prefixed.
// Also wasteful as we build the string with +=. This is OKish
// as we do this during config parsing.
copy := ""
for _, c := range s {
if c == '{' {
copy += "$"
}
copy += string(c)
}
return copy
}

View File

@ -0,0 +1,20 @@
package auto
import "testing"
func TestRewriteToExpand(t *testing.T) {
tests := []struct {
in string
expected string
}{
{in: "", expected: ""},
{in: "{1}", expected: "${1}"},
{in: "{1", expected: "${1"},
}
for i, tc := range tests {
got := rewriteToExpand(tc.in)
if got != tc.expected {
t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expected, got)
}
}
}

View File

@ -0,0 +1,172 @@
package auto
import (
"log"
"os"
"path"
"regexp"
"strconv"
"time"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/file"
"github.com/coredns/coredns/middleware/metrics"
"github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/middleware/proxy"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("auto", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
a, err := autoParse(c)
if err != nil {
return middleware.Error("auto", err)
}
// If we have enabled prometheus we should add newly discovered zones to it.
met := dnsserver.GetMiddleware(c, "prometheus")
if met != nil {
a.metrics = met.(*metrics.Metrics)
}
walkChan := make(chan bool)
c.OnStartup(func() error {
err := a.Walk()
if err != nil {
return err
}
go func() {
ticker := time.NewTicker(a.loader.duration)
for {
select {
case <-walkChan:
return
case <-ticker.C:
a.Walk()
}
}
}()
return nil
})
c.OnShutdown(func() error {
close(walkChan)
return nil
})
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
a.Next = next
return a
})
return nil
}
func autoParse(c *caddy.Controller) (Auto, error) {
var a = Auto{
loader: loader{template: "${1}", re: regexp.MustCompile(`db\.(.*)`), duration: time.Duration(60 * time.Second)},
Zones: &Zones{},
}
config := dnsserver.GetConfig(c)
for c.Next() {
if c.Val() == "auto" {
// auto [ZONES...]
a.Zones.origins = make([]string, len(c.ServerBlockKeys))
copy(a.Zones.origins, c.ServerBlockKeys)
args := c.RemainingArgs()
if len(args) > 0 {
a.Zones.origins = args
}
for i := range a.Zones.origins {
a.Zones.origins[i] = middleware.Host(a.Zones.origins[i]).Normalize()
}
for c.NextBlock() {
switch c.Val() {
case "directory": // directory DIR [REGEXP [TEMPLATE] [DURATION]]
if !c.NextArg() {
return a, c.ArgErr()
}
a.loader.directory = c.Val()
if !path.IsAbs(a.loader.directory) && config.Root != "" {
a.loader.directory = path.Join(config.Root, a.loader.directory)
}
_, err := os.Stat(a.loader.directory)
if err != nil {
if os.IsNotExist(err) {
log.Printf("[WARNING] Directory does not exist: %s", a.loader.directory)
} else {
return a, c.Errf("Unable to access root path '%s': %v", a.loader.directory, err)
}
}
// regexp
if c.NextArg() {
a.loader.re, err = regexp.Compile(c.Val())
if err != nil {
return a, err
}
if a.loader.re.NumSubexp() == 0 {
return a, c.Errf("Need at least one sub expression")
}
}
// template
if c.NextArg() {
a.loader.template = rewriteToExpand(c.Val())
}
// duration
if c.NextArg() {
i, err := strconv.Atoi(c.Val())
if err != nil {
return a, err
}
if i < 1 {
i = 1
}
a.loader.duration = time.Duration(i) * time.Second
}
case "no_reload":
a.loader.noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return a, c.ArgErr()
}
ups, err := dnsutil.ParseHostPortOrFile(args...)
if err != nil {
return a, err
}
a.loader.proxy = proxy.NewLookup(ups)
default:
t, _, e := file.TransferParse(c, false)
if e != nil {
return a, e
}
if t != nil {
a.loader.transferTo = append(a.loader.transferTo, t...)
}
}
}
}
}
return a, nil
}

View File

@ -0,0 +1,94 @@
package auto
import (
"testing"
"github.com/mholt/caddy"
)
func TestAutoParse(t *testing.T) {
tests := []struct {
inputFileRules string
shouldErr bool
expectedDirectory string
expectedTempl string
expectedRe string
expectedTo []string
}{
{
`auto example.org {
directory /tmp
transfer to 127.0.0.1
}`,
false, "/tmp", "${1}", `db\.(.*)`, []string{"127.0.0.1:53"},
},
{
`auto {
directory /tmp
}`,
false, "/tmp", "${1}", `db\.(.*)`, nil,
},
{
`auto {
directory /tmp (.*) bliep
}`,
false, "/tmp", "bliep", `(.*)`, nil,
},
{
`auto {
directory /tmp (.*) bliep
transfer to 127.0.0.1
transfer to 127.0.0.2
upstream 8.8.8.8
}`,
false, "/tmp", "bliep", `(.*)`, []string{"127.0.0.1:53", "127.0.0.2:53"},
},
// errors
{
`auto example.org {
directory
}`,
true, "", "${1}", `db\.(.*)`, nil,
},
{
`auto example.org {
directory /tmp * {1}
}`,
true, "", "${1}", ``, nil,
},
{
`auto example.org {
directory /tmp .* {1}
}`,
true, "", "${1}", ``, nil,
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.inputFileRules)
a, err := autoParse(c)
if err == nil && test.shouldErr {
t.Fatalf("Test %d expected errors, but got no error", i)
} else if err != nil && !test.shouldErr {
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
} else if !test.shouldErr {
if a.loader.directory != test.expectedDirectory {
t.Fatalf("Test %d expected %v, got %v", i, test.expectedDirectory, a.loader.directory)
}
if a.loader.template != test.expectedTempl {
t.Fatalf("Test %d expected %v, got %v", i, test.expectedTempl, a.loader.template)
}
if a.loader.re.String() != test.expectedRe {
t.Fatalf("Test %d expected %v, got %v", i, test.expectedRe, a.loader.re)
}
if test.expectedTo != nil {
for j, got := range a.loader.transferTo {
if got != test.expectedTo[j] {
t.Fatalf("Test %d expected %v, got %v", i, test.expectedTo[j], got)
}
}
}
}
}
}

108
vendor/github.com/miekg/coredns/middleware/auto/walk.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
package auto
import (
"log"
"os"
"path"
"path/filepath"
"regexp"
"github.com/coredns/coredns/middleware/file"
"github.com/miekg/dns"
)
// Walk will recursively walk of the file under l.directory and adds the one that match l.re.
func (a Auto) Walk() error {
// TODO(miek): should add something so that we don't stomp on each other.
toDelete := make(map[string]bool)
for _, n := range a.Zones.Names() {
toDelete[n] = true
}
filepath.Walk(a.loader.directory, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}
match, origin := matches(a.loader.re, info.Name(), a.loader.template)
if !match {
return nil
}
if _, ok := a.Zones.Z[origin]; ok {
// we already have this zone
toDelete[origin] = false
return nil
}
reader, err := os.Open(path)
if err != nil {
log.Printf("[WARNING] Opening %s failed: %s", path, err)
return nil
}
defer reader.Close()
zo, err := file.Parse(reader, origin, path)
if err != nil {
// Parse barfs warning by itself...
return nil
}
zo.NoReload = a.loader.noReload
zo.Proxy = a.loader.proxy
zo.TransferTo = a.loader.transferTo
a.Zones.Add(zo, origin)
if a.metrics != nil {
a.metrics.AddZone(origin)
}
zo.Notify()
log.Printf("[INFO] Inserting zone `%s' from: %s", origin, path)
toDelete[origin] = false
return nil
})
for origin, ok := range toDelete {
if !ok {
continue
}
if a.metrics != nil {
a.metrics.RemoveZone(origin)
}
a.Zones.Remove(origin)
log.Printf("[INFO] Deleting zone `%s'", origin)
}
return nil
}
// matches matches re to filename, if is is a match, the subexpression will be used to expand
// template to an origin. When match is true that origin is returned. Origin is fully qualified.
func matches(re *regexp.Regexp, filename, template string) (match bool, origin string) {
base := path.Base(filename)
matches := re.FindStringSubmatchIndex(base)
if matches == nil {
return false, ""
}
by := re.ExpandString(nil, template, base, matches)
if by == nil {
return false, ""
}
origin = dns.Fqdn(string(by))
return true, origin
}

View File

@ -0,0 +1,94 @@
package auto
import (
"io/ioutil"
"log"
"os"
"path"
"regexp"
"testing"
)
var dbFiles = []string{"db.example.org", "aa.example.org"}
const zoneContent = `; testzone
@ IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082534 7200 3600 1209600 3600
NS a.iana-servers.net.
NS b.iana-servers.net.
www IN A 127.0.0.1
`
func TestWalk(t *testing.T) {
log.SetOutput(ioutil.Discard)
tempdir, err := createFiles()
if err != nil {
if tempdir != "" {
os.RemoveAll(tempdir)
}
t.Fatal(err)
}
defer os.RemoveAll(tempdir)
ldr := loader{
directory: tempdir,
re: regexp.MustCompile(`db\.(.*)`),
template: `${1}`,
}
a := Auto{
loader: ldr,
Zones: &Zones{},
}
a.Walk()
// db.example.org and db.example.com should be here (created in createFiles)
for _, name := range []string{"example.com.", "example.org."} {
if _, ok := a.Zones.Z[name]; !ok {
t.Errorf("%s should have been added", name)
}
}
}
func TestWalkNonExistent(t *testing.T) {
log.SetOutput(ioutil.Discard)
nonExistingDir := "highly_unlikely_to_exist_dir"
ldr := loader{
directory: nonExistingDir,
re: regexp.MustCompile(`db\.(.*)`),
template: `${1}`,
}
a := Auto{
loader: ldr,
Zones: &Zones{},
}
a.Walk()
}
func createFiles() (string, error) {
dir, err := ioutil.TempDir(os.TempDir(), "coredns")
if err != nil {
return dir, err
}
for _, name := range dbFiles {
if err := ioutil.WriteFile(path.Join(dir, name), []byte(zoneContent), 0644); err != nil {
return dir, err
}
}
// symlinks
if err = os.Symlink(path.Join(dir, "db.example.org"), path.Join(dir, "db.example.com")); err != nil {
return dir, err
}
if err = os.Symlink(path.Join(dir, "db.example.org"), path.Join(dir, "aa.example.com")); err != nil {
return dir, err
}
return dir, nil
}

View File

@ -0,0 +1,58 @@
package auto
import (
"io/ioutil"
"log"
"os"
"path"
"regexp"
"testing"
)
func TestWatcher(t *testing.T) {
log.SetOutput(ioutil.Discard)
tempdir, err := createFiles()
if err != nil {
if tempdir != "" {
os.RemoveAll(tempdir)
}
t.Fatal(err)
}
defer os.RemoveAll(tempdir)
ldr := loader{
directory: tempdir,
re: regexp.MustCompile(`db\.(.*)`),
template: `${1}`,
}
a := Auto{
loader: ldr,
Zones: &Zones{},
}
a.Walk()
// example.org and example.com should exist
if x := len(a.Zones.Z["example.org."].All()); x != 4 {
t.Fatalf("Expected 4 RRs, got %d", x)
}
if x := len(a.Zones.Z["example.com."].All()); x != 4 {
t.Fatalf("Expected 4 RRs, got %d", x)
}
// Now remove one file, rescan and see if it's gone.
if err := os.Remove(path.Join(tempdir, "db.example.com")); err != nil {
t.Fatal(err)
}
a.Walk()
if _, ok := a.Zones.Z["example.com."]; ok {
t.Errorf("Expected %q to be gone.", "example.com.")
}
if _, ok := a.Zones.Z["example.org."]; !ok {
t.Errorf("Expected %q to still be there.", "example.org.")
}
}

View File

@ -0,0 +1,76 @@
// Package auto implements a on-the-fly loading file backend.
package auto
import (
"sync"
"github.com/coredns/coredns/middleware/file"
)
// Zones maps zone names to a *Zone. This keep track of what we zones we have loaded at
// any one time.
type Zones struct {
Z map[string]*file.Zone // A map mapping zone (origin) to the Zone's data.
names []string // All the keys from the map Z as a string slice.
origins []string // Any origins from the server block.
sync.RWMutex
}
// Names returns the names from z.
func (z *Zones) Names() []string {
z.RLock()
n := z.names
z.RUnlock()
return n
}
// Origins returns the origins from z.
func (z *Zones) Origins() []string {
// doesn't need locking, because there aren't multiple Go routines accessing it.
return z.origins
}
// Zones returns a zone with origin name from z, nil when not found.
func (z *Zones) Zones(name string) *file.Zone {
z.RLock()
zo := z.Z[name]
z.RUnlock()
return zo
}
// Add adds a new zone into z. If zo.NoReload is false, the
// reload goroutine is started.
func (z *Zones) Add(zo *file.Zone, name string) {
z.Lock()
if z.Z == nil {
z.Z = make(map[string]*file.Zone)
}
z.Z[name] = zo
z.names = append(z.names, name)
zo.Reload()
z.Unlock()
}
// Remove removes the zone named name from z. It also stop the the zone's reload goroutine.
func (z *Zones) Remove(name string) {
z.Lock()
if zo, ok := z.Z[name]; ok && !zo.NoReload {
zo.ReloadShutdown <- true
}
delete(z.Z, name)
// TODO(miek): just regenerate Names (might be bad if you have a lot of zones...)
z.names = []string{}
for n := range z.Z {
z.names = append(z.names, n)
}
z.Unlock()
}

33
vendor/github.com/miekg/coredns/middleware/backend.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package middleware
import (
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// ServiceBackend defines a (dynamic) backend that returns a slice of service definitions.
type ServiceBackend interface {
// Services communicates with the backend to retrieve the service definition. Exact indicates
// on exact much are that we are allowed to recurs.
Services(state request.Request, exact bool, opt Options) ([]msg.Service, []msg.Service, error)
// Reverse communicates with the backend to retrieve service definition based on a IP address
// instead of a name. I.e. a reverse DNS lookup.
Reverse(state request.Request, exact bool, opt Options) ([]msg.Service, []msg.Service, error)
// Lookup is used to find records else where.
Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error)
// IsNameError return true if err indicated a record not found condition
IsNameError(err error) bool
// Debug returns a string used when returning debug services.
Debug() string
}
// Options are extra options that can be specified for a lookup.
type Options struct {
Debug string // This is a debug query. A query prefixed with debug.o-o
}

View File

@ -0,0 +1,451 @@
package middleware
import (
"fmt"
"math"
"net"
"time"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// A returns A records from Backend or an error.
func A(b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, debug, err
}
for _, serv := range services {
ip := net.ParseIP(serv.Host)
switch {
case ip == nil:
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
// x CNAME x is a direct loop, don't add those
continue
}
newRecord := serv.NewCNAME(state.QName(), serv.Host)
if len(previousRecords) > 7 {
// don't add it, and just continue
continue
}
if dnsutil.DuplicateCNAME(newRecord, previousRecords) {
continue
}
state1 := state.NewWithQuestion(serv.Host, state.QType())
nextRecords, nextDebug, err := A(b, zone, state1, append(previousRecords, newRecord), opt)
if err == nil {
// Not only have we found something we should add the CNAME and the IP addresses.
if len(nextRecords) > 0 {
records = append(records, newRecord)
records = append(records, nextRecords...)
debug = append(debug, nextDebug...)
}
continue
}
// This means we can not complete the CNAME, try to look else where.
target := newRecord.Target
if dns.IsSubDomain(zone, target) {
// We should already have found it
continue
}
// Lookup
m1, e1 := b.Lookup(state, target, state.QType())
if e1 != nil {
debugMsg := msg.Service{Key: msg.Path(target, b.Debug()), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()}
debug = append(debug, debugMsg)
continue
}
// Len(m1.Answer) > 0 here is well?
records = append(records, newRecord)
records = append(records, m1.Answer...)
continue
case ip.To4() != nil:
records = append(records, serv.NewA(state.QName(), ip.To4()))
case ip.To4() == nil:
// nodata?
}
}
return records, debug, nil
}
// AAAA returns AAAA records from Backend or an error.
func AAAA(b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, debug, err
}
for _, serv := range services {
ip := net.ParseIP(serv.Host)
switch {
case ip == nil:
// Try to resolve as CNAME if it's not an IP, but only if we don't create loops.
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
// x CNAME x is a direct loop, don't add those
continue
}
newRecord := serv.NewCNAME(state.QName(), serv.Host)
if len(previousRecords) > 7 {
// don't add it, and just continue
continue
}
if dnsutil.DuplicateCNAME(newRecord, previousRecords) {
continue
}
state1 := state.NewWithQuestion(serv.Host, state.QType())
nextRecords, nextDebug, err := AAAA(b, zone, state1, append(previousRecords, newRecord), opt)
if err == nil {
// Not only have we found something we should add the CNAME and the IP addresses.
if len(nextRecords) > 0 {
records = append(records, newRecord)
records = append(records, nextRecords...)
debug = append(debug, nextDebug...)
}
continue
}
// This means we can not complete the CNAME, try to look else where.
target := newRecord.Target
if dns.IsSubDomain(zone, target) {
// We should already have found it
continue
}
m1, e1 := b.Lookup(state, target, state.QType())
if e1 != nil {
debugMsg := msg.Service{Key: msg.Path(target, b.Debug()), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()}
debug = append(debug, debugMsg)
continue
}
// Len(m1.Answer) > 0 here is well?
records = append(records, newRecord)
records = append(records, m1.Answer...)
continue
// both here again
case ip.To4() != nil:
// nada?
case ip.To4() == nil:
records = append(records, serv.NewAAAA(state.QName(), ip.To16()))
}
}
return records, debug, nil
}
// SRV returns SRV records from the Backend.
// If the Target is not a name but an IP address, a name is created on the fly.
func SRV(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, nil, nil, err
}
// Looping twice to get the right weight vs priority
w := make(map[int]int)
for _, serv := range services {
weight := 100
if serv.Weight != 0 {
weight = serv.Weight
}
if _, ok := w[serv.Priority]; !ok {
w[serv.Priority] = weight
continue
}
w[serv.Priority] += weight
}
lookup := make(map[string]bool)
for _, serv := range services {
w1 := 100.0 / float64(w[serv.Priority])
if serv.Weight == 0 {
w1 *= 100
} else {
w1 *= float64(serv.Weight)
}
weight := uint16(math.Floor(w1))
ip := net.ParseIP(serv.Host)
switch {
case ip == nil:
srv := serv.NewSRV(state.QName(), weight)
records = append(records, srv)
if _, ok := lookup[srv.Target]; ok {
break
}
lookup[srv.Target] = true
if !dns.IsSubDomain(zone, srv.Target) {
m1, e1 := b.Lookup(state, srv.Target, dns.TypeA)
if e1 == nil {
extra = append(extra, m1.Answer...)
} else {
debugMsg := msg.Service{Key: msg.Path(srv.Target, b.Debug()), Host: srv.Target, Text: " IN A: " + e1.Error()}
debug = append(debug, debugMsg)
}
m1, e1 = b.Lookup(state, srv.Target, dns.TypeAAAA)
if e1 == nil {
// If we have seen CNAME's we *assume* that they are already added.
for _, a := range m1.Answer {
if _, ok := a.(*dns.CNAME); !ok {
extra = append(extra, a)
}
}
} else {
debugMsg := msg.Service{Key: msg.Path(srv.Target, b.Debug()), Host: srv.Target, Text: " IN AAAA: " + e1.Error()}
debug = append(debug, debugMsg)
}
break
}
// Internal name, we should have some info on them, either v4 or v6
// Clients expect a complete answer, because we are a recursor in their view.
state1 := state.NewWithQuestion(srv.Target, dns.TypeA)
addr, debugAddr, e1 := A(b, zone, state1, nil, Options(opt))
if e1 == nil {
extra = append(extra, addr...)
debug = append(debug, debugAddr...)
}
// IPv6 lookups here as well? AAAA(zone, state1, nil).
case ip.To4() != nil:
serv.Host = msg.Domain(serv.Key)
srv := serv.NewSRV(state.QName(), weight)
records = append(records, srv)
extra = append(extra, serv.NewA(srv.Target, ip.To4()))
case ip.To4() == nil:
serv.Host = msg.Domain(serv.Key)
srv := serv.NewSRV(state.QName(), weight)
records = append(records, srv)
extra = append(extra, serv.NewAAAA(srv.Target, ip.To16()))
}
}
return records, extra, debug, nil
}
// MX returns MX records from the Backend. If the Target is not a name but an IP address, a name is created on the fly.
func MX(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, nil, debug, err
}
lookup := make(map[string]bool)
for _, serv := range services {
if !serv.Mail {
continue
}
ip := net.ParseIP(serv.Host)
switch {
case ip == nil:
mx := serv.NewMX(state.QName())
records = append(records, mx)
if _, ok := lookup[mx.Mx]; ok {
break
}
lookup[mx.Mx] = true
if !dns.IsSubDomain(zone, mx.Mx) {
m1, e1 := b.Lookup(state, mx.Mx, dns.TypeA)
if e1 == nil {
extra = append(extra, m1.Answer...)
} else {
debugMsg := msg.Service{Key: msg.Path(mx.Mx, b.Debug()), Host: mx.Mx, Text: " IN A: " + e1.Error()}
debug = append(debug, debugMsg)
}
m1, e1 = b.Lookup(state, mx.Mx, dns.TypeAAAA)
if e1 == nil {
// If we have seen CNAME's we *assume* that they are already added.
for _, a := range m1.Answer {
if _, ok := a.(*dns.CNAME); !ok {
extra = append(extra, a)
}
}
} else {
debugMsg := msg.Service{Key: msg.Path(mx.Mx, b.Debug()), Host: mx.Mx, Text: " IN AAAA: " + e1.Error()}
debug = append(debug, debugMsg)
}
break
}
// Internal name
state1 := state.NewWithQuestion(mx.Mx, dns.TypeA)
addr, debugAddr, e1 := A(b, zone, state1, nil, opt)
if e1 == nil {
extra = append(extra, addr...)
debug = append(debug, debugAddr...)
}
// e.AAAA as well
case ip.To4() != nil:
serv.Host = msg.Domain(serv.Key)
records = append(records, serv.NewMX(state.QName()))
extra = append(extra, serv.NewA(serv.Host, ip.To4()))
case ip.To4() == nil:
serv.Host = msg.Domain(serv.Key)
records = append(records, serv.NewMX(state.QName()))
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
}
}
return records, extra, debug, nil
}
// CNAME returns CNAME records from the backend or an error.
func CNAME(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, true, opt)
if err != nil {
return nil, debug, err
}
if len(services) > 0 {
serv := services[0]
if ip := net.ParseIP(serv.Host); ip == nil {
records = append(records, serv.NewCNAME(state.QName(), serv.Host))
}
}
return records, debug, nil
}
// TXT returns TXT records from Backend or an error.
func TXT(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, debug, err
}
for _, serv := range services {
if serv.Text == "" {
continue
}
records = append(records, serv.NewTXT(state.QName()))
}
return records, debug, nil
}
// PTR returns the PTR records from the backend, only services that have a domain name as host are included.
func PTR(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := b.Reverse(state, true, opt)
if err != nil {
return nil, debug, err
}
for _, serv := range services {
if ip := net.ParseIP(serv.Host); ip == nil {
records = append(records, serv.NewPTR(state.QName(), serv.Host))
}
}
return records, debug, nil
}
// NS returns NS records from the backend
func NS(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
// NS record for this zone live in a special place, ns.dns.<zone>. Fake our lookup.
// only a tad bit fishy...
old := state.QName()
state.Clear()
state.Req.Question[0].Name = "ns.dns." + zone
services, debug, err := b.Services(state, false, opt)
if err != nil {
return nil, nil, debug, err
}
// ... and reset
state.Req.Question[0].Name = old
for _, serv := range services {
ip := net.ParseIP(serv.Host)
switch {
case ip == nil:
return nil, nil, debug, fmt.Errorf("NS record must be an IP address: %s", serv.Host)
case ip.To4() != nil:
serv.Host = msg.Domain(serv.Key)
records = append(records, serv.NewNS(state.QName()))
extra = append(extra, serv.NewA(serv.Host, ip.To4()))
case ip.To4() == nil:
serv.Host = msg.Domain(serv.Key)
records = append(records, serv.NewNS(state.QName()))
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
}
}
return records, extra, debug, nil
}
// SOA returns a SOA record from the backend.
func SOA(b ServiceBackend, zone string, state request.Request, opt Options) ([]dns.RR, []msg.Service, error) {
header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: 300, Class: dns.ClassINET}
soa := &dns.SOA{Hdr: header,
Mbox: hostmaster + "." + zone,
Ns: "ns.dns." + zone,
Serial: uint32(time.Now().Unix()),
Refresh: 7200,
Retry: 1800,
Expire: 86400,
Minttl: minTTL,
}
// TODO(miek): fake some msg.Service here when returning?
return []dns.RR{soa}, nil, nil
}
// BackendError writes an error response to the client.
func BackendError(b ServiceBackend, zone string, rcode int, state request.Request, debug []msg.Service, err error, opt Options) (int, error) {
m := new(dns.Msg)
m.SetRcode(state.Req, rcode)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
m.Ns, _, _ = SOA(b, zone, state, opt)
if opt.Debug != "" {
m.Extra = ServicesToTxt(debug)
txt := ErrorToTxt(err)
if txt != nil {
m.Extra = append(m.Extra, ErrorToTxt(err))
}
}
state.SizeAndDo(m)
state.W.WriteMsg(m)
// Return success as the rcode to signal we have written to the client.
return dns.RcodeSuccess, err
}
// ServicesToTxt puts debug in TXT RRs.
func ServicesToTxt(debug []msg.Service) []dns.RR {
if debug == nil {
return nil
}
rr := make([]dns.RR, len(debug))
for i, d := range debug {
rr[i] = d.RR()
}
return rr
}
// ErrorToTxt puts in error's text into an TXT RR.
func ErrorToTxt(err error) dns.RR {
if err == nil {
return nil
}
msg := err.Error()
if len(msg) > 255 {
msg = msg[:255]
}
t := new(dns.TXT)
t.Hdr.Class = dns.ClassCHAOS
t.Hdr.Ttl = 0
t.Hdr.Rrtype = dns.TypeTXT
t.Hdr.Name = "."
t.Txt = []string{msg}
return t
}
const (
minTTL = 60
hostmaster = "hostmaster"
)

View File

@ -0,0 +1,21 @@
# bind
*bind* overrides the host to which the server should bind. Normally, the listener binds to the
wildcard host. However, you may force the listener to bind to another IP instead. This
directive accepts only an address, not a port.
## Syntax
~~~ txt
bind ADDRESS
~~~
**ADDRESS** is the IP address to bind to.
## Examples
To make your socket accessible only to that machine, bind to IP 127.0.0.1 (localhost):
~~~ txt
bind 127.0.0.1
~~~

View File

@ -0,0 +1,11 @@
// Package bind allows binding to a specific interface instead of bind to all of them.
package bind
import "github.com/mholt/caddy"
func init() {
caddy.RegisterPlugin("bind", caddy.Plugin{
ServerType: "dns",
Action: setupBind,
})
}

View File

@ -0,0 +1,30 @@
package bind
import (
"testing"
"github.com/coredns/coredns/core/dnsserver"
"github.com/mholt/caddy"
)
func TestSetupBind(t *testing.T) {
c := caddy.NewTestController("dns", `bind 1.2.3.4`)
err := setupBind(c)
if err != nil {
t.Fatalf("Expected no errors, but got: %v", err)
}
cfg := dnsserver.GetConfig(c)
if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
}
}
func TestBindAddress(t *testing.T) {
c := caddy.NewTestController("dns", `bind 1.2.3.bla`)
err := setupBind(c)
if err == nil {
t.Fatalf("Expected errors, but got none")
}
}

View File

@ -0,0 +1,24 @@
package bind
import (
"fmt"
"net"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
)
func setupBind(c *caddy.Controller) error {
config := dnsserver.GetConfig(c)
for c.Next() {
if !c.Args(&config.ListenHost) {
return middleware.Error("bind", c.ArgErr())
}
}
if net.ParseIP(config.ListenHost) == nil {
return middleware.Error("bind", fmt.Errorf("not a valid IP address: %s", config.ListenHost))
}
return nil
}

View File

@ -0,0 +1,64 @@
# cache
*cache* enables a frontend cache. It will cache all records except zone transfers and metadata records.
## Syntax
~~~ txt
cache [TTL] [ZONES...]
~~~
* **TTL** max TTL in seconds. If not specified, the maximum TTL will be used which is 3600 for
noerror responses and 1800 for denial of existence ones.
A set TTL of 300 *cache 300* would cache the record up to 300 seconds.
Smaller record provided TTLs will take precedence.
* **ZONES** zones it should cache for. If empty, the zones from the configuration block are used.
Each element in the cache is cached according to its TTL (with **TTL** as the max).
For the negative cache, the SOA's MinTTL value is used. A cache can contain up to 10,000 items by
default. A TTL of zero is not allowed. No cache invalidation triggered by other middlewares is available. Therefore even reloaded items might still be cached for the duration of the TTL.
If you want more control:
~~~ txt
cache [TTL] [ZONES...] {
success CAPACITY [TTL]
denial CAPACITY [TTL]
}
~~~
* **TTL** and **ZONES** as above.
* `success`, override the settings for caching successful responses, **CAPACITY** indicates the maximum
number of packets we cache before we start evicting (LRU). **TTL** overrides the cache maximum TTL.
* `denial`, override the settings for caching denial of existence responses, **CAPACITY** indicates the maximum
number of packets we cache before we start evicting (LRU). **TTL** overrides the cache maximum TTL.
There is a third category (`error`) but those responses are never cached.
The minimum TTL allowed on resource records is 5 seconds.
## Metrics
If monitoring is enabled (via the *prometheus* directive) then the following metrics are exported:
* coredns_cache_size{type} - Total elements in the cache by cache type.
* coredns_cache_capacity{type} - Total capacity of the cache by cache type.
* coredns_cache_hits_total{type} - Counter of cache hits by cache type.
* coredns_cache_misses_total - Counter of cache misses.
Cache types are either "denial" or "success".
## Examples
Enable caching for all zones, but cap everything to a TTL of 10 seconds:
~~~
cache 10
~~~
Proxy to Google Public DNS and only cache responses for example.org (or below).
~~~
proxy . 8.8.8.8:53
cache example.org
~~~

View File

@ -0,0 +1,137 @@
// Package cache implements a cache.
package cache
import (
"log"
"strconv"
"strings"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/response"
"github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
)
// Cache is middleware that looks up responses in a cache and caches replies.
// It has a success and a denial of existence cache.
type Cache struct {
Next middleware.Handler
Zones []string
ncache *lru.Cache
ncap int
nttl time.Duration
pcache *lru.Cache
pcap int
pttl time.Duration
}
// Return key under which we store the item. The empty string is returned
// when we don't want to cache the message. Currently we do not cache Truncated, errors
// zone transfers or dynamic update messages.
func key(m *dns.Msg, t response.Type, do bool) string {
// We don't store truncated responses.
if m.Truncated {
return ""
}
// Nor errors or Meta or Update
if t == response.OtherError || t == response.Meta || t == response.Update {
return ""
}
qtype := m.Question[0].Qtype
qname := strings.ToLower(m.Question[0].Name)
return rawKey(qname, qtype, do)
}
func rawKey(qname string, qtype uint16, do bool) string {
if do {
return "1" + qname + "." + strconv.Itoa(int(qtype))
}
return "0" + qname + "." + strconv.Itoa(int(qtype))
}
// ResponseWriter is a response writer that caches the reply message.
type ResponseWriter struct {
dns.ResponseWriter
*Cache
}
// WriteMsg implements the dns.ResponseWriter interface.
func (c *ResponseWriter) WriteMsg(res *dns.Msg) error {
do := false
mt, opt := response.Typify(res)
if opt != nil {
do = opt.Do()
}
// key returns empty string for anything we don't want to cache.
key := key(res, mt, do)
duration := c.pttl
if mt == response.NameError || mt == response.NoData {
duration = c.nttl
}
msgTTL := minMsgTTL(res, mt)
if msgTTL < duration {
duration = msgTTL
}
if key != "" {
c.set(res, key, mt, duration)
cacheSize.WithLabelValues(Success).Set(float64(c.pcache.Len()))
cacheSize.WithLabelValues(Denial).Set(float64(c.ncache.Len()))
}
setMsgTTL(res, uint32(duration.Seconds()))
return c.ResponseWriter.WriteMsg(res)
}
func (c *ResponseWriter) set(m *dns.Msg, key string, mt response.Type, duration time.Duration) {
if key == "" {
log.Printf("[ERROR] Caching called with empty cache key")
return
}
switch mt {
case response.NoError, response.Delegation:
i := newItem(m, duration)
c.pcache.Add(key, i)
case response.NameError, response.NoData:
i := newItem(m, duration)
c.ncache.Add(key, i)
case response.OtherError:
// don't cache these
default:
log.Printf("[WARNING] Caching called with unknown classification: %d", mt)
}
}
// Write implements the dns.ResponseWriter interface.
func (c *ResponseWriter) Write(buf []byte) (int, error) {
log.Printf("[WARNING] Caching called with Write: not caching reply")
n, err := c.ResponseWriter.Write(buf)
return n, err
}
const (
maxTTL = 1 * time.Hour
maxNTTL = 30 * time.Minute
minTTL = 5 // seconds
defaultCap = 10000 // default capacity of the cache.
// Success is the class for caching positive caching.
Success = "success"
// Denial is the class defined for negative caching.
Denial = "denial"
)

View File

@ -0,0 +1,151 @@
package cache
import (
"io/ioutil"
"log"
"testing"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/response"
"github.com/coredns/coredns/middleware/test"
lru "github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
)
type cacheTestCase struct {
test.Case
in test.Case
AuthenticatedData bool
Authoritative bool
RecursionAvailable bool
Truncated bool
}
var cacheTestCases = []cacheTestCase{
{
RecursionAvailable: true, AuthenticatedData: true, Authoritative: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("miek.nl. 3600 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 3600 IN MX 10 aspmx2.googlemail.com."),
},
},
in: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("miek.nl. 3601 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 3601 IN MX 10 aspmx2.googlemail.com."),
},
},
},
{
RecursionAvailable: true, AuthenticatedData: true, Authoritative: true,
Case: test.Case{
Qname: "mIEK.nL.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("mIEK.nL. 3600 IN MX 1 aspmx.l.google.com."),
test.MX("mIEK.nL. 3600 IN MX 10 aspmx2.googlemail.com."),
},
},
in: test.Case{
Qname: "mIEK.nL.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("mIEK.nL. 3601 IN MX 1 aspmx.l.google.com."),
test.MX("mIEK.nL. 3601 IN MX 10 aspmx2.googlemail.com."),
},
},
},
{
Truncated: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com.")},
},
in: test.Case{},
},
{
RecursionAvailable: true, Authoritative: true,
Case: test.Case{
Rcode: dns.RcodeNameError,
Qname: "example.org.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"),
},
},
in: test.Case{
Rcode: dns.RcodeNameError,
Qname: "example.org.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"),
},
},
},
}
func cacheMsg(m *dns.Msg, tc cacheTestCase) *dns.Msg {
m.RecursionAvailable = tc.RecursionAvailable
m.AuthenticatedData = tc.AuthenticatedData
m.Authoritative = tc.Authoritative
m.Rcode = tc.Rcode
m.Truncated = tc.Truncated
m.Answer = tc.in.Answer
m.Ns = tc.in.Ns
// m.Extra = tc.in.Extra , not the OPT record!
return m
}
func newTestCache(ttl time.Duration) (*Cache, *ResponseWriter) {
c := &Cache{Zones: []string{"."}, pcap: defaultCap, ncap: defaultCap, pttl: ttl, nttl: ttl}
c.pcache, _ = lru.New(c.pcap)
c.ncache, _ = lru.New(c.ncap)
crr := &ResponseWriter{nil, c}
return c, crr
}
func TestCache(t *testing.T) {
c, crr := newTestCache(maxTTL)
log.SetOutput(ioutil.Discard)
for _, tc := range cacheTestCases {
m := tc.in.Msg()
m = cacheMsg(m, tc)
do := tc.in.Do
mt, _ := response.Typify(m)
k := key(m, mt, do)
crr.set(m, k, mt, c.pttl)
name := middleware.Name(m.Question[0].Name).Normalize()
qtype := m.Question[0].Qtype
i, ok, _ := c.get(name, qtype, do)
if ok && m.Truncated {
t.Errorf("Truncated message should not have been cached")
continue
}
if ok {
resp := i.toMsg(m)
if !test.Header(t, tc.Case, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc.Case, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc.Case, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc.Case, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
}

View File

@ -0,0 +1,96 @@
package cache
import (
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
)
// ServeDNS implements the middleware.Handler interface.
func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
qname := state.Name()
qtype := state.QType()
zone := middleware.Zones(c.Zones).Matches(qname)
if zone == "" {
return c.Next.ServeDNS(ctx, w, r)
}
do := state.Do() // TODO(): might need more from OPT record? Like the actual bufsize?
if i, ok, expired := c.get(qname, qtype, do); ok && !expired {
resp := i.toMsg(r)
state.SizeAndDo(resp)
resp, _ = state.Scrub(resp)
w.WriteMsg(resp)
return dns.RcodeSuccess, nil
}
crr := &ResponseWriter{w, c}
return middleware.NextOrFailure(c.Name(), c.Next, ctx, crr, r)
}
// Name implements the Handler interface.
func (c *Cache) Name() string { return "cache" }
func (c *Cache) get(qname string, qtype uint16, do bool) (*item, bool, bool) {
k := rawKey(qname, qtype, do)
if i, ok := c.ncache.Get(k); ok {
cacheHits.WithLabelValues(Denial).Inc()
return i.(*item), ok, i.(*item).expired(time.Now())
}
if i, ok := c.pcache.Get(k); ok {
cacheHits.WithLabelValues(Success).Inc()
return i.(*item), ok, i.(*item).expired(time.Now())
}
cacheMisses.Inc()
return nil, false, false
}
var (
cacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "size",
Help: "The number of elements in the cache.",
}, []string{"type"})
cacheCapacity = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "capacity",
Help: "The cache's capacity.",
}, []string{"type"})
cacheHits = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "hits_total",
Help: "The count of cache hits.",
}, []string{"type"})
cacheMisses = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "misses_total",
Help: "The count of cache misses.",
})
)
const subsystem = "cache"
func init() {
prometheus.MustRegister(cacheSize)
prometheus.MustRegister(cacheCapacity)
prometheus.MustRegister(cacheHits)
prometheus.MustRegister(cacheMisses)
}

View File

@ -0,0 +1,115 @@
package cache
import (
"time"
"github.com/coredns/coredns/middleware/pkg/response"
"github.com/miekg/dns"
)
type item struct {
Rcode int
Authoritative bool
AuthenticatedData bool
RecursionAvailable bool
Answer []dns.RR
Ns []dns.RR
Extra []dns.RR
origTTL uint32
stored time.Time
}
func newItem(m *dns.Msg, d time.Duration) *item {
i := new(item)
i.Rcode = m.Rcode
i.Authoritative = m.Authoritative
i.AuthenticatedData = m.AuthenticatedData
i.RecursionAvailable = m.RecursionAvailable
i.Answer = m.Answer
i.Ns = m.Ns
i.Extra = make([]dns.RR, len(m.Extra))
// Don't copy OPT record as these are hop-by-hop.
j := 0
for _, e := range m.Extra {
if e.Header().Rrtype == dns.TypeOPT {
continue
}
i.Extra[j] = e
j++
}
i.Extra = i.Extra[:j]
i.origTTL = uint32(d.Seconds())
i.stored = time.Now().UTC()
return i
}
// toMsg turns i into a message, it tailers the reply to m.
// The Authoritative bit is always set to 0, because the answer is from the cache.
func (i *item) toMsg(m *dns.Msg) *dns.Msg {
m1 := new(dns.Msg)
m1.SetReply(m)
m1.Authoritative = false
m1.AuthenticatedData = i.AuthenticatedData
m1.RecursionAvailable = i.RecursionAvailable
m1.Rcode = i.Rcode
m1.Compress = true
m1.Answer = i.Answer
m1.Ns = i.Ns
m1.Extra = i.Extra
ttl := int(i.origTTL) - int(time.Now().UTC().Sub(i.stored).Seconds())
setMsgTTL(m1, uint32(ttl))
return m1
}
func (i *item) expired(now time.Time) bool {
ttl := int(i.origTTL) - int(now.UTC().Sub(i.stored).Seconds())
return ttl < 0
}
// setMsgTTL sets the ttl on all RRs in all sections. If ttl is smaller than minTTL
// that value is used.
func setMsgTTL(m *dns.Msg, ttl uint32) {
if ttl < minTTL {
ttl = minTTL
}
for _, r := range m.Answer {
r.Header().Ttl = ttl
}
for _, r := range m.Ns {
r.Header().Ttl = ttl
}
for _, r := range m.Extra {
if r.Header().Rrtype == dns.TypeOPT {
continue
}
r.Header().Ttl = ttl
}
}
func minMsgTTL(m *dns.Msg, mt response.Type) time.Duration {
if mt != response.NoError && mt != response.NameError && mt != response.NoData {
return 0
}
minTTL := maxTTL
for _, r := range append(m.Answer, m.Ns...) {
switch mt {
case response.NameError, response.NoData:
if r.Header().Rrtype == dns.TypeSOA {
return time.Duration(r.(*dns.SOA).Minttl) * time.Second
}
case response.NoError, response.Delegation:
if r.Header().Ttl < uint32(minTTL.Seconds()) {
minTTL = time.Duration(r.Header().Ttl) * time.Second
}
}
}
return minTTL
}

View File

@ -0,0 +1,20 @@
package cache
import (
"testing"
"github.com/miekg/dns"
)
func TestKey(t *testing.T) {
if x := rawKey("miek.nl.", dns.TypeMX, false); x != "0miek.nl..15" {
t.Errorf("failed to create correct key, got %s", x)
}
if x := rawKey("miek.nl.", dns.TypeMX, true); x != "1miek.nl..15" {
t.Errorf("failed to create correct key, got %s", x)
}
// rawKey does not lowercase.
if x := rawKey("miEK.nL.", dns.TypeMX, true); x != "1miEK.nL..15" {
t.Errorf("failed to create correct key, got %s", x)
}
}

View File

@ -0,0 +1,137 @@
package cache
import (
"fmt"
"strconv"
"time"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/hashicorp/golang-lru"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("cache", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
ca, err := cacheParse(c)
if err != nil {
return middleware.Error("cache", err)
}
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
ca.Next = next
return ca
})
// Export the capacity for the metrics. This only happens once, because this is a re-load change only.
cacheCapacity.WithLabelValues(Success).Set(float64(ca.pcap))
cacheCapacity.WithLabelValues(Denial).Set(float64(ca.ncap))
return nil
}
func cacheParse(c *caddy.Controller) (*Cache, error) {
ca := &Cache{pcap: defaultCap, ncap: defaultCap, pttl: maxTTL, nttl: maxNTTL}
for c.Next() {
// cache [ttl] [zones..]
origins := make([]string, len(c.ServerBlockKeys))
copy(origins, c.ServerBlockKeys)
args := c.RemainingArgs()
if len(args) > 0 {
// first args may be just a number, then it is the ttl, if not it is a zone
ttl, err := strconv.Atoi(args[0])
if err == nil {
// Reserve 0 (and smaller for future things)
if ttl <= 0 {
return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", ttl)
}
ca.pttl = time.Duration(ttl) * time.Second
ca.nttl = time.Duration(ttl) * time.Second
args = args[1:]
}
if len(args) > 0 {
copy(origins, args)
}
}
// Refinements? In an extra block.
for c.NextBlock() {
switch c.Val() {
// first number is cap, second is an new ttl
case Success:
args := c.RemainingArgs()
if len(args) == 0 {
return nil, c.ArgErr()
}
pcap, err := strconv.Atoi(args[0])
if err != nil {
return nil, err
}
ca.pcap = pcap
if len(args) > 1 {
pttl, err := strconv.Atoi(args[1])
if err != nil {
return nil, err
}
// Reserve 0 (and smaller for future things)
if pttl <= 0 {
return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", pttl)
}
ca.pttl = time.Duration(pttl) * time.Second
}
case Denial:
args := c.RemainingArgs()
if len(args) == 0 {
return nil, c.ArgErr()
}
ncap, err := strconv.Atoi(args[0])
if err != nil {
return nil, err
}
ca.ncap = ncap
if len(args) > 1 {
nttl, err := strconv.Atoi(args[1])
if err != nil {
return nil, err
}
// Reserve 0 (and smaller for future things)
if nttl <= 0 {
return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", nttl)
}
ca.nttl = time.Duration(nttl) * time.Second
}
default:
return nil, c.ArgErr()
}
}
for i := range origins {
origins[i] = middleware.Host(origins[i]).Normalize()
}
var err error
ca.Zones = origins
ca.pcache, err = lru.New(ca.pcap)
if err != nil {
return nil, err
}
ca.ncache, err = lru.New(ca.ncap)
if err != nil {
return nil, err
}
return ca, nil
}
return nil, nil
}

View File

@ -0,0 +1,80 @@
package cache
import (
"testing"
"time"
"github.com/mholt/caddy"
)
func TestSetup(t *testing.T) {
tests := []struct {
input string
shouldErr bool
expectedNcap int
expectedPcap int
expectedNttl time.Duration
expectedPttl time.Duration
}{
{`cache`, false, defaultCap, defaultCap, maxNTTL, maxTTL},
{`cache {}`, false, defaultCap, defaultCap, maxNTTL, maxTTL},
{`cache example.nl {
success 10
}`, false, defaultCap, 10, maxNTTL, maxTTL},
{`cache example.nl {
success 10
denial 10 15
}`, false, 10, 10, 15 * time.Second, maxTTL},
{`cache 25 example.nl {
success 10
denial 10 15
}`, false, 10, 10, 15 * time.Second, 25 * time.Second},
{`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, maxTTL},
// fails
{`cache example.nl {
success
denial 10 15
}`, true, defaultCap, defaultCap, maxTTL, maxTTL},
{`cache example.nl {
success 15
denial aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL},
{`cache example.nl {
positive 15
negative aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL},
{`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL},
{`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL},
{`cache 1 example.nl {
positive 0
}`, true, defaultCap, defaultCap, maxTTL, maxTTL},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.input)
ca, err := cacheParse(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 && err != nil {
continue
}
if ca.ncap != test.expectedNcap {
t.Errorf("Test %v: Expected ncap %v but found: %v", i, test.expectedNcap, ca.ncap)
}
if ca.pcap != test.expectedPcap {
t.Errorf("Test %v: Expected pcap %v but found: %v", i, test.expectedPcap, ca.pcap)
}
if ca.nttl != test.expectedNttl {
t.Errorf("Test %v: Expected nttl %v but found: %v", i, test.expectedNttl, ca.nttl)
}
if ca.pttl != test.expectedPttl {
t.Errorf("Test %v: Expected pttl %v but found: %v", i, test.expectedPttl, ca.pttl)
}
}
}

View File

@ -0,0 +1,23 @@
# chaos
The *chaos* middleware allows CoreDNS to respond to TXT queries in the CH class.
This is useful for retrieving version or author information from the server.
## Syntax
~~~
chaos [VERSION] [AUTHORS...]
~~~
* **VERSION** is the version to return. Defaults to `CoreDNS-<version>`, if not set.
* **AUTHORS** is what authors to return. No default.
Note that you have to make sure that this middleware will get actual queries for the
following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and
`id.server`.
## Examples
~~~
chaos CoreDNS-001 "Miek Gieben" miek@miek.nl
~~~

View File

@ -0,0 +1,62 @@
// Package chaos implements a middleware that answer to 'CH version.bind TXT' type queries.
package chaos
import (
"os"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Chaos allows CoreDNS to reply to CH TXT queries and return author or
// version information.
type Chaos struct {
Next middleware.Handler
Version string
Authors map[string]bool
}
// ServeDNS implements the middleware.Handler interface.
func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
if state.QClass() != dns.ClassCHAOS || state.QType() != dns.TypeTXT {
return middleware.NextOrFailure(c.Name(), c.Next, ctx, w, r)
}
m := new(dns.Msg)
m.SetReply(r)
hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0}
switch state.Name() {
default:
return c.Next.ServeDNS(ctx, w, r)
case "authors.bind.":
for a := range c.Authors {
m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{trim(a)}})
}
case "version.bind.", "version.server.":
m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(c.Version)}}}
case "hostname.bind.", "id.server.":
hostname, err := os.Hostname()
if err != nil {
hostname = "localhost"
}
m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}}
}
state.SizeAndDo(m)
w.WriteMsg(m)
return 0, nil
}
// Name implements the Handler interface.
func (c Chaos) Name() string { return "chaos" }
func trim(s string) string {
if len(s) < 256 {
return s
}
return s[:255]
}

View File

@ -0,0 +1,80 @@
package chaos
import (
"testing"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
func TestChaos(t *testing.T) {
em := Chaos{
Version: version,
Authors: map[string]bool{"Miek Gieben": true},
}
tests := []struct {
next middleware.Handler
qname string
qtype uint16
expectedCode int
expectedReply string
expectedErr error
}{
{
next: test.NextHandler(dns.RcodeSuccess, nil),
qname: "version.bind",
expectedCode: dns.RcodeSuccess,
expectedReply: version,
expectedErr: nil,
},
{
next: test.NextHandler(dns.RcodeSuccess, nil),
qname: "authors.bind",
expectedCode: dns.RcodeSuccess,
expectedReply: "Miek Gieben",
expectedErr: nil,
},
{
next: test.NextHandler(dns.RcodeSuccess, nil),
qname: "authors.bind",
qtype: dns.TypeSRV,
expectedCode: dns.RcodeSuccess,
expectedErr: nil,
},
}
ctx := context.TODO()
for i, tc := range tests {
req := new(dns.Msg)
if tc.qtype == 0 {
tc.qtype = dns.TypeTXT
}
req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype)
req.Question[0].Qclass = dns.ClassCHAOS
em.Next = tc.next
rec := dnsrecorder.New(&test.ResponseWriter{})
code, err := em.ServeDNS(ctx, rec, req)
if err != tc.expectedErr {
t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err)
}
if code != int(tc.expectedCode) {
t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code)
}
if tc.expectedReply != "" {
answer := rec.Msg.Answer[0].(*dns.TXT).Txt[0]
if answer != tc.expectedReply {
t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.expectedReply, answer)
}
}
}
}
const version = "CoreDNS-001"

View File

@ -0,0 +1,51 @@
package chaos
import (
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("chaos", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
version, authors, err := chaosParse(c)
if err != nil {
return middleware.Error("chaos", err)
}
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
return Chaos{Next: next, Version: version, Authors: authors}
})
return nil
}
func chaosParse(c *caddy.Controller) (string, map[string]bool, error) {
version := ""
authors := make(map[string]bool)
for c.Next() {
args := c.RemainingArgs()
if len(args) == 0 {
return defaultVersion, nil, nil
}
if len(args) == 1 {
return args[0], nil, nil
}
version = args[0]
for _, a := range args[1:] {
authors[a] = true
}
return version, authors, nil
}
return version, authors, nil
}
var defaultVersion = caddy.AppName + "-" + caddy.AppVersion

View File

@ -0,0 +1,63 @@
package chaos
import (
"fmt"
"strings"
"testing"
"github.com/mholt/caddy"
)
func TestSetupChaos(t *testing.T) {
tests := []struct {
input string
shouldErr bool
expectedVersion string // expected version.
expectedAuthor string // expected author (string, although we get a map).
expectedErrContent string // substring from the expected error. Empty for positive cases.
}{
// positive
{
`chaos`, false, defaultVersion, "", "",
},
{
`chaos v2`, false, "v2", "", "",
},
{
`chaos v3 "Miek Gieben"`, false, "v3", "Miek Gieben", "",
},
{
fmt.Sprintf(`chaos {
%s
}`, defaultVersion), false, defaultVersion, "", "",
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.input)
version, authors, err := chaosParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
}
if err != nil {
if !test.shouldErr {
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
}
if !strings.Contains(err.Error(), test.expectedErrContent) {
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
}
}
if !test.shouldErr && version != test.expectedVersion {
t.Errorf("Chaos not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedVersion, version)
}
if !test.shouldErr && authors != nil {
if _, ok := authors[test.expectedAuthor]; !ok {
t.Errorf("Chaos not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedAuthor, "Miek Gieben")
}
}
}
}

View File

@ -0,0 +1,49 @@
# dnssec
*dnssec* enables on-the-fly DNSSEC signing of served data.
## Syntax
~~~
dnssec [ZONES...]
~~~
* **ZONES** zones that should be signed. If empty, the zones from the configuration block
are used.
If keys are not specified (see below), a key is generated and used for all signing operations. The
DNSSEC signing will treat this key a CSK (common signing key), forgoing the ZSK/KSK split. All
signing operations are done online. Authenticated denial of existence is implemented with NSEC black
lies. Using ECDSA as an algorithm is preferred as this leads to smaller signatures (compared to
RSA). NSEC3 is *not* supported.
A single signing key can be specified by using the `key` directive.
NOTE: Key generation has not been implemented yet.
TODO(miek): think about key rollovers, and how to do them automatically.
~~~
dnssec [ZONES... ] {
key file KEY...
cache_capacity CAPACITY
}
~~~
* `key file` indicates that key file(s) should be read from disk. When multiple keys are specified, RRsets
will be signed with all keys. Generating a key can be done with `dnssec-keygen`: `dnssec-keygen -a
ECDSAP256SHA256 <zonename>`. A key created for zone *A* can be safely used for zone *B*.
* `cache_capacity` indicates the capacity of the LRU cache. The dnssec middleware uses LRU cache to manage
objects and the default capacity is 10000.
## Metrics
If monitoring is enabled (via the *prometheus* directive) then the following metrics are exported:
* coredns_dnssec_cache_size{type} - total elements in the cache, type is "signature".
* coredns_dnssec_cache_capacity{type} - total capacity of the cache, type is "signature".
* coredns_dnssec_cache_hits_total - Counter of cache hits.
* coredns_dnssec_cache_misses_total - Counter of cache misses.
## Examples

View File

@ -0,0 +1,24 @@
package dnssec
import "github.com/miekg/dns"
// nsec returns an NSEC useful for NXDOMAIN respsones.
// See https://tools.ietf.org/html/draft-valsorda-dnsop-black-lies-00
// For example, a request for the non-existing name a.example.com would
// cause the following NSEC record to be generated:
// a.example.com. 3600 IN NSEC \000.a.example.com. ( RRSIG NSEC )
// This inturn makes every NXDOMAIN answer a NODATA one, don't forget to flip
// the header rcode to NOERROR.
func (d Dnssec) nsec(name, zone string, ttl, incep, expir uint32) ([]dns.RR, error) {
nsec := &dns.NSEC{}
nsec.Hdr = dns.RR_Header{Name: name, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeNSEC}
nsec.NextDomain = "\\000." + name
nsec.TypeBitMap = []uint16{dns.TypeRRSIG, dns.TypeNSEC}
sigs, err := d.sign([]dns.RR{nsec}, zone, ttl, incep, expir)
if err != nil {
return nil, err
}
return append(sigs, nsec), nil
}

View File

@ -0,0 +1,49 @@
package dnssec
import (
"testing"
"time"
"github.com/coredns/coredns/middleware/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestZoneSigningBlackLies(t *testing.T) {
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
defer rm1()
defer rm2()
m := testNxdomainMsg()
state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Ns, 2) {
t.Errorf("authority section should have 2 sig")
}
var nsec *dns.NSEC
for _, r := range m.Ns {
if r.Header().Rrtype == dns.TypeNSEC {
nsec = r.(*dns.NSEC)
}
}
if m.Rcode != dns.RcodeSuccess {
t.Errorf("expected rcode %d, got %d", dns.RcodeSuccess, m.Rcode)
}
if nsec == nil {
t.Fatalf("expected NSEC, got none")
}
if nsec.Hdr.Name != "ww.miek.nl." {
t.Errorf("expected %s, got %s", "ww.miek.nl.", nsec.Hdr.Name)
}
if nsec.NextDomain != "\\000.ww.miek.nl." {
t.Errorf("expected %s, got %s", "\\000.ww.miek.nl.", nsec.NextDomain)
}
}
func testNxdomainMsg() *dns.Msg {
return &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError},
Question: []dns.Question{{Name: "ww.miek.nl.", Qclass: dns.ClassINET, Qtype: dns.TypeTXT}},
Ns: []dns.RR{test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1461471181 14400 3600 604800 14400")},
}
}

View File

@ -0,0 +1,23 @@
package dnssec
import (
"hash/fnv"
"strconv"
"github.com/miekg/dns"
)
// Key serializes the RRset and return a signature cache key.
func key(rrs []dns.RR) string {
h := fnv.New64()
buf := make([]byte, 256)
for _, r := range rrs {
off, err := dns.PackRR(r, buf, 0, nil, false)
if err == nil {
h.Write(buf[:off])
}
}
i := h.Sum64()
return strconv.FormatUint(i, 10)
}

View File

@ -0,0 +1,35 @@
package dnssec
import (
"testing"
"time"
"github.com/coredns/coredns/middleware/test"
"github.com/coredns/coredns/request"
"github.com/hashicorp/golang-lru"
)
func TestCacheSet(t *testing.T) {
fPriv, rmPriv, _ := test.TempFile(".", privKey)
fPub, rmPub, _ := test.TempFile(".", pubKey)
defer rmPriv()
defer rmPub()
dnskey, err := ParseKeyFile(fPub, fPriv)
if err != nil {
t.Fatalf("failed to parse key: %v\n", err)
}
cache, _ := lru.New(defaultCap)
m := testMsg()
state := request.Request{Req: m}
k := key(m.Answer) // calculate *before* we add the sig
d := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, nil, cache)
m = d.Sign(state, "miek.nl.", time.Now().UTC())
_, ok := d.get(k)
if !ok {
t.Errorf("signature was not added to the cache")
}
}

View File

@ -0,0 +1,72 @@
package dnssec
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"errors"
"os"
"time"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// DNSKEY holds a DNSSEC public and private key used for on-the-fly signing.
type DNSKEY struct {
K *dns.DNSKEY
s crypto.Signer
keytag uint16
}
// ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other
// utilities. It adds ".key" for the public key and ".private" for the private key.
func ParseKeyFile(pubFile, privFile string) (*DNSKEY, error) {
f, e := os.Open(pubFile)
if e != nil {
return nil, e
}
k, e := dns.ReadRR(f, pubFile)
if e != nil {
return nil, e
}
f, e = os.Open(privFile)
if e != nil {
return nil, e
}
p, e := k.(*dns.DNSKEY).ReadPrivateKey(f, privFile)
if e != nil {
return nil, e
}
if v, ok := p.(*rsa.PrivateKey); ok {
return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil
}
if v, ok := p.(*ecdsa.PrivateKey); ok {
return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil
}
return &DNSKEY{k.(*dns.DNSKEY), nil, 0}, errors.New("no known? private key found")
}
// getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true.
func (d Dnssec) getDNSKEY(state request.Request, zone string, do bool) *dns.Msg {
keys := make([]dns.RR, len(d.keys))
for i, k := range d.keys {
keys[i] = dns.Copy(k.K)
keys[i].Header().Name = zone
}
m := new(dns.Msg)
m.SetReply(state.Req)
m.Answer = keys
if !do {
return m
}
incep, expir := incepExpir(time.Now().UTC())
if sigs, err := d.sign(keys, zone, 3600, incep, expir); err == nil {
m.Answer = append(m.Answer, sigs...)
}
return m
}

View File

@ -0,0 +1,134 @@
// Package dnssec implements a middleware that signs responses on-the-fly using
// NSEC black lies.
package dnssec
import (
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/response"
"github.com/coredns/coredns/middleware/pkg/singleflight"
"github.com/coredns/coredns/request"
"github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
)
// Dnssec signs the reply on-the-fly.
type Dnssec struct {
Next middleware.Handler
zones []string
keys []*DNSKEY
inflight *singleflight.Group
cache *lru.Cache
}
// New returns a new Dnssec.
func New(zones []string, keys []*DNSKEY, next middleware.Handler, cache *lru.Cache) Dnssec {
return Dnssec{Next: next,
zones: zones,
keys: keys,
cache: cache,
inflight: new(singleflight.Group),
}
}
// Sign signs the message in state. it takes care of negative or nodata responses. It
// uses NSEC black lies for authenticated denial of existence. Signatures
// creates will be cached for a short while. By default we sign for 8 days,
// starting 3 hours ago.
func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg {
req := state.Req
mt, _ := response.Typify(req) // TODO(miek): need opt record here?
if mt == response.Delegation {
return req
}
incep, expir := incepExpir(now)
if mt == response.NameError {
if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 {
return req
}
ttl := req.Ns[0].Header().Ttl
if sigs, err := d.sign(req.Ns, zone, ttl, incep, expir); err == nil {
req.Ns = append(req.Ns, sigs...)
}
if sigs, err := d.nsec(state.Name(), zone, ttl, incep, expir); err == nil {
req.Ns = append(req.Ns, sigs...)
}
if len(req.Ns) > 1 { // actually added nsec and sigs, reset the rcode
req.Rcode = dns.RcodeSuccess
}
return req
}
for _, r := range rrSets(req.Answer) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
req.Answer = append(req.Answer, sigs...)
}
}
for _, r := range rrSets(req.Ns) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
req.Ns = append(req.Ns, sigs...)
}
}
for _, r := range rrSets(req.Extra) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
req.Extra = append(sigs, req.Extra...) // prepend to leave OPT alone
}
}
return req
}
func (d Dnssec) sign(rrs []dns.RR, signerName string, ttl, incep, expir uint32) ([]dns.RR, error) {
k := key(rrs)
sgs, ok := d.get(k)
if ok {
return sgs, nil
}
sigs, err := d.inflight.Do(k, func() (interface{}, error) {
sigs := make([]dns.RR, len(d.keys))
var e error
for i, k := range d.keys {
sig := k.newRRSIG(signerName, ttl, incep, expir)
e = sig.Sign(k.s, rrs)
sigs[i] = sig
}
d.set(k, sigs)
return sigs, e
})
return sigs.([]dns.RR), err
}
func (d Dnssec) set(key string, sigs []dns.RR) {
d.cache.Add(key, sigs)
}
func (d Dnssec) get(key string) ([]dns.RR, bool) {
if s, ok := d.cache.Get(key); ok {
cacheHits.Inc()
return s.([]dns.RR), true
}
cacheMisses.Inc()
return nil, false
}
func incepExpir(now time.Time) (uint32, uint32) {
incep := uint32(now.Add(-3 * time.Hour).Unix()) // -(2+1) hours, be sure to catch daylight saving time and such
expir := uint32(now.Add(eightDays).Unix()) // sign for 8 days
return incep, expir
}
const (
eightDays = 8 * 24 * time.Hour
defaultCap = 10000 // default capacity of the cache.
)

View File

@ -0,0 +1,195 @@
package dnssec
import (
"testing"
"time"
"github.com/coredns/coredns/middleware/test"
"github.com/coredns/coredns/request"
"github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
)
func TestZoneSigning(t *testing.T) {
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
defer rm1()
defer rm2()
m := testMsg()
state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig")
}
if !section(m.Ns, 1) {
t.Errorf("authority section should have 1 sig")
}
}
func TestZoneSigningDouble(t *testing.T) {
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
defer rm1()
defer rm2()
fPriv1, rmPriv1, _ := test.TempFile(".", privKey1)
fPub1, rmPub1, _ := test.TempFile(".", pubKey1)
defer rmPriv1()
defer rmPub1()
key1, err := ParseKeyFile(fPub1, fPriv1)
if err != nil {
t.Fatalf("failed to parse key: %v\n", err)
}
d.keys = append(d.keys, key1)
m := testMsg()
state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 2) {
t.Errorf("answer section should have 1 sig")
}
if !section(m.Ns, 2) {
t.Errorf("authority section should have 1 sig")
}
}
// TestSigningDifferentZone tests if a key for miek.nl and be used for example.org.
func TestSigningDifferentZone(t *testing.T) {
fPriv, rmPriv, _ := test.TempFile(".", privKey)
fPub, rmPub, _ := test.TempFile(".", pubKey)
defer rmPriv()
defer rmPub()
key, err := ParseKeyFile(fPub, fPriv)
if err != nil {
t.Fatalf("failed to parse key: %v\n", err)
}
m := testMsgEx()
state := request.Request{Req: m}
cache, _ := lru.New(defaultCap)
d := New([]string{"example.org."}, []*DNSKEY{key}, nil, cache)
m = d.Sign(state, "example.org.", time.Now().UTC())
if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig")
t.Logf("%+v\n", m)
}
if !section(m.Ns, 1) {
t.Errorf("authority section should have 1 sig")
t.Logf("%+v\n", m)
}
}
func TestSigningCname(t *testing.T) {
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
defer rm1()
defer rm2()
m := testMsgCname()
state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig")
}
}
func TestZoneSigningDelegation(t *testing.T) {
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
defer rm1()
defer rm2()
m := testDelegationMsg()
state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Ns, 0) {
t.Errorf("authority section should have 0 sig")
t.Logf("%v\n", m)
}
if !section(m.Extra, 0) {
t.Errorf("answer section should have 0 sig")
t.Logf("%v\n", m)
}
}
func section(rss []dns.RR, nrSigs int) bool {
i := 0
for _, r := range rss {
if r.Header().Rrtype == dns.TypeRRSIG {
i++
}
}
return nrSigs == i
}
func testMsg() *dns.Msg {
// don't care about the message header
return &dns.Msg{
Answer: []dns.RR{test.MX("miek.nl. 1703 IN MX 1 aspmx.l.google.com.")},
Ns: []dns.RR{test.NS("miek.nl. 1703 IN NS omval.tednet.nl.")},
}
}
func testMsgEx() *dns.Msg {
return &dns.Msg{
Answer: []dns.RR{test.MX("example.org. 1703 IN MX 1 aspmx.l.google.com.")},
Ns: []dns.RR{test.NS("example.org. 1703 IN NS omval.tednet.nl.")},
}
}
func testMsgCname() *dns.Msg {
return &dns.Msg{
Answer: []dns.RR{test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl.")},
}
}
func testDelegationMsg() *dns.Msg {
return &dns.Msg{
Ns: []dns.RR{
test.NS("miek.nl. 3600 IN NS linode.atoom.net."),
test.NS("miek.nl. 3600 IN NS ns-ext.nlnetlabs.nl."),
test.NS("miek.nl. 3600 IN NS omval.tednet.nl."),
},
Extra: []dns.RR{
test.A("omval.tednet.nl. 3600 IN A 185.49.141.42"),
test.AAAA("omval.tednet.nl. 3600 IN AAAA 2a04:b900:0:100::42"),
},
}
}
func newDnssec(t *testing.T, zones []string) (Dnssec, func(), func()) {
k, rm1, rm2 := newKey(t)
cache, _ := lru.New(defaultCap)
d := New(zones, []*DNSKEY{k}, nil, cache)
return d, rm1, rm2
}
func newKey(t *testing.T) (*DNSKEY, func(), func()) {
fPriv, rmPriv, _ := test.TempFile(".", privKey)
fPub, rmPub, _ := test.TempFile(".", pubKey)
key, err := ParseKeyFile(fPub, fPriv)
if err != nil {
t.Fatalf("failed to parse key: %v\n", err)
}
return key, rmPriv, rmPub
}
const (
pubKey = `miek.nl. IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4BXPP3gwhetiOUMnGA+x09nqzgF5IY OyjWB7N3rXqQbnOSILhH1hnuyh7mmA==`
privKey = `Private-key-format: v1.3
Algorithm: 13 (ECDSAP256SHA256)
PrivateKey: /4BZk8AFvyW5hL3cOLSVxIp1RTqHSAEloWUxj86p3gs=
Created: 20160423195532
Publish: 20160423195532
Activate: 20160423195532
`
pubKey1 = `example.org. IN DNSKEY 257 3 13 tVRWNSGpHZbCi7Pr7OmbADVUO3MxJ0Lb8Lk3o/HBHqCxf5K/J50lFqRa 98lkdAIiFOVRy8LyMvjwmxZKwB5MNw==`
privKey1 = `Private-key-format: v1.3
Algorithm: 13 (ECDSAP256SHA256)
PrivateKey: i8j4OfDGT8CQt24SDwLz2hg9yx4qKOEOh1LvbAuSp1c=
Created: 20160423211746
Publish: 20160423211746
Activate: 20160423211746
`
)

View File

@ -0,0 +1,82 @@
package dnssec
import (
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
)
// ServeDNS implements the middleware.Handler interface.
func (d Dnssec) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
do := state.Do()
qname := state.Name()
qtype := state.QType()
zone := middleware.Zones(d.zones).Matches(qname)
if zone == "" {
return middleware.NextOrFailure(d.Name(), d.Next, ctx, w, r)
}
// Intercept queries for DNSKEY, but only if one of the zones matches the qname, otherwise we let
// the query through.
if qtype == dns.TypeDNSKEY {
for _, z := range d.zones {
if qname == z {
resp := d.getDNSKEY(state, z, do)
resp.Authoritative = true
state.SizeAndDo(resp)
w.WriteMsg(resp)
return dns.RcodeSuccess, nil
}
}
}
drr := &ResponseWriter{w, d}
return middleware.NextOrFailure(d.Name(), d.Next, ctx, drr, r)
}
var (
cacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "cache_size",
Help: "The number of elements in the dnssec cache.",
}, []string{"type"})
cacheCapacity = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "cache_capacity",
Help: "The dnssec cache's capacity.",
}, []string{"type"})
cacheHits = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "cache_hits_total",
Help: "The count of cache hits.",
})
cacheMisses = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: middleware.Namespace,
Subsystem: subsystem,
Name: "cache_misses_total",
Help: "The count of cache misses.",
})
)
// Name implements the Handler interface.
func (d Dnssec) Name() string { return "dnssec" }
const subsystem = "dnssec"
func init() {
prometheus.MustRegister(cacheSize)
prometheus.MustRegister(cacheCapacity)
prometheus.MustRegister(cacheHits)
prometheus.MustRegister(cacheMisses)
}

View File

@ -0,0 +1,188 @@
package dnssec
import (
"sort"
"strings"
"testing"
"github.com/coredns/coredns/middleware/file"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var dnssecTestCases = []test.Case{
{
Qname: "miek.nl.", Qtype: dns.TypeDNSKEY,
Answer: []dns.RR{
test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"),
},
},
{
Qname: "miek.nl.", Qtype: dns.TypeDNSKEY, Do: true,
Answer: []dns.RR{
test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"),
test.RRSIG("miek.nl. 3600 IN RRSIG DNSKEY 13 2 3600 20160503150844 20160425120844 18512 miek.nl. Iw/kNOyM"),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
}
var dnsTestCases = []test.Case{
{
Qname: "miek.nl.", Qtype: dns.TypeDNSKEY,
Answer: []dns.RR{
test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"),
},
},
{
Qname: "miek.nl.", Qtype: dns.TypeMX,
Answer: []dns.RR{
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
},
Ns: []dns.RR{
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
},
},
{
Qname: "miek.nl.", Qtype: dns.TypeMX, Do: true,
Answer: []dns.RR{
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
test.RRSIG("miek.nl. 1800 IN RRSIG MX 13 2 3600 20160503192428 20160425162428 18512 miek.nl. 4nxuGKitXjPVA9zP1JIUvA09"),
},
Ns: []dns.RR{
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
test.RRSIG("miek.nl. 1800 IN RRSIG NS 13 2 3600 20161217114912 20161209084912 18512 miek.nl. ad9gA8VWgF1H8ze9/0Rk2Q=="),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "www.miek.nl.", Qtype: dns.TypeAAAA, Do: true,
Answer: []dns.RR{
test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"),
test.RRSIG("a.miek.nl. 1800 IN RRSIG AAAA 13 3 3600 20160503193047 20160425163047 18512 miek.nl. UAyMG+gcnoXW3"),
test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."),
test.RRSIG("www.miek.nl. 1800 IN RRSIG CNAME 13 3 3600 20160503193047 20160425163047 18512 miek.nl. E3qGZn"),
},
Ns: []dns.RR{
test.NS("miek.nl. 1800 IN NS linode.atoom.net."),
test.RRSIG("miek.nl. 1800 IN RRSIG NS 13 2 3600 20161217114912 20161209084912 18512 miek.nl. ad9gA8VWgF1H8ze9/0Rk2Q=="),
},
Extra: []dns.RR{test.OPT(4096, true)},
},
{
Qname: "www.example.org.", Qtype: dns.TypeAAAA, Do: true,
Rcode: dns.RcodeServerFailure,
// Extra: []dns.RR{test.OPT(4096, true)}, // test.ErrorHandler is a simple handler that does not do EDNS.
},
}
func TestLookupZone(t *testing.T) {
zone, err := file.Parse(strings.NewReader(dbMiekNL), "miek.nl.", "stdin")
if err != nil {
return
}
fm := file.File{Next: test.ErrorHandler(), Zones: file.Zones{Z: map[string]*file.Zone{"miek.nl.": zone}, Names: []string{"miek.nl."}}}
dnskey, rm1, rm2 := newKey(t)
defer rm1()
defer rm2()
cache, _ := lru.New(defaultCap)
dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, fm, cache)
ctx := context.TODO()
for _, tc := range dnsTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := dh.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
return
}
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
func TestLookupDNSKEY(t *testing.T) {
dnskey, rm1, rm2 := newKey(t)
defer rm1()
defer rm2()
cache, _ := lru.New(defaultCap)
dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, test.ErrorHandler(), cache)
ctx := context.TODO()
for _, tc := range dnssecTestCases {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := dh.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
return
}
resp := rec.Msg
if !resp.Authoritative {
t.Errorf("Authoritative Answer should be true, got false")
}
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
const dbMiekNL = `
$TTL 30M
$ORIGIN miek.nl.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
IN NS linode.atoom.net.
IN MX 1 aspmx.l.google.com.
IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
a IN A 139.162.196.78
IN AAAA 2a01:7e00::f03c:91ff:fef1:6735
www IN CNAME a`

View File

@ -0,0 +1,49 @@
package dnssec
import (
"log"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// ResponseWriter sign the response on the fly.
type ResponseWriter struct {
dns.ResponseWriter
d Dnssec
}
// WriteMsg implements the dns.ResponseWriter interface.
func (d *ResponseWriter) WriteMsg(res *dns.Msg) error {
// By definition we should sign anything that comes back, we should still figure out for
// which zone it should be.
state := request.Request{W: d.ResponseWriter, Req: res}
qname := state.Name()
zone := middleware.Zones(d.d.zones).Matches(qname)
if zone == "" {
return d.ResponseWriter.WriteMsg(res)
}
if state.Do() {
res = d.d.Sign(state, zone, time.Now().UTC())
cacheSize.WithLabelValues("signature").Set(float64(d.d.cache.Len()))
}
state.SizeAndDo(res)
return d.ResponseWriter.WriteMsg(res)
}
// Write implements the dns.ResponseWriter interface.
func (d *ResponseWriter) Write(buf []byte) (int, error) {
log.Printf("[WARNING] Dnssec called with Write: not signing reply")
n, err := d.ResponseWriter.Write(buf)
return n, err
}
// Hijack implements the dns.ResponseWriter interface.
func (d *ResponseWriter) Hijack() { d.ResponseWriter.Hijack() }

View File

@ -0,0 +1,53 @@
package dnssec
import "github.com/miekg/dns"
// newRRSIG return a new RRSIG, with all fields filled out, except the signed data.
func (k *DNSKEY) newRRSIG(signerName string, ttl, incep, expir uint32) *dns.RRSIG {
sig := new(dns.RRSIG)
sig.Hdr.Rrtype = dns.TypeRRSIG
sig.Algorithm = k.K.Algorithm
sig.KeyTag = k.keytag
sig.SignerName = signerName
sig.Hdr.Ttl = ttl
sig.OrigTtl = origTTL
sig.Inception = incep
sig.Expiration = expir
return sig
}
type rrset struct {
qname string
qtype uint16
}
// rrSets returns rrs as a map of RRsets. It skips RRSIG and OPT records as those don't need to be signed.
func rrSets(rrs []dns.RR) map[rrset][]dns.RR {
m := make(map[rrset][]dns.RR)
for _, r := range rrs {
if r.Header().Rrtype == dns.TypeRRSIG || r.Header().Rrtype == dns.TypeOPT {
continue
}
if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok {
s = append(s, r)
m[rrset{r.Header().Name, r.Header().Rrtype}] = s
continue
}
s := make([]dns.RR, 1, 3)
s[0] = r
m[rrset{r.Header().Name, r.Header().Rrtype}] = s
}
if len(m) > 0 {
return m
}
return nil
}
const origTTL = 3600

View File

@ -0,0 +1,112 @@
package dnssec
import (
"strconv"
"strings"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/hashicorp/golang-lru"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("dnssec", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
zones, keys, capacity, err := dnssecParse(c)
if err != nil {
return middleware.Error("dnssec", err)
}
cache, err := lru.New(capacity)
if err != nil {
return err
}
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
return New(zones, keys, next, cache)
})
// Export the capacity for the metrics. This only happens once, because this is a re-load change only.
cacheCapacity.WithLabelValues("signature").Set(float64(capacity))
return nil
}
func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
zones := []string{}
keys := []*DNSKEY{}
capacity := defaultCap
for c.Next() {
if c.Val() == "dnssec" {
// dnssec [zones...]
zones = make([]string, len(c.ServerBlockKeys))
copy(zones, c.ServerBlockKeys)
args := c.RemainingArgs()
if len(args) > 0 {
zones = args
}
for c.NextBlock() {
switch c.Val() {
case "key":
k, e := keyParse(c)
if e != nil {
return nil, nil, 0, e
}
keys = append(keys, k...)
case "cache_capacity":
if !c.NextArg() {
return nil, nil, 0, c.ArgErr()
}
value := c.Val()
cacheCap, err := strconv.Atoi(value)
if err != nil {
return nil, nil, 0, err
}
capacity = cacheCap
}
}
}
}
for i := range zones {
zones[i] = middleware.Host(zones[i]).Normalize()
}
return zones, keys, capacity, nil
}
func keyParse(c *caddy.Controller) ([]*DNSKEY, error) {
keys := []*DNSKEY{}
if !c.NextArg() {
return nil, c.ArgErr()
}
value := c.Val()
if value == "file" {
ks := c.RemainingArgs()
for _, k := range ks {
base := k
// Kmiek.nl.+013+26205.key, handle .private or without extension: Kmiek.nl.+013+26205
if strings.HasSuffix(k, ".key") {
base = k[:len(k)-4]
}
if strings.HasSuffix(k, ".private") {
base = k[:len(k)-8]
}
k, err := ParseKeyFile(base+".key", base+".private")
if err != nil {
return nil, err
}
keys = append(keys, k)
}
}
return keys, nil
}

View File

@ -0,0 +1,65 @@
package dnssec
import (
"strings"
"testing"
"github.com/mholt/caddy"
)
func TestSetupDnssec(t *testing.T) {
tests := []struct {
input string
shouldErr bool
expectedZones []string
expectedKeys []string
expectedCapacity int
expectedErrContent string
}{
{
`dnssec`, false, nil, nil, defaultCap, "",
},
{
`dnssec miek.nl`, false, []string{"miek.nl."}, nil, defaultCap, "",
},
{
`dnssec miek.nl {
cache_capacity 100
}`, false, []string{"miek.nl."}, nil, 100, "",
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.input)
zones, keys, capacity, err := dnssecParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
}
if err != nil {
if !test.shouldErr {
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
}
if !strings.Contains(err.Error(), test.expectedErrContent) {
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
}
}
if !test.shouldErr {
for i, z := range test.expectedZones {
if zones[i] != z {
t.Errorf("Dnssec not correctly set for input %s. Expected: %s, actual: %s", test.input, z, zones[i])
}
}
for i, k := range test.expectedKeys {
if k != keys[i].K.Header().Name {
t.Errorf("Dnssec not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, k, keys[i].K.Header().Name)
}
}
if capacity != test.expectedCapacity {
t.Errorf("Dnssec not correctly set capacity for input '%s' Expected: '%d', actual: '%d'", test.input, capacity, test.expectedCapacity)
}
}
}
}

View File

@ -0,0 +1,45 @@
# erratic
*erratic* is a middleware useful for testing client behavior. It returns a static response to all
queries, but the responses can be delayed by a random amount of time or dropped all together, i.e.
no answer at all.
~~~ txt
._<transport>.qname. 0 IN SRV 0 0 <port> .
~~~
The *erratic* middleware will respond to every A or AAAA query. For any other type it will return
a SERVFAIL response. The reply for A will return 192.0.2.53 (see RFC 5737), for AAAA it returns
2001:DB8::53 (see RFC 3849).
## Syntax
~~~ txt
erratic {
drop AMOUNT
}
~~~
* **AMOUNT** drop 1 per **AMOUNT** of the queries, the default is 2.
## Examples
~~~ txt
.:53 {
erratic {
drop 3
}
}
~~~
Or even shorter if the defaults suits you:
~~~ txt
. {
erratic
}
~~~
## Bugs
Delaying answers is not implemented.

View File

@ -0,0 +1,74 @@
// Package erratic implements a middleware that returns erratic answers (delayed, dropped).
package erratic
import (
"sync/atomic"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Erratic is a middleware that returns erratic repsonses to each client.
type Erratic struct {
amount uint64
q uint64 // counter of queries
}
// ServeDNS implements the middleware.Handler interface.
func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
drop := false
if e.amount > 0 {
queryNr := atomic.LoadUint64(&e.q)
if queryNr%e.amount == 0 {
drop = true
}
atomic.AddUint64(&e.q, 1)
}
m := new(dns.Msg)
m.SetReply(r)
m.Compress = true
m.Authoritative = true
// small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername
// in a non-racy way.
switch state.QType() {
case dns.TypeA:
rr := *(rrA.(*dns.A))
rr.Header().Name = state.QName()
m.Answer = append(m.Answer, &rr)
case dns.TypeAAAA:
rr := *(rrAAAA.(*dns.AAAA))
rr.Header().Name = state.QName()
m.Answer = append(m.Answer, &rr)
default:
if !drop {
// coredns will return error.
return dns.RcodeServerFailure, nil
}
}
if drop {
return 0, nil
}
state.SizeAndDo(m)
w.WriteMsg(m)
return 0, nil
}
// Name implements the Handler interface.
func (e *Erratic) Name() string { return "erratic" }
var (
rrA, _ = dns.NewRR(". IN 0 A 192.0.2.53")
rrAAAA, _ = dns.NewRR(". IN 0 AAAA 2001:DB8::53")
)

View File

@ -0,0 +1,45 @@
package erratic
import (
"testing"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
func TestErraticDrop(t *testing.T) {
e := &Erratic{amount: 2} // 50% drops
tests := []struct {
expectedCode int
expectedErr error
drop bool
}{
{expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true},
{expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: false},
}
ctx := context.TODO()
for i, tc := range tests {
req := new(dns.Msg)
req.SetQuestion("example.org.", dns.TypeA)
rec := dnsrecorder.New(&test.ResponseWriter{})
code, err := e.ServeDNS(ctx, rec, req)
if err != tc.expectedErr {
t.Errorf("Test %d: Expected error %q, but got %q", i, tc.expectedErr, err)
}
if code != int(tc.expectedCode) {
t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code)
}
if tc.drop && rec.Msg != nil {
t.Errorf("Test %d: Expected dropped packet, but got %q", i, rec.Msg.Question[0].Name)
}
}
}

View File

@ -0,0 +1,59 @@
package erratic
import (
"fmt"
"strconv"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("erratic", caddy.Plugin{
ServerType: "dns",
Action: setupErratic,
})
}
func setupErratic(c *caddy.Controller) error {
e, err := parseErratic(c)
if err != nil {
return middleware.Error("erratic", err)
}
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
return e
})
return nil
}
func parseErratic(c *caddy.Controller) (*Erratic, error) {
e := &Erratic{amount: 2}
for c.Next() { // 'erratic'
for c.NextBlock() {
switch c.Val() {
case "drop":
args := c.RemainingArgs()
if len(args) > 1 {
return nil, c.ArgErr()
}
if len(args) == 0 {
return nil, nil
}
amount, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return nil, err
}
if amount < 0 {
return nil, fmt.Errorf("illegal amount value given %q", args[0])
}
e.amount = uint64(amount)
}
}
}
return e, nil
}

View File

@ -0,0 +1,28 @@
package erratic
import (
"testing"
"github.com/mholt/caddy"
)
func TestSetupWhoami(t *testing.T) {
c := caddy.NewTestController("dns", `erratic {
drop
}`)
if err := setupErratic(c); err != nil {
t.Fatalf("Test 1, expected no errors, but got: %q", err)
}
c = caddy.NewTestController("dns", `erratic`)
if err := setupErratic(c); err != nil {
t.Fatalf("Test 2, expected no errors, but got: %q", err)
}
c = caddy.NewTestController("dns", `erratic {
drop -1
}`)
if err := setupErratic(c); err == nil {
t.Fatalf("Test 4, expected errors, but got: %q", err)
}
}

View File

@ -0,0 +1,40 @@
# errors
*errors* enables error logging.
TODO: what are errors.
## Syntax
~~~
errors [LOGFILE]
~~~
* **LOGFILE** is the path to the error log file to create (or append to), relative to the current
working directory. It can also be `stdout` or `stderr` to write to the console, syslog to write to the
system log (except on Windows), or visible to write the error (including full stack trace, if
applicable) to the response. Writing errors to the response is NOT advised except in local debug
situations. The default is stderr. The above syntax will simply enable error reporting on the
server. To specify custom error pages, open a block:
~~~
errors {
what where
}
~~~
* `what` can only be `log`.
* `where` is the path to the log file (as described above) and you can enable rotation to manage the log files.
## Examples
Log errors into a file in the parent directory:
~~~
errors ../error.log
~~~
Make errors visible to the client (for debugging only):
~~~
errors visible
~~~

View File

@ -0,0 +1,109 @@
// Package errors implements an HTTP error handling middleware.
package errors
import (
"fmt"
"log"
"runtime"
"strings"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// errorHandler handles DNS errors (and errors from other middleware).
type errorHandler struct {
Next middleware.Handler
LogFile string
Log *log.Logger
Debug bool // if true, errors are written out to client rather than to a log
}
// ServeDNS implements the middleware.Handler interface.
func (h errorHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
defer h.recovery(ctx, w, r)
rcode, err := middleware.NextOrFailure(h.Name(), h.Next, ctx, w, r)
if err != nil {
state := request.Request{W: w, Req: r}
errMsg := fmt.Sprintf("%s [ERROR %d %s %s] %v", time.Now().Format(timeFormat), rcode, state.Name(), state.Type(), err)
if h.Debug {
// Write error to response as a txt message instead of to log
answer := debugMsg(rcode, r)
txt, _ := dns.NewRR(". IN 0 TXT " + errMsg)
answer.Answer = append(answer.Answer, txt)
state.SizeAndDo(answer)
w.WriteMsg(answer)
return 0, err
}
h.Log.Println(errMsg)
}
return rcode, err
}
func (h errorHandler) Name() string { return "errors" }
func (h errorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
rec := recover()
if rec == nil {
return
}
state := request.Request{W: w, Req: r}
// Obtain source of panic
// From: https://gist.github.com/swdunlop/9629168
var name, file string // function name, file name
var line int
var pc [16]uintptr
n := runtime.Callers(3, pc[:])
for _, pc := range pc[:n] {
fn := runtime.FuncForPC(pc)
if fn == nil {
continue
}
file, line = fn.FileLine(pc)
name = fn.Name()
if !strings.HasPrefix(name, "runtime.") {
break
}
}
// Trim file path
delim := "/coredns/"
pkgPathPos := strings.Index(file, delim)
if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) {
file = file[pkgPathPos+len(delim):]
}
panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec)
if h.Debug {
// Write error and stack trace to the response rather than to a log
var stackBuf [4096]byte
stack := stackBuf[:runtime.Stack(stackBuf[:], false)]
answer := debugMsg(dns.RcodeServerFailure, r)
// add stack buf in TXT, limited to 255 chars for now.
txt, _ := dns.NewRR(". IN 0 TXT " + string(stack[:255]))
answer.Answer = append(answer.Answer, txt)
state.SizeAndDo(answer)
w.WriteMsg(answer)
} else {
// Currently we don't use the function name, since file:line is more conventional
h.Log.Printf(panicMsg)
}
}
// debugMsg creates a debug message that gets send back to the client.
func debugMsg(rcode int, r *dns.Msg) *dns.Msg {
answer := new(dns.Msg)
answer.SetRcode(r, rcode)
return answer
}
const timeFormat = "02/Jan/2006:15:04:05 -0700"

View File

@ -0,0 +1,97 @@
package errors
import (
"bytes"
"errors"
"fmt"
"log"
"strings"
"testing"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
func TestErrors(t *testing.T) {
buf := bytes.Buffer{}
em := errorHandler{Log: log.New(&buf, "", 0)}
testErr := errors.New("test error")
tests := []struct {
next middleware.Handler
expectedCode int
expectedLog string
expectedErr error
}{
{
next: genErrorHandler(dns.RcodeSuccess, nil),
expectedCode: dns.RcodeSuccess,
expectedLog: "",
expectedErr: nil,
},
{
next: genErrorHandler(dns.RcodeNotAuth, testErr),
expectedCode: dns.RcodeNotAuth,
expectedLog: fmt.Sprintf("[ERROR %d %s] %v\n", dns.RcodeNotAuth, "example.org. A", testErr),
expectedErr: testErr,
},
}
ctx := context.TODO()
req := new(dns.Msg)
req.SetQuestion("example.org.", dns.TypeA)
for i, tc := range tests {
em.Next = tc.next
buf.Reset()
rec := dnsrecorder.New(&test.ResponseWriter{})
code, err := em.ServeDNS(ctx, rec, req)
if err != tc.expectedErr {
t.Errorf("Test %d: Expected error %v, but got %v",
i, tc.expectedErr, err)
}
if code != tc.expectedCode {
t.Errorf("Test %d: Expected status code %d, but got %d",
i, tc.expectedCode, code)
}
if log := buf.String(); !strings.Contains(log, tc.expectedLog) {
t.Errorf("Test %d: Expected log %q, but got %q",
i, tc.expectedLog, log)
}
}
}
func TestVisibleErrorWithPanic(t *testing.T) {
const panicMsg = "I'm a panic"
eh := errorHandler{
Debug: true,
Next: middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
panic(panicMsg)
}),
}
ctx := context.TODO()
req := new(dns.Msg)
req.SetQuestion("example.org.", dns.TypeA)
rec := dnsrecorder.New(&test.ResponseWriter{})
code, err := eh.ServeDNS(ctx, rec, req)
if code != 0 {
t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code)
}
if err != nil {
t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err)
}
}
func genErrorHandler(rcode int, err error) middleware.Handler {
return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
return rcode, err
})
}

View File

@ -0,0 +1,111 @@
package errors
import (
"io"
"log"
"os"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/hashicorp/go-syslog"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("errors", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
handler, err := errorsParse(c)
if err != nil {
return middleware.Error("errors", err)
}
var writer io.Writer
switch handler.LogFile {
case "visible":
handler.Debug = true
case "stdout":
writer = os.Stdout
case "stderr":
writer = os.Stderr
case "syslog":
writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "coredns")
if err != nil {
return middleware.Error("errors", err)
}
default:
if handler.LogFile == "" {
writer = os.Stderr // default
break
}
var file *os.File
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return middleware.Error("errors", err)
}
writer = file
}
handler.Log = log.New(writer, "", 0)
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
handler.Next = next
return handler
})
return nil
}
func errorsParse(c *caddy.Controller) (errorHandler, error) {
handler := errorHandler{}
optionalBlock := func() (bool, error) {
var hadBlock bool
for c.NextBlock() {
hadBlock = true
what := c.Val()
if !c.NextArg() {
return hadBlock, c.ArgErr()
}
where := c.Val()
if what == "log" {
if where == "visible" {
handler.Debug = true
} else {
handler.LogFile = where
}
}
}
return hadBlock, nil
}
for c.Next() {
// Configuration may be in a block
hadBlock, err := optionalBlock()
if err != nil {
return handler, err
}
// Otherwise, the only argument would be an error log file name or 'visible'
if !hadBlock {
if c.NextArg() {
if c.Val() == "visible" {
handler.Debug = true
} else {
handler.LogFile = c.Val()
}
}
}
}
return handler, nil
}

View File

@ -0,0 +1,48 @@
package errors
import (
"testing"
"github.com/mholt/caddy"
)
func TestErrorsParse(t *testing.T) {
tests := []struct {
inputErrorsRules string
shouldErr bool
expectedErrorHandler errorHandler
}{
{`errors`, false, errorHandler{
LogFile: "",
}},
{`errors errors.txt`, false, errorHandler{
LogFile: "errors.txt",
}},
{`errors visible`, false, errorHandler{
LogFile: "",
Debug: true,
}},
{`errors { log visible }`, false, errorHandler{
LogFile: "",
Debug: true,
}},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.inputErrorsRules)
actualErrorsRule, err := errorsParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
t.Errorf("Test %d expected LogFile to be %s, but got %s",
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
}
if actualErrorsRule.Debug != test.expectedErrorHandler.Debug {
t.Errorf("Test %d expected Debug to be %v, but got %v",
i, test.expectedErrorHandler.Debug, actualErrorsRule.Debug)
}
}
}

View File

@ -0,0 +1,151 @@
# etcd
*etcd* enables reading zone data from an etcd instance. The data in etcd has to be encoded as
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
like [SkyDNS](https//github.com/skynetservices/skydns). It should also work just like SkyDNS.
The etcd middleware makes extensive use of the proxy middleware to forward and query other servers
in the network.
## Syntax
~~~
etcd [ZONES...]
~~~
* **ZONES** zones etcd should be authoritative for.
The path will default to `/skydns` the local etcd proxy (http://localhost:2379).
If no zones are specified the block's zone will be used as the zone.
If you want to `round robin` A and AAAA responses look at the `loadbalance` middleware.
~~~
etcd [ZONES...] {
stubzones
path PATH
endpoint ENDPOINT...
upstream ADDRESS...
tls CERT KEY CACERt
debug
}
~~~
* `stubzones` enables the stub zones feature. The stubzone is *only* done in the etcd tree located
under the *first* zone specified.
* **PATH** the path inside etcd. Defaults to "/skydns".
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add
the proxy middleware. **ADDRESS** can be an IP address, and IP:port or a string pointing to a file
that is structured as /etc/resolv.conf.
* `tls` followed by:
* no arguments, if the server certificate is signed by a system-installed CA and no client cert is needed
* a single argument that is the CA PEM file, if the server cert is not signed by a system CA and no client cert is needed
* two arguments - path to cert PEM file, the path to private key PEM file - if the server certificate is signed by a system-installed CA and a client certificate is needed
* three arguments - path to cert PEM file, path to client private key PEM file, path to CA PEM file - if the server certificate is not signed by a system-installed CA and client certificate is needed
* `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the
additional section of the reply in the form of TXT records.
## Examples
This is the default SkyDNS setup, with everying specified in full:
~~~
.:53 {
etcd skydns.local {
stubzones
path /skydns
endpoint http://localhost:2379
upstream 8.8.8.8:53 8.8.4.4:53
}
prometheus
cache 160 skydns.local
loadbalance
proxy . 8.8.8.8:53 8.8.4.4:53
}
~~~
Or a setup where we use `/etc/resolv.conf` as the basis for the proxy and the upstream
when resolving external pointing CNAMEs.
~~~
.:53 {
etcd skydns.local {
path /skydns
upstream /etc/resolv.conf
}
cache 160 skydns.local
proxy . /etc/resolv.conf
}
~~~
### Reverse zones
Reverse zones are supported. You need to make CoreDNS aware of the fact that you are also
authoritative for the reverse. For instance if you want to add the reverse for 10.0.0.0/24, you'll
need to add the zone `0.0.10.in-addr.arpa` to the list of zones. (The fun starts with IPv6 reverse zones
in the ip6.arpa domain.) Showing a snippet of a Corefile:
~~~
etcd skydns.local 0.0.10.in-addr.arpa {
stubzones
...
~~~
Next you'll need to populate the zone with reverse records, here we add a reverse for
10.0.0.127 pointing to reverse.skydns.local.
~~~
% curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/arpa/in-addr/10/0/0/127 \
-d value='{"host":"reverse.skydns.local."}'
~~~
Querying with dig:
~~~
% dig @localhost -x 10.0.0.127 +short
reverse.atoom.net.
~~~
Or with *debug* queries enabled:
~~~
% dig @localhost -p 1053 o-o.debug.127.0.0.10.in-addr.arpa. PTR
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;o-o.debug.127.0.0.10.in-addr.arpa. IN PTR
;; ANSWER SECTION:
127.0.0.10.in-addr.arpa. 300 IN PTR reverse.atoom.net.
;; ADDITIONAL SECTION:
127.0.0.10.in-addr.arpa. 300 CH TXT "reverse.atoom.net.:0(10,0,,false)[0,]"
~~~
## Debug queries
When debug queries are enabled CoreDNS will return errors and etcd records encountered during the resolution
process in the response. The general form looks like this:
skydns.test.skydns.dom.a. 0 CH TXT "127.0.0.1:0(10,0,,false)[0,]"
This shows the complete key as the owername, the rdata of the TXT record has:
`host:port(priority,weight,txt content,mail)[targetstrip,group]`.
Errors when communicating with an upstream will be returned as: `host:0(0,0,error message,false)[0,]`.
An example:
www.example.org. 0 CH TXT "www.example.org.:0(0,0, IN A: unreachable backend,false)[0,]"
Signalling that an A record for www.example.org. was sought, but it failed with that error.
Any errors seen doing parsing will show up like this:
. 0 CH TXT "/skydns/local/skydns/r/a: invalid character '.' after object key:value pair"
which shows `a.r.skydns.local.` has a json encoding problem.

View File

@ -0,0 +1,79 @@
// +build etcd
package etcd
// etcd needs to be running on http://localhost:2379
import (
"testing"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
)
// Check the ordering of returned cname.
func TestCnameLookup(t *testing.T) {
etc := newEtcdMiddleware()
for _, serv := range servicesCname {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
for _, tc := range dnsTestCasesCname {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := etc.ServeDNS(ctxt, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
return
}
resp := rec.Msg
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
var servicesCname = []*msg.Service{
{Host: "cname1.region2.skydns.test", Key: "a.server1.dev.region1.skydns.test."},
{Host: "cname2.region2.skydns.test", Key: "cname1.region2.skydns.test."},
{Host: "cname3.region2.skydns.test", Key: "cname2.region2.skydns.test."},
{Host: "cname4.region2.skydns.test", Key: "cname3.region2.skydns.test."},
{Host: "cname5.region2.skydns.test", Key: "cname4.region2.skydns.test."},
{Host: "cname6.region2.skydns.test", Key: "cname5.region2.skydns.test."},
{Host: "endpoint.region2.skydns.test", Key: "cname6.region2.skydns.test."},
{Host: "10.240.0.1", Key: "endpoint.region2.skydns.test."},
}
var dnsTestCasesCname = []test.Case{
{
Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("a.server1.dev.region1.skydns.test. 300 IN SRV 10 100 0 cname1.region2.skydns.test."),
},
Extra: []dns.RR{
test.CNAME("cname1.region2.skydns.test. 300 IN CNAME cname2.region2.skydns.test."),
test.CNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."),
test.CNAME("cname3.region2.skydns.test. 300 IN CNAME cname4.region2.skydns.test."),
test.CNAME("cname4.region2.skydns.test. 300 IN CNAME cname5.region2.skydns.test."),
test.CNAME("cname5.region2.skydns.test. 300 IN CNAME cname6.region2.skydns.test."),
test.CNAME("cname6.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."),
test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"),
},
},
}

View File

@ -0,0 +1,130 @@
// +build etcd
package etcd
import (
"sort"
"testing"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
)
func TestDebugLookup(t *testing.T) {
etc := newEtcdMiddleware()
etc.Debugging = true
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
for _, tc := range dnsTestCasesDebug {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
etc.ServeDNS(ctxt, rec, m)
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
func TestDebugLookupFalse(t *testing.T) {
etc := newEtcdMiddleware()
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
for _, tc := range dnsTestCasesDebugFalse {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
etc.ServeDNS(ctxt, rec, m)
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
var servicesDebug = []*msg.Service{
{Host: "127.0.0.1", Key: "a.dom.skydns.test."},
{Host: "127.0.0.2", Key: "b.sub.dom.skydns.test."},
}
var dnsTestCasesDebug = []test.Case{
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dom.skydns.test. 300 IN A 127.0.0.1"),
test.A("dom.skydns.test. 300 IN A 127.0.0.2"),
},
Extra: []dns.RR{
test.TXT(`a.dom.skydns.test. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]"`),
test.TXT(`b.sub.dom.skydns.test. 300 CH TXT "127.0.0.2:0(10,0,,false)[0,]"`),
},
},
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeTXT,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
Extra: []dns.RR{
test.TXT(`a.dom.skydns.test. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]"`),
test.TXT(`b.sub.dom.skydns.test. 300 CH TXT "127.0.0.2:0(10,0,,false)[0,]"`),
},
},
}
var dnsTestCasesDebugFalse = []test.Case{
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeA,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
},
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeTXT,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
},
}

189
vendor/github.com/miekg/coredns/middleware/etcd/etcd.go generated vendored Normal file
View File

@ -0,0 +1,189 @@
// Package etcd provides the etcd backend middleware.
package etcd
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/singleflight"
"github.com/coredns/coredns/middleware/proxy"
"github.com/coredns/coredns/request"
etcdc "github.com/coreos/etcd/client"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Etcd is a middleware talks to an etcd cluster.
type Etcd struct {
Next middleware.Handler
Zones []string
PathPrefix string
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
Client etcdc.KeysAPI
Ctx context.Context
Inflight *singleflight.Group
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
Debugging bool // Do we allow debug queries.
endpoints []string // Stored here as well, to aid in testing.
}
// Services implements the ServiceBackend interface.
func (e *Etcd) Services(state request.Request, exact bool, opt middleware.Options) (services, debug []msg.Service, err error) {
services, err = e.Records(state.Name(), exact)
if err != nil {
return
}
if opt.Debug != "" {
debug = services
}
services = msg.Group(services)
return
}
// Reverse implements the ServiceBackend interface.
func (e *Etcd) Reverse(state request.Request, exact bool, opt middleware.Options) (services, debug []msg.Service, err error) {
return e.Services(state, exact, opt)
}
// Lookup implements the ServiceBackend interface.
func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
return e.Proxy.Lookup(state, name, typ)
}
// IsNameError implements the ServiceBackend interface.
func (e *Etcd) IsNameError(err error) bool {
if ee, ok := err.(etcdc.Error); ok && ee.Code == etcdc.ErrorCodeKeyNotFound {
return true
}
return false
}
// Debug implements the ServiceBackend interface.
func (e *Etcd) Debug() string {
return e.PathPrefix
}
// Records looks up records in etcd. If exact is true, it will lookup just this
// name. This is used when find matches when completing SRV lookups for instance.
func (e *Etcd) Records(name string, exact bool) ([]msg.Service, error) {
path, star := msg.PathWithWildcard(name, e.PathPrefix)
r, err := e.get(path, true)
if err != nil {
return nil, err
}
segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
switch {
case exact && r.Node.Dir:
return nil, nil
case r.Node.Dir:
return e.loopNodes(r.Node.Nodes, segments, star, nil)
default:
return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
}
}
// get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries.
func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) {
resp, err := e.Inflight.Do(path, func() (interface{}, error) {
ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
defer cancel()
r, e := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
if e != nil {
return nil, e
}
return r, e
})
if err != nil {
return nil, err
}
return resp.(*etcdc.Response), err
}
// skydns/local/skydns/east/staging/web
// skydns/local/skydns/west/production/web
//
// skydns/local/skydns/*/*/web
// skydns/local/skydns/*/web
// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
// will be match against any wildcards when star is true.
func (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
if bx == nil {
bx = make(map[msg.Service]bool)
}
Nodes:
for _, n := range ns {
if n.Dir {
nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx)
if err != nil {
return nil, err
}
sx = append(sx, nodes...)
continue
}
if star {
keyParts := strings.Split(n.Key, "/")
for i, n := range nameParts {
if i > len(keyParts)-1 {
// name is longer than key
continue Nodes
}
if n == "*" || n == "any" {
continue
}
if keyParts[i] != n {
continue Nodes
}
}
}
serv := new(msg.Service)
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
}
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key}
if _, ok := bx[b]; ok {
continue
}
bx[b] = true
serv.Key = n.Key
serv.TTL = e.TTL(n, serv)
if serv.Priority == 0 {
serv.Priority = priority
}
sx = append(sx, *serv)
}
return sx, nil
}
// TTL returns the smaller of the etcd TTL and the service's
// TTL. If neither of these are set (have a zero value), a default is used.
func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
etcdTTL := uint32(node.TTL)
if etcdTTL == 0 && serv.TTL == 0 {
return ttl
}
if etcdTTL == 0 {
return serv.TTL
}
if serv.TTL == 0 {
return etcdTTL
}
if etcdTTL < serv.TTL {
return etcdTTL
}
return serv.TTL
}
const (
priority = 10 // default priority when nothing is set
ttl = 300 // default ttl when nothing is set
etcdTimeout = 5 * time.Second
)

View File

@ -0,0 +1,91 @@
// +build etcd
package etcd
import (
"sort"
"testing"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
)
func TestGroupLookup(t *testing.T) {
etc := newEtcdMiddleware()
for _, serv := range servicesGroup {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
for _, tc := range dnsTestCasesGroup {
m := tc.Msg()
rec := dnsrecorder.New(&test.ResponseWriter{})
_, err := etc.ServeDNS(ctxt, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
continue
}
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
// Note the key is encoded as DNS name, while in "reality" it is a etcd path.
var servicesGroup = []*msg.Service{
{Host: "127.0.0.1", Key: "a.dom.skydns.test.", Group: "g1"},
{Host: "127.0.0.2", Key: "b.sub.dom.skydns.test.", Group: "g1"},
{Host: "127.0.0.1", Key: "a.dom2.skydns.test.", Group: "g1"},
{Host: "127.0.0.2", Key: "b.sub.dom2.skydns.test.", Group: ""},
{Host: "127.0.0.1", Key: "a.dom1.skydns.test.", Group: "g1"},
{Host: "127.0.0.2", Key: "b.sub.dom1.skydns.test.", Group: "g2"},
}
var dnsTestCasesGroup = []test.Case{
// Groups
{
// hits the group 'g1' and only includes those records
Qname: "dom.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dom.skydns.test. 300 IN A 127.0.0.1"),
test.A("dom.skydns.test. 300 IN A 127.0.0.2"),
},
},
{
// One has group, the other has not... Include the non-group always.
Qname: "dom2.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dom2.skydns.test. 300 IN A 127.0.0.1"),
test.A("dom2.skydns.test. 300 IN A 127.0.0.2"),
},
},
{
// The groups differ.
Qname: "dom1.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dom1.skydns.test. 300 IN A 127.0.0.1"),
},
},
}

View File

@ -0,0 +1,120 @@
package etcd
import (
"errors"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/debug"
"github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// ServeDNS implements the middleware.Handler interface.
func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
opt := middleware.Options{}
state := request.Request{W: w, Req: r}
if state.QClass() != dns.ClassINET {
return dns.RcodeServerFailure, middleware.Error(e.Name(), errors.New("can only deal with ClassINET"))
}
name := state.Name()
if e.Debugging {
if bug := debug.IsDebug(name); bug != "" {
opt.Debug = r.Question[0].Name
state.Clear()
state.Req.Question[0].Name = bug
}
}
// We need to check stubzones first, because we may get a request for a zone we
// are not auth. for *but* do have a stubzone forward for. If we do the stubzone
// handler will handle the request.
if e.Stubmap != nil && len(*e.Stubmap) > 0 {
for zone := range *e.Stubmap {
if middleware.Name(zone).Matches(name) {
stub := Stub{Etcd: e, Zone: zone}
return stub.ServeDNS(ctx, w, r)
}
}
}
zone := middleware.Zones(e.Zones).Matches(state.Name())
if zone == "" {
if opt.Debug != "" {
r.Question[0].Name = opt.Debug
}
return middleware.NextOrFailure(e.Name(), e.Next, ctx, w, r)
}
var (
records, extra []dns.RR
debug []msg.Service
err error
)
switch state.Type() {
case "A":
records, debug, err = middleware.A(e, zone, state, nil, opt)
case "AAAA":
records, debug, err = middleware.AAAA(e, zone, state, nil, opt)
case "TXT":
records, debug, err = middleware.TXT(e, zone, state, opt)
case "CNAME":
records, debug, err = middleware.CNAME(e, zone, state, opt)
case "PTR":
records, debug, err = middleware.PTR(e, zone, state, opt)
case "MX":
records, extra, debug, err = middleware.MX(e, zone, state, opt)
case "SRV":
records, extra, debug, err = middleware.SRV(e, zone, state, opt)
case "SOA":
records, debug, err = middleware.SOA(e, zone, state, opt)
case "NS":
if state.Name() == zone {
records, extra, debug, err = middleware.NS(e, zone, state, opt)
break
}
fallthrough
default:
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, debug, err = middleware.A(e, zone, state, nil, opt)
}
if opt.Debug != "" {
// Substitute this name with the original when we return the request.
state.Clear()
state.Req.Question[0].Name = opt.Debug
}
if e.IsNameError(err) {
// Make err nil when returning here, so we don't log spam for NXDOMAIN.
return middleware.BackendError(e, zone, dns.RcodeNameError, state, debug, nil /* err */, opt)
}
if err != nil {
return middleware.BackendError(e, zone, dns.RcodeServerFailure, state, debug, err, opt)
}
if len(records) == 0 {
return middleware.BackendError(e, zone, dns.RcodeSuccess, state, debug, err, opt)
}
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
m.Answer = append(m.Answer, records...)
m.Extra = append(m.Extra, extra...)
if opt.Debug != "" {
m.Extra = append(m.Extra, middleware.ServicesToTxt(debug)...)
}
m = dnsutil.Dedup(m)
state.SizeAndDo(m)
m, _ = state.Scrub(m)
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
// Name implements the Handler interface.
func (e *Etcd) Name() string { return "etcd" }

View File

@ -0,0 +1,208 @@
// +build etcd
package etcd
import (
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/test"
"github.com/miekg/dns"
)
// Note the key is encoded as DNS name, while in "reality" it is a etcd path.
var services = []*msg.Service{
{Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."},
{Host: "10.0.0.1", Port: 8080, Key: "a.server1.prod.region1.skydns.test."},
{Host: "10.0.0.2", Port: 8080, Key: "b.server1.prod.region1.skydns.test."},
{Host: "::1", Port: 8080, Key: "b.server6.prod.region1.skydns.test."},
// Unresolvable internal name.
{Host: "unresolvable.skydns.test", Key: "cname.prod.region1.skydns.test."},
// Priority.
{Host: "priority.server1", Priority: 333, Port: 8080, Key: "priority.skydns.test."},
// Subdomain.
{Host: "sub.server1", Port: 0, Key: "a.sub.region1.skydns.test."},
{Host: "sub.server2", Port: 80, Key: "b.sub.region1.skydns.test."},
{Host: "10.0.0.1", Port: 8080, Key: "c.sub.region1.skydns.test."},
// Cname loop.
{Host: "a.cname.skydns.test", Key: "b.cname.skydns.test."},
{Host: "b.cname.skydns.test", Key: "a.cname.skydns.test."},
// Nameservers.
{Host: "10.0.0.2", Key: "a.ns.dns.skydns.test."},
{Host: "10.0.0.3", Key: "b.ns.dns.skydns.test."},
// Reverse.
{Host: "reverse.example.com", Key: "1.0.0.10.in-addr.arpa."}, // 10.0.0.1
}
var dnsTestCases = []test.Case{
// SRV Test
{
Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{test.SRV("a.server1.dev.region1.skydns.test. 300 SRV 10 100 8080 dev.server1.")},
},
// SRV Test (case test)
{
Qname: "a.SERVer1.dEv.region1.skydns.tEst.", Qtype: dns.TypeSRV,
Answer: []dns.RR{test.SRV("a.SERVer1.dEv.region1.skydns.tEst. 300 SRV 10 100 8080 dev.server1.")},
},
// NXDOMAIN Test
{
Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"),
},
},
// A Test
{
Qname: "a.server1.prod.region1.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A("a.server1.prod.region1.skydns.test. 300 A 10.0.0.1")},
},
// SRV Test where target is IP address
{
Qname: "a.server1.prod.region1.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{test.SRV("a.server1.prod.region1.skydns.test. 300 SRV 10 100 8080 a.server1.prod.region1.skydns.test.")},
Extra: []dns.RR{test.A("a.server1.prod.region1.skydns.test. 300 A 10.0.0.1")},
},
// AAAA Test
{
Qname: "b.server6.prod.region1.skydns.test.", Qtype: dns.TypeAAAA,
Answer: []dns.RR{test.AAAA("b.server6.prod.region1.skydns.test. 300 AAAA ::1")},
},
// Multiple A Record Test
{
Qname: "server1.prod.region1.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("server1.prod.region1.skydns.test. 300 A 10.0.0.1"),
test.A("server1.prod.region1.skydns.test. 300 A 10.0.0.2"),
},
},
// Priority Test
{
Qname: "priority.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{test.SRV("priority.skydns.test. 300 SRV 333 100 8080 priority.server1.")},
},
// Subdomain Test
{
Qname: "sub.region1.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 0 sub.server1."),
test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 80 sub.server2."),
test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 8080 c.sub.region1.skydns.test."),
},
Extra: []dns.RR{test.A("c.sub.region1.skydns.test. 300 IN A 10.0.0.1")},
},
// CNAME (unresolvable internal name)
{
Qname: "cname.prod.region1.skydns.test.", Qtype: dns.TypeA,
Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")},
},
// Wildcard Test
{
Qname: "*.region1.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 0 sub.server1."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 0 unresolvable.skydns.test."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 80 sub.server2."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 a.server1.prod.region1.skydns.test."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 b.server1.prod.region1.skydns.test."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 b.server6.prod.region1.skydns.test."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 c.sub.region1.skydns.test."),
test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 dev.server1."),
},
Extra: []dns.RR{
test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"),
test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"),
test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"),
test.A("c.sub.region1.skydns.test. 300 IN A 10.0.0.1"),
},
},
// Wildcard Test
{
Qname: "prod.*.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 0 unresolvable.skydns.test."),
test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 a.server1.prod.region1.skydns.test."),
test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 b.server1.prod.region1.skydns.test."),
test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 b.server6.prod.region1.skydns.test."),
},
Extra: []dns.RR{
test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"),
test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"),
test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"),
},
},
// Wildcard Test
{
Qname: "prod.any.skydns.test.", Qtype: dns.TypeSRV,
Answer: []dns.RR{
test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 0 unresolvable.skydns.test."),
test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 a.server1.prod.region1.skydns.test."),
test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 b.server1.prod.region1.skydns.test."),
test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 b.server6.prod.region1.skydns.test."),
},
Extra: []dns.RR{
test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"),
test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"),
test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"),
},
},
// CNAME loop detection
{
Qname: "a.cname.skydns.test.", Qtype: dns.TypeA,
Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")},
},
// NODATA Test
{
Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeTXT,
Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")},
},
// NODATA Test
{
Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeHINFO,
Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")},
},
// NXDOMAIN Test
{
Qname: "a.server1.nonexistent.region1.skydns.test.", Qtype: dns.TypeHINFO, Rcode: dns.RcodeNameError,
Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")},
},
{
Qname: "skydns.test.", Qtype: dns.TypeSOA,
Answer: []dns.RR{test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1460498836 14400 3600 604800 60")},
},
// NS Record Test
{
Qname: "skydns.test.", Qtype: dns.TypeNS,
Answer: []dns.RR{
test.NS("skydns.test. 300 NS a.ns.dns.skydns.test."),
test.NS("skydns.test. 300 NS b.ns.dns.skydns.test."),
},
Extra: []dns.RR{
test.A("a.ns.dns.skydns.test. 300 A 10.0.0.2"),
test.A("b.ns.dns.skydns.test. 300 A 10.0.0.3"),
},
},
// NS Record Test
{
Qname: "a.skydns.test.", Qtype: dns.TypeNS, Rcode: dns.RcodeNameError,
Ns: []dns.RR{test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1460498836 14400 3600 604800 60")},
},
// A Record For NS Record Test
{
Qname: "ns.dns.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("ns.dns.skydns.test. 300 A 10.0.0.2"),
test.A("ns.dns.skydns.test. 300 A 10.0.0.3"),
},
},
{
Qname: "skydns_extra.test.", Qtype: dns.TypeSOA,
Answer: []dns.RR{test.SOA("skydns_extra.test. 300 IN SOA ns.dns.skydns_extra.test. hostmaster.skydns_extra.test. 1460498836 14400 3600 604800 60")},
},
// Reverse lookup
{
Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
Answer: []dns.RR{test.PTR("1.0.0.10.in-addr.arpa. 300 PTR reverse.example.com.")},
},
}

Some files were not shown because too many files have changed in this diff Show More