minikube/pkg/drivers/kic/oci/network.go

174 lines
5.8 KiB
Go

/*
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"
"net"
"os/exec"
"runtime"
"strconv"
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
"k8s.io/klog/v2"
)
// RoutableHostIPFromInside returns the ip/dns of the host that container lives on
// is routable from inside the container
func RoutableHostIPFromInside(ociBin string, clusterName string, containerName string) (net.IP, error) {
if ociBin == Docker {
if runtime.GOOS == "linux" {
info, err := containerNetworkInspect(ociBin, clusterName)
if err != nil {
if errors.Is(err, ErrNetworkNotFound) {
klog.Infof("The container %s is not attached to a network, this could be because the cluster was created by minikube <v1.14, will try to get the IP using container gatway", containerName)
return containerGatewayIP(Docker, containerName)
}
return info.gateway, errors.Wrap(err, "network inspect")
}
return info.gateway, nil
}
// for windows and mac, the gateway ip is not routable so we use dns trick.
return digDNS(ociBin, containerName, "host.docker.internal")
}
// podman
if runtime.GOOS == "linux" {
return containerGatewayIP(ociBin, containerName)
}
return nil, fmt.Errorf("RoutableHostIPFromInside is currently only implemented for linux")
}
// digDNS will get the IP record for a dns
func digDNS(ociBin, containerName, dns string) (net.IP, error) {
rr, err := runCmd(exec.Command(ociBin, "exec", "-t", containerName, "dig", "+short", dns))
ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
if err != nil {
return ip, errors.Wrapf(err, "resolve dns to ip")
}
klog.Infof("got host ip for mount in container by digging dns: %s", ip.String())
return ip, nil
}
// containerGatewayIP gets the default gateway ip for the container
func containerGatewayIP(ociBin string, containerName string) (net.IP, error) {
rr, err := runCmd(exec.Command(ociBin, "container", "inspect", "--format", "{{.NetworkSettings.Gateway}}", containerName))
if err != nil {
return nil, errors.Wrapf(err, "inspect gateway")
}
ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
return ip, nil
}
// ForwardedPort will return port mapping for a container using cli.
// example : ForwardedPort("docker", "minikube", "22")
// will return the docker assigned port:
// 32769, nil
// only supports TCP ports
func ForwardedPort(ociBin string, ociID string, contPort int) (int, error) {
var rr *RunResult
var err error
var v semver.Version
if ociBin == Podman {
rr, err = runCmd(exec.Command(Podman, "version", "--format", "{{.Version}}"))
if err != nil {
return 0, errors.Wrapf(err, "podman version")
}
output := strings.TrimSpace(rr.Stdout.String())
v, err = semver.Make(output)
if err != nil {
return 0, errors.Wrapf(err, "podman version")
}
}
// podman 2.0.1 introduced docker syntax for .NetworkSettings.Ports (podman#5380)
if ociBin == Podman && v.LT(semver.Version{Major: 2, Minor: 0, Patch: 1}) {
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))
if err != nil {
return 0, errors.Wrapf(err, "get port %d for %q", contPort, ociID)
}
} else {
rr, err = runCmd(exec.Command(ociBin, "container", "inspect", "-f", fmt.Sprintf("'{{(index (index .NetworkSettings.Ports \"%d/tcp\") 0).HostPort}}'", contPort), ociID))
if err != nil {
return 0, errors.Wrapf(err, "get port %d for %q", contPort, ociID)
}
}
o := strings.TrimSpace(rr.Stdout.String())
o = strings.Trim(o, "'")
p, err := strconv.Atoi(o)
if err != nil {
return p, errors.Wrapf(err, "convert host-port %q to number", p)
}
return p, nil
}
// ContainerIPs returns ipv4,ipv6, error of a container by their name
func ContainerIPs(ociBin string, name string) (string, string, error) {
if ociBin == Podman {
return podmanContainerIP(ociBin, name)
}
return dockerContainerIP(ociBin, name)
}
// podmanContainerIP returns ipv4, ipv6 of container or error
func podmanContainerIP(ociBin string, name string) (string, string, error) {
rr, err := runCmd(exec.Command(ociBin, "container", "inspect",
"-f", "{{.NetworkSettings.IPAddress}}",
name))
if err != nil {
return "", "", errors.Wrapf(err, "podman inspect ip %s", name)
}
output := strings.TrimSpace(rr.Stdout.String())
if err == nil && output == "" { // podman returns empty for 127.0.0.1
// check network, if the ip address is missing
ipv4, ipv6, err := dockerContainerIP(ociBin, name)
if err == nil {
return ipv4, ipv6, nil
}
return DefaultBindIPV4, "", nil
}
return output, "", nil
}
// dockerContainerIP returns ipv4, ipv6 of container or error
func dockerContainerIP(ociBin string, name string) (string, string, error) {
// retrieve the IP address of the node using docker inspect
lines, err := inspect(ociBin, name, "{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}")
if err != nil {
return "", "", errors.Wrap(err, "inspecting NetworkSettings.Networks")
}
if len(lines) != 1 {
return "", "", errors.Errorf("IPs output should only be one line, got %d lines", len(lines))
}
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)
}
return ips[0], ips[1], nil
}