categraf/logs/util/docker/network.go

205 lines
7.0 KiB
Go

//go:build !no_logs
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
package docker
import (
"context"
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"sort"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"flashcat.cloud/categraf/logs/util/containers"
"flashcat.cloud/categraf/logs/util/containers/providers"
)
type dockerNetwork struct {
iface string
dockerName string
// Temporary store of id for containers that route through another container
// such as in the "pod container" case used by Kubernetes. The network
// resolution should resolve this network to the correct interface from the
// referenced container.
routingContainerID string
}
type dockerNetworks []dockerNetwork
func (a dockerNetworks) Len() int { return len(a) }
func (a dockerNetworks) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a dockerNetworks) Less(i, j int) bool { return a[i].dockerName < a[j].dockerName }
var hostNetwork = dockerNetwork{iface: "eth0", dockerName: "bridge"}
const (
ecsPauseContainerImage = "amazon/amazon-ecs-pause"
containerModePrefix = "container:"
)
func findDockerNetworks(containerID string, pid int, container types.Container) []dockerNetwork {
netMode := container.HostConfig.NetworkMode
// Check the known network modes that require specific handling.
// Other network modes will look at the docker NetworkSettings.
if netMode == containers.HostNetworkMode {
log.Println("Container %s is in network host mode, its network metrics are for the whole host", containerID)
return []dockerNetwork{hostNetwork}
} else if netMode == containers.NoneNetworkMode {
log.Println("Container %s is in network mode 'none', we will collect metrics for the whole host", containerID)
return []dockerNetwork{hostNetwork}
} else if strings.HasPrefix(netMode, "container:") {
netContainerID := strings.TrimPrefix(netMode, "container:")
log.Println("Container %s uses the network namespace of container:%s", containerID, netContainerID)
return []dockerNetwork{{routingContainerID: netContainerID}}
}
// Verify that we aren't using an older version of Docker that does
// not provide the network settings in container inspect.
netSettings := container.NetworkSettings
if netSettings == nil || netSettings.Networks == nil || len(netSettings.Networks) == 0 {
log.Println("No network settings available from docker, defaulting to host network")
return []dockerNetwork{hostNetwork}
}
var err error
interfaces := make(map[string]uint64)
for netName, netConf := range netSettings.Networks {
if netName == "host" {
log.Println("Container %s is in network host mode, its network metrics are for the whole host", containerID)
return []dockerNetwork{hostNetwork}
}
ipString := netConf.IPAddress
// Check if this is a CIDR or just an IP
var ip net.IP
if strings.Contains(ipString, "/") {
ip, _, err = net.ParseCIDR(ipString)
if err != nil {
log.Printf("Malformed IP %s for container id %s: %s, skipping", ipString, containerID, err)
continue
}
} else {
ip = net.ParseIP(ipString)
if ip == nil {
log.Printf("Malformed IP %s for container id %s: %s, skipping", ipString, containerID, err)
continue
}
}
// Convert IP to little endian uint64 for comparison to network routes.
interfaces[netName] = uint64(binary.LittleEndian.Uint32(ip.To4()))
}
destinations, err := providers.ContainerImpl().DetectNetworkDestinations(pid)
if err != nil {
log.Printf("Cannot list interfaces for container id %s: %s, skipping", containerID, err)
return nil
}
networks := make([]dockerNetwork, 0)
for _, d := range destinations {
for n, ip := range interfaces {
if ip&d.Mask == d.Subnet {
networks = append(networks, dockerNetwork{iface: d.Interface, dockerName: n})
}
}
}
sort.Sort(dockerNetworks(networks))
return networks
}
// resolveDockerNetworks will resolve any network mappings in-place for any
// networks that are pointing to a containerID and rely on another containers
// network namespace. All other networks are left as-is.
// This should be called after findDockerNetworks is called for all running
// containers.
func resolveDockerNetworks(containerNetworks map[string][]dockerNetwork) {
for cid, networks := range containerNetworks {
for _, nw := range networks {
if nw.routingContainerID == "" {
continue
}
if cnw, ok := containerNetworks[nw.routingContainerID]; ok {
containerNetworks[cid] = cnw
} else {
log.Printf("Unable to resolve network for c:%s that uses namespace of c:%s", cid, nw.routingContainerID)
containerNetworks[cid] = nil
}
}
}
}
// GetAgentContainerNetworkMode provides the network mode of the Agent container
// To get this info in an optimal way, consider calling util.GetAgentNetworkMode func GetContainerNetworkMode(cid string) (string, error) {
// instead to benefit from the cache
func GetAgentContainerNetworkMode(ctx context.Context) (string, error) {
agentCID, _ := providers.ContainerImpl().GetAgentCID()
return GetContainerNetworkMode(ctx, agentCID)
}
// GetContainerNetworkMode returns the network mode of a container
func GetContainerNetworkMode(ctx context.Context, cid string) (string, error) {
du, err := GetDockerUtil()
if err != nil {
return "", err
}
container, err := du.Inspect(ctx, cid, false)
if err != nil {
return "", err
}
mode, err := parseContainerNetworkMode(container.HostConfig)
if err != nil {
return mode, err
}
// Try to discover awsvpc mode
if strings.HasPrefix(mode, containerModePrefix) {
// Inspect the attached container
co, err := du.Inspect(ctx, mode[len(containerModePrefix):], false)
if err != nil {
return "", fmt.Errorf("cannot inspect attached container %s: %v", mode, err)
}
// In awsvpc mode, the attached container is an amazon ecs pause container
if co.Config != nil && strings.HasPrefix(co.Config.Image, ecsPauseContainerImage) {
return containers.AwsvpcNetworkMode, nil
}
return containers.UnknownNetworkMode, fmt.Errorf("unknown network mode: %s", mode)
}
return mode, nil
}
// parseContainerNetworkAddresses converts ECS container ports
// and networks into a list of NetworkAddress
// parseContainerNetworkMode returns the network mode of a container
func parseContainerNetworkMode(hostConfig *container.HostConfig) (string, error) {
if hostConfig == nil {
return "", errors.New("the HostConfig field is nil")
}
mode := string(hostConfig.NetworkMode)
switch mode {
case containers.DefaultNetworkMode:
return containers.DefaultNetworkMode, nil
case containers.HostNetworkMode:
return containers.HostNetworkMode, nil
case containers.BridgeNetworkMode:
return containers.BridgeNetworkMode, nil
case containers.NoneNetworkMode:
return containers.NoneNetworkMode, nil
}
if strings.HasPrefix(mode, containerModePrefix) {
return mode, nil
}
return containers.UnknownNetworkMode, fmt.Errorf("unknown network mode: %s", mode)
}