diff --git a/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml b/build/charts/antrea/templates/crds/clusternetworkpolicy.yaml index a2a90da43c9..54ea3f385ee 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,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -425,6 +438,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -437,6 +451,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/charts/antrea/templates/crds/networkpolicy.yaml b/build/charts/antrea/templates/crds/networkpolicy.yaml index 7eb31cd2190..e087638e3e5 100644 --- a/build/charts/antrea/templates/crds/networkpolicy.yaml +++ b/build/charts/antrea/templates/crds/networkpolicy.yaml @@ -140,6 +140,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -152,6 +153,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -322,6 +335,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -334,6 +348,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index 0957b19a79c..7a7dab36b26 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -1021,6 +1021,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1033,6 +1034,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -1234,6 +1247,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1246,6 +1260,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: @@ -1920,6 +1948,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1932,6 +1961,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -2102,6 +2143,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -2114,6 +2156,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index aa2c7efdd6a..503cd73c6a9 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -1021,6 +1021,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1033,6 +1034,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -1234,6 +1247,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1246,6 +1260,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: @@ -1920,6 +1948,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1932,6 +1961,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -2102,6 +2143,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -2114,6 +2156,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index 47e83a0ec7c..f74831cd4f6 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -1021,6 +1021,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1033,6 +1034,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -1234,6 +1247,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1246,6 +1260,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: @@ -1920,6 +1948,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1932,6 +1961,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -2102,6 +2143,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -2114,6 +2156,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index 7ea65444221..6979ba4f7b6 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -1034,6 +1034,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1046,6 +1047,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -1247,6 +1260,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1259,6 +1273,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: @@ -1933,6 +1961,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1945,6 +1974,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -2115,6 +2156,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -2127,6 +2169,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 3ec7336f8cc..03eb7cb7e39 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -1021,6 +1021,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1033,6 +1034,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -1234,6 +1247,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1246,6 +1260,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: @@ -1920,6 +1948,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -1932,6 +1961,18 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP query (0x11) is valid igmpType in ingress rules. + enum: [ 0x11 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 from: type: array items: @@ -2102,6 +2143,7 @@ spec: type: object oneOf: - required: [icmp] + - required: [igmp] properties: icmp: type: object @@ -2114,6 +2156,20 @@ spec: type: integer minimum: 0 maximum: 255 + igmp: + type: object + properties: + igmpType: + type: integer + # Only IGMP reports are igmpType in egress rules, + # 0x12 is IGMP report V1, 0x16 is IGMP report v2, 0x22 is IGMP report v3. + # It will match all IGMP report types if igmpType is not set. + enum: [ 0x12, 0x16, 0x22 ] + groupAddress: + type: string + oneOf: + - format: ipv4 + - format: ipv6 to: type: array items: diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index 00a162044de..a0935feb945 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -55,6 +55,7 @@ import ( "antrea.io/antrea/pkg/agent/secondarynetwork/cnipodcache" "antrea.io/antrea/pkg/agent/secondarynetwork/podwatch" "antrea.io/antrea/pkg/agent/stats" + agenttypes "antrea.io/antrea/pkg/agent/types" crdinformers "antrea.io/antrea/pkg/client/informers/externalversions" "antrea.io/antrea/pkg/controller/externalippool" "antrea.io/antrea/pkg/features" @@ -126,6 +127,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), @@ -133,7 +135,7 @@ func run(o *Options) error { features.DefaultFeatureGate.Enabled(features.FlowExporter), o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, - features.DefaultFeatureGate.Enabled(features.Multicast), + multicastEnabled, features.DefaultFeatureGate.Enabled(features.TrafficControl), ) @@ -172,7 +174,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) } @@ -311,6 +313,7 @@ func run(o *Options) error { antreaPolicyEnabled, antreaProxyEnabled, statusManagerEnabled, + multicastEnabled, loggingEnabled, asyncRuleDeleteInterval, o.config.DNSServerOverride, @@ -506,7 +509,6 @@ func run(o *Options) error { go nodeRouteController.Run(stopCh) go networkPolicyController.Run(stopCh) - // Initialize the NPL agent. if enableNodePortLocal { nplController, err := npl.InitializeNPLAgent( @@ -597,11 +599,15 @@ 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") } + var validator agenttypes.MulticastValidator + if antreaPolicyEnabled { + validator = networkPolicyController + } mcastController := multicast.NewMulticastController( ofClient, v4GroupIDAllocator, @@ -611,7 +617,8 @@ func run(o *Options) error { sets.NewString(append(o.config.Multicast.MulticastInterfaces, nodeConfig.NodeTransportInterfaceName)...), ovsBridgeClient, podUpdateChannel, - o.igmpQueryInterval) + o.igmpQueryInterval, + validator) if err := mcastController.Initialize(); err != nil { return err } diff --git a/pkg/agent/controller/networkpolicy/cache.go b/pkg/agent/controller/networkpolicy/cache.go index e0fc264f260..9f38fa375f6 100644 --- a/pkg/agent/controller/networkpolicy/cache.go +++ b/pkg/agent/controller/networkpolicy/cache.go @@ -37,11 +37,12 @@ import ( ) const ( - RuleIDLength = 16 - appliedToGroupIndex = "appliedToGroup" - addressGroupIndex = "addressGroup" - policyIndex = "policy" - toServicesIndex = "toServices" + RuleIDLength = 16 + appliedToGroupIndex = "appliedToGroup" + addressGroupIndex = "addressGroup" + policyIndex = "policy" + toServicesIndex = "toServices" + toIGMPReportGroupAddressIndex = "toIGMPReportGroupAddress" ) // rule is the struct stored in ruleCache, it contains necessary information @@ -95,6 +96,35 @@ type rule struct { EnableLogging bool } +func (r *rule) Less(r2 *rule) bool { + // priorities for rule r + tierPriority1, policyPriority1 := int32(0), float64(0) + // priorities for rule r2 + tierPriority2, policyPriority2 := int32(0), float64(0) + + if r.TierPriority != nil { + tierPriority1 = *r.TierPriority + } + if r.PolicyPriority != nil { + policyPriority1 = *r.PolicyPriority + } + if r2.TierPriority != nil { + tierPriority2 = *r2.TierPriority + } + if r2.PolicyPriority != nil { + policyPriority2 = *r2.PolicyPriority + } + + // compare two rules' priorities + if tierPriority1 == tierPriority2 { + if policyPriority1 == policyPriority2 { + return r.Priority > r2.Priority + } + return policyPriority1 > policyPriority2 + } + return tierPriority1 > tierPriority2 +} + // hashRule calculates a string based on the rule's content. func hashRule(r *rule) string { hash := sha1.New() // #nosec G401: not used for security purposes @@ -133,6 +163,17 @@ func (r *CompletedRule) isAntreaNetworkPolicyRule() bool { return r.SourceRef.Type != v1beta.K8sNetworkPolicy } +func (r *CompletedRule) isIGMPEgressPolicyRule() bool { + if r.Direction == v1beta.DirectionOut { + for _, svc := range r.Services { + if svc.Protocol != nil && *svc.Protocol == v1beta.ProtocolIGMP { + return true + } + } + } + return false +} + // ruleCache caches Antrea AddressGroups, AppliedToGroups and NetworkPolicies, // can construct complete rules that can be used by reconciler to enforce. type ruleCache struct { @@ -335,11 +376,34 @@ func toServicesIndexFunc(obj interface{}) ([]string, error) { return toSvcNamespacedName.UnsortedList(), nil } +// toIGMPReportGroupAddressIndexFunc knows how to get IGMP report groupAddresses of a *rule +// It's provided to cache.Indexer to build an index of NetworkPolicy. +func toIGMPReportGroupAddressIndexFunc(obj interface{}) ([]string, error) { + rule := obj.(*rule) + mcastGroupAddresses := sets.String{} + if rule.Direction == v1beta.DirectionOut { + for _, svc := range rule.Services { + if svc.Protocol != nil && *svc.Protocol == v1beta.ProtocolIGMP && svc.IGMPType == nil || + svc.IGMPType != nil && (*svc.IGMPType == crdv1alpha1.IGMPReportV1 || *svc.IGMPType == crdv1alpha1.IGMPReportV2 || *svc.IGMPType == crdv1alpha1.IGMPReportV3) { + mcastGroupAddresses.Insert(svc.GroupAddress) + } + } + } + + return mcastGroupAddresses.UnsortedList(), nil +} + // newRuleCache returns a new *ruleCache. func newRuleCache(dirtyRuleHandler func(string), podUpdateSubscriber channel.Subscriber, serviceGroupIDUpdate <-chan string) *ruleCache { rules := cache.NewIndexer( ruleKeyFunc, - cache.Indexers{addressGroupIndex: addressGroupIndexFunc, appliedToGroupIndex: appliedToGroupIndexFunc, policyIndex: policyIndexFunc, toServicesIndex: toServicesIndexFunc}, + cache.Indexers{ + addressGroupIndex: addressGroupIndexFunc, + appliedToGroupIndex: appliedToGroupIndexFunc, + policyIndex: policyIndexFunc, + toServicesIndex: toServicesIndexFunc, + toIGMPReportGroupAddressIndex: toIGMPReportGroupAddressIndexFunc, + }, ) cache := &ruleCache{ appliedToSetByGroup: make(map[string]v1beta.GroupMemberSet), diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go index c0c06ccfd70..ec3564369a3 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go @@ -17,6 +17,7 @@ package networkpolicy import ( "context" "fmt" + "net" "reflect" "sync" "time" @@ -24,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + apitypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/util/workqueue" @@ -36,6 +38,7 @@ import ( proxytypes "antrea.io/antrea/pkg/agent/proxy/types" "antrea.io/antrea/pkg/agent/types" "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" "antrea.io/antrea/pkg/querier" "antrea.io/antrea/pkg/util/channel" ) @@ -75,6 +78,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 @@ -119,6 +124,7 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, antreaPolicyEnabled bool, antreaProxyEnabled bool, statusManagerEnabled bool, + multicastEnabled bool, loggingEnabled bool, asyncRuleDeleteInterval time.Duration, dnsServerOverride string, @@ -132,18 +138,22 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, antreaPolicyEnabled: antreaPolicyEnabled, antreaProxyEnabled: antreaProxyEnabled, statusManagerEnabled: statusManagerEnabled, + multicastEnabled: multicastEnabled, loggingEnabled: loggingEnabled, } + if antreaPolicyEnabled { var err error if c.fqdnController, err = newFQDNController(ofClient, idAllocator, dnsServerOverride, c.enqueueRule, v4Enabled, v6Enabled); 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.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters, + v4Enabled, v6Enabled, antreaPolicyEnabled, multicastEnabled) c.ruleCache = newRuleCache(c.enqueueRule, podUpdateSubscriber, groupIDUpdates) if statusManagerEnabled { c.statusManager = newStatusController(antreaClientGetter, nodeName, c.ruleCache) @@ -474,6 +484,55 @@ func (c *Controller) Run(stopCh <-chan struct{}) { <-stopCh } +func (c *Controller) matchIGMPType(r *rule, igmpType uint8, groupAddress string) bool { + for _, s := range r.Services { + if (s.IGMPType == nil || uint8(*s.IGMPType) == igmpType) && (s.GroupAddress == "" || s.GroupAddress == groupAddress) { + return true + } + } + return false +} + +// Validate checks if there is rule to drop or allow IGMP report from a Pod to a group Address, and returns multicast +// NetworkPolicy Information +func (c *Controller) Validate(podName, podNamespace string, groupAddress net.IP, igmpType uint8) (types.McastNPValidationItem, error) { + var ruleTypePtr *v1beta2.NetworkPolicyType + action, uuid, ruleName := v1alpha1.RuleActionAllow, apitypes.UID(""), "" + member := &v1beta2.GroupMember{ + Pod: &v1beta2.PodReference{ + Name: podName, + Namespace: podNamespace, + }, + } + + objects, _ := c.ruleCache.rules.ByIndex(toIGMPReportGroupAddressIndex, groupAddress.String()) + objects2, _ := c.ruleCache.rules.ByIndex(toIGMPReportGroupAddressIndex, "") + objects = append(objects, objects2...) + var matchedRule *rule + for _, obj := range objects { + rule := obj.(*rule) + groupMembers, anyExists := c.ruleCache.unionAppliedToGroups(rule.AppliedToGroups) + if !anyExists { + continue + } + if groupMembers.Has(member) && (matchedRule == nil || matchedRule.Less(rule)) && + c.matchIGMPType(rule, igmpType, groupAddress.String()) { + matchedRule = rule + } + } + + if matchedRule != nil { + ruleTypePtr = new(v1beta2.NetworkPolicyType) + action, uuid, *ruleTypePtr, ruleName = *matchedRule.Action, matchedRule.PolicyUID, matchedRule.SourceRef.Type, matchedRule.Name + } + return types.McastNPValidationItem{ + RuleAction: action, + UUID: uuid, + NPType: ruleTypePtr, + Name: ruleName, + }, nil +} + func (c *Controller) enqueueRule(ruleID string) { c.queue.Add(ruleID) } diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go index b7f54207c57..12b911b7924 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go @@ -16,6 +16,7 @@ package networkpolicy import ( "fmt" + "net" "strings" "sync" "testing" @@ -36,6 +37,7 @@ import ( proxytypes "antrea.io/antrea/pkg/agent/proxy/types" agenttypes "antrea.io/antrea/pkg/agent/types" "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" "antrea.io/antrea/pkg/client/clientset/versioned" "antrea.io/antrea/pkg/client/clientset/versioned/fake" "antrea.io/antrea/pkg/querier" @@ -58,8 +60,7 @@ func newTestController() (*Controller, *fake.Clientset, *mockReconciler) { ch2 := make(chan string, 100) 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) + controller, _ := NewNetworkPolicyController(&antreaClientGetter{clientset}, nil, nil, "node1", podUpdateChannel, groupCounters, ch2, true, true, true, false, true, testAsyncDeleteInterval, "8.8.8.8:53", true, false) reconciler := newMockReconciler() controller.reconciler = reconciler controller.antreaPolicyLogger = nil @@ -619,3 +620,75 @@ func TestNetworkPolicyMetrics(t *testing.T) { waitForReconcilerDeleted() checkNetworkPolicyMetrics() } + +func TestValidate(t *testing.T) { + controller, _, _ := newTestController() + igmpType := int32(0x12) + actionAllow, actionDrop := v1alpha1.RuleActionAllow, v1alpha1.RuleActionDrop + appliedToGroup := v1beta2.NewGroupMemberSet() + appliedToGroup.Insert() + tierPriority01 := int32(100) + policyPriority01 := float64(10) + proto := v1beta2.ProtocolIGMP + rule1 := &rule{ + ID: "rule1", + Name: "rule01", + SourceRef: &v1beta2.NetworkPolicyReference{ + Type: v1beta2.AntreaClusterNetworkPolicy, + }, + Services: []v1beta2.Service{ + { + Protocol: &proto, + IGMPType: &igmpType, + GroupAddress: "225.1.2.3", + }, + }, + Action: &actionAllow, + AppliedToGroups: []string{"appliedToGroup01"}, + Priority: 0, + TierPriority: &tierPriority01, + PolicyPriority: &policyPriority01, + Direction: v1beta2.DirectionOut, + } + rule2 := &rule{ + ID: "rule2", + Name: "rule02", + SourceRef: &v1beta2.NetworkPolicyReference{ + Type: v1beta2.AntreaClusterNetworkPolicy, + }, + Services: []v1beta2.Service{ + { + Protocol: &proto, + IGMPType: &igmpType, + GroupAddress: "", + }, + }, + Action: &actionDrop, + AppliedToGroups: []string{"appliedToGroup01"}, + Priority: 1, + TierPriority: &tierPriority01, + PolicyPriority: &policyPriority01, + Direction: v1beta2.DirectionOut, + } + groups := v1beta2.GroupMemberSet{} + groupAddress1, groupAddress2 := "225.1.2.3", "225.1.2.4" + + groups["ns1/pod1"] = newAppliedToGroupMember("pod1", "ns1") + controller.ruleCache.appliedToSetByGroup["appliedToGroup01"] = groups + controller.ruleCache.rules.Add(rule1) + controller.ruleCache.rules.Add(rule2) + item, err := controller.Validate("pod1", "ns1", net.ParseIP(groupAddress1), 0x12) + if err != nil { + t.Fatalf("failed to validate group %s %v", groupAddress1, err) + } + if item.RuleAction != v1alpha1.RuleActionAllow { + t.Fatalf("groupAddress %s expect %v, but got %v", groupAddress1, v1alpha1.RuleActionAllow, item.RuleAction) + } + item, err = controller.Validate("pod1", "ns1", net.ParseIP(groupAddress2), 0x12) + if err != nil { + t.Fatalf("failed to validate group %s %+v", groupAddress2, err) + } + if item.RuleAction != v1alpha1.RuleActionDrop { + t.Fatalf("groupAddress %s expect %v, but got %v", groupAddress2, v1alpha1.RuleActionDrop, item.RuleAction) + } +} diff --git a/pkg/agent/controller/networkpolicy/packetin.go b/pkg/agent/controller/networkpolicy/packetin.go index cea0ea60e89..634520f3c75 100644 --- a/pkg/agent/controller/networkpolicy/packetin.go +++ b/pkg/agent/controller/networkpolicy/packetin.go @@ -78,7 +78,7 @@ func getMatchRegField(matchers *ofctrl.Matchers, field *binding.RegField) *ofctr func getMatch(matchers *ofctrl.Matchers, tableID uint8, disposition uint32) *ofctrl.MatchField { // Get match from CNPDenyConjIDReg if disposition is Drop or Reject. if disposition == openflow.DispositionDrop || disposition == openflow.DispositionRej { - return getMatchRegField(matchers, openflow.CNPDenyConjIDField) + return getMatchRegField(matchers, openflow.CNPConjIDField) } // Get match from ingress/egress reg if disposition is Allow or Pass. for _, table := range append(openflow.GetAntreaPolicyEgressTables(), openflow.EgressRuleTable) { diff --git a/pkg/agent/controller/networkpolicy/reconciler.go b/pkg/agent/controller/networkpolicy/reconciler.go index 16cbf78484b..09fb5c1d191 100644 --- a/pkg/agent/controller/networkpolicy/reconciler.go +++ b/pkg/agent/controller/networkpolicy/reconciler.go @@ -40,6 +40,16 @@ var ( baselineTierPriority int32 = 253 ) +type ruleType int + +const ( + unicast ruleType = 0 + igmp ruleType = 1 + multicast ruleType = 2 + + igmpServicesKey = "igmp-services-key" +) + // Reconciler is an interface that knows how to reconcile the desired state of // CompletedRule with the actual state of Openflow entries. type Reconciler interface { @@ -156,6 +166,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 +178,7 @@ func newLastRealized(rule *CompletedRule) *lastRealized { podIPs: nil, fqdnIPAddresses: nil, groupIDAddresses: nil, + groupAddresses: nil, } } @@ -209,6 +222,9 @@ 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 + + // multicastEnabled indicates whether multicast is enabled + multicastEnabled bool } // newReconciler returns a new *reconciler. @@ -220,6 +236,7 @@ func newReconciler(ofClient openflow.Client, v4Enabled bool, v6Enabled bool, antreaPolicyEnabled bool, + multicastEnabled bool, ) *reconciler { priorityAssigners := map[uint8]*tablePriorityAssigner{} if antreaPolicyEnabled { @@ -233,6 +250,18 @@ func newReconciler(ofClient openflow.Client, assigner: newPriorityAssigner(false), } } + if multicastEnabled { + for _, table := range openflow.GetAntreaMulticastEgressTables() { + priorityAssigners[table.GetID()] = &tablePriorityAssigner{ + assigner: newPriorityAssigner(false), + } + } + for _, table := range openflow.GetAntreaIGMPIngressTables() { + priorityAssigners[table.GetID()] = &tablePriorityAssigner{ + assigner: newPriorityAssigner(false), + } + } + } } reconciler := &reconciler{ ofClient: ofClient, @@ -242,6 +271,7 @@ func newReconciler(ofClient openflow.Client, priorityAssigners: priorityAssigners, fqdnController: fqdnController, groupCounters: groupCounters, + multicastEnabled: multicastEnabled, } // Check if ofClient is nil or not to be compatible with unit tests. if ofClient != nil { @@ -267,7 +297,9 @@ func (r *reconciler) Reconcile(rule *CompletedRule) error { value, exists := r.lastRealizeds.Load(rule.ID) ruleTable := r.getOFRuleTable(rule) priorityAssigner, _ := r.priorityAssigners[ruleTable] - if rule.isAntreaNetworkPolicyRule() { + // IGMP Egress policy is enforced in userspace via packet-in message, there won't be OpenFlow + // rules created for such rules. Therefore, assigning priority is not required. + if rule.isAntreaNetworkPolicyRule() && !rule.isIGMPEgressPolicyRule() { // For CNP, only release priorityMutex after rule is installed on OVS. Otherwise, // priority re-assignments for flows that have already been assigned priorities but // not yet installed on OVS will be missed. @@ -290,32 +322,71 @@ func (r *reconciler) Reconcile(rule *CompletedRule) error { return ofRuleInstallErr } +func (r *reconciler) getRuleType(rule *CompletedRule) ruleType { + if !r.multicastEnabled { + return unicast + } + for _, service := range rule.Services { + if service.Protocol != nil && *service.Protocol == v1beta2.ProtocolIGMP { + return igmp + } + } + + for _, ipBlock := range rule.To.IPBlocks { + ipAddr := ip.IPNetToNetIPNet(&ipBlock.CIDR) + if ipAddr.IP.IsMulticast() { + 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() { + rType := r.getRuleType(rule) + var ruleTables []*openflow.Table + var tableID uint8 + switch rType { + case unicast: + if !rule.isAntreaNetworkPolicyRule() { + if rule.Direction == v1beta2.DirectionIn { + return openflow.IngressRuleTable.GetID() + } + return openflow.EgressRuleTable.GetID() + } if rule.Direction == v1beta2.DirectionIn { - return openflow.IngressRuleTable.GetID() + ruleTables = openflow.GetAntreaPolicyIngressTables() + } else { + ruleTables = openflow.GetAntreaPolicyEgressTables() + } + if *rule.TierPriority != baselineTierPriority { + return ruleTables[0].GetID() + } + tableID = ruleTables[1].GetID() + case igmp: + if rule.Direction == v1beta2.DirectionIn { + ruleTables = openflow.GetAntreaIGMPIngressTables() + tableID = ruleTables[0].GetID() + } + case multicast: + // Multicast NetworkPolicy only supports egress so far, we leave tableID as 0 + // for ingress rules for multicast, later we will return empty flows for it. + if rule.Direction == v1beta2.DirectionOut { + ruleTables = openflow.GetAntreaMulticastEgressTables() + tableID = ruleTables[0].GetID() } - 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() } - return ruleTables[1].GetID() + return tableID } // getOFPriority retrieves the OFPriority for the input CompletedRule to be installed, // and re-arranges installed priorities on OVS if necessary. func (r *reconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *tablePriorityAssigner) (*uint16, bool, error) { - if !rule.isAntreaNetworkPolicyRule() { + // IGMP Egress policy is enforced in userspace via packet-in message, there won't be OpenFlow + // rules created for such rules. Therefore, assigning priority is not required. + if !rule.isAntreaNetworkPolicyRule() || rule.isIGMPEgressPolicyRule() { klog.V(2).Infof("Assigning default priority for k8s NetworkPolicy.") return nil, true, nil } @@ -398,7 +469,9 @@ func (r *reconciler) BatchReconcile(rules []*CompletedRule) error { func (r *reconciler) registerOFPriorities(rules []*CompletedRule) error { prioritiesToRegister := map[uint8][]types.Priority{} for _, rule := range rules { - if rule.isAntreaNetworkPolicyRule() { + // IGMP Egress policy is enforced in userspace via packet-in message, there won't be OpenFlow + // rules created for such rules. Therefore, assigning priority is not required. + if rule.isAntreaNetworkPolicyRule() && !rule.isIGMPEgressPolicyRule() { ruleTable := r.getOFRuleTable(rule) p := types.Priority{ TierPriority: *rule.TierPriority, @@ -446,13 +519,32 @@ func (r *reconciler) computeOFRulesForAdd(rule *CompletedRule, ofPriority *uint1 r.lastRealizeds.Store(rule.ID, lastRealized) ofRuleByServicesMap := map[servicesKey]*types.PolicyRule{} - + isIGMP := r.isIGMPRule(rule) + if isIGMP && rule.Direction == v1beta2.DirectionIn { + // IGMP query + ofPorts := r.getOFPorts(rule.TargetMembers) + lastRealized.podOFPorts[igmpServicesKey] = ofPorts + ofRuleByServicesMap[igmpServicesKey] = &types.PolicyRule{ + Direction: v1beta2.DirectionIn, + To: ofPortsToOFAddresses(ofPorts), + Service: rule.Services, + Action: rule.Action, + Name: rule.Name, + Priority: ofPriority, + TableID: table, + PolicyRef: rule.SourceRef, + EnableLogging: rule.EnableLogging, + } + return ofRuleByServicesMap, lastRealized + } else if isIGMP { + // IGMP report + return ofRuleByServicesMap, lastRealized + } 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) @@ -608,7 +700,45 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule, for svcKey, ofID := range lastRealized.ofIDs { staleOFIDs[svcKey] = ofID } - + isIGMP := r.isIGMPRule(newRule) + if isIGMP && newRule.Direction == v1beta2.DirectionIn { + // IGMP query + newOFPorts := r.getOFPorts(newRule.TargetMembers) + ofID, exists := lastRealized.ofIDs[igmpServicesKey] + // Install a new Openflow rule if this group doesn't exist, otherwise do incremental update. + if !exists { + ofRule := &types.PolicyRule{ + Direction: v1beta2.DirectionIn, + To: ofPortsToOFAddresses(newOFPorts), + Service: newRule.Services, + Action: newRule.Action, + Priority: ofPriority, + FlowID: ofID, + TableID: table, + PolicyRef: newRule.SourceRef, + EnableLogging: newRule.EnableLogging, + } + err := r.idAllocator.allocateForRule(ofRule) + if err != nil { + return err + } + if err = r.installOFRule(ofRule); err != nil { + return err + } + lastRealized.ofIDs[igmpServicesKey] = ofRule.FlowID + } else { + addedTo := ofPortsToOFAddresses(newOFPorts.Difference(lastRealized.podOFPorts[igmpServicesKey])) + deletedTo := ofPortsToOFAddresses(lastRealized.podOFPorts[igmpServicesKey].Difference(newOFPorts)) + if err := r.updateOFRule(ofID, nil, addedTo, nil, deletedTo, ofPriority); err != nil { + return err + } + } + lastRealized.podOFPorts[igmpServicesKey] = newOFPorts + return nil + } else if isIGMP { + // IGMP report + 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 { @@ -866,6 +996,15 @@ func (r *reconciler) Forget(ruleID string) error { return nil } +func (r *reconciler) isIGMPRule(rule *CompletedRule) bool { + isIGMP := false + if len(rule.Services) > 0 && (rule.Services[0].Protocol != nil) && + (*rule.Services[0].Protocol == v1beta2.ProtocolIGMP) { + isIGMP = true + } + return 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 0541264eb44..a25e4056e53 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, groupCounters, v4Enabled, v6Enabled, true, false) 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/32") 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: nil, + TargetMembers: appliedToGroup1, + }, + []*types.PolicyRule{ + { + Direction: v1beta2.DirectionOut, + From: ipsToOFAddresses(sets.NewString("2.2.2.2")), + To: []types.Address{ + openflow.NewIPNetAddress(*ipNet5), + }, + Service: nil, + PolicyRef: &np1, + }, + }, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/agent/controller/traceflow/packetin.go b/pkg/agent/controller/traceflow/packetin.go index f903499468e..65b23adaaca 100644 --- a/pkg/agent/controller/traceflow/packetin.go +++ b/pkg/agent/controller/traceflow/packetin.go @@ -208,7 +208,7 @@ func (c *Controller) parsePacketIn(pktIn *ofctrl.PacketIn) (*crdv1alpha1.Tracefl // Get drop table. if tableID == openflow.EgressMetricTable.GetID() || tableID == openflow.IngressMetricTable.GetID() { ob := getNetworkPolicyObservation(tableID, tableID == openflow.IngressMetricTable.GetID()) - if match := getMatchRegField(matchers, openflow.CNPDenyConjIDField); match != nil { + if match := getMatchRegField(matchers, openflow.CNPConjIDField); match != nil { notAllowConjInfo, err := getRegValue(match, nil) if err != nil { return nil, nil, nil, err diff --git a/pkg/agent/multicast/mcast_controller.go b/pkg/agent/multicast/mcast_controller.go index 01188cba172..25958265e08 100644 --- a/pkg/agent/multicast/mcast_controller.go +++ b/pkg/agent/multicast/mcast_controller.go @@ -75,7 +75,11 @@ func (c *Controller) eventHandler(stopCh <-chan struct{}) { for { select { case e := <-c.groupEventCh: - c.addOrUpdateGroupEvent(e) + if e.group.Equal(types.McastAllHosts) { + c.updateQueryGroup() + } else { + c.addOrUpdateGroupEvent(e) + } case <-stopCh: return } @@ -186,8 +190,7 @@ func (c *Controller) clearStaleGroups() { // removeLocalInterface searches the GroupMemberStatus which the deleted interface has joined, and then triggers a member // leave event so that Antrea can remove the corresponding interface from local multicast receivers on OVS. This function // should be called if the removed Pod receiver fails to send IGMP leave message before deletion. -func (c *Controller) removeLocalInterface(e interface{}) { - podEvent := e.(types.PodUpdate) +func (c *Controller) removeLocalInterface(podEvent types.PodUpdate) { // Ignore Pod creation event. if podEvent.IsAdd { return @@ -222,11 +225,12 @@ type Controller struct { installedGroupsMutex sync.RWMutex mRouteClient *MRouteClient ovsBridgeClient ovsconfig.OVSBridgeClient - // queryInterval is the interval to send IGMP query messages. queryInterval time.Duration // mcastGroupTimeout is the timeout to detect a group as stale if no IGMP report is received within the time. mcastGroupTimeout time.Duration + // the group ID in OVS for group which IGMP queries are sent to + queryGroupId binding.GroupIDType } func NewMulticastController(ofClient openflow.Client, @@ -237,9 +241,10 @@ func NewMulticastController(ofClient openflow.Client, multicastInterfaces sets.String, ovsBridgeClient ovsconfig.OVSBridgeClient, podUpdateSubscriber channel.Subscriber, - igmpQueryInterval time.Duration) *Controller { + igmpQueryInterval time.Duration, + validator types.MulticastValidator) *Controller { eventCh := make(chan *mcastGroupEvent, workerCount) - groupSnooper := newSnooper(ofClient, ifaceStore, eventCh, igmpQueryInterval) + groupSnooper := newSnooper(ofClient, ifaceStore, eventCh, igmpQueryInterval, validator) groupCache := cache.NewIndexer(getGroupEventKey, cache.Indexers{ podInterfaceIndex: podInterfaceIndexFunc, }) @@ -258,8 +263,9 @@ func NewMulticastController(ofClient openflow.Client, ovsBridgeClient: ovsBridgeClient, queryInterval: igmpQueryInterval, mcastGroupTimeout: igmpQueryInterval * 3, + queryGroupId: v4GroupAllocator.Allocate(), } - podUpdateSubscriber.Subscribe(c.removeLocalInterface) + podUpdateSubscriber.Subscribe(c.memberChanged) return c } @@ -278,6 +284,10 @@ func (c *Controller) Initialize() error { klog.ErrorS(err, "Failed to install multicast initial flows") return err } + err = c.initQueryGroup() + if err != nil { + return err + } return nil } @@ -367,7 +377,8 @@ func (c *Controller) syncGroup(groupKey string) error { return nil } status := obj.(*GroupMemberStatus) - memberPorts := make([]uint32, 0, len(status.localMembers)) + memberPorts := make([]uint32, 0, len(status.localMembers)+1) + memberPorts = append(memberPorts, config.HostGatewayOFPort) for memberInterfaceName := range status.localMembers { obj, found := c.ifaceStore.GetInterfaceByName(memberInterfaceName) if !found { @@ -473,6 +484,46 @@ func (c *Controller) addOrUpdateGroupEvent(e *mcastGroupEvent) { } } +func (c *Controller) memberChanged(e interface{}) { + podEvent := e.(types.PodUpdate) + namespace, name := podEvent.PodNamespace, podEvent.PodName + + klog.V(2).InfoS("Pod is updated", "IsAdd", podEvent.IsAdd, "namespace", namespace, "name", name) + event := &mcastGroupEvent{ + group: types.McastAllHosts, + } + c.groupEventCh <- event + c.removeLocalInterface(podEvent) +} + +func (c *Controller) initQueryGroup() error { + err := c.updateQueryGroup() + if err != nil { + return err + } + if err = c.ofClient.InstallMulticastFlows(types.McastAllHosts, c.queryGroupId); err != nil { + klog.ErrorS(err, "Failed to install multicast flows", "group", types.McastAllHosts) + return err + } + return nil +} + +// updateQueryGroup gets all containers' interfaces, and add all ofports into IGMP query group. +func (c *Controller) updateQueryGroup() error { + ifaces := c.ifaceStore.GetInterfacesByType(interfacestore.ContainerInterface) + memberPorts := make([]uint32, 0, len(ifaces)) + for _, iface := range ifaces { + memberPorts = append(memberPorts, uint32(iface.OFPort)) + } + // Install OpenFlow group for a new multicast group which has local Pod receivers joined. + if err := c.ofClient.InstallMulticastGroup(c.queryGroupId, memberPorts); err != nil { + return err + } + klog.V(2).InfoS("Installed OpenFlow group for local receivers", "group", types.McastAllHosts.String(), + "ofGroup", c.queryGroupId, "localReceivers", memberPorts) + return nil +} + func podInterfaceIndexFunc(obj interface{}) ([]string, error) { groupState := obj.(*GroupMemberStatus) podInterfaces := make([]string, 0, len(groupState.localMembers)) diff --git a/pkg/agent/multicast/mcast_controller_test.go b/pkg/agent/multicast/mcast_controller_test.go index 1f57b503f30..cb8945d7290 100644 --- a/pkg/agent/multicast/mcast_controller_test.go +++ b/pkg/agent/multicast/mcast_controller_test.go @@ -38,6 +38,7 @@ import ( multicasttest "antrea.io/antrea/pkg/agent/multicast/testing" "antrea.io/antrea/pkg/agent/openflow" openflowtest "antrea.io/antrea/pkg/agent/openflow/testing" + "antrea.io/antrea/pkg/agent/types" ovsconfigtest "antrea.io/antrea/pkg/ovs/ovsconfig/testing" "antrea.io/antrea/pkg/util/channel" ) @@ -112,7 +113,7 @@ func TestUpdateGroupMemberStatus(t *testing.T) { iface: if1, } mctrl.addGroupMemberStatus(event) - mockOFClient.EXPECT().SendIGMPQueryPacketOut(igmpQueryDstMac, mcastAllHosts, uint32(openflow13.P_NORMAL), gomock.Any()).Times(len(queryVersions)) + mockOFClient.EXPECT().SendIGMPQueryPacketOut(igmpQueryDstMac, types.McastAllHosts, uint32(0), gomock.Any()).Times(len(queryVersions)) for _, e := range []*mcastGroupEvent{ {group: mgroup, eType: groupJoin, time: event.time.Add(time.Second * 20), iface: if1}, {group: mgroup, eType: groupJoin, time: event.time.Add(time.Second * 40), iface: if1}, @@ -160,7 +161,7 @@ func TestCheckLastMember(t *testing.T) { } _ = mctrl.groupCache.Add(status) mctrl.addInstalledGroup(status.group.String()) - mockOFClient.EXPECT().SendIGMPQueryPacketOut(igmpQueryDstMac, mcastAllHosts, uint32(openflow13.P_NORMAL), gomock.Any()).AnyTimes() + mockOFClient.EXPECT().SendIGMPQueryPacketOut(igmpQueryDstMac, types.McastAllHosts, uint32(0), gomock.Any()).AnyTimes() var wg sync.WaitGroup wg.Add(1) go func() { @@ -365,12 +366,15 @@ func newMockMulticastController(t *testing.T) *Controller { groupAllocator := openflow.NewGroupAllocator(false) podUpdateSubscriber := channel.NewSubscribableChannel("PodUpdate", 100) queryInterval := 5 * time.Second - mctrl := NewMulticastController(mockOFClient, groupAllocator, nodeConfig, mockIfaceStore, mockMulticastSocket, sets.NewString(), ovsClient, podUpdateSubscriber, queryInterval) + mctrl := NewMulticastController(mockOFClient, groupAllocator, nodeConfig, mockIfaceStore, mockMulticastSocket, sets.NewString(), ovsClient, podUpdateSubscriber, queryInterval, nil) return mctrl } func (c *Controller) initialize(t *testing.T) error { mockOFClient.EXPECT().InstallMulticastInitialFlows(uint8(0)).Times(1) + mockOFClient.EXPECT().InstallMulticastGroup(gomock.Any(), gomock.Any()) + mockOFClient.EXPECT().InstallMulticastFlows(gomock.Any(), gomock.Any()) + mockIfaceStore.EXPECT().GetInterfacesByType(interfacestore.InterfaceType(0)).Times(1).Return([]*interfacestore.InterfaceConfig{}) mockMulticastSocket.EXPECT().AllocateVIFs(gomock.Any(), uint16(0)).Times(1).Return([]uint16{0}, nil) mockMulticastSocket.EXPECT().AllocateVIFs(gomock.Any(), uint16(1)).Times(1).Return([]uint16{1, 2}, nil) return c.Initialize() diff --git a/pkg/agent/multicast/mcast_discovery.go b/pkg/agent/multicast/mcast_discovery.go index e68da77bdf5..3846ad55a78 100644 --- a/pkg/agent/multicast/mcast_discovery.go +++ b/pkg/agent/multicast/mcast_discovery.go @@ -28,6 +28,8 @@ import ( "antrea.io/antrea/pkg/agent/interfacestore" "antrea.io/antrea/pkg/agent/openflow" + "antrea.io/antrea/pkg/agent/types" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" ) const ( @@ -41,7 +43,6 @@ var ( igmpMaxResponseTime = time.Second * 10 // igmpQueryDstMac is the MAC address used in the dst MAC field in the IGMP query message igmpQueryDstMac, _ = net.ParseMAC("01:00:5e:00:00:01") - mcastAllHosts = net.ParseIP("224.0.0.1").To4() ) type IGMPSnooper struct { @@ -49,6 +50,7 @@ type IGMPSnooper struct { ifaceStore interfacestore.InterfaceStore eventCh chan *mcastGroupEvent queryInterval time.Duration + validator types.MulticastValidator } func (s *IGMPSnooper) HandlePacketIn(pktIn *ofctrl.PacketIn) error { @@ -97,14 +99,55 @@ 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 sets the output port of the packetOut message. We expect the message to go through OVS pipeline + // from table0. The final OpenFlow message will use a standard OpenFlow port number OFPP_TABLE = 0xfffffff9 corrected + // by ofnet. + outPort := uint32(0) + if err := s.ofClient.SendIGMPQueryPacketOut(igmpQueryDstMac, types.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 } +func (s *IGMPSnooper) validate(event *mcastGroupEvent, igmpType uint8) (bool, error) { + if s.validator == nil { + // Return true directly if there is no validator. + return true, nil + } + if event.iface.Type != interfacestore.ContainerInterface { + return true, fmt.Errorf("interface is not container") + } + // Validate checks if packet should be dropped or not, and returns multicast NP information + item, err := s.validator.Validate(event.iface.PodName, event.iface.PodNamespace, event.group, igmpType) + if err != nil { + // It shall drop the packet if function Validate returns error + klog.ErrorS(err, "Failed to validate multicast group event") + return false, err + } + klog.V(2).InfoS("Got NetworkPolicy action for IGMP query", "RuleAction", item.RuleAction, "uuid", item.UUID, "Name", item.Name) + if item.RuleAction == v1alpha1.RuleActionDrop { + return false, nil + } + return true, nil +} + +func (s *IGMPSnooper) validatePacketAndNotify(event *mcastGroupEvent, igmpType uint8) { + allow, err := s.validate(event, igmpType) + if err != nil { + // Antrea Agent does not remove the Pod from the OpenFlow group bucket immediately when an error is returned, + // but it will be removed when after timeout (Controller.mcastGroupTimeout) + return + } + if !allow { + // If any rule is desired to drop the traffic, Antrea Agent removes the Pod from + // the OpenFlow group bucket directly + event.eType = groupLeave + } + s.eventCh <- event +} + func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { now := time.Now() iface, err := s.parseSrcInterface(pktIn) @@ -120,7 +163,8 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { if err != nil { return err } - switch igmp.GetMessageType() { + igmpType := igmp.GetMessageType() + switch igmpType { case protocol.IGMPv1Report: fallthrough case protocol.IGMPv2Report: @@ -132,7 +176,7 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { time: now, iface: iface, } - s.eventCh <- event + s.validatePacketAndNotify(event, igmpType) case protocol.IGMPv3Report: msg := igmp.(*protocol.IGMPv3MembershipReport) for _, gr := range msg.GroupRecords { @@ -148,7 +192,7 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error { time: now, iface: iface, } - s.eventCh <- event + s.validatePacketAndNotify(event, igmpType) } case protocol.IGMPv2LeaveGroup: @@ -236,8 +280,8 @@ func parseIGMPPacket(pkt protocol.Ethernet) (protocol.IGMPMessage, error) { } } -func newSnooper(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, eventCh chan *mcastGroupEvent, queryInterval time.Duration) *IGMPSnooper { - d := &IGMPSnooper{ofClient: ofClient, ifaceStore: ifaceStore, eventCh: eventCh, queryInterval: queryInterval} +func newSnooper(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, eventCh chan *mcastGroupEvent, queryInterval time.Duration, validator types.MulticastValidator) *IGMPSnooper { + d := &IGMPSnooper{ofClient: ofClient, ifaceStore: ifaceStore, eventCh: eventCh, queryInterval: queryInterval, validator: validator} ofClient.RegisterPacketInHandler(uint8(openflow.PacketInReasonMC), "MulticastGroupDiscovery", d) return d } diff --git a/pkg/agent/openflow/client.go b/pkg/agent/openflow/client.go index 869d4d28c57..2e32c348be6 100644 --- a/pkg/agent/openflow/client.go +++ b/pkg/agent/openflow/client.go @@ -723,6 +723,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) @@ -746,7 +747,7 @@ func (c *client) generatePipelines() { if c.enableMulticast { // TODO: add support for IPv6 protocol - c.featureMulticast = newFeatureMulticast(c.cookieAllocator, []binding.Protocol{binding.ProtocolIP}, c.bridge) + c.featureMulticast = newFeatureMulticast(c.cookieAllocator, []binding.Protocol{binding.ProtocolIP}, c.bridge, c.enableAntreaPolicy) c.activatedFeatures = append(c.activatedFeatures, c.featureMulticast) } c.featureTraceflow = newFeatureTraceflow() @@ -1182,9 +1183,12 @@ func (c *client) UninstallTrafficControlReturnPortFlow(returnOFPort uint32) erro func (c *client) InstallMulticastGroup(groupID binding.GroupIDType, localReceivers []uint32) error { c.replayMutex.RLock() defer c.replayMutex.RUnlock() + table := MulticastOutputTable + if c.enableAntreaPolicy { + table = MulticastIngressRuleTable + } - targetPorts := append([]uint32{config.HostGatewayOFPort}, localReceivers...) - if err := c.featureMulticast.multicastReceiversGroup(groupID, targetPorts...); err != nil { + if err := c.featureMulticast.multicastReceiversGroup(groupID, table.GetID(), localReceivers...); err != nil { return err } return nil diff --git a/pkg/agent/openflow/client_test.go b/pkg/agent/openflow/client_test.go index 57d9eddb061..ceebbb4a6fd 100644 --- a/pkg/agent/openflow/client_test.go +++ b/pkg/agent/openflow/client_test.go @@ -441,7 +441,7 @@ func prepareTraceflowFlow(ctrl *gomock.Controller) *client { mFlow.EXPECT().FlowProtocol().Return(binding.Protocol("ip")) mFlow.EXPECT().CopyToBuilder(priorityNormal+2, false).Return(EgressDefaultTable.ofTable.BuildFlow(priorityNormal + 2)).Times(1) c.featureNetworkPolicy.globalConjMatchFlowCache["mockContext"] = ctx - c.featureNetworkPolicy.policyCache.Add(&policyRuleConjunction{metricFlows: []binding.Flow{c.featureNetworkPolicy.denyRuleMetricFlow(123, false)}}) + c.featureNetworkPolicy.policyCache.Add(&policyRuleConjunction{metricFlows: []binding.Flow{c.featureNetworkPolicy.denyRuleMetricFlow(123, false, 1)}}) return c } diff --git a/pkg/agent/openflow/fields.go b/pkg/agent/openflow/fields.go index 4198e47bdc6..6e530278526 100644 --- a/pkg/agent/openflow/fields.go +++ b/pkg/agent/openflow/fields.go @@ -98,9 +98,9 @@ var ( // reg3(NXM_NX_REG3) // Field to store the selected Service Endpoint IP EndpointIPField = binding.NewRegField(3, 0, 31, "EndpointIP") - // Field to store the conjunction ID which is for "deny" rule in CNP. It shares the same register with EndpointIPField, + // Field to store the conjunction ID which is for rule in CNP. It shares the same register with EndpointIPField, // since the service selection will finish when a packet hitting NetworkPolicy related rules. - CNPDenyConjIDField = binding.NewRegField(3, 0, 31, "CNPDenyConjunctionID") + CNPConjIDField = binding.NewRegField(3, 0, 31, "CNPConjunctionID") // reg4(NXM_NX_REG4) // reg4[0..15]: Field to store the selected Service Endpoint port. diff --git a/pkg/agent/openflow/framework.go b/pkg/agent/openflow/framework.go index 08c92b4be56..d05964d4a3c 100644 --- a/pkg/agent/openflow/framework.go +++ b/pkg/agent/openflow/framework.go @@ -209,6 +209,14 @@ func (f *featureNetworkPolicy) getRequiredTables() []*Table { AntreaPolicyEgressRuleTable, AntreaPolicyIngressRuleTable, ) + if f.enableMulticast { + tables = append(tables, + MulticastEgressRuleTable, + MulticastEgressMetricTable, + MulticastIngressRuleTable, + MulticastIngressMetricTable, + ) + } } return tables @@ -244,10 +252,11 @@ func (f *featureEgress) getRequiredTables() []*Table { } func (f *featureMulticast) getRequiredTables() []*Table { - return []*Table{ + tables := []*Table{ MulticastRoutingTable, MulticastOutputTable, } + return tables } func (f *featureTraceflow) getRequiredTables() []*Table { diff --git a/pkg/agent/openflow/multicast.go b/pkg/agent/openflow/multicast.go index 8a2adedfcc1..8b79c40bf1e 100644 --- a/pkg/agent/openflow/multicast.go +++ b/pkg/agent/openflow/multicast.go @@ -16,24 +16,23 @@ package openflow import ( "fmt" - "net" "sync" "k8s.io/klog/v2" "antrea.io/antrea/pkg/agent/openflow/cookie" + "antrea.io/antrea/pkg/agent/types" binding "antrea.io/antrea/pkg/ovs/openflow" ) -var _, mcastCIDR, _ = net.ParseCIDR("224.0.0.0/4") - type featureMulticast struct { cookieAllocator cookie.Allocator ipProtocols []binding.Protocol bridge binding.Bridge - cachedFlows *flowCategoryCache - groupCache sync.Map + cachedFlows *flowCategoryCache + groupCache sync.Map + enableAntreaPolicy bool category cookie.Category } @@ -42,14 +41,15 @@ func (f *featureMulticast) getFeatureName() string { return "Multicast" } -func newFeatureMulticast(cookieAllocator cookie.Allocator, ipProtocols []binding.Protocol, bridge binding.Bridge) *featureMulticast { +func newFeatureMulticast(cookieAllocator cookie.Allocator, ipProtocols []binding.Protocol, bridge binding.Bridge, anpEnabled bool) *featureMulticast { return &featureMulticast{ - cookieAllocator: cookieAllocator, - ipProtocols: ipProtocols, - cachedFlows: newFlowCategoryCache(), - bridge: bridge, - category: cookie.Multicast, - groupCache: sync.Map{}, + cookieAllocator: cookieAllocator, + ipProtocols: ipProtocols, + cachedFlows: newFlowCategoryCache(), + bridge: bridge, + category: cookie.Multicast, + groupCache: sync.Map{}, + enableAntreaPolicy: anpEnabled, } } @@ -58,7 +58,7 @@ func multicastPipelineClassifyFlow(cookieID uint64, pipeline binding.Pipeline) b return PipelineIPClassifierTable.ofTable.BuildFlow(priorityHigh). Cookie(cookieID). MatchProtocol(binding.ProtocolIP). - MatchDstIPNet(*mcastCIDR). + MatchDstIPNet(*types.McastCIDR). Action().ResubmitToTables(targetTable.GetID()). Done() } @@ -75,13 +75,13 @@ func (f *featureMulticast) replayFlows() []binding.Flow { return getCachedFlows(f.cachedFlows) } -func (f *featureMulticast) multicastReceiversGroup(groupID binding.GroupIDType, ports ...uint32) error { +func (f *featureMulticast) multicastReceiversGroup(groupID binding.GroupIDType, tableID uint8, ports ...uint32) error { group := f.bridge.CreateGroupTypeAll(groupID).ResetBuckets() for i := range ports { group = group.Bucket(). LoadToRegField(OFPortFoundRegMark.GetField(), OFPortFoundRegMark.GetValue()). LoadToRegField(TargetOFPortField, ports[i]). - ResubmitToTable(MulticastRoutingTable.GetNext()). + ResubmitToTable(tableID). Done() } if err := group.Add(); err != nil { diff --git a/pkg/agent/openflow/network_policy.go b/pkg/agent/openflow/network_policy.go index a3ba1680713..7447a9e0e57 100644 --- a/pkg/agent/openflow/network_policy.go +++ b/pkg/agent/openflow/network_policy.go @@ -59,6 +59,7 @@ var ( MatchICMPv6Type = types.NewMatchKey(binding.ProtocolICMPv6, types.ICMPAddr, "icmpv6_type") MatchICMPv6Code = types.NewMatchKey(binding.ProtocolICMPv6, types.ICMPAddr, "icmpv6_code") MatchServiceGroupID = types.NewMatchKey(binding.ProtocolIP, types.ServiceGroupIDAddr, "reg7[0..31]") + MatchIGMPProtocol = types.NewMatchKey(binding.ProtocolIGMP, types.IGMPAddr, "igmp") Unsupported = types.NewMatchKey(binding.ProtocolIP, types.UnSupported, "unknown") // metricFlowIdentifier is used to identify metric flows in metric table. @@ -786,6 +787,20 @@ func getServiceMatchPairs(service v1beta2.Service, ipProtocols []binding.Protoco conjMatchesMatchPairs = append(conjMatchesMatchPairs, matchPairs) } } + case v1beta2.ProtocolIGMP: + var matchPairs []matchPair + if service.IGMPType != nil && *service.IGMPType == crdv1alpha1.IGMPQuery { + // Since OVS only matches layer 3 IP address on the IGMP query packet, and doesn't + // identify the multicast group address set in the IGMP protocol, the flow entry + // processes all IGMP query packets by matching the destintaion IP address ( 224.0.0.1 ) + if service.GroupAddress != "" { + matchPairs = append(matchPairs, matchPair{matchKey: MatchDstIP, matchValue: net.ParseIP(service.GroupAddress)}) + } else { + matchPairs = append(matchPairs, matchPair{matchKey: MatchDstIP, matchValue: types.McastAllHosts}) + } + matchPairs = append(matchPairs, matchPair{matchKey: MatchIGMPProtocol, matchValue: nil}) + conjMatchesMatchPairs = append(conjMatchesMatchPairs, matchPairs) + } default: addL4MatchPairs(MatchTCPDstPort) } @@ -1017,15 +1032,15 @@ func (f *featureNetworkPolicy) calculateActionFlowChangesForRule(rule *types.Pol var actionFlows []binding.Flow var metricFlows []binding.Flow if rule.IsAntreaNetworkPolicyRule() && *rule.Action == crdv1alpha1.RuleActionDrop { - metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress)) + metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress, rule.TableID)) 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)) + metricFlows = append(metricFlows, f.denyRuleMetricFlow(ruleOfID, isIngress, rule.TableID)) 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)...) + metricFlows = append(metricFlows, f.allowRulesMetricFlows(ruleOfID, isIngress, rule.TableID)...) actionFlows = append(actionFlows, f.conjunctionActionFlow(ruleOfID, ruleTable, dropTable.GetNext(), rule.Priority, rule.EnableLogging)...) } conj.actionFlows = actionFlows @@ -1787,6 +1802,7 @@ type featureNetworkPolicy struct { ovsMetersAreSupported bool enableDenyTracking bool enableAntreaPolicy bool + enableMulticast bool ctZoneSrcField *binding.RegField // deterministic represents whether to generate flows deterministically. // For example, if a flow has multiple actions, setting it to true can get consistent flow. @@ -1807,6 +1823,7 @@ func newFeatureNetworkPolicy( ovsMetersAreSupported, enableDenyTracking, enableAntreaPolicy bool, + enableMulticast bool, connectUplinkToBridge bool) *featureNetworkPolicy { return &featureNetworkPolicy{ cookieAllocator: cookieAllocator, @@ -1814,6 +1831,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 +1844,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/network_policy_test.go b/pkg/agent/openflow/network_policy_test.go index d520689a518..5bebdcd5260 100644 --- a/pkg/agent/openflow/network_policy_test.go +++ b/pkg/agent/openflow/network_policy_test.go @@ -460,12 +460,12 @@ func TestBatchInstallPolicyRuleFlows(t *testing.T) { Action().CT(true, IngressMetricTable.GetID(), CtZone, nil).LoadToLabelField(10, IngressRuleCTLabel).CTDone().Done(), AntreaPolicyIngressRuleTable.ofTable.BuildFlow(priority100).Cookie(cookiePolicy). MatchConjID(11). - Action().LoadToRegField(CNPDenyConjIDField, 11). + Action().LoadToRegField(CNPConjIDField, 11). Action().LoadRegMark(CnpDenyRegMark). Action().GotoTable(IngressMetricTable.GetID()).Done(), AntreaPolicyIngressRuleTable.ofTable.BuildFlow(priority200).Cookie(cookiePolicy). MatchConjID(12). - Action().LoadToRegField(CNPDenyConjIDField, 12). + Action().LoadToRegField(CNPConjIDField, 12). Action().LoadRegMark(CnpDenyRegMark). Action().GotoTable(IngressMetricTable.GetID()).Done(), AntreaPolicyIngressRuleTable.ofTable.BuildFlow(priority100).Cookie(cookiePolicy). @@ -510,10 +510,10 @@ func TestBatchInstallPolicyRuleFlows(t *testing.T) { MatchProtocol(binding.ProtocolIP).MatchCTStateNew(false).MatchCTLabelField(0, 10, IngressRuleCTLabel). Action().NextTable().Done(), IngressMetricTable.ofTable.BuildFlow(priorityNormal).Cookie(cookiePolicy). - MatchRegMark(CnpDenyRegMark).MatchRegFieldWithValue(CNPDenyConjIDField, 11). + MatchRegMark(CnpDenyRegMark).MatchRegFieldWithValue(CNPConjIDField, 11). Action().Drop().Done(), IngressMetricTable.ofTable.BuildFlow(priorityNormal).Cookie(cookiePolicy). - MatchRegMark(CnpDenyRegMark).MatchRegFieldWithValue(CNPDenyConjIDField, 12). + MatchRegMark(CnpDenyRegMark).MatchRegFieldWithValue(CNPConjIDField, 12). Action().Drop().Done(), } }, diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go index 51234729620..ace6b353158 100644 --- a/pkg/agent/openflow/pipeline.go +++ b/pkg/agent/openflow/pipeline.go @@ -177,10 +177,16 @@ var ( L2ForwardingOutTable = newTable("Output", stageOutput, pipelineIP) // Tables of pipelineMulticast are declared below. Do don't declare any tables of other pipelines here! - + // Tables in stageEgressSecurity: + // Since IGMP Egress rules only support IGMP report which is handled by packetIn, it is not necessary to add + // MulticastIGMPEgressMetricTable here. + MulticastEgressRuleTable = newTable("MulticastEgressRule", stageEgressSecurity, pipelineMulticast) + MulticastEgressMetricTable = newTable("MulticastEgressMetric", stageEgressSecurity, pipelineMulticast) // Tables in stageRouting: MulticastRoutingTable = newTable("MulticastRouting", stageRouting, pipelineMulticast) - + // Tables in stageIngressSecurity + MulticastIngressRuleTable = newTable("MulticastIngressRule", stageIngressSecurity, pipelineMulticast) + MulticastIngressMetricTable = newTable("MulticastIngressMetric", stageIngressSecurity, pipelineMulticast) // Tables in stageOutput MulticastOutputTable = newTable("MulticastOutput", stageOutput, pipelineMulticast) @@ -287,6 +293,18 @@ func GetAntreaPolicyEgressTables() []*Table { } } +func GetAntreaIGMPIngressTables() []*Table { + return []*Table{ + MulticastIngressRuleTable, + } +} + +func GetAntreaMulticastEgressTables() []*Table { + return []*Table{ + MulticastEgressRuleTable, + } +} + func GetAntreaPolicyIngressTables() []*Table { return []*Table{ AntreaPolicyIngressRuleTable, @@ -1583,7 +1601,7 @@ func (f *featurePodConnectivity) arpNormalFlow() binding.Flow { Done() } -func (f *featureNetworkPolicy) allowRulesMetricFlows(conjunctionID uint32, ingress bool) []binding.Flow { +func (f *featureNetworkPolicy) allowRulesMetricFlows(conjunctionID uint32, ingress bool, tableID uint8) []binding.Flow { cookieID := f.cookieAllocator.Request(f.category).Raw() metricTable := IngressMetricTable offset := 0 @@ -1595,6 +1613,12 @@ func (f *featureNetworkPolicy) allowRulesMetricFlows(conjunctionID uint32, ingre offset = 32 field = EgressRuleCTLabel } + if f.enableMulticast && tableID == MulticastEgressRuleTable.GetID() { + metricTable = MulticastEgressMetricTable + } + if f.enableMulticast && tableID == MulticastIngressRuleTable.GetID() { + metricTable = MulticastIngressMetricTable + } metricFlow := func(isCTNew bool, protocol binding.Protocol) binding.Flow { return metricTable.ofTable.BuildFlow(priorityNormal). Cookie(cookieID). @@ -1605,6 +1629,17 @@ func (f *featureNetworkPolicy) allowRulesMetricFlows(conjunctionID uint32, ingre Done() } var flows []binding.Flow + // Unlike rules for unicast traffic, each IGMP and multicast rule uses single metric flow to track stats + // in multicast metric tables. + if metricTable == MulticastEgressMetricTable || metricTable == MulticastIngressMetricTable { + flow := metricTable.ofTable.BuildFlow(priorityNormal). + Cookie(f.cookieAllocator.Request(f.category).Raw()). + MatchRegFieldWithValue(CNPConjIDField, conjunctionID). + Action().GotoTable(metricTable.GetNext()). + Done() + flows = append(flows, flow) + return flows + } // These two flows track the number of sessions in addition to the packet and byte counts. // The flow matching 'ct_state=+new' tracks the number of sessions and byte count of the first packet for each // session. @@ -1615,15 +1650,21 @@ func (f *featureNetworkPolicy) allowRulesMetricFlows(conjunctionID uint32, ingre return flows } -func (f *featureNetworkPolicy) denyRuleMetricFlow(conjunctionID uint32, ingress bool) binding.Flow { +func (f *featureNetworkPolicy) denyRuleMetricFlow(conjunctionID uint32, ingress bool, tableID uint8) binding.Flow { metricTable := IngressMetricTable if !ingress { metricTable = EgressMetricTable } + if f.enableMulticast && tableID == MulticastEgressRuleTable.GetID() { + metricTable = MulticastEgressMetricTable + } + if f.enableMulticast && tableID == MulticastIngressRuleTable.GetID() { + metricTable = MulticastIngressMetricTable + } return metricTable.ofTable.BuildFlow(priorityNormal). Cookie(f.cookieAllocator.Request(f.category).Raw()). MatchRegMark(CnpDenyRegMark). - MatchRegFieldWithValue(CNPDenyConjIDField, conjunctionID). + MatchRegFieldWithValue(CNPConjIDField, conjunctionID). Action().Drop(). Done() } @@ -1672,9 +1713,10 @@ func (f *featurePodConnectivity) ipv6Flows() []binding.Flow { return flows } -// conjunctionActionFlow generates the flow to jump to a specific table if policyRuleConjunction ID is matched. Priority of +// For normal traffic, conjunctionActionFlow generates the flow to jump to a specific table if policyRuleConjunction ID is matched. Priority of // conjunctionActionFlow is created at priorityLow for k8s network policies, and *priority assigned by PriorityAssigner for AntreaPolicy. func (f *featureNetworkPolicy) conjunctionActionFlow(conjunctionID uint32, table binding.Table, nextTable uint8, priority *uint16, enableLogging bool) []binding.Flow { + tableID := table.GetID() cookieID := f.cookieAllocator.Request(f.category).Raw() var ofPriority uint16 if priority == nil { @@ -1684,7 +1726,7 @@ func (f *featureNetworkPolicy) conjunctionActionFlow(conjunctionID uint32, table } conjReg := TFIngressConjIDField labelField := IngressRuleCTLabel - tableID := table.GetID() + if _, ok := f.egressTables[tableID]; ok { conjReg = TFEgressConjIDField labelField = EgressRuleCTLabel @@ -1720,6 +1762,19 @@ func (f *featureNetworkPolicy) conjunctionActionFlow(conjunctionID uint32, table Done() } var flows []binding.Flow + // As IGMP and multicast use a different pipeline 'Multicast', if the rule is + // IGMP ingress or multicast egress,conjunctionActionFlow generates the flow + // to mark the packet to be allowed if policyRuleConjunction ID is matched. + // Any matched flow will be resubmitted to next table in corresponding metric tables. + if f.enableMulticast && (tableID == MulticastEgressRuleTable.GetID() || tableID == MulticastIngressRuleTable.GetID()) { + flow := table.BuildFlow(ofPriority).MatchConjID(conjunctionID). + Action().LoadToRegField(CNPConjIDField, conjunctionID). + Action().NextTable(). + Cookie(f.cookieAllocator.Request(f.category).Raw()). + Done() + flows = append(flows, flow) + return flows + } for _, proto := range f.ipProtocols { flows = append(flows, conjActionFlow(proto)) } @@ -1728,7 +1783,8 @@ func (f *featureNetworkPolicy) conjunctionActionFlow(conjunctionID uint32, table // conjunctionActionDenyFlow generates the flow to mark the packet to be denied (dropped or rejected) if policyRuleConjunction // ID is matched. Any matched flow will be dropped in corresponding metric tables. -func (f *featureNetworkPolicy) conjunctionActionDenyFlow(conjunctionID uint32, table binding.Table, priority *uint16, disposition uint32, enableLogging bool) binding.Flow { +func (f *featureNetworkPolicy) conjunctionActionDenyFlow(conjunctionID uint32, table binding.Table, priority *uint16, + disposition uint32, enableLogging bool) binding.Flow { ofPriority := *priority metricTable := IngressMetricTable tableID := table.GetID() @@ -1736,9 +1792,15 @@ func (f *featureNetworkPolicy) conjunctionActionDenyFlow(conjunctionID uint32, t metricTable = EgressMetricTable } + if f.enableMulticast && tableID == MulticastEgressRuleTable.GetID() { + metricTable = MulticastEgressMetricTable + } + if f.enableMulticast && tableID == MulticastIngressRuleTable.GetID() { + metricTable = MulticastIngressMetricTable + } flowBuilder := table.BuildFlow(ofPriority). MatchConjID(conjunctionID). - Action().LoadToRegField(CNPDenyConjIDField, conjunctionID). + Action().LoadToRegField(CNPConjIDField, conjunctionID). Action().LoadRegMark(CnpDenyRegMark) var customReason int @@ -1782,6 +1844,7 @@ func (f *featureNetworkPolicy) conjunctionActionPassFlow(conjunctionID uint32, t } flowBuilder := table.BuildFlow(ofPriority).MatchConjID(conjunctionID). Action().LoadToRegField(conjReg, conjunctionID) + if enableLogging { flowBuilder = flowBuilder. Action().LoadRegMark(DispositionPassRegMark, CustomReasonLoggingRegMark). @@ -1993,6 +2056,8 @@ func (f *featureNetworkPolicy) addFlowMatch(fb binding.FlowBuilder, matchKey *ty } case MatchServiceGroupID: fb = fb.MatchRegFieldWithValue(ServiceGroupIDField, matchValue.(uint32)) + case MatchIGMPProtocol: + fb = fb.MatchProtocol(matchKey.GetOFProtocol()) } return fb } @@ -2064,7 +2129,7 @@ func (f *featureNetworkPolicy) dnsPacketInFlow(conjunctionID uint32) binding.Flo } // localProbeFlows generates the flows to forward locally generated request packets to stageConntrack directly, bypassing -// ingress rules of Network Policies. The packets are sent by kubelet to probe the liveness/readiness of local Pods. +// ingress rule of Network Policies. The packets are sent by kubelet to probe the liveness/readiness of local Pods. // On Linux and when OVS kernel datapath is used, the probe packets are identified by matching the HostLocalSourceMark. // On Windows or when OVS userspace (netdev) datapath is used, we need a different approach because: // 1. On Windows, kube-proxy userspace mode is used, and currently there is no way to distinguish kubelet generated traffic @@ -2589,8 +2654,8 @@ 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 -// it to antrea-agent. +// igmpPktInFlows generates the flow to load CustomReasonIGMPRegMark to mark the IGMP packet in MulticastRoutingTable +// and sends it to antrea-agent. func (f *featureMulticast) igmpPktInFlows(reason uint8) []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 @@ -2630,14 +2695,16 @@ func (f *featureMulticast) localMulticastForwardFlows(multicastIP net.IP, groupI // send multicast packets to access the external receivers. For the case that one or more local Pods have joined the target // multicast group, it is handled by the flows created by function "localMulticastForwardFlows" after local Pods report the // IGMP membership. +// Because there are ingress tables between MulticastRoutingTable and MulticastOutputTable, while currently ingress rules only +// support IGMP query, it is not necessary to goto the ingress tables for other multicast traffic. func (f *featureMulticast) externalMulticastReceiverFlow() binding.Flow { return MulticastRoutingTable.ofTable.BuildFlow(priorityLow). Cookie(f.cookieAllocator.Request(f.category).Raw()). MatchProtocol(binding.ProtocolIP). - MatchDstIPNet(*mcastCIDR). + MatchDstIPNet(*types.McastCIDR). Action().LoadRegMark(OFPortFoundRegMark). Action().LoadToRegField(TargetOFPortField, config.HostGatewayOFPort). - Action().NextTable(). + Action().GotoStage(stageOutput). Done() } diff --git a/pkg/agent/openflow/pipeline_test.go b/pkg/agent/openflow/pipeline_test.go index 5ce9894d517..35f68de3636 100644 --- a/pkg/agent/openflow/pipeline_test.go +++ b/pkg/agent/openflow/pipeline_test.go @@ -42,6 +42,7 @@ func TestBuildPipeline(t *testing.T) { features []feature expectedTables map[binding.PipelineID][]*Table }{ + { ipStack: dualStack, features: []feature{ @@ -215,13 +216,65 @@ func TestBuildPipeline(t *testing.T) { }, }, }, + { + ipStack: ipv4Only, + features: []feature{ + &featurePodConnectivity{ipProtocols: ipStackMap[ipv4Only], enableMulticast: true}, + &featureNetworkPolicy{enableAntreaPolicy: true, enableMulticast: true}, + &featureService{enableProxy: true, proxyAll: false}, + &featureMulticast{enableAntreaPolicy: true}, + }, + expectedTables: map[binding.PipelineID][]*Table{ + pipelineRoot: { + PipelineRootClassifierTable, + }, + pipelineIP: { + ClassifierTable, + SpoofGuardTable, + PipelineIPClassifierTable, + UnSNATTable, + ConntrackTable, + ConntrackStateTable, + PreRoutingClassifierTable, + SessionAffinityTable, + ServiceLBTable, + EndpointDNATTable, + AntreaPolicyEgressRuleTable, + EgressRuleTable, + EgressDefaultTable, + EgressMetricTable, + L3ForwardingTable, + L3DecTTLTable, + ServiceMarkTable, + SNATTable, + L2ForwardingCalcTable, + AntreaPolicyIngressRuleTable, + IngressRuleTable, + IngressDefaultTable, + IngressMetricTable, + ConntrackCommitTable, + L2ForwardingOutTable, + }, + pipelineARP: { + ARPSpoofGuardTable, + ARPResponderTable, + }, + pipelineMulticast: { + MulticastEgressRuleTable, + MulticastEgressMetricTable, + + MulticastRoutingTable, + + MulticastIngressRuleTable, + MulticastIngressMetricTable, + + MulticastOutputTable, + }, + }, + }, } { - pipelineIDs := []binding.PipelineID{pipelineRoot, pipelineIP} - if tc.ipStack != ipv6Only { - pipelineIDs = append(pipelineIDs, pipelineARP) - } pipelineRequiredTablesMap := make(map[binding.PipelineID]map[*Table]struct{}) - for _, pipelineID := range pipelineIDs { + for pipelineID := range tc.expectedTables { pipelineRequiredTablesMap[pipelineID] = make(map[*Table]struct{}) } pipelineRequiredTablesMap[pipelineRoot][PipelineRootClassifierTable] = struct{}{} diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go index f77edb1ec75..fa08798846c 100644 --- a/pkg/agent/route/route_linux.go +++ b/pkg/agent/route/route_linux.go @@ -83,8 +83,6 @@ var ( // IPTablesSyncInterval is exported so that sync interval can be configured for running integration test with // smaller values. It is meant to be used internally by Run. IPTablesSyncInterval = 60 * time.Second - // Multicast CIDR is used to skip masquerade Pod to external packets for multicast traffic. - _, mcastCIDR, _ = net.ParseCIDR("224.0.0.0/4") ) // Client takes care of routing container packets in host network, coordinating ip route, ip rule, iptables and ipset. @@ -592,7 +590,7 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, podIPSet, localAntreaFl "-A", antreaPostRoutingChain, "-m", "comment", "--comment", `"Antrea: skip masquerade for multicast traffic"`, "-s", podCIDR.String(), - "-d", mcastCIDR.String(), + "-d", types.McastCIDR.String(), "-j", iptables.ReturnTarget, }...) } diff --git a/pkg/agent/types/multicast.go b/pkg/agent/types/multicast.go new file mode 100644 index 00000000000..069c5496ee0 --- /dev/null +++ b/pkg/agent/types/multicast.go @@ -0,0 +1,43 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "net" + + apitypes "k8s.io/apimachinery/pkg/types" + + "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + "antrea.io/antrea/pkg/apis/crd/v1alpha1" +) + +type McastNPValidationItem struct { + RuleAction v1alpha1.RuleAction + UUID apitypes.UID + NPType *v1beta2.NetworkPolicyType + Name string +} + +var ( + McastAllHosts = net.ParseIP("224.0.0.1").To4() + _, McastCIDR, _ = net.ParseCIDR("224.0.0.0/4") +) + +type MulticastValidator interface { + // Validate checks whether IGMP report from Pod(podNamespace/podName) to groupAddress should be dropped, + // and returns multicast NetworkPolicy information. + // TODO: refacor the function name and return type here + Validate(podname, podNamespace string, groupAddress net.IP, igmpType uint8) (McastNPValidationItem, error) +} diff --git a/pkg/agent/types/networkpolicy.go b/pkg/agent/types/networkpolicy.go index a0a90d1bb13..a58cab80aaa 100644 --- a/pkg/agent/types/networkpolicy.go +++ b/pkg/agent/types/networkpolicy.go @@ -55,6 +55,7 @@ const ( L4PortAddr ICMPAddr ServiceGroupIDAddr + IGMPAddr UnSupported ) diff --git a/pkg/apis/controlplane/types.go b/pkg/apis/controlplane/types.go index 68920081df2..3d2351e4007 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 specified when the Protocol is IGMP. + IGMPType *int32 + GroupAddress string } // 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 c02582c105d..f5b8501e7a0 100644 --- a/pkg/apis/controlplane/v1beta2/generated.pb.go +++ b/pkg/apis/controlplane/v1beta2/generated.pb.go @@ -983,141 +983,143 @@ func init() { } var fileDescriptor_fbaa7d016762fa1d = []byte{ - // 2132 bytes of a gzipped FileDescriptorProto + // 2161 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, 0x9d, 0x97, 0x96, 0x9a, 0xa5, 0xd8, 0xd9, 0x59, 0x40, - 0x39, 0xc0, 0x78, 0x13, 0xda, 0x6d, 0x61, 0xb7, 0x0b, 0x71, 0x92, 0x46, 0x96, 0xda, 0xd4, 0xbc, + 0xbd, 0xe3, 0x8f, 0x24, 0xfe, 0xd9, 0x69, 0x9d, 0x97, 0x96, 0x9a, 0xa5, 0xd8, 0xdd, 0x59, 0x40, + 0x3d, 0xc0, 0x78, 0x13, 0xda, 0x6d, 0x60, 0xb7, 0x0b, 0x71, 0x92, 0x46, 0x96, 0xda, 0xd4, 0xbc, 0x64, 0x55, 0x09, 0xe8, 0xb2, 0x93, 0x99, 0x67, 0x67, 0x88, 0x3d, 0x6f, 0x98, 0x79, 0x0e, 0x8d, - 0x90, 0xd0, 0x22, 0xe0, 0xb0, 0x0b, 0x12, 0xdc, 0x10, 0x37, 0x38, 0x71, 0xe1, 0x2f, 0xe0, 0xc6, - 0xad, 0xe2, 0xb4, 0x2b, 0x84, 0xd8, 0x93, 0x45, 0x8d, 0x00, 0x71, 0xe0, 0x1f, 0x08, 0x17, 0xf4, - 0xde, 0xbc, 0x99, 0x79, 0x63, 0x27, 0x4d, 0x9d, 0xa4, 0x41, 0x82, 0x3d, 0xd5, 0xf3, 0x7e, 0xdf, - 0xef, 0xf7, 0xfd, 0x52, 0x78, 0xd3, 0x74, 0x99, 0x4f, 0x4c, 0xc3, 0xa1, 0xf5, 0xf0, 0x57, 0xdd, - 0xdb, 0xed, 0xd4, 0x4d, 0xcf, 0x09, 0xea, 0x16, 0x75, 0x99, 0x4f, 0xbb, 0x5e, 0xd7, 0x74, 0x49, - 0x7d, 0x6f, 0x71, 0x9b, 0x30, 0x73, 0xa9, 0xde, 0x21, 0x2e, 0xf1, 0x4d, 0x46, 0x6c, 0xc3, 0xf3, - 0x29, 0xa3, 0xc8, 0x08, 0xa9, 0xbe, 0xe5, 0x50, 0xf9, 0xcb, 0xf0, 0x76, 0x3b, 0x06, 0xa7, 0x37, - 0x54, 0x7a, 0x43, 0xd2, 0xbf, 0x74, 0xfb, 0x68, 0x79, 0x01, 0x33, 0x59, 0x50, 0xdf, 0x5b, 0x34, - 0xbb, 0xde, 0x8e, 0xb9, 0x38, 0x2a, 0xe9, 0xa5, 0x2f, 0x74, 0x1c, 0xb6, 0xd3, 0xdf, 0x36, 0x2c, - 0xda, 0xab, 0x77, 0x68, 0x87, 0xd6, 0xc5, 0xf1, 0x76, 0xbf, 0x2d, 0xbe, 0xc4, 0x87, 0xf8, 0x25, - 0xd1, 0x6f, 0xec, 0xde, 0x0e, 0x84, 0x14, 0xcf, 0xe9, 0x99, 0xd6, 0x8e, 0xe3, 0x12, 0x7f, 0x3f, - 0x91, 0xd5, 0x23, 0xcc, 0xac, 0xef, 0x8d, 0x0b, 0xa9, 0x1f, 0x45, 0xe5, 0xf7, 0x5d, 0xe6, 0xf4, - 0xc8, 0x18, 0xc1, 0x6b, 0xc7, 0x11, 0x04, 0xd6, 0x0e, 0xe9, 0x99, 0x63, 0x74, 0x5f, 0x3c, 0x8a, - 0xae, 0xcf, 0x9c, 0x6e, 0xdd, 0x71, 0x59, 0xc0, 0xfc, 0x51, 0x22, 0xfd, 0x1f, 0x1a, 0x94, 0x96, - 0x6d, 0xdb, 0x27, 0x41, 0xb0, 0xee, 0xd3, 0xbe, 0x87, 0xde, 0x81, 0x69, 0x6e, 0x89, 0x6d, 0x32, - 0xb3, 0xa2, 0xcd, 0x6b, 0x0b, 0xc5, 0xa5, 0x57, 0x8d, 0x90, 0xb1, 0xa1, 0x32, 0x4e, 0x7c, 0xc2, - 0xb1, 0x8d, 0xbd, 0x45, 0xe3, 0xc1, 0xf6, 0xb7, 0x89, 0xc5, 0xee, 0x13, 0x66, 0x36, 0xd0, 0x93, - 0x41, 0xed, 0xc2, 0x70, 0x50, 0x83, 0xe4, 0x0c, 0xc7, 0x5c, 0x51, 0x1f, 0x4a, 0x1d, 0x2e, 0xea, - 0x3e, 0xe9, 0x6d, 0x13, 0x3f, 0xa8, 0x64, 0xe6, 0xb3, 0x0b, 0xc5, 0xa5, 0xd7, 0x27, 0x74, 0xbb, - 0xb1, 0x9e, 0xf0, 0x68, 0x5c, 0x91, 0x02, 0x4b, 0xca, 0x61, 0x80, 0x53, 0x62, 0xf4, 0x3f, 0x6a, - 0x50, 0x56, 0x2d, 0xbd, 0xe7, 0x04, 0x0c, 0x7d, 0x73, 0xcc, 0x5a, 0xe3, 0xf9, 0xac, 0xe5, 0xd4, - 0xc2, 0xd6, 0xb2, 0x14, 0x3d, 0x1d, 0x9d, 0x28, 0x96, 0x9a, 0x90, 0x77, 0x18, 0xe9, 0x45, 0x26, - 0xbe, 0x31, 0xa9, 0x89, 0xaa, 0xba, 0x8d, 0x19, 0x29, 0x28, 0xdf, 0xe4, 0x2c, 0x71, 0xc8, 0x59, - 0x7f, 0x2f, 0x0b, 0xb3, 0x2a, 0x5a, 0xcb, 0x64, 0xd6, 0xce, 0x39, 0x38, 0xf1, 0x47, 0x1a, 0xcc, - 0x9a, 0xb6, 0x4d, 0xec, 0xf5, 0x33, 0x76, 0xe5, 0x27, 0xa5, 0x58, 0x6e, 0x55, 0x9a, 0x3b, 0x1e, - 0x17, 0x88, 0xde, 0xd7, 0x60, 0xce, 0x27, 0x3d, 0xba, 0x37, 0xa2, 0x48, 0xf6, 0xf4, 0x8a, 0x7c, - 0x4a, 0x2a, 0x32, 0x87, 0xc7, 0xf9, 0xe3, 0xc3, 0x84, 0xea, 0xff, 0xd4, 0xe0, 0xd2, 0xb2, 0xe7, - 0x75, 0x1d, 0x62, 0x6f, 0xd1, 0xff, 0xf1, 0x6c, 0xfa, 0xb3, 0x06, 0x28, 0x6d, 0xeb, 0x39, 0xe4, - 0x93, 0x95, 0xce, 0xa7, 0x37, 0x27, 0xce, 0xa7, 0x94, 0xc2, 0x47, 0x64, 0xd4, 0x4f, 0xb2, 0x30, - 0x97, 0x46, 0xfc, 0x38, 0xa7, 0xfe, 0x7b, 0x39, 0xf5, 0xab, 0x1c, 0xcc, 0xad, 0x74, 0xfb, 0x01, - 0x23, 0x7e, 0x4a, 0xc9, 0x17, 0xef, 0x8d, 0x1f, 0x68, 0x50, 0x26, 0xed, 0x36, 0xb1, 0x98, 0xb3, - 0x47, 0xce, 0xd0, 0x19, 0x15, 0x29, 0xb5, 0xbc, 0x36, 0xc2, 0x1c, 0x8f, 0x89, 0x43, 0xdf, 0x87, - 0xd9, 0xf8, 0xac, 0xd9, 0x6a, 0x74, 0xa9, 0xb5, 0x1b, 0xf9, 0xe1, 0xe6, 0xa4, 0x3a, 0x34, 0x5b, - 0x1b, 0x84, 0x25, 0xa1, 0xb0, 0x36, 0xca, 0x17, 0x8f, 0x8b, 0x42, 0xb7, 0xa1, 0xc4, 0x28, 0x33, - 0xbb, 0x91, 0xf9, 0xb9, 0x79, 0x6d, 0x21, 0x9b, 0xd4, 0x87, 0x2d, 0x05, 0x86, 0x53, 0x98, 0x68, - 0x09, 0x40, 0x7c, 0xb7, 0xcc, 0x0e, 0x09, 0x2a, 0x79, 0x41, 0x17, 0xdf, 0xf7, 0x56, 0x0c, 0xc1, - 0x0a, 0x16, 0xba, 0x09, 0x45, 0xab, 0xef, 0xfb, 0xc4, 0x65, 0xfc, 0xbb, 0x72, 0x51, 0x10, 0xcd, - 0x49, 0xa2, 0xe2, 0x4a, 0x02, 0xc2, 0x2a, 0x9e, 0xfe, 0x77, 0x0d, 0x8a, 0x6b, 0x9d, 0xff, 0x83, - 0x09, 0xe6, 0x43, 0x0d, 0x2e, 0x2b, 0x86, 0x9e, 0x43, 0xc1, 0x7d, 0x27, 0x5d, 0x70, 0x27, 0xb6, - 0x50, 0xd1, 0xf6, 0x88, 0x6a, 0xfb, 0xd3, 0x2c, 0x94, 0x15, 0xac, 0xb0, 0xd4, 0xda, 0x00, 0x34, - 0xbe, 0xf7, 0x33, 0xf5, 0xa1, 0xc2, 0xf7, 0xe3, 0x72, 0x7b, 0x48, 0xb9, 0xed, 0xc2, 0xb5, 0xb5, - 0xc7, 0x8c, 0xf8, 0xae, 0xd9, 0x5d, 0x73, 0x99, 0xc3, 0xf6, 0x31, 0x69, 0x13, 0x9f, 0xb8, 0x16, - 0x41, 0xf3, 0x90, 0x73, 0xcd, 0x1e, 0x11, 0xee, 0x28, 0x34, 0x4a, 0x92, 0x75, 0x6e, 0xc3, 0xec, - 0x11, 0x2c, 0x20, 0xa8, 0x0e, 0x05, 0xfe, 0x6f, 0xe0, 0x99, 0x16, 0xa9, 0x64, 0x04, 0xda, 0xac, - 0x44, 0x2b, 0x6c, 0x44, 0x00, 0x9c, 0xe0, 0xe8, 0xff, 0xd6, 0xa0, 0x2c, 0xc4, 0x2f, 0x07, 0x01, - 0xb5, 0x1c, 0x93, 0x39, 0xd4, 0x3d, 0x9f, 0x3e, 0x5b, 0x36, 0xa5, 0x44, 0x69, 0xff, 0x89, 0x47, - 0x0a, 0x41, 0x1d, 0x5f, 0x52, 0x52, 0xdc, 0x97, 0x47, 0xf8, 0xe3, 0x31, 0x89, 0xfa, 0x87, 0x59, - 0x28, 0x2a, 0x97, 0x8f, 0x1e, 0x42, 0xd6, 0xa3, 0xb6, 0xb4, 0x79, 0xe2, 0x5d, 0xa1, 0x45, 0xed, - 0x44, 0x8d, 0xa9, 0xe1, 0xa0, 0x96, 0xe5, 0x27, 0x9c, 0x23, 0xfa, 0xa1, 0x06, 0x97, 0x48, 0xca, - 0xab, 0xc2, 0x3b, 0xc5, 0xa5, 0xf5, 0x89, 0xf3, 0xf9, 0xf0, 0xd8, 0x68, 0xa0, 0xe1, 0xa0, 0x76, - 0x69, 0x04, 0x38, 0x22, 0x12, 0x7d, 0x0e, 0xb2, 0x8e, 0x17, 0x86, 0x75, 0xa9, 0x71, 0x85, 0x2b, - 0xd8, 0x6c, 0x05, 0x07, 0x83, 0x5a, 0xa1, 0xd9, 0x92, 0x0b, 0x0c, 0xe6, 0x08, 0xe8, 0x6d, 0xc8, - 0x7b, 0xd4, 0x67, 0xbc, 0xd9, 0x70, 0x8f, 0x7c, 0x69, 0x52, 0x1d, 0x79, 0xa4, 0xd9, 0x2d, 0xea, - 0xb3, 0xa4, 0xe2, 0xf0, 0xaf, 0x00, 0x87, 0x6c, 0xd1, 0x37, 0x20, 0xe7, 0x52, 0x9b, 0x88, 0x9e, - 0x54, 0x5c, 0xba, 0x33, 0x31, 0x7b, 0x6a, 0x93, 0xc4, 0xf0, 0x69, 0x91, 0x02, 0xfc, 0x48, 0x30, - 0xd5, 0x7f, 0xa3, 0xc1, 0xa5, 0x74, 0x48, 0xa4, 0xb3, 0x42, 0x3b, 0x3e, 0x2b, 0xe2, 0x44, 0xcb, - 0x1c, 0x99, 0x68, 0x0d, 0xc8, 0xf6, 0x1d, 0xbb, 0x92, 0x15, 0x08, 0xaf, 0x4a, 0x84, 0xec, 0x5b, - 0xcd, 0xd5, 0x83, 0x41, 0xed, 0xe5, 0xa3, 0x5e, 0x01, 0xd8, 0xbe, 0x47, 0x02, 0xe3, 0xad, 0xe6, - 0x2a, 0xe6, 0xc4, 0xfa, 0xef, 0x35, 0x98, 0x92, 0x7d, 0x1e, 0x3d, 0x84, 0x9c, 0xe5, 0xd8, 0xbe, - 0x0c, 0xbd, 0x13, 0x4e, 0x16, 0xb1, 0xa2, 0x2b, 0xcd, 0x55, 0x8c, 0x05, 0x43, 0xf4, 0x08, 0x2e, - 0x92, 0xc7, 0x16, 0xf1, 0x98, 0x4c, 0xaf, 0x13, 0xb2, 0xbe, 0x24, 0x59, 0x5f, 0x5c, 0x13, 0xcc, - 0xb0, 0x64, 0xaa, 0xb7, 0x21, 0x2f, 0x10, 0xd0, 0x2b, 0x90, 0x71, 0x3c, 0xa1, 0x7e, 0xa9, 0x31, - 0x37, 0x1c, 0xd4, 0x32, 0xcd, 0x56, 0x3a, 0xb2, 0x32, 0x8e, 0xc7, 0x87, 0x19, 0xcf, 0x27, 0x6d, - 0xe7, 0xf1, 0x3d, 0xe2, 0x76, 0xd8, 0x8e, 0xb8, 0xdf, 0x7c, 0xd2, 0x78, 0x5b, 0x0a, 0x0c, 0xa7, - 0x30, 0xf5, 0x5f, 0x6a, 0x80, 0xee, 0xf7, 0xbb, 0xcc, 0xb1, 0xcc, 0x80, 0x09, 0xf7, 0x36, 0xdd, - 0x36, 0x45, 0xaf, 0x40, 0x5e, 0xf4, 0x67, 0xe9, 0xd5, 0x38, 0xdc, 0xc2, 0x00, 0x08, 0x61, 0xe8, - 0x6d, 0xc8, 0x79, 0xd4, 0x3e, 0xf1, 0x13, 0x40, 0x2a, 0xad, 0xe3, 0x2b, 0x6e, 0x51, 0x3b, 0xc0, - 0x82, 0xaf, 0xfe, 0x9e, 0x06, 0x85, 0x38, 0xe4, 0x79, 0xec, 0xf0, 0x28, 0x17, 0x1a, 0xe5, 0x55, - 0x7c, 0x9f, 0x61, 0x01, 0x79, 0x8e, 0xe8, 0xba, 0x0d, 0xd3, 0xe2, 0x6d, 0xc8, 0xa2, 0x5d, 0x19, - 0x62, 0xd7, 0xa3, 0x11, 0xa1, 0x25, 0xcf, 0x0f, 0x94, 0xdf, 0x38, 0xc6, 0xd6, 0xff, 0x95, 0x85, - 0x99, 0x0d, 0xc2, 0xbe, 0x4b, 0xfd, 0xdd, 0x16, 0xed, 0x3a, 0xd6, 0xfe, 0x39, 0x14, 0xf3, 0x36, - 0xe4, 0xfd, 0x7e, 0x97, 0x44, 0x17, 0xbc, 0x3c, 0x71, 0x3e, 0xab, 0xfa, 0xe2, 0x7e, 0x97, 0x24, - 0x7e, 0xe4, 0x5f, 0x01, 0x0e, 0xd9, 0xa3, 0x3b, 0x70, 0xd9, 0x4c, 0x6d, 0x85, 0x61, 0x29, 0x2b, - 0x88, 0x78, 0xbb, 0x9c, 0x5e, 0x18, 0x03, 0x3c, 0x8a, 0x8b, 0x16, 0xf8, 0xa5, 0x3a, 0xd4, 0xe7, - 0xc5, 0x97, 0x4f, 0xd1, 0x5a, 0xa3, 0x14, 0x5e, 0x68, 0x78, 0x86, 0x63, 0x28, 0xba, 0x01, 0x25, - 0xe6, 0x10, 0x3f, 0x82, 0x88, 0x3a, 0x95, 0x6f, 0x94, 0xc5, 0xbc, 0xad, 0x9c, 0xe3, 0x14, 0x16, - 0x0a, 0xa0, 0x10, 0xd0, 0xbe, 0x6f, 0xf1, 0xda, 0x24, 0x26, 0xe7, 0xe2, 0xd2, 0xdd, 0xd3, 0x5d, - 0x45, 0x1c, 0x75, 0x33, 0xbc, 0x52, 0x6d, 0x46, 0xcc, 0x71, 0x22, 0x47, 0xff, 0x93, 0x06, 0xb3, - 0x29, 0xa2, 0x73, 0x18, 0x49, 0xb7, 0xd3, 0x23, 0xe9, 0x9d, 0x53, 0x19, 0x79, 0xc4, 0x50, 0xfa, - 0x3d, 0xb8, 0x96, 0x42, 0xe3, 0x05, 0x7e, 0x93, 0x99, 0xac, 0x1f, 0xa0, 0xcf, 0xc3, 0x34, 0x2f, - 0xf4, 0x1b, 0xc9, 0x24, 0x14, 0x2b, 0xbb, 0x21, 0xcf, 0x71, 0x8c, 0xc1, 0xb7, 0x20, 0xf9, 0xe0, - 0xea, 0x50, 0x57, 0xa4, 0x9c, 0xb2, 0x05, 0xad, 0xc7, 0x10, 0xac, 0x60, 0xe9, 0x7f, 0xc8, 0x8c, - 0x5c, 0x6a, 0x8b, 0x10, 0x1f, 0xdd, 0x82, 0x19, 0x53, 0x79, 0xe6, 0x0b, 0x2a, 0x9a, 0x08, 0xbe, - 0xd9, 0xe1, 0xa0, 0x36, 0xa3, 0xbe, 0xff, 0x05, 0x38, 0x8d, 0x87, 0x08, 0x4c, 0x3b, 0x9e, 0xdc, - 0x1c, 0xc3, 0x2b, 0xbb, 0x35, 0x79, 0x11, 0x16, 0xf4, 0x89, 0xa5, 0xf1, 0xca, 0x18, 0xb3, 0x46, - 0x35, 0xc8, 0xb7, 0xbf, 0x63, 0xbb, 0x51, 0x52, 0x14, 0xf8, 0x9d, 0xde, 0xfd, 0xda, 0xea, 0x46, - 0x80, 0xc3, 0x73, 0xc4, 0xf8, 0x42, 0xb8, 0x49, 0xfc, 0x3d, 0xc7, 0x22, 0x51, 0x6f, 0xff, 0xea, - 0xa4, 0x9a, 0x48, 0x7a, 0x65, 0xf0, 0x48, 0x56, 0xca, 0x88, 0x37, 0x56, 0xe4, 0xf0, 0xdd, 0xf0, - 0x13, 0x87, 0x87, 0x35, 0xba, 0x09, 0x39, 0xde, 0x12, 0xa5, 0x17, 0x5f, 0x8e, 0x0a, 0xe1, 0xd6, - 0xbe, 0x47, 0x0e, 0x06, 0xb5, 0xb4, 0x0b, 0xf8, 0x21, 0x16, 0xe8, 0x13, 0x0f, 0xb9, 0x71, 0xc1, - 0xcd, 0x1e, 0xd7, 0xce, 0x73, 0xa7, 0x69, 0xe7, 0xbf, 0xce, 0x8f, 0x44, 0x0d, 0x2f, 0x5e, 0xe8, - 0x0d, 0x28, 0xd8, 0x8e, 0xcf, 0x97, 0x7a, 0xea, 0x4a, 0x43, 0xab, 0x91, 0xb2, 0xab, 0x11, 0xe0, - 0x40, 0xfd, 0xc0, 0x09, 0x01, 0xb2, 0x20, 0xd7, 0xf6, 0x69, 0x4f, 0x0e, 0x8b, 0xa7, 0xab, 0xac, - 0x3c, 0x88, 0x13, 0xe3, 0xef, 0xfa, 0xb4, 0x87, 0x05, 0x73, 0xf4, 0x08, 0x32, 0x8c, 0x8a, 0xcb, - 0x39, 0x13, 0x11, 0x20, 0x45, 0x64, 0xb6, 0x28, 0xce, 0x30, 0xca, 0xc3, 0x3f, 0x48, 0x07, 0xdd, - 0xad, 0x13, 0x06, 0x5d, 0x12, 0xfe, 0x71, 0xa4, 0xc5, 0xac, 0x79, 0x59, 0xf0, 0x46, 0x0a, 0x76, - 0xd2, 0x33, 0xc7, 0x4a, 0xfc, 0x43, 0xb8, 0x68, 0x86, 0x3e, 0xb9, 0x28, 0x7c, 0xf2, 0x15, 0x3e, - 0xdb, 0x2c, 0x47, 0xce, 0x58, 0x7c, 0xc6, 0xdf, 0xcf, 0x7c, 0x3b, 0xfe, 0x6b, 0x96, 0xc1, 0x3d, - 0x1c, 0x12, 0x61, 0xc9, 0x0e, 0xbd, 0x0e, 0x33, 0xc4, 0x35, 0xb7, 0xbb, 0xe4, 0x1e, 0xed, 0x74, - 0x1c, 0xb7, 0x53, 0x99, 0x9a, 0xd7, 0x16, 0xa6, 0x1b, 0x57, 0xa5, 0x2e, 0x33, 0x6b, 0x2a, 0x10, - 0xa7, 0x71, 0x0f, 0xeb, 0x70, 0xd3, 0x13, 0x74, 0xb8, 0x28, 0xce, 0x0b, 0x47, 0xc5, 0xb9, 0xfe, - 0xb3, 0x2c, 0xa0, 0x94, 0xc7, 0x78, 0x4d, 0x0d, 0xf8, 0x7a, 0x32, 0xe3, 0xaa, 0xc7, 0xb2, 0x6b, - 0x9c, 0x55, 0xff, 0x8a, 0xad, 0x4f, 0xc3, 0xd3, 0x32, 0x91, 0x07, 0x25, 0xe6, 0x9b, 0xed, 0xb6, - 0x63, 0x09, 0xad, 0x64, 0xd0, 0xbf, 0xf6, 0x0c, 0x1d, 0xc4, 0x1f, 0x17, 0x8d, 0xd8, 0x1d, 0x5b, - 0x0a, 0xb5, 0xf2, 0x44, 0xa6, 0x9c, 0xe2, 0x94, 0x04, 0xf4, 0xae, 0x06, 0x65, 0x3e, 0x5b, 0xa8, - 0x28, 0x72, 0xeb, 0xff, 0xf2, 0xf3, 0x8b, 0xc5, 0x23, 0x1c, 0x92, 0x15, 0x74, 0x14, 0x82, 0xc7, - 0xa4, 0xe9, 0x7f, 0xd3, 0x60, 0x6e, 0xcc, 0x23, 0xfd, 0xf3, 0x78, 0x5d, 0xed, 0x42, 0x9e, 0x77, - 0xc9, 0xa8, 0x27, 0xad, 0x9f, 0xca, 0xd7, 0x49, 0x7f, 0x4e, 0x1a, 0x3a, 0x3f, 0x0b, 0x70, 0x28, - 0x44, 0x5f, 0x84, 0x99, 0xd4, 0xde, 0x76, 0xfc, 0x63, 0x86, 0xfe, 0xbb, 0x3c, 0x94, 0x23, 0xbe, - 0xc1, 0x66, 0xbf, 0xd7, 0x33, 0xfd, 0xf3, 0x18, 0x67, 0x7f, 0xac, 0xc1, 0x65, 0x35, 0x30, 0x9d, - 0xf8, 0x8a, 0x1a, 0xa7, 0xba, 0xa2, 0x30, 0x36, 0xae, 0x49, 0xd9, 0x97, 0x37, 0xd2, 0x22, 0xf0, - 0xa8, 0x4c, 0xf4, 0x5b, 0x0d, 0xae, 0x87, 0x52, 0xe4, 0xeb, 0xfb, 0x08, 0x85, 0x0c, 0xd4, 0xb3, - 0x50, 0xea, 0x33, 0x52, 0xa9, 0xeb, 0xcb, 0xcf, 0x90, 0x87, 0x9f, 0xa9, 0x0d, 0xfa, 0x85, 0x06, - 0x57, 0x43, 0x84, 0x51, 0x3d, 0x73, 0x67, 0xa6, 0xe7, 0xa7, 0xa5, 0x9e, 0x57, 0x97, 0x0f, 0x13, - 0x84, 0x0f, 0x97, 0xcf, 0x07, 0xf3, 0x5e, 0xb4, 0x3a, 0x56, 0xf2, 0x27, 0x53, 0x66, 0x7c, 0xf7, - 0x4c, 0x66, 0x8e, 0x18, 0x86, 0x13, 0x39, 0xfa, 0x23, 0xb8, 0xd2, 0x32, 0x3b, 0x8e, 0x2b, 0x26, - 0xca, 0x75, 0xc2, 0x1e, 0x78, 0xfc, 0x87, 0xa8, 0xd1, 0x9e, 0xd9, 0x09, 0xc3, 0x3e, 0xab, 0xac, - 0x87, 0x66, 0x87, 0x60, 0x01, 0xe1, 0x3b, 0x6d, 0xd7, 0xe9, 0x39, 0x4c, 0x0e, 0xab, 0x71, 0x3a, - 0xdd, 0xe3, 0x87, 0x38, 0x84, 0xe9, 0x26, 0x94, 0xd4, 0xbd, 0xf4, 0x45, 0x3c, 0x0d, 0xbe, 0x9f, - 0x81, 0x29, 0xd9, 0x67, 0xd1, 0x0d, 0x65, 0x21, 0x0d, 0x45, 0x54, 0x8e, 0x5f, 0x46, 0xd1, 0x86, - 0x5c, 0x85, 0x33, 0xc7, 0xe4, 0x69, 0x9f, 0x39, 0x5d, 0x23, 0xfc, 0xdf, 0x11, 0x46, 0xd3, 0x65, - 0x0f, 0xfc, 0x4d, 0xe6, 0x3b, 0x6e, 0x27, 0x7c, 0xda, 0x51, 0x16, 0xe7, 0xcf, 0xc2, 0x14, 0x71, - 0xc5, 0x96, 0x2d, 0xa6, 0x95, 0x7c, 0xa3, 0x38, 0x1c, 0xd4, 0xa6, 0xd6, 0xc2, 0x23, 0x1c, 0xc1, - 0xf8, 0xa2, 0xe7, 0x58, 0x3d, 0x8f, 0x4f, 0x8c, 0x62, 0xa2, 0xcb, 0x87, 0x8b, 0x5e, 0x73, 0xe5, - 0x7e, 0x4b, 0x4c, 0x91, 0x31, 0x34, 0xc2, 0x5c, 0x89, 0x1e, 0xa3, 0x14, 0x4c, 0x7e, 0x86, 0x63, - 0xa8, 0x4e, 0xa0, 0x3c, 0x3a, 0xf9, 0xbe, 0x80, 0x3b, 0x6f, 0x6c, 0x3d, 0x79, 0x5a, 0xbd, 0xf0, - 0xc1, 0xd3, 0xea, 0x85, 0x8f, 0x9e, 0x56, 0x2f, 0xbc, 0x3b, 0xac, 0x6a, 0x4f, 0x86, 0x55, 0xed, - 0x83, 0x61, 0x55, 0xfb, 0x68, 0x58, 0xd5, 0xfe, 0x32, 0xac, 0x6a, 0x3f, 0xff, 0x6b, 0xf5, 0xc2, - 0xd7, 0x8d, 0xc9, 0xfe, 0x7b, 0xcf, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x1d, 0x87, 0x1b, - 0x0f, 0x24, 0x00, 0x00, + 0x90, 0xd0, 0x22, 0xe0, 0xb0, 0x80, 0x04, 0x37, 0xc4, 0x0d, 0x4e, 0x5c, 0xf8, 0x0b, 0xb8, 0x71, + 0xab, 0x38, 0xed, 0x0a, 0x21, 0xf6, 0x64, 0x51, 0x23, 0x40, 0x1c, 0xe0, 0x0f, 0x08, 0x17, 0xf4, + 0xde, 0xbc, 0x99, 0x79, 0x63, 0x27, 0x4d, 0x9d, 0xa4, 0x41, 0x82, 0x3d, 0xc5, 0x7e, 0xbf, 0xef, + 0xf7, 0xfb, 0x7e, 0x0e, 0xbc, 0x69, 0xba, 0xcc, 0x27, 0xa6, 0xe1, 0xd0, 0x7a, 0xf8, 0xa9, 0xee, + 0xed, 0x76, 0xea, 0xa6, 0xe7, 0x04, 0x75, 0x8b, 0xba, 0xcc, 0xa7, 0x5d, 0xaf, 0x6b, 0xba, 0xa4, + 0xbe, 0xb7, 0xb0, 0x4d, 0x98, 0xb9, 0x58, 0xef, 0x10, 0x97, 0xf8, 0x26, 0x23, 0xb6, 0xe1, 0xf9, + 0x94, 0x51, 0x64, 0x84, 0x54, 0xdf, 0x70, 0xa8, 0xfc, 0x64, 0x78, 0xbb, 0x1d, 0x83, 0xd3, 0x1b, + 0x2a, 0xbd, 0x21, 0xe9, 0x5f, 0x5a, 0x3a, 0x5a, 0x5e, 0xc0, 0x4c, 0x16, 0xd4, 0xf7, 0x16, 0xcc, + 0xae, 0xb7, 0x63, 0x2e, 0x8c, 0x4a, 0x7a, 0xe9, 0x73, 0x1d, 0x87, 0xed, 0xf4, 0xb7, 0x0d, 0x8b, + 0xf6, 0xea, 0x1d, 0xda, 0xa1, 0x75, 0x71, 0xbc, 0xdd, 0x6f, 0x8b, 0x6f, 0xe2, 0x8b, 0xf8, 0x24, + 0xd1, 0x6f, 0xee, 0x2e, 0x05, 0x42, 0x8a, 0xe7, 0xf4, 0x4c, 0x6b, 0xc7, 0x71, 0x89, 0xbf, 0x9f, + 0xc8, 0xea, 0x11, 0x66, 0xd6, 0xf7, 0xc6, 0x85, 0xd4, 0x8f, 0xa2, 0xf2, 0xfb, 0x2e, 0x73, 0x7a, + 0x64, 0x8c, 0xe0, 0xb5, 0xe3, 0x08, 0x02, 0x6b, 0x87, 0xf4, 0xcc, 0x31, 0xba, 0xcf, 0x1f, 0x45, + 0xd7, 0x67, 0x4e, 0xb7, 0xee, 0xb8, 0x2c, 0x60, 0xfe, 0x28, 0x91, 0xfe, 0x77, 0x0d, 0x4a, 0xcb, + 0xb6, 0xed, 0x93, 0x20, 0x58, 0xf7, 0x69, 0xdf, 0x43, 0xef, 0xc0, 0x0c, 0xb7, 0xc4, 0x36, 0x99, + 0x59, 0xd1, 0xae, 0x6b, 0x37, 0x8a, 0x8b, 0xaf, 0x1a, 0x21, 0x63, 0x43, 0x65, 0x9c, 0xf8, 0x84, + 0x63, 0x1b, 0x7b, 0x0b, 0xc6, 0x83, 0xed, 0x6f, 0x12, 0x8b, 0xdd, 0x27, 0xcc, 0x6c, 0xa0, 0x27, + 0x83, 0xda, 0x85, 0xe1, 0xa0, 0x06, 0xc9, 0x19, 0x8e, 0xb9, 0xa2, 0x3e, 0x94, 0x3a, 0x5c, 0xd4, + 0x7d, 0xd2, 0xdb, 0x26, 0x7e, 0x50, 0xc9, 0x5c, 0xcf, 0xde, 0x28, 0x2e, 0xbe, 0x3e, 0xa1, 0xdb, + 0x8d, 0xf5, 0x84, 0x47, 0xe3, 0xb2, 0x14, 0x58, 0x52, 0x0e, 0x03, 0x9c, 0x12, 0xa3, 0xff, 0x41, + 0x83, 0xb2, 0x6a, 0xe9, 0x3d, 0x27, 0x60, 0xe8, 0xeb, 0x63, 0xd6, 0x1a, 0xcf, 0x67, 0x2d, 0xa7, + 0x16, 0xb6, 0x96, 0xa5, 0xe8, 0x99, 0xe8, 0x44, 0xb1, 0xd4, 0x84, 0xbc, 0xc3, 0x48, 0x2f, 0x32, + 0xf1, 0x8d, 0x49, 0x4d, 0x54, 0xd5, 0x6d, 0xcc, 0x4a, 0x41, 0xf9, 0x26, 0x67, 0x89, 0x43, 0xce, + 0xfa, 0x7b, 0x59, 0x98, 0x53, 0xd1, 0x5a, 0x26, 0xb3, 0x76, 0xce, 0xc1, 0x89, 0x3f, 0xd0, 0x60, + 0xce, 0xb4, 0x6d, 0x62, 0xaf, 0x9f, 0xb1, 0x2b, 0x3f, 0x2e, 0xc5, 0x72, 0xab, 0xd2, 0xdc, 0xf1, + 0xb8, 0x40, 0xf4, 0x23, 0x0d, 0xe6, 0x7d, 0xd2, 0xa3, 0x7b, 0x23, 0x8a, 0x64, 0x4f, 0xaf, 0xc8, + 0x27, 0xa4, 0x22, 0xf3, 0x78, 0x9c, 0x3f, 0x3e, 0x4c, 0xa8, 0xfe, 0x0f, 0x0d, 0x2e, 0x2e, 0x7b, + 0x5e, 0xd7, 0x21, 0xf6, 0x16, 0xfd, 0x1f, 0xcf, 0xa6, 0x3f, 0x69, 0x80, 0xd2, 0xb6, 0x9e, 0x43, + 0x3e, 0x59, 0xe9, 0x7c, 0x7a, 0x73, 0xe2, 0x7c, 0x4a, 0x29, 0x7c, 0x44, 0x46, 0xfd, 0x38, 0x0b, + 0xf3, 0x69, 0xc4, 0x8f, 0x72, 0xea, 0xbf, 0x97, 0x53, 0xbf, 0xcc, 0xc1, 0xfc, 0x4a, 0xb7, 0x1f, + 0x30, 0xe2, 0xa7, 0x94, 0x7c, 0xf1, 0xde, 0xf8, 0x9e, 0x06, 0x65, 0xd2, 0x6e, 0x13, 0x8b, 0x39, + 0x7b, 0xe4, 0x0c, 0x9d, 0x51, 0x91, 0x52, 0xcb, 0x6b, 0x23, 0xcc, 0xf1, 0x98, 0x38, 0xf4, 0x5d, + 0x98, 0x8b, 0xcf, 0x9a, 0xad, 0x46, 0x97, 0x5a, 0xbb, 0x91, 0x1f, 0x6e, 0x4d, 0xaa, 0x43, 0xb3, + 0xb5, 0x41, 0x58, 0x12, 0x0a, 0x6b, 0xa3, 0x7c, 0xf1, 0xb8, 0x28, 0xb4, 0x04, 0x25, 0x46, 0x99, + 0xd9, 0x8d, 0xcc, 0xcf, 0x5d, 0xd7, 0x6e, 0x64, 0x93, 0xfa, 0xb0, 0xa5, 0xc0, 0x70, 0x0a, 0x13, + 0x2d, 0x02, 0x88, 0xef, 0x2d, 0xb3, 0x43, 0x82, 0x4a, 0x5e, 0xd0, 0xc5, 0xf7, 0xbd, 0x15, 0x43, + 0xb0, 0x82, 0x85, 0x6e, 0x41, 0xd1, 0xea, 0xfb, 0x3e, 0x71, 0x19, 0xff, 0x5e, 0x99, 0x12, 0x44, + 0xf3, 0x92, 0xa8, 0xb8, 0x92, 0x80, 0xb0, 0x8a, 0xa7, 0xff, 0x4d, 0x83, 0xe2, 0x5a, 0xe7, 0xff, + 0x60, 0x82, 0xf9, 0x40, 0x83, 0x4b, 0x8a, 0xa1, 0xe7, 0x50, 0x70, 0xdf, 0x49, 0x17, 0xdc, 0x89, + 0x2d, 0x54, 0xb4, 0x3d, 0xa2, 0xda, 0xfe, 0x24, 0x0b, 0x65, 0x05, 0x2b, 0x2c, 0xb5, 0x36, 0x00, + 0x8d, 0xef, 0xfd, 0x4c, 0x7d, 0xa8, 0xf0, 0xfd, 0xa8, 0xdc, 0x1e, 0x52, 0x6e, 0xbb, 0x70, 0x75, + 0xed, 0x31, 0x23, 0xbe, 0x6b, 0x76, 0xd7, 0x5c, 0xe6, 0xb0, 0x7d, 0x4c, 0xda, 0xc4, 0x27, 0xae, + 0x45, 0xd0, 0x75, 0xc8, 0xb9, 0x66, 0x8f, 0x08, 0x77, 0x14, 0x1a, 0x25, 0xc9, 0x3a, 0xb7, 0x61, + 0xf6, 0x08, 0x16, 0x10, 0x54, 0x87, 0x02, 0xff, 0x1b, 0x78, 0xa6, 0x45, 0x2a, 0x19, 0x81, 0x36, + 0x27, 0xd1, 0x0a, 0x1b, 0x11, 0x00, 0x27, 0x38, 0xfa, 0xbf, 0x35, 0x28, 0x0b, 0xf1, 0xcb, 0x41, + 0x40, 0x2d, 0xc7, 0x64, 0x0e, 0x75, 0xcf, 0xa7, 0xcf, 0x96, 0x4d, 0x29, 0x51, 0xda, 0x7f, 0xe2, + 0x91, 0x42, 0x50, 0xc7, 0x97, 0x94, 0x14, 0xf7, 0xe5, 0x11, 0xfe, 0x78, 0x4c, 0xa2, 0xfe, 0x41, + 0x16, 0x8a, 0xca, 0xe5, 0xa3, 0x87, 0x90, 0xf5, 0xa8, 0x2d, 0x6d, 0x9e, 0x78, 0x57, 0x68, 0x51, + 0x3b, 0x51, 0x63, 0x7a, 0x38, 0xa8, 0x65, 0xf9, 0x09, 0xe7, 0x88, 0xbe, 0xaf, 0xc1, 0x45, 0x92, + 0xf2, 0xaa, 0xf0, 0x4e, 0x71, 0x71, 0x7d, 0xe2, 0x7c, 0x3e, 0x3c, 0x36, 0x1a, 0x68, 0x38, 0xa8, + 0x5d, 0x1c, 0x01, 0x8e, 0x88, 0x44, 0x9f, 0x81, 0xac, 0xe3, 0x85, 0x61, 0x5d, 0x6a, 0x5c, 0xe6, + 0x0a, 0x36, 0x5b, 0xc1, 0xc1, 0xa0, 0x56, 0x68, 0xb6, 0xe4, 0x02, 0x83, 0x39, 0x02, 0x7a, 0x1b, + 0xf2, 0x1e, 0xf5, 0x19, 0x6f, 0x36, 0xdc, 0x23, 0x5f, 0x98, 0x54, 0x47, 0x1e, 0x69, 0x76, 0x8b, + 0xfa, 0x2c, 0xa9, 0x38, 0xfc, 0x5b, 0x80, 0x43, 0xb6, 0xe8, 0x6b, 0x90, 0x73, 0xa9, 0x4d, 0x44, + 0x4f, 0x2a, 0x2e, 0xde, 0x99, 0x98, 0x3d, 0xb5, 0x49, 0x62, 0xf8, 0x8c, 0x48, 0x01, 0x7e, 0x24, + 0x98, 0xea, 0xbf, 0xd6, 0xe0, 0x62, 0x3a, 0x24, 0xd2, 0x59, 0xa1, 0x1d, 0x9f, 0x15, 0x71, 0xa2, + 0x65, 0x8e, 0x4c, 0xb4, 0x06, 0x64, 0xfb, 0x8e, 0x5d, 0xc9, 0x0a, 0x84, 0x57, 0x25, 0x42, 0xf6, + 0xad, 0xe6, 0xea, 0xc1, 0xa0, 0xf6, 0xf2, 0x51, 0xaf, 0x00, 0x6c, 0xdf, 0x23, 0x81, 0xf1, 0x56, + 0x73, 0x15, 0x73, 0x62, 0xfd, 0x77, 0x1a, 0x4c, 0xcb, 0x3e, 0x8f, 0x1e, 0x42, 0xce, 0x72, 0x6c, + 0x5f, 0x86, 0xde, 0x09, 0x27, 0x8b, 0x58, 0xd1, 0x95, 0xe6, 0x2a, 0xc6, 0x82, 0x21, 0x7a, 0x04, + 0x53, 0xe4, 0xb1, 0x45, 0x3c, 0x26, 0xd3, 0xeb, 0x84, 0xac, 0x2f, 0x4a, 0xd6, 0x53, 0x6b, 0x82, + 0x19, 0x96, 0x4c, 0xf5, 0x36, 0xe4, 0x05, 0x02, 0x7a, 0x05, 0x32, 0x8e, 0x27, 0xd4, 0x2f, 0x35, + 0xe6, 0x87, 0x83, 0x5a, 0xa6, 0xd9, 0x4a, 0x47, 0x56, 0xc6, 0xf1, 0xf8, 0x30, 0xe3, 0xf9, 0xa4, + 0xed, 0x3c, 0xbe, 0x47, 0xdc, 0x0e, 0xdb, 0x11, 0xf7, 0x9b, 0x4f, 0x1a, 0x6f, 0x4b, 0x81, 0xe1, + 0x14, 0xa6, 0xfe, 0x0b, 0x0d, 0xd0, 0xfd, 0x7e, 0x97, 0x39, 0x96, 0x19, 0x30, 0xe1, 0xde, 0xa6, + 0xdb, 0xa6, 0xe8, 0x15, 0xc8, 0x8b, 0xfe, 0x2c, 0xbd, 0x1a, 0x87, 0x5b, 0x18, 0x00, 0x21, 0x0c, + 0xbd, 0x0d, 0x39, 0x8f, 0xda, 0x27, 0x7e, 0x02, 0x48, 0xa5, 0x75, 0x7c, 0xc5, 0x2d, 0x6a, 0x07, + 0x58, 0xf0, 0xd5, 0xdf, 0xd3, 0xa0, 0x10, 0x87, 0x3c, 0x8f, 0x1d, 0x1e, 0xe5, 0x42, 0xa3, 0xbc, + 0x8a, 0xef, 0x33, 0x2c, 0x20, 0xcf, 0x11, 0x5d, 0x4b, 0x30, 0x23, 0xde, 0x86, 0x2c, 0xda, 0x95, + 0x21, 0x76, 0x2d, 0x1a, 0x11, 0x5a, 0xf2, 0xfc, 0x40, 0xf9, 0x8c, 0x63, 0x6c, 0xfd, 0x9f, 0x59, + 0x98, 0xdd, 0x20, 0xec, 0xdb, 0xd4, 0xdf, 0x6d, 0xd1, 0xae, 0x63, 0xed, 0x9f, 0x43, 0x31, 0x6f, + 0x43, 0xde, 0xef, 0x77, 0x49, 0x74, 0xc1, 0xcb, 0x13, 0xe7, 0xb3, 0xaa, 0x2f, 0xee, 0x77, 0x49, + 0xe2, 0x47, 0xfe, 0x2d, 0xc0, 0x21, 0x7b, 0x74, 0x07, 0x2e, 0x99, 0xa9, 0xad, 0x30, 0x2c, 0x65, + 0x05, 0x11, 0x6f, 0x97, 0xd2, 0x0b, 0x63, 0x80, 0x47, 0x71, 0xd1, 0x0d, 0x7e, 0xa9, 0x0e, 0xf5, + 0x79, 0xf1, 0xe5, 0x53, 0xb4, 0xd6, 0x28, 0x85, 0x17, 0x1a, 0x9e, 0xe1, 0x18, 0x8a, 0x6e, 0x42, + 0x89, 0x39, 0xc4, 0x8f, 0x20, 0xa2, 0x4e, 0xe5, 0x1b, 0x65, 0x31, 0x6f, 0x2b, 0xe7, 0x38, 0x85, + 0x85, 0x02, 0x28, 0x04, 0xb4, 0xef, 0x5b, 0xbc, 0x36, 0x89, 0xc9, 0xb9, 0xb8, 0x78, 0xf7, 0x74, + 0x57, 0x11, 0x47, 0xdd, 0x2c, 0xaf, 0x54, 0x9b, 0x11, 0x73, 0x9c, 0xc8, 0xd1, 0xff, 0xa8, 0xc1, + 0x5c, 0x8a, 0xe8, 0x1c, 0x46, 0xd2, 0xed, 0xf4, 0x48, 0x7a, 0xe7, 0x54, 0x46, 0x1e, 0x31, 0x94, + 0x7e, 0x07, 0xae, 0xa6, 0xd0, 0x78, 0x81, 0xdf, 0x64, 0x26, 0xeb, 0x07, 0xe8, 0xb3, 0x30, 0xc3, + 0x0b, 0xfd, 0x46, 0x32, 0x09, 0xc5, 0xca, 0x6e, 0xc8, 0x73, 0x1c, 0x63, 0xf0, 0x2d, 0x48, 0x3e, + 0xb8, 0x3a, 0xd4, 0x15, 0x29, 0xa7, 0x6c, 0x41, 0xeb, 0x31, 0x04, 0x2b, 0x58, 0xfa, 0xef, 0x33, + 0x23, 0x97, 0xda, 0x22, 0xc4, 0x47, 0xb7, 0x61, 0xd6, 0x54, 0x9e, 0xf9, 0x82, 0x8a, 0x26, 0x82, + 0x6f, 0x6e, 0x38, 0xa8, 0xcd, 0xaa, 0xef, 0x7f, 0x01, 0x4e, 0xe3, 0x21, 0x02, 0x33, 0x8e, 0x27, + 0x37, 0xc7, 0xf0, 0xca, 0x6e, 0x4f, 0x5e, 0x84, 0x05, 0x7d, 0x62, 0x69, 0xbc, 0x32, 0xc6, 0xac, + 0x51, 0x0d, 0xf2, 0xed, 0x6f, 0xd9, 0x6e, 0x94, 0x14, 0x05, 0x7e, 0xa7, 0x77, 0xbf, 0xb2, 0xba, + 0x11, 0xe0, 0xf0, 0x1c, 0x31, 0xbe, 0x10, 0x6e, 0x12, 0x7f, 0xcf, 0xb1, 0x48, 0xd4, 0xdb, 0xbf, + 0x3c, 0xa9, 0x26, 0x92, 0x5e, 0x19, 0x3c, 0x92, 0x95, 0x32, 0xe2, 0x8d, 0x15, 0x39, 0x7c, 0x37, + 0xfc, 0xd8, 0xe1, 0x61, 0x8d, 0x6e, 0x41, 0x8e, 0xb7, 0x44, 0xe9, 0xc5, 0x97, 0xa3, 0x42, 0xb8, + 0xb5, 0xef, 0x91, 0x83, 0x41, 0x2d, 0xed, 0x02, 0x7e, 0x88, 0x05, 0xfa, 0xc4, 0x43, 0x6e, 0x5c, + 0x70, 0xb3, 0xc7, 0xb5, 0xf3, 0xdc, 0x69, 0xda, 0xf9, 0xaf, 0xf2, 0x23, 0x51, 0xc3, 0x8b, 0x17, + 0x7a, 0x03, 0x0a, 0xb6, 0xe3, 0xf3, 0xa5, 0x9e, 0xba, 0xd2, 0xd0, 0x6a, 0xa4, 0xec, 0x6a, 0x04, + 0x38, 0x50, 0xbf, 0xe0, 0x84, 0x00, 0x59, 0x90, 0x6b, 0xfb, 0xb4, 0x27, 0x87, 0xc5, 0xd3, 0x55, + 0x56, 0x1e, 0xc4, 0x89, 0xf1, 0x77, 0x7d, 0xda, 0xc3, 0x82, 0x39, 0x7a, 0x04, 0x19, 0x46, 0xc5, + 0xe5, 0x9c, 0x89, 0x08, 0x90, 0x22, 0x32, 0x5b, 0x14, 0x67, 0x18, 0xe5, 0xe1, 0x1f, 0xa4, 0x83, + 0xee, 0xf6, 0x09, 0x83, 0x2e, 0x09, 0xff, 0x38, 0xd2, 0x62, 0xd6, 0xbc, 0x2c, 0x78, 0x23, 0x05, + 0x3b, 0xe9, 0x99, 0x63, 0x25, 0xfe, 0x21, 0x4c, 0x99, 0xa1, 0x4f, 0xa6, 0x84, 0x4f, 0xbe, 0xc4, + 0x67, 0x9b, 0xe5, 0xc8, 0x19, 0x0b, 0xcf, 0xf8, 0xfd, 0xcc, 0xb7, 0xe3, 0x5f, 0xb3, 0x0c, 0xee, + 0xe1, 0x90, 0x08, 0x4b, 0x76, 0xe8, 0x75, 0x98, 0x25, 0xae, 0xb9, 0xdd, 0x25, 0xf7, 0x68, 0xa7, + 0xe3, 0xb8, 0x9d, 0xca, 0xf4, 0x75, 0xed, 0xc6, 0x4c, 0xe3, 0x8a, 0xd4, 0x65, 0x76, 0x4d, 0x05, + 0xe2, 0x34, 0xee, 0x61, 0x1d, 0x6e, 0x66, 0x82, 0x0e, 0x17, 0xc5, 0x79, 0xe1, 0xa8, 0x38, 0xd7, + 0x7f, 0x9a, 0x05, 0x94, 0xf2, 0x18, 0xaf, 0xa9, 0x01, 0x5f, 0x4f, 0x66, 0x5d, 0xf5, 0x58, 0x76, + 0x8d, 0xb3, 0xea, 0x5f, 0xb1, 0xf5, 0x69, 0x78, 0x5a, 0x26, 0xf2, 0xa0, 0xc4, 0x7c, 0xb3, 0xdd, + 0x76, 0x2c, 0xa1, 0x95, 0x0c, 0xfa, 0xd7, 0x9e, 0xa1, 0x83, 0xf8, 0x71, 0xd1, 0x88, 0xdd, 0xb1, + 0xa5, 0x50, 0x2b, 0x4f, 0x64, 0xca, 0x29, 0x4e, 0x49, 0x40, 0xef, 0x6a, 0x50, 0xe6, 0xb3, 0x85, + 0x8a, 0x22, 0xb7, 0xfe, 0x2f, 0x3e, 0xbf, 0x58, 0x3c, 0xc2, 0x21, 0x59, 0x41, 0x47, 0x21, 0x78, + 0x4c, 0x9a, 0xfe, 0x57, 0x0d, 0xe6, 0xc7, 0x3c, 0xd2, 0x3f, 0x8f, 0xd7, 0xd5, 0x2e, 0xe4, 0x79, + 0x97, 0x8c, 0x7a, 0xd2, 0xfa, 0xa9, 0x7c, 0x9d, 0xf4, 0xe7, 0xa4, 0xa1, 0xf3, 0xb3, 0x00, 0x87, + 0x42, 0xf4, 0x05, 0x98, 0x4d, 0xed, 0x6d, 0xc7, 0x3f, 0x66, 0xe8, 0xbf, 0xcd, 0x43, 0x39, 0xe2, + 0x1b, 0x6c, 0xf6, 0x7b, 0x3d, 0xd3, 0x3f, 0x8f, 0x71, 0xf6, 0x87, 0x1a, 0x5c, 0x52, 0x03, 0xd3, + 0x89, 0xaf, 0xa8, 0x71, 0xaa, 0x2b, 0x0a, 0x63, 0xe3, 0xaa, 0x94, 0x7d, 0x69, 0x23, 0x2d, 0x02, + 0x8f, 0xca, 0x44, 0xbf, 0xd1, 0xe0, 0x5a, 0x28, 0x45, 0xbe, 0xbe, 0x8f, 0x50, 0xc8, 0x40, 0x3d, + 0x0b, 0xa5, 0x3e, 0x25, 0x95, 0xba, 0xb6, 0xfc, 0x0c, 0x79, 0xf8, 0x99, 0xda, 0xa0, 0x9f, 0x6b, + 0x70, 0x25, 0x44, 0x18, 0xd5, 0x33, 0x77, 0x66, 0x7a, 0x7e, 0x52, 0xea, 0x79, 0x65, 0xf9, 0x30, + 0x41, 0xf8, 0x70, 0xf9, 0x7c, 0x30, 0xef, 0x45, 0xab, 0x63, 0x25, 0x7f, 0x32, 0x65, 0xc6, 0x77, + 0xcf, 0x64, 0xe6, 0x88, 0x61, 0x38, 0x91, 0xa3, 0x3f, 0x82, 0xcb, 0x2d, 0xb3, 0xe3, 0xb8, 0x62, + 0xa2, 0x5c, 0x27, 0xec, 0x81, 0xc7, 0x3f, 0x88, 0x1a, 0xed, 0x99, 0x9d, 0x30, 0xec, 0xb3, 0xca, + 0x7a, 0x68, 0x76, 0x08, 0x16, 0x10, 0xbe, 0xd3, 0x76, 0x9d, 0x9e, 0xc3, 0xe4, 0xb0, 0x1a, 0xa7, + 0xd3, 0x3d, 0x7e, 0x88, 0x43, 0x98, 0x6e, 0x42, 0x49, 0xdd, 0x4b, 0x5f, 0xc4, 0xd3, 0xe0, 0xbf, + 0x32, 0x30, 0x2d, 0xfb, 0x2c, 0xba, 0xa9, 0x2c, 0xa4, 0xa1, 0x88, 0xca, 0xf1, 0xcb, 0x28, 0xda, + 0x90, 0xab, 0x70, 0xe6, 0x98, 0x3c, 0xed, 0x33, 0xa7, 0x6b, 0x84, 0xff, 0x1d, 0x61, 0x34, 0x5d, + 0xf6, 0xc0, 0xdf, 0x64, 0xbe, 0xe3, 0x76, 0xc2, 0xa7, 0x1d, 0x65, 0x71, 0xfe, 0x34, 0x4c, 0x13, + 0x57, 0x6c, 0xd9, 0x62, 0x5a, 0xc9, 0x37, 0x8a, 0xc3, 0x41, 0x6d, 0x7a, 0x2d, 0x3c, 0xc2, 0x11, + 0x8c, 0x2f, 0x7a, 0x8e, 0xd5, 0xf3, 0xf8, 0xc4, 0x28, 0x26, 0xba, 0x7c, 0xb8, 0xe8, 0x35, 0x57, + 0xee, 0xb7, 0xc4, 0x14, 0x19, 0x43, 0x23, 0xcc, 0x95, 0xe8, 0x31, 0x4a, 0xc1, 0xe4, 0x67, 0x38, + 0x86, 0x0a, 0xcc, 0x8e, 0xe4, 0x39, 0xa5, 0x60, 0xae, 0xc7, 0x3c, 0x25, 0x14, 0x2d, 0xc9, 0x5f, + 0x26, 0xe4, 0x4a, 0x20, 0xfa, 0x7f, 0x61, 0xe4, 0xc7, 0x85, 0xe8, 0x4d, 0x24, 0x85, 0xa9, 0x13, + 0x28, 0x8f, 0x4e, 0xd7, 0x2f, 0xc0, 0xaf, 0x8d, 0xad, 0x27, 0x4f, 0xab, 0x17, 0xde, 0x7f, 0x5a, + 0xbd, 0xf0, 0xe1, 0xd3, 0xea, 0x85, 0x77, 0x87, 0x55, 0xed, 0xc9, 0xb0, 0xaa, 0xbd, 0x3f, 0xac, + 0x6a, 0x1f, 0x0e, 0xab, 0xda, 0x9f, 0x87, 0x55, 0xed, 0x67, 0x7f, 0xa9, 0x5e, 0xf8, 0xaa, 0x31, + 0xd9, 0xbf, 0x10, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x31, 0xcc, 0xfd, 0xfc, 0x73, 0x24, 0x00, + 0x00, } func (m *AddressGroup) Marshal() (dAtA []byte, err error) { @@ -2679,6 +2681,16 @@ func (m *Service) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i -= len(m.GroupAddress) + copy(dAtA[i:], m.GroupAddress) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupAddress))) + i-- + dAtA[i] = 0x3a + if m.IGMPType != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.IGMPType)) + i-- + dAtA[i] = 0x30 + } if m.ICMPCode != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.ICMPCode)) i-- @@ -3373,6 +3385,11 @@ func (m *Service) Size() (n int) { if m.ICMPCode != nil { n += 1 + sovGenerated(uint64(*m.ICMPCode)) } + if m.IGMPType != nil { + n += 1 + sovGenerated(uint64(*m.IGMPType)) + } + l = len(m.GroupAddress) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -3910,6 +3927,8 @@ func (this *Service) String() string { `EndPort:` + valueToStringGenerated(this.EndPort) + `,`, `ICMPType:` + valueToStringGenerated(this.ICMPType) + `,`, `ICMPCode:` + valueToStringGenerated(this.ICMPCode) + `,`, + `IGMPType:` + valueToStringGenerated(this.IGMPType) + `,`, + `GroupAddress:` + fmt.Sprintf("%v", this.GroupAddress) + `,`, `}`, }, "") return s @@ -8407,6 +8426,58 @@ func (m *Service) Unmarshal(dAtA []byte) error { } } m.ICMPCode = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IGMPType", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IGMPType = &v + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GroupAddress", 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 + } + m.GroupAddress = string(dAtA[iNdEx:postIndex]) + 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 e1a23bf4a41..60584192abf 100644 --- a/pkg/apis/controlplane/v1beta2/generated.proto +++ b/pkg/apis/controlplane/v1beta2/generated.proto @@ -390,6 +390,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 int32 igmpType = 6; + + optional string 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 5ac4d8b4ac2..4c87f3385ce 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 *int32 `json:"igmpType,omitempty" protobuf:"varint,6,opt,name=igmpType"` + GroupAddress string `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 91049c1b299..8be066a1445 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go @@ -1434,6 +1434,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 = (*int32)(unsafe.Pointer(in.IGMPType)) + out.GroupAddress = in.GroupAddress return nil } @@ -1448,6 +1450,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 = (*int32)(unsafe.Pointer(in.IGMPType)) + out.GroupAddress = 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 957f7416c98..03bbcaa5cc2 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go @@ -980,6 +980,11 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(int32) **out = **in } + if in.IGMPType != nil { + in, out := &in.IGMPType, &out.IGMPType + *out = new(int32) + **out = **in + } return } diff --git a/pkg/apis/controlplane/zz_generated.deepcopy.go b/pkg/apis/controlplane/zz_generated.deepcopy.go index e5ca10a3280..ffb95f1b042 100644 --- a/pkg/apis/controlplane/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/zz_generated.deepcopy.go @@ -980,6 +980,11 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(int32) **out = **in } + if in.IGMPType != nil { + in, out := &in.IGMPType, &out.IGMPType + *out = new(int32) + **out = **in + } return } diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index bd334359756..c17d5d7b9d8 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", } @@ -501,6 +503,11 @@ const ( // RuleActionReject indicates that the traffic matching the rule must be rejected and the // client will receive a response. RuleActionReject RuleAction = "Reject" + + IGMPQuery int32 = 0x11 + IGMPReportV1 int32 = 0x12 + IGMPReportV2 int32 = 0x16 + IGMPReportV3 int32 = 0x22 ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -609,6 +616,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 +626,15 @@ type ICMPProtocol struct { ICMPType *int32 `json:"icmpType,omitempty"` ICMPCode *int32 `json:"icmpCode,omitempty"` } + +// IGMPProtocol matches IGMP traffic with IGMPType and GroupAddress. IGMPType must +// be filled with: +// IGMPQuery int32 = 0x11 +// IGMPReportV1 int32 = 0x12 +// IGMPReportV2 int32 = 0x16 +// IGMPReportV3 int32 = 0x22 +// If groupAddress is empty, all groupAddresses will be matched. +type IGMPProtocol struct { + IGMPType *int32 `json:"igmpType,omitempty"` + GroupAddress string `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..6435f0eff9d 100644 --- a/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/crd/v1alpha1/zz_generated.deepcopy.go @@ -182,6 +182,27 @@ 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(int32) + **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 +423,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 ae42535cc6a..5d8a1f885b7 100644 --- a/pkg/apiserver/openapi/zz_generated.openapi.go +++ b/pkg/apiserver/openapi/zz_generated.openapi.go @@ -1879,6 +1879,19 @@ 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{"integer"}, + Format: "int32", + }, + }, + "groupAddress": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy_test.go b/pkg/controller/networkpolicy/clusternetworkpolicy_test.go index 9f034454453..cc74ddbc866 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,14 @@ func TestProcessClusterNetworkPolicy(t *testing.T) { allowAction := crdv1alpha1.RuleActionAllow dropAction := crdv1alpha1.RuleActionDrop protocolTCP := controlplane.ProtocolTCP + query := crdv1alpha1.IGMPQuery + report := crdv1alpha1.IGMPReportV1 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}} - + queryAddr := "224.0.0.1" + reportAddr := "225.1.2.3" labelSelectorA, _ := metav1.LabelSelectorAsSelector(&selectorA) labelSelectorB, _ := metav1.LabelSelectorAsSelector(&selectorB) cgA := crdv1alpha3.ClusterGroup{ @@ -1254,6 +1258,124 @@ 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, + GroupAddress: queryAddr, + }, + }, + }, + }, + }, + }, + }, + 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: &query, + GroupAddress: queryAddr, + }, + }, + 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, + GroupAddress: reportAddr, + }, + }, + }, + }, + }, + }, + }, + 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: &report, + GroupAddress: reportAddr, + }, + }, + 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..5e434354b03 100644 --- a/pkg/controller/networkpolicy/crd_utils.go +++ b/pkg/controller/networkpolicy/crd_utils.go @@ -61,6 +61,14 @@ func toAntreaServicesForCRD(npPorts []v1alpha1.NetworkPolicyPort, npProtocols [] ICMPCode: npProtocol.ICMP.ICMPCode, }) } + if npProtocol.IGMP != nil { + curProtocol := controlplane.ProtocolIGMP + antreaServices = append(antreaServices, controlplane.Service{ + Protocol: &curProtocol, + IGMPType: npProtocol.IGMP.IGMPType, + GroupAddress: npProtocol.IGMP.GroupAddress, + }) + } } return antreaServices, namedPortExists } diff --git a/pkg/controller/networkpolicy/crd_utils_test.go b/pkg/controller/networkpolicy/crd_utils_test.go index be60d3e3bb3..90a1cfbdc49 100644 --- a/pkg/controller/networkpolicy/crd_utils_test.go +++ b/pkg/controller/networkpolicy/crd_utils_test.go @@ -30,6 +30,10 @@ import ( ) func TestToAntreaServicesForCRD(t *testing.T) { + igmpQuery := int32(17) + igmpReport := int32(18) + queryStr := "224.0.0.1" + reportStr := "225.1.2.3" tables := []struct { ports []crdv1alpha1.NetworkPolicyPort protocols []crdv1alpha1.NetworkPolicyProtocol @@ -101,6 +105,40 @@ func TestToAntreaServicesForCRD(t *testing.T) { }, expNamedPortExists: false, }, + { + protocols: []crdv1alpha1.NetworkPolicyProtocol{ + { + IGMP: &crdv1alpha1.IGMPProtocol{ + IGMPType: &igmpQuery, + GroupAddress: queryStr, + }, + }, + }, + expServices: []controlplane.Service{ + { + Protocol: &protocolIGMP, + IGMPType: &igmpQuery, + GroupAddress: queryStr, + }, + }, + }, + { + protocols: []crdv1alpha1.NetworkPolicyProtocol{ + { + IGMP: &crdv1alpha1.IGMPProtocol{ + IGMPType: &igmpReport, + GroupAddress: reportStr, + }, + }, + }, + expServices: []controlplane.Service{ + { + Protocol: &protocolIGMP, + IGMPType: &igmpReport, + GroupAddress: reportStr, + }, + }, + }, { 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..ead48aaddd2 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,80 @@ 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 { + if to.IPBlock == nil { + continue + } + toIPAddr, _, err := net.ParseCIDR(to.IPBlock.CIDR) + if err != nil { + return fmt.Sprintf("invalid multicast groupAddress 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 && (*r.Action == crdv1alpha1.RuleActionPass || *r.Action == crdv1alpha1.RuleActionReject) { + return fmt.Sprintf("multicast does not support action Pass or Reject"), false + } + } + if multicast && unicast { + return fmt.Sprintf("can not set multicast groupAddress and unicast ip address at the same time"), false + } + if multicast && otherSelectors { + return fmt.Sprintf("can not set multicast groupAddress and selectors at the same time"), false + } + } + return "", true +} + +func validateIGMPProtocol(protocol crdv1alpha1.NetworkPolicyProtocol) (string, bool) { + if protocol.IGMP.GroupAddress == "" { + return "", true + } + groupIP := net.ParseIP(protocol.IGMP.GroupAddress) + if !groupIP.IsMulticast() { + return fmt.Sprintf("groupAddress %+v is not multicast address", groupIP), false + } + + return "", true +} + +func (v *antreaPolicyValidator) validateMulticastIGMP(ingressRules, egressRules []crdv1alpha1.Rule) (string, bool) { + haveIGMP := false + haveICMP := false + for _, r := range append(ingressRules, egressRules...) { + for _, protocol := range r.Protocols { + if protocol.IGMP != nil { + haveIGMP = true + reason, allowed := validateIGMPProtocol(protocol) + if !allowed { + return reason, allowed + } + if *r.Action == crdv1alpha1.RuleActionPass || *r.Action == crdv1alpha1.RuleActionReject { + return "protocol IGMP does not support Pass or Reject", false + } + } + if protocol.ICMP != nil { + haveICMP = true + } + } + if haveIGMP && (len(r.Ports) != 0 || len(r.ToServices) != 0 || len(r.From) != 0 || len(r.To) != 0 || haveICMP) { + return "protocol IGMP can not be used with other protocols or other properties like from, to", false + } + } + 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 +744,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 +851,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 { + return fmt.Sprintf("invalid ip address: %v", err), false + } + if ipaddr.IsMulticast() { + multicast = true + } else { + unicast = true + } + } + if multicast && unicast { + return "can not set multicast groupAddress together with unicast ip address", false + } return "", true } diff --git a/pkg/util/ip/ip.go b/pkg/util/ip/ip.go index e48572b1580..ebdb6455d5a 100644 --- a/pkg/util/ip/ip.go +++ b/pkg/util/ip/ip.go @@ -158,6 +158,7 @@ func IPNetToNetIPNet(ipNet *v1beta2.IPNet) *net.IPNet { const ( ICMPProtocol = 1 + IGMPProtocol = 2 TCPProtocol = 6 UDPProtocol = 17 ICMPv6Protocol = 58 @@ -170,6 +171,8 @@ func IPProtocolNumberToString(protocolNum uint8, defaultValue string) string { switch protocolNum { case ICMPProtocol: return "ICMP" + case IGMPProtocol: + return "IGMP" case TCPProtocol: return "TCP" case UDPProtocol: