Skip to content

Commit

Permalink
Linux DNS install (#112)
Browse files Browse the repository at this point in the history
* added linux support for NetworkManager/dnsmasq and libnss-resolver to the DNS command

* Added some Linux distros for testing

* go fmt

* Refactored to split network routing and dns resoution config

* go fmt

* removed soem extra log messages

* consolidated bip discovery
  • Loading branch information
febbraro authored Nov 2, 2017
1 parent 6a00ac8 commit 65c1c70
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 47 deletions.
170 changes: 125 additions & 45 deletions commands/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"os/exec"
"regexp"
"runtime"
"strings"

"github.com/phase2/rig/util"
Expand Down Expand Up @@ -41,20 +40,55 @@ func (cmd *DNS) Commands() []cli.Command {
func (cmd *DNS) Run(c *cli.Context) error {
cmd.out.Info.Println("Configuring DNS")

if util.SupportsNativeDocker() {
cmd.ConfigureDNS(cmd.machine, c.String("nameservers"))
} else if cmd.machine.IsRunning() {
cmd.ConfigureDNS(cmd.machine, c.String("nameservers"))
cmd.ConfigureRoutes(cmd.machine)
} else {
if !util.SupportsNativeDocker() && !cmd.machine.IsRunning() {
return cmd.Error(fmt.Sprintf("Machine '%s' is not running.", cmd.machine.Name), "MACHINE-STOPPED", 12)
}

if err := cmd.StartDNS(cmd.machine, c.String("nameservers")); err != nil {
return cmd.Error(err.Error(), "DNS-SETUP-FAILED", 13)
}

if !util.SupportsNativeDocker() {
cmd.ConfigureRoutes(cmd.machine)
}
return cmd.Success("DNS Services have been started")
}

// RemoveHostFilter removs the host filter from the xhyve bridge interface
func (cmd *DNS) RemoveHostFilter(ipAddr string) {
// ConfigureRoutes will configure routing to allow access to containers on IP addresses
// within the Docker Machine bridge network
func (cmd *DNS) ConfigureRoutes(machine Machine) {
cmd.out.Info.Println("Setting up local networking (may require your admin password)")

if util.IsMac() {
cmd.configureMacRoutes(machine)
} else if util.IsWindows() {
cmd.configureWindowsRoutes(machine)
}
}

// ConfigureMac configures DNS resolution and network routing
func (cmd *DNS) configureMacRoutes(machine Machine) {
machineIP := machine.GetIP()

if machine.IsXhyve() {
cmd.removeHostFilter(machineIP)
}
exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run()
util.StreamCommand(exec.Command("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP))
if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil {
// Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.)
cmd.out.Verbose.Println("Restarting discoveryutil to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
util.StreamCommand(exec.Command("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
} else {
// Reset DNS cache. We have seen this suddenly make /etc/resolver/vm work.
cmd.out.Verbose.Println("Restarting mDNSResponder to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "killall", "-HUP", "mDNSResponder"))
}
}

// removeHostFilter removes the host filter from the xhyve bridge interface
func (cmd *DNS) removeHostFilter(ipAddr string) {
// #1: route -n get <machineIP> to find the interface name
routeData, err := exec.Command("route", "-n", "get", ipAddr).CombinedOutput()
if err != nil {
Expand All @@ -77,45 +111,21 @@ func (cmd *DNS) RemoveHostFilter(ipAddr string) {
util.StreamCommand(exec.Command("sudo", "ifconfig", iface, "-hostfilter", member))
}

// ConfigureRoutes will configure routing to allow access to containers on IP addresses
// within the Docker Machine bridge network
func (cmd *DNS) ConfigureRoutes(machine Machine) {
cmd.out.Info.Println("Setting up local networking (may require your admin password)")

machineIP := machine.GetIP()
bridgeIP := machine.GetBridgeIP()
if runtime.GOOS == "windows" {
exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run()
util.StreamCommand(exec.Command("runas", "/noprofile", "/user:Administrator", "route", "-p", "ADD", "172.17.0.0/16", machineIP))
} else if util.IsMac() {
if machine.IsXhyve() {
cmd.RemoveHostFilter(machine.GetIP())
}
exec.Command("sudo", "mkdir", "-p", "/etc/resolver").Run()
exec.Command("bash", "-c", "echo \"nameserver "+bridgeIP+"\" | sudo tee /etc/resolver/vm").Run()
exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run()
util.StreamCommand(exec.Command("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP))

if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil {
// Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.)
cmd.out.Verbose.Println("Restarting discoveryutil to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
util.StreamCommand(exec.Command("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
} else {
// Reset DNS cache. We have seen this suddenly make /etc/resolver/vm work.
cmd.out.Verbose.Println("Restarting mDNSResponder to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "killall", "-HUP", "mDNSResponder"))
}
}
// ConfigureWindowsRoutes configures network routing
func (cmd *DNS) configureWindowsRoutes(machine Machine) {
exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run()
util.StreamCommand(exec.Command("runas", "/noprofile", "/user:Administrator", "route", "-p", "ADD", "172.17.0.0/16", machine.GetIP()))
}

// ConfigureDNS will start the dnsdock service
func (cmd *DNS) ConfigureDNS(machine Machine, nameservers string) {
// StartDNS will start the dnsdock service
func (cmd *DNS) StartDNS(machine Machine, nameservers string) error {
dnsServers := strings.Split(nameservers, ",")

// Linux uses standard bridge IP
// May need to make this configurable is there are local linux/docker customizations?
var bridgeIP = "172.17.0.1"
bridgeIP, err := util.GetBridgeIP()
if err != nil {
return err
}

if !util.SupportsNativeDocker() {
machine.SetEnv()
bridgeIP = machine.GetBridgeIP()
Expand All @@ -132,14 +142,84 @@ func (cmd *DNS) ConfigureDNS(machine Machine, nameservers string) {
"-l", "com.dnsdock.name=dnsdock",
"-l", "com.dnsdock.image=outrigger",
"--name", "dnsdock",
"-p", bridgeIP + ":53:53/udp",
"-p", fmt.Sprintf("%s:53:53/udp", bridgeIP),
"aacebedo/dnsdock:v1.16.1-amd64",
"--domain=vm",
}
for _, server := range dnsServers {
args = append(args, "--nameserver="+server)
}
util.ForceStreamCommand(exec.Command("docker", args...))

// Configure the resolvers based on platform
var resolverReturn error
if util.IsMac() {
resolverReturn = cmd.configureMacResolver(machine)
} else if util.IsLinux() {
resolverReturn = cmd.configureLinuxResolver()
} else if util.IsWindows() {
resolverReturn = cmd.configureWindowsResolver(machine)
}
return resolverReturn
}

// configureMacResolver configures DNS resolution and network routing
func (cmd *DNS) configureMacResolver(machine Machine) error {
cmd.out.Verbose.Print("Configuring DNS resolution for macOS")
bridgeIP := machine.GetBridgeIP()

if err := exec.Command("sudo", "mkdir", "-p", "/etc/resolver").Run(); err != nil {
return err
}
if err := exec.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s' | sudo tee /etc/resolver/vm", bridgeIP)).Run(); err != nil {
return err
}
if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil {
// Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.)
cmd.out.Verbose.Println("Restarting discoveryutil to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
util.StreamCommand(exec.Command("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist"))
} else {
// Reset DNS cache. We have seen this suddenly make /etc/resolver/vm work.
cmd.out.Verbose.Println("Restarting mDNSResponder to flush DNS caches")
util.StreamCommand(exec.Command("sudo", "killall", "-HUP", "mDNSResponder"))
}
return nil
}

// configureLinuxResolver configures DNS resolution
func (cmd *DNS) configureLinuxResolver() error {
cmd.out.Verbose.Print("Configuring DNS resolution for linux")
bridgeIP, err := util.GetBridgeIP()
if err != nil {
return err
}

// Is NetworkManager in use
if _, err := os.Stat("/etc/NetworkManager/dnsmasq.d"); err == nil {
// Install for NetworkManager/dnsmasq connection to dnsdock
util.StreamCommand(exec.Command("bash", "-c", fmt.Sprintf("echo 'server=/vm/%s' | sudo tee /etc/NetworkManager/dnsmasq.d/dnsdock.conf", bridgeIP)))

// Restart NetworkManager if it is running
if err := exec.Command("systemctl", "is-active", "NetworkManager").Run(); err != nil {
util.StreamCommand(exec.Command("sudo", "systemctl", "restart", "NetworkManager"))
}
}

// Is libnss-resolver in use
if _, err := os.Stat("/etc/resolver"); err == nil {
// Install for libnss-resolver connection to dnsdock
exec.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s' | sudo tee /etc/resolver/vm", bridgeIP)).Run()
}

return nil
}

// configureWindowsResolver configures DNS resolution and network routing
func (cmd *DNS) configureWindowsResolver(machine Machine) error {
// TODO: Figure out Windows resolver configuration
cmd.out.Verbose.Print("TODO: Configuring DNS resolution for windows")
return nil
}

// StopDNS stops the dnsdock service and cleans up
Expand Down
4 changes: 2 additions & 2 deletions commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (cmd *Start) Run(c *cli.Context) error {

cmd.out.Info.Println("Setting up DNS...")
dns := DNS{BaseCommand{machine: cmd.machine, out: cmd.out}}
dns.ConfigureDNS(cmd.machine, c.String("nameservers"))
dns.StartDNS(cmd.machine, c.String("nameservers"))

// NFS mounts are Mac-only.
if util.IsMac() {
Expand Down Expand Up @@ -145,7 +145,7 @@ func (cmd *Start) Run(c *cli.Context) error {
// a virtual machine and networking is not required or managed by Outrigger.
func (cmd *Start) StartMinimal(nameservers string) error {
dns := DNS{BaseCommand{machine: cmd.machine, out: cmd.out}}
dns.ConfigureDNS(cmd.machine, nameservers)
dns.StartDNS(cmd.machine, nameservers)

dash := Dashboard{BaseCommand{machine: cmd.machine, out: cmd.out}}
dash.LaunchDashboard(cmd.machine)
Expand Down
16 changes: 16 additions & 0 deletions test/Dockerfile.centos
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM centos:latest

RUN yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
RUN yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
RUN yum install -y docker-ce

# Add services to test dns resolution
RUN yum -y install sudo NetworkManager dnsmasq

# To use run: `docker run --privileged -it --name dind -d docker:dind` to start a docker server
# Then add `--link dind:docker` to the run of this container to connect to it
ENV DOCKER_HOST tcp://docker:2375
16 changes: 16 additions & 0 deletions test/Dockerfile.fedora
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM fedora:latest

RUN dnf -y install dnf-plugins-core
RUN dnf config-manager \
--add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
RUN dnf -y install docker-ce

# Add services to test dns resolution
RUN dnf -y install sudo NetworkManager dnsmasq
RUN rpm -i https://github.com/azukiapp/libnss-resolver/releases/download/v0.3.0/fedora23-libnss-resolver-0.3.0-1.x86_64.rpm


# To use run: `docker run --privileged -it --name dind -d docker:dind` to start a docker server
# Then add `--link dind:docker` to the run of this container to connect to it
ENV DOCKER_HOST tcp://docker:2375
26 changes: 26 additions & 0 deletions test/Dockerfile.ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM ubuntu:latest

RUN apt-get update
RUN apt-get -y install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
RUN apt-key fingerprint 0EBFCD88
RUN add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
RUN apt-get update
RUN apt-get -y install docker-ce

# Add services to test dns resolution
RUN apt-get -y install sudo network-manager dnsmasq
RUN curl -o /tmp/libnss-resolver.deb -L https://github.com/azukiapp/libnss-resolver/releases/download/v0.3.0/ubuntu16-libnss-resolver_0.3.0_amd64.deb \
&& dpkg -i /tmp/libnss-resolver.deb

# To use run: `docker run --privileged -it --name dind -d docker:dind` to start a docker server
# Then add `--link dind:docker` to the run of this container to connect to it
ENV DOCKER_HOST tcp://docker:2375

28 changes: 28 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Linux Images for testing

The Dockerfiles in this directory are used for testing certain Linux commands. Some things don't work perfect, such as
restarting systemd services (there is no systemd in the container) or connecting dnsdock to the Docker Bridge IP, but
other code that checks for platform, etc. should run fine.

The way you use them is as follows.

## Build the Linux images

* For each Dockerfile, build the image
* `docker build -t test-fedora -f Dockerfile.fedora .`
* `docker build -t test-ubuntu -f Dockerfile.ubuntu .`
* `docker build -t test-centos -f Dockerfile.centos .`


## Build rig for Linux

* `GOARCH=amd64 GOOS=linux go build -o build/linux/rig cmd/main.go`


## Run the images (and Docker in Docker)

* Start Docker in Docker
* `docker run --privileged -it --name dind -d docker:dind`
* Start the container for the distro you want, mounting a linux targeted `rig` into it and linking it to the Docker in Docker image.
* `docker run -it -v $PWD/build/linux/rig:/usr/bin/rig --link dind:docker test-centos bash`
* You are now at a shell in the Linux container with `rig` in `/usr/bin/rig`
14 changes: 14 additions & 0 deletions util/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,17 @@ func ImageOlderThan(image string, elapsedSeconds float64) (bool, float64, error)
seconds := time.Since(datetime).Seconds()
return seconds > elapsedSeconds, seconds, nil
}

// GetBridgeIP returns the IP address of the Docker bridge network gateway
func GetBridgeIP() (string, error) {
output, err := exec.Command("docker", "network", "inspect", "bridge", "--format", "{{(index .IPAM.Config 0).Gateway}}").Output()
if err != nil {
return "", err
}

bip := strings.Trim(string(output), "\n")
if bip == "" {
bip = "172.17.0.1"
}
return bip, nil
}

0 comments on commit 65c1c70

Please sign in to comment.