mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-27 00:59:58 +00:00
TUN-528: Move cloudflared into a separate repo
This commit is contained in:
38
tunneldns/https_proxy.go
Normal file
38
tunneldns/https_proxy.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package tunneldns
|
||||
|
||||
import (
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Upstream is a simplified interface for proxy destination
|
||||
type Upstream interface {
|
||||
Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg, error)
|
||||
}
|
||||
|
||||
// ProxyPlugin is a simplified DNS proxy using a generic upstream interface
|
||||
type ProxyPlugin struct {
|
||||
Upstreams []Upstream
|
||||
Next plugin.Handler
|
||||
}
|
||||
|
||||
// ServeDNS implements interface for CoreDNS plugin
|
||||
func (p ProxyPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
var reply *dns.Msg
|
||||
var backendErr error
|
||||
|
||||
for _, upstream := range p.Upstreams {
|
||||
reply, backendErr = upstream.Exchange(ctx, r)
|
||||
if backendErr == nil {
|
||||
w.WriteMsg(reply)
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
return dns.RcodeServerFailure, errors.Wrap(backendErr, "failed to contact any of the upstreams")
|
||||
}
|
||||
|
||||
// Name implements interface for CoreDNS plugin
|
||||
func (p ProxyPlugin) Name() string { return "proxy" }
|
105
tunneldns/https_upstream.go
Normal file
105
tunneldns/https_upstream.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package tunneldns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// UpstreamHTTPS is the upstream implementation for DNS over HTTPS service
|
||||
type UpstreamHTTPS struct {
|
||||
client *http.Client
|
||||
endpoint *url.URL
|
||||
}
|
||||
|
||||
// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from hostname
|
||||
func NewUpstreamHTTPS(endpoint string) (Upstream, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update TLS and HTTP client configuration
|
||||
tls := &tls.Config{ServerName: u.Hostname()}
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: tls,
|
||||
DisableCompression: true,
|
||||
MaxIdleConns: 1,
|
||||
}
|
||||
http2.ConfigureTransport(transport)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: defaultTimeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
return &UpstreamHTTPS{client: client, endpoint: u}, nil
|
||||
}
|
||||
|
||||
// Exchange provides an implementation for the Upstream interface
|
||||
func (u *UpstreamHTTPS) Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg, error) {
|
||||
queryBuf, err := query.Pack()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to pack DNS query")
|
||||
}
|
||||
|
||||
// No content negotiation for now, use DNS wire format
|
||||
buf, backendErr := u.exchangeWireformat(queryBuf)
|
||||
if backendErr == nil {
|
||||
response := &dns.Msg{}
|
||||
if err := response.Unpack(buf); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unpack DNS response from body")
|
||||
}
|
||||
|
||||
response.Id = query.Id
|
||||
return response, nil
|
||||
}
|
||||
|
||||
log.WithError(backendErr).Errorf("failed to connect to an HTTPS backend %q", u.endpoint)
|
||||
return nil, backendErr
|
||||
}
|
||||
|
||||
// Perform message exchange with the default UDP wireformat defined in current draft
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
|
||||
func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
|
||||
req, err := http.NewRequest("POST", u.endpoint.String(), bytes.NewBuffer(msg))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create an HTTPS request")
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/dns-udpwireformat")
|
||||
req.Host = u.endpoint.Hostname()
|
||||
|
||||
resp, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to perform an HTTPS request")
|
||||
}
|
||||
|
||||
// Check response status code
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("returned status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Read wireformat response from the body
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read the response body")
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
45
tunneldns/metrics.go
Normal file
45
tunneldns/metrics.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package tunneldns
|
||||
|
||||
import (
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/metrics/vars"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
"github.com/coredns/coredns/plugin/pkg/rcode"
|
||||
"github.com/coredns/coredns/request"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// MetricsPlugin is an adapter for CoreDNS and built-in metrics
|
||||
type MetricsPlugin struct {
|
||||
Next plugin.Handler
|
||||
}
|
||||
|
||||
// NewMetricsPlugin creates a plugin with configured metrics
|
||||
func NewMetricsPlugin(next plugin.Handler) *MetricsPlugin {
|
||||
prometheus.MustRegister(vars.RequestCount)
|
||||
prometheus.MustRegister(vars.RequestDuration)
|
||||
prometheus.MustRegister(vars.RequestSize)
|
||||
prometheus.MustRegister(vars.RequestDo)
|
||||
prometheus.MustRegister(vars.RequestType)
|
||||
prometheus.MustRegister(vars.ResponseSize)
|
||||
prometheus.MustRegister(vars.ResponseRcode)
|
||||
return &MetricsPlugin{Next: next}
|
||||
}
|
||||
|
||||
// ServeDNS implements the CoreDNS plugin interface
|
||||
func (p MetricsPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
state := request.Request{W: w, Req: r}
|
||||
|
||||
rw := dnstest.NewRecorder(w)
|
||||
status, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, rw, r)
|
||||
|
||||
// Update built-in metrics
|
||||
vars.Report(ctx, state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start)
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
// Name implements the CoreDNS plugin interface
|
||||
func (p MetricsPlugin) Name() string { return "metrics" }
|
148
tunneldns/tunnel.go
Normal file
148
tunneldns/tunnel.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package tunneldns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/cache"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
var logger = log.CreateLogger()
|
||||
|
||||
// Listener is an adapter between CoreDNS server and Warp runnable
|
||||
type Listener struct {
|
||||
server *dnsserver.Server
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Run implements a foreground runner
|
||||
func Run(c *cli.Context) error {
|
||||
metricsListener, err := net.Listen("tcp", c.String("metrics"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Fatal("Failed to open the metrics listener")
|
||||
}
|
||||
|
||||
go metrics.ServeMetrics(metricsListener, nil, logger)
|
||||
|
||||
listener, err := CreateListener(c.String("address"), uint16(c.Uint("port")), c.StringSlice("upstream"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Failed to create the listeners")
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to start the server
|
||||
readySignal := make(chan struct{})
|
||||
err = listener.Start(readySignal)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Failed to start the listeners")
|
||||
return listener.Stop()
|
||||
}
|
||||
<-readySignal
|
||||
|
||||
// Wait for signal
|
||||
signals := make(chan os.Signal, 10)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||
defer signal.Stop(signals)
|
||||
<-signals
|
||||
|
||||
// Shut down server
|
||||
err = listener.Stop()
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to stop")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a CoreDNS server plugin from configuration
|
||||
func createConfig(address string, port uint16, p plugin.Handler) *dnsserver.Config {
|
||||
c := &dnsserver.Config{
|
||||
Zone: ".",
|
||||
Transport: "dns",
|
||||
ListenHosts: []string{address},
|
||||
Port: strconv.FormatUint(uint64(port), 10),
|
||||
}
|
||||
|
||||
c.AddPlugin(func(next plugin.Handler) plugin.Handler { return p })
|
||||
return c
|
||||
}
|
||||
|
||||
// Start blocks for serving requests
|
||||
func (l *Listener) Start(readySignal chan struct{}) error {
|
||||
defer close(readySignal)
|
||||
logger.WithField("addr", l.server.Address()).Infof("Starting DNS over HTTPS proxy server")
|
||||
|
||||
// Start UDP listener
|
||||
if udp, err := l.server.ListenPacket(); err == nil {
|
||||
l.wg.Add(1)
|
||||
go func() {
|
||||
l.server.ServePacket(udp)
|
||||
l.wg.Done()
|
||||
}()
|
||||
} else {
|
||||
return errors.Wrap(err, "failed to create a UDP listener")
|
||||
}
|
||||
|
||||
// Start TCP listener
|
||||
tcp, err := l.server.Listen()
|
||||
if err == nil {
|
||||
l.wg.Add(1)
|
||||
go func() {
|
||||
l.server.Serve(tcp)
|
||||
l.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "failed to create a TCP listener")
|
||||
}
|
||||
|
||||
// Stop signals server shutdown and blocks until completed
|
||||
func (l *Listener) Stop() error {
|
||||
if err := l.server.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateListener configures the server and bound sockets
|
||||
func CreateListener(address string, port uint16, upstreams []string) (*Listener, error) {
|
||||
// Build the list of upstreams
|
||||
upstreamList := make([]Upstream, 0)
|
||||
for _, url := range upstreams {
|
||||
logger.WithField("url", url).Infof("Adding DNS upstream")
|
||||
upstream, err := NewUpstreamHTTPS(url)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create HTTPS upstream")
|
||||
}
|
||||
upstreamList = append(upstreamList, upstream)
|
||||
}
|
||||
|
||||
// Create a local cache with HTTPS proxy plugin
|
||||
chain := cache.New()
|
||||
chain.Next = ProxyPlugin{
|
||||
Upstreams: upstreamList,
|
||||
}
|
||||
|
||||
// Format an endpoint
|
||||
endpoint := "dns://" + net.JoinHostPort(address, strconv.FormatUint(uint64(port), 10))
|
||||
|
||||
// Create the actual middleware server
|
||||
server, err := dnsserver.NewServer(endpoint, []*dnsserver.Config{createConfig(address, port, NewMetricsPlugin(chain))})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Listener{server: server}, nil
|
||||
}
|
Reference in New Issue
Block a user