123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- package tunneldns
- import (
- "net"
- "os"
- "os/signal"
- "strconv"
- "sync"
- "syscall"
- "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
- "github.com/cloudflare/cloudflared/logger"
- "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"
- "github.com/rs/zerolog"
- "github.com/urfave/cli/v2"
- )
- const (
- LogFieldAddress = "address"
- LogFieldURL = "url"
- )
- // Listener is an adapter between CoreDNS server and Warp runnable
- type Listener struct {
- server *dnsserver.Server
- wg sync.WaitGroup
- log *zerolog.Logger
- }
- 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"},
- },
- // Note TUN-3758 , we use Int because UInt is not supported with altsrc
- &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"},
- },
- },
- 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 {
- log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
- metricsListener, err := net.Listen("tcp", c.String("metrics"))
- if err != nil {
- log.Fatal().Err(err).Msg("Failed to open the metrics listener")
- }
- go metrics.ServeMetrics(metricsListener, nil, nil, log)
- listener, err := CreateListener(
- c.String("address"),
- // Note TUN-3758 , we use Int because UInt is not supported with altsrc
- uint16(c.Int("port")),
- c.StringSlice("upstream"),
- c.StringSlice("bootstrap"),
- log,
- )
- if err != nil {
- log.Err(err).Msg("Failed to create the listeners")
- return err
- }
- // Try to start the server
- readySignal := make(chan struct{})
- err = listener.Start(readySignal)
- if err != nil {
- log.Err(err).Msg("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 {
- log.Err(err).Msg("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)
- l.log.Info().Str(LogFieldAddress, l.server.Address()).Msg("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, bootstraps []string, log *zerolog.Logger) (*Listener, error) {
- // Build the list of upstreams
- upstreamList := make([]Upstream, 0)
- for _, url := range upstreams {
- log.Info().Str(LogFieldURL, url).Msg("Adding DNS upstream")
- upstream, err := NewUpstreamHTTPS(url, bootstraps, log)
- 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, log: log}, nil
- }
|