azure-container-networking/network/endpoint.go

413 строки
13 KiB
Go

// Copyright 2017 Microsoft. All rights reserved.
// MIT License
package network
import (
"context"
"fmt"
"net"
"strings"
"github.com/Azure/azure-container-networking/cni/log"
"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/netio"
"github.com/Azure/azure-container-networking/netlink"
"github.com/Azure/azure-container-networking/network/policy"
"github.com/Azure/azure-container-networking/platform"
"github.com/pkg/errors"
"go.uber.org/zap"
)
const (
InfraVnet = 0
)
var logger = log.CNILogger.With(zap.String("component", "net"))
type AzureHNSEndpoint struct{}
// Endpoint represents a container network interface.
type endpoint struct {
Id string
HnsId string `json:",omitempty"`
HNSNetworkID string `json:",omitempty"`
SandboxKey string
IfName string
HostIfName string
MacAddress net.HardwareAddr
InfraVnetIP net.IPNet
LocalIP string
IPAddresses []net.IPNet
Gateways []net.IP
DNS DNSInfo
Routes []RouteInfo
VlanID int
EnableSnatOnHost bool
EnableInfraVnet bool
EnableMultitenancy bool
AllowInboundFromHostToNC bool
AllowInboundFromNCToHost bool
NetworkContainerID string
NetworkNameSpace string `json:",omitempty"`
ContainerID string
PODName string `json:",omitempty"`
PODNameSpace string `json:",omitempty"`
InfraVnetAddressSpace string `json:",omitempty"`
NetNs string `json:",omitempty"` // used in windows
// SecondaryInterfaces is a map of interface name to InterfaceInfo
SecondaryInterfaces map[string]*InterfaceInfo
// Store nic type since we no longer populate SecondaryInterfaces
NICType cns.NICType
}
// EndpointInfo contains read-only information about an endpoint.
type EndpointInfo struct {
EndpointID string
ContainerID string
NetNsPath string
IfName string // value differs during creation vs. deletion flow; used in statefile, not necessarily the nic name
SandboxKey string
IfIndex int
MacAddress net.HardwareAddr
EndpointDNS DNSInfo
IPAddresses []net.IPNet
IPsToRouteViaHost []string
InfraVnetIP net.IPNet
Routes []RouteInfo
EndpointPolicies []policy.Policy // used in windows
NetworkPolicies []policy.Policy // used in windows
Gateways []net.IP
EnableSnatOnHost bool
EnableInfraVnet bool
EnableMultiTenancy bool
EnableSnatForDns bool
AllowInboundFromHostToNC bool
AllowInboundFromNCToHost bool
NetworkContainerID string
PODName string
PODNameSpace string
Data map[string]interface{}
InfraVnetAddressSpace string
SkipHotAttachEp bool
IPV6Mode string
VnetCidrs string
ServiceCidrs string
NATInfo []policy.NATInfo // windows only
NICType cns.NICType
SkipDefaultRoutes bool
HNSEndpointID string
HNSNetworkID string
HostIfName string // unused in windows, and in linux
// Fields related to the network are below
MasterIfName string
AdapterName string
NetworkID string
Mode string
Subnets []SubnetInfo
BridgeName string
NetNs string // used in windows
Options map[string]interface{}
DisableHairpinOnHostInterface bool
IsIPv6Enabled bool
HostSubnetPrefix string // can be used later to add an external interface
PnPID string
}
// RouteInfo contains information about an IP route.
type RouteInfo struct {
Dst net.IPNet
Src net.IP
Gw net.IP
Protocol int
DevName string
Scope int
Priority int
Table int
}
// InterfaceInfo contains information for secondary interfaces
type InterfaceInfo struct {
Name string
MacAddress net.HardwareAddr
IPConfigs []*IPConfig
Routes []RouteInfo
DNS DNSInfo
NICType cns.NICType
SkipDefaultRoutes bool
HostSubnetPrefix net.IPNet // Move this field from ipamAddResult
NCResponse *cns.GetNetworkContainerResponse
PnPID string
}
type IPConfig struct {
Address net.IPNet
Gateway net.IP
}
type apipaClient interface {
DeleteHostNCApipaEndpoint(ctx context.Context, networkContainerID string) error
CreateHostNCApipaEndpoint(ctx context.Context, networkContainerID string) (string, error)
}
func (epInfo *EndpointInfo) PrettyString() string {
return fmt.Sprintf("Id:%s ContainerID:%s NetNsPath:%s IfName:%s IfIndex:%d MacAddr:%s IPAddrs:%v Gateways:%v Data:%+v NICType: %s NetworkContainerID: %s HostIfName: %s NetNs: %s Options: %v",
epInfo.EndpointID, epInfo.ContainerID, epInfo.NetNsPath, epInfo.IfName, epInfo.IfIndex, epInfo.MacAddress.String(), epInfo.IPAddresses,
epInfo.Gateways, epInfo.Data, epInfo.NICType, epInfo.NetworkContainerID, epInfo.HostIfName, epInfo.NetNs, epInfo.Options)
}
func (ifInfo *InterfaceInfo) PrettyString() string {
return fmt.Sprintf("Name:%s NICType:%v MacAddr:%s IPConfigs:%+v Routes:%+v DNSInfo:%+v",
ifInfo.Name, ifInfo.NICType, ifInfo.MacAddress.String(), ifInfo.IPConfigs, ifInfo.Routes, ifInfo.DNS)
}
// NewEndpoint creates a new endpoint in the network.
func (nw *network) newEndpoint(
apipaCli apipaClient,
nl netlink.NetlinkInterface,
plc platform.ExecClient,
netioCli netio.NetIOInterface,
nsc NamespaceClientInterface,
iptc ipTablesClient,
dhcpc dhcpClient,
epInfo *EndpointInfo,
) (*endpoint, error) {
var ep *endpoint
var err error
defer func() {
if err != nil {
logger.Error("Failed to create endpoint with err", zap.String("id", epInfo.EndpointID), zap.Error(err))
}
}()
// Call the platform implementation.
// Pass nil for epClient and will be initialized in newendpointImpl
ep, err = nw.newEndpointImpl(apipaCli, nl, plc, netioCli, nil, nsc, iptc, dhcpc, epInfo)
if err != nil {
return nil, err
}
nw.Endpoints[ep.Id] = ep
logger.Info("Created endpoint. Num of endpoints", zap.Any("ep", ep), zap.Int("numEndpoints", len(nw.Endpoints)))
return ep, nil
}
// DeleteEndpoint deletes an existing endpoint from the network.
func (nw *network) deleteEndpoint(nl netlink.NetlinkInterface, plc platform.ExecClient, nioc netio.NetIOInterface, nsc NamespaceClientInterface,
iptc ipTablesClient, dhcpc dhcpClient, endpointID string,
) error {
var err error
logger.Info("Deleting endpoint from network", zap.String("endpointID", endpointID), zap.String("id", nw.Id))
defer func() {
if err != nil {
logger.Error("Failed to delete endpoint with", zap.String("endpointID", endpointID), zap.Error(err))
}
}()
// Look up the endpoint.
ep, err := nw.getEndpoint(endpointID)
if err != nil {
logger.Error("Endpoint not found. Not Returning error", zap.String("endpointID", endpointID), zap.Error(err))
return nil
}
// Call the platform implementation.
// Pass nil for epClient and will be initialized in deleteEndpointImpl
err = nw.deleteEndpointImpl(nl, plc, nil, nioc, nsc, iptc, dhcpc, ep)
if err != nil {
return err
}
// Remove the endpoint object.
delete(nw.Endpoints, endpointID)
logger.Info("Deleted endpoint. Num of endpoints", zap.Any("ep", ep), zap.Int("numEndpoints", len(nw.Endpoints)))
return nil
}
// GetEndpoint returns the endpoint with the given ID.
func (nw *network) getEndpoint(endpointId string) (*endpoint, error) {
ep := nw.Endpoints[endpointId]
if ep == nil {
return nil, errEndpointNotFound
}
return ep, nil
}
// GetEndpointByPOD returns the endpoint with the given ID.
func (nw *network) getEndpointByPOD(podName string, podNameSpace string, doExactMatchForPodName bool) (*endpoint, error) {
logger.Info("Trying to retrieve endpoint for pod name in namespace", zap.String("podName", podName), zap.String("podNameSpace", podNameSpace))
var ep *endpoint
for _, endpoint := range nw.Endpoints {
if podNameMatches(endpoint.PODName, podName, doExactMatchForPodName) && endpoint.PODNameSpace == podNameSpace {
if ep == nil {
ep = endpoint
} else {
return nil, errMultipleEndpointsFound
}
}
}
if ep == nil {
return nil, errEndpointNotFound
}
return ep, nil
}
func podNameMatches(source string, actualValue string, doExactMatch bool) bool {
if doExactMatch {
return source == actualValue
} else {
// If exact match flag is disabled we just check if the existing podname field for an endpoint
// starts with passed podname string.
return actualValue == GetPodNameWithoutSuffix(source)
}
}
//
// Endpoint
//
// GetInfo returns information about the endpoint.
func (ep *endpoint) getInfo() *EndpointInfo {
info := &EndpointInfo{
EndpointID: ep.Id,
IPAddresses: ep.IPAddresses,
InfraVnetIP: ep.InfraVnetIP,
Data: make(map[string]interface{}),
MacAddress: ep.MacAddress,
SandboxKey: ep.SandboxKey,
IfIndex: 0, // Azure CNI supports only one interface
EndpointDNS: ep.DNS,
EnableSnatOnHost: ep.EnableSnatOnHost,
EnableInfraVnet: ep.EnableInfraVnet,
EnableMultiTenancy: ep.EnableMultitenancy,
AllowInboundFromHostToNC: ep.AllowInboundFromHostToNC,
AllowInboundFromNCToHost: ep.AllowInboundFromNCToHost,
IfName: ep.IfName,
ContainerID: ep.ContainerID,
NetNsPath: ep.NetworkNameSpace,
PODName: ep.PODName,
PODNameSpace: ep.PODNameSpace,
NetworkContainerID: ep.NetworkContainerID,
HNSEndpointID: ep.HnsId,
HostIfName: ep.HostIfName,
NICType: ep.NICType,
}
info.Routes = append(info.Routes, ep.Routes...)
info.Gateways = append(info.Gateways, ep.Gateways...)
// Call the platform implementation.
ep.getInfoImpl(info)
return info
}
// Attach attaches an endpoint to a sandbox.
func (ep *endpoint) attach(sandboxKey string) error {
if ep.SandboxKey != "" {
return errEndpointInUse
}
ep.SandboxKey = sandboxKey
logger.Info("Attached endpoint to sandbox", zap.String("id", ep.Id), zap.String("sandboxKey", sandboxKey))
return nil
}
// Detach detaches an endpoint from its sandbox.
func (ep *endpoint) detach() error {
if ep.SandboxKey == "" {
return errEndpointNotInUse
}
logger.Info("Detached endpoint from sandbox", zap.String("id", ep.Id), zap.String("sandboxKey", ep.SandboxKey))
ep.SandboxKey = ""
return nil
}
// updateEndpoint updates an existing endpoint in the network.
func (nm *networkManager) updateEndpoint(nw *network, existingEpInfo, targetEpInfo *EndpointInfo) error {
var err error
logger.Info("Updating existing endpoint in network to target", zap.Any("existingEpInfo", existingEpInfo),
zap.String("id", nw.Id), zap.Any("targetEpInfo", targetEpInfo))
defer func() {
if err != nil {
logger.Error("Failed to update endpoint with err", zap.String("id", existingEpInfo.EndpointID), zap.Error(err))
}
}()
logger.Info("Trying to retrieve endpoint id", zap.String("id", existingEpInfo.EndpointID))
ep := nw.Endpoints[existingEpInfo.EndpointID]
if ep == nil {
return errEndpointNotFound
}
logger.Info("Retrieved endpoint to update", zap.Any("ep", ep))
// Call the platform implementation.
ep, err = nm.updateEndpointImpl(nw, existingEpInfo, targetEpInfo)
if err != nil {
return err
}
// Update routes for existing endpoint
nw.Endpoints[existingEpInfo.EndpointID].Routes = ep.Routes
return nil
}
func GetPodNameWithoutSuffix(podName string) string {
nameSplit := strings.Split(podName, "-")
if len(nameSplit) > 2 {
nameSplit = nameSplit[:len(nameSplit)-2]
} else {
return podName
}
return strings.Join(nameSplit, "-")
}
// IsEndpointStateInComplete returns true if both HNSEndpointID and HostVethName are missing.
func (epInfo *EndpointInfo) IsEndpointStateIncomplete() bool {
if epInfo.HNSEndpointID == "" && epInfo.HostIfName == "" {
return true
}
return false
}
func (ep *endpoint) validateEndpoint() error {
if ep.ContainerID == "" || ep.NICType == "" {
return errors.New("endpoint struct must contain a container id and nic type")
}
return nil
}
func validateEndpoints(eps []*endpoint) error {
containerIDs := map[string]bool{}
for _, ep := range eps {
if err := ep.validateEndpoint(); err != nil {
return errors.Wrap(err, "failed to validate endpoint struct")
}
containerIDs[ep.ContainerID] = true
if len(containerIDs) != 1 {
return errors.New("multiple distinct container ids detected")
}
}
return nil
}