475 строки
17 KiB
Go
475 строки
17 KiB
Go
// Copyright 2017 Microsoft. All rights reserved.
|
|
// MIT License
|
|
|
|
package network
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Azure/azure-container-networking/cns"
|
|
"github.com/Azure/azure-container-networking/network/hnswrapper"
|
|
"github.com/Azure/azure-container-networking/network/policy"
|
|
"github.com/Azure/azure-container-networking/platform"
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/Microsoft/hcsshim/hcn"
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
// HNS network types.
|
|
hnsL2bridge = "l2bridge"
|
|
CnetAddressSpace = "cnetAddressSpace"
|
|
vEthernetAdapterPrefix = "vEthernet"
|
|
baseDecimal = 10
|
|
bitSize = 32
|
|
defaultRouteCIDR = "0.0.0.0/0"
|
|
// prefix for interface name created by azure network
|
|
ifNamePrefix = "vEthernet"
|
|
// ipv4 default hop
|
|
ipv4DefaultHop = "0.0.0.0"
|
|
// ipv6 default hop
|
|
ipv6DefaultHop = "::"
|
|
// ipv6 route cmd
|
|
routeCmd = "netsh interface ipv6 %s route \"%s\" \"%s\" \"%s\" store=persistent"
|
|
// add/delete ipv4 and ipv6 route rules to/from windows node
|
|
netRouteCmd = "netsh interface %s %s route \"%s\" \"%s\" \"%s\""
|
|
// Default IPv6 Route
|
|
defaultIPv6Route = "::/0"
|
|
// Default IPv6 nextHop
|
|
defaultIPv6NextHop = "fe80::1234:5678:9abc"
|
|
)
|
|
|
|
// Windows implementation of route.
|
|
type route interface{}
|
|
|
|
// UseHnsV2 indicates whether to use HNSv1 or HNSv2
|
|
// HNSv2 should be used if the NetNs is a valid GUID and if the platform
|
|
// has HCN which supports HNSv2 API.
|
|
func UseHnsV2(netNs string) (bool, error) {
|
|
// Check if the netNs is a valid GUID to decide on HNSv1 or HNSv2
|
|
useHnsV2 := false
|
|
var err error
|
|
if _, err = uuid.Parse(netNs); err == nil {
|
|
useHnsV2 = true
|
|
if err = Hnsv2.HNSV2Supported(); err != nil {
|
|
logger.Info("HNSV2 is not supported on this windows platform")
|
|
}
|
|
}
|
|
|
|
return useHnsV2, err
|
|
}
|
|
|
|
// Regarding this Hnsv2 and Hnv1 variable
|
|
// this pattern is to avoid passing around os specific objects in platform agnostic code
|
|
var Hnsv2 hnswrapper.HnsV2WrapperInterface = hnswrapper.Hnsv2wrapper{}
|
|
|
|
var Hnsv1 hnswrapper.HnsV1WrapperInterface = hnswrapper.Hnsv1wrapper{}
|
|
|
|
func EnableHnsV2Timeout(timeoutValue int) {
|
|
if _, ok := Hnsv2.(hnswrapper.Hnsv2wrapperwithtimeout); !ok {
|
|
timeoutDuration := time.Duration(timeoutValue) * time.Second
|
|
Hnsv2 = hnswrapper.Hnsv2wrapperwithtimeout{Hnsv2: hnswrapper.Hnsv2wrapper{}, HnsCallTimeout: timeoutDuration}
|
|
}
|
|
}
|
|
|
|
func EnableHnsV1Timeout(timeoutValue int) {
|
|
if _, ok := Hnsv1.(hnswrapper.Hnsv1wrapperwithtimeout); !ok {
|
|
timeoutDuration := time.Duration(timeoutValue) * time.Second
|
|
Hnsv1 = hnswrapper.Hnsv1wrapperwithtimeout{Hnsv1: hnswrapper.Hnsv1wrapper{}, HnsCallTimeout: timeoutDuration}
|
|
}
|
|
}
|
|
|
|
// newNetworkImplHnsV1 creates a new container network for HNSv1.
|
|
func (nm *networkManager) newNetworkImplHnsV1(nwInfo *EndpointInfo, extIf *externalInterface) (*network, error) {
|
|
var (
|
|
vlanid int
|
|
err error
|
|
)
|
|
|
|
networkAdapterName := extIf.Name
|
|
|
|
// Pass adapter name here if it is not empty, this is cause if we don't tell HNS which adapter to use
|
|
// it will just pick one randomly, this is a problem for customers that have multiple adapters
|
|
if nwInfo.AdapterName != "" {
|
|
networkAdapterName = nwInfo.AdapterName
|
|
}
|
|
|
|
// FixMe: Find a better way to check if a nic that is selected is not part of a vSwitch
|
|
// per hns team, the hns calls fails if passed a vSwitch interface
|
|
if strings.HasPrefix(networkAdapterName, vEthernetAdapterPrefix) {
|
|
logger.Info("vSwitch detected, setting adapter name to empty")
|
|
networkAdapterName = ""
|
|
}
|
|
|
|
logger.Info("Adapter name used with HNS is", zap.String("networkAdapterName", networkAdapterName))
|
|
|
|
// Initialize HNS network.
|
|
hnsNetwork := &hcsshim.HNSNetwork{
|
|
Name: nwInfo.NetworkID,
|
|
Type: hnsL2bridge,
|
|
NetworkAdapterName: networkAdapterName,
|
|
Policies: policy.SerializePolicies(policy.NetworkPolicy, nwInfo.NetworkPolicies, nil, false, false),
|
|
}
|
|
|
|
// Set the VLAN and OutboundNAT policies
|
|
opt, _ := nwInfo.Options[genericData].(map[string]interface{})
|
|
if opt != nil && opt[VlanIDKey] != nil {
|
|
vlanPolicy := hcsshim.VlanPolicy{
|
|
Type: "VLAN",
|
|
}
|
|
vlanID, _ := strconv.ParseUint(opt[VlanIDKey].(string), baseDecimal, bitSize)
|
|
vlanPolicy.VLAN = uint(vlanID)
|
|
|
|
serializedVlanPolicy, _ := json.Marshal(vlanPolicy)
|
|
hnsNetwork.Policies = append(hnsNetwork.Policies, serializedVlanPolicy)
|
|
|
|
vlanid = (int)(vlanPolicy.VLAN)
|
|
}
|
|
|
|
// Populate subnets.
|
|
for _, subnet := range nwInfo.Subnets {
|
|
hnsSubnet := hcsshim.Subnet{
|
|
AddressPrefix: subnet.Prefix.String(),
|
|
GatewayAddress: subnet.Gateway.String(),
|
|
}
|
|
|
|
hnsNetwork.Subnets = append(hnsNetwork.Subnets, hnsSubnet)
|
|
}
|
|
|
|
hnsResponse, err := Hnsv1.CreateNetwork(hnsNetwork, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
logger.Info("HNSNetworkRequest DELETE", zap.String("id", hnsResponse.Id))
|
|
hnsResponse, err := Hnsv1.DeleteNetwork(hnsResponse.Id)
|
|
logger.Info("HNSNetworkRequest DELETE response", zap.Any("hnsResponse", hnsResponse), zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
// route entry for pod cidr
|
|
if err = nm.appIPV6RouteEntry(nwInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create the network object.
|
|
nw := &network{
|
|
Id: nwInfo.NetworkID,
|
|
HnsId: hnsResponse.Id,
|
|
Mode: nwInfo.Mode,
|
|
Endpoints: make(map[string]*endpoint),
|
|
extIf: extIf,
|
|
VlanId: vlanid,
|
|
EnableSnatOnHost: nwInfo.EnableSnatOnHost,
|
|
NetNs: nwInfo.NetNs,
|
|
}
|
|
|
|
nwInfo.HNSNetworkID = hnsResponse.Id // we use this later in stateless to clean up in ADD if there is an error
|
|
|
|
globals, err := Hnsv1.GetHNSGlobals()
|
|
if err != nil || globals.Version.Major <= hcsshim.HNSVersion1803.Major {
|
|
// err would be not nil for windows 1709 & below
|
|
// Sleep for 10 seconds as a workaround for windows 1803 & below
|
|
// This is done only when the network is created.
|
|
time.Sleep(time.Duration(10) * time.Second)
|
|
}
|
|
|
|
return nw, nil
|
|
}
|
|
|
|
func (nm *networkManager) appIPV6RouteEntry(nwInfo *EndpointInfo) error {
|
|
var (
|
|
err error
|
|
out string
|
|
)
|
|
|
|
if nwInfo.IPV6Mode == IPV6Nat {
|
|
if len(nwInfo.Subnets) < 2 {
|
|
return fmt.Errorf("Ipv6 subnet not found in network state")
|
|
}
|
|
|
|
// get interface name of VM adapter
|
|
ifName := nwInfo.MasterIfName
|
|
if !strings.Contains(nwInfo.MasterIfName, ifNamePrefix) {
|
|
ifName = fmt.Sprintf("%s (%s)", ifNamePrefix, nwInfo.MasterIfName)
|
|
}
|
|
|
|
cmd := fmt.Sprintf(routeCmd, "delete", nwInfo.Subnets[1].Prefix.String(),
|
|
ifName, ipv6DefaultHop)
|
|
if out, err = nm.plClient.ExecuteRawCommand(cmd); err != nil {
|
|
logger.Error("Deleting ipv6 route failed", zap.Any("out", out), zap.Error(err))
|
|
}
|
|
|
|
cmd = fmt.Sprintf(routeCmd, "add", nwInfo.Subnets[1].Prefix.String(),
|
|
ifName, ipv6DefaultHop)
|
|
if out, err = nm.plClient.ExecuteRawCommand(cmd); err != nil {
|
|
logger.Error("Adding ipv6 route failed", zap.Any("out", out), zap.Error(err))
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// configureHcnEndpoint configures hcn endpoint for creation
|
|
func (nm *networkManager) configureHcnNetwork(nwInfo *EndpointInfo, extIf *externalInterface) (*hcn.HostComputeNetwork, error) {
|
|
// Initialize HNS network.
|
|
hcnNetwork := &hcn.HostComputeNetwork{
|
|
Name: nwInfo.NetworkID,
|
|
Type: hcn.L2Bridge,
|
|
Ipams: []hcn.Ipam{
|
|
{
|
|
Type: hcnIpamTypeStatic,
|
|
},
|
|
},
|
|
SchemaVersion: hcn.SchemaVersion{
|
|
Major: hcnSchemaVersionMajor,
|
|
Minor: hcnSchemaVersionMinor,
|
|
},
|
|
}
|
|
|
|
// Set hcn network adaptor name policy
|
|
// FixMe: Find a better way to check if a nic that is selected is not part of a vSwitch
|
|
// per hns team, the hns calls fails if passed a vSwitch interface
|
|
// Pass adapter name here if it is not empty, this is cause if we don't tell HNS which adapter to use
|
|
// it will just pick one randomly, this is a problem for customers that have multiple adapters
|
|
if nwInfo.AdapterName != "" || !strings.HasPrefix(extIf.Name, vEthernetAdapterPrefix) {
|
|
var adapterName string
|
|
if nwInfo.AdapterName != "" {
|
|
adapterName = nwInfo.AdapterName
|
|
} else {
|
|
adapterName = extIf.Name
|
|
}
|
|
|
|
logger.Info("Adapter name used with HNS is", zap.String("adapterName", adapterName))
|
|
|
|
netAdapterNamePolicy, err := policy.GetHcnNetAdapterPolicy(adapterName)
|
|
if err != nil {
|
|
logger.Error("Failed to serialize network adapter policy due to", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
hcnNetwork.Policies = append(hcnNetwork.Policies, netAdapterNamePolicy)
|
|
}
|
|
|
|
// Set hcn subnet policy
|
|
var (
|
|
vlanid int
|
|
subnetPolicy []byte
|
|
)
|
|
|
|
opt, _ := nwInfo.Options[genericData].(map[string]interface{})
|
|
if opt != nil && opt[VlanIDKey] != nil {
|
|
var err error
|
|
vlanID, _ := strconv.ParseUint(opt[VlanIDKey].(string), baseDecimal, bitSize)
|
|
subnetPolicy, err = policy.SerializeHcnSubnetVlanPolicy((uint32)(vlanID))
|
|
if err != nil {
|
|
logger.Error("Failed to serialize subnet vlan policy due to", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
vlanid = (int)(vlanID)
|
|
}
|
|
|
|
// AccelnetNIC flag: hcn.EnableIov(9216) - treat Delegated/FrontendNIC also the same as Accelnet
|
|
// For L1VH with accelnet, hcn.DisableHostPort and hcn.EnableIov must be configured
|
|
if nwInfo.NICType == cns.NodeNetworkInterfaceFrontendNIC {
|
|
hcnNetwork.Type = hcn.Transparent
|
|
// set transparent network as non-persistent so that networks will be gone after the node gets rebooted
|
|
// hcnNetwork.flags = hcn.DisableHostPort | hcn.EnableIov | hcn.EnableNonPersistent (1024 + 8192 + 8 = 9224)
|
|
hcnNetwork.Flags = hcn.DisableHostPort | hcn.EnableIov | hcn.EnableNonPersistent
|
|
}
|
|
|
|
// Populate subnets.
|
|
for _, subnet := range nwInfo.Subnets {
|
|
hnsSubnet := hcn.Subnet{
|
|
IpAddressPrefix: subnet.Prefix.String(),
|
|
// Set the Gateway route
|
|
Routes: []hcn.Route{
|
|
{
|
|
NextHop: subnet.Gateway.String(),
|
|
DestinationPrefix: defaultRouteCIDR,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Set the subnet policy
|
|
if vlanid > 0 {
|
|
hnsSubnet.Policies = append(hnsSubnet.Policies, subnetPolicy)
|
|
}
|
|
|
|
hcnNetwork.Ipams[0].Subnets = append(hcnNetwork.Ipams[0].Subnets, hnsSubnet)
|
|
}
|
|
|
|
return hcnNetwork, nil
|
|
}
|
|
|
|
func (nm *networkManager) addIPv6DefaultRoute() error {
|
|
// add ipv6 default route if it does not exist in dualstack overlay windows node from persistent store
|
|
// persistent store setting is only read during the adapter restarts or reboots to re-populate the active store
|
|
|
|
// get interface index to add ipv6 default route and only consider there is one vEthernet interface for now
|
|
getIpv6IfIndexCmd := `((Get-NetIPInterface | where InterfaceAlias -Like "vEthernet*").IfIndex)[0]`
|
|
ifIndex, err := nm.plClient.ExecutePowershellCommand(getIpv6IfIndexCmd)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error while executing powershell command to get ipv6 Hyper-V interface")
|
|
}
|
|
|
|
getIPv6RoutePersistentCmd := fmt.Sprintf("Get-NetRoute -DestinationPrefix %s -PolicyStore Persistentstore", defaultIPv6Route)
|
|
if out, err := nm.plClient.ExecutePowershellCommand(getIPv6RoutePersistentCmd); err != nil {
|
|
logger.Info("ipv6 default route is not found from persistentstore, adding default ipv6 route to the windows node", zap.String("out", out), zap.Error(err))
|
|
// run powershell cmd to add ipv6 default route
|
|
// if there is an ipv6 default route in active store but not persistent store; to add ipv6 default route to persistent store
|
|
// need to remove ipv6 default route from active store and then use this command to add default route entry to both active and persistent store
|
|
addCmd := fmt.Sprintf("Remove-NetRoute -DestinationPrefix %s -InterfaceIndex %s -NextHop %s -confirm:$false;New-NetRoute -DestinationPrefix %s -InterfaceIndex %s -NextHop %s -confirm:$false",
|
|
defaultIPv6Route, ifIndex, defaultIPv6NextHop, defaultIPv6Route, ifIndex, defaultIPv6NextHop)
|
|
|
|
if _, err := nm.plClient.ExecutePowershellCommand(addCmd); err != nil {
|
|
return errors.Wrap(err, "Failed to add ipv6 default route to both persistent and active store")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// newNetworkImplHnsV2 creates a new container network for HNSv2.
|
|
func (nm *networkManager) newNetworkImplHnsV2(nwInfo *EndpointInfo, extIf *externalInterface) (*network, error) {
|
|
// network creation is not required for IB
|
|
if nwInfo.NICType == cns.BackendNIC {
|
|
return &network{Endpoints: make(map[string]*endpoint)}, nil
|
|
}
|
|
|
|
hcnNetwork, err := nm.configureHcnNetwork(nwInfo, extIf)
|
|
if err != nil {
|
|
logger.Error("Failed to configure hcn network due to", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// check if network exists, only create the network does not exist
|
|
hnsResponse, err := Hnsv2.GetNetworkByName(hcnNetwork.Name)
|
|
|
|
if err != nil {
|
|
// if network not found, create the HNS network.
|
|
if errors.As(err, &hcn.NetworkNotFoundError{}) {
|
|
logger.Info("Creating hcn network", zap.Any("hcnNetwork", hcnNetwork))
|
|
hnsResponse, err = Hnsv2.CreateNetwork(hcnNetwork)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to create hcn network: %s due to error: %v", hcnNetwork.Name, err)
|
|
}
|
|
logger.Info("Successfully created hcn network with response", zap.Any("hnsResponse", hnsResponse))
|
|
} else {
|
|
// we can't validate if the network already exists, don't continue
|
|
return nil, fmt.Errorf("Failed to create hcn network: %s, failed to query for existing network with error: %v", hcnNetwork.Name, err)
|
|
}
|
|
} else {
|
|
if hcnNetwork.Type == hcn.Transparent {
|
|
// CNI triggers Add() for new pod first and then delete older pod later
|
|
// for transparent network type, do not ignore network creation if network already exists
|
|
// return error to avoid creating second endpoint
|
|
logger.Error("HNS network with name already exists. Returning error for transparent network", zap.String("networkName", hcnNetwork.Name))
|
|
return nil, fmt.Errorf("HNS network with name:%s already exists. Returning error for transparent network", hcnNetwork.Name) //nolint
|
|
}
|
|
logger.Info("Network with name already exists", zap.String("name", hcnNetwork.Name))
|
|
}
|
|
|
|
// check if ipv6 default gateway route is missing before windows endpoint creation
|
|
for i := range nwInfo.Subnets {
|
|
if nwInfo.Subnets[i].Family == platform.AfINET6 {
|
|
if err = nm.addIPv6DefaultRoute(); err != nil {
|
|
// should not block network creation but remind user that it's failed to add ipv6 default route to windows node
|
|
logger.Error("failed to add missing ipv6 default route to windows node active/persistent store", zap.Error(err))
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
var vlanid int
|
|
opt, _ := nwInfo.Options[genericData].(map[string]interface{})
|
|
if opt != nil && opt[VlanIDKey] != nil {
|
|
vlanID, _ := strconv.ParseInt(opt[VlanIDKey].(string), baseDecimal, bitSize)
|
|
vlanid = (int)(vlanID)
|
|
}
|
|
|
|
// Create the network object.
|
|
nw := &network{
|
|
Id: nwInfo.NetworkID,
|
|
HnsId: hnsResponse.Id,
|
|
Mode: nwInfo.Mode,
|
|
Endpoints: make(map[string]*endpoint),
|
|
extIf: extIf,
|
|
VlanId: vlanid,
|
|
EnableSnatOnHost: nwInfo.EnableSnatOnHost,
|
|
NetNs: nwInfo.NetNs,
|
|
}
|
|
|
|
nwInfo.HNSNetworkID = hnsResponse.Id // we use this later in stateless to clean up in ADD if there is an error
|
|
|
|
return nw, nil
|
|
}
|
|
|
|
// NewNetworkImpl creates a new container network.
|
|
func (nm *networkManager) newNetworkImpl(nwInfo *EndpointInfo, extIf *externalInterface) (*network, error) {
|
|
if useHnsV2, err := UseHnsV2(nwInfo.NetNs); useHnsV2 {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nm.newNetworkImplHnsV2(nwInfo, extIf)
|
|
}
|
|
return nm.newNetworkImplHnsV1(nwInfo, extIf)
|
|
}
|
|
|
|
// DeleteNetworkImpl deletes an existing container network.
|
|
func (nm *networkManager) deleteNetworkImpl(nw *network, nicType cns.NICType) error {
|
|
if nicType != cns.NodeNetworkInterfaceFrontendNIC { //nolint
|
|
return nil
|
|
}
|
|
|
|
logger.Info("Deleting HNS network", zap.String("networkID", nw.HnsId), zap.Any("nictype", nicType))
|
|
|
|
if useHnsV2, err := UseHnsV2(nw.NetNs); useHnsV2 {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nm.deleteNetworkImplHnsV2(nw)
|
|
}
|
|
return nm.deleteNetworkImplHnsV1(nw)
|
|
}
|
|
|
|
// DeleteNetworkImplHnsV1 deletes an existing container network using HnsV1.
|
|
func (nm *networkManager) deleteNetworkImplHnsV1(nw *network) error {
|
|
logger.Info("HNSNetworkRequest DELETE id", zap.String("id", nw.HnsId))
|
|
hnsResponse, err := Hnsv1.DeleteNetwork(nw.HnsId)
|
|
logger.Info("HNSNetworkRequest DELETE response", zap.Any("hnsResponse", hnsResponse), zap.Error(err))
|
|
|
|
return err
|
|
}
|
|
|
|
// DeleteNetworkImplHnsV2 deletes an existing container network using Hnsv2.
|
|
func (nm *networkManager) deleteNetworkImplHnsV2(nw *network) error {
|
|
var hcnNetwork *hcn.HostComputeNetwork
|
|
var err error
|
|
logger.Info("Deleting hcn network with id", zap.String("id", nw.HnsId))
|
|
|
|
if hcnNetwork, err = Hnsv2.GetNetworkByID(nw.HnsId); err != nil {
|
|
return fmt.Errorf("Failed to get hcn network with id: %s due to err: %v", nw.HnsId, err)
|
|
}
|
|
|
|
if err = Hnsv2.DeleteNetwork(hcnNetwork); err != nil {
|
|
return fmt.Errorf("Failed to delete hcn network: %s due to error: %v", nw.HnsId, err)
|
|
}
|
|
|
|
logger.Info("Successfully deleted hcn network with id", zap.String("id", nw.HnsId))
|
|
|
|
return err
|
|
}
|
|
|
|
func getNetworkInfoImpl(_ *EndpointInfo, _ *network) {
|
|
}
|