network.go 6.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
Copyright 2019 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package oci

import (
	"fmt"
M
Medya Gh 已提交
21
	"net"
22 23
	"os/exec"
	"runtime"
M
Medya Gh 已提交
24
	"strconv"
25 26
	"strings"

M
Medya Gh 已提交
27
	"github.com/golang/glog"
28 29 30 31 32
	"github.com/pkg/errors"
)

// RoutableHostIPFromInside returns the ip/dns of the host that container lives on
// is routable from inside the container
M
Medya Gh 已提交
33
func RoutableHostIPFromInside(ociBin string, containerName string) (net.IP, error) {
34 35
	if ociBin == Docker {
		if runtime.GOOS == "linux" {
36
			return dockerGatewayIP(containerName)
37 38 39
		}
		// for windows and mac, the gateway ip is not routable so we use dns trick.
		return digDNS(ociBin, containerName, "host.docker.internal")
40
	}
41

42
	if runtime.GOOS == "linux" {
43
		return containerGatewayIP(ociBin, containerName)
44
	}
45 46

	return nil, fmt.Errorf("RoutableHostIPFromInside is currently only implemented for linux")
47 48
}

M
Medya Gh 已提交
49 50
// digDNS will get the IP record for a dns
func digDNS(ociBin, containerName, dns string) (net.IP, error) {
M
Medya Gh 已提交
51
	rr, err := runCmd(exec.Command(ociBin, "exec", "-t", containerName, "dig", "+short", dns))
M
Medya Gh 已提交
52
	ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
53
	if err != nil {
M
Medya Gh 已提交
54
		return ip, errors.Wrapf(err, "resolve dns to ip")
55
	}
56

M
Medya Gh 已提交
57
	glog.Infof("got host ip for mount in container by digging dns: %s", ip.String())
M
Medya Gh 已提交
58 59
	return ip, nil
}
60

M
Medya Gh 已提交
61 62
// dockerGatewayIP gets the default gateway ip for the docker bridge on the user's host machine
// gets the ip from user's host docker
63 64 65 66 67 68 69
func dockerGatewayIP(profile string) (net.IP, error) {
	// check if using custom network first
	network := "bridge"
	if networkExists(profile) {
		network = profile
	}
	rr, err := runCmd(exec.Command(Docker, "network", "ls", "--filter", fmt.Sprintf("name=%s", network), "--format", "{{.ID}}"))
M
Medya Gh 已提交
70
	if err != nil {
M
Medya Gh 已提交
71
		return nil, errors.Wrapf(err, "get network bridge")
M
Medya Gh 已提交
72
	}
73

M
Medya Gh 已提交
74
	bridgeID := strings.TrimSpace(rr.Stdout.String())
75
	rr, err = runCmd(exec.Command(Docker, "network", "inspect",
M
Medya Gh 已提交
76
		"--format", "{{(index .IPAM.Config 0).Gateway}}", bridgeID))
77
	if err != nil {
M
Medya Gh 已提交
78
		return nil, errors.Wrapf(err, "inspect IP bridge network %q.", bridgeID)
79
	}
80

M
Medya Gh 已提交
81
	ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
M
Medya Gh 已提交
82 83
	glog.Infof("got host ip for mount in container by inspect docker network: %s", ip.String())
	return ip, nil
84
}
M
Medya Gh 已提交
85

86 87
// containerGatewayIP gets the default gateway ip for the container
func containerGatewayIP(ociBin, containerName string) (net.IP, error) {
88
	rr, err := runCmd(exec.Command(ociBin, "container", "inspect", "--format", "{{.NetworkSettings.Gateway}}", containerName))
89 90 91 92 93 94 95
	if err != nil {
		return nil, errors.Wrapf(err, "inspect gateway")
	}
	ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
	return ip, nil
}

96 97
// ForwardedPort will return port mapping for a container using cli.
// example : ForwardedPort("docker", "minikube", "22")
M
Medya Gh 已提交
98 99 100
// will return the docker assigned port:
// 32769, nil
// only supports TCP ports
M
Medya Gh 已提交
101
func ForwardedPort(ociBin string, ociID string, contPort int) (int, error) {
M
Medya Gh 已提交
102
	var rr *RunResult
M
Medya Gh 已提交
103
	var err error
104

M
Medya Gh 已提交
105
	if ociBin == Podman {
106
		rr, err = runCmd(exec.Command(ociBin, "container", "inspect", "-f", fmt.Sprintf("{{range .NetworkSettings.Ports}}{{if eq .ContainerPort %s}}{{.HostPort}}{{end}}{{end}}", fmt.Sprint(contPort)), ociID))
M
Medya Gh 已提交
107
		if err != nil {
M
Medya Gh 已提交
108
			return 0, errors.Wrapf(err, "get port %d for %q", contPort, ociID)
M
Medya Gh 已提交
109 110
		}
	} else {
111
		rr, err = runCmd(exec.Command(ociBin, "container", "inspect", "-f", fmt.Sprintf("'{{(index (index .NetworkSettings.Ports \"%d/tcp\") 0).HostPort}}'", contPort), ociID))
M
Medya Gh 已提交
112
		if err != nil {
M
Medya Gh 已提交
113
			return 0, errors.Wrapf(err, "get port %d for %q", contPort, ociID)
M
Medya Gh 已提交
114 115 116
		}
	}

M
Medya Gh 已提交
117
	o := strings.TrimSpace(rr.Stdout.String())
M
Medya Gh 已提交
118 119
	o = strings.Trim(o, "'")
	p, err := strconv.Atoi(o)
120

M
Medya Gh 已提交
121 122 123
	if err != nil {
		return p, errors.Wrapf(err, "convert host-port %q to number", p)
	}
124

M
Medya Gh 已提交
125 126 127 128
	return p, nil
}

// ContainerIPs returns ipv4,ipv6, error of a container by their name
M
Medya Gh 已提交
129 130
func ContainerIPs(ociBin string, name string) (string, string, error) {
	if ociBin == Podman {
131
		return podmanContainerIP(name)
M
Medya Gh 已提交
132 133 134 135
	}
	return dockerContainerIP(name)
}

136 137 138
// podmanContainerIP returns ipv4, ipv6 of container or error
func podmanContainerIP(name string) (string, string, error) {
	rr, err := runCmd(exec.Command(Podman, "container", "inspect",
M
Medya Gh 已提交
139
		"-f", "{{.NetworkSettings.IPAddress}}",
M
Medya Gh 已提交
140
		name))
M
Medya Gh 已提交
141 142 143
	if err != nil {
		return "", "", errors.Wrapf(err, "podman inspect ip %s", name)
	}
M
Medya Gh 已提交
144
	output := strings.TrimSpace(rr.Stdout.String())
M
Medya Gh 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157
	if err == nil && output == "" { // podman returns empty for 127.0.0.1
		return DefaultBindIPV4, "", nil
	}
	return output, "", nil
}

// dockerContainerIP returns ipv4, ipv6 of container or error
func dockerContainerIP(name string) (string, string, error) {
	// retrieve the IP address of the node using docker inspect
	lines, err := inspect(Docker, name, "{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}")
	if err != nil {
		return "", "", errors.Wrap(err, "inspecting NetworkSettings.Networks")
	}
158

M
Medya Gh 已提交
159 160 161
	if len(lines) != 1 {
		return "", "", errors.Errorf("IPs output should only be one line, got %d lines", len(lines))
	}
162

M
Medya Gh 已提交
163 164 165 166
	ips := strings.Split(lines[0], ",")
	if len(ips) != 2 {
		return "", "", errors.Errorf("container addresses should have 2 values, got %d values: %+v", len(ips), ips)
	}
167

M
Medya Gh 已提交
168 169
	return ips[0], ips[1], nil
}
J
Jose Donizetti 已提交
170 171 172

// CreateNetwork creates a network
func CreateNetwork(name, ipRange string) error {
173 174 175 176
	// check if the network already exists
	if networkExists(name) {
		return nil
	}
J
Jose Donizetti 已提交
177 178

	subnet := fmt.Sprintf("--subnet=%s", ipRange)
P
compile  
Priya Wadhwa 已提交
179
	_, err := runCmd(exec.Command(Docker, "network", "create", "--driver=bridge", subnet, name))
J
Jose Donizetti 已提交
180 181 182 183 184 185 186
	if err != nil {
		return errors.Wrapf(err, "error creating network")
	}

	return nil
}

P
compile  
Priya Wadhwa 已提交
187 188 189
// removeNetwork removes a network
func removeNetwork(name string) error {
	if !networkExists(name) {
190 191
		return nil
	}
J
Jose Donizetti 已提交
192
	_, err := runCmd(exec.Command(Docker, "network", "remove", name))
193 194 195 196 197
	return err
}

func networkExists(name string) bool {
	rr, err := runCmd(exec.Command(Docker, "network", "ls", "--format", "{{.Name}}"))
J
Jose Donizetti 已提交
198
	if err != nil {
P
compile  
Priya Wadhwa 已提交
199 200
		glog.Warningf("error listing networks: %v", err)
		return false
J
Jose Donizetti 已提交
201
	}
202 203
	networks := strings.Split(rr.Output(), "\n")
	for _, n := range networks {
204
		if strings.Trim(n, "\n") == name {
205 206 207 208
			return true
		}
	}
	return false
J
Jose Donizetti 已提交
209
}