Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fanout plugin #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,14 @@ issues:
max-issues-per-linter: 0
max-same-issues: 0
exclude-rules:
- path: "setup_test.go"
linters:
- funlen
# CoreDNS plugin should have init func.
- path: "setup.go"
linters:
- gochecknoinits
# Binds to all network interfaces is used in test to get port.
- path: "fanout_test.go"
linters:
- gosec
48 changes: 48 additions & 0 deletions .license/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Copyright headers for source code
This folder contains the copyright templates for source files of NSM project.

Below is an example of valid copyright header for `.go` files:
```
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// Copyright (c) 2020 Cisco and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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.
```
Note you can use your company name instead of `Cisco and/or its affiliates`.
Also, source code files can have multi copyright holders, for example:
```
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// Copyright (c) 2020 Cisco and/or its affiliates.
//
// Copyright (c) 2020 Red Hat Inc. and/or its affiliates.
//
// Copyright (c) 2020 VMware, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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.
```
15 changes: 15 additions & 0 deletions .license/template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// {NSM COPYRIGHT HOLDERS}
//
// SPDX-License-Identifier: Apache-2.0
//
// 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.
102 changes: 101 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,102 @@
# fanout
Repository for the coredns fanout plugin used by Network Service Mesh

## Name

*fanout* - parallel proxying DNS messages to upstream resolvers.

## Description

Each incoming DNS query that hits the CoreDNS fanout plugin will be replicated in parallel to each listed IP (i.e. the DNS servers). The first non-negative response from any of the queried DNS Servers will be forwarded as a response to the application's DNS request.

## Syntax

* `tls` **CERT** **KEY** **CA** define the TLS properties for TLS connection. From 0 to 3 arguments can be
provided with the meaning as described below
* `tls` - no client authentication is used, and the system CAs are used to verify the server certificate
* `tls` **CA** - no client authentication is used, and the file CA is used to verify the server certificate
* `tls` **CERT** **KEY** - client authentication is used with the specified cert/key pair.
The server certificate is verified with the system CAs
* `tls` **CERT** **KEY** **CA** - client authentication is used with the specified cert/key pair.
The server certificate is verified using the specified CA file
* `tls_servername` **NAME** allows you to set a server name in the TLS configuration; for instance 9.9.9.9
needs this to be set to `dns.quad9.net`. Multiple upstreams are still allowed in this scenario,
but they have to use the same `tls_servername`. E.g. mixing 9.9.9.9 (QuadDNS) with 1.1.1.1
(Cloudflare) will not work.

* `worker-count` is the number of parallel queries per request. By default equals to count of IP list. Use this only for reducing parallel queries per request.
* `network` is a specific network protocol. Could be `tcp`, `udp`, `tcp-tls`.
* `except` is a list is a space-separated list of domains to exclude from proxying.

## Metrics

If monitoring is enabled (via the *prometheus* plugin) then the following metric are exported:

* `coredns_fanout_request_duration_seconds{to}` - duration per upstream interaction.
* `coredns_fanout_request_count_total{to}` - query count per upstream.
* `coredns_fanout_response_rcode_count_total{to, rcode}` - count of RCODEs per upstream.
* `coredns_fanout_healthcheck_failure_count_total{to}` - number of failed health checks per upstream.
* `coredns_fanout_healthcheck_broken_count_total{}` - counter of when all upstreams are unhealthy,
and we are randomly (this always uses the `random` policy) spraying to an upstream.

Where `to` is one of the upstream servers (**TO** from the config), `rcode` is the returned RCODE
from the upstream.

## Examples
Proxy all requests within `example.org.` to a nameservers running on a different ports. The first positive response from a proxy will be provided as the result.

~~~ corefile
example.org {
fanout . 127.0.0.1:9005 127.0.0.1:9006 127.0.0.1:9007 127.0.0.1:9008
}
~~~

Sends parallel requests between three resolvers, one of which has a IPv6 address via TCP. The first response from proxy will be provided as the result.

~~~ corefile
. {
fanout . 10.0.0.10:53 10.0.0.11:1053 [2003::1]:53 {
network TCP
}
}
~~~

Proxying everything except requests to `example.org`

~~~ corefile
. {
fanout . 10.0.0.10:1234 {
except example.org
}
}
~~~

Proxy everything except `example.org` using the host's `resolv.conf`'s nameservers:

~~~ corefile
. {
fanout . /etc/resolv.conf {
except example.org
}
}
~~~

Proxy all requests to 9.9.9.9 using the DNS-over-TLS protocol.
Note the `tls-server` is mandatory if you want a working setup, as 9.9.9.9 can't be
used in the TLS negotiation.

~~~ corefile
. {
fanout . tls://9.9.9.9 {
tls-server dns.quad9.net
}
}
~~~

Sends parallel requests between five resolvers via UDP uses two workers and without attempting to reconnect. The first positive response from a proxy will be provided as the result.
~~~ corefile
. {
fanout . 10.0.0.10:53 10.0.0.11:53 10.0.0.12:53 10.0.0.13:1053 10.0.0.14:1053 {
worker-count 2
}
}
~~~
100 changes: 100 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 fanout

import (
"crypto/tls"
"fmt"
"time"

"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)

// Client represents the proxy for remote DNS server
type Client interface {
Request(*request.Request) (*dns.Msg, error)
Endpoint() string
SetTLSConfig(*tls.Config)
}

type client struct {
transport Transport
addr string
net string
}

// NewClient creates new client with specific addr and network
func NewClient(addr, net string) Client {
a := &client{
addr: addr,
net: net,
transport: NewTransport(addr),
}
return a
}

// SetTLSConfig sets tls config for client
func (c *client) SetTLSConfig(cfg *tls.Config) {
if cfg != nil {
c.net = tcptlc
}
c.transport.SetTLSConfig(cfg)
}

// Endpoint returns address of DNS server
func (c *client) Endpoint() string {
return c.addr
}

// Request sends request to DNS server
func (c *client) Request(r *request.Request) (*dns.Msg, error) {
start := time.Now()
conn, err := c.transport.Dial(c.net)
if err != nil {
return nil, err
}
defer func() {
logErrIfNotNil(conn.Close())
}()

logErrIfNotNil(conn.SetWriteDeadline(time.Now().Add(maxTimeout)))
if err = conn.WriteMsg(r.Req); err != nil {
logErrIfNotNil(err)
return nil, err
}
logErrIfNotNil(conn.SetReadDeadline(time.Now().Add(readTimeout)))
var ret *dns.Msg
for {
ret, err = conn.ReadMsg()
if err != nil {
logErrIfNotNil(err)
return nil, err
}
if r.Req.Id == ret.Id {
break
}
}
rc, ok := dns.RcodeToString[ret.Rcode]
if !ok {
rc = fmt.Sprint(ret.Rcode)
}
RequestCount.WithLabelValues(c.addr).Add(1)
RcodeCount.WithLabelValues(rc, c.addr).Add(1)
RequestDuration.WithLabelValues(c.addr).Observe(time.Since(start).Seconds())
return ret, nil
}
53 changes: 53 additions & 0 deletions connect_result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 fanout

import (
"time"

"github.com/miekg/dns"
)

type response struct {
client Client
response *dns.Msg
start time.Time
err error
}

func isBetter(left, right *response) bool {
if right == nil {
return false
}
if left == nil {
return true
}
if right.err != nil {
return false
}
if left.err != nil {
return true
}
if right.response == nil {
return false
}
if left.response == nil {
return true
}
return left.response.MsgHdr.Rcode != dns.RcodeSuccess &&
right.response.MsgHdr.Rcode == dns.RcodeSuccess
}
30 changes: 30 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 fanout

import "time"

const (
maxIPCount = 100
maxWorkerCount = 32
minWorkerCount = 2
maxTimeout = 2 * time.Second
defaultTimeout = 30 * time.Second
readTimeout = 2 * time.Second
tcptlc = "tcp-tls"
tcp = "tcp"
)
18 changes: 18 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2020 Doc.ai and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// 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 fanout - parallel proxying DNS messages to upstream resolvers.
package fanout
Loading