Repository: antrea-io/antrea
Pull request #3215 (2022-01-20 21:17:17)

Add SecondaryNetwork feature gate to Antrea manifests
Signed-off-by: Antonin Bas <abas@vmware.com>

codecov-commenter: # [Codecov](https://codecov.io/gh/antrea-io/antrea/pull/3215?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3215](https://codecov.io/gh/antrea-io/antrea/pull/3215?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (f52de20) into [main](https://codecov.io/gh/antrea-io/antrea/commit/839dc5cc5adc9bcaa53aa73e4966f8f7043b4cec?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (839dc5c) will **decrease** coverage by `14.04%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/antrea-io/antrea/pull/3215/graphs/tree.svg?width=650&height=150&src=pr&token=TjWaLL2GoC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/antrea-io/antrea/pull/3215?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@             Coverage Diff             @@
##             main    #3215       +/-   ##
===========================================
- Coverage   58.95%   44.91%   -14.05%     
===========================================
  Files         331      459      +128     
  Lines       28223    43609    +15386     
===========================================
+ Hits        16639    19585     +2946     
- Misses       9739    21885    +12146     
- Partials     1845     2139      +294     
```

| Flag | Coverage Δ | |
|---|---|---|
| integration-tests | `34.02% <ø> (?)` | |
| kind-e2e-tests | `28.94% <ø> (-16.83%)` | :arrow_down: |
| unit-tests | `41.19% <ø> (+0.04%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/antrea-io/antrea/pull/3215?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pkg/controller/types/networkpolicy.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NvbnRyb2xsZXIvdHlwZXMvbmV0d29ya3BvbGljeS5nbw==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/agent/controller/networkpolicy/reject.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L2NvbnRyb2xsZXIvbmV0d29ya3BvbGljeS9yZWplY3QuZ28=) | `0.00% <0.00%> (-87.91%)` | :arrow_down: |
| [...g/agent/apiserver/handlers/featuregates/handler.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FnZW50L2FwaXNlcnZlci9oYW5kbGVycy9mZWF0dXJlZ2F0ZXMvaGFuZGxlci5nbw==) | `0.00% <0.00%> (-82.36%)` | :arrow_down: |
| [pkg/apis/controlplane/v1beta2/helper.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2FwaXMvY29udHJvbHBsYW5lL3YxYmV0YTIvaGVscGVyLmdv) | `33.33% <0.00%> (-66.67%)` | :arrow_down: |
| [...formers/externalversions/security/v1alpha1/tier.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9zZWN1cml0eS92MWFscGhhMS90aWVyLmdv) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...ers/externalversions/core/v1alpha2/clustergroup.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9jb3JlL3YxYWxwaGEyL2NsdXN0ZXJncm91cC5nbw==) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...s/externalversions/core/v1alpha2/externalentity.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9jb3JlL3YxYWxwaGEyL2V4dGVybmFsZW50aXR5Lmdv) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...xternalversions/security/v1alpha1/networkpolicy.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9zZWN1cml0eS92MWFscGhhMS9uZXR3b3JrcG9saWN5Lmdv) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [...versions/security/v1alpha1/clusternetworkpolicy.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2xlZ2FjeWNsaWVudC9pbmZvcm1lcnMvZXh0ZXJuYWx2ZXJzaW9ucy9zZWN1cml0eS92MWFscGhhMS9jbHVzdGVybmV0d29ya3BvbGljeS5nbw==) | `0.00% <0.00%> (-64.29%)` | :arrow_down: |
| [pkg/controller/networkpolicy/mutate.go](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NvbnRyb2xsZXIvbmV0d29ya3BvbGljeS9tdXRhdGUuZ28=) | `0.00% <0.00%> (-62.07%)` | :arrow_down: |
| ... and [233 more](https://codecov.io/gh/antrea-io/antrea/pull/3215/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |

antoninbas: > I assume we need not to include this commit in 1.5, esp. the SR-IOV support still has quite a few limitations.

I opened the PR in case @tnqn wanted to include it in 1.5, but you're right there is no rush
antoninbas: I rebased and resolved merge conflicts
antoninbas: /test-all

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 156b3a7e4..60ce7aca9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -227,7 +227,11 @@ do:
 
 The second step will compile the Antrea code in a `golang` container, and build
 a `Ubuntu 20.04` Docker image that includes all the generated binaries. [`Docker`](https://docs.docker.com/install)
-must be installed on your local machine in advance.
+must be installed on your local machine in advance. If you are a macOS user and
+cannot use [Docker Desktop](https://www.docker.com/products/docker-desktop) to
+contribute to Antrea for licensing reasons, check out this
+[document](docs/contributors/docker-desktop-alternatives.md) for possible
+alternatives.
 
 Alternatively, you can build the Antrea code in your local Go environment. The
 Antrea project uses the [Go modules support](https://github.com/golang/go/wiki/Modules) which was introduced in Go 1.11. It
diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml
index 37158f849..3e9360705 100644
--- a/build/yamls/antrea-aks.yml
+++ b/build/yamls/antrea-aks.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4333,7 +4338,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-t4d76tttk7
+  name: antrea-config-7gm92g46hf
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4404,7 +4409,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-t4d76tttk7
+          value: antrea-config-7gm92g46hf
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4455,7 +4460,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-t4d76tttk7
+          name: antrea-config-7gm92g46hf
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4736,7 +4741,7 @@ spec:
         operator: Exists
       volumes:
       - configMap:
-          name: antrea-config-t4d76tttk7
+          name: antrea-config-7gm92g46hf
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml
index 41f3ee075..27b17cd63 100644
--- a/build/yamls/antrea-eks.yml
+++ b/build/yamls/antrea-eks.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4333,7 +4338,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-t4d76tttk7
+  name: antrea-config-7gm92g46hf
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4404,7 +4409,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-t4d76tttk7
+          value: antrea-config-7gm92g46hf
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4455,7 +4460,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-t4d76tttk7
+          name: antrea-config-7gm92g46hf
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4738,7 +4743,7 @@ spec:
         operator: Exists
       volumes:
       - configMap:
-          name: antrea-config-t4d76tttk7
+          name: antrea-config-7gm92g46hf
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml
index 09ded9d69..50e984b40 100644
--- a/build/yamls/antrea-gke.yml
+++ b/build/yamls/antrea-gke.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4333,7 +4338,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-b696bmff84
+  name: antrea-config-hcg89gk472
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4404,7 +4409,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-b696bmff84
+          value: antrea-config-hcg89gk472
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4455,7 +4460,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-b696bmff84
+          name: antrea-config-hcg89gk472
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4739,7 +4744,7 @@ spec:
           path: /home/kubernetes/bin
         name: host-cni-bin
       - configMap:
-          name: antrea-config-b696bmff84
+          name: antrea-config-hcg89gk472
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml
index 699821483..29a6409db 100644
--- a/build/yamls/antrea-ipsec.yml
+++ b/build/yamls/antrea-ipsec.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4338,7 +4343,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-5fm86722mm
+  name: antrea-config-h7767t7cfh
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4418,7 +4423,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-5fm86722mm
+          value: antrea-config-h7767t7cfh
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4469,7 +4474,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-5fm86722mm
+          name: antrea-config-h7767t7cfh
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4785,7 +4790,7 @@ spec:
         operator: Exists
       volumes:
       - configMap:
-          name: antrea-config-5fm86722mm
+          name: antrea-config-h7767t7cfh
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/antrea-kind.yml b/build/yamls/antrea-kind.yml
index 561367286..2b8187468 100644
--- a/build/yamls/antrea-kind.yml
+++ b/build/yamls/antrea-kind.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4338,7 +4343,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-65d688467c
+  name: antrea-config-kkmb8ft29d
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4409,7 +4414,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-65d688467c
+          value: antrea-config-kkmb8ft29d
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4460,7 +4465,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-65d688467c
+          name: antrea-config-kkmb8ft29d
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4737,7 +4742,7 @@ spec:
           type: CharDevice
         name: dev-tun
       - configMap:
-          name: antrea-config-65d688467c
+          name: antrea-config-kkmb8ft29d
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml
index f0258e4e2..afb487f30 100644
--- a/build/yamls/antrea.yml
+++ b/build/yamls/antrea.yml
@@ -4037,6 +4037,11 @@ data:
     # Enable multicast traffic. This feature is supported only with noEncap mode.
     #  Multicast: false
 
+    # Enable support for provisioning secondary network interfaces for Pods (using
+    # Pod annotations). At the moment, Antrea can only create secondary network
+    # interfaces using SR-IOV VFs on baremetal Nodes.
+    #  SecondaryNetwork: false
+
     # Enable managing external IPs of Services of LoadBalancer type.
     #  ServiceExternalIP: false
 
@@ -4338,7 +4343,7 @@ metadata:
   annotations: {}
   labels:
     app: antrea
-  name: antrea-config-2k82d6f6t4
+  name: antrea-config-k7tg4t4t8h
   namespace: kube-system
 ---
 apiVersion: v1
@@ -4409,7 +4414,7 @@ spec:
             fieldRef:
               fieldPath: spec.serviceAccountName
         - name: ANTREA_CONFIG_MAP_NAME
-          value: antrea-config-2k82d6f6t4
+          value: antrea-config-k7tg4t4t8h
         image: projects.registry.vmware.com/antrea/antrea-ubuntu:latest
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -4460,7 +4465,7 @@ spec:
         key: node-role.kubernetes.io/master
       volumes:
       - configMap:
-          name: antrea-config-2k82d6f6t4
+          name: antrea-config-k7tg4t4t8h
         name: antrea-config
       - name: antrea-controller-tls
         secret:
@@ -4741,7 +4746,7 @@ spec:
         operator: Exists
       volumes:
       - configMap:
-          name: antrea-config-2k82d6f6t4
+          name: antrea-config-k7tg4t4t8h
         name: antrea-config
       - hostPath:
           path: /etc/cni/net.d
diff --git a/build/yamls/base/conf/antrea-agent.conf b/build/yamls/base/conf/antrea-agent.conf
index 38fee3d10..21fb3c61d 100644
--- a/build/yamls/base/conf/antrea-agent.conf
+++ b/build/yamls/base/conf/antrea-agent.conf
@@ -38,6 +38,11 @@ featureGates:
 # Enable multicast traffic. This feature is supported only with noEncap mode.
 #  Multicast: false
 
+# Enable support for provisioning secondary network interfaces for Pods (using
+# Pod annotations). At the moment, Antrea can only create secondary network
+# interfaces using SR-IOV VFs on baremetal Nodes.
+#  SecondaryNetwork: false
+
 # Enable managing external IPs of Services of LoadBalancer type.
 #  ServiceExternalIP: false
 
diff --git a/ci/jenkins/jobs/job-templates.yaml b/ci/jenkins/jobs/job-templates.yaml
index f89cef368..256e05281 100644
--- a/ci/jenkins/jobs/job-templates.yaml
+++ b/ci/jenkins/jobs/job-templates.yaml
@@ -47,6 +47,55 @@
         started-status: '{started_status}'
     wrappers: '{wrappers}'
 
+- job-template:
+    name: '{name}-{test_name}-for-pr'
+    node: '{node}'
+    block-downstream: false
+    block-upstream: false
+    builders: '{builders}'
+    concurrent: false
+    description: '{description}'
+    project-type: freestyle
+    properties:
+    - build-discarder:
+        artifact-days-to-keep: -1
+        artifact-num-to-keep: -1
+        days-to-keep: 7
+        num-to-keep: 30
+    - github:
+        url: 'https://github.com/{org_repo}'
+    publishers: '{publishers}'
+    scm:
+    - git:
+        branches: '{branches}'
+        credentials-id: '{git_credentials_id}'
+        name: origin
+        refspec: +refs/heads/*:refs/remotes/origin/* +refs/pull/${{ghprbPullId}}/*:refs/remotes/origin/pr/${{ghprbPullId}}/*
+        url: 'https://github.com/{org_repo}'
+        wipe-workspace: true
+    triggers:
+    - github-pull-request:
+        admin-list: '{admin_list}'
+        allow-whitelist-orgs-as-admins: '{allow_whitelist_orgs_as_admins}'
+        auth-id: '{ghpr_auth}'
+        auto-close-on-fail: false
+        build-desc-template: null
+        github-hooks: true
+        only-trigger-phrase: '{only_trigger_phrase}'
+        org-list: '{org_list}'
+        permit-all: '{trigger_permit_all}'
+        trigger-phrase: '{trigger_phrase}'
+        white-list-target-branches: '{white_list_target_branches}'
+        white-list: '{white_list}'
+        status-context: '{status_context}'
+        status-url: '{status_url}'
+        success-status: '{success_status}'
+        failure-status: '{failure_status}'
+        error-status: '{error_status}'
+        triggered-status: '{triggered_status}'
+        started-status: '{started_status}'
+    wrappers: '{wrappers}'
+
 - job-template:
     name: '{name}-{test_name}-integration-for-pull-request'
     node: '{node}'
diff --git a/ci/jenkins/jobs/projects.yaml b/ci/jenkins/jobs/projects.yaml
index 46c46d2b6..873965c13 100644
--- a/ci/jenkins/jobs/projects.yaml
+++ b/ci/jenkins/jobs/projects.yaml
@@ -547,7 +547,9 @@
           started_status: null
           wrappers: []
           publishers: []
-      - '{name}-{test_name}-for-pull-request':
+      - '{name}-{test_name}-for-pr':
+          # The cluster name of this job could be too long since job name is encoded in CAPI cluster name.
+          # Use the shorter name as a workaround.
           test_name: all-features-conformance
           node: 'antrea-test-node'
           description: 'This is the {test_name} test for {name}.'
diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go
index ed2d18d02..c80035c4d 100644
--- a/cmd/antrea-agent/agent.go
+++ b/cmd/antrea-agent/agent.go
@@ -238,8 +238,8 @@ func run(o *Options) error {
 	v4GroupCounter := proxytypes.NewGroupCounter(false, groupIDUpdates)
 	v6GroupCounter := proxytypes.NewGroupCounter(true, groupIDUpdates)
 
-	v4Enabled := config.IsIPv4Enabled(nodeConfig, networkConfig.TrafficEncapMode)
-	v6Enabled := config.IsIPv6Enabled(nodeConfig, networkConfig.TrafficEncapMode)
+	v4Enabled := networkConfig.IPv4Enabled
+	v6Enabled := networkConfig.IPv6Enabled
 	var proxier proxy.Proxier
 	if features.DefaultFeatureGate.Enabled(features.AntreaProxy) {
 		proxyAll := o.config.AntreaProxy.ProxyAll
@@ -290,7 +290,9 @@ func run(o *Options) error {
 		statusManagerEnabled,
 		loggingEnabled,
 		asyncRuleDeleteInterval,
-		o.config.DNSServerOverride)
+		o.config.DNSServerOverride,
+		v4Enabled,
+		v6Enabled)
 	if err != nil {
 		return fmt.Errorf("error creating new NetworkPolicy controller: %v", err)
 	}
@@ -612,7 +614,9 @@ func run(o *Options) error {
 		*o.config.EnablePrometheusMetrics,
 		o.config.ClientConnection.Kubeconfig,
 		cipherSuites,
-		cipher.TLSVersionMap[o.config.TLSMinVersion])
+		cipher.TLSVersionMap[o.config.TLSMinVersion],
+		v4Enabled,
+		v6Enabled)
 	if err != nil {
 		return fmt.Errorf("error when creating agent API server: %v", err)
 	}
diff --git a/docs/contributors/cherry-picks.md b/docs/contributors/cherry-picks.md
index f9a464750..5e2c2db40 100644
--- a/docs/contributors/cherry-picks.md
+++ b/docs/contributors/cherry-picks.md
@@ -17,7 +17,7 @@ policy](../versioning.md#minor-releases-and-patch-releases).
 
 * A PR which was approved and merged into the main branch.
 * The PR was identified as a good candidate for backporting by an Antrea
-  maintainer: they will leave a comment on Github for the PR and provide a list
+  maintainer: they will label the PR with `action/backport` and comment a list
   of release branches to which the patch should be backported (example:
   [`release-1.0`](https://github.com/antrea-io/antrea/tree/release-1.0)).
 * Have the [Github CLI](https://cli.github.com/) installed (version >= 1.3) and
diff --git a/docs/contributors/docker-desktop-alternatives.md b/docs/contributors/docker-desktop-alternatives.md
new file mode 100644
index 000000000..97b20b0fd
--- /dev/null
+++ b/docs/contributors/docker-desktop-alternatives.md
@@ -0,0 +1,53 @@
+# Docker Desktop Alternatives
+
+The Antrea build system relies on Docker to build container images, which can
+then be used to test Antrea locally. As an Antrea developer, if you run `make`,
+`docker build` will be invoked to build the `antrea-ubuntu` container image. On
+Linux, Docker Engine (based on moby) runs natively, but if you use macOS or
+Windows for Antrea development, Docker needs to run inside a Linux Virtual
+Machine (VM). This VM is typically managed by [Docker
+Desktop](https://www.docker.com/products/docker-desktop). Starting January 31
+2022, Docker Desktop requires a per user paid subscription for professional use
+in "large" companies (more than 250 employees or more than $10 million in annual
+revenue). See <https://www.docker.com/pricing/faq> for details. For developers
+who contribute to Antrea as an employee of such a company (and not in their own
+individual capacity), it is no longer possible to use Docker Desktop to build
+(and possibly run) Antrea Docker images locally, unless they have a Docker
+subscription.
+
+For contributors who do not have a Docker subscription, we recommend the
+following Docker Desktop alternatives.
+
+## Colima (macOS)
+
+[Colima](https://github.com/abiosoft/colima) is a UI built with
+[Lima](https://github.com/lima-vm/lima). It supports running a container runtime
+(docker, containerd or kuberneters) on macOS, inside a Lima VM. Major benefits
+of Colima include its ability to be used as a drop-in replacement for Docker
+Desktop and its ability to coexist with Docker Desktop on the same macOS
+machine.
+
+To install and run Colima, follow these steps:
+
+* `brew install colima`
+* `colima start` to start Colima (the Linux VM) with the default
+  configuration. Check the Colima documentation for configuration options. By
+  default, Colima will use the Docker runtime. This means that you can keep
+  using the `docker` CLI and that no changes are required to build Antrea.
+* `docker context list` and check that the `colima` context is selected. You can
+  use `docker context use desktop-linux` to go back to Docker Desktop.
+* `make` to build Antrea locally. Check that the `antrea-ubuntu` image is
+  available by listing all images with `docker images`.
+
+TODO: validate that Kind can be used with Colima without any issue.
+
+## Rancher Desktop (macOS and Windows)
+
+Rancher Desktop is another possible alternative to Docker Desktop, which
+supports Windows in addition to macOS. On macOS, it also uses Lima as the Linux
+VM. Two major differences with Colima are that Rancher Desktop will always run
+Kubernetes, and that Rancher Desktop uses the
+[`nerdctl`](https://github.com/containerd/nerdctl) UI for container management
+instead of `docker`. However, the `nerdctl` and `docker` UIs are supposed to be
+compatible, so in theory it should be possible to alias `docker` to `nerdctl`
+and keep using the Antrea build system as is (to be tested).
diff --git a/docs/contributors/github-labels.md b/docs/contributors/github-labels.md
index cdea5aa57..480d50074 100644
--- a/docs/contributors/github-labels.md
+++ b/docs/contributors/github-labels.md
@@ -111,6 +111,8 @@ The labels in this list originated within Kubernetes at
 | triage/needs-information           | Indicates an issue needs more information in order to work on it. | Humans |
 | triage/not-reproducible            | Indicates an issue can not be reproduced as described. | Humans |
 | triage/unresolved                  | Indicates an issue that can not or will not be resolved. | Humans |
+| action/backport                    | Indicates a PR that requires backports. | Humans |
+| action/release-note                | Indicates a PR that should be included in release notes.  | Humans |
 
 ## Labels that apply only to issues
 
diff --git a/docs/feature-gates.md b/docs/feature-gates.md
index 9c49b826a..5b96d8ab5 100644
--- a/docs/feature-gates.md
+++ b/docs/feature-gates.md
@@ -47,6 +47,7 @@ example, to enable `AntreaProxy` on Linux, edit the Agent configuration in the
 | `AntreaIPAM`            | Agent + Controller | `false` | Alpha | v1.4          | N/A          | N/A        | Yes                |       |
 | `Multicast`             | Agent              | `false` | Alpha | v1.5          | N/A          | N/A        | Yes                |       |
 | `SecondaryNetwork`      | Agent              | `false` | Alpha | v1.5          | N/A          | N/A        | Yes                |       |
+| `ServiceExternalIP`     | Agent + Controller | `false` | Alpha | v1.5          | N/A          | N/A        | Yes                |       |
 
 ## Description and Requirements of Features
 
@@ -254,3 +255,23 @@ More documentation will be coming in the future.
 
 At the moment, Antrea can only create secondary network interfaces using SR-IOV
 VFs on baremetal Linux Nodes.
+
+### ServiceExternalIP
+
+The `ServiceExternalIP` feature enables a controller which can allocate external
+IPs for Services with type `LoadBalancer`. External IPs are allocated from an
+`ExternalIPPool` resource and each IP gets assigned to a Node selected by the
+`nodeSelector` of the pool automatically. That Node will receive Service traffic
+destined to that IP and distribute it among the backend Endpoints for the
+Service (through kube-proxy). To enable external IP allocation for a
+`LoadBalancer` Service, you need to annotate the Service with
+`"service.antrea.io/external-ip-pool": "<externalIPPool name>"` and define the
+appropriate `ExternalIPPool` resource.
+
+More documentation will be coming in the future.
+
+#### Requirements for this Feature
+
+This feature is currently only supported for Nodes running Linux. At the moment,
+it only works with kube-proxy and it requires Antrea ProxyAll to be disabled
+(which is the default Antrea configuration).
diff --git a/docs/maintainers/release.md b/docs/maintainers/release.md
index 1a91d966b..d2a9f0924 100644
--- a/docs/maintainers/release.md
+++ b/docs/maintainers/release.md
@@ -15,13 +15,14 @@ release. We use `<TAG>` as a placeholder for the release tag (e.g. `v1.4.0`).
 2. Open a PR (labelled with `kind/release`) against the appropriate release
    branch with the following commits:
    - a commit to update the [CHANGELOG](../../CHANGELOG). *For a minor release*,
-     all significant changes and all bug fixes since the first version of the
-     previous minor release should be mentioned, even bug fixes which have
-     already been included in some patch release. *For a patch release*, you
-     will mention all the bug fixes since the previous release with the same
-     minor version. The commit message must be *exactly* `"Update CHANGELOG for
-     <TAG> release"`, as a bot will look for this commit and cherry-pick it to
-     update the main branch (starting with Antrea v1.0). The
+     all significant changes and all bug fixes (labelled with
+     `action/release-note` since the first version of the previous minor release
+     should be mentioned, even bug fixes which have already been included in
+     some patch release. *For a patch release*, you will mention all the bug
+     fixes since the previous release with the same minor version. The commit
+     message must be *exactly* `"Update CHANGELOG for <TAG> release"`, as a bot
+     will look for this commit and cherry-pick it to update the main branch
+     (starting with Antrea v1.0). The
      [process-changelog.go](../../hack/release/process-changelog.go) script may
      be used to easily generate links to PRs and the Github profiles of PR
      authors.
diff --git a/hack/.notableofcontents b/hack/.notableofcontents
index 8b28362ff..42f5e577f 100644
--- a/hack/.notableofcontents
+++ b/hack/.notableofcontents
@@ -6,6 +6,7 @@ docs/assets/README.md
 docs/assets/logo/README.md
 docs/contributors/cherry-picks.md
 docs/contributors/code-generation.md
+docs/contributors/docker-desktop-alternatives.md
 docs/contributors/eks-terraform.md
 docs/contributors/github-labels.md
 docs/contributors/manual-installation.md
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index 4ce1790d4..015f25085 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -343,6 +343,9 @@ func (i *Initializer) Initialize() error {
 		return err
 	}
 
+	i.networkConfig.IPv4Enabled = config.IsIPv4Enabled(i.nodeConfig, i.networkConfig.TrafficEncapMode)
+	i.networkConfig.IPv6Enabled = config.IsIPv6Enabled(i.nodeConfig, i.networkConfig.TrafficEncapMode)
+
 	if err := i.prepareHostNetwork(); err != nil {
 		return err
 	}
diff --git a/pkg/agent/apiserver/apiserver.go b/pkg/agent/apiserver/apiserver.go
index 434ccde40..763df7492 100644
--- a/pkg/agent/apiserver/apiserver.go
+++ b/pkg/agent/apiserver/apiserver.go
@@ -84,10 +84,10 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic
 	s.Handler.NonGoRestfulMux.HandleFunc("/ovstracing", ovstracing.HandleFunc(aq))
 }
 
-func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier) error {
+func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) error {
 	systemGroup := genericapiserver.NewDefaultAPIGroupInfo(systemv1beta1.GroupName, scheme, metav1.ParameterCodec, codecs)
 	systemStorage := map[string]rest.Storage{}
-	supportBundleStorage := supportbundle.NewAgentStorage(ovsctl.NewClient(aq.GetNodeConfig().OVSBridge), aq, npq)
+	supportBundleStorage := supportbundle.NewAgentStorage(ovsctl.NewClient(aq.GetNodeConfig().OVSBridge), aq, npq, v4Enabled, v6Enabled)
 	systemStorage["supportbundles"] = supportBundleStorage.SupportBundle
 	systemStorage["supportbundles/download"] = supportBundleStorage.Download
 	systemGroup.VersionedResourcesStorageMap["v1beta1"] = systemStorage
@@ -96,7 +96,7 @@ func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.Agent
 
 // New creates an APIServer for running in antrea agent.
 func New(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, bindPort int,
-	enableMetrics bool, kubeconfig string, cipherSuites []uint16, tlsMinVersion uint16) (*agentAPIServer, error) {
+	enableMetrics bool, kubeconfig string, cipherSuites []uint16, tlsMinVersion uint16, v4Enabled, v6Enabled bool) (*agentAPIServer, error) {
 	cfg, err := newConfig(npq, bindPort, enableMetrics, kubeconfig)
 	if err != nil {
 		return nil, err
@@ -107,7 +107,7 @@ func New(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier
 	}
 	s.SecureServingInfo.CipherSuites = cipherSuites
 	s.SecureServingInfo.MinTLSVersion = tlsMinVersion
-	if err := installAPIGroup(s, aq, npq); err != nil {
+	if err := installAPIGroup(s, aq, npq, v4Enabled, v6Enabled); err != nil {
 		return nil, err
 	}
 	installHandlers(aq, npq, s)
diff --git a/pkg/agent/config/node_config.go b/pkg/agent/config/node_config.go
index 248d9c835..01bd20a96 100644
--- a/pkg/agent/config/node_config.go
+++ b/pkg/agent/config/node_config.go
@@ -154,6 +154,8 @@ type NetworkConfig struct {
 	IPSecPSK              string
 	TransportIface        string
 	TransportIfaceCIDRs   []string
+	IPv4Enabled           bool
+	IPv6Enabled           bool
 }
 
 // IsIPv4Enabled returns true if the cluster network supports IPv4.
diff --git a/pkg/agent/controller/networkpolicy/fqdn.go b/pkg/agent/controller/networkpolicy/fqdn.go
index cfe0ddae0..f7962039e 100644
--- a/pkg/agent/controller/networkpolicy/fqdn.go
+++ b/pkg/agent/controller/networkpolicy/fqdn.go
@@ -148,9 +148,11 @@ type fqdnController struct {
 	selectorItemToFQDN map[fqdnSelectorItem]sets.String
 	// selectorItemToRuleIDs maps fqdnToSelectorItem to the rules that contains the selector.
 	selectorItemToRuleIDs map[fqdnSelectorItem]sets.String
+	ipv4Enabled           bool
+	ipv6Enabled           bool
 }
 
-func newFQDNController(client openflow.Client, allocator *idAllocator, dnsServerOverride string, dirtyRuleHandler func(string)) (*fqdnController, error) {
+func newFQDNController(client openflow.Client, allocator *idAllocator, dnsServerOverride string, dirtyRuleHandler func(string), v4Enabled, v6Enabled bool) (*fqdnController, error) {
 	controller := &fqdnController{
 		ofClient:               client,
 		dirtyRuleHandler:       dirtyRuleHandler,
@@ -162,6 +164,8 @@ func newFQDNController(client openflow.Client, allocator *idAllocator, dnsServer
 		fqdnToSelectorItem:     map[string]map[fqdnSelectorItem]struct{}{},
 		selectorItemToFQDN:     map[fqdnSelectorItem]sets.String{},
 		selectorItemToRuleIDs:  map[fqdnSelectorItem]sets.String{},
+		ipv4Enabled:            v4Enabled,
+		ipv6Enabled:            v6Enabled,
 	}
 	if controller.ofClient != nil {
 		if err := controller.ofClient.NewDNSpacketInConjunction(dnsInterceptRuleID); err != nil {
@@ -584,14 +588,14 @@ func (f *fqdnController) parseDNSResponse(msg *dns.Msg) (string, map[string]net.
 	for _, ans := range msg.Answer {
 		switch r := ans.(type) {
 		case *dns.A:
-			if f.ofClient.IsIPv4Enabled() {
+			if f.ipv4Enabled {
 				responseIPs[r.A.String()] = r.A
 				if r.Header().Ttl < lowestTTL {
 					lowestTTL = r.Header().Ttl
 				}
 			}
 		case *dns.AAAA:
-			if f.ofClient.IsIPv6Enabled() {
+			if f.ipv6Enabled {
 				responseIPs[r.AAAA.String()] = r.AAAA
 				if r.Header().Ttl < lowestTTL {
 					lowestTTL = r.Header().Ttl
@@ -650,7 +654,7 @@ func (f *fqdnController) lookupIP(ctx context.Context, fqdn string) error {
 		return responseIPs
 	}
 
-	if f.ofClient.IsIPv4Enabled() {
+	if f.ipv4Enabled {
 		lookupTime := time.Now()
 		if ips, err := resolver.LookupIP(ctx, "ip4", fqdn); err == nil {
 			f.onDNSResponse(fqdn, makeResponseIPs(ips), defaultTTL, lookupTime, nil)
@@ -658,7 +662,7 @@ func (f *fqdnController) lookupIP(ctx context.Context, fqdn string) error {
 			v4ok = false
 		}
 	}
-	if f.ofClient.IsIPv6Enabled() {
+	if f.ipv6Enabled {
 		lookupTime := time.Now()
 		if ips, err := resolver.LookupIP(ctx, "ip6", fqdn); err == nil {
 			f.onDNSResponse(fqdn, makeResponseIPs(ips), defaultTTL, lookupTime, nil)
@@ -695,7 +699,7 @@ func (f *fqdnController) makeDNSRequest(ctx context.Context, fqdn string) error
 		return r, nil
 	}
 	v4ok, v6ok := true, true
-	if f.ofClient.IsIPv4Enabled() {
+	if f.ipv4Enabled {
 		m := dns.Msg{}
 		m.SetQuestion(fqdnToQuery, dns.TypeA)
 		lookupTime := time.Now()
@@ -705,7 +709,7 @@ func (f *fqdnController) makeDNSRequest(ctx context.Context, fqdn string) error
 			v4ok = false
 		}
 	}
-	if f.ofClient.IsIPv6Enabled() {
+	if f.ipv6Enabled {
 		m := dns.Msg{}
 		m.SetQuestion(fqdnToQuery, dns.TypeAAAA)
 		lookupTime := time.Now()
diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go
index 2bb74f562..33ba8111d 100644
--- a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go
+++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go
@@ -120,7 +120,9 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider,
 	statusManagerEnabled bool,
 	loggingEnabled bool,
 	asyncRuleDeleteInterval time.Duration,
-	dnsServerOverride string) (*Controller, error) {
+	dnsServerOverride string,
+	v4Enabled bool,
+	v6Enabled bool) (*Controller, error) {
 	idAllocator := newIDAllocator(asyncRuleDeleteInterval, dnsInterceptRuleID)
 	c := &Controller{
 		antreaClientProvider: antreaClientGetter,
@@ -133,14 +135,14 @@ func NewNetworkPolicyController(antreaClientGetter agent.AntreaClientProvider,
 	}
 	if antreaPolicyEnabled {
 		var err error
-		if c.fqdnController, err = newFQDNController(ofClient, idAllocator, dnsServerOverride, c.enqueueRule); err != nil {
+		if c.fqdnController, err = newFQDNController(ofClient, idAllocator, dnsServerOverride, c.enqueueRule, v4Enabled, v6Enabled); err != nil {
 			return nil, err
 		}
 		if c.ofClient != nil {
 			c.ofClient.RegisterPacketInHandler(uint8(openflow.PacketInReasonNP), "dnsresponse", c.fqdnController)
 		}
 	}
-	c.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters)
+	c.reconciler = newReconciler(ofClient, ifaceStore, idAllocator, c.fqdnController, groupCounters, v4Enabled, v6Enabled)
 	c.ruleCache = newRuleCache(c.enqueueRule, entityUpdates, groupIDUpdates)
 	if statusManagerEnabled {
 		c.statusManager = newStatusController(antreaClientGetter, nodeName, c.ruleCache)
diff --git a/pkg/agent/controller/networkpolicy/reconciler.go b/pkg/agent/controller/networkpolicy/reconciler.go
index 06a75ae40..7d9a369d6 100644
--- a/pkg/agent/controller/networkpolicy/reconciler.go
+++ b/pkg/agent/controller/networkpolicy/reconciler.go
@@ -217,6 +217,8 @@ func newReconciler(ofClient openflow.Client,
 	idAllocator *idAllocator,
 	fqdnController *fqdnController,
 	groupCounters []proxytypes.GroupCounter,
+	v4Enabled bool,
+	v6Enabled bool,
 ) *reconciler {
 	priorityAssigners := map[uint8]*tablePriorityAssigner{}
 	for _, table := range openflow.GetAntreaPolicyBaselineTierTables() {
@@ -240,8 +242,8 @@ func newReconciler(ofClient openflow.Client,
 	}
 	// Check if ofClient is nil or not to be compatible with unit tests.
 	if ofClient != nil {
-		reconciler.ipv4Enabled = ofClient.IsIPv4Enabled()
-		reconciler.ipv6Enabled = ofClient.IsIPv6Enabled()
+		reconciler.ipv4Enabled = v4Enabled
+		reconciler.ipv6Enabled = v6Enabled
 	}
 	return reconciler
 }
diff --git a/pkg/agent/interfacestore/interface_cache.go b/pkg/agent/interfacestore/interface_cache.go
index f860cf16f..0045d29f4 100644
--- a/pkg/agent/interfacestore/interface_cache.go
+++ b/pkg/agent/interfacestore/interface_cache.go
@@ -16,7 +16,6 @@ package interfacestore
 
 import (
 	"fmt"
-	"sync"
 
 	"k8s.io/client-go/tools/cache"
 
@@ -65,7 +64,6 @@ const (
 // Todo: add periodic task to sync local cache with container veth pair
 
 type interfaceCache struct {
-	sync.RWMutex
 	cache cache.Indexer
 }
 
@@ -97,8 +95,6 @@ func getInterfaceKey(obj interface{}) (string, error) {
 
 // AddInterface adds interfaceConfig into local cache.
 func (c *interfaceCache) AddInterface(interfaceConfig *InterfaceConfig) {
-	c.Lock()
-	defer c.Unlock()
 	c.cache.Add(interfaceConfig)
 
 	if interfaceConfig.Type == ContainerInterface {
@@ -108,8 +104,6 @@ func (c *interfaceCache) AddInterface(interfaceConfig *InterfaceConfig) {
 
 // DeleteInterface deletes interface from local cache.
 func (c *interfaceCache) DeleteInterface(interfaceConfig *InterfaceConfig) {
-	c.Lock()
-	defer c.Unlock()
 	c.cache.Delete(interfaceConfig)
 
 	if interfaceConfig.Type == ContainerInterface {
@@ -119,8 +113,6 @@ func (c *interfaceCache) DeleteInterface(interfaceConfig *InterfaceConfig) {
 
 // GetInterface retrieves interface from local cache given the interface key.
 func (c *interfaceCache) GetInterface(interfaceKey string) (*InterfaceConfig, bool) {
-	c.RLock()
-	defer c.RUnlock()
 	iface, found, _ := c.cache.GetByKey(interfaceKey)
 	if !found {
 		return nil, false
@@ -131,8 +123,6 @@ func (c *interfaceCache) GetInterface(interfaceKey string) (*InterfaceConfig, bo
 // GetInterfaceByName retrieves interface from local cache given the interface
 // name.
 func (c *interfaceCache) GetInterfaceByName(interfaceName string) (*InterfaceConfig, bool) {
-	c.RLock()
-	defer c.RUnlock()
 	interfaceConfigs, _ := c.cache.ByIndex(interfaceNameIndex, interfaceName)
 	if len(interfaceConfigs) == 0 {
 		return nil, false
@@ -142,8 +132,6 @@ func (c *interfaceCache) GetInterfaceByName(interfaceName string) (*InterfaceCon
 
 // GetInterfaceByIP retrieves interface from local cache given the interface IP.
 func (c *interfaceCache) GetInterfaceByIP(interfaceIP string) (*InterfaceConfig, bool) {
-	c.RLock()
-	defer c.RUnlock()
 	interfaceConfigs, _ := c.cache.ByIndex(interfaceIPIndex, interfaceIP)
 	if len(interfaceConfigs) == 0 {
 		return nil, false
@@ -152,15 +140,11 @@ func (c *interfaceCache) GetInterfaceByIP(interfaceIP string) (*InterfaceConfig,
 }
 
 func (c *interfaceCache) GetContainerInterfaceNum() int {
-	c.RLock()
-	defer c.RUnlock()
 	keys, _ := c.cache.IndexKeys(interfaceTypeIndex, ContainerInterface.String())
 	return len(keys)
 }
 
 func (c *interfaceCache) GetInterfacesByType(interfaceType InterfaceType) []*InterfaceConfig {
-	c.RLock()
-	defer c.RUnlock()
 	objs, _ := c.cache.ByIndex(interfaceTypeIndex, interfaceType.String())
 	interfaces := make([]*InterfaceConfig, len(objs))
 	for i := range objs {
@@ -170,22 +154,16 @@ func (c *interfaceCache) GetInterfacesByType(interfaceType InterfaceType) []*Int
 }
 
 func (c *interfaceCache) Len() int {
-	c.RLock()
-	defer c.RUnlock()
 	return len(c.cache.ListKeys())
 }
 
 func (c *interfaceCache) GetInterfaceKeysByType(interfaceType InterfaceType) []string {
-	c.RLock()
-	defer c.RUnlock()
 	keys, _ := c.cache.IndexKeys(interfaceTypeIndex, interfaceType.String())
 	return keys
 }
 
 // GetContainerInterface retrieves InterfaceConfig by the given container ID.
 func (c *interfaceCache) GetContainerInterface(containerID string) (*InterfaceConfig, bool) {
-	c.RLock()
-	defer c.RUnlock()
 	objs, _ := c.cache.ByIndex(containerIDIndex, containerID)
 	if len(objs) == 0 {
 		return nil, false
@@ -202,8 +180,6 @@ func (c *interfaceCache) GetInterfacesByEntity(name, namespace string) []*Interf
 // name temporarily when the previous Pod is being deleted and the new Pod is being created almost simultaneously.
 // https://github.com/antrea-io/antrea/issues/785#issuecomment-642051884
 func (c *interfaceCache) GetContainerInterfacesByPod(podName string, podNamespace string) []*InterfaceConfig {
-	c.RLock()
-	defer c.RUnlock()
 	objs, _ := c.cache.ByIndex(podIndex, k8s.NamespacedName(podNamespace, podName))
 	interfaces := make([]*InterfaceConfig, len(objs))
 	for i := range objs {
@@ -215,8 +191,6 @@ func (c *interfaceCache) GetContainerInterfacesByPod(podName string, podNamespac
 // GetNodeTunnelInterface retrieves InterfaceConfig for the tunnel to the Node.
 func (c *interfaceCache) GetNodeTunnelInterface(nodeName string) (*InterfaceConfig, bool) {
 	key := util.GenerateNodeTunnelInterfaceKey(nodeName)
-	c.RLock()
-	defer c.RUnlock()
 	obj, ok, _ := c.cache.GetByKey(key)
 	if !ok {
 		return nil, false
@@ -226,8 +200,6 @@ func (c *interfaceCache) GetNodeTunnelInterface(nodeName string) (*InterfaceConf
 
 // GetInterfaceByOFPort retrieves InterfaceConfig by the given ofPort number.
 func (c *interfaceCache) GetInterfaceByOFPort(ofPort uint32) (*InterfaceConfig, bool) {
-	c.RLock()
-	defer c.RUnlock()
 	ofportStr := fmt.Sprintf("%d", ofPort)
 	interfaceConfigs, _ := c.cache.ByIndex(ofPortIndex, ofportStr)
 	if len(interfaceConfigs) == 0 {
diff --git a/pkg/agent/openflow/client.go b/pkg/agent/openflow/client.go
index ad577481a..09627844d 100644
--- a/pkg/agent/openflow/client.go
+++ b/pkg/agent/openflow/client.go
@@ -248,10 +248,6 @@ type Client interface {
 	StartPacketInHandler(packetInStartedReason []uint8, stopCh <-chan struct{})
 	// Get traffic metrics of each NetworkPolicy rule.
 	NetworkPolicyMetrics() map[uint32]*types.RuleMetric
-	// Returns if IPv4 is supported on this Node or not.
-	IsIPv4Enabled() bool
-	// Returns if IPv6 is supported on this Node or not.
-	IsIPv6Enabled() bool
 	// SendTCPPacketOut sends TCP packet as a packet-out to OVS.
 	SendTCPPacketOut(
 		srcMAC string,
@@ -652,7 +648,7 @@ func (c *client) InstallDefaultServiceFlows(nodePortAddressesIPv4, nodePortAddre
 		c.sessionAffinityReselectFlow(),
 		c.l2ForwardOutputServiceHairpinFlow(),
 	}
-	if c.IsIPv4Enabled() {
+	if c.networkConfig.IPv4Enabled {
 		flows = append(flows, c.serviceHairpinResponseDNATFlow(binding.ProtocolIP))
 		flows = append(flows, c.serviceLBBypassFlows(binding.ProtocolIP)...)
 		flows = append(flows, c.l3FwdServiceDefaultFlowsViaGW(binding.ProtocolIP, cookie.Service)...)
@@ -666,7 +662,7 @@ func (c *client) InstallDefaultServiceFlows(nodePortAddressesIPv4, nodePortAddre
 			flows = append(flows, c.serviceClassifierFlows(nodePortAddressesIPv4, binding.ProtocolIP)...)
 		}
 	}
-	if c.IsIPv6Enabled() {
+	if c.networkConfig.IPv6Enabled {
 		flows = append(flows, c.serviceHairpinResponseDNATFlow(binding.ProtocolIPv6))
 		flows = append(flows, c.serviceLBBypassFlows(binding.ProtocolIPv6)...)
 		flows = append(flows, c.l3FwdServiceDefaultFlowsViaGW(binding.ProtocolIPv6, cookie.Service)...)
@@ -786,10 +782,10 @@ func (c *client) Initialize(roundInfo types.RoundInfo, nodeConfig *config.NodeCo
 	c.nodeConfig = nodeConfig
 	c.networkConfig = networkConfig
 
-	if config.IsIPv4Enabled(nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if networkConfig.IPv4Enabled {
 		c.ipProtocols = append(c.ipProtocols, binding.ProtocolIP)
 	}
-	if config.IsIPv6Enabled(nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if networkConfig.IPv6Enabled {
 		c.ipProtocols = append(c.ipProtocols, binding.ProtocolIPv6)
 	}
 
@@ -1058,14 +1054,6 @@ func (c *client) InitialTLVMap() error {
 	return c.bridge.AddTLVMap(0x0104, 0x80, 4, 0)
 }
 
-func (c *client) IsIPv4Enabled() bool {
-	return config.IsIPv4Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode)
-}
-
-func (c *client) IsIPv6Enabled() bool {
-	return config.IsIPv6Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode)
-}
-
 // setBasePacketOutBuilder sets base IP properties of a packetOutBuilder which can have more packet data added.
 func setBasePacketOutBuilder(packetOutBuilder binding.PacketOutBuilder, srcMAC string, dstMAC string, srcIP string, dstIP string, inPort uint32, outPort uint32) (binding.PacketOutBuilder, error) {
 	// Set ethernet header.
diff --git a/pkg/agent/openflow/network_policy.go b/pkg/agent/openflow/network_policy.go
index af36a63f4..badd3020c 100644
--- a/pkg/agent/openflow/network_policy.go
+++ b/pkg/agent/openflow/network_policy.go
@@ -781,7 +781,7 @@ func (c *clause) addAddrFlows(client *client, addrType types.AddressType, addres
 func (c *clause) addServiceFlows(client *client, ports []v1beta2.Service, priority *uint16, matchSrc bool) []*conjMatchFlowContextChange {
 	var conjMatchFlowContextChanges []*conjMatchFlowContextChange
 	for _, port := range ports {
-		matches := generateServicePortConjMatches(c.ruleTable.GetID(), port, priority, client.IsIPv4Enabled(), client.IsIPv6Enabled(), matchSrc)
+		matches := generateServicePortConjMatches(c.ruleTable.GetID(), port, priority, client.networkConfig.IPv4Enabled, client.networkConfig.IPv6Enabled, matchSrc)
 		for _, match := range matches {
 			ctxChange := c.addConjunctiveMatchFlow(client, match)
 			conjMatchFlowContextChanges = append(conjMatchFlowContextChanges, ctxChange)
@@ -994,7 +994,7 @@ func (c *client) addRuleToConjunctiveMatch(conj *policyRuleConjunction, rule *ty
 	}
 	if conj.serviceClause != nil {
 		for _, port := range rule.Service {
-			matches := generateServicePortConjMatches(conj.serviceClause.ruleTable.GetID(), port, rule.Priority, c.IsIPv4Enabled(), c.IsIPv6Enabled(), false)
+			matches := generateServicePortConjMatches(conj.serviceClause.ruleTable.GetID(), port, rule.Priority, c.networkConfig.IPv4Enabled, c.networkConfig.IPv6Enabled, false)
 			for _, match := range matches {
 				c.addActionToConjunctiveMatch(conj.serviceClause, match)
 			}
diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go
index 42dc5cc47..a62e8f721 100644
--- a/pkg/agent/route/route_linux.go
+++ b/pkg/agent/route/route_linux.go
@@ -394,10 +394,8 @@ func (c *Client) writeEKSMangleRule(iptablesData *bytes.Buffer) {
 // It's idempotent and can safely be called on every startup.
 func (c *Client) syncIPTables() error {
 	var err error
-	v4Enabled := config.IsIPv4Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode)
-	v6Enabled := config.IsIPv6Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode)
 
-	c.ipt, err = iptables.New(v4Enabled, v6Enabled)
+	c.ipt, err = iptables.New(c.networkConfig.IPv4Enabled, c.networkConfig.IPv6Enabled)
 	if err != nil {
 		return fmt.Errorf("error creating IPTables instance: %v", err)
 	}
@@ -443,7 +441,7 @@ func (c *Client) syncIPTables() error {
 		return true
 	})
 	// Use iptables-restore to configure IPv4 settings.
-	if v4Enabled {
+	if c.networkConfig.IPv4Enabled {
 		iptablesData := c.restoreIptablesData(c.nodeConfig.PodIPv4CIDR, antreaPodIPSet, localAntreaFlexibleIPAMPodIPSet, antreaNodePortIPSet, config.VirtualServiceIPv4, snatMarkToIPv4)
 		// Setting --noflush to keep the previous contents (i.e. non antrea managed chains) of the tables.
 		if err := c.ipt.Restore(iptablesData.Bytes(), false, false); err != nil {
@@ -452,7 +450,7 @@ func (c *Client) syncIPTables() error {
 	}
 
 	// Use ip6tables-restore to configure IPv6 settings.
-	if v6Enabled {
+	if c.networkConfig.IPv6Enabled {
 		iptablesData := c.restoreIptablesData(c.nodeConfig.PodIPv6CIDR, antreaPodIP6Set, localAntreaFlexibleIPAMPodIP6Set, antreaNodePortIP6Set, config.VirtualServiceIPv6, snatMarkToIPv6)
 		// Setting --noflush to keep the previous contents (i.e. non antrea managed chains) of the tables.
 		if err := c.ipt.Restore(iptablesData.Bytes(), false, true); err != nil {
@@ -668,12 +666,12 @@ func (c *Client) initIPRoutes() error {
 }
 
 func (c *Client) initServiceIPRoutes() error {
-	if config.IsIPv4Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if c.networkConfig.IPv4Enabled {
 		if err := c.addVirtualServiceIPRoute(false); err != nil {
 			return err
 		}
 	}
-	if config.IsIPv6Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if c.networkConfig.IPv6Enabled {
 		if err := c.addVirtualServiceIPRoute(true); err != nil {
 			return err
 		}
diff --git a/pkg/agent/route/route_windows.go b/pkg/agent/route/route_windows.go
index 047b45cf6..81b6fb7d8 100644
--- a/pkg/agent/route/route_windows.go
+++ b/pkg/agent/route/route_windows.go
@@ -106,12 +106,12 @@ func (c *Client) Initialize(nodeConfig *config.NodeConfig, done func()) error {
 }
 
 func (c *Client) initServiceIPRoutes() error {
-	if config.IsIPv4Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if c.networkConfig.IPv4Enabled {
 		if err := c.addVirtualServiceIPRoute(false); err != nil {
 			return err
 		}
 	}
-	if config.IsIPv6Enabled(c.nodeConfig, c.networkConfig.TrafficEncapMode) {
+	if c.networkConfig.IPv6Enabled {
 		return fmt.Errorf("IPv6 is not supported on Windows")
 	}
 	return nil
diff --git a/pkg/apiserver/registry/system/supportbundle/rest.go b/pkg/apiserver/registry/system/supportbundle/rest.go
index 7c7b6cb22..55dbf4ed8 100644
--- a/pkg/apiserver/registry/system/supportbundle/rest.go
+++ b/pkg/apiserver/registry/system/supportbundle/rest.go
@@ -71,7 +71,7 @@ func NewControllerStorage() Storage {
 }
 
 // NewAgentStorage creates a support bundle storage for working on antrea agent.
-func NewAgentStorage(client ovsctl.OVSCtlClient, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier) Storage {
+func NewAgentStorage(client ovsctl.OVSCtlClient, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) Storage {
 	bundle := &supportBundleREST{
 		mode:         modeAgent,
 		ovsCtlClient: client,
@@ -81,6 +81,8 @@ func NewAgentStorage(client ovsctl.OVSCtlClient, aq agentquerier.AgentQuerier, n
 			ObjectMeta: metav1.ObjectMeta{Name: modeAgent},
 			Status:     systemv1beta1.SupportBundleStatusNone,
 		},
+		v4Enabled: v4Enabled,
+		v6Enabled: v6Enabled,
 	}
 	return Storage{
 		Mode:          modeAgent,
@@ -114,6 +116,8 @@ type supportBundleREST struct {
 	ovsCtlClient ovsctl.OVSCtlClient
 	aq           agentquerier.AgentQuerier
 	npq          querier.AgentNetworkPolicyInfoQuerier
+	v4Enabled    bool
+	v6Enabled    bool
 }
 
 // Create triggers a bundle generation. It only allows resource creation when
@@ -253,7 +257,7 @@ func (r *supportBundleREST) collect(ctx context.Context, dumpers ...func(string)
 }
 
 func (r *supportBundleREST) collectAgent(ctx context.Context, since string) (*systemv1beta1.SupportBundle, error) {
-	dumper := support.NewAgentDumper(defaultFS, defaultExecutor, r.ovsCtlClient, r.aq, r.npq, since)
+	dumper := support.NewAgentDumper(defaultFS, defaultExecutor, r.ovsCtlClient, r.aq, r.npq, since, r.v4Enabled, r.v6Enabled)
 	return r.collect(
 		ctx,
 		dumper.DumpLog,
diff --git a/pkg/support/dump.go b/pkg/support/dump.go
index f60e9b201..c8c621b7d 100644
--- a/pkg/support/dump.go
+++ b/pkg/support/dump.go
@@ -281,6 +281,8 @@ type agentDumper struct {
 	aq           agentquerier.AgentQuerier
 	npq          querier.AgentNetworkPolicyInfoQuerier
 	since        string
+	v4Enabled    bool
+	v6Enabled    bool
 }
 
 func (d *agentDumper) DumpAgentInfo(basedir string) error {
@@ -326,7 +328,7 @@ func (d *agentDumper) DumpOVSPorts(basedir string) error {
 	return writeFile(d.fs, filepath.Join(basedir, "ovsports"), "ports", []byte(strings.Join(portData, "\n")))
 }
 
-func NewAgentDumper(fs afero.Fs, executor exec.Interface, ovsCtlClient ovsctl.OVSCtlClient, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, since string) AgentDumper {
+func NewAgentDumper(fs afero.Fs, executor exec.Interface, ovsCtlClient ovsctl.OVSCtlClient, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, since string, v4Enabled, v6Enabled bool) AgentDumper {
 	return &agentDumper{
 		fs:           fs,
 		executor:     executor,
@@ -334,5 +336,7 @@ func NewAgentDumper(fs afero.Fs, executor exec.Interface, ovsCtlClient ovsctl.OV
 		aq:           aq,
 		npq:          npq,
 		since:        since,
+		v4Enabled:    v4Enabled,
+		v6Enabled:    v6Enabled,
 	}
 }
diff --git a/pkg/support/dump_others.go b/pkg/support/dump_others.go
index 36c81b700..b3b9e061c 100644
--- a/pkg/support/dump_others.go
+++ b/pkg/support/dump_others.go
@@ -22,7 +22,6 @@ import (
 	"path"
 	"path/filepath"
 
-	"antrea.io/antrea/pkg/agent/config"
 	"antrea.io/antrea/pkg/agent/util/iptables"
 	"antrea.io/antrea/pkg/util/logdir"
 )
@@ -48,11 +47,7 @@ func (d *agentDumper) DumpHostNetworkInfo(basedir string) error {
 }
 
 func (d *agentDumper) dumpIPTables(basedir string) error {
-	nodeConfig := d.aq.GetNodeConfig()
-	networkConfig := d.aq.GetNetworkConfig()
-	v4Enabled := config.IsIPv4Enabled(nodeConfig, networkConfig.TrafficEncapMode)
-	v6Enabled := config.IsIPv6Enabled(nodeConfig, networkConfig.TrafficEncapMode)
-	c, err := iptables.New(v4Enabled, v6Enabled)
+	c, err := iptables.New(d.v4Enabled, d.v6Enabled)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/pkg/agent/controller/networkpolicy/fqdn_test.go b/pkg/agent/controller/networkpolicy/fqdn_test.go
index 3abe00ce2..273520d3a 100644
--- a/pkg/agent/controller/networkpolicy/fqdn_test.go
+++ b/pkg/agent/controller/networkpolicy/fqdn_test.go
@@ -29,8 +29,6 @@ import (
 
 func newMockFQDNController(t *testing.T, controller *gomock.Controller, dnsServer *string) (*fqdnController, *openflowtest.MockClient) {
 	mockOFClient := openflowtest.NewMockClient(controller)
-	mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-	mockOFClient.EXPECT().IsIPv6Enabled().Return(false).AnyTimes()
 	mockOFClient.EXPECT().NewDNSpacketInConjunction(gomock.Any()).Return(nil).AnyTimes()
 	dirtyRuleHandler := func(rule string) {}
 	dnsServerAddr := "8.8.8.8:53" // dummy DNS server, will not be used since we don't send any request in these tests
@@ -42,6 +40,8 @@ func newMockFQDNController(t *testing.T, controller *gomock.Controller, dnsServe
 		newIDAllocator(testAsyncDeleteInterval),
 		dnsServerAddr,
 		dirtyRuleHandler,
+		true,
+		false,
 	)
 	require.NoError(t, err)
 	return f, mockOFClient
diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go
index 545fb338e..09fff89c3 100644
--- a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go
+++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go
@@ -56,7 +56,7 @@ func newTestController() (*Controller, *fake.Clientset, *mockReconciler) {
 	ch2 := make(chan string, 100)
 	groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(false, ch2)}
 	controller, _ := NewNetworkPolicyController(&antreaClientGetter{clientset}, nil, nil, "node1", ch, groupCounters, ch2,
-		true, true, true, true, testAsyncDeleteInterval, "8.8.8.8:53")
+		true, true, true, true, testAsyncDeleteInterval, "8.8.8.8:53", true, false)
 	reconciler := newMockReconciler()
 	controller.reconciler = reconciler
 	controller.antreaPolicyLogger = nil
diff --git a/pkg/agent/controller/networkpolicy/reconciler_test.go b/pkg/agent/controller/networkpolicy/reconciler_test.go
index ac4fc0366..4ffd07571 100644
--- a/pkg/agent/controller/networkpolicy/reconciler_test.go
+++ b/pkg/agent/controller/networkpolicy/reconciler_test.go
@@ -98,11 +98,11 @@ func newCIDR(cidrStr string) *net.IPNet {
 	return tmpIPNet
 }
 
-func newTestReconciler(t *testing.T, controller *gomock.Controller, ifaceStore interfacestore.InterfaceStore, ofClient *openflowtest.MockClient) *reconciler {
+func newTestReconciler(t *testing.T, controller *gomock.Controller, ifaceStore interfacestore.InterfaceStore, ofClient *openflowtest.MockClient, v4Enabled, v6Enabled bool) *reconciler {
 	f, _ := newMockFQDNController(t, controller, nil)
 	ch := make(chan string, 100)
 	groupCounters := []proxytypes.GroupCounter{proxytypes.NewGroupCounter(false, ch)}
-	r := newReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f, groupCounters)
+	r := newReconciler(ofClient, ifaceStore, newIDAllocator(testAsyncDeleteInterval), f, groupCounters, v4Enabled, v6Enabled)
 	return r
 }
 
@@ -177,8 +177,6 @@ func TestReconcilerForget(t *testing.T) {
 			defer controller.Finish()
 			ifaceStore := interfacestore.NewInterfaceStore()
 			mockOFClient := openflowtest.NewMockClient(controller)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(false).AnyTimes()
 			if len(tt.expectedOFRuleIDs) == 0 {
 				mockOFClient.EXPECT().UninstallPolicyRuleFlows(gomock.Any()).Times(0)
 			} else {
@@ -186,7 +184,7 @@ func TestReconcilerForget(t *testing.T) {
 					mockOFClient.EXPECT().UninstallPolicyRuleFlows(ofID)
 				}
 			}
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, false)
 			for key, value := range tt.lastRealizeds {
 				r.lastRealizeds.Store(key, value)
 			}
@@ -524,13 +522,11 @@ func TestReconcilerReconcile(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
 			mockOFClient := openflowtest.NewMockClient(controller)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(false).AnyTimes()
 			// TODO: mock idAllocator and priorityAssigner
 			for i := 0; i < len(tt.expectedOFRules); i++ {
 				mockOFClient.EXPECT().InstallPolicyRuleFlows(gomock.Any())
 			}
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, false)
 			if err := r.Reconcile(tt.args); (err != nil) != tt.wantErr {
 				t.Fatalf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
 			}
@@ -589,9 +585,7 @@ func TestReconcileWithTransientError(t *testing.T) {
 	controller := gomock.NewController(t)
 	defer controller.Finish()
 	mockOFClient := openflowtest.NewMockClient(controller)
-	mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-	mockOFClient.EXPECT().IsIPv6Enabled().Return(true).AnyTimes()
-	r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+	r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, true)
 	// Set deleteInterval to verify openflow ID is released immediately.
 	r.idAllocator.deleteInterval = 0
 
@@ -750,9 +744,7 @@ func TestReconcilerBatchReconcile(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
 			mockOFClient := openflowtest.NewMockClient(controller)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(false).AnyTimes()
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, true)
 			if tt.numInstalledRules > 0 {
 				// BatchInstall should skip rules already installed
 				r.lastRealizeds.Store(tt.args[0].ID, newLastRealized(tt.args[0]))
@@ -947,8 +939,6 @@ func TestReconcilerUpdate(t *testing.T) {
 			defer controller.Finish()
 			mockOFClient := openflowtest.NewMockClient(controller)
 			mockOFClient.EXPECT().InstallPolicyRuleFlows(gomock.Any()).MaxTimes(2)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(true).AnyTimes()
 			priority := gomock.Any()
 			if !tt.originalRule.isAntreaNetworkPolicyRule() {
 				priority = nil
@@ -968,7 +958,7 @@ func TestReconcilerUpdate(t *testing.T) {
 			if len(tt.expectedDeletedTo) > 0 {
 				mockOFClient.EXPECT().DeletePolicyRuleAddress(gomock.Any(), types.DstAddress, gomock.Eq(tt.expectedDeletedTo), priority)
 			}
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, true)
 			if err := r.Reconcile(tt.originalRule); (err != nil) != tt.wantErr {
 				t.Fatalf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
 			}
@@ -1468,13 +1458,11 @@ func TestReconcilerReconcileIPv6Only(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
 			mockOFClient := openflowtest.NewMockClient(controller)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(false).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(true).AnyTimes()
 			// TODO: mock idAllocator and priorityAssigner
 			for i := 0; i < len(tt.expectedOFRules); i++ {
 				mockOFClient.EXPECT().InstallPolicyRuleFlows(gomock.Any())
 			}
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, false, true)
 			if err := r.Reconcile(tt.args); (err != nil) != tt.wantErr {
 				t.Fatalf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
 			}
@@ -1871,13 +1859,11 @@ func TestReconcilerReconcileDualStack(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
 			mockOFClient := openflowtest.NewMockClient(controller)
-			mockOFClient.EXPECT().IsIPv4Enabled().Return(true).AnyTimes()
-			mockOFClient.EXPECT().IsIPv6Enabled().Return(true).AnyTimes()
 			// TODO: mock idAllocator and priorityAssigner
 			for i := 0; i < len(tt.expectedOFRules); i++ {
 				mockOFClient.EXPECT().InstallPolicyRuleFlows(gomock.Any())
 			}
-			r := newTestReconciler(t, controller, ifaceStore, mockOFClient)
+			r := newTestReconciler(t, controller, ifaceStore, mockOFClient, true, true)
 			if err := r.Reconcile(tt.args); (err != nil) != tt.wantErr {
 				t.Fatalf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
 			}
diff --git a/pkg/agent/openflow/network_policy_test.go b/pkg/agent/openflow/network_policy_test.go
index d1c983d44..4b547fa9f 100644
--- a/pkg/agent/openflow/network_policy_test.go
+++ b/pkg/agent/openflow/network_policy_test.go
@@ -156,7 +156,7 @@ func TestInstallPolicyRuleFlows(t *testing.T) {
 
 	c = prepareClient(ctrl)
 	c.nodeConfig = &config.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: nil}
-	c.networkConfig = &config.NetworkConfig{}
+	c.networkConfig = &config.NetworkConfig{IPv4Enabled: true}
 	c.ipProtocols = []binding.Protocol{binding.ProtocolIP}
 	defaultAction := crdv1alpha1.RuleActionAllow
 	ruleID1 := uint32(101)
@@ -512,7 +512,7 @@ func TestBatchInstallPolicyRuleFlows(t *testing.T) {
 			c.cookieAllocator = cookie.NewAllocator(0)
 			c.ofEntryOperations = mockOperations
 			c.nodeConfig = &config.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: nil}
-			c.networkConfig = &config.NetworkConfig{}
+			c.networkConfig = &config.NetworkConfig{IPv4Enabled: true}
 			c.ipProtocols = []binding.Protocol{binding.ProtocolIP}
 			c.deterministic = true
 
@@ -638,7 +638,7 @@ func TestInstallPolicyRuleFlowsInDualStackCluster(t *testing.T) {
 
 	c = prepareClient(ctrl)
 	c.nodeConfig = &config.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: podIPv6CIDR}
-	c.networkConfig = &config.NetworkConfig{}
+	c.networkConfig = &config.NetworkConfig{IPv4Enabled: true, IPv6Enabled: true}
 	c.ipProtocols = []binding.Protocol{binding.ProtocolIP, binding.ProtocolIPv6}
 	defaultAction := crdv1alpha1.RuleActionAllow
 	ruleID1 := uint32(101)
@@ -1114,7 +1114,7 @@ func TestGetMatchFlowUpdates(t *testing.T) {
 
 	c = prepareClient(ctrl)
 	c.nodeConfig = &config.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: nil}
-	c.networkConfig = &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}
+	c.networkConfig = &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}
 	c.ipProtocols = []binding.Protocol{binding.ProtocolIP}
 	outDropTable.EXPECT().BuildFlow(gomock.Any()).Return(newMockDropFlowBuilder(ctrl)).AnyTimes()
 	cnpOutTable.EXPECT().BuildFlow(gomock.Any()).Return(newMockRuleFlowBuilder(ctrl)).AnyTimes()
diff --git a/pkg/agent/openflow/testing/mock_openflow.go b/pkg/agent/openflow/testing/mock_openflow.go
index 7647ebe10..ab49be308 100644
--- a/pkg/agent/openflow/testing/mock_openflow.go
+++ b/pkg/agent/openflow/testing/mock_openflow.go
@@ -533,34 +533,6 @@ func (mr *MockClientMockRecorder) IsConnected() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockClient)(nil).IsConnected))
 }
 
-// IsIPv4Enabled mocks base method
-func (m *MockClient) IsIPv4Enabled() bool {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsIPv4Enabled")
-	ret0, _ := ret[0].(bool)
-	return ret0
-}
-
-// IsIPv4Enabled indicates an expected call of IsIPv4Enabled
-func (mr *MockClientMockRecorder) IsIPv4Enabled() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsIPv4Enabled", reflect.TypeOf((*MockClient)(nil).IsIPv4Enabled))
-}
-
-// IsIPv6Enabled mocks base method
-func (m *MockClient) IsIPv6Enabled() bool {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsIPv6Enabled")
-	ret0, _ := ret[0].(bool)
-	return ret0
-}
-
-// IsIPv6Enabled indicates an expected call of IsIPv6Enabled
-func (mr *MockClientMockRecorder) IsIPv6Enabled() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsIPv6Enabled", reflect.TypeOf((*MockClient)(nil).IsIPv6Enabled))
-}
-
 // NetworkPolicyMetrics mocks base method
 func (m *MockClient) NetworkPolicyMetrics() map[uint32]*types.RuleMetric {
 	m.ctrl.T.Helper()
diff --git a/test/integration/agent/openflow_test.go b/test/integration/agent/openflow_test.go
index a2f1c30db..e4ddef735 100644
--- a/test/integration/agent/openflow_test.go
+++ b/test/integration/agent/openflow_test.go
@@ -329,7 +329,7 @@ func testReplayFlows(t *testing.T) {
 }
 
 func testInitialize(t *testing.T, config *testConfig) {
-	if _, err := c.Initialize(roundInfo, config.nodeConfig, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap}); err != nil {
+	if _, err := c.Initialize(roundInfo, config.nodeConfig, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap, IPv4Enabled: config.enableIPv4, IPv6Enabled: config.enableIPv6}); err != nil {
 		t.Errorf("Failed to initialize openflow client: %v", err)
 	}
 	for _, tableFlow := range prepareDefaultFlows(config) {
@@ -427,7 +427,7 @@ func TestNetworkPolicyFlows(t *testing.T) {
 	err := ofTestUtils.PrepareOVSBridge(br)
 	require.Nil(t, err, fmt.Sprintf("Failed to prepare OVS bridge %s", br))
 
-	_, err = c.Initialize(roundInfo, &config1.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: podIPv6CIDR, GatewayConfig: gwConfig}, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap})
+	_, err = c.Initialize(roundInfo, &config1.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: podIPv6CIDR, GatewayConfig: gwConfig}, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap, IPv4Enabled: true, IPv6Enabled: true})
 	require.Nil(t, err, "Failed to initialize OFClient")
 
 	defer func() {
@@ -579,7 +579,7 @@ func TestProxyServiceFlows(t *testing.T) {
 	err := ofTestUtils.PrepareOVSBridge(br)
 	require.Nil(t, err, fmt.Sprintf("Failed to prepare OVS bridge %s", br))
 
-	_, err = c.Initialize(roundInfo, &config1.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: podIPv6CIDR, GatewayConfig: gwConfig}, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap})
+	_, err = c.Initialize(roundInfo, &config1.NodeConfig{PodIPv4CIDR: podIPv4CIDR, PodIPv6CIDR: podIPv6CIDR, GatewayConfig: gwConfig}, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap, IPv4Enabled: true})
 	require.Nil(t, err, "Failed to initialize OFClient")
 
 	defer func() {
@@ -1519,7 +1519,7 @@ func TestSNATFlows(t *testing.T) {
 	require.Nil(t, err, fmt.Sprintf("Failed to prepare OVS bridge %s", br))
 
 	config := prepareConfiguration()
-	_, err = c.Initialize(roundInfo, config.nodeConfig, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap})
+	_, err = c.Initialize(roundInfo, config.nodeConfig, &config1.NetworkConfig{TrafficEncapMode: config1.TrafficEncapModeEncap, IPv4Enabled: true, IPv6Enabled: true})
 	require.Nil(t, err, "Failed to initialize OFClient")
 
 	defer func() {
diff --git a/test/integration/agent/route_test.go b/test/integration/agent/route_test.go
index d5c30245b..ab72879d1 100644
--- a/test/integration/agent/route_test.go
+++ b/test/integration/agent/route_test.go
@@ -109,6 +109,7 @@ func TestInitialize(t *testing.T) {
 		{
 			networkConfig: &config.NetworkConfig{
 				TrafficEncapMode: config.TrafficEncapModeNoEncap,
+				IPv4Enabled:      true,
 			},
 			expectNoTrackRules: false,
 		},
@@ -116,6 +117,7 @@ func TestInitialize(t *testing.T) {
 			networkConfig: &config.NetworkConfig{
 				TrafficEncapMode: config.TrafficEncapModeHybrid,
 				TunnelType:       ovsconfig.GeneveTunnel,
+				IPv4Enabled:      true,
 			},
 			noSNAT:               true,
 			expectNoTrackRules:   true,
@@ -125,6 +127,7 @@ func TestInitialize(t *testing.T) {
 			networkConfig: &config.NetworkConfig{
 				TrafficEncapMode: config.TrafficEncapModeEncap,
 				TunnelType:       ovsconfig.VXLANTunnel,
+				IPv4Enabled:      true,
 			},
 			expectNoTrackRules:   true,
 			expectUDPPortInRules: 4789,
@@ -132,6 +135,7 @@ func TestInitialize(t *testing.T) {
 		{
 			networkConfig: &config.NetworkConfig{
 				TrafficEncapMode: config.TrafficEncapModeNoEncap,
+				IPv4Enabled:      true,
 			},
 			xtablesHoldDuration: 5 * time.Second,
 			expectNoTrackRules:  false,
@@ -246,7 +250,7 @@ func TestIpTablesSync(t *testing.T) {
 	gwLink := createDummyGW(t)
 	defer netlink.LinkDel(gwLink)
 
-	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, false, false, false, false)
+	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false)
 	assert.Nil(t, err)
 
 	inited := make(chan struct{})
@@ -297,7 +301,7 @@ func TestAddAndDeleteSNATRule(t *testing.T) {
 	gwLink := createDummyGW(t)
 	defer netlink.LinkDel(gwLink)
 
-	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, false, false, false, false)
+	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true}, false, false, false, false)
 	assert.Nil(t, err)
 
 	inited := make(chan struct{})
@@ -351,7 +355,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}, false, false, false, false)
+		routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false)
 		assert.NoError(t, err)
 		err = routeClient.Initialize(nodeConfig, func() {})
 		assert.NoError(t, err)
@@ -414,7 +418,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}, false, false, false, false)
+		routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false)
 		assert.NoError(t, err)
 		err = routeClient.Initialize(nodeConfig, func() {})
 		assert.NoError(t, err)
@@ -555,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}, false, false, false, false)
+		routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: tc.mode, IPv4Enabled: true}, false, false, false, false)
 		assert.NoError(t, err)
 		err = routeClient.Initialize(nodeConfig, func() {})
 		assert.NoError(t, err)
@@ -593,7 +597,7 @@ func TestRouteTablePolicyOnly(t *testing.T) {
 	gwLink := createDummyGW(t)
 	defer netlink.LinkDel(gwLink)
 
-	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly}, false, false, false, false)
+	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly, IPv4Enabled: true}, false, false, false, false)
 	assert.NoError(t, err)
 	err = routeClient.Initialize(nodeConfig, func() {})
 	assert.NoError(t, err)
@@ -649,7 +653,7 @@ func TestIPv6RoutesAndNeighbors(t *testing.T) {
 	gwLink := createDummyGW(t)
 	defer netlink.LinkDel(gwLink)
 
-	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, false, false, false, false)
+	routeClient, err := route.NewClient(&config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap, IPv4Enabled: true, IPv6Enabled: true}, false, false, false, false)
 	assert.Nil(t, err)
 	_, ipv6Subnet, _ := net.ParseCIDR("fd74:ca9b:172:19::/64")
 	gwIPv6 := net.ParseIP("fd74:ca9b:172:19::1")