cni/network/network_windows.go (327 lines of code) (raw):

package network import ( "encoding/json" "fmt" "net" "net/netip" "os" "path/filepath" "strconv" "strings" "github.com/Azure/azure-container-networking/cni" "github.com/Azure/azure-container-networking/cni/util" "github.com/Azure/azure-container-networking/cns" "github.com/Azure/azure-container-networking/network" "github.com/Azure/azure-container-networking/network/networkutils" "github.com/Azure/azure-container-networking/network/policy" "github.com/Microsoft/hcsshim" hnsv2 "github.com/Microsoft/hcsshim/hcn" cniTypesCurr "github.com/containernetworking/cni/pkg/types/100" "github.com/pkg/errors" "go.uber.org/zap" "golang.org/x/sys/windows/registry" ) var ( snatConfigFileName = filepath.FromSlash(os.Getenv("TEMP")) + "\\snatConfig" // windows build for version 1903 win1903Version = 18362 dualStackCount = 2 ) func addDefaultRoute(_ string, _ *network.EndpointInfo, _ *network.InterfaceInfo) { } func addSnatForDNS(_ string, _ *network.EndpointInfo, _ *network.InterfaceInfo) { } // updates options field func setNetworkOptions(cnsNwConfig *cns.GetNetworkContainerResponse, nwInfo *network.EndpointInfo) { if cnsNwConfig != nil && cnsNwConfig.MultiTenancyInfo.ID != 0 { logger.Info("Setting Network Options") optionsMap := make(map[string]interface{}) optionsMap[network.VlanIDKey] = strconv.Itoa(cnsNwConfig.MultiTenancyInfo.ID) logger.Info("Add vlanIDKey to optionsMap", zap.String("vlanIDKey", network.VlanIDKey)) nwInfo.Options[dockerNetworkOption] = optionsMap } } func setEndpointOptions(cnsNwConfig *cns.GetNetworkContainerResponse, epInfo *network.EndpointInfo, _ string) { if cnsNwConfig != nil && cnsNwConfig.MultiTenancyInfo.ID != 0 { logger.Info("Setting Endpoint Options") var cnetAddressMap []string for _, ipSubnet := range cnsNwConfig.CnetAddressSpace { cnetAddressMap = append(cnetAddressMap, ipSubnet.IPAddress+"/"+strconv.Itoa(int(ipSubnet.PrefixLength))) } epInfo.Data[network.CnetAddressSpace] = cnetAddressMap epInfo.AllowInboundFromHostToNC = cnsNwConfig.AllowHostToNCCommunication epInfo.AllowInboundFromNCToHost = cnsNwConfig.AllowNCToHostCommunication epInfo.NetworkContainerID = cnsNwConfig.NetworkContainerID } } func addSnatInterface(nwCfg *cni.NetworkConfig, result *cniTypesCurr.Result) { } func (plugin *NetPlugin) getNetworkName(netNs string, interfaceInfo *network.InterfaceInfo, nwCfg *cni.NetworkConfig) (string, error) { var err error // Swiftv2 path => interfaceInfo.NICType = delegated NIC // For singletenancy => nwCfg.Name // Swiftv1 => interfaceInfo.NCResponse != nil && ipamAddResult != nil determineWinVer() // Swiftv2 L1VH Network Name swiftv2NetworkNamePrefix := "azure-" if interfaceInfo != nil && (interfaceInfo.NICType == cns.NodeNetworkInterfaceFrontendNIC || interfaceInfo.NICType == cns.BackendNIC) { logger.Info("swiftv2", zap.String("network name", interfaceInfo.MacAddress.String())) return swiftv2NetworkNamePrefix + interfaceInfo.MacAddress.String(), nil } // For singletenancy, the network name is simply the nwCfg.Name if !nwCfg.MultiTenancy { return nwCfg.Name, nil } // in multitenancy case, the network name will be in the state file or can be built from cnsResponse if len(strings.TrimSpace(netNs)) == 0 { return "", fmt.Errorf("NetNs cannot be empty") } // First try to build the network name from the cnsResponse if present // This will happen during ADD call // ifIndex, err := findDefaultInterface(*ipamAddResult) if interfaceInfo != nil && interfaceInfo.NCResponse != nil { // swiftv1 path if err != nil { logger.Error("Error finding InfraNIC interface", zap.Error(err)) return "", errors.Wrap(err, "cns did not return an InfraNIC") } // networkName will look like ~ azure-vlan1-172-28-1-0_24 ipAddrNet := interfaceInfo.IPConfigs[0].Address prefix, err := netip.ParsePrefix(ipAddrNet.String()) if err != nil { logger.Error("Error parsing network CIDR", zap.String("cidr", ipAddrNet.String()), zap.Error(err)) return "", errors.Wrapf(err, "cns returned invalid CIDR %s", ipAddrNet.String()) } networkName := strings.ReplaceAll(prefix.Masked().String(), ".", "-") networkName = strings.ReplaceAll(networkName, "/", "_") networkName = fmt.Sprintf("%s-vlan%v-%v", nwCfg.Name, interfaceInfo.NCResponse.MultiTenancyInfo.ID, networkName) return networkName, nil } // If no cnsResponse was present, try to get the network name from the state file // This will happen during DEL call networkName, err := plugin.nm.FindNetworkIDFromNetNs(netNs) if err != nil { logger.Error("No endpoint available", zap.String("netns", netNs), zap.Error(err)) return "", fmt.Errorf("No endpoint available with netNs: %s: %w", netNs, err) } return networkName, nil } func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, _ *network.EndpointInfo) { } func getNetworkDNSSettings(nwCfg *cni.NetworkConfig, _ network.DNSInfo) (network.DNSInfo, error) { var nwDNS network.DNSInfo // use custom dns if present nwDNS = getCustomDNS(nwCfg) if len(nwDNS.Servers) > 0 || nwDNS.Suffix != "" { return nwDNS, nil } if (len(nwCfg.DNS.Search) == 0) != (len(nwCfg.DNS.Nameservers) == 0) { err := fmt.Errorf("Wrong DNS configuration: %+v", nwCfg.DNS) return nwDNS, err } nwDNS = network.DNSInfo{ Servers: nwCfg.DNS.Nameservers, } return nwDNS, nil } func getEndpointDNSSettings(nwCfg *cni.NetworkConfig, dns network.DNSInfo, namespace string) (network.DNSInfo, error) { var epDNS network.DNSInfo // use custom dns if present epDNS = getCustomDNS(nwCfg) if len(epDNS.Servers) > 0 || epDNS.Suffix != "" { return epDNS, nil } if (len(nwCfg.DNS.Search) == 0) != (len(nwCfg.DNS.Nameservers) == 0) { err := fmt.Errorf("Wrong DNS configuration: %+v", nwCfg.DNS) return epDNS, err } if len(nwCfg.DNS.Search) > 0 { epDNS = network.DNSInfo{ Servers: nwCfg.DNS.Nameservers, Suffix: namespace + "." + strings.Join(nwCfg.DNS.Search, ","), Options: nwCfg.DNS.Options, } } else { epDNS = dns epDNS.Options = nwCfg.DNS.Options } return epDNS, nil } /* getPoliciesFromRuntimeCfg returns network policies from network config. Windows test-netconnection to ---> to node ipv4 to node ipv6 to localhost ipv4 to localhost ipv6 host port mapping w/ no host ip ok ok fail fail localhost ipv4 host ip fail fail fail fail node ipv6 host ip fail ok fail fail localhost ipv6 host ip fail fail fail fail node ipv4 host ip ok fail fail fail */ func getPoliciesFromRuntimeCfg(nwCfg *cni.NetworkConfig, isIPv6Enabled bool) ([]policy.Policy, error) { logger.Info("Runtime Info", zap.Any("config", nwCfg.RuntimeConfig)) var policies []policy.Policy var protocol uint32 for _, mapping := range nwCfg.RuntimeConfig.PortMappings { cfgProto := strings.ToUpper(strings.TrimSpace(mapping.Protocol)) switch cfgProto { case "TCP": protocol = policy.ProtocolTcp case "UDP": protocol = policy.ProtocolUdp } // To support hostport policy mapping // uint32 NatFlagsLocalRoutedVip = 1 // To support hostport policy mapping for ipv6 in dualstack overlay mode // uint32 NatFlagsIPv6 = 2 // if host ip is specified, we create a policy to match that ip only (ipv4 or ipv6), or ipv4 if no host ip flag := hnsv2.NatFlagsLocalRoutedVip // ipv4 flag if mapping.HostIp != "" { hostIP, err := netip.ParseAddr(mapping.HostIp) if err != nil { return nil, errors.Wrapf(err, "failed to parse hostIP %v", hostIP) } if hostIP.Is6() && isIPv6Enabled { flag = hnsv2.NatFlagsIPv6 } if hostIP.Is6() && !isIPv6Enabled { logger.Info("Do not use ipv6 hostIP to create windows pod on ipv4 cluster") } } hnsPortMappingPolicy, err := createPortMappingPolicy(mapping.HostPort, mapping.ContainerPort, mapping.HostIp, protocol, flag) if err != nil { return nil, err } logger.Info("Creating port mapping policy", zap.Any("policy", hnsPortMappingPolicy)) policies = append(policies, *hnsPortMappingPolicy) // if no host ip specified and ipv6 enabled, we also create an identical ipv6 policy in addition to the previous ipv4 policy if mapping.HostIp == "" && isIPv6Enabled { ipv6HnsPortMappingPolicy, err := createPortMappingPolicy(mapping.HostPort, mapping.ContainerPort, mapping.HostIp, protocol, hnsv2.NatFlagsIPv6) if err != nil { return nil, err } logger.Info("Creating ipv6 port mapping policy", zap.Any("policy", ipv6HnsPortMappingPolicy)) policies = append(policies, *ipv6HnsPortMappingPolicy) } } return policies, nil } func createPortMappingPolicy(hostPort, containerPort int, hostIP string, protocol uint32, flags hnsv2.NatFlags) (*policy.Policy, error) { rawPolicy, err := json.Marshal(&hnsv2.PortMappingPolicySetting{ ExternalPort: uint16(hostPort), InternalPort: uint16(containerPort), VIP: hostIP, Protocol: protocol, Flags: flags, }) if err != nil { return nil, errors.Wrap(err, "failed to marshal HNS portMappingPolicySetting") } hnsv2Policy, err := json.Marshal(&hnsv2.EndpointPolicy{ Type: hnsv2.PortMapping, Settings: rawPolicy, }) if err != nil { return nil, errors.Wrap(err, "failed to marshal HNS endpointPolicy") } hnsPolicy := policy.Policy{ Type: policy.EndpointPolicy, Data: hnsv2Policy, } return &hnsPolicy, nil } func getEndpointPolicies(args PolicyArgs) ([]policy.Policy, error) { var policies []policy.Policy if args.nwCfg.IPV6Mode == network.IPV6Nat { ipv6Policy, err := getIPV6EndpointPolicy(args.subnetInfos) if err != nil { return nil, errors.Wrap(err, "failed to get ipv6 endpoint policy") } policies = append(policies, ipv6Policy) } if args.nwCfg.WindowsSettings.EnableLoopbackDSR { dsrPolicies, err := getLoopbackDSRPolicy(args) if err != nil { return nil, errors.Wrap(err, "failed to get loopback dsr policy") } policies = append(policies, dsrPolicies...) } return policies, nil } func getLoopbackDSRPolicy(args PolicyArgs) ([]policy.Policy, error) { var policies []policy.Policy for _, config := range args.ipconfigs { // consider DSR policy only for ipv4 address. Add for ipv6 when required if config.Address.IP.To4() != nil { dsrData := policy.LoopbackDSR{ Type: policy.LoopbackDSRPolicy, IPAddress: config.Address.IP, } dsrDataBytes, err := json.Marshal(dsrData) if err != nil { return nil, errors.Wrap(err, "failed to marshal dsr data") } dsrPolicy := policy.Policy{ Type: policy.EndpointPolicy, Data: dsrDataBytes, } policies = append(policies, dsrPolicy) } } return policies, nil } func getIPV6EndpointPolicy(subnetInfos []network.SubnetInfo) (policy.Policy, error) { var eppolicy policy.Policy if len(subnetInfos) < dualStackCount { return eppolicy, fmt.Errorf("network state doesn't have ipv6 subnet") } // Everything should be snat'd except podcidr exceptionList := []string{subnetInfos[1].Prefix.String()} rawPolicy, _ := json.Marshal(&hcsshim.OutboundNatPolicy{ Policy: hcsshim.Policy{Type: hcsshim.OutboundNat}, Exceptions: exceptionList, }) eppolicy = policy.Policy{ Type: policy.EndpointPolicy, Data: rawPolicy, } logger.Info("ipv6 outboundnat policy", zap.Any("policy", eppolicy)) return eppolicy, nil } func getCustomDNS(nwCfg *cni.NetworkConfig) network.DNSInfo { var search string if len(nwCfg.RuntimeConfig.DNS.Searches) > 0 { search = strings.Join(nwCfg.RuntimeConfig.DNS.Searches, ",") } return network.DNSInfo{ Servers: nwCfg.RuntimeConfig.DNS.Servers, Suffix: search, Options: nwCfg.RuntimeConfig.DNS.Options, } } func determineWinVer() { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) if err == nil { defer k.Close() cb, _, err := k.GetStringValue("CurrentBuild") if err == nil { winVer, err := strconv.Atoi(cb) if err == nil { policy.ValidWinVerForDnsNat = winVer >= win1903Version } } } if err != nil { logger.Error(err.Error()) } } func getNATInfo(nwCfg *cni.NetworkConfig, ncPrimaryIPIface interface{}, enableSnatForDNS bool) (natInfo []policy.NATInfo) { // TODO: Remove v4overlay and dualstackoverlay options, after 'overlay' rolls out in AKS-RP if nwCfg.ExecutionMode == string(util.V4Swift) && nwCfg.IPAM.Mode != string(util.V4Overlay) && nwCfg.IPAM.Mode != string(util.DualStackOverlay) && nwCfg.IPAM.Mode != string(util.Overlay) { ncPrimaryIP := "" if ncPrimaryIPIface != nil { ncPrimaryIP = ncPrimaryIPIface.(string) } natInfo = append(natInfo, []policy.NATInfo{{VirtualIP: ncPrimaryIP, Destinations: []string{networkutils.AzureDNS}}, {Destinations: []string{networkutils.AzureIMDS}}}...) } else if nwCfg.MultiTenancy && enableSnatForDNS { natInfo = append(natInfo, policy.NATInfo{Destinations: []string{networkutils.AzureDNS}}) } return natInfo } func platformInit(cniConfig *cni.NetworkConfig) { if cniConfig.WindowsSettings.HnsTimeoutDurationInSeconds > 0 { logger.Info("Enabling timeout for Hns calls", zap.Int("timeout", cniConfig.WindowsSettings.HnsTimeoutDurationInSeconds)) network.EnableHnsV1Timeout(cniConfig.WindowsSettings.HnsTimeoutDurationInSeconds) network.EnableHnsV2Timeout(cniConfig.WindowsSettings.HnsTimeoutDurationInSeconds) } } // isDualNicFeatureSupported returns if the dual nic feature is supported. Currently it's only supported for windows hnsv2 path func (plugin *NetPlugin) isDualNicFeatureSupported(netNs string) bool { useHnsV2, err := network.UseHnsV2(netNs) if useHnsV2 && err == nil { return true } logger.Error("DualNicFeature is not supported") return false } func getOverlayGateway(podsubnet *net.IPNet) (net.IP, error) { logger.Warn("No gateway specified for Overlay NC. CNI will choose one, but connectivity may break") ncgw := podsubnet.IP ncgw[3]++ ncgw = net.ParseIP(ncgw.String()) if ncgw == nil || !podsubnet.Contains(ncgw) { return nil, errors.Wrap(errInvalidArgs, "%w: Failed to retrieve overlay gateway from podsubnet"+podsubnet.IP.String()) } return ncgw, nil }