345 lines
9.3 KiB
Go
345 lines
9.3 KiB
Go
package tunneldns
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
|
"github.com/cloudflare/cloudflared/logger"
|
|
"github.com/cloudflare/cloudflared/metrics"
|
|
odoh "github.com/cloudflare/odoh-go"
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/cache"
|
|
"github.com/pkg/errors"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
// Listener is an adapter between CoreDNS server and Warp runnable
|
|
type Listener struct {
|
|
server *dnsserver.Server
|
|
wg sync.WaitGroup
|
|
logger logger.Service
|
|
}
|
|
|
|
const (
|
|
dohResolver = "https://1.1.1.1/dns-query"
|
|
)
|
|
|
|
var OdohConfig odoh.ObliviousDoHConfigContents
|
|
|
|
func Command(hidden bool) *cli.Command {
|
|
return &cli.Command{
|
|
Name: "proxy-dns",
|
|
Action: cliutil.ErrorHandler(Run),
|
|
Usage: "Run a DNS over HTTPS proxy server.",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "metrics",
|
|
Value: "localhost:",
|
|
Usage: "Listen address for metrics reporting.",
|
|
EnvVars: []string{"TUNNEL_METRICS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "address",
|
|
Usage: "Listen address for the DNS over HTTPS proxy server.",
|
|
Value: "localhost",
|
|
EnvVars: []string{"TUNNEL_DNS_ADDRESS"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "port",
|
|
Usage: "Listen on given port for the DNS over HTTPS proxy server.",
|
|
Value: 53,
|
|
EnvVars: []string{"TUNNEL_DNS_PORT"},
|
|
},
|
|
&cli.StringSliceFlag{
|
|
Name: "upstream",
|
|
Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.",
|
|
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
|
|
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
|
},
|
|
&cli.StringSliceFlag{
|
|
Name: "bootstrap",
|
|
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
|
|
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
|
|
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
|
|
},
|
|
},
|
|
Subcommands: []*cli.Command{
|
|
{
|
|
Name: "odoh",
|
|
Action: cliutil.ErrorHandler(RunOdoh),
|
|
Usage: "Runs an Oblivious DNS over HTTPS client.",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "target",
|
|
Usage: "ODoH target URL",
|
|
Value: "https://1.1.1.1/dns-query",
|
|
EnvVars: []string{"TUNNEL_DNS_ODOH_TARGET"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "proxy",
|
|
Usage: "ODoH proxy URL",
|
|
Value: "https://odoh1.surfdomeinen.nl/proxy",
|
|
EnvVars: []string{"TUNNEL_DNS_ODOH_PROXY"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "useproxy",
|
|
Usage: "Set flag to enable proxy usage",
|
|
Value: false,
|
|
EnvVars: []string{"TUNNEL_DNS_ODOH_USE_PROXY"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ArgsUsage: " ", // can't be the empty string or we get the default output
|
|
Hidden: hidden,
|
|
}
|
|
}
|
|
|
|
// Run implements a foreground runner
|
|
func Run(c *cli.Context) error {
|
|
logger, err := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
|
if err != nil {
|
|
return cliutil.PrintLoggerSetupError("error setting up logger", err)
|
|
}
|
|
|
|
metricsListener, err := net.Listen("tcp", c.String("metrics"))
|
|
if err != nil {
|
|
logger.Fatalf("Failed to open the metrics listener: %s", err)
|
|
}
|
|
|
|
go metrics.ServeMetrics(metricsListener, nil, nil, logger)
|
|
|
|
listener, err := CreateListener(
|
|
c.String("address"),
|
|
uint16(c.Uint("port")),
|
|
c.StringSlice("upstream"),
|
|
c.StringSlice("bootstrap"),
|
|
logger,
|
|
)
|
|
if err != nil {
|
|
logger.Errorf("Failed to create the listeners: %s", err)
|
|
return err
|
|
}
|
|
|
|
// Try to start the server
|
|
readySignal := make(chan struct{})
|
|
err = listener.Start(readySignal)
|
|
if err != nil {
|
|
logger.Errorf("Failed to start the listeners: %s", err)
|
|
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.Errorf("failed to stop: %s", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// RunOdoh implements a foreground runner
|
|
func RunOdoh(c *cli.Context) error {
|
|
logger, err := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
|
if err != nil {
|
|
return cliutil.PrintLoggerSetupError("error setting up logger", err)
|
|
}
|
|
|
|
metricsListener, err := net.Listen("tcp", c.String("metrics"))
|
|
if err != nil {
|
|
logger.Fatalf("Failed to open the metrics listener: %s", err)
|
|
}
|
|
|
|
go metrics.ServeMetrics(metricsListener, nil, nil, logger)
|
|
|
|
listener, err := CreateObliviousDNSListener(
|
|
c.String("address"),
|
|
uint16(c.Uint("port")),
|
|
c.String("target"),
|
|
c.String("proxy"),
|
|
c.Bool("useproxy"),
|
|
logger,
|
|
)
|
|
if err != nil {
|
|
logger.Errorf("Failed to create the listeners: %s", err)
|
|
return err
|
|
}
|
|
|
|
// Update odohconfig
|
|
go listener.UpdateOdohConfig()
|
|
|
|
// Try to start the server
|
|
readySignal := make(chan struct{})
|
|
err = listener.Start(readySignal)
|
|
if err != nil {
|
|
logger.Errorf("Failed to start the listeners: %s", err)
|
|
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.Errorf("failed to stop: %s", err)
|
|
}
|
|
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)
|
|
|
|
l.logger.Infof("Starting DNS proxy server on: %s", l.server.Address())
|
|
|
|
// 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")
|
|
}
|
|
|
|
// UpdateOdohConfig periodically updates odoh configs
|
|
// Currently supports `odoh.cloudflare-dns.com.`.
|
|
func (l *Listener) UpdateOdohConfig() {
|
|
l.logger.Infof("Starting Oblivious DoH key updates")
|
|
dohResolver, _ := url.Parse(dohResolver)
|
|
client := http.Client{}
|
|
dnsQuery := new(dns.Msg)
|
|
dnsQuery.SetQuestion(targetHostname, dns.TypeHTTPS)
|
|
dnsQuery.RecursionDesired = true
|
|
packedDNSQuery, _ := dnsQuery.Pack()
|
|
for {
|
|
configs, err := FetchObliviousDoHConfig(&client, packedDNSQuery, dohResolver)
|
|
if err != nil {
|
|
l.logger.Errorf("odoh config not updated with err ", err)
|
|
}
|
|
OdohConfig = configs.Configs[0].Contents
|
|
time.Sleep(odohConfigDuration)
|
|
}
|
|
}
|
|
|
|
// 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, bootstraps []string, logger logger.Service) (*Listener, error) {
|
|
// Build the list of upstreams
|
|
upstreamList := make([]Upstream, 0)
|
|
for _, url := range upstreams {
|
|
logger.Infof("Adding DNS upstream - url: %s", url)
|
|
upstream, err := NewUpstreamHTTPS(url, bootstraps, nil, logger)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create HTTPS upstream")
|
|
}
|
|
upstreamList = append(upstreamList, upstream)
|
|
}
|
|
|
|
return buildListenerFromUpstream(upstreamList, address, port, logger)
|
|
}
|
|
|
|
// CreateObliviousDNSListener configures the server and bound sockets
|
|
func CreateObliviousDNSListener(address string, port uint16, target string, proxy string, useproxy bool, logger logger.Service) (*Listener, error) {
|
|
logger.Infof("Adding Oblivious DoH target - url: %s", target)
|
|
var upstream Upstream
|
|
var err error
|
|
targetURL, err := url.Parse(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
odohCtx := ObliviousDoHCtx{
|
|
useproxy: useproxy,
|
|
target: targetURL,
|
|
}
|
|
if useproxy {
|
|
logger.Infof("Adding Oblivious DoH proxy - url: %s", proxy)
|
|
upstream, err = NewUpstreamHTTPS(proxy, nil, &odohCtx, logger)
|
|
} else {
|
|
logger.Infof("No Oblivious DoH proxy is set")
|
|
upstream, err = NewUpstreamHTTPS(target, nil, &odohCtx, logger)
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create HTTPS upstream")
|
|
}
|
|
|
|
return buildListenerFromUpstream([]Upstream{upstream}, address, port, logger)
|
|
}
|
|
|
|
func buildListenerFromUpstream(upstreams []Upstream, address string, port uint16, logger logger.Service) (*Listener, error) {
|
|
// Create a local cache with HTTPS proxy plugin
|
|
chain := cache.New()
|
|
chain.Next = ProxyPlugin{
|
|
Upstreams: upstreams,
|
|
}
|
|
|
|
// 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, logger: logger}, nil
|
|
}
|