diff --git a/build/charts/antrea/conf/antrea-agent.conf b/build/charts/antrea/conf/antrea-agent.conf index e93fe46c4fb..bf5f4fac548 100644 --- a/build/charts/antrea/conf/antrea-agent.conf +++ b/build/charts/antrea/conf/antrea-agent.conf @@ -79,6 +79,9 @@ featureGates: # Enable Egress traffic shaping. {{- include "featureGate" (dict "featureGates" .Values.featureGates "name" "EgressTrafficShaping" "default" false) }} +# Enable users to protect their Kubernetes Node. +{{- include "featureGate" (dict "featureGates" .Values.featureGates "name" "HostNetworkPolicy" "default" false) }} + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: {{ .Values.ovs.bridgeName | quote }} diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index 0b08d80b60a..17f0a75d351 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -5574,6 +5574,9 @@ data: # Enable Egress traffic shaping. # EgressTrafficShaping: false + # Enable users to protect their Kubernetes Node. + # HostNetworkPolicy: false + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: "br-int" @@ -6866,7 +6869,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e59e0431902646d46cba490279184fea2bdd3c8b486b5a7b1d3ece9a91614634 + checksum/config: f27020b859ba443bfbf88673e95e57fadc74ae9c225f2c97aa7640b342695180 labels: app: antrea component: antrea-agent @@ -7107,7 +7110,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e59e0431902646d46cba490279184fea2bdd3c8b486b5a7b1d3ece9a91614634 + checksum/config: f27020b859ba443bfbf88673e95e57fadc74ae9c225f2c97aa7640b342695180 labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index 1ebcf9995d5..118492fb0df 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -5574,6 +5574,9 @@ data: # Enable Egress traffic shaping. # EgressTrafficShaping: false + # Enable users to protect their Kubernetes Node. + # HostNetworkPolicy: false + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: "br-int" @@ -6866,7 +6869,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e59e0431902646d46cba490279184fea2bdd3c8b486b5a7b1d3ece9a91614634 + checksum/config: f27020b859ba443bfbf88673e95e57fadc74ae9c225f2c97aa7640b342695180 labels: app: antrea component: antrea-agent @@ -7108,7 +7111,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e59e0431902646d46cba490279184fea2bdd3c8b486b5a7b1d3ece9a91614634 + checksum/config: f27020b859ba443bfbf88673e95e57fadc74ae9c225f2c97aa7640b342695180 labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index 572595eb632..1b4577a785d 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -5574,6 +5574,9 @@ data: # Enable Egress traffic shaping. # EgressTrafficShaping: false + # Enable users to protect their Kubernetes Node. + # HostNetworkPolicy: false + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: "br-int" @@ -6866,7 +6869,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 3b1758664de8044af1aa7454c64bd1a4911750e562e1ae9375c9c16a335a469d + checksum/config: 6e7d2494e5a3e1996e2f6ca1465455d97af508cf61f52923d9d6e7aaf54d4bf1 labels: app: antrea component: antrea-agent @@ -7105,7 +7108,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 3b1758664de8044af1aa7454c64bd1a4911750e562e1ae9375c9c16a335a469d + checksum/config: 6e7d2494e5a3e1996e2f6ca1465455d97af508cf61f52923d9d6e7aaf54d4bf1 labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index a9cc6bd36d3..cd163b5ae17 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -5587,6 +5587,9 @@ data: # Enable Egress traffic shaping. # EgressTrafficShaping: false + # Enable users to protect their Kubernetes Node. + # HostNetworkPolicy: false + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: "br-int" @@ -6879,7 +6882,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: a34de3efa658ac40c9bde28e08832dd897259fdcf639beab9d4e47531d7da948 + checksum/config: 04bd3dcf4d5a4b03dadcf85a0f2c242aa8a6a9b6debdcdc65c1d7cf5a7d5220f checksum/ipsec-secret: d0eb9c52d0cd4311b6d252a951126bf9bea27ec05590bed8a394f0f792dcb2a4 labels: app: antrea @@ -7164,7 +7167,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: a34de3efa658ac40c9bde28e08832dd897259fdcf639beab9d4e47531d7da948 + checksum/config: 04bd3dcf4d5a4b03dadcf85a0f2c242aa8a6a9b6debdcdc65c1d7cf5a7d5220f labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-windows-containerd-with-ovs.yml b/build/yamls/antrea-windows-containerd-with-ovs.yml index 2602f648e0a..ffc1e1bb0dd 100644 --- a/build/yamls/antrea-windows-containerd-with-ovs.yml +++ b/build/yamls/antrea-windows-containerd-with-ovs.yml @@ -293,7 +293,7 @@ spec: template: metadata: annotations: - checksum/agent-windows: 9580d68fcd452c53eb53272cc077b07295505b7209185d3e36619fb2f02fb935 + checksum/agent-windows: bb43d8d5840ffd71ff946d44052fefc5bd88ca5ad58ac5048d85a5cf26a7ef13 checksum/windows-config: 6ff4f8bd0b310ebe4d4612bdd9697ffb3d79e0e0eab3936420417dd5a8fc128d microsoft.com/hostprocess-inherit-user: "true" labels: diff --git a/build/yamls/antrea-windows-containerd.yml b/build/yamls/antrea-windows-containerd.yml index cab853bc539..d39bf7cb17c 100644 --- a/build/yamls/antrea-windows-containerd.yml +++ b/build/yamls/antrea-windows-containerd.yml @@ -229,7 +229,7 @@ spec: template: metadata: annotations: - checksum/agent-windows: 7749579c82f76822f449f6d6f765f07486310e8cd21cb117c8349ad1e118788b + checksum/agent-windows: 542068477bbe94774e38a839710706f2d0705ecc7f1ab9aa1a1cf3e46eb73afb checksum/windows-config: 6ff4f8bd0b310ebe4d4612bdd9697ffb3d79e0e0eab3936420417dd5a8fc128d microsoft.com/hostprocess-inherit-user: "true" labels: diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 3227814289f..59f67c1c170 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -5574,6 +5574,9 @@ data: # Enable Egress traffic shaping. # EgressTrafficShaping: false + # Enable users to protect their Kubernetes Node. + # HostNetworkPolicy: false + # Name of the OpenVSwitch bridge antrea-agent will create and use. # Make sure it doesn't conflict with your existing OpenVSwitch bridges. ovsBridge: "br-int" @@ -6866,7 +6869,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: aa947bf5c403412b9c8cfcbcc335659992f19bd428886e80f43bafa052bac1e6 + checksum/config: aee5243d7649345d098be8bd43257c2e48cefa251ed4f82a8c4b3190fdda0885 labels: app: antrea component: antrea-agent @@ -7105,7 +7108,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: aa947bf5c403412b9c8cfcbcc335659992f19bd428886e80f43bafa052bac1e6 + checksum/config: aee5243d7649345d098be8bd43257c2e48cefa251ed4f82a8c4b3190fdda0885 labels: app: antrea component: antrea-controller diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index d4464951921..c3422bce901 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -138,6 +138,7 @@ func run(o *Options) error { enableAntreaIPAM := features.DefaultFeatureGate.Enabled(features.AntreaIPAM) enableBridgingMode := enableAntreaIPAM && o.config.EnableBridgingMode l7NetworkPolicyEnabled := features.DefaultFeatureGate.Enabled(features.L7NetworkPolicy) + hostNetworkPolicyEnabled := features.DefaultFeatureGate.Enabled(features.HostNetworkPolicy) enableMulticlusterGW := features.DefaultFeatureGate.Enabled(features.Multicluster) && o.config.Multicluster.EnableGateway enableMulticlusterNP := features.DefaultFeatureGate.Enabled(features.Multicluster) && o.config.Multicluster.EnableStretchedNetworkPolicy enableFlowExporter := features.DefaultFeatureGate.Enabled(features.FlowExporter) && o.config.FlowExporter.Enable @@ -217,7 +218,7 @@ func run(o *Options) error { egressConfig := &config.EgressConfig{ ExceptCIDRs: exceptCIDRs, } - routeClient, err := route.NewClient(networkConfig, o.config.NoSNAT, o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, multicastEnabled, serviceCIDRProvider) + routeClient, err := route.NewClient(networkConfig, o.config.NoSNAT, o.config.AntreaProxy.ProxyAll, connectUplinkToBridge, hostNetworkPolicyEnabled, multicastEnabled, serviceCIDRProvider) if err != nil { return fmt.Errorf("error creating route client: %v", err) } @@ -280,7 +281,7 @@ func run(o *Options) error { o.config.ExternalNode.ExternalNodeNamespace, connectUplinkToBridge, o.enableAntreaProxy, - l7NetworkPolicyEnabled) + hostNetworkPolicyEnabled) err = agentInitializer.Initialize() if err != nil { return fmt.Errorf("error initializing agent: %v", err) @@ -469,6 +470,7 @@ func run(o *Options) error { groupIDUpdates, antreaPolicyEnabled, l7NetworkPolicyEnabled, + hostNetworkPolicyEnabled, o.enableAntreaProxy, statusManagerEnabled, multicastEnabled, diff --git a/docs/feature-gates.md b/docs/feature-gates.md index bec2c838fc1..de161ca78a3 100644 --- a/docs/feature-gates.md +++ b/docs/feature-gates.md @@ -56,6 +56,7 @@ edit the Agent configuration in the | `L7NetworkPolicy` | Agent + Controller | `false` | Alpha | v1.10 | N/A | N/A | Yes | | | `AdminNetworkPolicy` | Controller | `false` | Alpha | v1.13 | N/A | N/A | Yes | | | `EgressTrafficShaping` | Agent | `false` | Alpha | v1.14 | N/A | N/A | Yes | OVS meters should be supported | +| `HostNetworkPolicy` | Agent + Controller | `false` | Alpha | v1.15 | N/A | N/A | Yes | | ## Description and Requirements of Features @@ -404,6 +405,14 @@ this [document](antrea-l7-network-policy.md#prerequisites) for more information The `AdminNetworkPolicy` API (which currently includes the AdminNetworkPolicy and BaselineAdminNetworkPolicy objects) complements the Antrea-native policies and help cluster administrators to set security postures in a portable manner. +### HostNetworkPolicy + +`HostNetworkPolicy` enables users to protect their Kubernetes Nodes. + +#### Requirements for this Feature + +This feature is currently only supported for Nodes running Linux. + ### EgressTrafficShaping The `EgressTrafficShaping` feature gate of Antrea Agent enables traffic shaping of Egress, which could limit the diff --git a/pkg/agent/config/node_config.go b/pkg/agent/config/node_config.go index ebba7f3da9e..3bdd653e83f 100644 --- a/pkg/agent/config/node_config.go +++ b/pkg/agent/config/node_config.go @@ -50,6 +50,12 @@ const ( L7NetworkPolicyReturnPortName = "antrea-l7-tap1" ) +const ( + HostNetworkPolicyIngressRulesChain = "ANTREA-INGRESS-RULES" + HostNetworkPolicyDefaultIngressRulesChain = "ANTREA-DEFAULT-INGRESS-RULES" + HostNetworkPolicyEgressRulesChain = "ANTREA-EGRESS-RULES" +) + var ( // VirtualServiceIPv4 or VirtualServiceIPv6 is used in the following scenarios: // - The IP is used to perform SNAT for packets of Service sourced from Antrea gateway and destined for external diff --git a/pkg/agent/controller/networkpolicy/cache.go b/pkg/agent/controller/networkpolicy/cache.go index 70dd3a711f0..56dfb240f18 100644 --- a/pkg/agent/controller/networkpolicy/cache.go +++ b/pkg/agent/controller/networkpolicy/cache.go @@ -39,7 +39,7 @@ import ( ) const ( - RuleIDLength = 16 + ruleIDLength = 16 appliedToGroupIndex = "appliedToGroup" addressGroupIndex = "addressGroup" policyIndex = "policy" @@ -48,7 +48,7 @@ const ( ) // rule is the struct stored in ruleCache, it contains necessary information -// to construct a complete rule that can be used by reconciler to enforce. +// to construct a complete rule that can be used by podReconciler to enforce. // The K8s NetworkPolicy object doesn't provide ID for its rule, here we // calculate an ID based on the rule's fields. That means: // 1. If a rule's selector/services/direction changes, it becomes "another" rule. @@ -137,11 +137,11 @@ func hashRule(r *rule) string { b, _ := json.Marshal(r) hash.Write(b) hashValue := hex.EncodeToString(hash.Sum(nil)) - return hashValue[:RuleIDLength] + return hashValue[:ruleIDLength] } // CompletedRule contains IPAddresses and Pods flattened from AddressGroups and AppliedToGroups. -// It's the struct used by reconciler. +// It's the struct used by podReconciler. type CompletedRule struct { *rule // Source GroupMembers of this rule, can't coexist with ToAddresses. @@ -182,8 +182,19 @@ func (r *CompletedRule) isIGMPEgressPolicyRule() bool { return false } +func (r *CompletedRule) isHostNetworkPolicyRule(nodeName string) bool { + var targets []*v1beta.GroupMember + for _, gm := range r.TargetMembers { + targets = append(targets, gm) + } + if len(targets) == 1 && targets[0].Node != nil && targets[0].Node.Name == nodeName { + return true + } + return false +} + // ruleCache caches Antrea AddressGroups, AppliedToGroups and NetworkPolicies, -// can construct complete rules that can be used by reconciler to enforce. +// can construct complete rules that can be used by podReconciler to enforce. type ruleCache struct { appliedToSetLock sync.RWMutex // appliedToSetByGroup stores the AppliedToGroup members. diff --git a/pkg/agent/controller/networkpolicy/fqdn.go b/pkg/agent/controller/networkpolicy/fqdn.go index 882eb2fa1c1..3fe75d586ea 100644 --- a/pkg/agent/controller/networkpolicy/fqdn.go +++ b/pkg/agent/controller/networkpolicy/fqdn.go @@ -93,7 +93,7 @@ type subscriber struct { } // ruleRealizationUpdate is a rule realization result reported by policy -// rule reconciler. +// rule podReconciler. type ruleRealizationUpdate struct { ruleId string err error @@ -103,7 +103,7 @@ type ruleRealizationUpdate struct { // applied to workloads on this Node. type ruleSyncTracker struct { mutex sync.RWMutex - // updateCh is the channel used by the rule reconciler to report rule realization status. + // updateCh is the channel used by the rule podReconciler to report rule realization status. updateCh chan ruleRealizationUpdate // ruleToSubscribers keeps track of the subscribers that are currently subscribed // to each dirty rule. Once an update of the rule realization status is received, @@ -575,7 +575,7 @@ func (rst *ruleSyncTracker) Run(stopCh <-chan struct{}) { } } -// notifyRuleUpdate is an interface for the reconciler to notify the ruleSyncTracker of a +// notifyRuleUpdate is an interface for the podReconciler to notify the ruleSyncTracker of a // rule realization status. func (f *fqdnController) notifyRuleUpdate(ruleID string, err error) { f.ruleSyncTracker.updateCh <- ruleRealizationUpdate{ruleID, err} diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go index f25446e30d2..7e7cbebc07d 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go @@ -69,19 +69,20 @@ type packetInAction func(*ofctrl.PacketIn) error // Controller is responsible for watching Antrea AddressGroups, AppliedToGroups, // and NetworkPolicies, feeding them to ruleCache, getting dirty rules from -// ruleCache, invoking reconciler to reconcile them. +// ruleCache, invoking podReconciler or nodeReconciler to reconcile them. // // a.Feed AddressGroups,AppliedToGroups // and NetworkPolicies -// |-----------| <-------- |----------- | c. Reconcile dirty rules |----------- | -// | ruleCache | | Controller | ------------> | reconciler | -// | ----------| --------> |----------- | |----------- | +// |-----------| <-------- |----------- | c. Reconcile dirty rules |-------------- | +// | ruleCache | | Controller | ------------> | podReconciler | +// | ----------| --------> |----------- | |-------------- | // b. Notify dirty rules type Controller struct { // antreaPolicyEnabled indicates whether Antrea NetworkPolicy and // ClusterNetworkPolicy are enabled. - antreaPolicyEnabled bool - l7NetworkPolicyEnabled bool + antreaPolicyEnabled bool + l7NetworkPolicyEnabled bool + hostNetworkPolicyEnabled bool // antreaProxyEnabled indicates whether Antrea proxy is enabled. antreaProxyEnabled bool // statusManagerEnabled indicates whether a statusManager is configured. @@ -102,9 +103,12 @@ type Controller struct { queue workqueue.RateLimitingInterface // ruleCache maintains the desired state of NetworkPolicy rules. ruleCache *ruleCache - // reconciler provides interfaces to reconcile the desired state of + // podReconciler provides interfaces to reconcile the desired state of // NetworkPolicy rules with the actual state of Openflow entries. - reconciler Reconciler + podReconciler Reconciler + // nodeReconciler provides interfaces to reconcile the desired state of + // NetworkPolicy rules with the actual state of iptables entries. + nodeReconciler Reconciler // l7RuleReconciler provides interfaces to reconcile the desired state of // NetworkPolicy rules which have L7 rules with the actual state of Suricata rules. l7RuleReconciler L7RuleReconciler @@ -144,6 +148,7 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, groupIDUpdates <-chan string, antreaPolicyEnabled bool, l7NetworkPolicyEnabled bool, + hostNetworkPolicyEnabled bool, antreaProxyEnabled bool, statusManagerEnabled bool, multicastEnabled bool, @@ -157,18 +162,19 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, nodeConfig *config.NodeConfig) (*Controller, error) { idAllocator := newIDAllocator(asyncRuleDeleteInterval, dnsInterceptRuleID) c := &Controller{ - antreaClientProvider: antreaClientGetter, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(minRetryDelay, maxRetryDelay), "networkpolicyrule"), - ofClient: ofClient, - nodeType: nodeType, - antreaPolicyEnabled: antreaPolicyEnabled, - l7NetworkPolicyEnabled: l7NetworkPolicyEnabled, - antreaProxyEnabled: antreaProxyEnabled, - statusManagerEnabled: statusManagerEnabled, - multicastEnabled: multicastEnabled, - gwPort: gwPort, - tunPort: tunPort, - nodeConfig: nodeConfig, + antreaClientProvider: antreaClientGetter, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(minRetryDelay, maxRetryDelay), "networkpolicyrule"), + ofClient: ofClient, + nodeType: nodeType, + antreaPolicyEnabled: antreaPolicyEnabled, + l7NetworkPolicyEnabled: l7NetworkPolicyEnabled, + hostNetworkPolicyEnabled: hostNetworkPolicyEnabled, + antreaProxyEnabled: antreaProxyEnabled, + statusManagerEnabled: statusManagerEnabled, + multicastEnabled: multicastEnabled, + gwPort: gwPort, + tunPort: tunPort, + nodeConfig: nodeConfig, } if l7NetworkPolicyEnabled { @@ -186,8 +192,16 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider, c.ofClient.RegisterPacketInHandler(uint8(openflow.PacketInCategoryDNS), c.fqdnController) } } - c.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters, + c.podReconciler = newPodReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters, v4Enabled, v6Enabled, antreaPolicyEnabled, multicastEnabled) + + if c.hostNetworkPolicyEnabled { + var err error + c.nodeReconciler, err = newNodeReconciler(v4Enabled, v6Enabled) + if err != nil { + return nil, err + } + } c.ruleCache = newRuleCache(c.enqueueRule, podUpdateSubscriber, externalEntityUpdateSubscriber, groupIDUpdates, nodeType) if statusManagerEnabled { c.statusManager = newStatusController(antreaClientGetter, nodeName, c.ruleCache) @@ -445,7 +459,7 @@ func (c *Controller) GetNetworkPolicyByRuleFlowID(ruleFlowID uint32) *v1beta2.Ne } func (c *Controller) GetRuleByFlowID(ruleFlowID uint32) *types.PolicyRule { - rule, exists, err := c.reconciler.GetRuleByFlowID(ruleFlowID) + rule, exists, err := c.podReconciler.GetRuleByFlowID(ruleFlowID) if err != nil { klog.Errorf("Error when getting network policy by rule flow ID: %v", err) return nil @@ -511,7 +525,7 @@ func (c *Controller) Run(stopCh <-chan struct{}) { } klog.Infof("Starting IDAllocator worker to maintain the async rule cache") - go c.reconciler.RunIDAllocatorWorker(stopCh) + go c.podReconciler.RunIDAllocatorWorker(stopCh) if c.statusManagerEnabled { go c.statusManager.Run(stopCh) @@ -621,9 +635,14 @@ func (c *Controller) syncRule(key string) error { rule, effective, realizable := c.ruleCache.GetCompletedRule(key) if !effective { klog.V(2).InfoS("Rule was not effective, removing it", "ruleID", key) - if err := c.reconciler.Forget(key); err != nil { + if err := c.podReconciler.Forget(key); err != nil { return err } + if c.hostNetworkPolicyEnabled { + if err := c.nodeReconciler.Forget(key); err != nil { + return err + } + } if c.statusManagerEnabled { // We don't know whether this is a rule owned by Antrea Policy, but // harmless to delete it. @@ -646,7 +665,12 @@ func (c *Controller) syncRule(key string) error { return nil } - if c.l7NetworkPolicyEnabled && len(rule.L7Protocols) != 0 { + var isHostNetworkPolicy bool + if c.hostNetworkPolicyEnabled { + isHostNetworkPolicy = rule.isHostNetworkPolicyRule(c.nodeConfig.Name) + } + + if c.l7NetworkPolicyEnabled && len(rule.L7Protocols) != 0 && !isHostNetworkPolicy { // Allocate VLAN ID for the L7 rule. vlanID := c.l7VlanIDAllocator.allocate(key) rule.L7RuleVlanID = &vlanID @@ -656,23 +680,30 @@ func (c *Controller) syncRule(key string) error { } } - err := c.reconciler.Reconcile(rule) - if c.fqdnController != nil { - // No matter whether the rule reconciliation succeeds or not, fqdnController - // needs to be notified of the status. - klog.V(2).InfoS("Rule realization was done", "ruleID", key) - c.fqdnController.notifyRuleUpdate(key, err) - } - if err != nil { - return err + if isHostNetworkPolicy { + if err := c.nodeReconciler.Reconcile(rule); err != nil { + return err + } + } else { + err := c.podReconciler.Reconcile(rule) + if c.fqdnController != nil { + // No matter whether the rule reconciliation succeeds or not, fqdnController + // needs to be notified of the status. + klog.V(2).InfoS("Rule realization was done", "ruleID", key) + c.fqdnController.notifyRuleUpdate(key, err) + } + if err != nil { + return err + } } + if c.statusManagerEnabled && v1beta2.IsSourceAntreaNativePolicy(rule.SourceRef) { c.statusManager.SetRuleRealization(key, rule.PolicyUID) } return nil } -// syncRules calls the reconciler to sync all the rules after watchers complete full sync. +// syncRules calls the podReconciler to sync all the rules after watchers complete full sync. // After flows for those init events are installed, subsequent rules will be handled asynchronously // by the syncRule() function. func (c *Controller) syncRules(keys []string) error { @@ -681,7 +712,7 @@ func (c *Controller) syncRules(keys []string) error { klog.V(4).Infof("Finished syncing all rules before bookmark event (%v)", time.Since(startTime)) }() - var allRules []*CompletedRule + var allPodRules, allHostRules []*CompletedRule for _, key := range keys { rule, effective, realizable := c.ruleCache.GetCompletedRule(key) // It's normal that a rule is not effective on this Node but abnormal that it is not realizable after watchers @@ -691,7 +722,11 @@ func (c *Controller) syncRules(keys []string) error { } else if !realizable { klog.Errorf("Rule %s is effective but not realizable", key) } else { - if c.l7NetworkPolicyEnabled && len(rule.L7Protocols) != 0 { + var isHostNetworkPolicy bool + if c.hostNetworkPolicyEnabled { + isHostNetworkPolicy = rule.isHostNetworkPolicyRule(c.nodeConfig.Name) + } + if c.l7NetworkPolicyEnabled && len(rule.L7Protocols) != 0 && !isHostNetworkPolicy { // Allocate VLAN ID for the L7 rule. vlanID := c.l7VlanIDAllocator.allocate(key) rule.L7RuleVlanID = &vlanID @@ -700,14 +735,23 @@ func (c *Controller) syncRules(keys []string) error { return err } } - allRules = append(allRules, rule) + if isHostNetworkPolicy { + allHostRules = append(allHostRules, rule) + } else { + allPodRules = append(allPodRules, rule) + } + } + } + if c.hostNetworkPolicyEnabled { + if err := c.nodeReconciler.BatchReconcile(allHostRules); err != nil { + return err } } - if err := c.reconciler.BatchReconcile(allRules); err != nil { + if err := c.podReconciler.BatchReconcile(allPodRules); err != nil { return err } if c.statusManagerEnabled { - for _, rule := range allRules { + for _, rule := range allPodRules { if v1beta2.IsSourceAntreaNativePolicy(rule.SourceRef) { c.statusManager.SetRuleRealization(rule.ID, rule.PolicyUID) } diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go index d2179ce6a88..00a4237da59 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go @@ -71,9 +71,9 @@ func newTestController() (*Controller, *fake.Clientset, *mockReconciler) { ch2 := make(chan string, 100) groupIDAllocator := openflow.NewGroupAllocator() groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(groupIDAllocator, ch2)} - controller, _ := NewNetworkPolicyController(&antreaClientGetter{clientset}, nil, nil, "node1", podUpdateChannel, nil, groupCounters, ch2, true, true, true, true, false, nil, testAsyncDeleteInterval, "8.8.8.8:53", config.K8sNode, true, false, config.HostGatewayOFPort, config.DefaultTunOFPort, &config.NodeConfig{}) + controller, _ := NewNetworkPolicyController(&antreaClientGetter{clientset}, nil, nil, "node1", podUpdateChannel, nil, groupCounters, ch2, true, true, false, true, true, false, nil, testAsyncDeleteInterval, "8.8.8.8:53", config.K8sNode, true, false, config.HostGatewayOFPort, config.DefaultTunOFPort, &config.NodeConfig{}) reconciler := newMockReconciler() - controller.reconciler = reconciler + controller.podReconciler = reconciler controller.auditLogger = nil return controller, clientset, reconciler } @@ -513,7 +513,7 @@ func TestNetworkPolicyMetrics(t *testing.T) { metrics.InitializeNetworkPolicyMetrics() controller, clientset, reconciler := newTestController() - // Define functions to wait for a message from reconciler + // Define functions to wait for a message from podReconciler waitForReconcilerUpdated := func() { select { case ruleID := <-reconciler.updated: diff --git a/pkg/agent/controller/networkpolicy/node_reconciler_other.go b/pkg/agent/controller/networkpolicy/node_reconciler_other.go new file mode 100644 index 00000000000..db39a7adeac --- /dev/null +++ b/pkg/agent/controller/networkpolicy/node_reconciler_other.go @@ -0,0 +1,643 @@ +//go:build !windows +// +build !windows + +// Copyright 2023 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 networkpolicy + +import ( + "bytes" + "container/list" + "crypto/sha1" // #nosec G505: not used for security purposes + "encoding/hex" + "fmt" + "net" + "sort" + "strings" + "sync" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/agent/config" + "antrea.io/antrea/pkg/agent/types" + "antrea.io/antrea/pkg/agent/util/ipset" + "antrea.io/antrea/pkg/agent/util/iptables" + "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + secv1beta1 "antrea.io/antrea/pkg/apis/crd/v1beta1" +) + +const ( + ipsetPrefix = "ANTREA-POL" + iptablesChainPrefix = "ANTREA-POL" +) + +// ruleKey is a struct to identify a core iptables rule. +type ruleKey struct { + priority types.Priority + ruleID string +} + +type iptRulesCache struct { + keyList *list.List // keyList is a linked list to store + rules map[ruleKey]iptables.IPTablesRule + sync.Mutex +} + +type nodeReconciler struct { + iptablesClient iptables.Interface + ipsetClient ipset.Interface + ipProtocols []iptables.Protocol + cachedCoreIPTRulesMap map[string]*iptRulesCache + + // lastRealizeds caches the last realized rules. It's a mapping from ruleID to *lastRealized. + lastRealizeds sync.Map +} + +func newIPTRulesCache() *iptRulesCache { + return &iptRulesCache{ + keyList: list.New(), + rules: make(map[ruleKey]iptables.IPTablesRule), + } +} + +func newNodeReconciler(ipv4Enabled, ipv6Enabled bool) (*nodeReconciler, error) { + iptablesClient, err := iptables.New(ipv4Enabled, ipv6Enabled) + if err != nil { + return nil, err + } + var ipProtocols []iptables.Protocol + cachedCoreIPTRulesMap := make(map[string]*iptRulesCache) + if ipv4Enabled { + ipProtocols = append(ipProtocols, iptables.ProtocolIPv4) + cachedCoreIPTRulesMap[genCacheCategory(config.HostNetworkPolicyIngressRulesChain, false)] = newIPTRulesCache() + cachedCoreIPTRulesMap[genCacheCategory(config.HostNetworkPolicyEgressRulesChain, false)] = newIPTRulesCache() + } + if ipv6Enabled { + ipProtocols = append(ipProtocols, iptables.ProtocolIPv6) + cachedCoreIPTRulesMap[genCacheCategory(config.HostNetworkPolicyIngressRulesChain, true)] = newIPTRulesCache() + cachedCoreIPTRulesMap[genCacheCategory(config.HostNetworkPolicyEgressRulesChain, true)] = newIPTRulesCache() + } + return &nodeReconciler{ + iptablesClient: iptablesClient, + ipsetClient: ipset.NewClient(), + ipProtocols: ipProtocols, + cachedCoreIPTRulesMap: cachedCoreIPTRulesMap, + }, nil +} + +// Reconcile checks whether the provided rule have been enforced or not, and invoke the add or update method accordingly. +func (r *nodeReconciler) Reconcile(rule *CompletedRule) error { + klog.InfoS("Reconciling host NetworkPolicy rule", "rule", rule.ID, "policy", rule.SourceRef.ToString()) + + value, exists := r.lastRealizeds.Load(rule.ID) + var err error + if !exists { + err = r.add(rule) + } else { + err = r.update(value.(*CompletedRule), rule) + } + return err +} + +func (r *nodeReconciler) RunIDAllocatorWorker(stopCh <-chan struct{}) { + +} + +func (r *nodeReconciler) BatchReconcile(rules []*CompletedRule) error { + if len(rules) == 0 { + return nil + } + + var ingressRulesToInstall, egressRulesToInstall, rulesToInstall []*CompletedRule + for _, rule := range rules { + if _, exists := r.lastRealizeds.Load(rule.ID); exists { + klog.ErrorS(nil, "Rule should not have been realized yet: initialization phase", "rule", rule.ID) + } else { + rulesToInstall = append(rulesToInstall, rule) + if rule.Direction == v1beta2.DirectionIn { + ingressRulesToInstall = append(ingressRulesToInstall, rule) + } else { + egressRulesToInstall = append(egressRulesToInstall, rule) + } + } + } + + for _, ipProtocol := range r.ipProtocols { + isIPv6 := iptables.IsIPv6Protocol(ipProtocol) + + var allServiceIPTRules []iptables.IPTablesRule + var allServiceIPTRuleChains []string + allIPSetNames := make(map[string]string) + allCoreIPTRuleTargets := make(map[string]string) + + for _, rule := range rulesToInstall { + ruleName := rule.Name + policyName := rule.PolicyName + ruleID := rule.ID + + ipsetName := genIPSetName(policyName, ruleName, isIPv6) + ipsetAddresses := getAddresses(rule) + allIPSetNames[ruleID] = ipsetName + + serviceIPTRuleChain := genServiceIPTRuleChain(policyName, ruleName) + serviceIPTRuleTarget := getServiceIPTRuleTarget(rule.Action) + + coreIPTRuleTarget := serviceIPTRuleChain + allCoreIPTRuleTargets[ruleID] = coreIPTRuleTarget + + if err := r.syncIPSet(ipsetName, nil, ipsetAddresses, isIPv6); err != nil { + return err + } + + serviceIPTRules := buildServiceIPTRules(ipProtocol, rule.Services, serviceIPTRuleChain, serviceIPTRuleTarget) + allServiceIPTRules = append(allServiceIPTRules, serviceIPTRules...) + allServiceIPTRuleChains = append(allServiceIPTRuleChains, serviceIPTRuleChain) + } + + if err := r.syncIPTRules(allServiceIPTRuleChains, allServiceIPTRules, isIPv6); err != nil { + return err + } + + allIngressCoreIPTRules := make(map[ruleKey]iptables.IPTablesRule) + var allIngressRuleKeys []ruleKey + for _, rule := range ingressRulesToInstall { + ruleName := rule.Name + policyName := rule.PolicyName + ruleID := rule.ID + + ipsetName := allIPSetNames[ruleID] + + coreIPTRuleTarget := allCoreIPTRuleTargets[ruleID] + coreIPTRuleComment := genCoreIPTRuleComment(policyName, ruleName) + coreIPTRule := buildCoreIPTRule(config.HostNetworkPolicyIngressRulesChain, ipsetName, coreIPTRuleTarget, coreIPTRuleComment, true) + + k := ruleKey{ + priority: *genPriority(rule), + ruleID: rule.ID, + } + allIngressRuleKeys = append(allIngressRuleKeys, k) + allIngressCoreIPTRules[k] = coreIPTRule + } + if err := r.syncCoreIPTRules(allIngressRuleKeys, allIngressCoreIPTRules, config.HostNetworkPolicyIngressRulesChain, isIPv6); err != nil { + return err + } + + allEgressCoreIPTRules := make(map[ruleKey]iptables.IPTablesRule) + var allEgressRuleKeys []ruleKey + for _, rule := range egressRulesToInstall { + ruleName := rule.Name + policyName := rule.PolicyName + ruleID := rule.ID + + ipsetName := allIPSetNames[ruleID] + + coreIPTRuleTarget := allCoreIPTRuleTargets[ruleID] + coreIPTRuleComment := genCoreIPTRuleComment(policyName, ruleName) + coreIPTRule := buildCoreIPTRule(config.HostNetworkPolicyEgressRulesChain, ipsetName, coreIPTRuleTarget, coreIPTRuleComment, false) + + k := ruleKey{ + priority: *genPriority(rule), + ruleID: rule.ID, + } + allEgressRuleKeys = append(allEgressRuleKeys, k) + allEgressCoreIPTRules[k] = coreIPTRule + } + if err := r.syncCoreIPTRules(allEgressRuleKeys, allEgressCoreIPTRules, config.HostNetworkPolicyEgressRulesChain, isIPv6); err != nil { + return err + } + } + + for _, rule := range rules { + r.lastRealizeds.Store(rule.ID, rule) + } + + return nil +} + +func (r *nodeReconciler) Forget(ruleID string) error { + klog.InfoS("Forgetting rule", "rule", ruleID) + + value, exists := r.lastRealizeds.Load(ruleID) + if !exists { + return nil + } + + rule := value.(*CompletedRule) + ruleName := rule.Name + policyName := rule.PolicyName + + serviceIPTRuleChain := genServiceIPTRuleChain(policyName, ruleName) + coreIPTRuleChain := getCoreIPTRuleChain(rule) + + priority := genPriority(rule) + + for _, ipProtocol := range r.ipProtocols { + isIPv6 := iptables.IsIPv6Protocol(ipProtocol) + + if err := r.removeCoreIPRule(ruleID, priority, coreIPTRuleChain, isIPv6); err != nil { + return err + } + if err := r.removeIPSet(genIPSetName(policyName, ruleName, isIPv6)); err != nil { + return err + } + if err := r.iptablesClient.DeleteChain(ipProtocol, iptables.FilterTable, serviceIPTRuleChain); err != nil { + return err + } + } + + r.lastRealizeds.Delete(ruleID) + + return nil +} + +func (r *nodeReconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule, bool, error) { + return nil, false, nil +} + +func (r *nodeReconciler) update(preRule, rule *CompletedRule) error { + ruleName := rule.Name + policyName := rule.PolicyName + + preIPSetAddresses := getAddresses(preRule) + ipsetAddresses := getAddresses(rule) + + for _, ipProtocol := range r.ipProtocols { + isIPv6 := iptables.IsIPv6Protocol(ipProtocol) + + ipsetName := genIPSetName(policyName, ruleName, isIPv6) + if err := r.syncIPSet(ipsetName, preIPSetAddresses, ipsetAddresses, isIPv6); err != nil { + return err + } + } + + r.lastRealizeds.Store(rule.ID, rule) + + return nil +} + +func (r *nodeReconciler) add(rule *CompletedRule) error { + ruleName := rule.Name + policyName := rule.PolicyName + + ipsetAddresses := getAddresses(rule) + + serviceIPTRuleChain := genServiceIPTRuleChain(policyName, ruleName) + serviceIPTRuleTarget := getServiceIPTRuleTarget(rule.Action) + + coreIPTRuleChain := getCoreIPTRuleChain(rule) + coreIPTRuleTarget := serviceIPTRuleChain + coreIPTRuleComment := genCoreIPTRuleComment(policyName, ruleName) + + isIngress := isIngressRule(rule) + + for _, ipProtocol := range r.ipProtocols { + isIPv6 := iptables.IsIPv6Protocol(ipProtocol) + + ipsetName := genIPSetName(policyName, ruleName, isIPv6) + if err := r.syncIPSet(ipsetName, nil, ipsetAddresses, isIPv6); err != nil { + return err + } + + serviceIPTRules := buildServiceIPTRules(ipProtocol, rule.Services, serviceIPTRuleChain, serviceIPTRuleTarget) + if err := r.syncIPTRules([]string{serviceIPTRuleChain}, serviceIPTRules, isIPv6); err != nil { + return err + } + + coreIPTRule := buildCoreIPTRule(coreIPTRuleChain, ipsetName, coreIPTRuleTarget, coreIPTRuleComment, isIngress) + if err := r.syncCoreIPTRule(rule.ID, genPriority(rule), coreIPTRuleChain, coreIPTRule, isIPv6); err != nil { + return err + } + } + + r.lastRealizeds.Store(rule.ID, rule) + + return nil +} + +func (r *nodeReconciler) syncIPSet(name string, preAddresses, curAddresses v1beta2.GroupMemberSet, isIPv6 bool) error { + membersToAdd := getIPSetMembers(curAddresses.Difference(preAddresses), isIPv6) + membersToDel := getIPSetMembers(preAddresses.Difference(curAddresses), isIPv6) + + // CreateIPSet ignores the error when the set already exists. + if err := r.ipsetClient.CreateIPSet(name, ipset.HashIP, isIPv6); err != nil { + return err + } + for member := range membersToAdd { + if err := r.ipsetClient.AddEntry(name, member); err != nil { + return err + } + } + for member := range membersToDel { + if err := r.ipsetClient.DelEntry(name, member); err != nil { + return err + } + } + return nil +} + +func (r *nodeReconciler) removeIPSet(name string) error { + return r.ipsetClient.DestroyIPSet(name) +} + +func (r *nodeReconciler) syncIPTRules(chains []string, rules []iptables.IPTablesRule, isIPv6 bool) error { + iptablesData := bytes.NewBuffer(nil) + writeLine(iptablesData, "*filter") + for _, chain := range chains { + writeLine(iptablesData, iptables.MakeChainLine(chain)) + } + for _, rule := range rules { + writeLine(iptablesData, rule.GetSpec()) + } + writeLine(iptablesData, "COMMIT") + return r.iptablesClient.Restore(iptablesData.String(), false, isIPv6) +} + +func (r *nodeReconciler) syncCoreIPTRule(ruleID string, priority *types.Priority, chain string, rule iptables.IPTablesRule, isIPv6 bool) error { + // There are 4 categories of cached core iptables rules: + // - For IPv4, iptables rules installed in chain ANTREA-INGRESS-RULE for ingress policy rules. + // - For IPv6, ip6tables rules installed in chain ANTREA-INGRESS-RULE for ingress policy rules. + // - For IPv4, iptables rules installed in chain ANTREA-EGRESS-RULE for egress policy rules. + // - For IPv6, ip6tables rules installed in chain ANTREA-EGRESS-RULE for egress policy rules. + cachedCoreIPTRules := r.cachedCoreIPTRulesMap[genCacheCategory(chain, isIPv6)] + cachedCoreIPTRules.Lock() + defer cachedCoreIPTRules.Unlock() + + cachedRuleKeyList := cachedCoreIPTRules.keyList + cachedRules := cachedCoreIPTRules.rules + + // Generate a rule key with the current priority of the policy rule. + curRuleKey := ruleKey{ + priority: *priority, + ruleID: ruleID, + } + + // rules stores the latest iptables rules to sync. + var rules []iptables.IPTablesRule + + // KeyEleToRemove is the element storing stale rule key that should be removed. + var keyEleToMark *list.Element + + if cachedRuleKeyList.Len() == 0 { + // If there is no cached iptables rule, just append the iptables rule to add. + rules = append(rules, rule) + } else { + var ruleAdded bool + // Iterate all cached rule keys. + for keyEle := cachedRuleKeyList.Front(); keyEle != nil; keyEle = keyEle.Next() { + ruleKey := keyEle.Value.(ruleKey) + rulePriority := ruleKey.priority + // If the current iptables rule has not been added to the iptables rule list to be synced, and its corresponding + // priority is not less than the priority stored in the element, which means the location before the element + // is the appropriate place to insert the rule key for the current policy rule. + if !rulePriority.Less(*priority) && !ruleAdded { + rules = append(rules, rule) + // keyEleToMark is used to mark the element stored in the list where all rule keys are stored. The rule key + // generated from current priority will be inserted before this element. + keyEleToMark = keyEle + ruleAdded = true + } + // Get the cached iptables rule with its corresponding rule key and add it the iptables rule list to be synced. + rules = append(rules, cachedRules[ruleKey]) + } + } + + // Sync the iptables rule list. + if err := r.syncIPTRules([]string{chain}, rules, isIPv6); err != nil { + return err + } + + // Insert the rule key for the current iptables rule before the marked element and store the current iptables rule. + if keyEleToMark == nil { + cachedRuleKeyList.PushBack(curRuleKey) + } else { + cachedRuleKeyList.InsertBefore(curRuleKey, keyEleToMark) + } + cachedRules[curRuleKey] = rule + + return nil +} + +func (r *nodeReconciler) syncCoreIPTRules(ruleKeys []ruleKey, ruleMap map[ruleKey]iptables.IPTablesRule, chain string, isIPv6 bool) error { + cachedCoreIPTRules := r.cachedCoreIPTRulesMap[genCacheCategory(chain, isIPv6)] + cachedCoreIPTRules.Lock() + defer cachedCoreIPTRules.Unlock() + + sort.Slice(ruleKeys, func(i, j int) bool { + return ruleKeys[i].priority.Less(ruleKeys[j].priority) + }) + + var rules []iptables.IPTablesRule + for _, ruleKey := range ruleKeys { + rules = append(rules, ruleMap[ruleKey]) + } + + // Sync the iptables rule list. + if err := r.syncIPTRules([]string{chain}, rules, isIPv6); err != nil { + return err + } + + // Update the cache. + for _, ruleKey := range ruleKeys { + cachedCoreIPTRules.keyList.PushBack(ruleKey) + } + cachedCoreIPTRules.rules = ruleMap + + return nil +} + +func (r *nodeReconciler) removeCoreIPRule(policyRuleID string, priority *types.Priority, chain string, isIPv6 bool) error { + cachedCoreIPTRules := r.cachedCoreIPTRulesMap[genCacheCategory(chain, isIPv6)] + cachedCoreIPTRules.Lock() + defer cachedCoreIPTRules.Unlock() + + cachedRuleKeyList := cachedCoreIPTRules.keyList + cachedRules := cachedCoreIPTRules.rules + + ruleKeyToRemove := ruleKey{ + priority: *priority, + ruleID: policyRuleID, + } + + // rules stores the latest iptables rules to sync. + var rules []iptables.IPTablesRule + var keyEleToRemove *list.Element + for keyEle := cachedRuleKeyList.Front(); keyEle != nil; keyEle = keyEle.Next() { + // If the rule key stored in the element equals the previous rule key, it means this is a rule key for + // stale iptables rule, which should not be added to the iptables rule list to be synced. + ruleKey := keyEle.Value.(ruleKey) + if ruleKeyToRemove == ruleKey { + keyEleToRemove = keyEle + continue + } + // Get the cached iptables rule with its corresponding rule key and add it the iptables rule list to be synced. + rules = append(rules, cachedRules[ruleKey]) + } + + // Sync the iptables rule list. + if err := r.syncIPTRules([]string{chain}, rules, isIPv6); err != nil { + return err + } + + cachedRuleKeyList.Remove(keyEleToRemove) + delete(cachedRules, ruleKeyToRemove) + + return nil +} + +func writeLine(buf *bytes.Buffer, line string) { + // We avoid strings.Join for performance reasons. + buf.WriteString(line) + buf.WriteByte('\n') +} + +func genPriority(rule *CompletedRule) *types.Priority { + if rule == nil { + return nil + } + return &types.Priority{ + TierPriority: *rule.TierPriority, + PolicyPriority: *rule.PolicyPriority, + RulePriority: rule.Priority, + } +} + +func isIngressRule(rule *CompletedRule) bool { + if rule.Direction == v1beta2.DirectionIn { + return true + } + return false +} + +func getAddresses(rule *CompletedRule) v1beta2.GroupMemberSet { + if rule == nil { + return nil + } + if rule.Direction == v1beta2.DirectionIn { + return rule.FromAddresses + } + return rule.ToAddresses +} + +func genIPSetName(policyName, ruleName string, isIPv6 bool) string { + hash := sha1.New() // #nosec G401: not used for security purposes + hash.Write([]byte(policyName)) + hash.Write([]byte{'-'}) + hash.Write([]byte(ruleName)) + hash.Write([]byte{'-'}) + if isIPv6 { + hash.Write([]byte("6")) + } else { + hash.Write([]byte("4")) + } + return fmt.Sprintf("%s-%s", ipsetPrefix, strings.ToUpper(hex.EncodeToString(hash.Sum(nil))[:16])) +} + +func getIPSetMembers(addresses v1beta2.GroupMemberSet, isIPv6 bool) sets.Set[string] { + ips := sets.New[string]() + for _, member := range addresses { + for _, ip := range member.IPs { + ipAddr := net.IP(ip) + if isIPv6 && ipAddr.To4() == nil || !isIPv6 && ipAddr.To4() != nil { + ips.Insert(ipAddr.String()) + } + } + } + return ips +} + +func genCacheCategory(chain string, isIPv6 bool) string { + if isIPv6 { + return fmt.Sprintf("%s_6", chain) + } + return fmt.Sprintf("%s_4", chain) +} + +func buildCoreIPTRule(chain, ipset, target, comment string, isIngress bool) iptables.IPTablesRule { + builder := iptables.NewRuleBuilder(chain) + if isIngress { + builder = builder.MatchIPSetSrc(ipset) + } else { + builder = builder.MatchIPSetDst(ipset) + } + return builder.SetTarget(target). + SetComment(comment). + Done() +} + +func getCoreIPTRuleChain(rule *CompletedRule) string { + if rule.Direction == v1beta2.DirectionIn { + return config.HostNetworkPolicyIngressRulesChain + } + return config.HostNetworkPolicyEgressRulesChain +} + +func genCoreIPTRuleComment(policyName, ruleName string) string { + return fmt.Sprintf(`"Antrea: for NetworkPolicy %s,rule %s"`, policyName, ruleName) +} + +func buildServiceIPTRules(ipProtocol iptables.Protocol, services []v1beta2.Service, chain string, target string) []iptables.IPTablesRule { + var rules []iptables.IPTablesRule + baseBuilder := iptables.NewRuleBuilder(chain) + for _, svc := range services { + var rule iptables.IPTablesRule + copiedBuilder := baseBuilder.CopyBuilder().SetTarget(target) + transProtocol := *svc.Protocol + switch transProtocol { + case v1beta2.ProtocolTCP: + fallthrough + case v1beta2.ProtocolUDP: + fallthrough + case v1beta2.ProtocolSCTP: + rule = copiedBuilder.MatchTransProtocol(transProtocol). + MatchSrcPort(svc.SrcPort, svc.SrcEndPort). + MatchDstPort(svc.Port, svc.EndPort). + Done() + case v1beta2.ProtocolICMP: + rule = copiedBuilder.MatchICMP(svc.ICMPType, svc.ICMPCode, ipProtocol). + Done() + case v1beta2.ProtocolIGMP: + rule = copiedBuilder.MatchIGMP(svc.IGMPType, svc.GroupAddress). + Done() + rules = append(rules, rule) + } + rules = append(rules, rule) + } + return rules +} + +func genServiceIPTRuleChain(policyName, ruleName string) string { + hash := sha1.New() // #nosec G401: not used for security purposes + hash.Write([]byte(policyName)) + hash.Write([]byte{'-'}) + hash.Write([]byte(ruleName)) + + return fmt.Sprintf("%s-%s", iptablesChainPrefix, strings.ToUpper(hex.EncodeToString(hash.Sum(nil))[:16])) +} + +func getServiceIPTRuleTarget(ruleAction *secv1beta1.RuleAction) string { + var target string + switch *ruleAction { + case secv1beta1.RuleActionDrop: + target = iptables.DropTarget + case secv1beta1.RuleActionReject: + target = iptables.RejectTarget + case secv1beta1.RuleActionAllow: + target = iptables.AcceptTarget + default: + klog.InfoS("Unknown rule action", "action", ruleAction) + } + return target +} diff --git a/pkg/agent/controller/networkpolicy/node_reconciler_other_test.go b/pkg/agent/controller/networkpolicy/node_reconciler_other_test.go new file mode 100644 index 00000000000..504176e1fa3 --- /dev/null +++ b/pkg/agent/controller/networkpolicy/node_reconciler_other_test.go @@ -0,0 +1,808 @@ +//go:build !windows +// +build !windows + +// Copyright 2023 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 networkpolicy + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "antrea.io/antrea/pkg/agent/util/ipset" + ipsettest "antrea.io/antrea/pkg/agent/util/ipset/testing" + "antrea.io/antrea/pkg/agent/util/iptables" + iptablestest "antrea.io/antrea/pkg/agent/util/iptables/testing" + "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + secv1beta1 "antrea.io/antrea/pkg/apis/crd/v1beta1" +) + +var ( + ruleActionAllow = secv1beta1.RuleActionAllow + + policyPriority1 = float64(1) + tierPriority1 = int32(1) + tierPriority2 = int32(2) + + ruleID1 = "rule-1" + ruleID2 = "rule-2" + ruleID3 = "rule-3" + ruleID4 = "rule-4" + ruleID5 = "rule-5" + ingressRule1 = &CompletedRule{ + rule: &rule{ + ID: ruleID1, + Name: "rule-01", + PolicyName: "ingress-policy", + Direction: v1beta2.DirectionIn, + Services: []v1beta2.Service{serviceTCP80, serviceTCP443}, + Action: &ruleActionAllow, + Priority: 1, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority1, + SourceRef: &cnp1, + }, + FromAddresses: dualAddressGroup1, + ToAddresses: nil, + } + updatedIngressRule1 = &CompletedRule{ + rule: &rule{ + ID: ruleID1, + Name: "rule-01", + PolicyName: "ingress-policy", + Direction: v1beta2.DirectionIn, + Services: []v1beta2.Service{serviceTCP80, serviceTCP443}, + Action: &ruleActionAllow, + Priority: 1, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority1, + SourceRef: &cnp1, + }, + FromAddresses: addressGroup2, + ToAddresses: nil, + } + ingressRule2 = &CompletedRule{ + rule: &rule{ + ID: ruleID2, + Name: "rule-02", + PolicyName: "ingress-policy", + Direction: v1beta2.DirectionIn, + Services: []v1beta2.Service{serviceTCP443}, + Action: &ruleActionAllow, + Priority: 2, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority2, + SourceRef: &cnp1, + }, + FromAddresses: dualAddressGroup1, + ToAddresses: nil, + } + ingressRule3 = &CompletedRule{ + rule: &rule{ + ID: ruleID3, + Name: "rule-03", + PolicyName: "ingress-policy", + Direction: v1beta2.DirectionIn, + Services: []v1beta2.Service{serviceTCP8080}, + Action: &ruleActionAllow, + Priority: 1, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority2, + SourceRef: &cnp1, + }, + FromAddresses: dualAddressGroup1, + ToAddresses: nil, + } + egressRule1 = &CompletedRule{ + rule: &rule{ + ID: ruleID4, + Name: "rule-11", + PolicyName: "egress-policy", + Direction: v1beta2.DirectionOut, + Services: []v1beta2.Service{serviceTCP80, serviceTCP443}, + Action: &ruleActionAllow, + Priority: 1, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority1, + SourceRef: &cnp1, + }, + ToAddresses: dualAddressGroup1, + FromAddresses: nil, + } + egressRule2 = &CompletedRule{ + rule: &rule{ + ID: ruleID5, + Name: "rule-12", + PolicyName: "egress-policy", + Direction: v1beta2.DirectionOut, + Services: []v1beta2.Service{serviceTCP443}, + Action: &ruleActionAllow, + Priority: 2, + PolicyPriority: &policyPriority1, + TierPriority: &tierPriority2, + SourceRef: &cnp1, + }, + ToAddresses: dualAddressGroup1, + FromAddresses: nil, + } +) + +func newTestNodeReconciler(mockIPTablesClient *iptablestest.MockInterface, mockIPSetClient *ipsettest.MockInterface, ipv4Enabled, ipv6Enabled bool) *nodeReconciler { + r, _ := newNodeReconciler(ipv4Enabled, ipv6Enabled) + r.iptablesClient = mockIPTablesClient + r.ipsetClient = mockIPSetClient + return r +} + +func TestNodeReconcilerReconcile(t *testing.T) { + tests := []struct { + name string + rules []*CompletedRule + ipv4Enabled bool + ipv6Enabled bool + expectedCalls func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4, add a rule", + ipv4Enabled: true, + ipv6Enabled: false, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + }, + rules: []*CompletedRule{ + ingressRule1, + }, + }, + { + name: "IPv6, add a rule", + ipv4Enabled: false, + ipv6Enabled: true, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-B00F39B6EF12A879", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-B00F39B6EF12A879", "2002:1a23:fb44::1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-B00F39B6EF12A879 src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, true) + }, + rules: []*CompletedRule{ + ingressRule1, + }, + }, + { + name: "Dualstack, add a rule", + ipv4Enabled: true, + ipv6Enabled: true, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + mockIPSetClient.CreateIPSet("ANTREA-POL-B00F39B6EF12A879", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-B00F39B6EF12A879", "2002:1a23:fb44::1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-B00F39B6EF12A879 src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, true) + }, + rules: []*CompletedRule{ + ingressRule1, + }, + }, + { + name: "IPv4, add multiple rules", + ipv4Enabled: true, + ipv6Enabled: false, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + + mockIPSetClient.CreateIPSet("ANTREA-POL-27A6013D5666B38F", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-27A6013D5666B38F", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + + mockIPSetClient.CreateIPSet("ANTREA-POL-03FD9A4F343E7036", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-03FD9A4F343E7036", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-44DEFAF8D9027629 - [0:0] +-A ANTREA-POL-44DEFAF8D9027629 -j ACCEPT -p tcp --dport 8080 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-03FD9A4F343E7036 src -j ANTREA-POL-44DEFAF8D9027629 -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-03" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + }, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + ingressRule3, + }, + }, + { + name: "IPv4, update a rule's from IP addresses", + ipv4Enabled: true, + ipv6Enabled: false, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false).Times(2) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + + mockIPSetClient.DelEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.2") + }, + rules: []*CompletedRule{ + ingressRule1, + updatedIngressRule1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + mockIPTablesClient := iptablestest.NewMockInterface(controller) + mockIPSetClient := ipsettest.NewMockInterface(controller) + + r := newTestNodeReconciler(mockIPTablesClient, mockIPSetClient, tt.ipv4Enabled, tt.ipv6Enabled) + + tt.expectedCalls(mockIPTablesClient.EXPECT(), mockIPSetClient.EXPECT()) + for _, rule := range tt.rules { + assert.NoError(t, r.Reconcile(rule)) + } + }) + } +} + +func TestNodeReconcilerForget(t *testing.T) { + tests := []struct { + name string + ruleIDsToCache []*CompletedRule + ruleIDsToForget []string + expectedCalls func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "unknown rule", + ruleIDsToForget: []string{"unknown-rule"}, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + }, + }, + { + name: "remove the only cached rule", + ruleIDsToCache: []*CompletedRule{ + ingressRule1, + }, + ruleIDsToForget: []string{ruleID1}, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + mockIPTablesClient.Restore(gomock.Any(), false, false).Times(2) + + mockIPSetClient.DestroyIPSet("ANTREA-POL-675350EDAEE0B86E") + mockIPTablesClient.DeleteChain(iptables.ProtocolIPv4, iptables.FilterTable, "ANTREA-POL-8B80F355CD750B7F") + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +COMMIT +`, false, false) + }, + }, + { + name: "remove multiple rules", + ruleIDsToCache: []*CompletedRule{ + ingressRule1, + ingressRule2, + ingressRule3, + }, + ruleIDsToForget: []string{ruleID3, ruleID2}, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet(gomock.Any(), ipset.HashIP, false).Times(3) + mockIPSetClient.AddEntry(gomock.Any(), "1.1.1.1").Times(3) + mockIPTablesClient.Restore(gomock.Any(), false, false).Times(6) + + mockIPSetClient.DestroyIPSet("ANTREA-POL-03FD9A4F343E7036") + mockIPTablesClient.DeleteChain(iptables.ProtocolIPv4, iptables.FilterTable, "ANTREA-POL-44DEFAF8D9027629") + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + + mockIPSetClient.DestroyIPSet("ANTREA-POL-27A6013D5666B38F") + mockIPTablesClient.DeleteChain(iptables.ProtocolIPv4, iptables.FilterTable, "ANTREA-POL-DF198C4D22D7DD8A") + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + mockIPTablesClient := iptablestest.NewMockInterface(controller) + mockIPSetClient := ipsettest.NewMockInterface(controller) + + r := newTestNodeReconciler(mockIPTablesClient, mockIPSetClient, true, false) + tt.expectedCalls(mockIPTablesClient.EXPECT(), mockIPSetClient.EXPECT()) + + for _, rule := range tt.ruleIDsToCache { + assert.NoError(t, r.Reconcile(rule)) + } + for _, rule := range tt.ruleIDsToForget { + assert.NoError(t, r.Forget(rule)) + } + }) + } +} + +func TestNodeReconcilerBatchReconcile(t *testing.T) { + tests := []struct { + name string + ipv4Enabled bool + ipv6Enabled bool + rules []*CompletedRule + expectedCalls func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4, only ingress rules", + ipv4Enabled: true, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-27A6013D5666B38F", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-27A6013D5666B38F", "1.1.1.1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +COMMIT +`, false, false) + }, + }, + { + name: "IPv6, only ingress rules", + ipv6Enabled: true, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-B00F39B6EF12A879", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-B00F39B6EF12A879", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-DAE0216934B1DD68", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-DAE0216934B1DD68", "2002:1a23:fb44::1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-DAE0216934B1DD68 src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-B00F39B6EF12A879 src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +COMMIT +`, false, true) + }, + }, + { + name: "dualstack, only ingress rules", + ipv4Enabled: true, + ipv6Enabled: true, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-27A6013D5666B38F", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-27A6013D5666B38F", "1.1.1.1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +COMMIT +`, false, false) + + mockIPSetClient.CreateIPSet("ANTREA-POL-B00F39B6EF12A879", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-B00F39B6EF12A879", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-DAE0216934B1DD68", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-DAE0216934B1DD68", "2002:1a23:fb44::1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-DAE0216934B1DD68 src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-B00F39B6EF12A879 src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +COMMIT +`, false, true) + }, + }, + { + name: "IPv4, only egress rules", + ipv4Enabled: true, + rules: []*CompletedRule{ + egressRule1, + egressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-BBAAA19777E4A8DF", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-BBAAA19777E4A8DF", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-2EC3A159D5DEBDCE", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-2EC3A159D5DEBDCE", "1.1.1.1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-2EC3A159D5DEBDCE dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-BBAAA19777E4A8DF dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +COMMIT +`, false, false) + }, + }, + { + name: "IPv6, only egress rules", + ipv6Enabled: true, + rules: []*CompletedRule{ + egressRule1, + egressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-9A8E118445994507", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-9A8E118445994507", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-EDAE6F8124E64541", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-EDAE6F8124E64541", "2002:1a23:fb44::1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-EDAE6F8124E64541 dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-9A8E118445994507 dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +COMMIT +`, false, true) + }, + }, + { + name: "dualstack, only egress rules", + ipv4Enabled: true, + ipv6Enabled: true, + rules: []*CompletedRule{ + egressRule1, + egressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-BBAAA19777E4A8DF", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-BBAAA19777E4A8DF", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-2EC3A159D5DEBDCE", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-2EC3A159D5DEBDCE", "1.1.1.1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-2EC3A159D5DEBDCE dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-BBAAA19777E4A8DF dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +COMMIT +`, false, false) + + mockIPSetClient.CreateIPSet("ANTREA-POL-9A8E118445994507", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-9A8E118445994507", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-EDAE6F8124E64541", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-EDAE6F8124E64541", "2002:1a23:fb44::1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-EDAE6F8124E64541 dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-9A8E118445994507 dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +COMMIT +`, false, true) + }, + }, + { + name: "IPv4, ingress and egress rules", + ipv4Enabled: true, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + egressRule1, + egressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-675350EDAEE0B86E", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-675350EDAEE0B86E", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-27A6013D5666B38F", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-27A6013D5666B38F", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-BBAAA19777E4A8DF", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-BBAAA19777E4A8DF", "1.1.1.1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-2EC3A159D5DEBDCE", ipset.HashIP, false) + mockIPSetClient.AddEntry("ANTREA-POL-2EC3A159D5DEBDCE", "1.1.1.1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, false) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-27A6013D5666B38F src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-675350EDAEE0B86E src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, false) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-2EC3A159D5DEBDCE dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-BBAAA19777E4A8DF dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, false) + }, + }, + { + name: "IPv6, ingress and egress rules", + ipv6Enabled: true, + rules: []*CompletedRule{ + ingressRule1, + ingressRule2, + egressRule1, + egressRule2, + }, + expectedCalls: func(mockIPTablesClient *iptablestest.MockInterfaceMockRecorder, mockIPSetClient *ipsettest.MockInterfaceMockRecorder) { + mockIPSetClient.CreateIPSet("ANTREA-POL-B00F39B6EF12A879", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-B00F39B6EF12A879", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-DAE0216934B1DD68", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-DAE0216934B1DD68", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-9A8E118445994507", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-9A8E118445994507", "2002:1a23:fb44::1") + + mockIPSetClient.CreateIPSet("ANTREA-POL-EDAE6F8124E64541", ipset.HashIP, true) + mockIPSetClient.AddEntry("ANTREA-POL-EDAE6F8124E64541", "2002:1a23:fb44::1") + + mockIPTablesClient.Restore(`*filter +:ANTREA-POL-8B80F355CD750B7F - [0:0] +:ANTREA-POL-DF198C4D22D7DD8A - [0:0] +:ANTREA-POL-7AE594092016AB49 - [0:0] +:ANTREA-POL-3D230AF0BC1AE946 - [0:0] +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-8B80F355CD750B7F -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-DF198C4D22D7DD8A -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 80 +-A ANTREA-POL-7AE594092016AB49 -j ACCEPT -p tcp --dport 443 +-A ANTREA-POL-3D230AF0BC1AE946 -j ACCEPT -p tcp --dport 443 +COMMIT +`, false, true) + + mockIPTablesClient.Restore(`*filter +:ANTREA-INGRESS-RULES - [0:0] +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-DAE0216934B1DD68 src -j ANTREA-POL-DF198C4D22D7DD8A -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-02" +-A ANTREA-INGRESS-RULES -m set --match-ipset ANTREA-POL-B00F39B6EF12A879 src -j ANTREA-POL-8B80F355CD750B7F -m comment --comment "Antrea: for NetworkPolicy ingress-policy,rule rule-01" +COMMIT +`, false, true) + mockIPTablesClient.Restore(`*filter +:ANTREA-EGRESS-RULES - [0:0] +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-EDAE6F8124E64541 dst -j ANTREA-POL-3D230AF0BC1AE946 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-12" +-A ANTREA-EGRESS-RULES -m set --match-ipset ANTREA-POL-9A8E118445994507 dst -j ANTREA-POL-7AE594092016AB49 -m comment --comment "Antrea: for NetworkPolicy egress-policy,rule rule-11" +COMMIT +`, false, true) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + mockIPTablesClient := iptablestest.NewMockInterface(controller) + mockIPSetClient := ipsettest.NewMockInterface(controller) + + r := newTestNodeReconciler(mockIPTablesClient, mockIPSetClient, tt.ipv4Enabled, tt.ipv6Enabled) + tt.expectedCalls(mockIPTablesClient.EXPECT(), mockIPSetClient.EXPECT()) + + assert.NoError(t, r.BatchReconcile(tt.rules)) + }) + } +} diff --git a/pkg/agent/controller/networkpolicy/node_reconciler_windows.go b/pkg/agent/controller/networkpolicy/node_reconciler_windows.go new file mode 100644 index 00000000000..e67c1a24e99 --- /dev/null +++ b/pkg/agent/controller/networkpolicy/node_reconciler_windows.go @@ -0,0 +1,46 @@ +//go:build windows +// +build windows + +// Copyright 2023 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 networkpolicy + +import "antrea.io/antrea/pkg/agent/types" + +type nodeReconciler struct { +} + +func (r *nodeReconciler) Reconcile(rule *CompletedRule) error { + return nil +} + +func (r *nodeReconciler) RunIDAllocatorWorker(stopCh <-chan struct{}) { +} + +func (r *nodeReconciler) BatchReconcile(rules []*CompletedRule) error { + return nil +} + +func (r *nodeReconciler) Forget(ruleID string) error { + return nil +} + +func (r *nodeReconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule, bool, error) { + return nil, false, nil +} + +func newNodeReconciler(ipv4Enabled, ipv6Enabled bool) (*nodeReconciler, error) { + return &nodeReconciler{}, nil +} diff --git a/pkg/agent/controller/networkpolicy/reconciler.go b/pkg/agent/controller/networkpolicy/pod_reconciler.go similarity index 94% rename from pkg/agent/controller/networkpolicy/reconciler.go rename to pkg/agent/controller/networkpolicy/pod_reconciler.go index 135fcb109ad..0e43c3f7fa6 100644 --- a/pkg/agent/controller/networkpolicy/reconciler.go +++ b/pkg/agent/controller/networkpolicy/pod_reconciler.go @@ -98,10 +98,10 @@ func normalizeServices(services []v1beta2.Service) servicesKey { return servicesKey(b.String()) } -// lastRealized is the struct cached by reconciler. It's used to track the +// lastRealized is the struct cached by podReconciler. It's used to track the // actual state of rules we have enforced, so that we can know how to reconcile // a rule when it's updated/removed. -// It includes the last version of CompletedRule the reconciler has realized +// It includes the last version of CompletedRule the podReconciler has realized // and the related runtime information including the ofIDs, the Openflow ports // or the IP addresses of the target Pods got from the InterfaceStore. // @@ -194,11 +194,11 @@ type tablePriorityAssigner struct { mutex sync.RWMutex } -// reconciler implements Reconciler. +// podReconciler implements Reconciler. // Note that although its Reconcile and Forget methods are thread-safe, it's // assumed each rule can only be processed by a single client at any given // time. Different rules can be processed in parallel. -type reconciler struct { +type podReconciler struct { // ofClient is the Openflow interface. ofClient openflow.Client @@ -220,11 +220,11 @@ type reconciler struct { ipv6Enabled bool // fqdnController manages dns cache of FQDN rules. It provides interfaces for the - // reconciler to register FQDN policy rules and query the IP addresses corresponded + // podReconciler to register FQDN policy rules and query the IP addresses corresponded // to a FQDN. fqdnController *fqdnController - // groupCounters is a list of GroupCounter for v4 and v6 env. reconciler uses these + // groupCounters is a list of GroupCounter for v4 and v6 env. podReconciler uses these // GroupCounters to get the groupIDs of a specific Service. groupCounters []proxytypes.GroupCounter @@ -232,8 +232,8 @@ type reconciler struct { multicastEnabled bool } -// newReconciler returns a new *reconciler. -func newReconciler(ofClient openflow.Client, +// newPodReconciler returns a new *podReconciler. +func newPodReconciler(ofClient openflow.Client, ifaceStore interfacestore.InterfaceStore, idAllocator *idAllocator, fqdnController *fqdnController, @@ -242,7 +242,7 @@ func newReconciler(ofClient openflow.Client, v6Enabled bool, antreaPolicyEnabled bool, multicastEnabled bool, -) *reconciler { +) *podReconciler { priorityAssigners := map[uint8]*tablePriorityAssigner{} if antreaPolicyEnabled { for _, table := range openflow.GetAntreaPolicyBaselineTierTables() { @@ -268,7 +268,7 @@ func newReconciler(ofClient openflow.Client, } } } - reconciler := &reconciler{ + reconciler := &podReconciler{ ofClient: ofClient, ifaceStore: ifaceStore, lastRealizeds: sync.Map{}, @@ -288,13 +288,13 @@ func newReconciler(ofClient openflow.Client, // RunIDAllocatorWorker runs the worker that deletes the rules from the cache in // idAllocator. -func (r *reconciler) RunIDAllocatorWorker(stopCh <-chan struct{}) { +func (r *podReconciler) RunIDAllocatorWorker(stopCh <-chan struct{}) { r.idAllocator.runWorker(stopCh) } // Reconcile checks whether the provided rule have been enforced or not, and // invoke the add or update method accordingly. -func (r *reconciler) Reconcile(rule *CompletedRule) error { +func (r *podReconciler) Reconcile(rule *CompletedRule) error { klog.InfoS("Reconciling NetworkPolicy rule", "rule", rule.ID, "policy", rule.SourceRef.ToString()) var err error var ofPriority *uint16 @@ -322,12 +322,12 @@ func (r *reconciler) Reconcile(rule *CompletedRule) error { ofRuleInstallErr = r.update(value.(*lastRealized), rule, ofPriority, ruleTable) } if ofRuleInstallErr != nil && ofPriority != nil && !registeredBefore { - priorityAssigner.assigner.Release(*ofPriority) + priorityAssigner.assigner.release(*ofPriority) } return ofRuleInstallErr } -func (r *reconciler) getRuleType(rule *CompletedRule) ruleType { +func (r *podReconciler) getRuleType(rule *CompletedRule) ruleType { if !r.multicastEnabled { return unicast } @@ -349,7 +349,7 @@ func (r *reconciler) getRuleType(rule *CompletedRule) ruleType { // getOFRuleTable retrieves the OpenFlow table to install the CompletedRule. // The decision is made based on whether the rule is created for an ACNP/ANNP, and // the Tier of that NetworkPolicy. -func (r *reconciler) getOFRuleTable(rule *CompletedRule) uint8 { +func (r *podReconciler) getOFRuleTable(rule *CompletedRule) uint8 { rType := r.getRuleType(rule) var ruleTables []*openflow.Table var tableID uint8 @@ -388,7 +388,7 @@ func (r *reconciler) getOFRuleTable(rule *CompletedRule) uint8 { // 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) { +func (r *podReconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *tablePriorityAssigner) (*uint16, bool, error) { // 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() { @@ -400,7 +400,7 @@ func (r *reconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *table PolicyPriority: *rule.PolicyPriority, RulePriority: rule.Priority, } - ofPriority, registered := pa.assigner.GetOFPriority(p) + ofPriority, registered := pa.assigner.getOFPriority(p) if !registered { allPrioritiesInPolicy := make([]types.Priority, rule.MaxPriority+1) for i := int32(0); i <= rule.MaxPriority; i++ { @@ -410,7 +410,7 @@ func (r *reconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *table RulePriority: i, } } - priorityUpdates, revertFunc, err := pa.assigner.RegisterPriorities(allPrioritiesInPolicy) + priorityUpdates, revertFunc, err := pa.assigner.registerPriorities(allPrioritiesInPolicy) if err != nil { return nil, registered, err } @@ -422,7 +422,7 @@ func (r *reconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *table return nil, registered, err } } - ofPriority, _ = pa.assigner.GetOFPriority(p) + ofPriority, _ = pa.assigner.getOFPriority(p) } klog.V(2).InfoS("Assigning OFPriority to rule", "rule", rule.ID, "priority", ofPriority) return &ofPriority, registered, nil @@ -431,7 +431,7 @@ func (r *reconciler) getOFPriority(rule *CompletedRule, tableID uint8, pa *table // BatchReconcile reconciles the desired state of the provided CompletedRules // with the actual state of Openflow entries in batch. It should only be invoked // if all rules are newly added without last realized status. -func (r *reconciler) BatchReconcile(rules []*CompletedRule) error { +func (r *podReconciler) BatchReconcile(rules []*CompletedRule) error { var rulesToInstall []*CompletedRule var priorities []*uint16 prioritiesByTable := map[uint8][]*uint16{} @@ -462,7 +462,7 @@ func (r *reconciler) BatchReconcile(rules []*CompletedRule) error { for tableID, ofPriorities := range prioritiesByTable { pa := r.priorityAssigners[tableID] for _, ofPriority := range ofPriorities { - pa.assigner.Release(*ofPriority) + pa.assigner.release(*ofPriority) } } } @@ -471,7 +471,7 @@ func (r *reconciler) BatchReconcile(rules []*CompletedRule) error { // registerOFPriorities constructs a Priority type for each CompletedRule in the input list, // and registers those Priorities with appropriate tablePriorityAssigner based on Tier. -func (r *reconciler) registerOFPriorities(rules []*CompletedRule) error { +func (r *podReconciler) registerOFPriorities(rules []*CompletedRule) error { prioritiesToRegister := map[uint8][]types.Priority{} for _, rule := range rules { // IGMP Egress policy is enforced in userspace via packet-in message, there won't be OpenFlow @@ -487,7 +487,7 @@ func (r *reconciler) registerOFPriorities(rules []*CompletedRule) error { } } for tableID, priorities := range prioritiesToRegister { - if _, _, err := r.priorityAssigners[tableID].assigner.RegisterPriorities(priorities); err != nil { + if _, _, err := r.priorityAssigners[tableID].assigner.registerPriorities(priorities); err != nil { return err } } @@ -495,7 +495,7 @@ func (r *reconciler) registerOFPriorities(rules []*CompletedRule) error { } // add converts CompletedRule to PolicyRule(s) and invokes installOFRule to install them. -func (r *reconciler) add(rule *CompletedRule, ofPriority *uint16, table uint8) error { +func (r *podReconciler) add(rule *CompletedRule, ofPriority *uint16, table uint8) error { klog.V(2).InfoS("Adding new rule", "rule", rule) ofRuleByServicesMap, lastRealized := r.computeOFRulesForAdd(rule, ofPriority, table) for svcKey, ofRule := range ofRuleByServicesMap { @@ -517,7 +517,7 @@ func (r *reconciler) add(rule *CompletedRule, ofPriority *uint16, table uint8) e return nil } -func (r *reconciler) computeOFRulesForAdd(rule *CompletedRule, ofPriority *uint16, table uint8) ( +func (r *podReconciler) computeOFRulesForAdd(rule *CompletedRule, ofPriority *uint16, table uint8) ( map[servicesKey]*types.PolicyRule, *lastRealized) { lastRealized := newLastRealized(rule) // TODO: Handle the case that the following processing fails or partially succeeds. @@ -672,7 +672,7 @@ func (r *reconciler) computeOFRulesForAdd(rule *CompletedRule, ofPriority *uint1 } // batchAdd converts CompletedRules to PolicyRules and invokes BatchInstallPolicyRuleFlows to install them. -func (r *reconciler) batchAdd(rules []*CompletedRule, ofPriorities []*uint16) error { +func (r *podReconciler) batchAdd(rules []*CompletedRule, ofPriorities []*uint16) error { lastRealizeds := make([]*lastRealized, len(rules)) ofIDUpdateMaps := make([]map[servicesKey]uint32, len(rules)) @@ -711,7 +711,7 @@ func (r *reconciler) batchAdd(rules []*CompletedRule, ofPriorities []*uint16) er // update calculates the difference of Addresses between oldRule and newRule, // and invokes Openflow client's methods to reconcile them. -func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule, ofPriority *uint16, table uint8) error { +func (r *podReconciler) update(lastRealized *lastRealized, newRule *CompletedRule, ofPriority *uint16, table uint8) error { klog.V(2).InfoS("Updating existing rule", "rule", newRule) // staleOFIDs tracks servicesKey that are no long needed. // Firstly fill it with the last realized ofIDs. @@ -871,7 +871,7 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule, LogLabel: newRule.LogLabel, } // If the PolicyRule for the original services doesn't exist and IPBlocks is present, it means the - // reconciler hasn't installed flows for IPBlocks, then it must be added to the new PolicyRule. + // podReconciler hasn't installed flows for IPBlocks, then it must be added to the new PolicyRule. if svcKey == originalSvcKey && len(newRule.To.IPBlocks) > 0 { to := ipBlocksToOFAddresses(newRule.To.IPBlocks, r.ipv4Enabled, r.ipv6Enabled, false) ofRule.To = append(ofRule.To, to...) @@ -943,7 +943,7 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule, return nil } -func (r *reconciler) installOFRule(ofRule *types.PolicyRule) error { +func (r *podReconciler) installOFRule(ofRule *types.PolicyRule) error { klog.V(2).InfoS("Installing ofRule", "id", ofRule.FlowID, "direction", ofRule.Direction, "from", len(ofRule.From), "to", len(ofRule.To), "service", len(ofRule.Service)) if err := r.ofClient.InstallPolicyRuleFlows(ofRule); err != nil { r.idAllocator.forgetRule(ofRule.FlowID) @@ -952,7 +952,7 @@ func (r *reconciler) installOFRule(ofRule *types.PolicyRule) error { return nil } -func (r *reconciler) updateOFRule(ofID uint32, addedFrom []types.Address, addedTo []types.Address, deletedFrom []types.Address, deletedTo []types.Address, priority *uint16, enableLogging, isMCNPRule bool) error { +func (r *podReconciler) updateOFRule(ofID uint32, addedFrom []types.Address, addedTo []types.Address, deletedFrom []types.Address, deletedTo []types.Address, priority *uint16, enableLogging, isMCNPRule bool) error { klog.V(2).InfoS("Updating ofRule", "id", ofID, "addedFrom", len(addedFrom), "addedTo", len(addedTo), "deletedFrom", len(deletedFrom), "deletedTo", len(deletedTo)) // TODO: This might be unnecessarily complex and hard for error handling, consider revising the Openflow interfaces. if len(addedFrom) > 0 { @@ -978,7 +978,7 @@ func (r *reconciler) updateOFRule(ofID uint32, addedFrom []types.Address, addedT return nil } -func (r *reconciler) uninstallOFRule(ofID uint32, table uint8) error { +func (r *podReconciler) uninstallOFRule(ofID uint32, table uint8) error { klog.V(2).InfoS("Uninstalling ofRule", "id", ofID) stalePriorities, err := r.ofClient.UninstallPolicyRuleFlows(ofID) if err != nil { @@ -994,7 +994,7 @@ func (r *reconciler) uninstallOFRule(ofID uint32, table uint8) error { } // If there are stalePriorities, priorityAssigners[table] must not be nil. priorityAssigner, _ := r.priorityAssigners[table] - priorityAssigner.assigner.Release(uint16(priorityNum)) + priorityAssigner.assigner.release(uint16(priorityNum)) } } r.idAllocator.forgetRule(ofID) @@ -1003,7 +1003,7 @@ func (r *reconciler) uninstallOFRule(ofID uint32, table uint8) error { // Forget invokes UninstallPolicyRuleFlows to uninstall Openflow entries // associated with the provided ruleID if it was enforced before. -func (r *reconciler) Forget(ruleID string) error { +func (r *podReconciler) Forget(ruleID string) error { klog.InfoS("Forgetting rule", "rule", ruleID) value, exists := r.lastRealizeds.Load(ruleID) @@ -1033,7 +1033,7 @@ func (r *reconciler) Forget(ruleID string) error { return nil } -func (r *reconciler) isIGMPRule(rule *CompletedRule) bool { +func (r *podReconciler) isIGMPRule(rule *CompletedRule) bool { isIGMP := false if len(rule.Services) > 0 && (rule.Services[0].Protocol != nil) && (*rule.Services[0].Protocol == v1beta2.ProtocolIGMP) { @@ -1042,11 +1042,11 @@ func (r *reconciler) isIGMPRule(rule *CompletedRule) bool { return isIGMP } -func (r *reconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule, bool, error) { +func (r *podReconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule, bool, error) { return r.idAllocator.getRuleFromAsyncCache(ruleFlowID) } -func (r *reconciler) getOFPorts(members v1beta2.GroupMemberSet) sets.Set[int32] { +func (r *podReconciler) getOFPorts(members v1beta2.GroupMemberSet) sets.Set[int32] { ofPorts := sets.New[int32]() for _, m := range members { var entityName, ns string @@ -1071,7 +1071,7 @@ func (r *reconciler) getOFPorts(members v1beta2.GroupMemberSet) sets.Set[int32] return ofPorts } -func (r *reconciler) getIPs(members v1beta2.GroupMemberSet) sets.Set[string] { +func (r *podReconciler) getIPs(members v1beta2.GroupMemberSet) sets.Set[string] { ips := sets.New[string]() for _, m := range members { var entityName, ns string @@ -1100,7 +1100,7 @@ func (r *reconciler) getIPs(members v1beta2.GroupMemberSet) sets.Set[string] { return ips } -func (r *reconciler) getSvcGroupIDs(members v1beta2.GroupMemberSet) sets.Set[int64] { +func (r *podReconciler) getSvcGroupIDs(members v1beta2.GroupMemberSet) sets.Set[int64] { var svcRefs []v1beta2.ServiceReference for _, m := range members { if m.Service != nil { @@ -1162,7 +1162,7 @@ func ofPortsToOFAddresses(ofPorts sets.Set[int32]) []types.Address { return addresses } -func (r *reconciler) svcRefsToGroupIDs(svcRefs []v1beta2.ServiceReference) sets.Set[int64] { +func (r *podReconciler) svcRefsToGroupIDs(svcRefs []v1beta2.ServiceReference) sets.Set[int64] { groupIDs := sets.New[int64]() for _, svcRef := range svcRefs { for _, groupCounter := range r.groupCounters { diff --git a/pkg/agent/controller/networkpolicy/reconciler_test.go b/pkg/agent/controller/networkpolicy/pod_reconciler_test.go similarity index 99% rename from pkg/agent/controller/networkpolicy/reconciler_test.go rename to pkg/agent/controller/networkpolicy/pod_reconciler_test.go index 0b6cbc58f30..328002af645 100644 --- a/pkg/agent/controller/networkpolicy/reconciler_test.go +++ b/pkg/agent/controller/networkpolicy/pod_reconciler_test.go @@ -107,12 +107,12 @@ func newCIDR(cidrStr string) *net.IPNet { return tmpIPNet } -func newTestReconciler(t *testing.T, controller *gomock.Controller, ifaceStore interfacestore.InterfaceStore, ofClient *openflowtest.MockClient, v4Enabled, v6Enabled bool) *reconciler { +func newTestReconciler(t *testing.T, controller *gomock.Controller, ifaceStore interfacestore.InterfaceStore, ofClient *openflowtest.MockClient, v4Enabled, v6Enabled bool) *podReconciler { f, _ := newMockFQDNController(t, controller, nil) ch := make(chan string, 100) groupIDAllocator := openflow.NewGroupAllocator() groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(groupIDAllocator, ch)} - r := newReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f, groupCounters, v4Enabled, v6Enabled, true, false) + r := newPodReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f, groupCounters, v4Enabled, v6Enabled, true, false) return r } @@ -864,7 +864,7 @@ func TestReconcilerReconcileServiceRelatedRule(t *testing.T) { } } -// TestReconcileWithTransientError ensures the reconciler can reconcile a rule properly after the first attempt meets +// TestReconcileWithTransientError ensures the podReconciler can reconcile a rule properly after the first attempt meets // transient error. // The input rule is an egress rule with named port, applying to 3 Pods and 1 IPBlock. The first 2 Pods have different // port numbers for the named port and the 3rd Pod cannot resolve it. diff --git a/pkg/agent/controller/networkpolicy/priority.go b/pkg/agent/controller/networkpolicy/priority.go index 69bd4a49698..299227f6ba9 100644 --- a/pkg/agent/controller/networkpolicy/priority.go +++ b/pkg/agent/controller/networkpolicy/priority.go @@ -27,20 +27,20 @@ import ( const ( MaxUint16 = ^uint16(0) zoneOffset = uint16(5) - DefaultTierPriority = int32(250) - BaselinePolicyBottomPriority = uint16(10) - BaselinePolicyTopPriority = uint16(180) - PolicyBottomPriority = uint16(100) - PolicyTopPriority = uint16(65000) - PriorityOffsetBaselineTier = float64(10) - TierOffsetBaselineTier = uint16(0) - PriorityOffsetMultiTier = float64(20) - PriorityOffsetDefaultTier = float64(100) - TierOffsetMultiTier = uint16(200) + defaultTierPriority = int32(250) + baselinePolicyBottomPriority = uint16(10) + baselinePolicyTopPriority = uint16(180) + policyBottomPriority = uint16(100) + policyTopPriority = uint16(65000) + priorityOffsetBaselineTier = float64(10) + tierOffsetBaselineTier = uint16(0) + priorityOffsetMultiTier = float64(20) + priorityOffsetDefaultTier = float64(100) + tierOffsetMultiTier = uint16(200) ) -// PriorityUpdate stores the original and updated ofPriority of a Priority. -type PriorityUpdate struct { +// priorityUpdate stores the original and updated ofPriority of a Priority. +type priorityUpdate struct { Original uint16 Updated uint16 } @@ -55,7 +55,7 @@ type reassignCost struct { // priorityUpdatesToOFUpdates converts a map of Priority and its ofPriority update to a map // of ofPriority updates. -func priorityUpdatesToOFUpdates(allUpdates map[types.Priority]*PriorityUpdate) map[uint16]uint16 { +func priorityUpdatesToOFUpdates(allUpdates map[types.Priority]*priorityUpdate) map[uint16]uint16 { processed := map[uint16]uint16{} for _, update := range allUpdates { processed[update.Original] = update.Updated @@ -83,11 +83,11 @@ type priorityAssigner struct { } func newPriorityAssigner(isBaselineTier bool) *priorityAssigner { - bottomPriority := PolicyBottomPriority - topPriority := PolicyTopPriority + bottomPriority := policyBottomPriority + topPriority := policyTopPriority if isBaselineTier { - bottomPriority = BaselinePolicyBottomPriority - topPriority = BaselinePolicyTopPriority + bottomPriority = baselinePolicyBottomPriority + topPriority = baselinePolicyTopPriority } pa := &priorityAssigner{ priorityMap: map[types.Priority]uint16{}, @@ -108,14 +108,14 @@ func newPriorityAssigner(isBaselineTier bool) *priorityAssigner { // It computes the initial OpenFlow priority by offsetting the tier priority, policy priority and rule priority // with pre-determined coefficients. func (pa *priorityAssigner) initialOFPriority(p types.Priority) uint16 { - tierOffsetBase := TierOffsetMultiTier - priorityOffsetBase := PriorityOffsetMultiTier - if p.TierPriority == DefaultTierPriority { - priorityOffsetBase = PriorityOffsetDefaultTier + tierOffsetBase := tierOffsetMultiTier + priorityOffsetBase := priorityOffsetMultiTier + if p.TierPriority == defaultTierPriority { + priorityOffsetBase = priorityOffsetDefaultTier } if pa.isBaselineTier { - tierOffsetBase = TierOffsetBaselineTier - priorityOffsetBase = PriorityOffsetBaselineTier + tierOffsetBase = tierOffsetBaselineTier + priorityOffsetBase = priorityOffsetBaselineTier } tierOffset := tierOffsetBase * uint16(p.TierPriority) priorityOffset := uint16(p.PolicyPriority * priorityOffsetBase) @@ -205,7 +205,7 @@ func (pa *priorityAssigner) findReassignBoundaries(lowerBound, upperBound uint16 // new Priorities to be registered. It also records all the priority updates due to the reassignment in the // map of updates, which is passed to it as parameter. func (pa *priorityAssigner) reassignBoundaryPriorities(lowerBound, upperBound uint16, prioritiesToRegister types.ByPriority, - updates map[types.Priority]*PriorityUpdate) error { + updates map[types.Priority]*priorityUpdate) error { numNewPriorities, gap := len(prioritiesToRegister), int(upperBound-lowerBound-1) low, high, err := pa.findReassignBoundaries(lowerBound, upperBound, numNewPriorities, gap) if err != nil { @@ -232,7 +232,7 @@ func (pa *priorityAssigner) reassignBoundaryPriorities(lowerBound, upperBound ui // if exists (the Priority has already been reassigned in a previous step), the original // ofPriority of that Priority would have been recorded. if _, exists := updates[p]; !exists { - updates[p] = &PriorityUpdate{Original: pa.priorityMap[p]} + updates[p] = &priorityUpdate{Original: pa.priorityMap[p]} } } // assign ofPriorities by the order of siftedPrioritiesLow, prioritiesToRegister and siftedPrioritiesHigh. @@ -246,19 +246,19 @@ func (pa *priorityAssigner) reassignBoundaryPriorities(lowerBound, upperBound ui return nil } -// GetOFPriority returns if the Priority is registered with the priorityAssigner, +// getOFPriority returns if the Priority is registered with the priorityAssigner, // and retrieves the corresponding ofPriority. -func (pa *priorityAssigner) GetOFPriority(p types.Priority) (uint16, bool) { +func (pa *priorityAssigner) getOFPriority(p types.Priority) (uint16, bool) { of, registered := pa.priorityMap[p] return of, registered } -// RegisterPriorities registers a list of types.Priority with the priorityAssigner. It allocates ofPriorities for +// registerPriorities registers a list of types.Priority with the priorityAssigner. It allocates ofPriorities for // input priorities that are not yet registered. It also returns the ofPriority updates if there are reassignments, // as well as a revert function that can undo the registration if any error occurred in data plane. // Note that this function modifies the priorities slice in the parameter, as it only keeps the Priorities which // this priorityAssigner has not yet registered. Input priorities are not assumed to be unique or consecutive. -func (pa *priorityAssigner) RegisterPriorities(priorities []types.Priority) (map[uint16]uint16, func(), error) { +func (pa *priorityAssigner) registerPriorities(priorities []types.Priority) (map[uint16]uint16, func(), error) { // create a zero-length slice with the same underlying array to save memory usage. prioritiesToRegister := priorities[:0] priorityDedup := map[types.Priority]struct{}{} @@ -291,7 +291,7 @@ func (pa *priorityAssigner) RegisterPriorities(priorities []types.Priority) (map // registerConsecutivePriorities registers lists of consecutive Priorities with the priorityAssigner. func (pa *priorityAssigner) registerConsecutivePriorities(consecutivePriorities [][]types.Priority) (map[uint16]uint16, func(), error) { - allPriorityUpdates := map[types.Priority]*PriorityUpdate{} + allPriorityUpdates := map[types.Priority]*priorityUpdate{} revertFunc := func() { // in case of error, all new Priorities need to be unregistered. for _, newPriorities := range consecutivePriorities { @@ -325,7 +325,7 @@ func (pa *priorityAssigner) registerConsecutivePriorities(consecutivePriorities // It first identifies the lower and upper bound for insertion, by obtaining the ofPriorities of // registered Priorities surrounding (immediately lower and higher than) the inserting Priorities. // It then decides the range to register new Priorities, and reassign existing ones if necessary. -func (pa *priorityAssigner) insertConsecutivePriorities(priorities types.ByPriority, updates map[types.Priority]*PriorityUpdate) error { +func (pa *priorityAssigner) insertConsecutivePriorities(priorities types.ByPriority, updates map[types.Priority]*priorityUpdate) error { numPriorities := len(priorities) pLow, pHigh := priorities[0], priorities[numPriorities-1] insertionPointLow := pa.initialOFPriority(pLow) @@ -371,8 +371,8 @@ func (pa *priorityAssigner) insertConsecutivePriorities(priorities types.ByPrior return nil } -// Release removes the priority that currently corresponds to the input OFPriority from the known priorities. -func (pa *priorityAssigner) Release(ofPriority uint16) { +// release removes the priority that currently corresponds to the input OFPriority from the known priorities. +func (pa *priorityAssigner) release(ofPriority uint16) { priority, exists := pa.ofPriorityMap[ofPriority] if !exists { klog.V(2).Infof("OF priority %v not known, skip releasing priority", ofPriority) diff --git a/pkg/agent/controller/networkpolicy/priority_test.go b/pkg/agent/controller/networkpolicy/priority_test.go index 0439bf87d8e..bb2328189c7 100644 --- a/pkg/agent/controller/networkpolicy/priority_test.go +++ b/pkg/agent/controller/networkpolicy/priority_test.go @@ -87,7 +87,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { originalPriorities []types.Priority originalOfPriorities []uint16 expectedPriorityMap map[types.Priority]uint16 - expectedUpdates map[types.Priority]*PriorityUpdate + expectedUpdates map[types.Priority]*priorityUpdate }{ { "push-down-single", @@ -97,7 +97,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { []uint16{10000, 10001, 10002}, map[types.Priority]uint16{ p1140: 9996, p1133: 9997, p1132: 9998, p1131: 9999, p1130: 10000, p1121: 10001, p1120: 10002}, - map[types.Priority]*PriorityUpdate{p1140: {10000, 9996}}, + map[types.Priority]*priorityUpdate{p1140: {10000, 9996}}, }, { "push-down-multiple", @@ -108,7 +108,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { map[types.Priority]uint16{ p190: 9995, p1140: 9996, p1133: 9997, p1132: 9998, p1131: 9999, p1130: 10000, p1121: 10001, p1120: 10002}, - map[types.Priority]*PriorityUpdate{ + map[types.Priority]*priorityUpdate{ p1140: {10000, 9996}, p190: {9998, 9995}, }, @@ -122,7 +122,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { map[types.Priority]uint16{ p1142: 9998, p1141: 9999, p1140: 10000, p1133: 10001, p1132: 10002, p1131: 10003, p1130: 10004, p1121: 10005}, - map[types.Priority]*PriorityUpdate{p1121: {10002, 10005}}, + map[types.Priority]*priorityUpdate{p1121: {10002, 10005}}, }, { "push-up-multiple", @@ -133,7 +133,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { map[types.Priority]uint16{ p1142: 9998, p1141: 9999, p1140: 10000, p1133: 10001, p1132: 10002, p1131: 10003, p1130: 10004, p1121: 10005, p1120: 10006}, - map[types.Priority]*PriorityUpdate{ + map[types.Priority]*priorityUpdate{ p1121: {10002, 10005}, p1120: {10003, 10006}, }, @@ -148,7 +148,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { p193: 9994, p192: 9995, p191: 9996, p190: 9997, p1140: 10000, p1133: 10001, p1132: 10002, p1131: 10003, p1130: 10004, p1121: 10005, p1120: 10006}, - map[types.Priority]*PriorityUpdate{ + map[types.Priority]*priorityUpdate{ p1121: {10002, 10005}, p1120: {10003, 10006}, }, @@ -160,7 +160,7 @@ func TestReassignBoundaryPriorities(t *testing.T) { for i, p := range tt.originalOfPriorities { pa.updatePriorityAssignment(p, tt.originalPriorities[i]) } - priorityUpdates := map[types.Priority]*PriorityUpdate{} + priorityUpdates := map[types.Priority]*priorityUpdate{} err := pa.reassignBoundaryPriorities(tt.lowerBound, tt.upperBound, prioritiesToRegister, priorityUpdates) assert.NoError(t, err, "Error occurred in reassignment") assert.Equalf(t, tt.expectedPriorityMap, pa.priorityMap, "priorityMap unexpected after reassignment") @@ -263,7 +263,7 @@ func TestInsertConsecutivePriorities(t *testing.T) { for i, p := range tt.originalOfPriorities { pa.updatePriorityAssignment(p, tt.originalPriorities[i]) } - priorityUpdates := map[types.Priority]*PriorityUpdate{} + priorityUpdates := map[types.Priority]*priorityUpdate{} err := pa.insertConsecutivePriorities(prioritiesToRegister, priorityUpdates) assert.NoError(t, err, "Error occurred in priority insertion") assert.Equalf(t, tt.expectedOFPriorityMap, pa.ofPriorityMap, "priorityMap unexpected after insertion") @@ -286,7 +286,7 @@ func TestRegisterPrioritiesAndRevert(t *testing.T) { insertionPoint1132 + 2: p1121, insertionPoint1132 + 3: p1120, insertionPoint191: p191, insertionPoint191 + 1: p190, } - _, revertFunc, err := pa.RegisterPriorities(prioritiesToRegister) + _, revertFunc, err := pa.registerPriorities(prioritiesToRegister) assert.NoError(t, err, "Error occurred in priority registration") assert.Equalf(t, expectedOFMapAfterRegister, pa.ofPriorityMap, "priorityMap unexpected after registration") @@ -303,15 +303,15 @@ func TestRegisterDuplicatePriorities(t *testing.T) { prioritiesToRegister := []types.Priority{p1131, p1130} prioritiesToRegisterDuplicate := []types.Priority{p1130, p1131, p1130, p1130, p1130, p1131, p1130} - _, _, err := pa1.RegisterPriorities(prioritiesToRegister) + _, _, err := pa1.registerPriorities(prioritiesToRegister) assert.NoError(t, err, "Error occurred in priority registration") - _, _, err2 := pa2.RegisterPriorities(prioritiesToRegisterDuplicate) + _, _, err2 := pa2.registerPriorities(prioritiesToRegisterDuplicate) assert.NoError(t, err2, "Error occurred in priority registration") - ofPriority1130, _ := pa1.GetOFPriority(p1130) - ofPriority1130Dup, _ := pa2.GetOFPriority(p1130) + ofPriority1130, _ := pa1.getOFPriority(p1130) + ofPriority1130Dup, _ := pa2.getOFPriority(p1130) assert.Equal(t, ofPriority1130, ofPriority1130Dup) - ofPriority1131, _ := pa1.GetOFPriority(p1131) - ofPriority1131Dup, _ := pa2.GetOFPriority(p1131) + ofPriority1131, _ := pa1.getOFPriority(p1131) + ofPriority1131Dup, _ := pa2.getOFPriority(p1131) assert.Equal(t, ofPriority1131, ofPriority1131Dup) } @@ -325,27 +325,27 @@ func generatePriorities(tierPriority, start, end int32, policyPriority float64) func TestRegisterAllOFPriorities(t *testing.T) { pa := newPriorityAssigner(true) - maxPriorities := generatePriorities(253, int32(BaselinePolicyBottomPriority), int32(BaselinePolicyTopPriority), 5) - _, _, err := pa.RegisterPriorities(maxPriorities) + maxPriorities := generatePriorities(253, int32(baselinePolicyBottomPriority), int32(baselinePolicyTopPriority), 5) + _, _, err := pa.registerPriorities(maxPriorities) assert.NoError(t, err, "Error occurred in registering max number of allowed priorities in baseline tier") extraPriority := types.Priority{ TierPriority: 253, PolicyPriority: 5, - RulePriority: int32(BaselinePolicyTopPriority) - int32(BaselinePolicyBottomPriority) + 1, + RulePriority: int32(baselinePolicyTopPriority) - int32(baselinePolicyBottomPriority) + 1, } - _, _, err = pa.RegisterPriorities([]types.Priority{extraPriority}) + _, _, err = pa.registerPriorities([]types.Priority{extraPriority}) assert.Errorf(t, err, "Error should be raised after max number of priorities are registered in baseline tier") pa = newPriorityAssigner(false) - consecPriorities1 := generatePriorities(5, int32(PolicyBottomPriority), 10000, 5) - _, _, err = pa.RegisterPriorities(consecPriorities1) + consecPriorities1 := generatePriorities(5, int32(policyBottomPriority), 10000, 5) + _, _, err = pa.registerPriorities(consecPriorities1) assert.NoError(t, err, "Error occurred before registering max number of allowed priorities") - consecPriorities2 := generatePriorities(10, 10001, int32(PolicyTopPriority), 5) - _, _, err = pa.RegisterPriorities(consecPriorities2) + consecPriorities2 := generatePriorities(10, 10001, int32(policyTopPriority), 5) + _, _, err = pa.registerPriorities(consecPriorities2) assert.NoError(t, err, "Error occurred in registering max number of allowed priorities") - _, _, err = pa.RegisterPriorities([]types.Priority{extraPriority}) + _, _, err = pa.registerPriorities([]types.Priority{extraPriority}) assert.Errorf(t, err, "Error should be raised after max number of priorities are registered") } diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go index e4c6c4dfcec..5523522b684 100644 --- a/pkg/agent/route/route_linux.go +++ b/pkg/agent/route/route_linux.go @@ -72,6 +72,7 @@ const ( antreaForwardChain = "ANTREA-FORWARD" antreaPreRoutingChain = "ANTREA-PREROUTING" antreaPostRoutingChain = "ANTREA-POSTROUTING" + antreaInputChain = "ANTREA-INPUT" antreaOutputChain = "ANTREA-OUTPUT" antreaMangleChain = "ANTREA-MANGLE" @@ -107,11 +108,12 @@ type Client struct { // markToSNATIP caches marks to SNAT IPs. It's used in Egress feature. markToSNATIP sync.Map // iptablesInitialized is used to notify when iptables initialization is done. - iptablesInitialized chan struct{} - proxyAll bool - connectUplinkToBridge bool - multicastEnabled bool - isCloudEKS bool + iptablesInitialized chan struct{} + proxyAll bool + connectUplinkToBridge bool + multicastEnabled bool + isCloudEKS bool + hostNetworkPolicyEnabled bool // serviceRoutes caches ip routes about Services. serviceRoutes sync.Map // serviceNeighbors caches neighbors. @@ -129,17 +131,24 @@ type Client struct { } // NewClient returns a route client. -func NewClient(networkConfig *config.NetworkConfig, noSNAT, proxyAll, connectUplinkToBridge, multicastEnabled bool, serviceCIDRProvider servicecidr.Interface) (*Client, error) { +func NewClient(networkConfig *config.NetworkConfig, + noSNAT bool, + proxyAll bool, + connectUplinkToBridge bool, + hostNetworkPolicyEnabled bool, + multicastEnabled bool, + serviceCIDRProvider servicecidr.Interface) (*Client, error) { return &Client{ - networkConfig: networkConfig, - noSNAT: noSNAT, - proxyAll: proxyAll, - multicastEnabled: multicastEnabled, - connectUplinkToBridge: connectUplinkToBridge, - ipset: ipset.NewClient(), - netlink: &netlink.Handle{}, - isCloudEKS: env.IsCloudEKS(), - serviceCIDRProvider: serviceCIDRProvider, + networkConfig: networkConfig, + noSNAT: noSNAT, + proxyAll: proxyAll, + multicastEnabled: multicastEnabled, + connectUplinkToBridge: connectUplinkToBridge, + hostNetworkPolicyEnabled: hostNetworkPolicyEnabled, + ipset: ipset.NewClient(), + netlink: &netlink.Handle{}, + isCloudEKS: env.IsCloudEKS(), + serviceCIDRProvider: serviceCIDRProvider, }, nil } @@ -508,6 +517,11 @@ func (c *Client) syncIPTables() error { if c.proxyAll { jumpRules = append(jumpRules, jumpRule{iptables.NATTable, iptables.OutputChain, antreaOutputChain, "Antrea: jump to Antrea output rules"}) } + if c.hostNetworkPolicyEnabled { + jumpRules = append(jumpRules, jumpRule{iptables.FilterTable, iptables.InputChain, antreaInputChain, "Antrea: jump to Antrea input rules"}, + jumpRule{iptables.FilterTable, iptables.OutputChain, antreaOutputChain, "Antrea: jump to Antrea output rules"}) + } + for _, rule := range jumpRules { if err := c.iptables.EnsureChain(iptables.ProtocolDual, rule.table, rule.dstChain); err != nil { return err @@ -623,7 +637,7 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, "-m", "comment", "--comment", `"Antrea: drop Pod multicast traffic forwarded via underlay network"`, "-m", "set", "--match-set", clusterNodeIPSet, "src", "-d", types.McastCIDR.String(), - "-j", iptables.DROPTarget, + "-j", iptables.DropTarget, }...) } } @@ -665,6 +679,13 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, writeLine(iptablesData, "*filter") writeLine(iptablesData, iptables.MakeChainLine(antreaForwardChain)) + if c.hostNetworkPolicyEnabled { + writeLine(iptablesData, iptables.MakeChainLine(antreaInputChain)) + writeLine(iptablesData, iptables.MakeChainLine(antreaOutputChain)) + writeLine(iptablesData, iptables.MakeChainLine(config.HostNetworkPolicyDefaultIngressRulesChain)) + writeLine(iptablesData, iptables.MakeChainLine(config.HostNetworkPolicyIngressRulesChain)) + writeLine(iptablesData, iptables.MakeChainLine(config.HostNetworkPolicyEgressRulesChain)) + } writeLine(iptablesData, []string{ "-A", antreaForwardChain, "-m", "comment", "--comment", `"Antrea: accept packets from local Pods"`, @@ -694,6 +715,23 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, "-j", iptables.AcceptTarget, }...) } + if c.hostNetworkPolicyEnabled { + writeLine(iptablesData, []string{ + "-A", antreaInputChain, + "-m", "comment", "--comment", `"Antrea: default host ingress rules"`, + "-j", config.HostNetworkPolicyDefaultIngressRulesChain, + }...) + writeLine(iptablesData, []string{ + "-A", antreaInputChain, + "-m", "comment", "--comment", `"Antrea: host network policy ingress rules"`, + "-j", config.HostNetworkPolicyIngressRulesChain, + }...) + writeLine(iptablesData, []string{ + "-A", antreaOutputChain, + "-m", "comment", "--comment", `"Antrea: host network policy egress rules"`, + "-j", config.HostNetworkPolicyEgressRulesChain, + }...) + } writeLine(iptablesData, "COMMIT") writeLine(iptablesData, "*nat") diff --git a/pkg/agent/route/route_linux_test.go b/pkg/agent/route/route_linux_test.go index e9f14ade813..96d8b9820ba 100644 --- a/pkg/agent/route/route_linux_test.go +++ b/pkg/agent/route/route_linux_test.go @@ -235,22 +235,24 @@ func TestSyncIPSet(t *testing.T) { func TestSyncIPTables(t *testing.T) { tests := []struct { - name string - isCloudEKS bool - proxyAll bool - multicastEnabled bool - connectUplinkToBridge bool - networkConfig *config.NetworkConfig - nodeConfig *config.NodeConfig - nodePortsIPv4 []string - nodePortsIPv6 []string - markToSNATIP map[uint32]string - expectedCalls func(iptables *iptablestest.MockInterfaceMockRecorder) + name string + isCloudEKS bool + proxyAll bool + multicastEnabled bool + connectUplinkToBridge bool + hostNetworkPolicyEnabled bool + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + nodePortsIPv4 []string + nodePortsIPv6 []string + markToSNATIP map[uint32]string + expectedCalls func(iptables *iptablestest.MockInterfaceMockRecorder) }{ { - name: "encap,egress=true,multicastEnabled=true,proxyAll=true", - proxyAll: true, - multicastEnabled: true, + name: "encap,egress=true,multicastEnabled=true,proxyAll=true,hostNetworkPolicy=true", + proxyAll: true, + multicastEnabled: true, + hostNetworkPolicyEnabled: true, networkConfig: &config.NetworkConfig{ TrafficEncapMode: config.TrafficEncapModeEncap, TunnelType: ovsconfig.GeneveTunnel, @@ -285,6 +287,10 @@ func TestSyncIPTables(t *testing.T) { mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.PreRoutingChain, []string{"-j", antreaPreRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea prerouting rules"}) mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaOutputChain) mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.FilterTable, antreaInputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.FilterTable, iptables.InputChain, []string{"-j", antreaInputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea input rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.FilterTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.FilterTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) mockIPTables.Restore(`*raw :ANTREA-PREROUTING - [0:0] :ANTREA-OUTPUT - [0:0] @@ -299,8 +305,16 @@ COMMIT COMMIT *filter :ANTREA-FORWARD - [0:0] +:ANTREA-INPUT - [0:0] +:ANTREA-OUTPUT - [0:0] +:ANTREA-DEFAULT-INGRESS-RULES - [0:0] +:ANTREA-INGRESS-RULES - [0:0] +:ANTREA-EGRESS-RULES - [0:0] -A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT -A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +-A ANTREA-INPUT -m comment --comment "Antrea: default host ingress rules" -j ANTREA-DEFAULT-INGRESS-RULES +-A ANTREA-INPUT -m comment --comment "Antrea: host network policy ingress rules" -j ANTREA-INGRESS-RULES +-A ANTREA-OUTPUT -m comment --comment "Antrea: host network policy egress rules" -j ANTREA-EGRESS-RULES COMMIT *nat :ANTREA-PREROUTING - [0:0] @@ -328,8 +342,16 @@ COMMIT COMMIT *filter :ANTREA-FORWARD - [0:0] +:ANTREA-INPUT - [0:0] +:ANTREA-OUTPUT - [0:0] +:ANTREA-DEFAULT-INGRESS-RULES - [0:0] +:ANTREA-INGRESS-RULES - [0:0] +:ANTREA-EGRESS-RULES - [0:0] -A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT -A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +-A ANTREA-INPUT -m comment --comment "Antrea: default host ingress rules" -j ANTREA-DEFAULT-INGRESS-RULES +-A ANTREA-INPUT -m comment --comment "Antrea: host network policy ingress rules" -j ANTREA-INGRESS-RULES +-A ANTREA-OUTPUT -m comment --comment "Antrea: host network policy egress rules" -j ANTREA-EGRESS-RULES COMMIT *nat :ANTREA-PREROUTING - [0:0] @@ -485,13 +507,14 @@ COMMIT ctrl := gomock.NewController(t) mockIPTables := iptablestest.NewMockInterface(ctrl) c := &Client{iptables: mockIPTables, - networkConfig: tt.networkConfig, - nodeConfig: tt.nodeConfig, - proxyAll: tt.proxyAll, - isCloudEKS: tt.isCloudEKS, - multicastEnabled: tt.multicastEnabled, - connectUplinkToBridge: tt.connectUplinkToBridge, - markToSNATIP: sync.Map{}, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + proxyAll: tt.proxyAll, + isCloudEKS: tt.isCloudEKS, + multicastEnabled: tt.multicastEnabled, + connectUplinkToBridge: tt.connectUplinkToBridge, + hostNetworkPolicyEnabled: tt.hostNetworkPolicyEnabled, + markToSNATIP: sync.Map{}, } for mark, snatIP := range tt.markToSNATIP { c.markToSNATIP.Store(mark, net.ParseIP(snatIP)) diff --git a/pkg/agent/route/route_windows.go b/pkg/agent/route/route_windows.go index 7a67d6475b7..1d144988f84 100644 --- a/pkg/agent/route/route_windows.go +++ b/pkg/agent/route/route_windows.go @@ -71,7 +71,13 @@ type Client struct { } // NewClient returns a route client. -func NewClient(networkConfig *config.NetworkConfig, noSNAT, proxyAll, connectUplinkToBridge, multicastEnabled bool, serviceCIDRProvider servicecidr.Interface) (*Client, error) { +func NewClient(networkConfig *config.NetworkConfig, + noSNAT bool, + proxyAll bool, + connectUplinkToBridge bool, + hostNetworkPolicyEnabled bool, + multicastEnabled bool, + serviceCIDRProvider servicecidr.Interface) (*Client, error) { return &Client{ networkConfig: networkConfig, nodeRoutes: &sync.Map{}, diff --git a/pkg/agent/route/route_windows_test.go b/pkg/agent/route/route_windows_test.go index c29afd469dc..516e019838b 100644 --- a/pkg/agent/route/route_windows_test.go +++ b/pkg/agent/route/route_windows_test.go @@ -61,7 +61,7 @@ func TestRouteOperation(t *testing.T) { gwIP2 := net.ParseIP("192.168.3.1") _, destCIDR2, _ := net.ParseCIDR(dest2) - client, err := NewClient(&config.NetworkConfig{}, true, false, false, false, nil) + client, err := NewClient(&config.NetworkConfig{}, true, false, false, false, false, nil) require.Nil(t, err) called := false diff --git a/pkg/agent/util/ipset/ipset.go b/pkg/agent/util/ipset/ipset.go index 7882bf85e6f..b002a8ff79e 100644 --- a/pkg/agent/util/ipset/ipset.go +++ b/pkg/agent/util/ipset/ipset.go @@ -37,6 +37,8 @@ var memberPattern = regexp.MustCompile("(?m)^(.*\n)*Members:\n") type Interface interface { CreateIPSet(name string, setType SetType, isIPv6 bool) error + DestroyIPSet(name string) error + AddEntry(name string, entry string) error DelEntry(name string, entry string) error @@ -52,6 +54,14 @@ func NewClient() *Client { return &Client{} } +func (c *Client) DestroyIPSet(name string) error { + cmd := exec.Command("ipset", "destroy", name) + if err := cmd.Run(); err != nil { + return fmt.Errorf("error destroying ipset %s: %v", name, err) + } + return nil +} + // CreateIPSet creates a new set, it will ignore error when the set already exists. func (c *Client) CreateIPSet(name string, setType SetType, isIPv6 bool) error { var cmd *exec.Cmd diff --git a/pkg/agent/util/ipset/testing/mock_ipset.go b/pkg/agent/util/ipset/testing/mock_ipset.go index 2787530ee67..204c5ca12ec 100644 --- a/pkg/agent/util/ipset/testing/mock_ipset.go +++ b/pkg/agent/util/ipset/testing/mock_ipset.go @@ -95,6 +95,20 @@ func (mr *MockInterfaceMockRecorder) DelEntry(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelEntry", reflect.TypeOf((*MockInterface)(nil).DelEntry), arg0, arg1) } +// DestroyIPSet mocks base method. +func (m *MockInterface) DestroyIPSet(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DestroyIPSet", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// DestroyIPSet indicates an expected call of DestroyIPSet. +func (mr *MockInterfaceMockRecorder) DestroyIPSet(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestroyIPSet", reflect.TypeOf((*MockInterface)(nil).DestroyIPSet), arg0) +} + // ListEntries mocks base method. func (m *MockInterface) ListEntries(arg0 string) ([]string, error) { m.ctrl.T.Helper() diff --git a/pkg/agent/util/iptables/builder.go b/pkg/agent/util/iptables/builder.go new file mode 100644 index 00000000000..85b0b681d94 --- /dev/null +++ b/pkg/agent/util/iptables/builder.go @@ -0,0 +1,174 @@ +//go:build !windows +// +build !windows + +// Copyright 2023 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 iptables + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/util/intstr" + + "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + crdv1beta1 "antrea.io/antrea/pkg/apis/crd/v1beta1" +) + +type iptablesRule struct { + chain string + specs *strings.Builder +} + +type iptablesRuleBuilder struct { + iptablesRule +} + +func NewRuleBuilder(chain string) IPTablesRuleBuilder { + builder := &iptablesRuleBuilder{ + iptablesRule{ + chain: chain, + specs: &strings.Builder{}, + }, + } + return builder +} + +func (b *iptablesRuleBuilder) MatchIPSetSrc(ipset string) IPTablesRuleBuilder { + matchStr := fmt.Sprintf("-m set --match-ipset %s src ", ipset) + b.specs.WriteString(matchStr) + return b +} + +func (b *iptablesRuleBuilder) MatchIPSetDst(ipset string) IPTablesRuleBuilder { + matchStr := fmt.Sprintf("-m set --match-ipset %s dst ", ipset) + b.specs.WriteString(matchStr) + return b +} + +func (b *iptablesRuleBuilder) MatchTransProtocol(protocol v1beta2.Protocol) IPTablesRuleBuilder { + var protoStr string + switch protocol { + case v1beta2.ProtocolTCP: + protoStr = "tcp" + case v1beta2.ProtocolUDP: + protoStr = "udp" + case v1beta2.ProtocolSCTP: + protoStr = "sctp" + } + matchStr := fmt.Sprintf("-p %s ", protoStr) + b.specs.WriteString(matchStr) + return b +} + +func (b *iptablesRuleBuilder) MatchDstPort(port *intstr.IntOrString, endPort *int32) IPTablesRuleBuilder { + if port == nil { + return b + } + var matchStr string + if endPort != nil { + matchStr = fmt.Sprintf("--dport %s:%d ", port.String(), *endPort) + } else { + matchStr = fmt.Sprintf("--dport %s ", port.String()) + } + b.specs.WriteString(matchStr) + return b +} + +func (b *iptablesRuleBuilder) MatchSrcPort(port, endPort *int32) IPTablesRuleBuilder { + if port == nil { + return b + } + var matchStr string + if endPort != nil { + matchStr = fmt.Sprintf("--sport %d:%d ", *port, *endPort) + } else { + matchStr = fmt.Sprintf("--sport %d ", *port) + } + b.specs.WriteString(matchStr) + return b +} + +func (b *iptablesRuleBuilder) MatchICMP(icmpType, icmpCode *int32, ipProtocol Protocol) IPTablesRuleBuilder { + parts := []string{"-p"} + icmpTypeStr := "icmp" + if ipProtocol != ProtocolIPv4 { + icmpTypeStr = "icmpv6" + } + parts = append(parts, icmpTypeStr) + + if icmpType != nil { + icmpTypeFlag := "--icmp-type" + if ipProtocol != ProtocolIPv4 { + icmpTypeFlag = "--icmpv6-type" + } + + if icmpCode != nil { + parts = append(parts, icmpTypeFlag, fmt.Sprintf("%d/%d", *icmpType, *icmpCode)) + } else { + parts = append(parts, icmpTypeFlag, strconv.Itoa(int(*icmpType))) + } + } + b.specs.WriteString(strings.Join(parts, " ")) + b.specs.WriteByte(' ') + + return b +} + +func (b *iptablesRuleBuilder) MatchIGMP(igmpType *int32, groupAddress string) IPTablesRuleBuilder { + parts := []string{"-p", "igmp"} + if igmpType != nil && *igmpType == crdv1beta1.IGMPQuery { + parts = append(parts, "-d", groupAddress) + } + b.specs.WriteString(strings.Join(parts, " ")) + b.specs.WriteByte(' ') + + return b +} + +func (b *iptablesRuleBuilder) SetTarget(target string) IPTablesRuleBuilder { + targetStr := fmt.Sprintf("-j %s ", target) + b.specs.WriteString(targetStr) + return b +} + +func (b *iptablesRuleBuilder) SetComment(comment string) IPTablesRuleBuilder { + commentStr := fmt.Sprintf("-m comment --comment %s ", comment) + b.specs.WriteString(commentStr) + return b +} + +func (b *iptablesRuleBuilder) CopyBuilder() IPTablesRuleBuilder { + var copiedSpec strings.Builder + copiedSpec.Grow(b.specs.Len()) + copiedSpec.WriteString(b.specs.String()) + builder := &iptablesRuleBuilder{ + iptablesRule{ + chain: b.chain, + specs: &copiedSpec, + }, + } + return builder +} + +func (b *iptablesRuleBuilder) Done() IPTablesRule { + return &b.iptablesRule +} + +func (e *iptablesRule) GetSpec() string { + spec := fmt.Sprintf("-A %s %s", e.chain, e.specs) + return spec[:len(spec)-1] +} diff --git a/pkg/agent/util/iptables/interfaces.go b/pkg/agent/util/iptables/interfaces.go new file mode 100644 index 00000000000..ad774570d54 --- /dev/null +++ b/pkg/agent/util/iptables/interfaces.go @@ -0,0 +1,42 @@ +//go:build !windows +// +build !windows + +// Copyright 2023 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 iptables + +import ( + "k8s.io/apimachinery/pkg/util/intstr" + + "antrea.io/antrea/pkg/apis/controlplane/v1beta2" +) + +type IPTablesRuleBuilder interface { + MatchIPSetSrc(ipset string) IPTablesRuleBuilder + MatchIPSetDst(ipset string) IPTablesRuleBuilder + MatchTransProtocol(protocol v1beta2.Protocol) IPTablesRuleBuilder + MatchDstPort(port *intstr.IntOrString, endPort *int32) IPTablesRuleBuilder + MatchSrcPort(port, endPort *int32) IPTablesRuleBuilder + MatchICMP(icmpType, icmpCode *int32, ipProtocol Protocol) IPTablesRuleBuilder + MatchIGMP(igmpType *int32, groupAddress string) IPTablesRuleBuilder + SetTarget(target string) IPTablesRuleBuilder + SetComment(comment string) IPTablesRuleBuilder + CopyBuilder() IPTablesRuleBuilder + Done() IPTablesRule +} + +type IPTablesRule interface { + GetSpec() string +} diff --git a/pkg/agent/util/iptables/iptables.go b/pkg/agent/util/iptables/iptables.go index 9514c16008d..8f9365c5f17 100644 --- a/pkg/agent/util/iptables/iptables.go +++ b/pkg/agent/util/iptables/iptables.go @@ -36,7 +36,7 @@ const ( RawTable = "raw" AcceptTarget = "ACCEPT" - DROPTarget = "DROP" + DropTarget = "DROP" MasqueradeTarget = "MASQUERADE" MarkTarget = "MARK" ReturnTarget = "RETURN" @@ -44,8 +44,10 @@ const ( NoTrackTarget = "NOTRACK" SNATTarget = "SNAT" DNATTarget = "DNAT" + RejectTarget = "REJECT" PreRoutingChain = "PREROUTING" + InputChain = "INPUT" ForwardChain = "FORWARD" PostRoutingChain = "POSTROUTING" OutputChain = "OUTPUT" @@ -352,3 +354,7 @@ func (c *Client) Save() ([]byte, error) { func MakeChainLine(chain string) string { return fmt.Sprintf(":%s - [0:0]", chain) } + +func IsIPv6Protocol(protocol Protocol) bool { + return protocol == ProtocolIPv6 +} diff --git a/pkg/apis/controlplane/v1beta2/types.go b/pkg/apis/controlplane/v1beta2/types.go index 8eae3c3cc4e..bca48cd0d65 100644 --- a/pkg/apis/controlplane/v1beta2/types.go +++ b/pkg/apis/controlplane/v1beta2/types.go @@ -199,6 +199,7 @@ const ( AntreaClusterNetworkPolicy NetworkPolicyType = "AntreaClusterNetworkPolicy" AntreaNetworkPolicy NetworkPolicyType = "AntreaNetworkPolicy" AdminNetworkPolicy NetworkPolicyType = "AdminNetworkPolicy" + HostNetworkPolicy NetworkPolicyType = "HostNetworkPolicy" BaselineAdminNetworkPolicy NetworkPolicyType = "BaselineAdminNetworkPolicy" ) diff --git a/pkg/apiserver/handlers/featuregates/handler_test.go b/pkg/apiserver/handlers/featuregates/handler_test.go index e7a3d9c1f76..13a376a6c0c 100644 --- a/pkg/apiserver/handlers/featuregates/handler_test.go +++ b/pkg/apiserver/handlers/featuregates/handler_test.go @@ -60,6 +60,7 @@ func Test_getGatesResponse(t *testing.T) { {Component: "agent", Name: "EndpointSlice", Status: "Enabled", Version: "GA"}, {Component: "agent", Name: "ExternalNode", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "FlowExporter", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "HostNetworkPolicy", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "IPsecCertAuth", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "L7NetworkPolicy", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "LoadBalancerModeDSR", Status: "Disabled", Version: "ALPHA"}, @@ -192,6 +193,7 @@ func Test_getControllerGatesResponse(t *testing.T) { {Component: "controller", Name: "AntreaIPAM", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "AntreaPolicy", Status: "Enabled", Version: "BETA"}, {Component: "controller", Name: "Egress", Status: egressStatus, Version: "BETA"}, + {Component: "controller", Name: "HostNetworkPolicy", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "IPsecCertAuth", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "L7NetworkPolicy", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "Multicast", Status: multicastStatus, Version: "BETA"}, diff --git a/pkg/features/antrea_features.go b/pkg/features/antrea_features.go index e2b5cb801c8..14c33e7cb8f 100644 --- a/pkg/features/antrea_features.go +++ b/pkg/features/antrea_features.go @@ -146,6 +146,10 @@ const ( // alpha: v1.14 // Enable Egress traffic shaping. EgressTrafficShaping featuregate.Feature = "EgressTrafficShaping" + + // alpha: v1.15 + // Enable users to protect their Kubernetes Node. + HostNetworkPolicy featuregate.Feature = "HostNetworkPolicy" ) var ( @@ -184,6 +188,7 @@ var ( LoadBalancerModeDSR: {Default: false, PreRelease: featuregate.Alpha}, AdminNetworkPolicy: {Default: false, PreRelease: featuregate.Alpha}, EgressTrafficShaping: {Default: false, PreRelease: featuregate.Alpha}, + HostNetworkPolicy: {Default: false, PreRelease: featuregate.Alpha}, } // AgentGates consists of all known feature gates for the Antrea Agent. @@ -211,6 +216,7 @@ var ( Traceflow, TrafficControl, EgressTrafficShaping, + HostNetworkPolicy, ) // ControllerGates consists of all known feature gates for the Antrea Controller. @@ -229,6 +235,7 @@ var ( ServiceExternalIP, SupportBundleCollection, Traceflow, + HostNetworkPolicy, ) // UnsupportedFeaturesOnWindows records the features not supported on @@ -255,6 +262,7 @@ var ( LoadBalancerModeDSR: {}, CleanupStaleUDPSvcConntrack: {}, EgressTrafficShaping: {}, + HostNetworkPolicy: {}, } // supportedFeaturesOnExternalNode records the features supported on an external // Node. Antrea Agent checks the enabled features if it is running on an diff --git a/test/integration/agent/route_test.go b/test/integration/agent/route_test.go index 65ae265a71c..974fb3f7919 100644 --- a/test/integration/agent/route_test.go +++ b/test/integration/agent/route_test.go @@ -145,7 +145,7 @@ func TestInitialize(t *testing.T) { for _, tc := range tcs { t.Logf("Running Initialize test with mode %s node config %s", tc.networkConfig.TrafficEncapMode, nodeConfig) - routeClient, err := route.NewClient(tc.networkConfig, tc.noSNAT, false, false, false, nil) + routeClient, err := route.NewClient(tc.networkConfig, tc.noSNAT, false, false, false, false, nil) assert.NoError(t, err) var xtablesReleasedTime, initializedTime time.Time @@ -252,7 +252,7 @@ func TestIpTablesSync(t *testing.T) { gwLink := createDummyGW(t) defer netlink.LinkDel(gwLink) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false, false, nil) assert.Nil(t, err) inited := make(chan struct{}) @@ -303,7 +303,7 @@ func TestAddAndDeleteSNATRule(t *testing.T) { gwLink := createDummyGW(t) defer netlink.LinkDel(gwLink) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false, false, nil) assert.Nil(t, err) inited := make(chan struct{}) @@ -357,7 +357,7 @@ func TestAddAndDeleteRoutes(t *testing.T) { for _, tc := range tcs { t.Logf("Running test with mode %s peer cidr %s peer ip %s node config %s", tc.mode, tc.peerCIDR, tc.peerIP, nodeConfig) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, false, nil) assert.NoError(t, err) err = routeClient.Initialize(nodeConfig, func() {}) assert.NoError(t, err) @@ -422,7 +422,7 @@ func TestSyncRoutes(t *testing.T) { for _, tc := range tcs { t.Logf("Running test with mode %s peer cidr %s peer ip %s node config %s", tc.mode, tc.peerCIDR, tc.peerIP, nodeConfig) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, false, nil) assert.NoError(t, err) err = routeClient.Initialize(nodeConfig, func() {}) assert.NoError(t, err) @@ -465,7 +465,7 @@ func TestSyncGatewayKernelRoute(t *testing.T) { } require.NoError(t, netlink.AddrAdd(gwLink, &netlink.Addr{IPNet: gwNet}), "configuring gw IP failed") - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, false, false, false, false, false, nil) assert.NoError(t, err) err = routeClient.Initialize(nodeConfig, func() {}) assert.NoError(t, err) @@ -559,7 +559,7 @@ func TestReconcile(t *testing.T) { for _, tc := range tcs { t.Logf("Running test with mode %s added routes %v desired routes %v", tc.mode, tc.addedRoutes, tc.desiredPeerCIDRs) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false, false, nil) assert.NoError(t, err) err = routeClient.Initialize(nodeConfig, func() {}) assert.NoError(t, err) @@ -598,7 +598,7 @@ func TestRouteTablePolicyOnly(t *testing.T) { gwLink := createDummyGW(t) defer netlink.LinkDel(gwLink) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly, IPv4Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly, IPv4Enabled: true}, false, false, false, false, false, nil) assert.NoError(t, err) err = routeClient.Initialize(nodeConfig, func() {}) assert.NoError(t, err) @@ -654,7 +654,7 @@ func TestIPv6RoutesAndNeighbors(t *testing.T) { gwLink := createDummyGW(t) defer netlink.LinkDel(gwLink) - routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true, IPv6Enabled: true}, false, false, false, false, nil) + routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true, IPv6Enabled: true}, false, false, false, false, false, nil) assert.Nil(t, err) _, ipv6Subnet, _ := net.ParseCIDR("fd74:ca9b:172:19::/64") gwIPv6 := net.ParseIP("fd74:ca9b:172:19::1")