diff --git a/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml b/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml index 307048661e3..2d5a006ef2f 100644 --- a/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml +++ b/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml @@ -212,6 +212,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -224,6 +225,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -425,6 +434,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -437,6 +447,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index e4a1b3c394f..fa4d2beda95 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -963,6 +963,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -975,6 +976,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -1176,6 +1185,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1188,6 +1198,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index 80487df0538..883f3f96805 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -963,6 +963,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -975,6 +976,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -1176,6 +1185,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1188,6 +1198,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index a0ed8f18816..ab67ce8394b 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -963,6 +963,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -975,6 +976,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -1176,6 +1185,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1188,6 +1198,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index d029f1a8e2f..3f306af885a 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -976,6 +976,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -988,6 +989,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -1189,6 +1198,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1201,6 +1211,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 1ceb4e17493..8a16112abd2 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -963,6 +963,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -975,6 +976,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr from: type: array items: @@ -1176,6 +1185,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1188,6 +1198,14 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: string + groupAddress: + type: string + format: cidr to: type: array items: diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index f99a82a5fb3..1152fc1de3a 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -124,6 +124,7 @@ func run(o *Options) error { ovsDatapathType := ovsconfig.OVSDatapathType(o.config.OVSDatapathType) ovsBridgeClient := ovsconfig.NewOVSBridge(o.config.OVSBridge, ovsDatapathType, ovsdbConnection) ovsBridgeMgmtAddr := ofconfig.GetMgmtAddress(o.config.OVSRunDir, o.config.OVSBridge) + multicastEnabled := features.DefaultFeatureGate.Enabled(features.Multicast) ofClient := openflow.NewClient(o.config.OVSBridge, ovsBridgeMgmtAddr, features.DefaultFeatureGate.Enabled(features.AntreaProxy), features.DefaultFeatureGate.Enabled(features.AntreaPolicy), @@ -131,7 +132,7 @@ func run(o *Options) error { features.DefaultFeatureGate.Enabled(features.FlowExporter), o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, - features.DefaultFeatureGate.Enabled(features.Multicast)) + multicastEnabled) _, serviceCIDRNet, _ := net.ParseCIDR(o.config.ServiceCIDR) var serviceCIDRNetv6 *net.IPNet @@ -165,7 +166,7 @@ func run(o *Options) error { egressConfig := &config.EgressConfig{ ExceptCIDRs: exceptCIDRs, } - routeClient, err := route.NewClient(networkConfig, o.config.NoSNAT, o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, features.DefaultFeatureGate.Enabled(features.Multicast)) + routeClient, err := route.NewClient(networkConfig, o.config.NoSNAT, o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, multicastEnabled) if err != nil { return fmt.Errorf("error creating route client: %v", err) } @@ -290,11 +291,13 @@ func run(o *Options) error { antreaPolicyEnabled, antreaProxyEnabled, statusManagerEnabled, + multicastEnabled, loggingEnabled, asyncRuleDeleteInterval, o.config.DNSServerOverride, v4Enabled, - v6Enabled) + v6Enabled, + ) if err != nil { return fmt.Errorf("error creating new NetworkPolicy controller: %v", err) } @@ -479,7 +482,6 @@ func run(o *Options) error { go nodeRouteController.Run(stopCh) go networkPolicyController.Run(stopCh) - // Initialize the NPL agent. if enableNodePortLocal { nplController, err := npl.InitializeNPLAgent( @@ -505,6 +507,10 @@ func run(o *Options) error { } go ipamController.Run(stopCh) } + // Start the localPodInformer + if localPodInformer != nil { + go localPodInformer.Run(stopCh) + } if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) { // Create the NetworkAttachmentDefinition client, which handles access to secondary network object definition from the API Server. @@ -570,11 +576,12 @@ func run(o *Options) error { } } - if features.DefaultFeatureGate.Enabled(features.Multicast) { + if multicastEnabled { multicastSocket, err := multicast.CreateMulticastSocket() if err != nil { return fmt.Errorf("failed to create multicast socket") } + mcastValidator := networkPolicyController.GetMcastValidator() mcastController := multicast.NewMulticastController( ofClient, v4GroupIDAllocator, @@ -583,7 +590,9 @@ func run(o *Options) error { multicastSocket, sets.NewString(append(o.config.MulticastInterfaces, nodeConfig.NodeTransportInterfaceName)...), ovsBridgeClient, - podUpdateChannel) + podUpdateChannel, + mcastValidator, + features.DefaultFeatureGate.Enabled(features.AntreaPolicy)) if err := mcastController.Initialize(); err != nil { return err } diff --git a/pkg/agent/controller/networkpolicy/multicast.go b/pkg/agent/controller/networkpolicy/multicast.go new file mode 100644 index 00000000000..868c12327c8 --- /dev/null +++ b/pkg/agent/controller/networkpolicy/multicast.go @@ -0,0 +1,494 @@ +package networkpolicy + +import ( + "antrea.io/antrea/pkg/agent/util" + "antrea.io/antrea/pkg/util/channel" + "antrea.io/antrea/pkg/util/k8s" + "fmt" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/util/workqueue" + "math" + "net" + "sync" + "time" + + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/agent/interfacestore" + "antrea.io/antrea/pkg/agent/openflow" + crdv1beta "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" + binding "antrea.io/antrea/pkg/ovs/openflow" +) + +type ruleType int +type eventType uint8 +const ( + unicast ruleType = 0 + igmp ruleType = 1 + multicast ruleType = 2 + + queryInterval = time.Second * 125 + // mcastGroupTimeout is the timeout to detect a group as stale if no IGMP report is received within the time. + mcastGroupTimeout = queryInterval * 3 + resyncPeriod = 0 * time.Minute + + groupJoin eventType = iota + groupLeave + rulechanged +) + +var ( + mcastAllHosts = net.ParseIP("224.0.0.1").To4() + _, mcastAllHostsCIDR, _ = net.ParseCIDR("224.0.0.1/32") +) + +type mcastItem struct { + groupAddress net.IPNet + ruleIDs map[string]*uint16 +} + +type GroupMemberStatus struct { + group net.IP + // localMembers is a map for the local Pod member and its last update time, key is the Pod's interface name, + // and value is its last update time. + localMembers map[string]time.Time + lastIGMPReport time.Time + mutex sync.RWMutex + ofGroupID binding.GroupIDType +} + +type mcastGroupEvent struct { + eType eventType + iface *interfacestore.InterfaceConfig +} + +type multicastController struct { + ofClient openflow.Client + + ifaceStore interfacestore.InterfaceStore + + queryGroupId binding.GroupIDType + groupInstalled bool + queryGroupStatus GroupMemberStatus + podMap map[string]*corev1.Pod + podMapMutex sync.RWMutex + + ruleCache *ruleCache + mcastItemRuleIDMap map[string]mcastItem + mcastItemMutex sync.RWMutex + ruleIDGroupAddressMap map[string]sets.String + ruleIDGroupMapMutex sync.RWMutex + eventCh chan mcastGroupEvent + queue workqueue.RateLimitingInterface +} + +func (c *multicastController) syncQueryGroup(stopCh <- chan struct{}) { + for { + select { + case event := <- c.eventCh: + klog.Info("resync query group") + if c.queryGroupId == 0 { + klog.Infof("c.queryGroupId == 0 %v", c.queryGroupId == 0) + return + } + now := time.Now() + if event.eType == groupJoin { + if event.iface != nil { + c.queryGroupStatus.mutex.Lock() + c.queryGroupStatus.localMembers[event.iface.InterfaceName] = now + c.queryGroupStatus.mutex.Unlock() + c.queryGroupStatus.lastIGMPReport = now + if len(c.queryGroupStatus.localMembers) > 0 { + err := c.updateGroup() + if err != nil { + klog.Errorf("failed to update query group: %+v", err) + } + } else { + klog.Infof("no member in query group: %+v", c.queryGroupStatus.localMembers) + } + } + } else if event.eType == groupLeave { + c.queryGroupStatus.mutex.Lock() + delete(c.queryGroupStatus.localMembers, event.iface.InterfaceName) + c.queryGroupStatus.mutex.Unlock() + c.queryGroupStatus.lastIGMPReport = now + if len(c.queryGroupStatus.localMembers) > 0 { + err := c.updateGroup() + if err != nil { + klog.Errorf("failed to update query group: %+v", err) + } + } else { + klog.Infof("no member in query group: %+v", c.queryGroupStatus.localMembers) + } + } else if event.eType == rulechanged { + if len(c.queryGroupStatus.localMembers) > 0 { + err := c.updateGroup() + if err != nil { + klog.Errorf("failed to update query group: %+v", err) + } + } else { + klog.Infof("no member in query group: %+v", c.queryGroupStatus.localMembers) + } + } + case <-stopCh: + return + } + } +} + +func (c *multicastController) groupIsStale() bool { + return false +} + +func (c *multicastController) groupHasInstalled() bool { + return c.groupInstalled +} + +func (c *multicastController) updateGroup() error { + groupKey := mcastAllHosts.String() + c.queryGroupStatus.mutex.Lock() + defer c.queryGroupStatus.mutex.Unlock() + memberPorts := make([]uint32, 0) + blocked_ports := make(map[uint32]bool) + for memberInterfaceName := range c.queryGroupStatus.localMembers { + obj, found := c.ifaceStore.GetInterfaceByName(memberInterfaceName) + if !found { + klog.InfoS("Failed to find interface from cache", "interface", memberInterfaceName) + continue + } + action, _, name, _ := c.validation(obj, *mcastAllHostsCIDR, crdv1beta.DirectionIn) + if name != "" && (*action == v1alpha1.RuleActionDrop) { + klog.V(4).Infof("policy will block ofport: %d, pod: %s/%s", obj.OFPort, obj.PodNamespace, obj.PodName) + blocked_ports[uint32(obj.OFPort)] = true + } + memberPorts = append(memberPorts, uint32(obj.OFPort)) + } + if c.groupHasInstalled() { + if c.groupIsStale() { + // Remove the multicast flow entry if no local Pod is in the group. + if err := c.ofClient.UninstallMulticastFlows(c.queryGroupStatus.group); err != nil { + klog.ErrorS(err, "Failed to uninstall multicast flows", "group", groupKey) + return err + } + // Remove the multicast flow entry if no local Pod is in the group. + if err := c.ofClient.UninstallGroup(c.queryGroupStatus.ofGroupID); err != nil { + klog.ErrorS(err, "Failed to uninstall multicast group", "group", groupKey) + return err + } + + c.groupInstalled = false + klog.InfoS("Removed multicast group from cache after all members left", "group", groupKey) + return nil + } + // Reinstall OpenFlow group because the local pod receivers have changed. + if err := c.ofClient.InstallMulticastGroup(c.queryGroupStatus.ofGroupID, blocked_ports, true, memberPorts); err != nil { + return err + } + klog.V(2).InfoS("Updated OpenFlow group for local receivers", "group", groupKey, "ofGroup", c.queryGroupStatus.ofGroupID, "localReceivers", memberPorts) + return nil + } + // Install OpenFlow group for a new multicast group which has local Pod receivers joined. + if err := c.ofClient.InstallMulticastGroup(c.queryGroupId, blocked_ports, true, memberPorts); err != nil { + return err + } + klog.V(2).InfoS("Installed OpenFlow group for local receivers", "group", groupKey, "ofGroup", c.queryGroupStatus.ofGroupID, "localReceivers", memberPorts) + // Install OpenFlow flow to forward packets to local Pod receivers which are included in the group. + if err := c.ofClient.InstallMulticastFlows(c.queryGroupStatus.group, c.queryGroupStatus.ofGroupID); err != nil { + klog.ErrorS(err, "Failed to install multicast flows", "group", c.queryGroupStatus.group) + return err + } + if err := c.ofClient.InstallMulticastIGMPQueryFlow(); err != nil { + klog.ErrorS(err, "Failed to install igmp query flows", "group", c.queryGroupStatus.group) + return err + } + c.groupInstalled = true + return nil +} + +func (c *multicastController) initialize(groupID binding.GroupIDType) bool { + c.queryGroupId = groupID + c.queryGroupStatus = GroupMemberStatus{ + group: mcastAllHosts, + localMembers: make(map[string]time.Time), + lastIGMPReport: time.Now(), + ofGroupID: c.queryGroupId, + } + now := time.Now() + ifaces := c.ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface) + for _, iface := range ifaces { + c.queryGroupStatus.localMembers[iface.InterfaceName] = now + } + c.queryGroupStatus.lastIGMPReport = now + if len(c.queryGroupStatus.localMembers) > 0 { + err := c.updateGroup() + if err != nil { + klog.Errorf("failed to update query group: %+v", err) + } + } else { + klog.Infof("no member in query group: %+v", c.queryGroupStatus.localMembers) + } + return true +} + +func (c *multicastController) addGroupAddressForTableIDs (ruleID string, priority *uint16, mcastGroupAddresses []string) { + c.mcastItemMutex.Lock() + defer c.mcastItemMutex.Unlock() + c.ruleIDGroupMapMutex.Lock() + defer c.ruleIDGroupMapMutex.Unlock() + mcastGroupAddressSet := sets.String{} + for _, mcastGroupAddress := range mcastGroupAddresses { + item, exists := c.mcastItemRuleIDMap[mcastGroupAddress] + ip, cidr, err := net.ParseCIDR(mcastGroupAddress) + if err != nil { + ip = net.ParseIP(mcastGroupAddress) + cidr = &net.IPNet{} + cidr.IP = ip + cidr.Mask = net.CIDRMask(32, 32) + } + mcastGroupAddressSet.Insert(mcastGroupAddress) + if !exists { + item := mcastItem{ + groupAddress: *cidr, + ruleIDs: make(map[string]*uint16), + } + item.ruleIDs[ruleID] = priority + c.mcastItemRuleIDMap[mcastGroupAddress] = item + } else { + item.ruleIDs[ruleID] = priority + c.mcastItemRuleIDMap[mcastGroupAddress] = item + } + } + if mcastGroupAddressSet.Len() > 0 { + c.ruleIDGroupAddressMap[ruleID] = mcastGroupAddressSet + } + g := mcastGroupEvent{ + eType: rulechanged, + } + c.eventCh <- g +} + +func (c *multicastController) updateGroupAddressForTableIDs (ruleID string, priority *uint16, mcastGroupAddresses []string) { + c.mcastItemMutex.Lock() + defer c.mcastItemMutex.Unlock() + + c.ruleIDGroupMapMutex.Lock() + defer c.ruleIDGroupMapMutex.Unlock() + + staleMcastGroupAddresses, ok := c.ruleIDGroupAddressMap[ruleID] + for _, mcastGroupAddress := range mcastGroupAddresses { + item, exists := c.mcastItemRuleIDMap[mcastGroupAddress] + ip, cidr, err := net.ParseCIDR(mcastGroupAddress) + if err != nil { + ip = net.ParseIP(mcastGroupAddress) + cidr = &net.IPNet{} + cidr.IP = ip + cidr.Mask = net.CIDRMask(32, 32) + } + if !exists { + item := mcastItem{ + groupAddress: *cidr, + ruleIDs: make(map[string]*uint16), + } + item.ruleIDs[ruleID] = priority + c.mcastItemRuleIDMap[mcastGroupAddress] = item + } else { + item.ruleIDs[ruleID] = priority + c.mcastItemRuleIDMap[mcastGroupAddress] = item + } + } + if !ok { + staleMcastGroupAddresses = sets.String{} + } + for staleGroupAddress := range staleMcastGroupAddresses { + if item, ok := c.mcastItemRuleIDMap[staleGroupAddress]; ok { + if _, ok = item.ruleIDs[ruleID]; ok { + delete(item.ruleIDs, ruleID) + if len(item.ruleIDs) > 0 { + c.mcastItemRuleIDMap[staleGroupAddress] = item + } else { + c.cleanupGroupAddressForTableIDsUnlocked(staleGroupAddress) + } + } + } + } + + newMcastGroupAddresses := sets.String{} + for _, mcastGroupAddress := range mcastGroupAddresses { + newMcastGroupAddresses.Insert(mcastGroupAddress) + } + c.ruleIDGroupAddressMap[ruleID] = newMcastGroupAddresses + g := mcastGroupEvent{ + eType: rulechanged, + } + c.eventCh <- g +} + +func (c *multicastController) deleteGroupAddressForTableIDs (ruleID string, groupAddresses []string) { + c.mcastItemMutex.Lock() + defer c.mcastItemMutex.Unlock() + c.ruleIDGroupMapMutex.Lock() + defer c.ruleIDGroupMapMutex.Unlock() + for _, groupAddress := range groupAddresses { + item, exists := c.mcastItemRuleIDMap[groupAddress] + klog.Infof("deleteGroupAddressForTableIDs groupAddress: %v, exist %v, map %+v %+v", + groupAddress, exists, c.mcastItemRuleIDMap, item) + if _, ok := item.ruleIDs[ruleID]; exists && ok { + delete(item.ruleIDs, ruleID) + if len(item.ruleIDs) > 0 { + c.mcastItemRuleIDMap[groupAddress] = item + } else { + c.cleanupGroupAddressForTableIDsUnlocked(groupAddress) + } + } + klog.Infof("after deleteGroupAddressForTableIDs groupAddress: %v, exist %v, map %+v %+v", + groupAddress, exists, c.mcastItemRuleIDMap, item) + } + + delete(c.ruleIDGroupAddressMap, ruleID) + g := mcastGroupEvent{ + eType: rulechanged, + } + c.eventCh <- g +} + +// cleanupFQDNSelectorItem handles a fqdnSelectorItem delete event. +func (c *multicastController) cleanupGroupAddressForTableIDsUnlocked (groupAddress string) { + _, exists := c.mcastItemRuleIDMap[groupAddress] + if exists { + delete(c.mcastItemRuleIDMap, groupAddress) + } +} + +func (c *multicastController) validation(iface *interfacestore.InterfaceConfig, + groupAddress net.IPNet, direction crdv1beta.Direction) (*v1alpha1.RuleAction, apitypes.UID, string, error) { + action, uuid, ruleName := v1alpha1.RuleActionDrop, apitypes.UID("0"), "" + if iface == nil { + //check the whole group + klog.Info("Iface should not be empty") + return nil, apitypes.UID(""), "", fmt.Errorf("iface should not be empty") + } + ns, podname := iface.PodNamespace, iface.PodName + c.mcastItemMutex.Lock() + defer c.mcastItemMutex.Unlock() + item, exists := c.mcastItemRuleIDMap[groupAddress.String()] + if !exists { + item, exists = c.mcastItemRuleIDMap[groupAddress.IP.String()] + if !exists { + klog.Infof("rule for group %s does not exist: %+v", groupAddress.String(), c.mcastItemRuleIDMap) + action = v1alpha1.RuleActionAllow + return &action, apitypes.UID(""), "", nil + } + } + var matchedRule *CompletedRule + for ruleID := range item.ruleIDs { + rule, _, _ := c.ruleCache.GetCompletedRule(ruleID) + member := &crdv1beta.GroupMember{ + Pod: &crdv1beta.PodReference { + Name: podname, + Namespace: ns, + }, + } + + if (matchedRule == nil) || *(item.ruleIDs[ruleID]) > *(item.ruleIDs[matchedRule.ID]) { + if rule.Direction == crdv1beta.DirectionIn && direction == rule.Direction{ + if rule.TargetMembers.Has(member) == true { + matchedRule = rule + } + } else if rule.Direction == crdv1beta.DirectionOut && direction == rule.Direction{ + if rule.TargetMembers.Has(member) == true { + matchedRule = rule + } + } + } + } + if matchedRule != nil { + action, uuid, ruleName = *matchedRule.Action, matchedRule.PolicyUID, matchedRule.Name + } else { + action, uuid, ruleName = v1alpha1.RuleActionAllow, apitypes.UID(""), "" + } + klog.V(4).Infof("validation: action %v, uuid %v, ruleName %v, found %v", + action, uuid, ruleName) + return &action, uuid, ruleName, nil +} + +func (c *multicastController) run(stopCh <- chan struct{}) { + go c.syncQueryGroup(stopCh) +} + +func (c *multicastController) memberChanged(pod string, evType channel.EventType, options ...string) { + namespace, name := k8s.SplitNamespacedName(pod) + containerID := options[0] + interfaceName := util.GenerateContainerInterfaceName(name, namespace, containerID) + iface, ok := c.ifaceStore.GetInterfaceByName(interfaceName) + klog.Infof("memberChanged: %+v", evType) + switch evType { + case channel.EventAdd: + if ok { + g := mcastGroupEvent{ + eType: groupJoin, + iface: iface, + } + c.eventCh <- g + } + + case channel.EventDel: + if ok { + g := mcastGroupEvent{ + eType: groupLeave, + iface: iface, + } + c.eventCh <- g + } + default: + } +} + +func newMulticastController(ofClient openflow.Client, + ifaceStore interfacestore.InterfaceStore, + ruleCache *ruleCache, + podUpdateSubscriber channel.Subscriber, + ) (*multicastController, error) { + mcastController := &multicastController{ + ofClient: ofClient, + ifaceStore: ifaceStore, + groupInstalled: false, + podMap: make(map[string]*corev1.Pod), + ruleCache: ruleCache, + mcastItemRuleIDMap: make(map[string]mcastItem), + ruleIDGroupAddressMap: make(map[string]sets.String), + queryGroupId: math.MaxUint32, + eventCh: make(chan mcastGroupEvent), + } + klog.Infof("podUpdateSubscriber.Subscribe(mcastController.memberChanged)") + podUpdateSubscriber.Subscribe(mcastController.memberChanged) + return mcastController, nil +} + +type McastValidator struct { + mcastController *multicastController +} + +func newMcastValidator(c *multicastController) (*McastValidator, error) { + if c == nil { + return nil, fmt.Errorf("multicastController should not be nil") + } + m := &McastValidator{ + mcastController: c, + } + return m, nil +} + +func (m *McastValidator) Initialize (groupID binding.GroupIDType) bool { + return m.mcastController.initialize(groupID) +} + +func (m *McastValidator) Validation (iface *interfacestore.InterfaceConfig, groupAddress net.IP) (*v1alpha1.RuleAction, apitypes.UID, string, error) { + groupAddressCidr := net.IPNet{ + IP: groupAddress, + Mask: net.CIDRMask(32,32), + } + return m.mcastController.validation(iface, groupAddressCidr, crdv1beta.DirectionOut) +} \ No newline at end of file diff --git a/pkg/agent/controller/networkpolicy/multicast_test.go b/pkg/agent/controller/networkpolicy/multicast_test.go new file mode 100644 index 00000000000..2146642bd78 --- /dev/null +++ b/pkg/agent/controller/networkpolicy/multicast_test.go @@ -0,0 +1,27 @@ +package networkpolicy + +import ( + "testing" + + "github.com/golang/mock/gomock" +) + +func newMockMcastController(t *testing.T, controller *gomock.Controller, dnsServer *string) { + +} + +func TestAddGroupAddressForTableIDs(t *testing.T) { + +} + +func TestDeleteGroupAddressForTableIDs(t *testing.T) { + +} + +func TestValidation(t *testing.T) { + +} + +func TestSyncQueryGroup(t *testing.T) { + +} \ No newline at end of file diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go index c0c06ccfd70..e95c114b4ee 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go @@ -75,6 +75,8 @@ type Controller struct { antreaProxyEnabled bool // statusManagerEnabled indicates whether a statusManager is configured. statusManagerEnabled bool + // multicastEnabled indicates whether multicast is enabled. + multicastEnabled bool // loggingEnabled indicates where Antrea policy audit logging is enabled. loggingEnabled bool // antreaClientProvider provides interfaces to get antreaClient, which can be @@ -106,6 +108,8 @@ type Controller struct { ifaceStore interfacestore.InterfaceStore // denyConnStore is for storing deny connections for flow exporter. denyConnStore *connections.DenyConnectionStore + mcastController *multicastController + mcastValidator *McastValidator } // NewNetworkPolicyController returns a new *Controller. @@ -119,6 +123,7 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, antreaPolicyEnabled bool, antreaProxyEnabled bool, statusManagerEnabled bool, + multicastEnabled bool, loggingEnabled bool, asyncRuleDeleteInterval time.Duration, dnsServerOverride string, @@ -132,19 +137,27 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, antreaPolicyEnabled: antreaPolicyEnabled, antreaProxyEnabled: antreaProxyEnabled, statusManagerEnabled: statusManagerEnabled, + multicastEnabled: multicastEnabled, loggingEnabled: loggingEnabled, } + c.ruleCache = newRuleCache(c.enqueueRule, podUpdateSubscriber, groupIDUpdates) if antreaPolicyEnabled { var err error if c.fqdnController, err = newFQDNController(ofClient, idAllocator, dnsServerOverride, c.enqueueRule, v4Enabled, v6Enabled); err != nil { return nil, err } + if c.mcastController, err = newMulticastController(ofClient, ifaceStore, c.ruleCache, podUpdateSubscriber); err != nil { + return nil, err + } + if c.mcastValidator, err = newMcastValidator(c.mcastController); err != nil { + return nil, err + } if c.ofClient != nil { c.ofClient.RegisterPacketInHandler(uint8(openflow.PacketInReasonNP), "dnsresponse", c.fqdnController) } } - c.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters, v4Enabled, v6Enabled, antreaPolicyEnabled) - c.ruleCache = newRuleCache(c.enqueueRule, podUpdateSubscriber, groupIDUpdates) + c.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, c.mcastController, groupCounters, + v4Enabled, v6Enabled, antreaPolicyEnabled, multicastEnabled) if statusManagerEnabled { c.statusManager = newStatusController(antreaClientGetter, nodeName, c.ruleCache) } @@ -451,6 +464,9 @@ func (c *Controller) Run(stopCh <-chan struct{}) { go wait.Until(c.fqdnController.worker, time.Second, stopCh) } go c.fqdnController.runRuleSyncTracker(stopCh) + if c.multicastEnabled { + go c.mcastController.run(stopCh) + } } klog.Infof("Waiting for all watchers to complete full sync") c.fullSyncGroup.Wait() @@ -560,6 +576,10 @@ func (c *Controller) syncRule(key string) error { return nil } +func (c *Controller) GetMcastValidator() *McastValidator { + return c.mcastValidator +} + // syncRules calls the reconciler to sync all the rules after watchers complete full sync. // After flows for those init events are installed, subsequent rules will be handled asynchronously // by the syncRule() function. diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go index b7f54207c57..437a98ea5c9 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go @@ -59,7 +59,7 @@ func newTestController() (*Controller, *fake.Clientset, *mockReconciler) { groupIDAllocator := openflow.NewGroupAllocator(false) groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(groupIDAllocator, ch2)} controller, _ := NewNetworkPolicyController(&antreaClientGetter{clientset}, nil, nil, "node1", podUpdateChannel, groupCounters, ch2, - true, true, true, true, testAsyncDeleteInterval, "8.8.8.8:53", true, false) + true, true, true, true, true, testAsyncDeleteInterval, "8.8.8.8:53", true, false, ) reconciler := newMockReconciler() controller.reconciler = reconciler controller.antreaPolicyLogger = nil diff --git a/pkg/agent/controller/networkpolicy/reconciler.go b/pkg/agent/controller/networkpolicy/reconciler.go index 16cbf78484b..493f7297d8b 100644 --- a/pkg/agent/controller/networkpolicy/reconciler.go +++ b/pkg/agent/controller/networkpolicy/reconciler.go @@ -15,6 +15,7 @@ package networkpolicy import ( + crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1" "encoding/binary" "fmt" "net" @@ -156,6 +157,8 @@ type lastRealized struct { // the toServices of this policy rule. It must be empty for policy rule // that is not egress and does not have toServices field. groupIDAddresses sets.Int64 + //groupAddresses track the latest realized set of multicast groups for the multicast traffic + groupAddresses sets.String } func newLastRealized(rule *CompletedRule) *lastRealized { @@ -166,6 +169,7 @@ func newLastRealized(rule *CompletedRule) *lastRealized { podIPs: nil, fqdnIPAddresses: nil, groupIDAddresses: nil, + groupAddresses: nil, } } @@ -209,6 +213,12 @@ type reconciler struct { // groupCounters is a list of GroupCounter for v4 and v6 env. reconciler uses these // GroupCounters to get the groupIDs of a specific Service. groupCounters []proxytypes.GroupCounter + + // multicast controller manages multicast cache for multicast rule. + mcastController *multicastController + + // multicastEnabled indicates whether multicast is enabled + multicastEnabled bool } // newReconciler returns a new *reconciler. @@ -216,10 +226,12 @@ func newReconciler(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, idAllocator *idAllocator, fqdnController *fqdnController, + mcastController *multicastController, groupCounters []proxytypes.GroupCounter, v4Enabled bool, v6Enabled bool, antreaPolicyEnabled bool, + multicastEnabled bool, ) *reconciler { priorityAssigners := map[uint8]*tablePriorityAssigner{} if antreaPolicyEnabled { @@ -233,6 +245,18 @@ func newReconciler(ofClient openflow.Client, assigner: newPriorityAssigner(false), } } + if multicastEnabled { + for _, table := range openflow.GetAntreaMulticastEgressTable() { + priorityAssigners[table.GetID()] = &tablePriorityAssigner{ + assigner: newPriorityAssigner(false), + } + } + for _, table := range openflow.GetAntreaIGMPTables() { + priorityAssigners[table.GetID()] = &tablePriorityAssigner{ + assigner: newPriorityAssigner(false), + } + } + } } reconciler := &reconciler{ ofClient: ofClient, @@ -241,7 +265,9 @@ func newReconciler(ofClient openflow.Client, idAllocator: idAllocator, priorityAssigners: priorityAssigners, fqdnController: fqdnController, + mcastController: mcastController, groupCounters: groupCounters, + multicastEnabled: multicastEnabled, } // Check if ofClient is nil or not to be compatible with unit tests. if ofClient != nil { @@ -265,7 +291,7 @@ func (r *reconciler) Reconcile(rule *CompletedRule) error { var ofPriority *uint16 value, exists := r.lastRealizeds.Load(rule.ID) - ruleTable := r.getOFRuleTable(rule) + ruleTable, _ := r.getOFRuleTable(rule) priorityAssigner, _ := r.priorityAssigners[ruleTable] if rule.isAntreaNetworkPolicyRule() { // For CNP, only release priorityMutex after rule is installed on OVS. Otherwise, @@ -290,26 +316,74 @@ func (r *reconciler) Reconcile(rule *CompletedRule) error { return ofRuleInstallErr } +//0 is unicast traffic +//1 is igmp traffic +//2 is multicast traffic +func (r *reconciler) getRuleType (rule *CompletedRule) ruleType { + if !r.multicastEnabled { + return unicast + } + for _, service := range rule.Services { + if service.IGMPType != nil && ((*service.IGMPType == string(crdv1alpha1.IGMPQuery)) || (*service.IGMPType == string(crdv1alpha1.IGMPReport))) { + //this is IGMP rule + return igmp + } + } + + for _, ipBlock := range rule.To.IPBlocks { + ip := net.IP(ipBlock.CIDR.IP) + ipLen := net.IPv4len + if ip.To4() == nil { + ipLen = net.IPv6len + } + mask := net.CIDRMask(int(ipBlock.CIDR.PrefixLength), 8*ipLen) + maskedIP := ip.Mask(mask) + + if maskedIP.IsMulticast() { + klog.Infof("multicast") + return multicast + } + } + return unicast +} + // getOFRuleTable retreives the OpenFlow table to install the CompletedRule. // The decision is made based on whether the rule is created for a CNP/ANP, and // the Tier of that NetworkPolicy. -func (r *reconciler) getOFRuleTable(rule *CompletedRule) uint8 { - if !rule.isAntreaNetworkPolicyRule() { +func (r *reconciler) getOFRuleTable(rule *CompletedRule) (uint8, ruleType) { + //if it is normal traffic + rule_type := r.getRuleType(rule) + var ruleTables []*openflow.Table + klog.Infof("rule %+v, rule_type:%v", rule, rule_type) + var tableID uint8 + switch rule_type { + case unicast: + if !rule.isAntreaNetworkPolicyRule() { + if rule.Direction == v1beta2.DirectionIn { + return openflow.IngressRuleTable.GetID(), unicast + } + return openflow.EgressRuleTable.GetID(), unicast + } if rule.Direction == v1beta2.DirectionIn { - return openflow.IngressRuleTable.GetID() + ruleTables = openflow.GetAntreaPolicyIngressTables() + } else { + ruleTables = openflow.GetAntreaPolicyEgressTables() } - return openflow.EgressRuleTable.GetID() - } - var ruleTables []*openflow.Table - if rule.Direction == v1beta2.DirectionIn { - ruleTables = openflow.GetAntreaPolicyIngressTables() - } else { - ruleTables = openflow.GetAntreaPolicyEgressTables() - } - if *rule.TierPriority != baselineTierPriority { - return ruleTables[0].GetID() + if *rule.TierPriority != baselineTierPriority { + return ruleTables[0].GetID(), unicast + } + //if it is IGMP + //if it is normal multicast traffic + tableID = ruleTables[1].GetID() + case igmp: + ruleTables = openflow.GetAntreaIGMPTables() + tableID = ruleTables[0].GetID() + case multicast: + // multicast np only supports egress so far + ruleTables = openflow.GetAntreaMulticastEgressTable() + tableID = ruleTables[0].GetID() } - return ruleTables[1].GetID() + return tableID, rule_type } // getOFPriority retrieves the OFPriority for the input CompletedRule to be installed, @@ -370,7 +444,7 @@ func (r *reconciler) BatchReconcile(rules []*CompletedRule) error { return err } for _, rule := range rulesToInstall { - ruleTable := r.getOFRuleTable(rule) + ruleTable, _ := r.getOFRuleTable(rule) priorityAssigner := r.priorityAssigners[ruleTable] klog.V(2).Infof("Adding rule %s of NetworkPolicy %s to be reconciled in batch", rule.ID, rule.SourceRef.ToString()) ofPriority, _, _ := r.getOFPriority(rule, ruleTable, priorityAssigner) @@ -399,7 +473,7 @@ func (r *reconciler) registerOFPriorities(rules []*CompletedRule) error { prioritiesToRegister := map[uint8][]types.Priority{} for _, rule := range rules { if rule.isAntreaNetworkPolicyRule() { - ruleTable := r.getOFRuleTable(rule) + ruleTable, _ := r.getOFRuleTable(rule) p := types.Priority{ TierPriority: *rule.TierPriority, PolicyPriority: *rule.PolicyPriority, @@ -441,121 +515,126 @@ func (r *reconciler) add(rule *CompletedRule, ofPriority *uint16, table uint8) e func (r *reconciler) computeOFRulesForAdd(rule *CompletedRule, ofPriority *uint16, table uint8) ( map[servicesKey]*types.PolicyRule, *lastRealized) { + klog.Infof("computeOFRulesForAdd %+v %+v %+v", rule, rule.ID, rule.Services) lastRealized := newLastRealized(rule) // TODO: Handle the case that the following processing fails or partially succeeds. r.lastRealizeds.Store(rule.ID, lastRealized) ofRuleByServicesMap := map[servicesKey]*types.PolicyRule{} + groupAddress, igmp := r.getMcastGroupAddress(rule) + if !igmp { + if rule.Direction == v1beta2.DirectionIn { + // Addresses got from source GroupMembers' IPs. + from1 := groupMembersToOFAddresses(rule.FromAddresses) + // Get addresses that in From IPBlock but not in Except IPBlocks. + from2 := ipBlocksToOFAddresses(rule.From.IPBlocks, r.ipv4Enabled, r.ipv6Enabled) - if rule.Direction == v1beta2.DirectionIn { - // Addresses got from source GroupMembers' IPs. - from1 := groupMembersToOFAddresses(rule.FromAddresses) - // Get addresses that in From IPBlock but not in Except IPBlocks. - from2 := ipBlocksToOFAddresses(rule.From.IPBlocks, r.ipv4Enabled, r.ipv6Enabled) - - membersByServicesMap, servicesMap := groupMembersByServices(rule.Services, rule.TargetMembers) - for svcKey, members := range membersByServicesMap { - ofPorts := r.getOFPorts(members) - lastRealized.podOFPorts[svcKey] = ofPorts - ofRuleByServicesMap[svcKey] = &types.PolicyRule{ - Direction: v1beta2.DirectionIn, - From: append(from1, from2...), - To: ofPortsToOFAddresses(ofPorts), - Service: filterUnresolvablePort(servicesMap[svcKey]), - Action: rule.Action, - Name: rule.Name, - Priority: ofPriority, - TableID: table, - PolicyRef: rule.SourceRef, - EnableLogging: rule.EnableLogging, - } - } - } else { - if r.fqdnController != nil && len(rule.To.FQDNs) > 0 { - // TODO: addFQDNRule installs new conjunctive flows, so maybe it doesn't - // belong in computeOFRulesForAdd. The error handling needs to be corrected - // as well: if the flows failed to install, there should be a retry - // mechanism. - if err := r.fqdnController.addFQDNRule(rule.ID, rule.To.FQDNs, r.getOFPorts(rule.TargetMembers)); err != nil { - klog.ErrorS(err, "Error when adding FQDN rule", "ruleID", rule.ID) + membersByServicesMap, servicesMap := groupMembersByServices(rule.Services, rule.TargetMembers) + for svcKey, members := range membersByServicesMap { + ofPorts := r.getOFPorts(members) + lastRealized.podOFPorts[svcKey] = ofPorts + ofRuleByServicesMap[svcKey] = &types.PolicyRule{ + Direction: v1beta2.DirectionIn, + From: append(from1, from2...), + To: ofPortsToOFAddresses(ofPorts), + Service: filterUnresolvablePort(servicesMap[svcKey]), + Action: rule.Action, + Name: rule.Name, + Priority: ofPriority, + TableID: table, + PolicyRef: rule.SourceRef, + EnableLogging: rule.EnableLogging, + } } - } - ips := r.getIPs(rule.TargetMembers) - lastRealized.podIPs = ips - from := ipsToOFAddresses(ips) - memberByServicesMap, servicesMap := groupMembersByServices(rule.Services, rule.ToAddresses) - for svcKey, members := range memberByServicesMap { - ofRuleByServicesMap[svcKey] = &types.PolicyRule{ - Direction: v1beta2.DirectionOut, - From: from, - To: groupMembersToOFAddresses(members), - Service: filterUnresolvablePort(servicesMap[svcKey]), - Action: rule.Action, - Priority: ofPriority, - Name: rule.Name, - TableID: table, - PolicyRef: rule.SourceRef, - EnableLogging: rule.EnableLogging, + } else { + if r.fqdnController != nil && len(rule.To.FQDNs) > 0 { + // TODO: addFQDNRule installs new conjunctive flows, so maybe it doesn't + // belong in computeOFRulesForAdd. The error handling needs to be corrected + // as well: if the flows failed to install, there should be a retry + // mechanism. + if err := r.fqdnController.addFQDNRule(rule.ID, rule.To.FQDNs, r.getOFPorts(rule.TargetMembers)); err != nil { + klog.ErrorS(err, "Error when adding FQDN rule", "ruleID", rule.ID) + } } - } - - // If there are no "ToAddresses", the above process doesn't create any PolicyRule. - // We must ensure there is at least one PolicyRule, otherwise the Pods won't be - // isolated, so we create a PolicyRule with the original services if it doesn't exist. - // If there are IPBlocks or Pods that cannot resolve any named port, they will share - // this PolicyRule. Antrea policies do not need this default isolation. - if !rule.isAntreaNetworkPolicyRule() || len(rule.To.IPBlocks) > 0 || len(rule.To.FQDNs) > 0 || len(rule.To.ToServices) > 0 { - svcKey := normalizeServices(rule.Services) - ofRule, exists := ofRuleByServicesMap[svcKey] - // Create a new Openflow rule if the group doesn't exist. - if !exists { - ofRule = &types.PolicyRule{ + ips := r.getIPs(rule.TargetMembers) + lastRealized.podIPs = ips + from := ipsToOFAddresses(ips) + memberByServicesMap, servicesMap := groupMembersByServices(rule.Services, rule.ToAddresses) + for svcKey, members := range memberByServicesMap { + ofRuleByServicesMap[svcKey] = &types.PolicyRule{ Direction: v1beta2.DirectionOut, From: from, - To: []types.Address{}, - Service: filterUnresolvablePort(rule.Services), + To: groupMembersToOFAddresses(members), + Service: filterUnresolvablePort(servicesMap[svcKey]), Action: rule.Action, + Priority: ofPriority, Name: rule.Name, - Priority: nil, TableID: table, PolicyRef: rule.SourceRef, EnableLogging: rule.EnableLogging, } - ofRuleByServicesMap[svcKey] = ofRule } - if len(rule.To.IPBlocks) > 0 { - // Diff Addresses between To and Except of IPBlocks - to := ipBlocksToOFAddresses(rule.To.IPBlocks, r.ipv4Enabled, r.ipv6Enabled) - ofRule.To = append(ofRule.To, to...) - } - if r.fqdnController != nil && len(rule.To.FQDNs) > 0 { - var addresses []types.Address - addressSet := sets.NewString() - matchedIPs := r.fqdnController.getIPsForFQDNSelectors(rule.To.FQDNs) - for _, ipAddr := range matchedIPs { - addresses = append(addresses, openflow.NewIPAddress(ipAddr)) - addressSet.Insert(ipAddr.String()) + + // If there are no "ToAddresses", the above process doesn't create any PolicyRule. + // We must ensure there is at least one PolicyRule, otherwise the Pods won't be + // isolated, so we create a PolicyRule with the original services if it doesn't exist. + // If there are IPBlocks or Pods that cannot resolve any named port, they will share + // this PolicyRule. Antrea policies do not need this default isolation. + if !rule.isAntreaNetworkPolicyRule() || len(rule.To.IPBlocks) > 0 || len(rule.To.FQDNs) > 0 || len(rule.To.ToServices) > 0 { + svcKey := normalizeServices(rule.Services) + ofRule, exists := ofRuleByServicesMap[svcKey] + // Create a new Openflow rule if the group doesn't exist. + if !exists { + ofRule = &types.PolicyRule{ + Direction: v1beta2.DirectionOut, + From: from, + To: []types.Address{}, + Service: filterUnresolvablePort(rule.Services), + Action: rule.Action, + Name: rule.Name, + Priority: nil, + TableID: table, + PolicyRef: rule.SourceRef, + EnableLogging: rule.EnableLogging, + } + ofRuleByServicesMap[svcKey] = ofRule } - ofRule.To = append(ofRule.To, addresses...) - // If the rule installation fails, this will be reset - lastRealized.fqdnIPAddresses = addressSet - } - if len(rule.To.ToServices) > 0 { - var addresses []types.Address - addressSet := sets.NewInt64() - for _, svcRef := range rule.To.ToServices { - for _, groupCounter := range r.groupCounters { - for _, groupID := range groupCounter.GetAllGroupIDs(k8s.NamespacedName(svcRef.Namespace, svcRef.Name)) { - addresses = append(addresses, openflow.NewServiceGroupIDAddress(groupID)) - addressSet.Insert(int64(groupID)) + if len(rule.To.IPBlocks) > 0 { + // Diff Addresses between To and Except of IPBlocks + to := ipBlocksToOFAddresses(rule.To.IPBlocks, r.ipv4Enabled, r.ipv6Enabled) + ofRule.To = append(ofRule.To, to...) + } + if r.fqdnController != nil && len(rule.To.FQDNs) > 0 { + var addresses []types.Address + addressSet := sets.NewString() + matchedIPs := r.fqdnController.getIPsForFQDNSelectors(rule.To.FQDNs) + for _, ipAddr := range matchedIPs { + addresses = append(addresses, openflow.NewIPAddress(ipAddr)) + addressSet.Insert(ipAddr.String()) + } + ofRule.To = append(ofRule.To, addresses...) + // If the rule installation fails, this will be reset + lastRealized.fqdnIPAddresses = addressSet + } + if len(rule.To.ToServices) > 0 { + var addresses []types.Address + addressSet := sets.NewInt64() + for _, svcRef := range rule.To.ToServices { + for _, groupCounter := range r.groupCounters { + for _, groupID := range groupCounter.GetAllGroupIDs(k8s.NamespacedName(svcRef.Namespace, svcRef.Name)) { + addresses = append(addresses, openflow.NewServiceGroupIDAddress(groupID)) + addressSet.Insert(int64(groupID)) + } } } + ofRule.To = append(ofRule.To, addresses...) + // If the rule installation fails, this will be reset. + lastRealized.groupIDAddresses = addressSet } - ofRule.To = append(ofRule.To, addresses...) - // If the rule installation fails, this will be reset. - lastRealized.groupIDAddresses = addressSet } } + } else if r.mcastController != nil { + r.mcastController.addGroupAddressForTableIDs(rule.ID, ofPriority,groupAddress) } return ofRuleByServicesMap, lastRealized } @@ -568,7 +647,7 @@ func (r *reconciler) batchAdd(rules []*CompletedRule, ofPriorities []*uint16) er var allOFRules []*types.PolicyRule for idx, rule := range rules { - ruleTable := r.getOFRuleTable(rule) + ruleTable, _ := r.getOFRuleTable(rule) ofRuleByServicesMap, lastRealized := r.computeOFRulesForAdd(rule, ofPriorities[idx], ruleTable) lastRealizeds[idx] = lastRealized for svcKey, ofRule := range ofRuleByServicesMap { @@ -608,7 +687,12 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule, for svcKey, ofID := range lastRealized.ofIDs { staleOFIDs[svcKey] = ofID } - + groupAddresses, isIGMP := r.getMcastGroupAddress(newRule) + if isIGMP { + r.mcastController.updateGroupAddressForTableIDs(newRule.ID, ofPriority, groupAddresses) + lastRealized.CompletedRule = newRule + return nil + } // As rule identifier is calculated from the rule's content, the update can // only happen to Group members. if newRule.Direction == v1beta2.DirectionIn { @@ -846,7 +930,7 @@ func (r *reconciler) Forget(ruleID string) error { } lastRealized := value.(*lastRealized) - table := r.getOFRuleTable(lastRealized.CompletedRule) + table, _ := r.getOFRuleTable(lastRealized.CompletedRule) priorityAssigner, exists := r.priorityAssigners[table] if exists { priorityAssigner.mutex.Lock() @@ -862,10 +946,58 @@ func (r *reconciler) Forget(ruleID string) error { if r.fqdnController != nil { r.fqdnController.deleteFQDNRule(ruleID, lastRealized.To.FQDNs) } + if r.mcastController != nil { + groupAddresses, isIGMP := r.getMcastGroupAddress(lastRealized.CompletedRule) + klog.Infof("deleteing rule %v: groupAddresses %v, isIGMP %v", ruleID, groupAddresses, isIGMP) + if isIGMP { + r.mcastController.deleteGroupAddressForTableIDs(ruleID, groupAddresses) + } + } r.lastRealizeds.Delete(ruleID) return nil } +func (r *reconciler) getMcastGroupAddress(rule * CompletedRule) ([]string, bool) { + groupAddresses := []string{} + isIGMP := false + if len(rule.Services) > 0 && (rule.Services[0].Protocol != nil) && + (*rule.Services[0].Protocol == v1beta2.ProtocolIGMP) { + isIGMP = true + if rule.Direction == v1beta2.DirectionIn { + for _, service := range rule.Services { + if service.GroupAddress != nil { + groupAddressCidr := net.IPNet{ + IP: net.IP(service.GroupAddress.CIDR.IP), + Mask: net.CIDRMask(int(service.GroupAddress.CIDR.PrefixLength),32), + } + + groupAddresses = append(groupAddresses, groupAddressCidr.String()) + klog.Infof("getMcastGroupAddress service.GroupAddresses %+v", service.GroupAddress) + } + } + if len(groupAddresses) == 0 { + groupAddresses = append(groupAddresses, mcastAllHosts.String()) + } + } else if rule.Direction == v1beta2.DirectionOut { + for _, service := range rule.Services { + if service.GroupAddress != nil { + groupAddressCidr := net.IPNet{ + IP: net.IP(service.GroupAddress.CIDR.IP), + Mask: net.CIDRMask(int(service.GroupAddress.CIDR.PrefixLength),32), + } + + groupAddresses = append(groupAddresses, groupAddressCidr.String()) + klog.Infof("getMcastGroupAddress service.GroupAddresses %+v", service.GroupAddress) + } + } + } else { + isIGMP = false + } + } + klog.Infof("getMcastGroupAddress %+v %v %+v", groupAddresses, isIGMP) + return groupAddresses, isIGMP +} + func (r *reconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule, bool, error) { return r.idAllocator.getRuleFromAsyncCache(ruleFlowID) } diff --git a/pkg/agent/controller/networkpolicy/reconciler_test.go b/pkg/agent/controller/networkpolicy/reconciler_test.go index dc9a4448826..1c6bf2b19dd 100644 --- a/pkg/agent/controller/networkpolicy/reconciler_test.go +++ b/pkg/agent/controller/networkpolicy/reconciler_test.go @@ -103,7 +103,7 @@ func newTestReconciler(t *testing.T, controller *gomock.Controller, ifaceStore i ch := make(chan string, 100) groupIDAllocator := openflow.NewGroupAllocator(v6Enabled) groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(groupIDAllocator, ch)} - r := newReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f, groupCounters, v4Enabled, v6Enabled, true) + r := newReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f,nil, groupCounters, v4Enabled, v6Enabled, true) return r } @@ -215,6 +215,7 @@ func TestReconcilerReconcile(t *testing.T) { ipNet2 := newCIDR("10.20.0.0/16") ipNet3 := newCIDR("10.20.1.0/24") ipNet4 := newCIDR("10.20.2.0/28") + ipNet5 := newCIDR("234.10.10.100/4") diffNet1 := newCIDR("10.20.128.0/17") diffNet2 := newCIDR("10.20.64.0/18") diffNet3 := newCIDR("10.20.32.0/19") @@ -238,6 +239,9 @@ func TestReconcilerReconcile(t *testing.T) { {IP: v1beta2.IPAddress(ipNet4.IP), PrefixLength: 28}, }, } + ipBlock3 := v1beta2.IPBlock{ + CIDR: v1beta2.IPNet{IP: v1beta2.IPAddress(ipNet5.IP), PrefixLength: 32}, + } tests := []struct { name string @@ -518,6 +522,32 @@ func TestReconcilerReconcile(t *testing.T) { }, false, }, + { + "egress-rule-for-mcast-ipblocks", + &CompletedRule{ + rule: &rule{ + ID: "egress-rule", + Direction: v1beta2.DirectionOut, + To: v1beta2.NetworkPolicyPeer{IPBlocks: []v1beta2.IPBlock{ipBlock3}}, + SourceRef: &np1, + }, + FromAddresses: nil, + ToAddresses: addressGroup1, + TargetMembers: appliedToGroup1, + }, + []*types.PolicyRule{ + { + Direction: v1beta2.DirectionOut, + From: ipsToOFAddresses(sets.NewString("2.2.2.2")), + To: []types.Address{ + openflow.NewIPAddress(net.ParseIP("224.10.10.100")), + }, + Service: nil, + PolicyRef: &np1, + }, + }, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/agent/multicast/mcast_controller.go b/pkg/agent/multicast/mcast_controller.go index 59b36a3a81a..997c09fc20c 100644 --- a/pkg/agent/multicast/mcast_controller.go +++ b/pkg/agent/multicast/mcast_controller.go @@ -46,12 +46,14 @@ const ( // How long to wait before retrying the processing of a multicast group change. minRetryDelay = 5 * time.Second maxRetryDelay = 300 * time.Second + resyncPeriod = 0 * time.Minute ) var ( workerCount uint8 = 2 // Use IGMP v1, v2, and v3 query messages to snoop the multicast groups in which local Pods have joined. queryVersions = []uint8{1, 2, 3} + igmpGroup binding.GroupIDType ) type mcastGroupEvent struct { @@ -228,6 +230,8 @@ type Controller struct { mRouteClient *MRouteClient ovsBridgeClient ovsconfig.OVSBridgeClient podDeletionCh chan *interfacestore.InterfaceConfig + queryGroupId binding.GroupIDType + anpEnabled bool } func NewMulticastController(ofClient openflow.Client, @@ -237,9 +241,11 @@ func NewMulticastController(ofClient openflow.Client, multicastSocket RouteInterface, multicastInterfaces sets.String, ovsBridgeClient ovsconfig.OVSBridgeClient, - podUpdateSubscriber channel.Subscriber) *Controller { + podUpdateSubscriber channel.Subscriber, + mcastValidator Validator, + anpEnabled bool) *Controller { eventCh := make(chan *mcastGroupEvent, workerCount) - groupSnooper := newSnooper(ofClient, ifaceStore, eventCh) + groupSnooper := newSnooper(ofClient, ifaceStore, eventCh, anpEnabled, mcastValidator) groupCache := cache.NewIndexer(getGroupEventKey, cache.Indexers{ podInterfaceIndex: podInterfaceIndexFunc, }) @@ -256,6 +262,7 @@ func NewMulticastController(ofClient openflow.Client, queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(minRetryDelay, maxRetryDelay), "multicastgroup"), mRouteClient: multicastRouteClient, ovsBridgeClient: ovsBridgeClient, + anpEnabled: anpEnabled, } podUpdateSubscriber.Subscribe(c.removeLocalInterface) return c @@ -281,6 +288,9 @@ func (c *Controller) Initialize() error { klog.ErrorS(err, "Failed to install multicast initial flows") return err } + c.queryGroupId = c.v4GroupAllocator.Allocate() + klog.Infof("multicast/mcast_controller: c.queryGroupId=%v", c.queryGroupId) + c.igmpSnooper.Initialize(c.queryGroupId) return nil } @@ -409,15 +419,15 @@ func (c *Controller) syncGroup(groupKey string) error { klog.InfoS("Removed multicast group from cache after all members left", "group", groupKey) return nil } - // Reinstall OpenFlow group because the local Pod receivers have changed. - if err := c.ofClient.InstallMulticastGroup(status.ofGroupID, memberPorts); err != nil { + // Reinstall OpenFlow group because the local pod receivers have changed. + if err := c.ofClient.InstallMulticastGroup(status.ofGroupID, nil, false, memberPorts); err != nil { return err } klog.V(2).InfoS("Updated OpenFlow group for local receivers", "group", groupKey, "ofGroup", status.ofGroupID, "localReceivers", memberPorts) return nil } // Install OpenFlow group for a new multicast group which has local Pod receivers joined. - if err := c.ofClient.InstallMulticastGroup(status.ofGroupID, memberPorts); err != nil { + if err := c.ofClient.InstallMulticastGroup(status.ofGroupID, nil, false, memberPorts); err != nil { return err } klog.V(2).InfoS("Installed OpenFlow group for local receivers", "group", groupKey, "ofGroup", status.ofGroupID, "localReceivers", memberPorts) diff --git a/pkg/agent/multicast/mcast_controller_test.go b/pkg/agent/multicast/mcast_controller_test.go index c07b0514eb9..6147850b728 100644 --- a/pkg/agent/multicast/mcast_controller_test.go +++ b/pkg/agent/multicast/mcast_controller_test.go @@ -91,7 +91,7 @@ func TestAddGroupMemberStatus(t *testing.T) { assert.True(t, ok) assert.Equal(t, mgroup.String(), key) mockIfaceStore.EXPECT().GetInterfaceByName(if1.InterfaceName).Return(if1, true) - mockOFClient.EXPECT().InstallMulticastGroup(gomock.Any(), gomock.Any()) + mockOFClient.EXPECT().InstallMulticastGroup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockOFClient.EXPECT().InstallMulticastFlows(mgroup, gomock.Any()).Times(1) mockMulticastSocket.EXPECT().MulticastInterfaceJoinMgroup(mgroup.To4(), nodeIf1IP.To4(), if1.InterfaceName).Times(1) err = mctrl.syncGroup(key) @@ -189,7 +189,7 @@ func TestCheckLastMember(t *testing.T) { mctrl.queue.Forget(obj) } mockIfaceStore.EXPECT().GetInterfaceByName(if1.InterfaceName).Return(if1, true).Times(1) - mockOFClient.EXPECT().InstallMulticastGroup(gomock.Any(), gomock.Any()).Times(1) + mockOFClient.EXPECT().InstallMulticastGroup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1) for _, tc := range []struct { ev *mcastGroupEvent exists bool @@ -364,7 +364,7 @@ func newMockMulticastController(t *testing.T) *Controller { mockOFClient.EXPECT().RegisterPacketInHandler(gomock.Any(), gomock.Any(), gomock.Any()).Times(1) groupAllocator := openflow.NewGroupAllocator(false) podUpdateSubscriber := channel.NewSubscribableChannel("PodUpdate", 100) - mctrl := NewMulticastController(mockOFClient, groupAllocator, nodeConfig, mockIfaceStore, mockMulticastSocket, sets.NewString(), ovsClient, podUpdateSubscriber) + mctrl := NewMulticastController(mockOFClient, groupAllocator, nodeConfig, mockIfaceStore, mockMulticastSocket, sets.NewString(), ovsClient, podUpdateSubscriber,nil) return mctrl } diff --git a/pkg/agent/multicast/mcast_discovery.go b/pkg/agent/multicast/mcast_discovery.go index 7d4b7fe91fc..ba8a2db1743 100644 --- a/pkg/agent/multicast/mcast_discovery.go +++ b/pkg/agent/multicast/mcast_discovery.go @@ -28,6 +28,7 @@ import ( "antrea.io/antrea/pkg/agent/interfacestore" "antrea.io/antrea/pkg/agent/openflow" + binding "antrea.io/antrea/pkg/ovs/openflow" ) const ( @@ -52,9 +53,12 @@ var ( ) type IGMPSnooper struct { - ofClient openflow.Client - ifaceStore interfacestore.InterfaceStore - eventCh chan *mcastGroupEvent + ofClient openflow.Client + ifaceStore interfacestore.InterfaceStore + eventCh chan *mcastGroupEvent + anpEnabled bool + mcastValidator Validator + queryGroupId binding.GroupIDType } func (s *IGMPSnooper) HandlePacketIn(pktIn *ofctrl.PacketIn) error { @@ -103,10 +107,14 @@ func (s *IGMPSnooper) queryIGMP(group net.IP, versions []uint8) error { if err != nil { return err } - if err := s.ofClient.SendIGMPQueryPacketOut(igmpQueryDstMac, mcastAllHosts, openflow13.P_NORMAL, igmp); err != nil { + outPort := uint32(openflow13.P_NORMAL) + if s.anpEnabled { + outPort = 0 + } + if err := s.ofClient.SendIGMPQueryPacketOut(igmpQueryDstMac, mcastAllHosts, outPort, igmp); err != nil { return err } - klog.V(2).InfoS("Sent packetOut for IGMP query", "group", group.String(), "version", version) + klog.V(2).InfoS("Sent packetOut for IGMP query", "group", group.String(), "version", version, "outPort", outPort) } return nil } @@ -138,6 +146,13 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { time: now, iface: iface, } + ruleAction, uid, name, err := s.mcastValidator.Validation(iface, mgroup) + if ruleAction != nil { + klog.Infof("group %+v action %+v", mgroup, ruleAction) + } + klog.Infof("uid %+v", uid) + klog.Infof("name %+v", name) + klog.Infof("error %+v", err) s.eventCh <- event case protocol.IGMPv3Report: msg := igmp.(*protocol.IGMPv3MembershipReport) @@ -154,6 +169,13 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { time: now, iface: iface, } + ruleAction, uid, name, err := s.mcastValidator.Validation(iface, mgroup) + if ruleAction != nil { + klog.Infof("group %+v action %+v", mgroup, ruleAction) + } + klog.Infof("uid %+v", uid) + klog.Infof("name %+v", name) + klog.Infof("err %+v", err) s.eventCh <- event } @@ -166,6 +188,13 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { time: now, iface: iface, } + ruleAction, uid, name, err := s.mcastValidator.Validation(iface, mgroup) + if ruleAction != nil { + klog.Infof("group %+v action %+v", mgroup, ruleAction) + } + klog.Infof("uid %+v", uid) + klog.Infof("name %+v", name) + klog.Infof("err %+v", err) s.eventCh <- event } return nil @@ -242,8 +271,14 @@ func parseIGMPPacket(pkt protocol.Ethernet) (protocol.IGMPMessage, error) { } } -func newSnooper(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, eventCh chan *mcastGroupEvent) *IGMPSnooper { - d := &IGMPSnooper{ofClient: ofClient, ifaceStore: ifaceStore, eventCh: eventCh} +func newSnooper(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, eventCh chan *mcastGroupEvent, anpEnabled bool, multicastValidator Validator) *IGMPSnooper { + + d := &IGMPSnooper{ofClient: ofClient, ifaceStore: ifaceStore, eventCh: eventCh, anpEnabled: anpEnabled, mcastValidator: multicastValidator} + ofClient.RegisterPacketInHandler(uint8(openflow.PacketInReasonMC), "MulticastGroupDiscovery", d) return d } + +func (s *IGMPSnooper) Initialize(id binding.GroupIDType) { + s.mcastValidator.Initialize(id) +} diff --git a/pkg/agent/multicast/multicast_api.go b/pkg/agent/multicast/multicast_api.go new file mode 100644 index 00000000000..cc979df5551 --- /dev/null +++ b/pkg/agent/multicast/multicast_api.go @@ -0,0 +1,14 @@ +package multicast + +import ( + "antrea.io/antrea/pkg/agent/interfacestore" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" + "antrea.io/antrea/pkg/ovs/openflow" + "k8s.io/apimachinery/pkg/types" + "net" +) + +type Validator interface { + Initialize(bucketID openflow.GroupIDType) bool + Validation(iface *interfacestore.InterfaceConfig, groupAddress net.IP) (*v1alpha1.RuleAction, types.UID, string, error) +} \ No newline at end of file diff --git a/pkg/agent/openflow/client.go b/pkg/agent/openflow/client.go index 0bef5c20634..2d950da8989 100644 --- a/pkg/agent/openflow/client.go +++ b/pkg/agent/openflow/client.go @@ -270,6 +270,7 @@ type Client interface { InstallMulticastInitialFlows(pktInReason uint8) error // InstallMulticastFlows installs the flow to forward Multicast traffic normally, and output it to antrea-gw0 // to ensure it can be forwarded to the external addresses. + InstallMulticastIGMPQueryFlow() error InstallMulticastFlows(multicastIP net.IP, groupID binding.GroupIDType) error // UninstallMulticastFlows removes the flow matching the given multicastIP. UninstallMulticastFlows(multicastIP net.IP) error @@ -279,7 +280,11 @@ type Client interface { dstIP net.IP, outPort uint32, igmp ofutil.Message) error - InstallMulticastGroup(ofGroupID binding.GroupIDType, localReceivers []uint32) error + //InstallMulticastGroup(ofGroupID binding.GroupIDType, localReceivers []uint32) error + InstallMulticastGroup(groupID binding.GroupIDType, + blockedPorts map[uint32]bool, + queryGroup bool, + localReceivers []uint32) error } // GetFlowTableStatus returns an array of flow table status. @@ -708,6 +713,7 @@ func (c *client) generatePipelines() { c.ovsMetersAreSupported, c.enableDenyTracking, c.enableAntreaPolicy, + c.enableMulticast, c.connectUplinkToBridge) c.activatedFeatures = append(c.activatedFeatures, c.featureNetworkPolicy) c.traceableFeatures = append(c.traceableFeatures, c.featureNetworkPolicy) @@ -1099,6 +1105,14 @@ func (c *client) InstallMulticastInitialFlows(pktInReason uint8) error { return c.addFlows(c.featureMulticast.cachedFlows, cacheKey, flows) } +func (c *client) InstallMulticastIGMPQueryFlow() error { + flows := c.featureMulticast.igmpMetric() + cache_key := "multicast_query" + c.replayMutex.Lock() + defer c.replayMutex.Unlock() + return c.addFlows(c.featureMulticast.cachedFlows, cache_key, flows) +} + func (c *client) InstallMulticastFlows(multicastIP net.IP, groupID binding.GroupIDType) error { flows := c.featureMulticast.localMulticastForwardFlows(multicastIP, groupID) cacheKey := fmt.Sprintf("multicast_%s", multicastIP.String()) @@ -1134,12 +1148,12 @@ func (c *client) SendIGMPQueryPacketOut( return c.bridge.SendPacketOut(packetOutObj) } -func (c *client) InstallMulticastGroup(groupID binding.GroupIDType, localReceivers []uint32) error { +func (c *client) InstallMulticastGroup(groupID binding.GroupIDType, blockedPorts map[uint32]bool, queryGroup bool, localReceivers []uint32) error { c.replayMutex.RLock() defer c.replayMutex.RUnlock() targetPorts := append([]uint32{config.HostGatewayOFPort}, localReceivers...) - if err := c.featureMulticast.multicastReceiversGroup(groupID, targetPorts...); err != nil { + if err := c.featureMulticast.multicastGroup(groupID, blockedPorts, queryGroup, targetPorts...); err != nil { return err } return nil diff --git a/pkg/agent/openflow/fields.go b/pkg/agent/openflow/fields.go index 72cd5f300d2..a92906ca193 100644 --- a/pkg/agent/openflow/fields.go +++ b/pkg/agent/openflow/fields.go @@ -123,11 +123,11 @@ var ( NotAntreaFlexibleIPAMRegMark = binding.NewOneBitZeroRegMark(4, 20, "NotAntreaFlexibleIPAM") // reg4[21]: Mark to indicate externalTrafficPolicy of the Service is Cluster. ToClusterServiceRegMark = binding.NewOneBitRegMark(4, 21, "ToClusterService") - + McastDropByNPRegMark = binding.NewRegField(4, 22, 22, "McastDropByNP") // reg5(NXM_NX_REG5) // Field to cache the Egress conjunction ID hit by TraceFlow packet. TFEgressConjIDField = binding.NewRegField(5, 0, 31, "TFEgressConjunctionID") - + McastEgressConjIDField = binding.NewRegField(5, 0, 31, "McastEgressConjunctionID") // reg6(NXM_NX_REG6) // Field to store the Ingress conjunction ID hit by TraceFlow packet. TFIngressConjIDField = binding.NewRegField(6, 0, 31, "TFIngressConjunctionID") diff --git a/pkg/agent/openflow/framework.go b/pkg/agent/openflow/framework.go index 4d26157da46..c23dd614106 100644 --- a/pkg/agent/openflow/framework.go +++ b/pkg/agent/openflow/framework.go @@ -242,7 +242,12 @@ func (f *featureEgress) getRequiredTables() []*Table { func (f *featureMulticast) getRequiredTables() []*Table { return []*Table{ - MulticastRoutingTable, + MulticastIGMPTable, + MulticastIGMPMetricTable, + MulticastGroupTable, + MulticastEgressRuleTable, + MulticastEgressMetricTable, + //MulticastTable, MulticastOutputTable, } } diff --git a/pkg/agent/openflow/multicast.go b/pkg/agent/openflow/multicast.go index 871ecc91b68..496b7614e12 100644 --- a/pkg/agent/openflow/multicast.go +++ b/pkg/agent/openflow/multicast.go @@ -26,6 +26,7 @@ import ( ) var _, mcastCIDR, _ = net.ParseCIDR("224.0.0.0/4") +var mcastAllHosts = net.ParseIP("224.0.0.1").To4() type featureMulticast struct { cookieAllocator cookie.Allocator @@ -75,14 +76,34 @@ func (f *featureMulticast) replayFlows() []binding.Flow { return getCachedFlows(f.cachedFlows) } -func (f *featureMulticast) multicastReceiversGroup(groupID binding.GroupIDType, ports ...uint32) error { +func (f *featureMulticast) multicastGroup(groupID binding.GroupIDType, blockedPorts map[uint32]bool, + querygroup bool, ports ...uint32) error { + var table *Table + if querygroup { + table = MulticastIGMPTable + } else { + table = MulticastGroupTable + } group := f.bridge.CreateGroupTypeAll(groupID).ResetBuckets() + ok := false for i := range ports { - group = group.Bucket(). - LoadToRegField(OFPortFoundRegMark.GetField(), OFPortFoundRegMark.GetValue()). - LoadToRegField(TargetOFPortField, ports[i]). - ResubmitToTable(MulticastRoutingTable.GetNext()). - Done() + if blockedPorts != nil { + _, ok = blockedPorts[ports[i]] + } + if ok && querygroup { + group = group.Bucket(). + LoadToRegField(OFPortFoundRegMark.GetField(), OFPortFoundRegMark.GetValue()). + LoadToRegField(TargetOFPortField, ports[i]). + LoadToRegField(McastDropByNPRegMark, 1). + ResubmitToTable(table.GetNext()). + Done() + } else { + group = group.Bucket(). + LoadToRegField(OFPortFoundRegMark.GetField(), OFPortFoundRegMark.GetValue()). + LoadToRegField(TargetOFPortField, ports[i]). + ResubmitToTable(table.GetNext()). + Done() + } } if err := group.Add(); err != nil { return fmt.Errorf("error when installing Multicast receiver Group: %w", err) @@ -91,6 +112,14 @@ func (f *featureMulticast) multicastReceiversGroup(groupID binding.GroupIDType, return nil } +func (f *featureMulticast) multicastReceiversGroup(groupID binding.GroupIDType, blockedPorts map[uint32]bool, ports ...uint32) error { + return f.multicastGroup(groupID, blockedPorts, false, ports...) +} + +func (f *featureMulticast) multicastQueryGroups(groupID binding.GroupIDType, blockedPorts map[uint32]bool, ports ...uint32) error { + return f.multicastGroup(groupID, blockedPorts, true, ports...) +} + func (f *featureMulticast) multicastOutputFlow(cookieID uint64) binding.Flow { return MulticastOutputTable.ofTable.BuildFlow(priorityNormal). Cookie(cookieID). diff --git a/pkg/agent/openflow/network_policy.go b/pkg/agent/openflow/network_policy.go index c2bb9e29d14..872dc390c92 100644 --- a/pkg/agent/openflow/network_policy.go +++ b/pkg/agent/openflow/network_policy.go @@ -58,6 +58,7 @@ var ( MatchICMPCode = types.NewMatchKey(binding.ProtocolICMP, types.ICMPAddr, "icmp_code") MatchICMPv6Type = types.NewMatchKey(binding.ProtocolICMPv6, types.ICMPAddr, "icmpv6_type") MatchICMPv6Code = types.NewMatchKey(binding.ProtocolICMPv6, types.ICMPAddr, "icmpv6_code") + MatchIGMPDstIP = types.NewMatchKey(binding.ProtocolIGMP, types.IPAddr, "nw_dst") MatchServiceGroupID = types.NewMatchKey(binding.ProtocolIP, types.ServiceGroupIDAddr, "reg7[0..31]") Unsupported = types.NewMatchKey(binding.ProtocolIP, types.UnSupported, "unknown") @@ -1016,18 +1017,34 @@ func (f *featureNetworkPolicy) calculateActionFlowChangesForRule(rule *types.Pol // Install action flows. var actionFlows []binding.Flow var metricFlows []binding.Flow - if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionDrop { - metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress)) - actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionDrop, rule.EnableLogging)) - } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionReject { - metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress)) - actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionRej, rule.EnableLogging)) - } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionPass { - actionFlows = append(actionFlows, f.conjunctionActionPassFlow(ruleOfID, ruleTable, rule.Priority, rule.EnableLogging)) + if !f.enableMulticast || (f.enableMulticast && rule.TableID != MulticastEgressRuleTable.GetID()) { + if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionDrop { + metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress)) + actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionDrop, rule.EnableLogging)) + } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionReject { + metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress)) + actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionRej, rule.EnableLogging)) + } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionPass { + actionFlows = append(actionFlows, f.conjunctionActionPassFlow(ruleOfID, ruleTable, rule.Priority, rule.EnableLogging)) + } else { + metricFlows = append(metricFlows, f.allowRulesMetricFlows(ruleOfID, isIngress)...) + actionFlows = append(actionFlows, f.conjunctionActionFlow(ruleOfID, ruleTable, dropTable.GetNext(), rule.Priority, rule.EnableLogging)...) + } } else { - metricFlows = append(metricFlows, f.allowRulesMetricFlows(ruleOfID, isIngress)...) - actionFlows = append(actionFlows, f.conjunctionActionFlow(ruleOfID, ruleTable, dropTable.GetNext(), rule.Priority, rule.EnableLogging)...) + if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionDrop { + metricFlows = append(metricFlows, f.mcastDenyRuleMetricFlow(ruleOfID, isIngress)) + actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionDrop, rule.EnableLogging)) + } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionReject { + metricFlows = append(metricFlows, f.mcastDenyRuleMetricFlow(ruleOfID, isIngress)) + actionFlows = append(actionFlows, f.conjunctionActionDenyFlow(ruleOfID, ruleTable, rule.Priority, DispositionRej, rule.EnableLogging)) + } else if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionPass { + actionFlows = append(actionFlows, f.conjunctionActionPassFlow(ruleOfID, ruleTable, rule.Priority, rule.EnableLogging)) + } else { + metricFlows = append(metricFlows, f.mcastAllowRuleMetricFlow(ruleOfID, isIngress)...) + actionFlows = append(actionFlows, f.conjunctionActionFlow(ruleOfID, ruleTable, dropTable.GetNext(), rule.Priority, rule.EnableLogging)...) + } } + conj.actionFlows = actionFlows conj.metricFlows = metricFlows } @@ -1784,6 +1801,8 @@ type featureNetworkPolicy struct { // egressTables map records all IDs of tables related to egress rules. egressTables map[uint8]struct{} + enableMulticast bool + ovsMetersAreSupported bool enableDenyTracking bool enableAntreaPolicy bool @@ -1807,6 +1826,7 @@ func newFeatureNetworkPolicy( ovsMetersAreSupported, enableDenyTracking, enableAntreaPolicy bool, + enableMulticast bool, connectUplinkToBridge bool) *featureNetworkPolicy { return &featureNetworkPolicy{ cookieAllocator: cookieAllocator, @@ -1814,6 +1834,7 @@ func newFeatureNetworkPolicy( bridge: bridge, globalConjMatchFlowCache: make(map[string]*conjMatchFlowContext), policyCache: cache.NewIndexer(policyConjKeyFunc, cache.Indexers{priorityIndex: priorityIndexFunc}), + enableMulticast: enableMulticast, ovsMetersAreSupported: ovsMetersAreSupported, enableDenyTracking: enableDenyTracking, enableAntreaPolicy: enableAntreaPolicy, @@ -1826,6 +1847,9 @@ func (f *featureNetworkPolicy) initFlows() []binding.Flow { f.egressTables = map[uint8]struct{}{EgressRuleTable.GetID(): {}, EgressDefaultTable.GetID(): {}} if f.enableAntreaPolicy { f.egressTables[AntreaPolicyEgressRuleTable.GetID()] = struct{}{} + if f.enableMulticast { + f.egressTables[MulticastEgressRuleTable.GetID()] = struct{}{} + } } var flows []binding.Flow flows = append(flows, f.establishedConnectionFlows()...) diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go index c675bfaf6bc..75aec7e2f36 100644 --- a/pkg/agent/openflow/pipeline.go +++ b/pkg/agent/openflow/pipeline.go @@ -179,7 +179,13 @@ var ( // Tables of pipelineMulticast are declared below. Do don't declare any tables of other pipelines here! // Tables in stageRouting: - MulticastRoutingTable = newTable("MulticastRouting", stageRouting, pipelineMulticast) + MulticastIGMPTable = newTable("MulticastIGMP", stageEgressSecurity, pipelineMulticast) + MulticastIGMPMetricTable = newTable("MulticastIGMPMetric", stageEgressSecurity, pipelineMulticast) + MulticastGroupTable = newTable("MulticastGroup", stageEgressSecurity, pipelineMulticast) + MulticastEgressRuleTable = newTable("MulticastEgressRule", stageEgressSecurity, pipelineMulticast) + MulticastEgressMetricTable = newTable("MulticastEgressMetric", stageEgressSecurity, pipelineMulticast) + //todo add Ingress when there is requirement for multicast ingress + //MulticastTable = newTable("Multicast", stageRouting, pipelineMulticast) // Tables in stageOutput MulticastOutputTable = newTable("MulticastOutput", stageOutput, pipelineMulticast) @@ -287,6 +293,20 @@ func GetAntreaPolicyEgressTables() []*Table { } } +func GetAntreaIGMPTables() []*Table { + return []*Table { + MulticastIGMPTable, + MulticastIGMPMetricTable, + } +} + +func GetAntreaMulticastEgressTable() []*Table { + return []*Table { + MulticastEgressRuleTable, + MulticastEgressMetricTable, + } +} + func GetAntreaPolicyIngressTables() []*Table { return []*Table{ AntreaPolicyIngressRuleTable, @@ -1628,6 +1648,41 @@ func (f *featureNetworkPolicy) denyRuleMetricFlow(conjunctionID uint32, ingress Done() } + +func (f *featureNetworkPolicy) mcastAllowRuleMetricFlow(conjunctionID uint32, ingress bool) []binding.Flow { + metricTable := MulticastEgressRuleTable + cookieID := f.cookieAllocator.Request(f.category).Raw() + field := McastEgressConjIDField + if ingress { + //todo add ingress support + } + metricFlow := func(protocol binding.Protocol) binding.Flow { + return metricTable.ofTable.BuildFlow(priorityNormal). + Cookie(cookieID). + MatchProtocol(protocol). + MatchRegFieldWithValue(field, conjunctionID). + Action().NextTable(). + Done() + } + var flows []binding.Flow + for _, ipProtocol := range f.ipProtocols { + flows = append(flows, metricFlow(ipProtocol)) + } + return flows +} + +func (f *featureNetworkPolicy) mcastDenyRuleMetricFlow(conjunctionID uint32, ingress bool) binding.Flow { + metricTable := MulticastEgressMetricTable + if !ingress { + //todo add ingress support + } + return metricTable.ofTable.BuildFlow(priorityNormal). + Cookie(f.cookieAllocator.Request(f.category).Raw()). + MatchRegFieldWithValue(McastEgressConjIDField, conjunctionID). + Action().Drop(). + Done() +} + // ipv6Flows generates the flows to allow IPv6 packets from link-local addresses and handle multicast packets, Neighbor // Solicitation and ND Advertisement packets properly. func (f *featurePodConnectivity) ipv6Flows() []binding.Flow { @@ -1735,11 +1790,17 @@ func (f *featureNetworkPolicy) conjunctionActionDenyFlow(conjunctionID uint32, t if _, ok := f.egressTables[tableID]; ok { metricTable = EgressMetricTable } - - flowBuilder := table.BuildFlow(ofPriority). - MatchConjID(conjunctionID). - Action().LoadToRegField(CNPDenyConjIDField, conjunctionID). - Action().LoadRegMark(CnpDenyRegMark) + flowBuilder := table.BuildFlow(ofPriority) + if f.enableMulticast && tableID == MulticastEgressRuleTable.GetID() { + metricTable = MulticastEgressMetricTable + flowBuilder = flowBuilder.MatchConjID(conjunctionID). + Action().LoadToRegField(McastEgressConjIDField, conjunctionID). + Action().LoadRegMark(CnpDenyRegMark) + } else { + flowBuilder = flowBuilder.MatchConjID(conjunctionID). + Action().LoadToRegField(CNPDenyConjIDField, conjunctionID). + Action().LoadRegMark(CnpDenyRegMark) + } var customReason int if f.enableDenyTracking { @@ -1780,8 +1841,16 @@ func (f *featureNetworkPolicy) conjunctionActionPassFlow(conjunctionID uint32, t conjReg = TFEgressConjIDField nextTable = EgressRuleTable } - flowBuilder := table.BuildFlow(ofPriority).MatchConjID(conjunctionID). - Action().LoadToRegField(conjReg, conjunctionID) + flowBuilder := table.BuildFlow(ofPriority) + if f.enableMulticast && tableID == MulticastEgressRuleTable.GetID() { + nextTable = MulticastEgressMetricTable + flowBuilder = flowBuilder.MatchConjID(conjunctionID). + Action().LoadToRegField(McastEgressConjIDField, conjunctionID) + } else { + flowBuilder = flowBuilder.MatchConjID(conjunctionID). + Action().LoadToRegField(conjReg, conjunctionID) + } + if enableLogging { flowBuilder = flowBuilder. Action().LoadRegMark(DispositionPassRegMark, CustomReasonLoggingRegMark). @@ -2645,7 +2714,7 @@ func pipelineClassifyFlow(cookieID uint64, protocol binding.Protocol, pipeline b Done() } -// igmpPktInFlows generates the flow to load CustomReasonIGMPRegMark to mark the IGMP packet in MulticastRoutingTable and sends +// igmpPktInFlows generates the flow to load CustomReasonIGMPRegMark to mark the IGMP packet in MulticastIGMPTable and sends // it to antrea-agent. func (f *featureMulticast) igmpPktInFlows(reason uint8) []binding.Flow { flows := []binding.Flow{ @@ -2654,7 +2723,7 @@ func (f *featureMulticast) igmpPktInFlows(reason uint8) []binding.Flow { // group and its members in the meanwhile. // Do not set dst IP address because IGMPv1 report message uses target multicast group as IP destination in // the packet. - MulticastRoutingTable.ofTable.BuildFlow(priorityHigh). + MulticastIGMPTable.ofTable.BuildFlow(priorityHigh). Cookie(f.cookieAllocator.Request(f.category).Raw()). MatchProtocol(binding.ProtocolIGMP). MatchRegMark(FromLocalRegMark). @@ -2666,14 +2735,43 @@ func (f *featureMulticast) igmpPktInFlows(reason uint8) []binding.Flow { return flows } +func (f *featureMulticast) igmpMetric() []binding.Flow{ + flows := []binding.Flow{ + // Set a custom reason for the IGMP packets, and then send it to antrea-agent and forward it normally in the + // OVS bridge, so that the OVS multicast db cache can be updated, and antrea-agent can identify the local multicast + // group and its members in the meanwhile. + // Do not set dst IP address because IGMPv1 report message uses target multicast group as IP destination in + // the packet. + MulticastIGMPMetricTable.ofTable.BuildFlow(priorityHigh). + Cookie(f.cookieAllocator.Request(f.category).Raw()). + MatchProtocol(binding.ProtocolIGMP). + MatchRegFieldWithValue(McastDropByNPRegMark, 1). + Action().Drop(). + Done(), + MulticastIGMPMetricTable.ofTable.BuildFlow(priorityNormal). + Cookie(f.cookieAllocator.Request(f.category).Raw()). + MatchProtocol(binding.ProtocolIGMP). + MatchRegFieldWithValue(OFPortFoundRegMark.GetField(), OFPortFoundRegMark.GetValue()). + Action().GotoTable(MulticastEgressRuleTable.GetID()). + Done(), + } + return flows +} + // localMulticastForwardFlows generates the flow to forward multicast packets with OVS action "normal", and outputs // it to Antrea gateway in the meanwhile, so that the packet can be forwarded to local Pods which have joined the Multicast // group and to the external receivers. For external multicast packets accessing to the given multicast IP also hits the // flow, and the packet is not sent back to Antrea gateway because OVS datapath will drop it when it finds the output // port is the same as the input port. func (f *featureMulticast) localMulticastForwardFlows(multicastIP net.IP, groupID binding.GroupIDType) []binding.Flow { + var table *Table + if multicastIP.String() == mcastAllHosts.String() { + table = MulticastIGMPTable + } else { + table = MulticastGroupTable + } return []binding.Flow{ - MulticastRoutingTable.ofTable.BuildFlow(priorityNormal). + table.ofTable.BuildFlow(priorityNormal). Cookie(f.cookieAllocator.Request(f.category).Raw()). MatchProtocol(binding.ProtocolIP). MatchDstIP(multicastIP). @@ -2687,13 +2785,14 @@ func (f *featureMulticast) localMulticastForwardFlows(multicastIP net.IP, groupI // multicast group, it is handled by the flows created by function "localMulticastForwardFlows" after local Pods report the // IGMP membership. func (f *featureMulticast) externalMulticastReceiverFlow() binding.Flow { - return MulticastRoutingTable.ofTable.BuildFlow(priorityLow). + return MulticastGroupTable.ofTable.BuildFlow(priorityLow). Cookie(f.cookieAllocator.Request(f.category).Raw()). MatchProtocol(binding.ProtocolIP). MatchDstIPNet(*mcastCIDR). Action().LoadRegMark(OFPortFoundRegMark). Action().LoadToRegField(TargetOFPortField, config.HostGatewayOFPort). Action().NextTable(). +// Action().GotoTable(MulticastGroupTable.GetNext()). Done() } diff --git a/pkg/agent/openflow/pipeline_test.go b/pkg/agent/openflow/pipeline_test.go index 32e9e446604..1e8da38221b 100644 --- a/pkg/agent/openflow/pipeline_test.go +++ b/pkg/agent/openflow/pipeline_test.go @@ -38,145 +38,197 @@ func TestBuildPipeline(t *testing.T) { dualStack: {binding.ProtocolIP, binding.ProtocolIPv6}, } for _, tc := range []struct { - ipStack ipStack - features []feature - expectedTables map[binding.PipelineID][]*Table + ipStack ipStack + enableMulticast bool + features []feature + expectedTables map[binding.PipelineID][]*Table }{ + { - ipStack: dualStack, - features: []feature{ - &featurePodConnectivity{ipProtocols: ipStackMap[dualStack]}, - &featureNetworkPolicy{enableAntreaPolicy: true}, - &featureService{enableProxy: true, proxyAll: true}, - &featureEgress{}, - }, - expectedTables: map[binding.PipelineID][]*Table{ - pipelineRoot: { - PipelineRootClassifierTable, - }, - pipelineIP: { - ClassifierTable, - SpoofGuardTable, - IPv6Table, - SNATConntrackTable, - ConntrackTable, - ConntrackStateTable, - PreRoutingClassifierTable, - NodePortMarkTable, - SessionAffinityTable, - ServiceLBTable, - EndpointDNATTable, - AntreaPolicyEgressRuleTable, - EgressRuleTable, - EgressDefaultTable, - EgressMetricTable, - L3ForwardingTable, - EgressMarkTable, - L3DecTTLTable, - ServiceMarkTable, - SNATConntrackCommitTable, - L2ForwardingCalcTable, - AntreaPolicyIngressRuleTable, - IngressRuleTable, - IngressDefaultTable, - IngressMetricTable, - ConntrackCommitTable, - L2ForwardingOutTable, + ipStack: dualStack, + enableMulticast: false, + features: []feature{ + &featurePodConnectivity{ipProtocols: ipStackMap[dualStack]}, + &featureNetworkPolicy{enableAntreaPolicy: true}, + &featureService{enableProxy: true, proxyAll: true}, + &featureEgress{}, }, - pipelineARP: { - ARPSpoofGuardTable, - ARPResponderTable, + expectedTables: map[binding.PipelineID][]*Table{ + pipelineRoot: { + PipelineRootClassifierTable, + }, + pipelineIP: { + ClassifierTable, + SpoofGuardTable, + IPv6Table, + SNATConntrackTable, + ConntrackTable, + ConntrackStateTable, + PreRoutingClassifierTable, + NodePortMarkTable, + SessionAffinityTable, + ServiceLBTable, + EndpointDNATTable, + AntreaPolicyEgressRuleTable, + EgressRuleTable, + EgressDefaultTable, + EgressMetricTable, + L3ForwardingTable, + EgressMarkTable, + L3DecTTLTable, + ServiceMarkTable, + SNATConntrackCommitTable, + L2ForwardingCalcTable, + AntreaPolicyIngressRuleTable, + IngressRuleTable, + IngressDefaultTable, + IngressMetricTable, + ConntrackCommitTable, + L2ForwardingOutTable, + }, + pipelineARP: { + ARPSpoofGuardTable, + ARPResponderTable, + }, }, }, - }, - { - ipStack: ipv6Only, - features: []feature{ - &featurePodConnectivity{ipProtocols: ipStackMap[ipv6Only]}, - &featureNetworkPolicy{enableAntreaPolicy: true}, - &featureService{enableProxy: true, proxyAll: true}, - &featureEgress{}, - }, - expectedTables: map[binding.PipelineID][]*Table{ - pipelineRoot: { - PipelineRootClassifierTable, + { + ipStack: ipv6Only, + enableMulticast: false, + features: []feature{ + &featurePodConnectivity{ipProtocols: ipStackMap[ipv6Only]}, + &featureNetworkPolicy{enableAntreaPolicy: true}, + &featureService{enableProxy: true, proxyAll: true}, + &featureEgress{}, }, - pipelineIP: { - ClassifierTable, - SpoofGuardTable, - IPv6Table, - SNATConntrackTable, - ConntrackTable, - ConntrackStateTable, - PreRoutingClassifierTable, - NodePortMarkTable, - SessionAffinityTable, - ServiceLBTable, - EndpointDNATTable, - AntreaPolicyEgressRuleTable, - EgressRuleTable, - EgressDefaultTable, - EgressMetricTable, - L3ForwardingTable, - EgressMarkTable, - L3DecTTLTable, - ServiceMarkTable, - SNATConntrackCommitTable, - L2ForwardingCalcTable, - AntreaPolicyIngressRuleTable, - IngressRuleTable, - IngressDefaultTable, - IngressMetricTable, - ConntrackCommitTable, - L2ForwardingOutTable, + expectedTables: map[binding.PipelineID][]*Table{ + pipelineRoot: { + PipelineRootClassifierTable, + }, + pipelineIP: { + ClassifierTable, + SpoofGuardTable, + IPv6Table, + SNATConntrackTable, + ConntrackTable, + ConntrackStateTable, + PreRoutingClassifierTable, + NodePortMarkTable, + SessionAffinityTable, + ServiceLBTable, + EndpointDNATTable, + AntreaPolicyEgressRuleTable, + EgressRuleTable, + EgressDefaultTable, + EgressMetricTable, + L3ForwardingTable, + EgressMarkTable, + L3DecTTLTable, + ServiceMarkTable, + SNATConntrackCommitTable, + L2ForwardingCalcTable, + AntreaPolicyIngressRuleTable, + IngressRuleTable, + IngressDefaultTable, + IngressMetricTable, + ConntrackCommitTable, + L2ForwardingOutTable, + }, }, }, - }, - { - ipStack: ipv4Only, - features: []feature{ - &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only]}, - &featureNetworkPolicy{enableAntreaPolicy: true}, - &featureService{enableProxy: false}, - &featureEgress{}, - }, - expectedTables: map[binding.PipelineID][]*Table{ - pipelineRoot: { - PipelineRootClassifierTable, + { + ipStack: ipv4Only, + enableMulticast: false, + features: []feature{ + &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only]}, + &featureNetworkPolicy{enableAntreaPolicy: true}, + &featureService{enableProxy: false}, + &featureEgress{}, }, - pipelineIP: { - ClassifierTable, - SpoofGuardTable, - ConntrackTable, - ConntrackStateTable, - DNATTable, - AntreaPolicyEgressRuleTable, - EgressRuleTable, - EgressDefaultTable, - EgressMetricTable, - L3ForwardingTable, - L3DecTTLTable, - L2ForwardingCalcTable, - AntreaPolicyIngressRuleTable, - IngressRuleTable, - IngressDefaultTable, - IngressMetricTable, - ConntrackCommitTable, - L2ForwardingOutTable, + expectedTables: map[binding.PipelineID][]*Table{ + pipelineRoot: { + PipelineRootClassifierTable, + }, + pipelineIP: { + ClassifierTable, + SpoofGuardTable, + ConntrackTable, + ConntrackStateTable, + DNATTable, + AntreaPolicyEgressRuleTable, + EgressRuleTable, + EgressDefaultTable, + EgressMetricTable, + L3ForwardingTable, + L3DecTTLTable, + L2ForwardingCalcTable, + AntreaPolicyIngressRuleTable, + IngressRuleTable, + IngressDefaultTable, + IngressMetricTable, + ConntrackCommitTable, + L2ForwardingOutTable, + }, + pipelineARP: { + ARPSpoofGuardTable, + ARPResponderTable, + }, }, - pipelineARP: { - ARPSpoofGuardTable, - ARPResponderTable, + }, + { + ipStack: ipv4Only, + enableMulticast: false, + features: []feature{ + &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only]}, + &featureNetworkPolicy{enableAntreaPolicy: true}, + &featureService{enableProxy: true, proxyAll: false}, + &featureEgress{}, + }, + expectedTables: map[binding.PipelineID][]*Table{ + pipelineRoot: { + PipelineRootClassifierTable, + }, + pipelineIP: { + ClassifierTable, + SpoofGuardTable, + SNATConntrackTable, + ConntrackTable, + ConntrackStateTable, + PreRoutingClassifierTable, + SessionAffinityTable, + ServiceLBTable, + EndpointDNATTable, + AntreaPolicyEgressRuleTable, + EgressRuleTable, + EgressDefaultTable, + EgressMetricTable, + L3ForwardingTable, + EgressMarkTable, + L3DecTTLTable, + ServiceMarkTable, + SNATConntrackCommitTable, + L2ForwardingCalcTable, + AntreaPolicyIngressRuleTable, + IngressRuleTable, + IngressDefaultTable, + IngressMetricTable, + ConntrackCommitTable, + L2ForwardingOutTable, + }, + pipelineARP: { + ARPSpoofGuardTable, + ARPResponderTable, + }, }, }, - }, { - ipStack: ipv4Only, + ipStack: ipv4Only, + enableMulticast: true, features: []feature{ - &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only]}, + &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only], enableMulticast: true}, &featureNetworkPolicy{enableAntreaPolicy: true}, &featureService{enableProxy: true, proxyAll: false}, - &featureEgress{}, + &featureMulticast{}, }, expectedTables: map[binding.PipelineID][]*Table{ pipelineRoot: { @@ -185,6 +237,7 @@ func TestBuildPipeline(t *testing.T) { pipelineIP: { ClassifierTable, SpoofGuardTable, + PipelineIPClassifierTable, SNATConntrackTable, ConntrackTable, ConntrackStateTable, @@ -197,7 +250,6 @@ func TestBuildPipeline(t *testing.T) { EgressDefaultTable, EgressMetricTable, L3ForwardingTable, - EgressMarkTable, L3DecTTLTable, ServiceMarkTable, SNATConntrackCommitTable, @@ -213,6 +265,14 @@ func TestBuildPipeline(t *testing.T) { ARPSpoofGuardTable, ARPResponderTable, }, + pipelineMulticast: { + MulticastIGMPTable, + MulticastIGMPMetricTable, + MulticastGroupTable, + MulticastEgressRuleTable, + MulticastEgressMetricTable, + MulticastOutputTable, + }, }, }, } { @@ -220,6 +280,9 @@ func TestBuildPipeline(t *testing.T) { if tc.ipStack != ipv6Only { pipelineIDs = append(pipelineIDs, pipelineARP) } + if tc.enableMulticast { + pipelineIDs = append(pipelineIDs, pipelineMulticast) + } pipelineRequiredTablesMap := make(map[binding.PipelineID]map[*Table]struct{}) for _, pipelineID := range pipelineIDs { pipelineRequiredTablesMap[pipelineID] = make(map[*Table]struct{}) @@ -247,10 +310,12 @@ func TestBuildPipeline(t *testing.T) { tables := tc.expectedTables[pipelineID] for i := 0; i < len(tables)-1; i++ { + fmt.Println(tables[i].GetName(), tables[i].GetID()) require.NotNil(t, tables[i].ofTable, "table %q should be initialized", tables[i].name) require.Less(t, tables[i].GetID(), tables[i+1].GetID(), fmt.Sprintf("id of table %q should less than that of table %q", tables[i].GetName(), tables[i+1].GetName())) } - require.NotNil(t, tables[len(tables)-1].ofTable, "table %q should be initialized", tables[len(tables)-1].name) + //require.NotNil(t, tables[len(tables)-1].ofTable, "table %q should be initialized", tables[len(tables)-1].name) + require.NotNil(t, tables, "table %+v should be initialized: ", tables, tc.expectedTables[pipelineID]) } reset() } @@ -262,4 +327,4 @@ func reset() { tableCache.Delete(objs[i]) } binding.ResetTableID() -} +} \ No newline at end of file diff --git a/pkg/agent/openflow/testing/mock_openflow.go b/pkg/agent/openflow/testing/mock_openflow.go index c306bc7590f..5f337128aa9 100644 --- a/pkg/agent/openflow/testing/mock_openflow.go +++ b/pkg/agent/openflow/testing/mock_openflow.go @@ -296,17 +296,31 @@ func (mr *MockClientMockRecorder) InstallMulticastFlows(arg0, arg1 interface{}) } // InstallMulticastGroup mocks base method -func (m *MockClient) InstallMulticastGroup(arg0 openflow.GroupIDType, arg1 []uint32) error { +func (m *MockClient) InstallMulticastGroup(arg0 openflow.GroupIDType, arg1 map[uint32]bool, arg2 bool, arg3 []uint32) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InstallMulticastGroup", arg0, arg1) + ret := m.ctrl.Call(m, "InstallMulticastGroup", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // InstallMulticastGroup indicates an expected call of InstallMulticastGroup -func (mr *MockClientMockRecorder) InstallMulticastGroup(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) InstallMulticastGroup(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallMulticastGroup", reflect.TypeOf((*MockClient)(nil).InstallMulticastGroup), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallMulticastGroup", reflect.TypeOf((*MockClient)(nil).InstallMulticastGroup), arg0, arg1, arg2, arg3) +} + +// InstallMulticastIGMPQueryFlow mocks base method +func (m *MockClient) InstallMulticastIGMPQueryFlow() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstallMulticastIGMPQueryFlow") + ret0, _ := ret[0].(error) + return ret0 +} + +// InstallMulticastIGMPQueryFlow indicates an expected call of InstallMulticastIGMPQueryFlow +func (mr *MockClientMockRecorder) InstallMulticastIGMPQueryFlow() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallMulticastIGMPQueryFlow", reflect.TypeOf((*MockClient)(nil).InstallMulticastIGMPQueryFlow)) } // InstallMulticastInitialFlows mocks base method diff --git a/pkg/agent/util/net.go b/pkg/agent/util/net.go index 81719628e9f..93d57165617 100644 --- a/pkg/agent/util/net.go +++ b/pkg/agent/util/net.go @@ -368,3 +368,13 @@ func NewIPNet(ip net.IP) *net.IPNet { } return &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)} } + +func CheckIPIsMulticast(ipStr string) (isMulticast bool, err error) { + ipAddr := net.ParseIP(ipStr) + if ipAddr == nil { + isMulticast, err = false, fmt.Errorf("invalid IPaddress") + } else { + isMulticast, err = ipAddr.IsMulticast(), nil + } + return +} \ No newline at end of file diff --git a/pkg/apis/controlplane/types.go b/pkg/apis/controlplane/types.go index 198ee9c893e..522fd8f703d 100644 --- a/pkg/apis/controlplane/types.go +++ b/pkg/apis/controlplane/types.go @@ -269,6 +269,8 @@ const ( ProtocolSCTP Protocol = "SCTP" // ProtocolICMP is the ICMP protocol. ProtocolICMP Protocol = "ICMP" + + ProtocolIGMP Protocol = "IGMP" ) // Service describes a port to allow traffic on. @@ -290,6 +292,10 @@ type Service struct { // both are not specified and the Protocol is ICMP, this matches all ICMP traffic. ICMPType *int32 ICMPCode *int32 + + //IGMPType and GroupAddress can only be specidied, when the procotol is IGMP. + IGMPType *string + GroupAddress *IPBlock } // NetworkPolicyPeer describes a peer of NetworkPolicyRules. diff --git a/pkg/apis/controlplane/v1beta2/generated.pb.go b/pkg/apis/controlplane/v1beta2/generated.pb.go index cd3920bccf4..c98d859ca13 100644 --- a/pkg/apis/controlplane/v1beta2/generated.pb.go +++ b/pkg/apis/controlplane/v1beta2/generated.pb.go @@ -954,136 +954,138 @@ func init() { } var fileDescriptor_fbaa7d016762fa1d = []byte{ - // 2062 bytes of a gzipped FileDescriptorProto + // 2095 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x1a, 0xcd, 0x6f, 0x1b, 0x59, 0xbd, 0xe3, 0x8f, 0x26, 0xfe, 0xd9, 0x69, 0x9c, 0x97, 0x96, 0x9a, 0xa5, 0xd8, 0xd9, 0x59, 0x40, 0x39, 0xb0, 0xe3, 0x4d, 0x68, 0xb7, 0x85, 0xdd, 0x2e, 0xc4, 0x49, 0x1a, 0x59, 0x6a, 0x53, 0xf3, 0x92, 0x55, 0x25, 0xa0, 0xb0, 0x93, 0x99, 0x67, 0xe7, 0x11, 0x7b, 0x66, 0x98, 0x79, 0x0e, 0x8d, - 0x90, 0xd0, 0x22, 0xe0, 0xb0, 0x0b, 0x12, 0xdc, 0xb8, 0xc2, 0x89, 0x0b, 0xff, 0x04, 0x07, 0xa4, - 0x8a, 0xd3, 0xae, 0x10, 0x62, 0x4f, 0x16, 0x35, 0x02, 0xc4, 0x81, 0x7f, 0x20, 0x5c, 0xd0, 0x7b, - 0xf3, 0x66, 0xe6, 0x8d, 0x9d, 0x34, 0x75, 0x93, 0x06, 0x89, 0xdd, 0x53, 0x3d, 0xef, 0xf7, 0xfd, - 0x7e, 0xdf, 0x2f, 0x85, 0xb7, 0x4c, 0x87, 0xf9, 0xc4, 0x34, 0xa8, 0x5b, 0x0f, 0x7f, 0xd5, 0xbd, - 0xbd, 0x4e, 0xdd, 0xf4, 0x68, 0x50, 0xb7, 0x5c, 0x87, 0xf9, 0x6e, 0xd7, 0xeb, 0x9a, 0x0e, 0xa9, - 0xef, 0x2f, 0xed, 0x10, 0x66, 0x2e, 0xd7, 0x3b, 0xc4, 0x21, 0xbe, 0xc9, 0x88, 0x6d, 0x78, 0xbe, - 0xcb, 0x5c, 0x64, 0x84, 0x54, 0xdf, 0xa1, 0xae, 0xfc, 0x65, 0x78, 0x7b, 0x1d, 0x83, 0xd3, 0x1b, - 0x2a, 0xbd, 0x21, 0xe9, 0x5f, 0xba, 0x75, 0xbc, 0xbc, 0x80, 0x99, 0x2c, 0xa8, 0xef, 0x2f, 0x99, - 0x5d, 0x6f, 0xd7, 0x5c, 0x1a, 0x95, 0xf4, 0xd2, 0xab, 0x1d, 0xca, 0x76, 0xfb, 0x3b, 0x86, 0xe5, - 0xf6, 0xea, 0x1d, 0xb7, 0xe3, 0xd6, 0xc5, 0xf1, 0x4e, 0xbf, 0x2d, 0xbe, 0xc4, 0x87, 0xf8, 0x25, - 0xd1, 0xaf, 0xef, 0xdd, 0x0a, 0x84, 0x14, 0x8f, 0xf6, 0x4c, 0x6b, 0x97, 0x3a, 0xc4, 0x3f, 0x48, - 0x64, 0xf5, 0x08, 0x33, 0xeb, 0xfb, 0xe3, 0x42, 0xea, 0xc7, 0x51, 0xf9, 0x7d, 0x87, 0xd1, 0x1e, - 0x19, 0x23, 0x78, 0xfd, 0x24, 0x82, 0xc0, 0xda, 0x25, 0x3d, 0x73, 0x8c, 0xee, 0x4b, 0xc7, 0xd1, - 0xf5, 0x19, 0xed, 0xd6, 0xa9, 0xc3, 0x02, 0xe6, 0x8f, 0x12, 0xe9, 0xff, 0xd4, 0xa0, 0xb4, 0x62, - 0xdb, 0x3e, 0x09, 0x82, 0x0d, 0xdf, 0xed, 0x7b, 0xe8, 0x1d, 0x98, 0xe6, 0x96, 0xd8, 0x26, 0x33, - 0x2b, 0xda, 0x82, 0xb6, 0x58, 0x5c, 0x7e, 0xcd, 0x08, 0x19, 0x1b, 0x2a, 0xe3, 0xc4, 0x27, 0x1c, - 0xdb, 0xd8, 0x5f, 0x32, 0xee, 0xef, 0x7c, 0x97, 0x58, 0xec, 0x1e, 0x61, 0x66, 0x03, 0x3d, 0x1e, - 0xd4, 0x2e, 0x0c, 0x07, 0x35, 0x48, 0xce, 0x70, 0xcc, 0x15, 0xf5, 0xa1, 0xd4, 0xe1, 0xa2, 0xee, - 0x91, 0xde, 0x0e, 0xf1, 0x83, 0x4a, 0x66, 0x21, 0xbb, 0x58, 0x5c, 0x7e, 0x63, 0x42, 0xb7, 0x1b, - 0x1b, 0x09, 0x8f, 0xc6, 0x65, 0x29, 0xb0, 0xa4, 0x1c, 0x06, 0x38, 0x25, 0x46, 0xff, 0x93, 0x06, - 0x65, 0xd5, 0xd2, 0xbb, 0x34, 0x60, 0xe8, 0x5b, 0x63, 0xd6, 0x1a, 0xcf, 0x66, 0x2d, 0xa7, 0x16, - 0xb6, 0x96, 0xa5, 0xe8, 0xe9, 0xe8, 0x44, 0xb1, 0xd4, 0x84, 0x3c, 0x65, 0xa4, 0x17, 0x99, 0xf8, - 0xe6, 0xa4, 0x26, 0xaa, 0xea, 0x36, 0x66, 0xa4, 0xa0, 0x7c, 0x93, 0xb3, 0xc4, 0x21, 0x67, 0xfd, - 0xbd, 0x2c, 0xcc, 0xa9, 0x68, 0x2d, 0x93, 0x59, 0xbb, 0xe7, 0xe0, 0xc4, 0x9f, 0x68, 0x30, 0x67, - 0xda, 0x36, 0xb1, 0x37, 0xce, 0xd8, 0x95, 0x9f, 0x96, 0x62, 0xb9, 0x55, 0x69, 0xee, 0x78, 0x5c, - 0x20, 0x7a, 0x5f, 0x83, 0x79, 0x9f, 0xf4, 0xdc, 0xfd, 0x11, 0x45, 0xb2, 0xa7, 0x57, 0xe4, 0x33, - 0x52, 0x91, 0x79, 0x3c, 0xce, 0x1f, 0x1f, 0x25, 0x54, 0xff, 0x97, 0x06, 0x97, 0x56, 0x3c, 0xaf, - 0x4b, 0x89, 0xbd, 0xed, 0xfe, 0x9f, 0x67, 0xd3, 0x5f, 0x34, 0x40, 0x69, 0x5b, 0xcf, 0x21, 0x9f, - 0xac, 0x74, 0x3e, 0xbd, 0x35, 0x71, 0x3e, 0xa5, 0x14, 0x3e, 0x26, 0xa3, 0x7e, 0x96, 0x85, 0xf9, - 0x34, 0xe2, 0x27, 0x39, 0xf5, 0xbf, 0xcb, 0xa9, 0x5f, 0xe7, 0x60, 0x7e, 0xb5, 0xdb, 0x0f, 0x18, - 0xf1, 0x53, 0x4a, 0xbe, 0x78, 0x6f, 0xfc, 0x48, 0x83, 0x32, 0x69, 0xb7, 0x89, 0xc5, 0xe8, 0x3e, - 0x39, 0x43, 0x67, 0x54, 0xa4, 0xd4, 0xf2, 0xfa, 0x08, 0x73, 0x3c, 0x26, 0x0e, 0xfd, 0x10, 0xe6, - 0xe2, 0xb3, 0x66, 0xab, 0xd1, 0x75, 0xad, 0xbd, 0xc8, 0x0f, 0x37, 0x26, 0xd5, 0xa1, 0xd9, 0xda, - 0x24, 0x2c, 0x09, 0x85, 0xf5, 0x51, 0xbe, 0x78, 0x5c, 0x14, 0xba, 0x05, 0x25, 0xe6, 0x32, 0xb3, - 0x1b, 0x99, 0x9f, 0x5b, 0xd0, 0x16, 0xb3, 0x49, 0x7d, 0xd8, 0x56, 0x60, 0x38, 0x85, 0x89, 0x96, - 0x01, 0xc4, 0x77, 0xcb, 0xec, 0x90, 0xa0, 0x92, 0x17, 0x74, 0xf1, 0x7d, 0x6f, 0xc7, 0x10, 0xac, - 0x60, 0xa1, 0x1b, 0x50, 0xb4, 0xfa, 0xbe, 0x4f, 0x1c, 0xc6, 0xbf, 0x2b, 0x17, 0x05, 0xd1, 0xbc, - 0x24, 0x2a, 0xae, 0x26, 0x20, 0xac, 0xe2, 0xe9, 0xff, 0xd0, 0xa0, 0xb8, 0xde, 0xf9, 0x18, 0x4c, - 0x30, 0x1f, 0x6a, 0x30, 0xab, 0x18, 0x7a, 0x0e, 0x05, 0xf7, 0x9d, 0x74, 0xc1, 0x9d, 0xd8, 0x42, - 0x45, 0xdb, 0x63, 0xaa, 0xed, 0xcf, 0xb3, 0x50, 0x56, 0xb0, 0xc2, 0x52, 0x6b, 0x03, 0xb8, 0xf1, - 0xbd, 0x9f, 0xa9, 0x0f, 0x15, 0xbe, 0x9f, 0x94, 0xdb, 0x23, 0xca, 0x6d, 0x17, 0xae, 0xae, 0x3f, - 0x62, 0xc4, 0x77, 0xcc, 0xee, 0xba, 0xc3, 0x28, 0x3b, 0xc0, 0xa4, 0x4d, 0x7c, 0xe2, 0x58, 0x04, - 0x2d, 0x40, 0xce, 0x31, 0x7b, 0x44, 0xb8, 0xa3, 0xd0, 0x28, 0x49, 0xd6, 0xb9, 0x4d, 0xb3, 0x47, - 0xb0, 0x80, 0xa0, 0x3a, 0x14, 0xf8, 0xbf, 0x81, 0x67, 0x5a, 0xa4, 0x92, 0x11, 0x68, 0x73, 0x12, - 0xad, 0xb0, 0x19, 0x01, 0x70, 0x82, 0xa3, 0xff, 0x47, 0x83, 0xb2, 0x10, 0xbf, 0x12, 0x04, 0xae, - 0x45, 0x4d, 0x46, 0x5d, 0xe7, 0x7c, 0xfa, 0x6c, 0xd9, 0x94, 0x12, 0xa5, 0xfd, 0xcf, 0x3d, 0x52, - 0x08, 0xea, 0xf8, 0x92, 0x92, 0xe2, 0xbe, 0x32, 0xc2, 0x1f, 0x8f, 0x49, 0xd4, 0x3f, 0xcc, 0x42, - 0x51, 0xb9, 0x7c, 0xf4, 0x00, 0xb2, 0x9e, 0x6b, 0x4b, 0x9b, 0x27, 0xde, 0x15, 0x5a, 0xae, 0x9d, - 0xa8, 0x31, 0x35, 0x1c, 0xd4, 0xb2, 0xfc, 0x84, 0x73, 0x44, 0x3f, 0xd6, 0xe0, 0x12, 0x49, 0x79, - 0x55, 0x78, 0xa7, 0xb8, 0xbc, 0x31, 0x71, 0x3e, 0x1f, 0x1d, 0x1b, 0x0d, 0x34, 0x1c, 0xd4, 0x2e, - 0x8d, 0x00, 0x47, 0x44, 0xa2, 0x2f, 0x40, 0x96, 0x7a, 0x61, 0x58, 0x97, 0x1a, 0x97, 0xb9, 0x82, - 0xcd, 0x56, 0x70, 0x38, 0xa8, 0x15, 0x9a, 0x2d, 0xb9, 0xc0, 0x60, 0x8e, 0x80, 0xbe, 0x0d, 0x79, - 0xcf, 0xf5, 0x19, 0x6f, 0x36, 0xdc, 0x23, 0x5f, 0x9e, 0x54, 0x47, 0x1e, 0x69, 0x76, 0xcb, 0xf5, - 0x59, 0x52, 0x71, 0xf8, 0x57, 0x80, 0x43, 0xb6, 0xe8, 0x9b, 0x90, 0x73, 0x5c, 0x9b, 0x88, 0x9e, - 0x54, 0x5c, 0xbe, 0x3d, 0x31, 0x7b, 0xd7, 0x26, 0x89, 0xe1, 0xd3, 0x22, 0x05, 0xf8, 0x91, 0x60, - 0xaa, 0xff, 0x56, 0x83, 0x4b, 0xe9, 0x90, 0x48, 0x67, 0x85, 0x76, 0x72, 0x56, 0xc4, 0x89, 0x96, - 0x39, 0x36, 0xd1, 0x1a, 0x90, 0xed, 0x53, 0xbb, 0x92, 0x15, 0x08, 0xaf, 0x49, 0x84, 0xec, 0xdb, - 0xcd, 0xb5, 0xc3, 0x41, 0xed, 0xe5, 0xe3, 0x5e, 0x01, 0xd8, 0x81, 0x47, 0x02, 0xe3, 0xed, 0xe6, - 0x1a, 0xe6, 0xc4, 0xfa, 0xef, 0x35, 0x98, 0x92, 0x7d, 0x1e, 0x3d, 0x80, 0x9c, 0x45, 0x6d, 0x5f, - 0x86, 0xde, 0x73, 0x4e, 0x16, 0xb1, 0xa2, 0xab, 0xcd, 0x35, 0x8c, 0x05, 0x43, 0xf4, 0x10, 0x2e, - 0x92, 0x47, 0x16, 0xf1, 0x98, 0x4c, 0xaf, 0xe7, 0x64, 0x7d, 0x49, 0xb2, 0xbe, 0xb8, 0x2e, 0x98, - 0x61, 0xc9, 0x54, 0x6f, 0x43, 0x5e, 0x20, 0xa0, 0x57, 0x20, 0x43, 0x3d, 0xa1, 0x7e, 0xa9, 0x31, - 0x3f, 0x1c, 0xd4, 0x32, 0xcd, 0x56, 0x3a, 0xb2, 0x32, 0xd4, 0xe3, 0xc3, 0x8c, 0xe7, 0x93, 0x36, - 0x7d, 0x74, 0x97, 0x38, 0x1d, 0xb6, 0x2b, 0xee, 0x37, 0x9f, 0x34, 0xde, 0x96, 0x02, 0xc3, 0x29, - 0x4c, 0xfd, 0x3d, 0x0d, 0x0a, 0x71, 0x58, 0x71, 0xff, 0xf0, 0x48, 0x12, 0xe2, 0xf2, 0x89, 0xd9, - 0x1c, 0x86, 0x05, 0xe4, 0x19, 0x3c, 0x78, 0x0b, 0xa6, 0xc5, 0xfb, 0x8b, 0xe5, 0x76, 0xa5, 0x1b, - 0xaf, 0x45, 0x6d, 0xb8, 0x25, 0xcf, 0x0f, 0x95, 0xdf, 0x38, 0xc6, 0xd6, 0xff, 0x9d, 0x85, 0x99, - 0x4d, 0xc2, 0xbe, 0xef, 0xfa, 0x7b, 0x2d, 0xb7, 0x4b, 0xad, 0x83, 0x73, 0x28, 0x98, 0x6d, 0xc8, - 0xfb, 0xfd, 0x2e, 0x89, 0x8a, 0xe4, 0xca, 0xc4, 0x39, 0xa3, 0xea, 0x8b, 0xfb, 0x5d, 0x92, 0xa4, - 0x26, 0xff, 0x0a, 0x70, 0xc8, 0x1e, 0xdd, 0x86, 0x59, 0x33, 0xb5, 0x79, 0x85, 0xe5, 0xa2, 0x20, - 0x7c, 0x3a, 0x9b, 0x5e, 0xca, 0x02, 0x3c, 0x8a, 0x8b, 0x16, 0xf9, 0xa5, 0x52, 0xd7, 0xe7, 0x05, - 0x8e, 0x4f, 0xaa, 0x5a, 0xa3, 0x14, 0x5e, 0x68, 0x78, 0x86, 0x63, 0x28, 0xba, 0x0e, 0x25, 0x46, - 0x89, 0x1f, 0x41, 0x44, 0x2d, 0xc8, 0x37, 0xca, 0x62, 0xa6, 0x55, 0xce, 0x71, 0x0a, 0x0b, 0x05, - 0x50, 0x08, 0xdc, 0xbe, 0x6f, 0xf1, 0xfc, 0x17, 0xd3, 0x69, 0x71, 0xf9, 0xce, 0xe9, 0xae, 0x22, - 0xae, 0x23, 0x33, 0xbc, 0x1a, 0x6c, 0x45, 0xcc, 0x71, 0x22, 0x47, 0xff, 0xb3, 0x06, 0x73, 0x29, - 0xa2, 0x73, 0x18, 0xfb, 0x76, 0xd2, 0x63, 0xdf, 0xed, 0x53, 0x19, 0x79, 0xcc, 0xe0, 0xf7, 0x03, - 0xb8, 0x9a, 0x42, 0xe3, 0x45, 0x74, 0x8b, 0x99, 0xac, 0x1f, 0xa0, 0x2f, 0xc2, 0x34, 0x2f, 0xa6, - 0x9b, 0xc9, 0xb4, 0x11, 0x2b, 0xbb, 0x29, 0xcf, 0x71, 0x8c, 0xc1, 0x37, 0x0d, 0xf9, 0xa8, 0x49, - 0x5d, 0x47, 0xa4, 0x9c, 0xb2, 0x69, 0x6c, 0xc4, 0x10, 0xac, 0x60, 0xe9, 0x7f, 0xcc, 0x8c, 0x5c, - 0x6a, 0x8b, 0x10, 0x1f, 0xdd, 0x84, 0x19, 0x53, 0x79, 0x4a, 0x0b, 0x2a, 0x9a, 0x08, 0xbe, 0xb9, - 0xe1, 0xa0, 0x36, 0xa3, 0xbe, 0xb1, 0x05, 0x38, 0x8d, 0x87, 0x08, 0x4c, 0x53, 0x4f, 0x6e, 0x67, - 0xe1, 0x95, 0xdd, 0x9c, 0xbc, 0xd0, 0x09, 0xfa, 0xc4, 0xd2, 0x78, 0x2d, 0x8b, 0x59, 0xa3, 0x1a, - 0xe4, 0xdb, 0xdf, 0xb3, 0x9d, 0x28, 0x29, 0x0a, 0xfc, 0x4e, 0xef, 0x7c, 0x7d, 0x6d, 0x33, 0xc0, - 0xe1, 0x39, 0x62, 0x7c, 0xe9, 0xda, 0x22, 0xfe, 0x3e, 0xb5, 0x48, 0xd4, 0x3f, 0xbf, 0x36, 0xa9, - 0x26, 0x92, 0x5e, 0x69, 0xee, 0xc9, 0xda, 0x16, 0xf1, 0xc6, 0x8a, 0x1c, 0xbe, 0x7f, 0x7d, 0xea, - 0xe8, 0xb0, 0x46, 0x37, 0x20, 0xc7, 0xdb, 0x8e, 0xf4, 0xe2, 0xcb, 0x51, 0x21, 0xdc, 0x3e, 0xf0, - 0xc8, 0xe1, 0xa0, 0x96, 0x76, 0x01, 0x3f, 0xc4, 0x02, 0x7d, 0xe2, 0x41, 0x32, 0x2e, 0xb8, 0xd9, - 0x93, 0x5a, 0x66, 0xee, 0x34, 0x2d, 0xf3, 0x37, 0xf9, 0x91, 0xa8, 0xe1, 0xc5, 0x0b, 0xbd, 0x09, - 0x05, 0x9b, 0xfa, 0x7c, 0x71, 0x76, 0x1d, 0x69, 0x68, 0x35, 0x52, 0x76, 0x2d, 0x02, 0x1c, 0xaa, - 0x1f, 0x38, 0x21, 0x40, 0x16, 0xe4, 0xda, 0xbe, 0xdb, 0x93, 0x03, 0xd9, 0xe9, 0x2a, 0x2b, 0x0f, - 0xe2, 0xc4, 0xf8, 0x3b, 0xbe, 0xdb, 0xc3, 0x82, 0x39, 0x7a, 0x08, 0x19, 0xe6, 0x8a, 0xcb, 0x39, - 0x13, 0x11, 0x20, 0x45, 0x64, 0xb6, 0x5d, 0x9c, 0x61, 0x2e, 0x0f, 0xff, 0x20, 0x1d, 0x74, 0x37, - 0x9f, 0x33, 0xe8, 0x92, 0xf0, 0x8f, 0x23, 0x2d, 0x66, 0xcd, 0xcb, 0x82, 0x37, 0x52, 0xb0, 0x93, - 0x9e, 0x39, 0x56, 0xe2, 0x1f, 0xc0, 0x45, 0x33, 0xf4, 0xc9, 0x45, 0xe1, 0x93, 0xaf, 0xf2, 0xf9, - 0x61, 0x25, 0x72, 0xc6, 0xd2, 0x53, 0xfe, 0x46, 0xe5, 0xdb, 0xf1, 0x5f, 0x8c, 0x0c, 0xee, 0xe1, - 0x90, 0x08, 0x4b, 0x76, 0xe8, 0x0d, 0x98, 0x21, 0x8e, 0xb9, 0xd3, 0x25, 0x77, 0xdd, 0x4e, 0x87, - 0x3a, 0x9d, 0xca, 0xd4, 0x82, 0xb6, 0x38, 0xdd, 0xb8, 0x22, 0x75, 0x99, 0x59, 0x57, 0x81, 0x38, - 0x8d, 0x7b, 0x54, 0x87, 0x9b, 0x9e, 0xa0, 0xc3, 0x45, 0x71, 0x5e, 0x38, 0x2e, 0xce, 0xf5, 0x5f, - 0x64, 0x01, 0xa5, 0x3c, 0xc6, 0x6b, 0x6a, 0xc0, 0x57, 0x80, 0x19, 0x47, 0x3d, 0x96, 0x5d, 0xe3, - 0xac, 0xfa, 0x57, 0x6c, 0x7d, 0x1a, 0x9e, 0x96, 0x89, 0x3c, 0x28, 0x31, 0xdf, 0x6c, 0xb7, 0xa9, - 0x25, 0xb4, 0x92, 0x41, 0xff, 0xfa, 0x53, 0x74, 0x10, 0x7f, 0xc0, 0x33, 0x62, 0x77, 0x6c, 0x2b, - 0xd4, 0xca, 0x33, 0x94, 0x72, 0x8a, 0x53, 0x12, 0xd0, 0xbb, 0x1a, 0x94, 0xf9, 0x6c, 0xa1, 0xa2, - 0xc8, 0xcd, 0xfa, 0x2b, 0xcf, 0x2e, 0x16, 0x8f, 0x70, 0x48, 0xd6, 0xbc, 0x51, 0x08, 0x1e, 0x93, - 0xa6, 0xff, 0x5d, 0x83, 0xf9, 0x31, 0x8f, 0xf4, 0xcf, 0xe3, 0x05, 0xb3, 0x0b, 0x79, 0xde, 0x25, - 0xa3, 0x9e, 0xb4, 0x71, 0x2a, 0x5f, 0x27, 0xfd, 0x39, 0x69, 0xe8, 0xfc, 0x2c, 0xc0, 0xa1, 0x10, - 0x7d, 0x09, 0x66, 0x52, 0xbb, 0xd1, 0xc9, 0x0f, 0x06, 0xfa, 0x1f, 0x72, 0x50, 0x8e, 0xf8, 0x06, - 0x5b, 0xfd, 0x5e, 0xcf, 0xf4, 0xcf, 0x63, 0x9c, 0xfd, 0xa9, 0x06, 0xb3, 0x6a, 0x60, 0xd2, 0xf8, - 0x8a, 0x1a, 0xa7, 0xba, 0xa2, 0x30, 0x36, 0xae, 0x4a, 0xd9, 0xb3, 0x9b, 0x69, 0x11, 0x78, 0x54, - 0x26, 0xfa, 0x9d, 0x06, 0xd7, 0x42, 0x29, 0xf2, 0x85, 0x7b, 0x84, 0x42, 0x06, 0xea, 0x59, 0x28, - 0xf5, 0x39, 0xa9, 0xd4, 0xb5, 0x95, 0xa7, 0xc8, 0xc3, 0x4f, 0xd5, 0x06, 0xfd, 0x4a, 0x83, 0x2b, - 0x21, 0xc2, 0xa8, 0x9e, 0xb9, 0x33, 0xd3, 0xf3, 0xb3, 0x52, 0xcf, 0x2b, 0x2b, 0x47, 0x09, 0xc2, - 0x47, 0xcb, 0xd7, 0x1f, 0xc2, 0xe5, 0x96, 0xd9, 0xa1, 0x8e, 0x18, 0xee, 0x36, 0x08, 0xbb, 0xef, - 0xf1, 0x1f, 0xa2, 0x5c, 0x7a, 0x66, 0x27, 0x8c, 0xc0, 0xac, 0xb2, 0xa9, 0x99, 0x1d, 0x82, 0x05, - 0x04, 0xbd, 0x02, 0xf9, 0x2e, 0xed, 0x51, 0x26, 0xe7, 0xc6, 0x38, 0xb2, 0xef, 0xf2, 0x43, 0x1c, - 0xc2, 0x74, 0x13, 0x4a, 0xea, 0xeb, 0xca, 0x8b, 0x78, 0x09, 0x7b, 0x3f, 0x03, 0x53, 0xb2, 0xe5, - 0xa1, 0xeb, 0xca, 0x6e, 0x18, 0x8a, 0xa8, 0x9c, 0xbc, 0x17, 0xa2, 0x4d, 0xb9, 0x95, 0x66, 0x4e, - 0x48, 0x99, 0x3e, 0xa3, 0x5d, 0x23, 0xfc, 0xcf, 0x00, 0x46, 0xd3, 0x61, 0xf7, 0xfd, 0x2d, 0xe6, - 0x53, 0xa7, 0x13, 0xbe, 0x64, 0x28, 0x3b, 0xec, 0xe7, 0x61, 0x8a, 0x38, 0x62, 0xe1, 0x15, 0x83, - 0x43, 0xbe, 0x51, 0x1c, 0x0e, 0x6a, 0x53, 0xeb, 0xe1, 0x11, 0x8e, 0x60, 0x7c, 0xe7, 0xa2, 0x56, - 0xcf, 0xe3, 0xc3, 0x9b, 0x18, 0xae, 0xf2, 0xe1, 0xce, 0xd5, 0x5c, 0xbd, 0xd7, 0x12, 0x03, 0x5d, - 0x0c, 0x8d, 0x30, 0x57, 0xa3, 0xb7, 0x17, 0x05, 0x93, 0x9f, 0xe1, 0x18, 0xaa, 0x13, 0x28, 0x8f, - 0x0e, 0xa1, 0x2f, 0xe0, 0xce, 0x1b, 0xaf, 0x3e, 0x7e, 0x52, 0xbd, 0xf0, 0xc1, 0x93, 0xea, 0x85, - 0x8f, 0x9e, 0x54, 0x2f, 0xbc, 0x3b, 0xac, 0x6a, 0x8f, 0x87, 0x55, 0xed, 0x83, 0x61, 0x55, 0xfb, - 0x68, 0x58, 0xd5, 0xfe, 0x3a, 0xac, 0x6a, 0xbf, 0xfc, 0x5b, 0xf5, 0xc2, 0x37, 0xa6, 0x64, 0xb4, - 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xb2, 0x66, 0xc6, 0xb2, 0xd7, 0x22, 0x00, 0x00, + 0x90, 0xd0, 0x22, 0xe0, 0xb0, 0x80, 0x04, 0x37, 0xae, 0x20, 0x0e, 0x5c, 0xf8, 0x27, 0x38, 0x20, + 0x55, 0x9c, 0x76, 0x85, 0x10, 0x7b, 0xb2, 0xa8, 0x11, 0x20, 0x0e, 0xfc, 0x03, 0xe1, 0x82, 0xde, + 0x9b, 0x37, 0x33, 0x6f, 0xec, 0xa4, 0xa9, 0x9b, 0x34, 0x48, 0xec, 0x9e, 0xea, 0x79, 0xbf, 0xef, + 0xf7, 0xfb, 0x7e, 0x29, 0xbc, 0x65, 0x3a, 0xcc, 0x27, 0xa6, 0x41, 0xdd, 0x7a, 0xf8, 0xab, 0xee, + 0xed, 0x75, 0xea, 0xa6, 0x47, 0x83, 0xba, 0xe5, 0x3a, 0xcc, 0x77, 0xbb, 0x5e, 0xd7, 0x74, 0x48, + 0x7d, 0x7f, 0x69, 0x87, 0x30, 0x73, 0xb9, 0xde, 0x21, 0x0e, 0xf1, 0x4d, 0x46, 0x6c, 0xc3, 0xf3, + 0x5d, 0xe6, 0x22, 0x23, 0xa4, 0xfa, 0x16, 0x75, 0xe5, 0x2f, 0xc3, 0xdb, 0xeb, 0x18, 0x9c, 0xde, + 0x50, 0xe9, 0x0d, 0x49, 0xff, 0xd2, 0xad, 0xe3, 0xe5, 0x05, 0xcc, 0x64, 0x41, 0x7d, 0x7f, 0xc9, + 0xec, 0x7a, 0xbb, 0xe6, 0xd2, 0xa8, 0xa4, 0x97, 0x5e, 0xed, 0x50, 0xb6, 0xdb, 0xdf, 0x31, 0x2c, + 0xb7, 0x57, 0xef, 0xb8, 0x1d, 0xb7, 0x2e, 0x8e, 0x77, 0xfa, 0x6d, 0xf1, 0x25, 0x3e, 0xc4, 0x2f, + 0x89, 0x7e, 0x7d, 0xef, 0x56, 0x20, 0xa4, 0x78, 0xb4, 0x67, 0x5a, 0xbb, 0xd4, 0x21, 0xfe, 0x41, + 0x22, 0xab, 0x47, 0x98, 0x59, 0xdf, 0x1f, 0x17, 0x52, 0x3f, 0x8e, 0xca, 0xef, 0x3b, 0x8c, 0xf6, + 0xc8, 0x18, 0xc1, 0xeb, 0x27, 0x11, 0x04, 0xd6, 0x2e, 0xe9, 0x99, 0x63, 0x74, 0x5f, 0x38, 0x8e, + 0xae, 0xcf, 0x68, 0xb7, 0x4e, 0x1d, 0x16, 0x30, 0x7f, 0x94, 0x48, 0xff, 0xa7, 0x06, 0xa5, 0x15, + 0xdb, 0xf6, 0x49, 0x10, 0x6c, 0xf8, 0x6e, 0xdf, 0x43, 0xef, 0xc0, 0x34, 0xb7, 0xc4, 0x36, 0x99, + 0x59, 0xd1, 0x16, 0xb4, 0xc5, 0xe2, 0xf2, 0x6b, 0x46, 0xc8, 0xd8, 0x50, 0x19, 0x27, 0x3e, 0xe1, + 0xd8, 0xc6, 0xfe, 0x92, 0x71, 0x7f, 0xe7, 0xdb, 0xc4, 0x62, 0xf7, 0x08, 0x33, 0x1b, 0xe8, 0xf1, + 0xa0, 0x76, 0x61, 0x38, 0xa8, 0x41, 0x72, 0x86, 0x63, 0xae, 0xa8, 0x0f, 0xa5, 0x0e, 0x17, 0x75, + 0x8f, 0xf4, 0x76, 0x88, 0x1f, 0x54, 0x32, 0x0b, 0xd9, 0xc5, 0xe2, 0xf2, 0x1b, 0x13, 0xba, 0xdd, + 0xd8, 0x48, 0x78, 0x34, 0x2e, 0x4b, 0x81, 0x25, 0xe5, 0x30, 0xc0, 0x29, 0x31, 0xfa, 0x9f, 0x34, + 0x28, 0xab, 0x96, 0xde, 0xa5, 0x01, 0x43, 0xdf, 0x18, 0xb3, 0xd6, 0x78, 0x36, 0x6b, 0x39, 0xb5, + 0xb0, 0xb5, 0x2c, 0x45, 0x4f, 0x47, 0x27, 0x8a, 0xa5, 0x26, 0xe4, 0x29, 0x23, 0xbd, 0xc8, 0xc4, + 0x37, 0x27, 0x35, 0x51, 0x55, 0xb7, 0x31, 0x23, 0x05, 0xe5, 0x9b, 0x9c, 0x25, 0x0e, 0x39, 0xeb, + 0xef, 0x65, 0x61, 0x4e, 0x45, 0x6b, 0x99, 0xcc, 0xda, 0x3d, 0x07, 0x27, 0xfe, 0x48, 0x83, 0x39, + 0xd3, 0xb6, 0x89, 0xbd, 0x71, 0xc6, 0xae, 0xfc, 0xa4, 0x14, 0xcb, 0xad, 0x4a, 0x73, 0xc7, 0xe3, + 0x02, 0xd1, 0x4f, 0x34, 0x98, 0xf7, 0x49, 0xcf, 0xdd, 0x1f, 0x51, 0x24, 0x7b, 0x7a, 0x45, 0x3e, + 0x25, 0x15, 0x99, 0xc7, 0xe3, 0xfc, 0xf1, 0x51, 0x42, 0xf5, 0x7f, 0x69, 0x70, 0x69, 0xc5, 0xf3, + 0xba, 0x94, 0xd8, 0xdb, 0xee, 0xff, 0x79, 0x36, 0xfd, 0x45, 0x03, 0x94, 0xb6, 0xf5, 0x1c, 0xf2, + 0xc9, 0x4a, 0xe7, 0xd3, 0x5b, 0x13, 0xe7, 0x53, 0x4a, 0xe1, 0x63, 0x32, 0xea, 0xa7, 0x59, 0x98, + 0x4f, 0x23, 0x7e, 0x9c, 0x53, 0xff, 0xbb, 0x9c, 0xfa, 0x55, 0x0e, 0xe6, 0x57, 0xbb, 0xfd, 0x80, + 0x11, 0x3f, 0xa5, 0xe4, 0x8b, 0xf7, 0xc6, 0x0f, 0x34, 0x28, 0x93, 0x76, 0x9b, 0x58, 0x8c, 0xee, + 0x93, 0x33, 0x74, 0x46, 0x45, 0x4a, 0x2d, 0xaf, 0x8f, 0x30, 0xc7, 0x63, 0xe2, 0xd0, 0xf7, 0x61, + 0x2e, 0x3e, 0x6b, 0xb6, 0x1a, 0x5d, 0xd7, 0xda, 0x8b, 0xfc, 0x70, 0x63, 0x52, 0x1d, 0x9a, 0xad, + 0x4d, 0xc2, 0x92, 0x50, 0x58, 0x1f, 0xe5, 0x8b, 0xc7, 0x45, 0xa1, 0x5b, 0x50, 0x62, 0x2e, 0x33, + 0xbb, 0x91, 0xf9, 0xb9, 0x05, 0x6d, 0x31, 0x9b, 0xd4, 0x87, 0x6d, 0x05, 0x86, 0x53, 0x98, 0x68, + 0x19, 0x40, 0x7c, 0xb7, 0xcc, 0x0e, 0x09, 0x2a, 0x79, 0x41, 0x17, 0xdf, 0xf7, 0x76, 0x0c, 0xc1, + 0x0a, 0x16, 0xba, 0x01, 0x45, 0xab, 0xef, 0xfb, 0xc4, 0x61, 0xfc, 0xbb, 0x72, 0x51, 0x10, 0xcd, + 0x4b, 0xa2, 0xe2, 0x6a, 0x02, 0xc2, 0x2a, 0x9e, 0xfe, 0x0f, 0x0d, 0x8a, 0xeb, 0x9d, 0x8f, 0xc0, + 0x04, 0xf3, 0x81, 0x06, 0xb3, 0x8a, 0xa1, 0xe7, 0x50, 0x70, 0xdf, 0x49, 0x17, 0xdc, 0x89, 0x2d, + 0x54, 0xb4, 0x3d, 0xa6, 0xda, 0xfe, 0x2c, 0x0b, 0x65, 0x05, 0x2b, 0x2c, 0xb5, 0x36, 0x80, 0x1b, + 0xdf, 0xfb, 0x99, 0xfa, 0x50, 0xe1, 0xfb, 0x71, 0xb9, 0x3d, 0xa2, 0xdc, 0x76, 0xe1, 0xea, 0xfa, + 0x23, 0x46, 0x7c, 0xc7, 0xec, 0xae, 0x3b, 0x8c, 0xb2, 0x03, 0x4c, 0xda, 0xc4, 0x27, 0x8e, 0x45, + 0xd0, 0x02, 0xe4, 0x1c, 0xb3, 0x47, 0x84, 0x3b, 0x0a, 0x8d, 0x92, 0x64, 0x9d, 0xdb, 0x34, 0x7b, + 0x04, 0x0b, 0x08, 0xaa, 0x43, 0x81, 0xff, 0x1b, 0x78, 0xa6, 0x45, 0x2a, 0x19, 0x81, 0x36, 0x27, + 0xd1, 0x0a, 0x9b, 0x11, 0x00, 0x27, 0x38, 0xfa, 0x7f, 0x34, 0x28, 0x0b, 0xf1, 0x2b, 0x41, 0xe0, + 0x5a, 0xd4, 0x64, 0xd4, 0x75, 0xce, 0xa7, 0xcf, 0x96, 0x4d, 0x29, 0x51, 0xda, 0xff, 0xdc, 0x23, + 0x85, 0xa0, 0x8e, 0x2f, 0x29, 0x29, 0xee, 0x2b, 0x23, 0xfc, 0xf1, 0x98, 0x44, 0xfd, 0x83, 0x2c, + 0x14, 0x95, 0xcb, 0x47, 0x0f, 0x20, 0xeb, 0xb9, 0xb6, 0xb4, 0x79, 0xe2, 0x5d, 0xa1, 0xe5, 0xda, + 0x89, 0x1a, 0x53, 0xc3, 0x41, 0x2d, 0xcb, 0x4f, 0x38, 0x47, 0xf4, 0x43, 0x0d, 0x2e, 0x91, 0x94, + 0x57, 0x85, 0x77, 0x8a, 0xcb, 0x1b, 0x13, 0xe7, 0xf3, 0xd1, 0xb1, 0xd1, 0x40, 0xc3, 0x41, 0xed, + 0xd2, 0x08, 0x70, 0x44, 0x24, 0xfa, 0x1c, 0x64, 0xa9, 0x17, 0x86, 0x75, 0xa9, 0x71, 0x99, 0x2b, + 0xd8, 0x6c, 0x05, 0x87, 0x83, 0x5a, 0xa1, 0xd9, 0x92, 0x0b, 0x0c, 0xe6, 0x08, 0xe8, 0x9b, 0x90, + 0xf7, 0x5c, 0x9f, 0xf1, 0x66, 0xc3, 0x3d, 0xf2, 0xc5, 0x49, 0x75, 0xe4, 0x91, 0x66, 0xb7, 0x5c, + 0x9f, 0x25, 0x15, 0x87, 0x7f, 0x05, 0x38, 0x64, 0x8b, 0xbe, 0x0e, 0x39, 0xc7, 0xb5, 0x89, 0xe8, + 0x49, 0xc5, 0xe5, 0xdb, 0x13, 0xb3, 0x77, 0x6d, 0x92, 0x18, 0x3e, 0x2d, 0x52, 0x80, 0x1f, 0x09, + 0xa6, 0xfa, 0x6f, 0x35, 0xb8, 0x94, 0x0e, 0x89, 0x74, 0x56, 0x68, 0x27, 0x67, 0x45, 0x9c, 0x68, + 0x99, 0x63, 0x13, 0xad, 0x01, 0xd9, 0x3e, 0xb5, 0x2b, 0x59, 0x81, 0xf0, 0x9a, 0x44, 0xc8, 0xbe, + 0xdd, 0x5c, 0x3b, 0x1c, 0xd4, 0x5e, 0x3e, 0xee, 0x15, 0x80, 0x1d, 0x78, 0x24, 0x30, 0xde, 0x6e, + 0xae, 0x61, 0x4e, 0xac, 0xff, 0x5e, 0x83, 0x29, 0xd9, 0xe7, 0xd1, 0x03, 0xc8, 0x59, 0xd4, 0xf6, + 0x65, 0xe8, 0x3d, 0xe7, 0x64, 0x11, 0x2b, 0xba, 0xda, 0x5c, 0xc3, 0x58, 0x30, 0x44, 0x0f, 0xe1, + 0x22, 0x79, 0x64, 0x11, 0x8f, 0xc9, 0xf4, 0x7a, 0x4e, 0xd6, 0x97, 0x24, 0xeb, 0x8b, 0xeb, 0x82, + 0x19, 0x96, 0x4c, 0xf5, 0x36, 0xe4, 0x05, 0x02, 0x7a, 0x05, 0x32, 0xd4, 0x13, 0xea, 0x97, 0x1a, + 0xf3, 0xc3, 0x41, 0x2d, 0xd3, 0x6c, 0xa5, 0x23, 0x2b, 0x43, 0x3d, 0x3e, 0xcc, 0x78, 0x3e, 0x69, + 0xd3, 0x47, 0x77, 0x89, 0xd3, 0x61, 0xbb, 0xe2, 0x7e, 0xf3, 0x49, 0xe3, 0x6d, 0x29, 0x30, 0x9c, + 0xc2, 0xd4, 0xdf, 0xd3, 0xa0, 0x10, 0x87, 0x15, 0xf7, 0x0f, 0x8f, 0x24, 0x21, 0x2e, 0x9f, 0x98, + 0xcd, 0x61, 0x58, 0x40, 0x9e, 0xc1, 0x83, 0xb7, 0x60, 0x5a, 0xbc, 0xbf, 0x58, 0x6e, 0x57, 0xba, + 0xf1, 0x5a, 0xd4, 0x86, 0x5b, 0xf2, 0xfc, 0x50, 0xf9, 0x8d, 0x63, 0x6c, 0xfd, 0xdf, 0x59, 0x98, + 0xd9, 0x24, 0xec, 0xbb, 0xae, 0xbf, 0xd7, 0x72, 0xbb, 0xd4, 0x3a, 0x38, 0x87, 0x82, 0xd9, 0x86, + 0xbc, 0xdf, 0xef, 0x92, 0xa8, 0x48, 0xae, 0x4c, 0x9c, 0x33, 0xaa, 0xbe, 0xb8, 0xdf, 0x25, 0x49, + 0x6a, 0xf2, 0xaf, 0x00, 0x87, 0xec, 0xd1, 0x6d, 0x98, 0x35, 0x53, 0x9b, 0x57, 0x58, 0x2e, 0x0a, + 0xc2, 0xa7, 0xb3, 0xe9, 0xa5, 0x2c, 0xc0, 0xa3, 0xb8, 0x68, 0x91, 0x5f, 0x2a, 0x75, 0x7d, 0x5e, + 0xe0, 0xf8, 0xa4, 0xaa, 0x35, 0x4a, 0xe1, 0x85, 0x86, 0x67, 0x38, 0x86, 0xa2, 0xeb, 0x50, 0x62, + 0x94, 0xf8, 0x11, 0x44, 0xd4, 0x82, 0x7c, 0xa3, 0x2c, 0x66, 0x5a, 0xe5, 0x1c, 0xa7, 0xb0, 0x50, + 0x00, 0x85, 0xc0, 0xed, 0xfb, 0x16, 0xcf, 0x7f, 0x31, 0x9d, 0x16, 0x97, 0xef, 0x9c, 0xee, 0x2a, + 0xe2, 0x3a, 0x32, 0xc3, 0xab, 0xc1, 0x56, 0xc4, 0x1c, 0x27, 0x72, 0xf4, 0x3f, 0x6b, 0x30, 0x97, + 0x22, 0x3a, 0x87, 0xb1, 0x6f, 0x27, 0x3d, 0xf6, 0xdd, 0x3e, 0x95, 0x91, 0xc7, 0x0c, 0x7e, 0xdf, + 0x83, 0xab, 0x29, 0x34, 0x5e, 0x44, 0xb7, 0x98, 0xc9, 0xfa, 0x01, 0xfa, 0x3c, 0x4c, 0xf3, 0x62, + 0xba, 0x99, 0x4c, 0x1b, 0xb1, 0xb2, 0x9b, 0xf2, 0x1c, 0xc7, 0x18, 0x7c, 0xd3, 0x90, 0x8f, 0x9a, + 0xd4, 0x75, 0x44, 0xca, 0x29, 0x9b, 0xc6, 0x46, 0x0c, 0xc1, 0x0a, 0x96, 0xfe, 0xc7, 0xcc, 0xc8, + 0xa5, 0xb6, 0x08, 0xf1, 0xd1, 0x4d, 0x98, 0x31, 0x95, 0xa7, 0xb4, 0xa0, 0xa2, 0x89, 0xe0, 0x9b, + 0x1b, 0x0e, 0x6a, 0x33, 0xea, 0x1b, 0x5b, 0x80, 0xd3, 0x78, 0x88, 0xc0, 0x34, 0xf5, 0xe4, 0x76, + 0x16, 0x5e, 0xd9, 0xcd, 0xc9, 0x0b, 0x9d, 0xa0, 0x4f, 0x2c, 0x8d, 0xd7, 0xb2, 0x98, 0x35, 0xaa, + 0x41, 0xbe, 0xfd, 0x1d, 0xdb, 0x89, 0x92, 0xa2, 0xc0, 0xef, 0xf4, 0xce, 0x57, 0xd7, 0x36, 0x03, + 0x1c, 0x9e, 0x23, 0xc6, 0x97, 0xae, 0x2d, 0xe2, 0xef, 0x53, 0x8b, 0x44, 0xfd, 0xf3, 0x2b, 0x93, + 0x6a, 0x22, 0xe9, 0x95, 0xe6, 0x9e, 0xac, 0x6d, 0x11, 0x6f, 0xac, 0xc8, 0xe1, 0xfb, 0xd7, 0x27, + 0x8e, 0x0e, 0x6b, 0x74, 0x03, 0x72, 0xbc, 0xed, 0x48, 0x2f, 0xbe, 0x1c, 0x15, 0xc2, 0xed, 0x03, + 0x8f, 0x1c, 0x0e, 0x6a, 0x69, 0x17, 0xf0, 0x43, 0x2c, 0xd0, 0x27, 0x1e, 0x24, 0xe3, 0x82, 0x9b, + 0x3d, 0xa9, 0x65, 0xe6, 0x4e, 0xd3, 0x32, 0x7f, 0x9d, 0x1f, 0x89, 0x1a, 0x5e, 0xbc, 0xd0, 0x9b, + 0x50, 0xb0, 0xa9, 0xcf, 0x17, 0x67, 0xd7, 0x91, 0x86, 0x56, 0x23, 0x65, 0xd7, 0x22, 0xc0, 0xa1, + 0xfa, 0x81, 0x13, 0x02, 0x64, 0x41, 0xae, 0xed, 0xbb, 0x3d, 0x39, 0x90, 0x9d, 0xae, 0xb2, 0xf2, + 0x20, 0x4e, 0x8c, 0xbf, 0xe3, 0xbb, 0x3d, 0x2c, 0x98, 0xa3, 0x87, 0x90, 0x61, 0xae, 0xb8, 0x9c, + 0x33, 0x11, 0x01, 0x52, 0x44, 0x66, 0xdb, 0xc5, 0x19, 0xe6, 0xf2, 0xf0, 0x0f, 0xd2, 0x41, 0x77, + 0xf3, 0x39, 0x83, 0x2e, 0x09, 0xff, 0x38, 0xd2, 0x62, 0xd6, 0xbc, 0x2c, 0x78, 0x23, 0x05, 0x3b, + 0xe9, 0x99, 0x63, 0x25, 0xfe, 0x01, 0x5c, 0x34, 0x43, 0x9f, 0x5c, 0x14, 0x3e, 0xf9, 0x32, 0x9f, + 0x1f, 0x56, 0x22, 0x67, 0x2c, 0x3d, 0xe5, 0x6f, 0x54, 0xbe, 0x1d, 0xff, 0xc5, 0xc8, 0xe0, 0x1e, + 0x0e, 0x89, 0xb0, 0x64, 0x87, 0xde, 0x80, 0x19, 0xe2, 0x98, 0x3b, 0x5d, 0x72, 0xd7, 0xed, 0x74, + 0xa8, 0xd3, 0xa9, 0x4c, 0x2d, 0x68, 0x8b, 0xd3, 0x8d, 0x2b, 0x52, 0x97, 0x99, 0x75, 0x15, 0x88, + 0xd3, 0xb8, 0x47, 0x75, 0xb8, 0xe9, 0x09, 0x3a, 0x5c, 0x14, 0xe7, 0x85, 0xe3, 0xe2, 0x5c, 0xff, + 0x79, 0x16, 0x50, 0xca, 0x63, 0xbc, 0xa6, 0x06, 0x7c, 0x05, 0x98, 0x71, 0xd4, 0x63, 0xd9, 0x35, + 0xce, 0xaa, 0x7f, 0xc5, 0xd6, 0xa7, 0xe1, 0x69, 0x99, 0xc8, 0x83, 0x12, 0xf3, 0xcd, 0x76, 0x9b, + 0x5a, 0x42, 0x2b, 0x19, 0xf4, 0xaf, 0x3f, 0x45, 0x07, 0xf1, 0x07, 0x3c, 0x23, 0x76, 0xc7, 0xb6, + 0x42, 0xad, 0x3c, 0x43, 0x29, 0xa7, 0x38, 0x25, 0x01, 0xbd, 0xab, 0x41, 0x99, 0xcf, 0x16, 0x2a, + 0x8a, 0xdc, 0xac, 0xbf, 0xf4, 0xec, 0x62, 0xf1, 0x08, 0x87, 0x64, 0xcd, 0x1b, 0x85, 0xe0, 0x31, + 0x69, 0xfa, 0xdf, 0x35, 0x98, 0x1f, 0xf3, 0x48, 0xff, 0x3c, 0x5e, 0x30, 0xbb, 0x90, 0xe7, 0x5d, + 0x32, 0xea, 0x49, 0x1b, 0xa7, 0xf2, 0x75, 0xd2, 0x9f, 0x93, 0x86, 0xce, 0xcf, 0x02, 0x1c, 0x0a, + 0xd1, 0x97, 0x60, 0x26, 0xb5, 0x1b, 0x9d, 0xfc, 0x60, 0xa0, 0xff, 0x21, 0x07, 0xe5, 0x88, 0x6f, + 0xb0, 0xd5, 0xef, 0xf5, 0x4c, 0xff, 0x3c, 0xc6, 0xd9, 0x1f, 0x6b, 0x30, 0xab, 0x06, 0x26, 0x8d, + 0xaf, 0xa8, 0x71, 0xaa, 0x2b, 0x0a, 0x63, 0xe3, 0xaa, 0x94, 0x3d, 0xbb, 0x99, 0x16, 0x81, 0x47, + 0x65, 0xa2, 0xdf, 0x69, 0x70, 0x2d, 0x94, 0x22, 0x5f, 0xb8, 0x47, 0x28, 0x64, 0xa0, 0x9e, 0x85, + 0x52, 0x9f, 0x91, 0x4a, 0x5d, 0x5b, 0x79, 0x8a, 0x3c, 0xfc, 0x54, 0x6d, 0xd0, 0x2f, 0x35, 0xb8, + 0x12, 0x22, 0x8c, 0xea, 0x99, 0x3b, 0x33, 0x3d, 0x3f, 0x2d, 0xf5, 0xbc, 0xb2, 0x72, 0x94, 0x20, + 0x7c, 0xb4, 0x7c, 0xfd, 0x21, 0x5c, 0x6e, 0x99, 0x1d, 0xea, 0x88, 0xe1, 0x6e, 0x83, 0xb0, 0xfb, + 0x1e, 0xff, 0x21, 0xca, 0xa5, 0x67, 0x76, 0xc2, 0x08, 0xcc, 0x2a, 0x9b, 0x9a, 0xd9, 0x21, 0x58, + 0x40, 0xd0, 0x2b, 0x90, 0xef, 0xd2, 0x1e, 0x65, 0x72, 0x6e, 0x8c, 0x23, 0xfb, 0x2e, 0x3f, 0xc4, + 0x21, 0x4c, 0x37, 0xa1, 0xa4, 0xbe, 0xae, 0xbc, 0x88, 0x97, 0xb0, 0xdf, 0x64, 0x61, 0x4a, 0xb6, + 0x3c, 0x74, 0x5d, 0xd9, 0x0d, 0x43, 0x11, 0x95, 0x93, 0xf7, 0x42, 0xb4, 0x29, 0xb7, 0xd2, 0xcc, + 0x09, 0x29, 0xd3, 0x67, 0xb4, 0x6b, 0x84, 0xff, 0x19, 0xc0, 0x68, 0x3a, 0xec, 0xbe, 0xbf, 0xc5, + 0x7c, 0xea, 0x74, 0xc2, 0x97, 0x0c, 0x65, 0x87, 0xfd, 0x2c, 0x4c, 0x11, 0x47, 0x2c, 0xbc, 0x62, + 0x70, 0xc8, 0x37, 0x8a, 0xc3, 0x41, 0x6d, 0x6a, 0x3d, 0x3c, 0xc2, 0x11, 0x8c, 0xef, 0x5c, 0xd4, + 0xea, 0x79, 0x7c, 0x78, 0x13, 0xc3, 0x55, 0x3e, 0xdc, 0xb9, 0x9a, 0xab, 0xf7, 0x5a, 0x62, 0xa0, + 0x8b, 0xa1, 0x11, 0xe6, 0x6a, 0xf4, 0xf6, 0xa2, 0x60, 0xf2, 0x33, 0x1c, 0x43, 0x05, 0x66, 0x47, + 0xf2, 0x0c, 0x9b, 0x77, 0x88, 0xb9, 0x11, 0xf3, 0x94, 0x50, 0xd4, 0x93, 0x0f, 0xf1, 0x72, 0x3a, + 0x17, 0xad, 0xf8, 0x34, 0xc3, 0x77, 0xfc, 0x00, 0x1f, 0xbd, 0x1b, 0xa4, 0xd8, 0xeb, 0x04, 0xca, + 0xa3, 0xd3, 0xf1, 0x0b, 0x08, 0x86, 0xc6, 0xab, 0x8f, 0x9f, 0x54, 0x2f, 0xbc, 0xff, 0xa4, 0x7a, + 0xe1, 0xc3, 0x27, 0xd5, 0x0b, 0xef, 0x0e, 0xab, 0xda, 0xe3, 0x61, 0x55, 0x7b, 0x7f, 0x58, 0xd5, + 0x3e, 0x1c, 0x56, 0xb5, 0xbf, 0x0e, 0xab, 0xda, 0x2f, 0xfe, 0x56, 0xbd, 0xf0, 0xb5, 0x29, 0xa9, + 0xfd, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x38, 0x39, 0x7a, 0xad, 0x70, 0x23, 0x00, 0x00, } func (m *AddressGroup) Marshal() (dAtA []byte, err error) { @@ -2589,6 +2591,25 @@ func (m *Service) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.GroupAddress != nil { + { + size, err := m.GroupAddress.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.IGMPType != nil { + i -= len(*m.IGMPType) + copy(dAtA[i:], *m.IGMPType) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.IGMPType))) + i-- + dAtA[i] = 0x32 + } if m.ICMPCode != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.ICMPCode)) i-- @@ -3260,6 +3281,14 @@ func (m *Service) Size() (n int) { if m.ICMPCode != nil { n += 1 + sovGenerated(uint64(*m.ICMPCode)) } + if m.IGMPType != nil { + l = len(*m.IGMPType) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.GroupAddress != nil { + l = m.GroupAddress.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -3775,6 +3804,8 @@ func (this *Service) String() string { `EndPort:` + valueToStringGenerated(this.EndPort) + `,`, `ICMPType:` + valueToStringGenerated(this.ICMPType) + `,`, `ICMPCode:` + valueToStringGenerated(this.ICMPCode) + `,`, + `IGMPType:` + valueToStringGenerated(this.IGMPType) + `,`, + `GroupAddress:` + strings.Replace(this.GroupAddress.String(), "IPBlock", "IPBlock", 1) + `,`, `}`, }, "") return s @@ -8122,6 +8153,75 @@ func (m *Service) Unmarshal(dAtA []byte) error { } } m.ICMPCode = &v + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IGMPType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.IGMPType = &s + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GroupAddress", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GroupAddress == nil { + m.GroupAddress = &IPBlock{} + } + if err := m.GroupAddress.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/controlplane/v1beta2/generated.proto b/pkg/apis/controlplane/v1beta2/generated.proto index 6d045370837..76fec045f27 100644 --- a/pkg/apis/controlplane/v1beta2/generated.proto +++ b/pkg/apis/controlplane/v1beta2/generated.proto @@ -378,6 +378,12 @@ message Service { optional int32 icmpType = 4; optional int32 icmpCode = 5; + + // IGMPType and GroupAddress can only be specified, when the Protocol is IGMP. + // +optional + optional string igmpType = 6; + + optional IPBlock groupAddress = 7; } // ServiceReference represents reference to a v1.Service. diff --git a/pkg/apis/controlplane/v1beta2/types.go b/pkg/apis/controlplane/v1beta2/types.go index e56f4ab03a5..a978c010118 100644 --- a/pkg/apis/controlplane/v1beta2/types.go +++ b/pkg/apis/controlplane/v1beta2/types.go @@ -267,6 +267,8 @@ const ( ProtocolSCTP Protocol = "SCTP" // ProtocolICMP is the ICMP protocol. ProtocolICMP Protocol = "ICMP" + + ProtocolIGMP Protocol = "IGMP" ) // Service describes a port to allow traffic on. @@ -289,6 +291,10 @@ type Service struct { // +optional ICMPType *int32 `json:"icmpType,omitempty" protobuf:"bytes,4,opt,name=icmpType"` ICMPCode *int32 `json:"icmpCode,omitempty" protobuf:"bytes,5,opt,name=icmpCode"` + //IGMPType and GroupAddress can only be specified, when the Protocol is IGMP. + // +optional + IGMPType *string `json:"igmpType,omitempty" protobuf:"bytes,6,opt,name=igmpType"` + GroupAddress *IPBlock `json:"groupAddress,omitempty" protobuf:"bytes,7,opt,name=groupAddress"` } // NetworkPolicyPeer describes a peer of NetworkPolicyRules. diff --git a/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go b/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go index b0c7b6d6f0a..eb9213a881b 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go @@ -1400,6 +1400,8 @@ func autoConvert_v1beta2_Service_To_controlplane_Service(in *Service, out *contr out.EndPort = (*int32)(unsafe.Pointer(in.EndPort)) out.ICMPType = (*int32)(unsafe.Pointer(in.ICMPType)) out.ICMPCode = (*int32)(unsafe.Pointer(in.ICMPCode)) + out.IGMPType = (*string)(unsafe.Pointer(in.IGMPType)) + out.GroupAddress = (*controlplane.IPBlock)(unsafe.Pointer(in.GroupAddress)) return nil } @@ -1414,6 +1416,8 @@ func autoConvert_controlplane_Service_To_v1beta2_Service(in *controlplane.Servic out.EndPort = (*int32)(unsafe.Pointer(in.EndPort)) out.ICMPType = (*int32)(unsafe.Pointer(in.ICMPType)) out.ICMPCode = (*int32)(unsafe.Pointer(in.ICMPCode)) + out.IGMPType = (*string)(unsafe.Pointer(in.IGMPType)) + out.GroupAddress = (*IPBlock)(unsafe.Pointer(in.GroupAddress)) return nil } diff --git a/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go b/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go index 5e05b54869d..e3dd802d4e2 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go @@ -952,6 +952,16 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(int32) **out = **in } + if in.IGMPType != nil { + in, out := &in.IGMPType, &out.IGMPType + *out = new(string) + **out = **in + } + if in.GroupAddress != nil { + in, out := &in.GroupAddress, &out.GroupAddress + *out = new(IPBlock) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/controlplane/zz_generated.deepcopy.go b/pkg/apis/controlplane/zz_generated.deepcopy.go index 15a25fbbb70..2fc9b13f61e 100644 --- a/pkg/apis/controlplane/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/zz_generated.deepcopy.go @@ -952,6 +952,16 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(int32) **out = **in } + if in.IGMPType != nil { + in, out := &in.IGMPType, &out.IGMPType + *out = new(string) + **out = **in + } + if in.GroupAddress != nil { + in, out := &in.GroupAddress, &out.GroupAddress + *out = new(IPBlock) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index bd334359756..96923c36b76 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -57,6 +57,7 @@ const ( // According to code in Antrea agent and controller, default protocol is ICMP if protocol is not inputted by users. const ( ICMPProtocolNumber int32 = 1 + IGMPProtocolNumber int32 = 2 TCPProtocolNumber int32 = 6 UDPProtocolNumber int32 = 17 SCTPProtocolNumber int32 = 132 @@ -72,6 +73,7 @@ var ProtocolsToString = map[int32]string{ TCPProtocolNumber: "TCP", UDPProtocolNumber: "UDP", ICMPProtocolNumber: "ICMP", + IGMPProtocolNumber: "IGMP", SCTPProtocolNumber: "SCTP", } @@ -488,6 +490,7 @@ type NetworkPolicyPort struct { // RuleAction describes the action to be applied on traffic matching a rule. type RuleAction string +type IGMPKind string const ( // RuleActionAllow describes that the traffic matching the rule must be allowed. @@ -501,6 +504,9 @@ const ( // RuleActionReject indicates that the traffic matching the rule must be rejected and the // client will receive a response. RuleActionReject RuleAction = "Reject" + + IGMPQuery IGMPKind = "IGMPQuery" + IGMPReport IGMPKind = "IGMPReport" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -609,6 +615,7 @@ type NamespacedName struct { // `ports`. All fields should be used as a standalone field. type NetworkPolicyProtocol struct { ICMP *ICMPProtocol `json:"icmp,omitempty"` + IGMP *IGMPProtocol `json:"igmp,omitempty"` } // ICMPProtocol matches ICMP traffic with specific ICMPType and/or ICMPCode. All @@ -618,3 +625,9 @@ type ICMPProtocol struct { ICMPType *int32 `json:"icmpType,omitempty"` ICMPCode *int32 `json:"icmpCode,omitempty"` } + +type IGMPProtocol struct { + //add igmp type + IGMPType *IGMPKind `json:"igmpType"` + GroupAddress *IPBlock `json:"groupAddress,omitempty"` +} diff --git a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go index 5420a8b2123..152c2fbbcd1 100644 --- a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go @@ -182,6 +182,32 @@ func (in *ICMPProtocol) DeepCopy() *ICMPProtocol { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IGMPProtocol) DeepCopyInto(out *IGMPProtocol) { + *out = *in + if in.IGMPType != nil { + in, out := &in.IGMPType, &out.IGMPType + *out = new(IGMPKind) + **out = **in + } + if in.GroupAddress != nil { + in, out := &in.GroupAddress, &out.GroupAddress + *out = new(IPBlock) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IGMPProtocol. +func (in *IGMPProtocol) DeepCopy() *IGMPProtocol { + if in == nil { + return nil + } + out := new(IGMPProtocol) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPBlock) DeepCopyInto(out *IPBlock) { *out = *in @@ -402,6 +428,11 @@ func (in *NetworkPolicyProtocol) DeepCopyInto(out *NetworkPolicyProtocol) { *out = new(ICMPProtocol) (*in).DeepCopyInto(*out) } + if in.IGMP != nil { + in, out := &in.IGMP, &out.IGMP + *out = new(IGMPProtocol) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apiserver/openapi/zz_generated.openapi.go b/pkg/apiserver/openapi/zz_generated.openapi.go index 649ad329deb..76b6f5eab65 100644 --- a/pkg/apiserver/openapi/zz_generated.openapi.go +++ b/pkg/apiserver/openapi/zz_generated.openapi.go @@ -1823,11 +1823,23 @@ func schema_pkg_apis_controlplane_v1beta2_Service(ref common.ReferenceCallback) Format: "int32", }, }, + "igmpType": { + SchemaProps: spec.SchemaProps{ + Description: "IGMPType and GroupAddress can only be specified, when the Protocol is IGMP.", + Type: []string{"string"}, + Format: "", + }, + }, + "groupAddress": { + SchemaProps: spec.SchemaProps{ + Ref: ref("antrea.io/antrea/pkg/apis/controlplane/v1beta2.IPBlock"), + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, + "antrea.io/antrea/pkg/apis/controlplane/v1beta2.IPBlock", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, } } diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy.go b/pkg/controller/networkpolicy/clusternetworkpolicy.go index 52c67775b93..452c5fb0359 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy.go @@ -380,6 +380,7 @@ func (n *NetworkPolicyController) processClusterNetworkPolicy(cnp *crdv1alpha1.C } var rules []controlplane.NetworkPolicyRule processRules := func(cnpRules []crdv1alpha1.Rule, direction controlplane.Direction) { + klog.Infof("%v", cnpRules) for idx, cnpRule := range cnpRules { services, namedPortExists := toAntreaServicesForCRD(cnpRule.Ports, cnpRule.Protocols) clusterPeers, perNSPeers := splitPeersByScope(cnpRule, direction) diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go index 9f034454453..3d853bc1869 100644 --- a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go +++ b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go @@ -15,6 +15,7 @@ package networkpolicy import ( + "net" "testing" "github.com/stretchr/testify/assert" @@ -76,11 +77,16 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { allowAction := crdv1alpha1.RuleActionAllow dropAction := crdv1alpha1.RuleActionDrop protocolTCP := controlplane.ProtocolTCP + query := crdv1alpha1.IGMPQuery + report := crdv1alpha1.IGMPReport + queryStr := string(query) + reportStr := string(report) selectorA := metav1.LabelSelector{MatchLabels: map[string]string{"foo1": "bar1"}} selectorB := metav1.LabelSelector{MatchLabels: map[string]string{"foo2": "bar2"}} selectorC := metav1.LabelSelector{MatchLabels: map[string]string{"foo3": "bar3"}} selectorD := metav1.LabelSelector{MatchLabels: map[string]string{"internal.antrea.io/service-account": saA.Name}} - + ipb, _ := toAntreaIPBlockForCRD(&crdv1alpha1.IPBlock{"224.0.0.1/32"}) + ipb1,_ := toAntreaIPBlockForCRD(&crdv1alpha1.IPBlock{"225.1.2.3/32"}) labelSelectorA, _ := metav1.LabelSelectorAsSelector(&selectorA) labelSelectorB, _ := metav1.LabelSelectorAsSelector(&selectorB) cgA := crdv1alpha3.ClusterGroup{ @@ -1254,6 +1260,136 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { expectedAppliedToGroups: 1, expectedAddressGroups: 1, }, + { + name: "rule-with-igmp-query", + inputPolicy: &crdv1alpha1.ClusterNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "cnpL", UID: "uidL"}, + Spec: crdv1alpha1.ClusterNetworkPolicySpec{ + AppliedTo: []crdv1alpha1.NetworkPolicyPeer{ + {PodSelector: &selectorA}, + }, + Priority: p10, + Ingress: []crdv1alpha1.Rule{ + { + Action: &dropAction, + Protocols: []crdv1alpha1.NetworkPolicyProtocol { + { + IGMP: &crdv1alpha1.IGMPProtocol{ + IGMPType: &query, + GroupAddresses: []crdv1alpha1.IPBlock{ + { + CIDR: "224.0.0.1/32", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedPolicy: &antreatypes.NetworkPolicy{ + UID: "uidL", + Name: "uidL", + SourceRef: &controlplane.NetworkPolicyReference{ + Type: controlplane.AntreaClusterNetworkPolicy, + Name: "cnpL", + UID: "uidL", + }, + Priority: &p10, + TierPriority: &DefaultTierPriority, + Rules: []controlplane.NetworkPolicyRule{ + { + Direction: controlplane.DirectionIn, + Services: []controlplane.Service{ + { + Protocol: &protocolIGMP, + IGMPType: &queryStr, + GroupAddresses: []controlplane.IPBlock{ + *ipb, + }, + }, + }, + Priority: 0, + Action: &dropAction, + From: controlplane.NetworkPolicyPeer { + IPBlocks: []controlplane.IPBlock{ + {CIDR: controlplane.IPNet{IP: controlplane.IPAddress(net.IPv4zero), PrefixLength: 0}}, + {CIDR: controlplane.IPNet{IP: controlplane.IPAddress(net.IPv6zero), PrefixLength: 0}}, + }, + }, + }, + }, + AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("", &selectorA, nil, nil, nil).NormalizedName)}, + }, + expectedAppliedToGroups: 1, + expectedAddressGroups: 0, + }, + { + name: "rule-with-igmp-report", + inputPolicy: &crdv1alpha1.ClusterNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "cnpL", UID: "uidL"}, + Spec: crdv1alpha1.ClusterNetworkPolicySpec{ + AppliedTo: []crdv1alpha1.NetworkPolicyPeer{ + {PodSelector: &selectorA}, + }, + Priority: p10, + Egress: []crdv1alpha1.Rule{ + { + Action: &dropAction, + Protocols: []crdv1alpha1.NetworkPolicyProtocol { + { + IGMP: &crdv1alpha1.IGMPProtocol{ + IGMPType: &report, + GroupAddresses: []crdv1alpha1.IPBlock{ + { + CIDR: "225.1.2.3/32", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedPolicy: &antreatypes.NetworkPolicy{ + UID: "uidL", + Name: "uidL", + SourceRef: &controlplane.NetworkPolicyReference{ + Type: controlplane.AntreaClusterNetworkPolicy, + Name: "cnpL", + UID: "uidL", + }, + Priority: &p10, + TierPriority: &DefaultTierPriority, + Rules: []controlplane.NetworkPolicyRule{ + { + Direction: controlplane.DirectionOut, + Services: []controlplane.Service{ + { + Protocol: &protocolIGMP, + IGMPType: &reportStr, + GroupAddresses: []controlplane.IPBlock{ + *ipb1, + }, + }, + }, + Priority: 0, + Action: &dropAction, + To: controlplane.NetworkPolicyPeer { + IPBlocks: []controlplane.IPBlock{ + {CIDR: controlplane.IPNet{IP: controlplane.IPAddress(net.IPv4zero), PrefixLength: 0}}, + {CIDR: controlplane.IPNet{IP: controlplane.IPAddress(net.IPv6zero), PrefixLength: 0}}, + }, + }, + }, + }, + AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("", &selectorA, nil, nil, nil).NormalizedName)}, + }, + expectedAppliedToGroups: 1, + expectedAddressGroups: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/controller/networkpolicy/crd_utils.go b/pkg/controller/networkpolicy/crd_utils.go index 5a775708d71..77f9a8a6e99 100644 --- a/pkg/controller/networkpolicy/crd_utils.go +++ b/pkg/controller/networkpolicy/crd_utils.go @@ -61,6 +61,35 @@ func toAntreaServicesForCRD(npPorts []v1alpha1.NetworkPolicyPort, npProtocols [] ICMPCode: npProtocol.ICMP.ICMPCode, }) } + if npProtocol.IGMP != nil { + curProtocol := controlplane.ProtocolIGMP + var groupAddress controlplane.IPBlock + if npProtocol.IGMP.IGMPType != nil { + klog.Infof("npProtocol.IGMP.IGMPType %s", *npProtocol.IGMP.IGMPType) + } + + ipblock, err := toAntreaIPBlockForCRD(npProtocol.IGMP.GroupAddress) + if err == nil { + groupAddress = *ipblock + klog.V(4).Infof("get groupaddress %+v", ipblock.CIDR) + } else { + ipb, _ := toAntreaIPBlockForCRD(&v1alpha1.IPBlock{ + CIDR: "224.0.0.1/32", + }) + groupAddress = *ipb + } + klog.V(4).Infof("get groupaddress %+v %v %+v", npProtocol.IGMP.GroupAddress, + npProtocol.IGMP.IGMPType == nil, groupAddress) + igmpType := "" + if npProtocol.IGMP.IGMPType != nil { + igmpType = string(*npProtocol.IGMP.IGMPType) + } + antreaServices = append(antreaServices, controlplane.Service{ + Protocol: &curProtocol, + IGMPType: &igmpType, + GroupAddress: &groupAddress, + }) + } } return antreaServices, namedPortExists } diff --git a/pkg/controller/networkpolicy/crd_utils_test.go b/pkg/controller/networkpolicy/crd_utils_test.go index be60d3e3bb3..d702fdb07f7 100644 --- a/pkg/controller/networkpolicy/crd_utils_test.go +++ b/pkg/controller/networkpolicy/crd_utils_test.go @@ -30,6 +30,11 @@ import ( ) func TestToAntreaServicesForCRD(t *testing.T) { + ipb, _ := toAntreaIPBlockForCRD(&crdv1alpha1.IPBlock{ + CIDR: "224.0.0.1/32", + }) + query := crdv1alpha1.IGMPQuery + queryStr := string(query) tables := []struct { ports []crdv1alpha1.NetworkPolicyPort protocols []crdv1alpha1.NetworkPolicyProtocol @@ -101,6 +106,29 @@ func TestToAntreaServicesForCRD(t *testing.T) { }, expNamedPortExists: false, }, + { + protocols: []crdv1alpha1.NetworkPolicyProtocol { + { + IGMP: &crdv1alpha1.IGMPProtocol{ + IGMPType: &query, + GroupAddresses: []crdv1alpha1.IPBlock{ + crdv1alpha1.IPBlock{ + CIDR: "224.0.0.1/32", + }, + }, + }, + }, + }, + expServices: []controlplane.Service{ + { + Protocol: &protocolIGMP, + IGMPType: &queryStr, + GroupAddresses: []controlplane.IPBlock{ + *ipb, + }, + }, + }, + }, { protocols: []crdv1alpha1.NetworkPolicyProtocol{ { diff --git a/pkg/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/controller/networkpolicy/networkpolicy_controller_test.go index 3524c5fa749..b7b4f1ca142 100644 --- a/pkg/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/controller/networkpolicy/networkpolicy_controller_test.go @@ -61,6 +61,7 @@ var ( protocolTCP = controlplane.ProtocolTCP protocolICMP = controlplane.ProtocolICMP + protocolIGMP = controlplane.ProtocolIGMP int80 = intstr.FromInt(80) int81 = intstr.FromInt(81) diff --git a/pkg/controller/networkpolicy/validate.go b/pkg/controller/networkpolicy/validate.go index 12cd8551a59..23b161f6be8 100644 --- a/pkg/controller/networkpolicy/validate.go +++ b/pkg/controller/networkpolicy/validate.go @@ -17,6 +17,7 @@ package networkpolicy import ( "encoding/json" "fmt" + "net" "reflect" "regexp" "strconv" @@ -418,6 +419,14 @@ func (v *antreaPolicyValidator) createValidate(curObj interface{}, userInfo auth if !allowed { return reason, allowed } + reason, allowed = v.validateEgressMulticastAddress(egress) + if !allowed { + return reason, allowed + } + reason, allowed = v.validateMulticastIGMP(ingress, egress) + if !allowed { + return reason, allowed + } if err := v.validatePort(ingress, egress); err != nil { return err.Error(), false } @@ -615,6 +624,78 @@ func (v *antreaPolicyValidator) validateTierForPassAction(tier string, ingress, return "", true } +func (v *antreaPolicyValidator) validateEgressMulticastAddress(egressRule []crdv1alpha1.Rule) (string, bool) { + for _, r := range egressRule { + multicast := false + unicast := false + otherSelectors := false + for _, to := range r.To { + toIPAddr, _, err := net.ParseCIDR(to.IPBlock.CIDR) + if err != nil { + return fmt.Sprintf("invalid multicast ip address (to.IPBlock.CIDR): %v", err.Error()), false + } + if toIPAddr.IsMulticast() { + multicast = true + } else{ + unicast = true + } + if to.PodSelector != nil || to.NamespaceSelector != nil || to.Namespaces != nil || + to.ExternalEntitySelector != nil || to.ServiceAccount != nil || to.NodeSelector != nil { + otherSelectors = true + } + if multicast && unicast { + return fmt.Sprintf("can not set multicast ip address and unicast ip address at the same time"), false + } + if multicast && otherSelectors { + return fmt.Sprintf("can not set multicast ip address and selectors at the same time"), false + } + } + } + return "", true +} + +func igmpValidation(protocol crdv1alpha1.NetworkPolicyProtocol) (string, bool) { + if protocol.IGMP != nil { + if protocol.ICMP != nil { + return fmt.Sprintf("icmp can not set with igmp in a single rule"), false + } + if protocol.IGMP.IGMPType != nil && (*protocol.IGMP.IGMPType != crdv1alpha1.IGMPQuery) && (*protocol.IGMP.IGMPType != crdv1alpha1.IGMPReport) { + return fmt.Sprintf("invalid IGMP type: %s, expected are: %s or %s", + *protocol.IGMP.IGMPType, crdv1alpha1.IGMPQuery, crdv1alpha1.IGMPReport), false + } + groupIP, _, err := net.ParseCIDR(protocol.IGMP.GroupAddress.CIDR) + if err != nil { + return fmt.Sprintf("invalid ipaddress %s error %s", + protocol.IGMP.GroupAddress.CIDR, err.Error()), false + } + if groupIP.IsMulticast() == false { + return fmt.Sprintf("ipaddress %+v(cidr %s) is not multicast", + groupIP, protocol.IGMP.GroupAddress.CIDR), false + } + + } + return "", true +} + +func (v *antreaPolicyValidator) validateMulticastIGMP(ingressRules, egressRules []crdv1alpha1.Rule) (string, bool) { + for _, r := range ingressRules { + for _, protocol := range r.Protocols { + reason, allowed := igmpValidation(protocol) + if !allowed { + return reason, allowed + } + } + } + for _, r := range egressRules { + for _, protocol := range r.Protocols { + reason, allowed := igmpValidation(protocol) + if !allowed { + return reason, allowed + } + } + } + return "", true +} // validateFQDNSelectors validates the toFQDN field set in Antrea-native policy egress rules are valid. func (v *antreaPolicyValidator) validateFQDNSelectors(egressRules []crdv1alpha1.Rule) (string, bool) { for _, r := range egressRules { @@ -661,6 +742,14 @@ func (v *antreaPolicyValidator) updateValidate(curObj, oldObj interface{}, userI if !allowed { return reason, allowed } + reason, allowed = v.validateEgressMulticastAddress(egress) + if !allowed { + return reason, allowed + } + reason, allowed = v.validateMulticastIGMP(ingress, egress) + if !allowed { + return reason, allowed + } if err := v.validatePort(ingress, egress); err != nil { return err.Error(), false } @@ -760,6 +849,22 @@ func validateAntreaGroupSpec(s crdv1alpha2.GroupSpec) (string, bool) { if selector+serviceRef+ipBlock+ipBlocks+childGroups > 1 { return errMsg, false } + multicast := false + unicast := false + for _, ipb := range s.IPBlocks { + ipaddr, _, err :=net.ParseCIDR(ipb.CIDR) + if err != nil { + continue + } + if ipaddr.IsMulticast() { + multicast = true + } else { + unicast = true + } + if multicast && unicast { + return "can not set multicast ipaddress together with unicast ip address", false + } + } return "", true }