Skip to content
Draft
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
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ OUTPUT_BIN ?= $(repo_dir)/bin
DCP_DIR ?= $(home_dir)/.dcp
DCP_BINARY ?= ${OUTPUT_BIN}/dcp$(bin_exe_suffix)
DCPTUN_CLIENT_BINARY ?= $(OUTPUT_BIN)/dcptun_c
DCPDNS_BINARY ?= $(OUTPUT_BIN)/dcpdns_c

# Locations and definitions for tool binaries
GO_BIN ?= go
Expand Down Expand Up @@ -266,11 +267,11 @@ endif
##@ Development

ifeq ($(build_os),linux)
COMMON_BUILD_PREREQS := build-dcp build-dcptun-containerexe
COMMON_BUILD_PREREQS := build-dcp build-dcptun-containerexe build-dcpdns-containerexe
COMPILE_PREREQS := $(COMMON_BUILD_PREREQS)
else
COMMON_BUILD_PREREQS := build-dcp
COMPILE_PREREQS := $(COMMON_BUILD_PREREQS) build-dcptun-containerexe
COMPILE_PREREQS := $(COMMON_BUILD_PREREQS) build-dcptun-containerexe build-dcpdns-containerexe
endif

# Note: Go runtime is incompatible with C/C++ stack protection feature https://github.com/golang/go/blob/master/src/runtime/cgo/cgo.go#L28 More info/rationale https://github.com/golang/go/issues/21871#issuecomment-329330371
Expand Down Expand Up @@ -305,6 +306,15 @@ else
GOOS=linux $(GO_BIN) build -o $(DCPTUN_CLIENT_BINARY) $(BUILD_ARGS) ./cmd/dcptun
endif

.PHONY: build-dcpdns-containerexe
build-dcpdns-containerexe: $(DCPDNS_BINARY) ## Builds DCP DNS forwarder binary for Linux (used in containers on the Apple container runtime)
$(DCPDNS_BINARY): $(GO_SOURCES) go.mod | $(OUTPUT_BIN)
ifeq ($(detected_OS),windows)
$$env:GOOS = "linux"; $(GO_BIN) build -o $(DCPDNS_BINARY) $(BUILD_ARGS) ./cmd/dcpdns
else
GOOS=linux $(GO_BIN) build -o $(DCPDNS_BINARY) $(BUILD_ARGS) ./cmd/dcpdns
endif

.PHONY: clean
clean: | ${OUTPUT_BIN} ${TOOL_BIN} ## Deletes build output (all binaries), and all cached tool binaries.
$(rm_rf) $(OUTPUT_BIN)/*
Expand All @@ -318,11 +328,13 @@ lint: golangci-lint generate-grpc ## Runs the linter
install: compile | $(DCP_DIR) ## Installs all binaries to their destinations
$(install) $(DCP_BINARY) $(DCP_DIR)
$(install) $(DCPTUN_CLIENT_BINARY) $(DCP_DIR)
$(install) $(DCPDNS_BINARY) $(DCP_DIR)

.PHONY: uninstall
uninstall: ## Uninstalls all binaries from their destinations
$(rm_f) $(DCP_DIR)/dcp$(bin_exe_suffix)
$(rm_f) $(DCP_DIR)/dcptun_c
$(rm_f) $(DCP_DIR)/dcpdns_c

ifneq ($(detected_OS),windows)
.PHONY: link-dcp
Expand Down
101 changes: 101 additions & 0 deletions cmd/dcpdns/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// dcpdns is the DNS forwarder DCP runs inside a container on the Apple container runtime's
// network to provide name resolution for container names and network aliases.
// See internal/dcpdns for details.
package main

import (
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"

"github.com/go-logr/logr/funcr"

"github.com/microsoft/dcp/internal/dcpdns"
)

func main() {
listenAddr := flag.String("listen", ":53", "Address to serve DNS on")
hostsPath := flag.String("hosts", "", "Path to the hosts-format records file (required)")
upstream := flag.String("upstream", "", "Upstream resolver as host[:port]; defaults to the first nameserver in /etc/resolv.conf")
verbose := flag.Bool("v", false, "Enable verbose logging")
flag.Parse()

log := funcr.New(func(prefix, args string) {
fmt.Fprintln(os.Stdout, prefix, args)
}, funcr.Options{Verbosity: verbosity(*verbose)})

if *hostsPath == "" {
log.Error(nil, "--hosts is required")
os.Exit(2)
}

upstreamAddr := *upstream
if upstreamAddr == "" {
resolvConfUpstream, resolvErr := upstreamFromResolvConf("/etc/resolv.conf")
if resolvErr != nil {
log.Error(resolvErr, "No --upstream specified and no nameserver found in /etc/resolv.conf")
os.Exit(2)
}
upstreamAddr = resolvConfUpstream
}
if _, _, splitErr := net.SplitHostPort(upstreamAddr); splitErr != nil {
upstreamAddr = net.JoinHostPort(upstreamAddr, "53")
}

udpConn, udpErr := net.ListenPacket("udp", *listenAddr)
if udpErr != nil {
log.Error(udpErr, "Failed to listen for UDP DNS queries", "Address", *listenAddr)
os.Exit(1)
}

tcpListener, tcpErr := net.Listen("tcp", *listenAddr)
if tcpErr != nil {
log.Error(tcpErr, "Failed to listen for TCP DNS queries", "Address", *listenAddr)
os.Exit(1)
}

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

log.Info("DCP DNS forwarder starting", "Listen", *listenAddr, "Upstream", upstreamAddr, "Hosts", *hostsPath)

server := dcpdns.NewServer(dcpdns.NewRecords(*hostsPath), upstreamAddr, log)
if serveErr := server.Serve(ctx, udpConn, tcpListener); serveErr != nil {
log.Error(serveErr, "DNS forwarder failed")
os.Exit(1)
}
}

func verbosity(verbose bool) int {
if verbose {
return 2
}
return 0
}

// upstreamFromResolvConf returns the first nameserver listed in the given resolv.conf file.
func upstreamFromResolvConf(path string) (string, error) {
content, readErr := os.ReadFile(path)
if readErr != nil {
return "", readErr
}

for _, line := range strings.Split(string(content), "\n") {
fields := strings.Fields(line)
if len(fields) >= 2 && fields[0] == "nameserver" {
return fields[1], nil
}
}

return "", fmt.Errorf("no nameserver entries in %s", path)
}
Loading