Repository: antrea-io/antrea
Pull request #4064 (2022-07-29 13:34:29)

[CI] Fix NetworkPolicy tests on Clouds
GKE's cluster version now defaults to v1.24.1, which no longer creates
secret for service account automatically, the verification in old test
cases would fail. This patch makes the test determine conformance
container image version based on the cluster's version to avoid such
issue.

However, the test suite Netpol introduced in new conformance image uses
a Namespace creation function which is not robust, leanding to random
test failures in GKE test. This patch skips it temporarily.

Besides, legacy NetworkPolicy tests for AKS and EKS have been skipped
by mistake for a while because of a conflicting skip regex. This patch
fixes it.

For https://github.com/antrea-io/antrea/issues/3762

Signed-off-by: Quan Tian <qtian@vmware.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/antrea-io/antrea/pull/4064?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) Report
> Merging [#4064](https://codecov.io/gh/antrea-io/antrea/pull/4064?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) (eb5e2e4) into [main](https://codecov.io/gh/antrea-io/antrea/commit/28d2655ccef7a0780976e47231632b85be10b1e7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) (28d2655) will **decrease** coverage by `8.90%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/antrea-io/antrea/pull/4064/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=antrea-io)](https://codecov.io/gh/antrea-io/antrea/pull/4064?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io)

```diff
@@            Coverage Diff             @@
##             main    #4064      +/-   ##
==========================================
- Coverage   64.09%   55.18%   -8.91%     
==========================================
  Files         294      293       -1     
  Lines       44161    44134      -27     
==========================================
- Hits        28305    24357    -3948     
- Misses      13548    17755    +4207     
+ Partials     2308     2022     -286     
```

| Flag | Coverage Δ | | *Carryforward flag |
|---|---|---|---|
| kind-e2e-tests | `32.68% <ø> (-17.41%)` | :arrow_down: | |
| unit-tests | `44.22% <ø> (ø)` | | Carriedforward from [28d2655](https://codecov.io/gh/antrea-io/antrea/commit/28d2655ccef7a0780976e47231632b85be10b1e7?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) |

*This pull request uses carry forward flags. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) to find out more.

| [Impacted Files](https://codecov.io/gh/antrea-io/antrea/pull/4064?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) | Coverage Δ | |
|---|---|---|
| [pkg/apis/controlplane/v1beta2/helper.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FwaXMvY29udHJvbHBsYW5lL3YxYmV0YTIvaGVscGVyLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/agent/nodeportlocal/k8s/annotations.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvazhzL2Fubm90YXRpb25zLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/agent/proxy/endpointslicecache.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L3Byb3h5L2VuZHBvaW50c2xpY2VjYWNoZS5nbw==) | `0.00% <0.00%> (-83.60%)` | :arrow_down: |
| [pkg/agent/secondarynetwork/cnipodcache/cache.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L3NlY29uZGFyeW5ldHdvcmsvY25pcG9kY2FjaGUvY2FjaGUuZ28=) | `0.00% <0.00%> (-77.56%)` | :arrow_down: |
| [...g/agent/apiserver/handlers/featuregates/handler.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L2FwaXNlcnZlci9oYW5kbGVycy9mZWF0dXJlZ2F0ZXMvaGFuZGxlci5nbw==) | `4.54% <0.00%> (-77.28%)` | :arrow_down: |
| [pkg/agent/nodeportlocal/util/parse\_port.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvdXRpbC9wYXJzZV9wb3J0Lmdv) | `0.00% <0.00%> (-70.00%)` | :arrow_down: |
| [pkg/agent/nodeportlocal/k8s/npl\_controller.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvazhzL25wbF9jb250cm9sbGVyLmdv) | `0.00% <0.00%> (-61.13%)` | :arrow_down: |
| [...agent/controller/traceflow/traceflow\_controller.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L2NvbnRyb2xsZXIvdHJhY2VmbG93L3RyYWNlZmxvd19jb250cm9sbGVyLmdv) | `14.03% <0.00%> (-58.93%)` | :arrow_down: |
| [pkg/agent/nodeportlocal/portcache/port\_table.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvcG9ydGNhY2hlL3BvcnRfdGFibGUuZ28=) | `8.75% <0.00%> (-58.75%)` | :arrow_down: |
| [pkg/agent/nodeportlocal/npl\_agent\_init.go](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io#diff-cGtnL2FnZW50L25vZGVwb3J0bG9jYWwvbnBsX2FnZW50X2luaXQuZ28=) | `0.00% <0.00%> (-57.15%)` | :arrow_down: |
| ... and [103 more](https://codecov.io/gh/antrea-io/antrea/pull/4064/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=antrea-io) | |

tnqn: > LGTM Are we running legacy NetworkPolicy tests for all 3 clouds?

Yes, the latest patch updated to run legacy NetworkPolicy tests only for all 3 clouds because AKS also seems unable to run it due to some some issues: https://jenkins.antrea-ci.rocks/view/cloud/job/cloud-antrea-aks-conformance-net-policy/287/. I think it's perhaps better to wait for NetPol being more reliable before running it on any cloud.
```
netpol-1199-x/a Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-x/b Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-x/c Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-y/a Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-y/b Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-y/c Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
netpol-1199-z/a Ready:False: Unschedulable: 0/2 nodes are available: 2 Too many pods.
```

Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 0cff40dc2..27c2fd728 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -104,6 +104,48 @@ jobs:
           flags: unit-tests
           name: codecov-unit-test
 
+  test-integration:
+    needs: check-changes
+    if: ${{ needs.check-changes.outputs.has_changes == 'yes' }}
+    name: Integration test
+    runs-on: [ubuntu-latest]
+    steps:
+      - name: Set up Go 1.17
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.17
+      - name: Check-out code
+        uses: actions/checkout@v3
+      - uses: actions/cache@v3
+        with:
+          # In order:
+          # * Module download cache
+          # * Build cache (Linux)
+          # * Build cache (Mac)
+          path: |
+            ~/go/pkg/mod
+            ~/.cache/go-build
+            ~/Library/Caches/go-build
+            %LocalAppData%\go-build
+          key: ${{ runner.os }}-${{ env.go-cache-name }}-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-${{ env.go-cache-name }}-
+      - name: Run integration tests
+        run: |
+          ./build/images/ovs/build.sh
+          NO_PULL=1 make docker-test-integration
+      - name: Run integration tests for multicluster
+        run: |
+          cd multicluster
+          make test-integration
+      - name: Codecov
+        uses: codecov/codecov-action@v3
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: .coverage/coverage-integration.txt,multicluster/.coverage/coverage-integration.txt
+          flags: integration-tests
+          name: codecov-integration-test
+
   # golangci-lint-ubuntu and golangci-lint-macos are intentionally not merged into one job with os matrix, otherwise the
   # job wouldn't be expanded if it's skipped and the report of the required check would be missing.
   # See https://github.com/antrea-io/antrea/issues/3563.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 369a5f5ea..3c5caa9dc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -161,7 +161,6 @@ the repo. It's a Github security mechanism.
 
 Here are the trigger phrases for individual checks:
 
-* `/test-integration`: Integration tests
 * `/test-e2e`: Linux IPv4 e2e tests
 * `/test-conformance`: Linux IPv4 conformance tests
 * `/test-networkpolicy`: Linux IPv4 networkpolicy tests
@@ -187,7 +186,7 @@ Here are the trigger phrases for groups of checks:
 * `/test-ipv6-only-all`: Linux IPv6 only tests
 
 Besides, you can skip a check with `/skip-*`, e.g. `/skip-e2e`: skip Linux IPv4
-e2e tests. Integration check cannot be skipped.
+e2e tests.
 
 Skipping a check should be used only when the change doesn't influence the
 specific function. For example:
diff --git a/Makefile b/Makefile
index 64f92785d..7961c0a79 100644
--- a/Makefile
+++ b/Makefile
@@ -241,7 +241,7 @@ tidy:
 	@echo
 	@echo "==> Running integration tests <=="
 	@echo "SOME TESTS WILL FAIL IF NOT RUN AS ROOT!"
-	$(GO) test -coverpkg=antrea.io/antrea/pkg/... -coverprofile=.coverage/coverage-integration.txt -covermode=atomic -cover antrea.io/antrea/test/integration/...
+	$(GO) test -v -coverpkg=antrea.io/antrea/pkg/... -coverprofile=.coverage/coverage-integration.txt -covermode=atomic -cover antrea.io/antrea/test/integration/...
 
 test-tidy:
 	@echo
diff --git a/build/charts/antrea/README.md b/build/charts/antrea/README.md
index 8276efaae..2743da639 100644
--- a/build/charts/antrea/README.md
+++ b/build/charts/antrea/README.md
@@ -106,6 +106,7 @@ Kubernetes: `>= 1.16.0-0`
 | trafficEncryptionMode | string | `"none"` | Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.It must be one of "none", "ipsec", "wireGuard". |
 | transportInterface | string | `""` | Name of the interface on Node which is used for tunneling or routing the traffic across Nodes. |
 | transportInterfaceCIDRs | list | `[]` | Network CIDRs of the interface on Node which is used for tunneling or routing the traffic across Nodes. |
+| tunnelPort | int | `0` | TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT). If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN, and 7471 for STT. |
 | tunnelType | string | `"geneve"` | Tunnel protocol used for encapsulating traffic across Nodes. It must be one of "geneve", "vxlan", "gre", "stt". |
 | webhooks.labelsMutator.enable | bool | `false` |  |
 | whereabouts.enable | bool | `false` |  |
diff --git a/build/charts/antrea/conf/antrea-agent.conf b/build/charts/antrea/conf/antrea-agent.conf
index ed3f4dbd9..72d03382e 100644
--- a/build/charts/antrea/conf/antrea-agent.conf
+++ b/build/charts/antrea/conf/antrea-agent.conf
@@ -110,6 +110,11 @@ noSNAT: {{ .Values.noSNAT }}
 # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
 tunnelType: {{ .Values.tunnelType | quote }}
 
+# TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+# If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+# and 7471 for STT.
+tunnelPort: {{ .Values.tunnelPort }}
+
 # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
 # It has the following options:
 # - none (default):  Inter-node Pod traffic will not be encrypted.
diff --git a/build/charts/antrea/values.yaml b/build/charts/antrea/values.yaml
index dd9a48087..7eaf6510a 100644
--- a/build/charts/antrea/values.yaml
+++ b/build/charts/antrea/values.yaml
@@ -10,6 +10,10 @@ trafficEncapMode: "encap"
 # -- Tunnel protocol used for encapsulating traffic across Nodes. It must be one
 # of "geneve", "vxlan", "gre", "stt".
 tunnelType: "geneve"
+# -- TunnelPort is the destination port for UDP and TCP based tunnel protocols
+# (Geneve, VXLAN, and STT). If zero, it will use the assigned IANA port for the
+# protocol, i.e. 6081 for Geneve, 4789 for VXLAN, and 7471 for STT.
+tunnelPort: 0
 # -- Determines how tunnel traffic is encrypted. Currently encryption only works
 # with encap mode.It must be one of "none", "ipsec", "wireGuard".
 trafficEncryptionMode: "none"
diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml
index 02f8d5344..4311a1a48 100644
--- a/build/yamls/antrea-aks.yml
+++ b/build/yamls/antrea-aks.yml
@@ -2686,6 +2686,11 @@ data:
     # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
     tunnelType: "geneve"
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+    # If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+    # and 7471 for STT.
+    tunnelPort: 0
+
     # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
     # It has the following options:
     # - none (default):  Inter-node Pod traffic will not be encrypted.
@@ -3732,7 +3737,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: beca655f34bfd122082c7efa73505680278a8aa97e74099ca6040bcc4311622f
+        checksum/config: 814300ca95f9d7451131665ebed709cb7639deec890e2ff5ae4c357ae9b00c41
       labels:
         app: antrea
         component: antrea-agent
@@ -3973,7 +3978,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: beca655f34bfd122082c7efa73505680278a8aa97e74099ca6040bcc4311622f
+        checksum/config: 814300ca95f9d7451131665ebed709cb7639deec890e2ff5ae4c357ae9b00c41
       labels:
         app: antrea
         component: antrea-controller
diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml
index 42001d9c5..b06179ad8 100644
--- a/build/yamls/antrea-eks.yml
+++ b/build/yamls/antrea-eks.yml
@@ -2686,6 +2686,11 @@ data:
     # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
     tunnelType: "geneve"
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+    # If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+    # and 7471 for STT.
+    tunnelPort: 0
+
     # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
     # It has the following options:
     # - none (default):  Inter-node Pod traffic will not be encrypted.
@@ -3732,7 +3737,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: beca655f34bfd122082c7efa73505680278a8aa97e74099ca6040bcc4311622f
+        checksum/config: 814300ca95f9d7451131665ebed709cb7639deec890e2ff5ae4c357ae9b00c41
       labels:
         app: antrea
         component: antrea-agent
@@ -3975,7 +3980,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: beca655f34bfd122082c7efa73505680278a8aa97e74099ca6040bcc4311622f
+        checksum/config: 814300ca95f9d7451131665ebed709cb7639deec890e2ff5ae4c357ae9b00c41
       labels:
         app: antrea
         component: antrea-controller
diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml
index fa2f02d27..be9b7d2b2 100644
--- a/build/yamls/antrea-gke.yml
+++ b/build/yamls/antrea-gke.yml
@@ -2686,6 +2686,11 @@ data:
     # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
     tunnelType: "geneve"
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+    # If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+    # and 7471 for STT.
+    tunnelPort: 0
+
     # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
     # It has the following options:
     # - none (default):  Inter-node Pod traffic will not be encrypted.
@@ -3732,7 +3737,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: 741b313c6ab0ed98e7d994985861722f503a93529f90a5141b8a6e0c124d8904
+        checksum/config: 3f9907e9f0f4db91b926904114567c4e2c496f6fe03abb4ef80df5af937c0f19
       labels:
         app: antrea
         component: antrea-agent
@@ -3972,7 +3977,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: 741b313c6ab0ed98e7d994985861722f503a93529f90a5141b8a6e0c124d8904
+        checksum/config: 3f9907e9f0f4db91b926904114567c4e2c496f6fe03abb4ef80df5af937c0f19
       labels:
         app: antrea
         component: antrea-controller
diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml
index bb735b76f..ac3a844b6 100644
--- a/build/yamls/antrea-ipsec.yml
+++ b/build/yamls/antrea-ipsec.yml
@@ -2699,6 +2699,11 @@ data:
     # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
     tunnelType: "gre"
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+    # If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+    # and 7471 for STT.
+    tunnelPort: 0
+
     # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
     # It has the following options:
     # - none (default):  Inter-node Pod traffic will not be encrypted.
@@ -3745,7 +3750,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: c74f29ceba3905db50cef22ee46f73e1c101c108a70e70918b17413c174081e8
+        checksum/config: 7b6ba1830aabcf74b5b0a71c74edf49254c4237d604f9d984428252903901f98
         checksum/ipsec-secret: d0eb9c52d0cd4311b6d252a951126bf9bea27ec05590bed8a394f0f792dcb2a4
       labels:
         app: antrea
@@ -4031,7 +4036,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: c74f29ceba3905db50cef22ee46f73e1c101c108a70e70918b17413c174081e8
+        checksum/config: 7b6ba1830aabcf74b5b0a71c74edf49254c4237d604f9d984428252903901f98
       labels:
         app: antrea
         component: antrea-controller
diff --git a/build/yamls/antrea-windows.yml b/build/yamls/antrea-windows.yml
index 7af06ec8e..21984166d 100644
--- a/build/yamls/antrea-windows.yml
+++ b/build/yamls/antrea-windows.yml
@@ -48,6 +48,11 @@ data:
     # - stt
     #tunnelType: geneve
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols
+    # (Geneve, VXLAN, and STT). If zero, it will use the assigned IANA port for the
+    # protocol, i.e. 6081 for Geneve, 4789 for VXLAN, and 7471 for STT.
+    #tunnelPort: 0
+
     # Default MTU to use for the host gateway interface and the network interface of each Pod.
     # If omitted, antrea-agent will discover the MTU of the Node's primary interface and
     # also adjust MTU to accommodate for tunnel encapsulation overhead.
@@ -163,7 +168,7 @@ kind: ConfigMap
 metadata:
   labels:
     app: antrea
-  name: antrea-windows-config-8kfkb8t957
+  name: antrea-windows-config-4mtgd4ghdf
   namespace: kube-system
 ---
 apiVersion: apps/v1
@@ -251,7 +256,7 @@ spec:
         operator: Exists
       volumes:
       - configMap:
-          name: antrea-windows-config-8kfkb8t957
+          name: antrea-windows-config-4mtgd4ghdf
         name: antrea-windows-config
       - configMap:
           defaultMode: 420
diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml
index c1f3d3782..356dc6290 100644
--- a/build/yamls/antrea.yml
+++ b/build/yamls/antrea.yml
@@ -2686,6 +2686,11 @@ data:
     # Note that "gre" is not supported for IPv6 clusters (IPv6-only or dual-stack clusters).
     tunnelType: "geneve"
 
+    # TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+    # If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+    # and 7471 for STT.
+    tunnelPort: 0
+
     # Determines how tunnel traffic is encrypted. Currently encryption only works with encap mode.
     # It has the following options:
     # - none (default):  Inter-node Pod traffic will not be encrypted.
@@ -3732,7 +3737,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: 056a828ba2400e94aa9c43e6e74a4b007027bf6b95a68e1e15f34cd6ffeb2baa
+        checksum/config: 05340bff4942128434fb2b7ab2c0288d9586d3324d58da987c1a58db78aab6d3
       labels:
         app: antrea
         component: antrea-agent
@@ -3972,7 +3977,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: 056a828ba2400e94aa9c43e6e74a4b007027bf6b95a68e1e15f34cd6ffeb2baa
+        checksum/config: 05340bff4942128434fb2b7ab2c0288d9586d3324d58da987c1a58db78aab6d3
       labels:
         app: antrea
         component: antrea-controller
diff --git a/build/yamls/windows/base/conf/antrea-agent.conf b/build/yamls/windows/base/conf/antrea-agent.conf
index ab72ae1e4..e9e4d633d 100644
--- a/build/yamls/windows/base/conf/antrea-agent.conf
+++ b/build/yamls/windows/base/conf/antrea-agent.conf
@@ -30,6 +30,11 @@ featureGates:
 # - stt
 #tunnelType: geneve
 
+# TunnelPort is the destination port for UDP and TCP based tunnel protocols
+# (Geneve, VXLAN, and STT). If zero, it will use the assigned IANA port for the
+# protocol, i.e. 6081 for Geneve, 4789 for VXLAN, and 7471 for STT.
+#tunnelPort: 0
+
 # Default MTU to use for the host gateway interface and the network interface of each Pod.
 # If omitted, antrea-agent will discover the MTU of the Node's primary interface and
 # also adjust MTU to accommodate for tunnel encapsulation overhead.
diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go
index 78e715e60..2f0ea0695 100644
--- a/cmd/antrea-agent/agent.go
+++ b/cmd/antrea-agent/agent.go
@@ -162,6 +162,7 @@ func run(o *Options) error {
 	_, ipsecAuthenticationMode := config.GetIPsecAuthenticationModeFromStr(o.config.IPsec.AuthenticationMode)
 	networkConfig := &config.NetworkConfig{
 		TunnelType:            ovsconfig.TunnelType(o.config.TunnelType),
+		TunnelPort:            o.config.TunnelPort,
 		TrafficEncapMode:      encapMode,
 		TrafficEncryptionMode: encryptionMode,
 		TransportIface:        o.config.TransportInterface,
diff --git a/docs/antrea-network-policy.md b/docs/antrea-network-policy.md
index ff8186eb2..fcc18d6ea 100644
--- a/docs/antrea-network-policy.md
+++ b/docs/antrea-network-policy.md
@@ -647,11 +647,33 @@ The rules are logged in the following format:
     Deduplication:
     <yyyy/mm/dd> <time> <ovs-table-name> <antrea-native-policy-reference> <action> <openflow-priority> <source-ip> <source-port> <destination-ip> <destination-port> <protocol> <packet-length> [<num of packets> packets in <duplicate duration>]
 
-    Example:
+    Examples:
     2020/11/02 22:21:21.148395 AntreaPolicyAppTierIngressRule AntreaNetworkPolicy:default/test-anp Allow 61800 10.10.1.65 35402 10.0.0.5 80 TCP 60
     2021/06/24 23:56:41.346165 AntreaPolicyEgressRule AntreaNetworkPolicy:default/test-anp Drop 44900 10.10.1.65 35402 10.0.0.5 80 TCP 60 [3 packets in 1.011379442s]
 ```
 
+Kubernetes NetworkPolicies can also be audited using Antrea logging to the same file
+(`/var/log/antrea/networkpolicy/np.log`). Add Annotation
+`networkpolicy.antrea.io/enable-logging: "true` on a Namespace to enable logging
+for all NetworkPolicies in the Namespace. Packets of any connection that match
+a NetworkPolicy rule will be logged with a reference to the NetworkPolicy name,
+but packets dropped by the implicit "default drop" (not allowed by any NetworkPolicy)
+will only be logged with consistent name `K8sNetworkPolicy` for reference.
+Note that currently, Antrea only retrieves the logging Annotation once when adding
+NetworkPolicies and in case of agent restart, users should not update Namespace
+logging Annotations, otherwise it would risk NetworkPolicies working in a stale state.
+The rules are logged in the following format:
+
+```text
+    <yyyy/mm/dd> <time> <ovs-table-name> <k8s-network-policy-reference> Allow <openflow-priority> <source-ip> <source-port> <destination-ip> <destination-port> <protocol> <packet-length>
+    Default dropped traffic:
+    <yyyy/mm/dd> <time> <ovs-table-name> K8sNetworkPolicy Drop -1 <source-ip> <source-port> <destination-ip> <destination-port> <protocol> <packet-length> [<num of packets> packets in <duplicate duration>]
+
+    Examples:
+    2022/07/26 06:55:56.170456 IngressRule K8sNetworkPolicy:default/test-np-log Allow 190 10.10.1.82 49518 10.10.1.84 80 TCP 60
+    2022/07/26 06:55:57.142206 IngressDefaultRule K8sNetworkPolicy Drop -1 10.10.1.83 38608 10.10.1.84 80 TCP 60
+```
+
 Fluentd can be used to assist with collecting and analyzing the logs. Refer to the
 [Fluentd cookbook](cookbooks/fluentd) for documentation.
 
diff --git a/docs/multicluster/quick-start.md b/docs/multicluster/quick-start.md
index 273408626..aefa62ed5 100644
--- a/docs/multicluster/quick-start.md
+++ b/docs/multicluster/quick-start.md
@@ -11,12 +11,12 @@ simplicity, the diagram just shows two Nodes for each cluster).
 
 ## Preparation
 
-We assume an Antrea version >= `v1.7.0` is used in this guide, and the Antrea
+We assume an Antrea version >= `v1.8.0` is used in this guide, and the Antrea
 version is set to an environment variable `TAG`. For example, the following
-command sets the Antrea version to `v1.7.0`.
+command sets the Antrea version to `v1.8.0`.
 
 ```bash
-export TAG=v1.7.0
+export TAG=v1.8.0
 ```
 
 To use the latest version of Antrea Multi-cluster from the Antrea main branch,
@@ -80,11 +80,11 @@ Antrea provides several template YAML manifests to set up a ClusterSet quicker.
 You can run the following commands that use the template manifests to create a
 ClusterSet named `test-clusteraset` in the leader cluster and get a
 ServiceAccount token for the member clusters (both cluster A and B in our case)
-to access the leader cluster (cluster A in our case) apiserver.
+to access the leader cluster (cluster A in our case) API server.
 
 ```bash
-$kubectl apply -f https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/multicluster_clusterset_template.yaml
-$kubectl apply -f https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/multicluster_leader_access_token_template.yaml
+$kubectl apply -f https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/leader-clusterset-template.yml
+$kubectl apply -f https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/leader-access-token-template.yml
 $kubectl get secret leader-access-token -n antrea-multicluster -o yaml | grep -w -e '^apiVersion' -e '^data' -e '^metadata' -e '^ *name:' -e '^kind' -e '  ca.crt' -e '  token:' -e '^type' -e '  namespace' | sed -e 's/kubernetes.io\/service-account-token/Opaque/g' -e 's/antrea-multicluster/kube-system/g' > leader-access-token.yml
 ```
 
@@ -100,8 +100,8 @@ member:
 
 ```bash
 $kubectl apply -f leader-access-token.yml
-$curl -L https://raw.githubusercontent.com/antrea-io/antrea/v1.7.0/multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml > multicluster_membercluster.yaml
-$sed -e 's/test-cluster-member/test-cluster-leader/g' -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' multicluster_membercluster.yaml | kubectl apply -f -
+$curl -L https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/member-clusterset-template.yml > member-clusterset.yml
+$sed -e 's/test-cluster-member/test-cluster-leader/g' -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' member-clusterset.yml | kubectl apply -f -
 ```
 
 Here, `172.10.0.11` is the `kube-apiserver` IP of cluster A. You should replace
@@ -150,14 +150,14 @@ NAMESPACE             NAME                                   READY   UP-TO-DATE
 kube-system           deployment.apps/antrea-mc-controller   1/1     1            1           40s
 ```
 
-### Step 2 - initialize ClusterSet
+### Step 2 - join ClusterSet
 
 Run the following commands to make cluster B join the ClusterSet:
 
 ```bash
 $kubectl apply -f leader-access-token.yml
-$curl -L https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml > multicluster_membercluster.yaml
-$sed -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' multicluster_membercluster.yaml | kubectl apply -f -
+$curl -L https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/member-clusterset-template.yml > member-clusterset.yml
+$sed -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' member-clusterset.yml | kubectl apply -f -
 ```
 
 `leader-access-token.yml` saves the leader cluster ServiceAccount token which
@@ -181,21 +181,13 @@ and [ClusterNetworkPolicy Replication](user-guide.md#multi-cluster-clusternetwor
 Please check the relevant Antrea Multi-cluster User Guide sections to learn more.
 
 If you want to add a new member cluster to your ClusterSet, you can follow the
-steps for cluster B to do so. But note, you will need the following two changes:
-
-1. You need to add the new mumber cluster to the `ClusterSet` in the leader
-cluster (cluster A). You can do that by adding the cluster ID of the new member
-to `multicluster_clusterset_template.yaml` and re-applying the manifest in
-cluster A.
-
-2. You need to update the member cluster ID in
-`multicluster_membercluster_template.yaml` to the cluster ID of the new member
-cluster in the step 2 of initializing ClusterSet. For example, you can run the
-following commands to initialize the ClusterSet for a member cluster with ID
-`test-cluster-member2`:
+steps for cluster B to do so. Remember to update the member cluster ID in
+`member-clusterset-template.yml` to the new member cluster's ID in the step 2 of
+joining ClusterSet. For example, you can run the following commands to join the
+ClusterSet in a member cluster with ID `test-cluster-member2`:
 
 ```bash
 $kubectl apply -f leader-access-token.yml
-$curl -L https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml  > multicluster_membercluster.yaml
-$sed -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' -e 's/test-cluster-member/test-cluster-member2/g' multicluster_membercluster.yaml | kubectl apply -f -
+$curl -L https://raw.githubusercontent.com/antrea-io/antrea/$TAG/multicluster/config/samples/clusterset_init/member-clusterset-template.yml  > member-clusterset.yml
+$sed -e 's/<LEADER_CLUSTER_IP>/172.10.0.11/g' -e 's/test-cluster-member/test-cluster-member2/g' member-clusterset.yml | kubectl apply -f -
 ```
diff --git a/docs/multicluster/user-guide.md b/docs/multicluster/user-guide.md
index 56d64688a..bd5cb7376 100644
--- a/docs/multicluster/user-guide.md
+++ b/docs/multicluster/user-guide.md
@@ -35,12 +35,12 @@ with two clusters quickly.
 
 ### Preparation
 
-We assume an Antrea version >= `v1.7.0` is used in this guide, and the Antrea
+We assume an Antrea version >= `v1.8.0` is used in this guide, and the Antrea
 version is set to an environment variable `TAG`. For example, the following
-command sets the Antrea version to `v1.7.0`.
+command sets the Antrea version to `v1.8.0`.
 
 ```bash
-export TAG=v1.7.0
+export TAG=v1.8.0
 ```
 
 To use the latest version of Antrea Multi-cluster from the Antrea main branch,
@@ -198,44 +198,38 @@ In all clusters, a `ClusterSet` CR must be created to define the ClusterSet, and
 two `ClusterClaim` CRs must be created to claim the ClusterSet and claim the
 cluster is a member of the ClusterSet.
 
-- Create `ClusterClaim` and `ClusterSet` in member cluster `test-cluster-east`
-with the following YAML manifest (you can also refer to
-[multicluster_membercluster_template.yaml](../../multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml)):
+- Create `ClusterClaim` and `ClusterSet` in the leader cluster
+`test-cluster-north` with the following YAML manifest (you can also refer to
+[leader-clusterset-template.yml](../../multicluster/config/samples/clusterset_init/leader-clusterset-template.yml)):
 
 ```yaml
 apiVersion: multicluster.crd.antrea.io/v1alpha2
 kind: ClusterClaim
 metadata:
   name: id.k8s.io
-  namespace: kube-system
-value: test-cluster-east
+  namespace: antrea-multicluster
+value: test-cluster-north
 ---
 apiVersion: multicluster.crd.antrea.io/v1alpha2
 kind: ClusterClaim
 metadata:
   name: clusterset.k8s.io
-  namespace: kube-system
+  namespace: antrea-multicluster
 value: test-clusterset
 ---
 apiVersion: multicluster.crd.antrea.io/v1alpha1
 kind: ClusterSet
 metadata:
   name: test-clusterset
-  namespace: kube-system
+  namespace: antrea-multicluster
 spec:
   leaders:
     - clusterID: test-cluster-north
-      secret: "member-east-access-token"
-      server: "https://172.18.0.1:6443"
-  members:
-    - clusterID: test-cluster-east
-  namespace: antrea-multicluster
 ```
 
-Note: update `server: "https://172.18.0.1:6443"` in the `ClusterSet` spec to the
-correct leader cluster API server address.
-
-- Create `ClusterClaim` and `ClusterSet` in member cluster `test-cluster-west`:
+- Create `ClusterClaim` and `ClusterSet` in member cluster `test-cluster-east`
+with the following YAML manifest (you can also refer to
+[member-clusterset-template.yml](../../multicluster/config/samples/clusterset_init/member-clusterset-template.yml)):
 
 ```yaml
 apiVersion: multicluster.crd.antrea.io/v1alpha2
@@ -243,7 +237,7 @@ kind: ClusterClaim
 metadata:
   name: id.k8s.io
   namespace: kube-system
-value: test-cluster-west
+value: test-cluster-east
 ---
 apiVersion: multicluster.crd.antrea.io/v1alpha2
 kind: ClusterClaim
@@ -260,51 +254,44 @@ metadata:
 spec:
   leaders:
     - clusterID: test-cluster-north
-      secret: "member-west-access-token"
+      secret: "member-east-access-token"
       server: "https://172.18.0.1:6443"
-  members:
-    - clusterID: test-cluster-west
   namespace: antrea-multicluster
 ```
 
-- Create `ClusterClaim` and `ClusterSet` in the leader cluster
-`test-cluster-north` with the following YAML manifest (you can also refer to
-[multicluster_clusterset_template.yaml](../../multicluster/config/samples/clusterset_init/multicluster_clusterset_template.yaml)):
+Note: update `server: "https://172.18.0.1:6443"` in the `ClusterSet` spec to the
+correct leader cluster API server address.
+
+- Create `ClusterClaim` and `ClusterSet` in member cluster `test-cluster-west`:
 
 ```yaml
 apiVersion: multicluster.crd.antrea.io/v1alpha2
 kind: ClusterClaim
 metadata:
   name: id.k8s.io
-  namespace: antrea-multicluster
-value: test-cluster-north
+  namespace: kube-system
+value: test-cluster-west
 ---
 apiVersion: multicluster.crd.antrea.io/v1alpha2
 kind: ClusterClaim
 metadata:
   name: clusterset.k8s.io
-  namespace: antrea-multicluster
+  namespace: kube-system
 value: test-clusterset
 ---
 apiVersion: multicluster.crd.antrea.io/v1alpha1
 kind: ClusterSet
 metadata:
   name: test-clusterset
-  namespace: antrea-multicluster
+  namespace: kube-system
 spec:
   leaders:
     - clusterID: test-cluster-north
-  members:
-    - clusterID: test-cluster-east
-      serviceAccount: "member-east-access-sa"
-    - clusterID: test-cluster-west
-      serviceAccount: "member-west-access-sa"
+      secret: "member-west-access-token"
+      server: "https://172.18.0.1:6443"
   namespace: antrea-multicluster
 ```
 
-In the leader cluster, the `ClusterSet` spec should include all member clusters
-of the ClusterSet.
-
 #### Initialize ClusterSet for a Dual-role Cluster
 
 If you want to make the leader cluster `test-cluster-north` also a member
@@ -342,31 +329,6 @@ spec:
     - clusterID: test-cluster-north
       secret: "member-north-access-token"
       server: "https://172.18.0.1:6443"
-  members:
-    - clusterID: test-cluster-north
-  namespace: antrea-multicluster
-```
-
-Last, update the ClusterSet `test-clusterset` in Namepsace `antrea-multicluster`
-(where the leader Multi-cluster Controller runs) to include `test-cluster-north`
-as a member cluster of the ClusterSet:
-
-```yaml
-apiVersion: multicluster.crd.antrea.io/v1alpha1
-kind: ClusterSet
-metadata:
-  name: test-clusterset
-  namespace: antrea-multicluster
-spec:
-  leaders:
-    - clusterID: test-cluster-north
-  members:
-    - clusterID: test-cluster-east
-      serviceAccount: "member-east-access-sa"
-    - clusterID: test-cluster-west
-      serviceAccount: "member-west-access-sa"
-    - clusterID: test-cluster-north
-      serviceAccount: "member-north-access-sa"
   namespace: antrea-multicluster
 ```
 
diff --git a/multicluster/Makefile b/multicluster/Makefile
index df9168b77..58c49881c 100644
--- a/multicluster/Makefile
+++ b/multicluster/Makefile
@@ -71,15 +71,8 @@ test-unit: fmt vet .coverage
 	go test -race -coverprofile=.coverage/coverage-unit.txt -covermode=atomic -cover antrea.io/antrea/multicluster/controllers/multicluster/...
 test-integration: .coverage
 	@echo "==> Running Multi-cluster integration tests <=="
-ifneq ($(NO_LOCAL),)
-	@echo "===> Building Antrea Multi-cluster Integration Test Docker image <==="
-	docker build -t antrea/mc-test -f build/images/test/Dockerfile $(DOCKER_BUILD_ARGS) .
-	$(CURDIR)/hack/verify-tools.sh "/home/jenkins"
-	$(CURDIR)/hack/mc-integration-test.sh "/home/jenkins"
-else
 	$(CURDIR)/hack/verify-tools.sh
 	$(CURDIR)/hack/mc-integration-test.sh
-endif
 ##@ Build
 
 build: fmt vet ## Build manager binary.
diff --git a/multicluster/config/samples/clusterset_init/multicluster_leader_access_token_template.yaml b/multicluster/config/samples/clusterset_init/leader-access-token-template.yml
similarity index 100%
rename from multicluster/config/samples/clusterset_init/multicluster_leader_access_token_template.yaml
rename to multicluster/config/samples/clusterset_init/leader-access-token-template.yml
diff --git a/multicluster/config/samples/clusterset_init/multicluster_clusterset_template.yaml b/multicluster/config/samples/clusterset_init/leader-clusterset-template.yml
similarity index 70%
rename from multicluster/config/samples/clusterset_init/multicluster_clusterset_template.yaml
rename to multicluster/config/samples/clusterset_init/leader-clusterset-template.yml
index 305dc536b..fe221ec0d 100644
--- a/multicluster/config/samples/clusterset_init/multicluster_clusterset_template.yaml
+++ b/multicluster/config/samples/clusterset_init/leader-clusterset-template.yml
@@ -20,9 +20,3 @@ metadata:
 spec:
   leaders:
     - clusterID: test-cluster-leader
-  members:
-    - clusterID: test-cluster-member
-      serviceAccount: antrea-mc-member-access-sa
-    - clusterID: test-cluster-leader
-      serviceAccount: antrea-mc-member-access-sa
-  namespace: antrea-multicluster
diff --git a/multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml b/multicluster/config/samples/clusterset_init/member-clusterset-template.yml
similarity index 87%
rename from multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml
rename to multicluster/config/samples/clusterset_init/member-clusterset-template.yml
index e31271f47..f8bd957d0 100644
--- a/multicluster/config/samples/clusterset_init/multicluster_membercluster_template.yaml
+++ b/multicluster/config/samples/clusterset_init/member-clusterset-template.yml
@@ -22,7 +22,4 @@ spec:
     - clusterID: test-cluster-leader
       secret: leader-access-token
       server: https://<LEADER_CLUSTER_IP>:6443
-  members:
-    - clusterID: test-cluster-leader
-    - clusterID: test-cluster-member
   namespace: antrea-multicluster
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index 67a54f21f..9b1f51f95 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -726,6 +726,7 @@ func (i *Initializer) setupDefaultTunnelInterface() error {
 	if portExists {
 		if i.networkConfig.TrafficEncapMode.SupportsEncap() &&
 			tunnelIface.TunnelInterfaceConfig.Type == i.networkConfig.TunnelType &&
+			tunnelIface.TunnelInterfaceConfig.DestinationPort == i.networkConfig.TunnelPort &&
 			tunnelIface.TunnelInterfaceConfig.LocalIP.Equal(localIP) {
 			klog.V(2).Infof("Tunnel port %s already exists on OVS bridge", tunnelPortName)
 			// This could happen when upgrading from previous versions that didn't set it.
@@ -761,7 +762,12 @@ func (i *Initializer) setupDefaultTunnelInterface() error {
 		externalIDs := map[string]interface{}{
 			interfacestore.AntreaInterfaceTypeKey: interfacestore.AntreaTunnel,
 		}
-		tunnelPortUUID, err := i.ovsBridgeClient.CreateTunnelPortExt(tunnelPortName, i.networkConfig.TunnelType, config.DefaultTunOFPort, shouldEnableCsum, localIPStr, "", "", "", nil, externalIDs)
+		extraOptions := map[string]interface{}{}
+		if i.networkConfig.TunnelPort != 0 {
+			extraOptions["dst_port"] = strconv.Itoa(int(i.networkConfig.TunnelPort))
+		}
+		tunnelPortUUID, err := i.ovsBridgeClient.CreateTunnelPortExt(tunnelPortName,
+			i.networkConfig.TunnelType, config.DefaultTunOFPort, shouldEnableCsum, localIPStr, "", "", "", extraOptions, externalIDs)
 		if err != nil {
 			klog.ErrorS(err, "Failed to create tunnel port on OVS bridge", "port", tunnelPortName, "type", i.networkConfig.TunnelType)
 			return err
@@ -772,7 +778,7 @@ func (i *Initializer) setupDefaultTunnelInterface() error {
 			return err
 		}
 		klog.InfoS("Allocated OpenFlow port for tunnel interface", "port", tunnelPortName, "ofPort", tunPort)
-		tunnelIface = interfacestore.NewTunnelInterface(tunnelPortName, i.networkConfig.TunnelType, localIP, shouldEnableCsum)
+		tunnelIface = interfacestore.NewTunnelInterface(tunnelPortName, i.networkConfig.TunnelType, i.networkConfig.TunnelPort, localIP, shouldEnableCsum)
 		tunnelIface.OVSPortConfig = &interfacestore.OVSPortConfig{PortUUID: tunnelPortUUID, OFPort: tunPort}
 		i.ifaceStore.AddInterface(tunnelIface)
 		i.nodeConfig.TunnelOFPort = uint32(tunPort)
diff --git a/pkg/agent/config/node_config.go b/pkg/agent/config/node_config.go
index 650c25fc4..b230fb578 100644
--- a/pkg/agent/config/node_config.go
+++ b/pkg/agent/config/node_config.go
@@ -172,6 +172,7 @@ type IPsecConfig struct {
 type NetworkConfig struct {
 	TrafficEncapMode      TrafficEncapModeType
 	TunnelType            ovsconfig.TunnelType
+	TunnelPort            int32
 	TrafficEncryptionMode TrafficEncryptionModeType
 	IPsecConfig           IPsecConfig
 	TransportIface        string
diff --git a/pkg/agent/controller/networkpolicy/audit_logging.go b/pkg/agent/controller/networkpolicy/audit_logging.go
index 97265d12b..c29190374 100644
--- a/pkg/agent/controller/networkpolicy/audit_logging.go
+++ b/pkg/agent/controller/networkpolicy/audit_logging.go
@@ -28,6 +28,7 @@ import (
 	"k8s.io/klog/v2"
 
 	"antrea.io/antrea/pkg/agent/openflow"
+	"antrea.io/antrea/pkg/apis/controlplane/v1beta2"
 	binding "antrea.io/antrea/pkg/ovs/openflow"
 	"antrea.io/antrea/pkg/util/ip"
 	"antrea.io/antrea/pkg/util/logdir"
@@ -196,13 +197,17 @@ func getNetworkPolicyInfo(pktIn *ofctrl.PacketIn, c *Controller, ob *logInfo) er
 	// Set match to corresponding ingress/egress reg according to disposition.
 	match = getMatch(matchers, tableID, info)
 
-	// Get Network Policy full name and OF priority of the conjunction.
-	info, err = getInfoInReg(match, nil)
-	if err != nil {
-		return fmt.Errorf("received error while unloading conjunction id from reg: %v", err)
+	if match != nil {
+		// Get NetworkPolicy full name and OF priority of the conjunction.
+		info, err = getInfoInReg(match, nil)
+		if err != nil {
+			return fmt.Errorf("received error while unloading conjunction id from reg: %v", err)
+		}
+		ob.npRef, ob.ofPriority = c.ofClient.GetPolicyInfoFromConjunction(info)
+	} else {
+		// For K8s NetworkPolicy implicit drop action, we cannot get Namespace/name.
+		ob.npRef, ob.ofPriority = string(v1beta2.K8sNetworkPolicy), "-1"
 	}
-	ob.npRef, ob.ofPriority = c.ofClient.GetPolicyInfoFromConjunction(info)
-
 	return nil
 }
 
diff --git a/pkg/agent/controller/networkpolicy/packetin.go b/pkg/agent/controller/networkpolicy/packetin.go
index b14942149..7730641f1 100644
--- a/pkg/agent/controller/networkpolicy/packetin.go
+++ b/pkg/agent/controller/networkpolicy/packetin.go
@@ -180,7 +180,7 @@ func (c *Controller) storeDenyConnection(pktIn *ofctrl.PacketIn) error {
 			denyConn.EgressNetworkPolicyRuleAction = flowexporter.RuleActionToUint8(disposition)
 		}
 	} else {
-		// For K8s NetworkPolicy implicit drop action, we cannot get name/namespace.
+		// For K8s NetworkPolicy implicit drop action, we cannot get Namespace/name.
 		if tableID == openflow.IngressDefaultTable.GetID() {
 			denyConn.IngressNetworkPolicyType = registry.PolicyTypeK8sNetworkPolicy
 			denyConn.IngressNetworkPolicyRuleAction = flowexporter.RuleActionToUint8(disposition)
diff --git a/pkg/agent/controller/networkpolicy/reconciler.go b/pkg/agent/controller/networkpolicy/reconciler.go
index f08718d77..f8a0d2cf3 100644
--- a/pkg/agent/controller/networkpolicy/reconciler.go
+++ b/pkg/agent/controller/networkpolicy/reconciler.go
@@ -734,7 +734,7 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule,
 		} else {
 			addedTo := ofPortsToOFAddresses(newOFPorts.Difference(lastRealized.podOFPorts[igmpServicesKey]))
 			deletedTo := ofPortsToOFAddresses(lastRealized.podOFPorts[igmpServicesKey].Difference(newOFPorts))
-			if err := r.updateOFRule(ofID, nil, addedTo, nil, deletedTo, ofPriority); err != nil {
+			if err := r.updateOFRule(ofID, nil, addedTo, nil, deletedTo, ofPriority, newRule.EnableLogging); err != nil {
 				return err
 			}
 		}
@@ -803,7 +803,7 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule,
 					addedTo = ofPortsToOFAddresses(newOFPorts.Difference(originalOfPortsSet))
 					deletedTo = ofPortsToOFAddresses(originalOfPortsSet.Difference(newOFPorts))
 				}
-				if err := r.updateOFRule(ofID, addedFrom, addedTo, deletedFrom, deletedTo, ofPriority); err != nil {
+				if err := r.updateOFRule(ofID, addedFrom, addedTo, deletedFrom, deletedTo, ofPriority, newRule.EnableLogging); err != nil {
 					return err
 				}
 				// Delete valid servicesKey from staleOFIDs.
@@ -893,7 +893,7 @@ func (r *reconciler) update(lastRealized *lastRealized, newRule *CompletedRule,
 					addedTo = svcGroupIDsToOFAddresses(newGroupIDSet.Difference(originalGroupIDSet))
 					deletedTo = svcGroupIDsToOFAddresses(originalGroupIDSet.Difference(newGroupIDSet))
 				}
-				if err := r.updateOFRule(ofID, addedFrom, addedTo, deletedFrom, deletedTo, ofPriority); err != nil {
+				if err := r.updateOFRule(ofID, addedFrom, addedTo, deletedFrom, deletedTo, ofPriority, newRule.EnableLogging); err != nil {
 					return err
 				}
 				if r.fqdnController != nil {
@@ -930,17 +930,17 @@ 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) error {
+func (r *reconciler) updateOFRule(ofID uint32, addedFrom []types.Address, addedTo []types.Address, deletedFrom []types.Address, deletedTo []types.Address, priority *uint16, enableLogging bool) error {
 	klog.V(2).Infof("Updating ofRule %d (addedFrom: %d, addedTo: %d, deleteFrom: %d, deletedTo: %d)",
 		ofID, len(addedFrom), len(addedTo), len(deletedFrom), len(deletedTo))
 	// TODO: This might be unnecessarily complex and hard for error handling, consider revising the Openflow interfaces.
 	if len(addedFrom) > 0 {
-		if err := r.ofClient.AddPolicyRuleAddress(ofID, types.SrcAddress, addedFrom, priority); err != nil {
+		if err := r.ofClient.AddPolicyRuleAddress(ofID, types.SrcAddress, addedFrom, priority, enableLogging); err != nil {
 			return fmt.Errorf("error adding policy rule source addresses for ofRule %v: %v", ofID, err)
 		}
 	}
 	if len(addedTo) > 0 {
-		if err := r.ofClient.AddPolicyRuleAddress(ofID, types.DstAddress, addedTo, priority); err != nil {
+		if err := r.ofClient.AddPolicyRuleAddress(ofID, types.DstAddress, addedTo, priority, enableLogging); err != nil {
 			return fmt.Errorf("error adding policy rule destination addresses for ofRule %v: %v", ofID, err)
 		}
 	}
diff --git a/pkg/agent/controller/noderoute/node_route_controller.go b/pkg/agent/controller/noderoute/node_route_controller.go
index 03f98df73..bcfae3ce5 100644
--- a/pkg/agent/controller/noderoute/node_route_controller.go
+++ b/pkg/agent/controller/noderoute/node_route_controller.go
@@ -729,7 +729,7 @@ func ParseTunnelInterfaceConfig(
 		klog.V(2).Infof("OVS port %s has no options", portData.Name)
 		return nil
 	}
-	remoteIP, localIP, psk, remoteName, csum := ovsconfig.ParseTunnelInterfaceOptions(portData)
+	remoteIP, localIP, tunnelPort, psk, remoteName, csum := ovsconfig.ParseTunnelInterfaceOptions(portData)
 
 	var interfaceConfig *interfacestore.InterfaceConfig
 	var nodeName string
@@ -746,7 +746,12 @@ func ParseTunnelInterfaceConfig(
 			remoteName,
 		)
 	} else {
-		interfaceConfig = interfacestore.NewTunnelInterface(portData.Name, ovsconfig.TunnelType(portData.IFType), localIP, csum)
+		interfaceConfig = interfacestore.NewTunnelInterface(
+			portData.Name,
+			ovsconfig.TunnelType(portData.IFType),
+			tunnelPort,
+			localIP,
+			csum)
 	}
 	interfaceConfig.OVSPortConfig = portConfig
 	return interfaceConfig
diff --git a/pkg/agent/interfacestore/types.go b/pkg/agent/interfacestore/types.go
index fa838a116..c5ea76029 100644
--- a/pkg/agent/interfacestore/types.go
+++ b/pkg/agent/interfacestore/types.go
@@ -145,8 +145,8 @@ func NewGatewayInterface(gatewayName string) *InterfaceConfig {
 
 // NewTunnelInterface creates InterfaceConfig for the default tunnel port
 // interface.
-func NewTunnelInterface(tunnelName string, tunnelType ovsconfig.TunnelType, localIP net.IP, csum bool) *InterfaceConfig {
-	tunnelConfig := &TunnelInterfaceConfig{Type: tunnelType, LocalIP: localIP, Csum: csum}
+func NewTunnelInterface(tunnelName string, tunnelType ovsconfig.TunnelType, destinationPort int32, localIP net.IP, csum bool) *InterfaceConfig {
+	tunnelConfig := &TunnelInterfaceConfig{Type: tunnelType, DestinationPort: destinationPort, LocalIP: localIP, Csum: csum}
 	return &InterfaceConfig{InterfaceName: tunnelName, Type: TunnelInterface, TunnelInterfaceConfig: tunnelConfig}
 }
 
diff --git a/pkg/agent/openflow/client.go b/pkg/agent/openflow/client.go
index cd0cea0b9..f3a791744 100644
--- a/pkg/agent/openflow/client.go
+++ b/pkg/agent/openflow/client.go
@@ -125,7 +125,7 @@ type Client interface {
 
 	// AddPolicyRuleAddress adds one or multiple addresses to the specified NetworkPolicy rule. If addrType is true, the
 	// addresses are added to PolicyRule.From, else to PolicyRule.To.
-	AddPolicyRuleAddress(ruleID uint32, addrType types.AddressType, addresses []types.Address, priority *uint16) error
+	AddPolicyRuleAddress(ruleID uint32, addrType types.AddressType, addresses []types.Address, priority *uint16, enableLogging bool) error
 
 	// DeletePolicyRuleAddress removes addresses from the specified NetworkPolicy rule. If addrType is srcAddress, the addresses
 	// are removed from PolicyRule.From, else from PolicyRule.To.
diff --git a/pkg/agent/openflow/network_policy.go b/pkg/agent/openflow/network_policy.go
index 20624b95e..489a956dc 100644
--- a/pkg/agent/openflow/network_policy.go
+++ b/pkg/agent/openflow/network_policy.go
@@ -422,6 +422,8 @@ type conjMatchFlowContext struct {
 	// empty, and uninstalled when both two are empty. When the dropFlow is uninstalled from the switch, the
 	// conjMatchFlowContext is removed from the cache.
 	dropFlow binding.Flow
+	// dropFlowEnableLogging describes the logging requirement of the dropFlow.
+	dropFlowEnableLogging bool
 }
 
 // createOrUpdateConjunctiveMatchFlow creates or updates the conjunctive match flow with the latest actions. It returns
@@ -684,7 +686,7 @@ func (c *client) NewDNSpacketInConjunction(id uint32) error {
 
 	c.featureNetworkPolicy.conjMatchFlowLock.Lock()
 	defer c.featureNetworkPolicy.conjMatchFlowLock.Unlock()
-	ctxChanges := conj.serviceClause.addServiceFlows(c.featureNetworkPolicy, []v1beta2.Service{udpService}, &dnsPriority, true)
+	ctxChanges := conj.serviceClause.addServiceFlows(c.featureNetworkPolicy, []v1beta2.Service{udpService}, &dnsPriority, true, false)
 	if err := c.featureNetworkPolicy.applyConjunctiveMatchFlows(ctxChanges); err != nil {
 		return err
 	}
@@ -695,7 +697,7 @@ func (c *client) NewDNSpacketInConjunction(id uint32) error {
 
 func (c *client) AddAddressToDNSConjunction(id uint32, addrs []types.Address) error {
 	dnsPriority := priorityDNSIntercept
-	return c.AddPolicyRuleAddress(id, types.DstAddress, addrs, &dnsPriority)
+	return c.AddPolicyRuleAddress(id, types.DstAddress, addrs, &dnsPriority, false)
 }
 
 func (c *client) DeleteAddressFromDNSConjunction(id uint32, addrs []types.Address) error {
@@ -703,7 +705,7 @@ func (c *client) DeleteAddressFromDNSConjunction(id uint32, addrs []types.Addres
 	return c.DeletePolicyRuleAddress(id, types.DstAddress, addrs, &dnsPriority)
 }
 
-func (c *clause) addConjunctiveMatchFlow(featureNetworkPolicy *featureNetworkPolicy, match *conjunctiveMatch) *conjMatchFlowContextChange {
+func (c *clause) addConjunctiveMatchFlow(featureNetworkPolicy *featureNetworkPolicy, match *conjunctiveMatch, enableLogging bool) *conjMatchFlowContextChange {
 	matcherKey := match.generateGlobalMapKey()
 	_, found := c.matches[matcherKey]
 	if found {
@@ -718,19 +720,29 @@ func (c *clause) addConjunctiveMatchFlow(featureNetworkPolicy *featureNetworkPol
 	context, found = featureNetworkPolicy.globalConjMatchFlowCache[matcherKey]
 	if !found {
 		context = &conjMatchFlowContext{
-			conjunctiveMatch:     match,
-			actions:              make(map[uint32]*conjunctiveAction),
-			featureNetworkPolicy: featureNetworkPolicy,
+			conjunctiveMatch:      match,
+			actions:               make(map[uint32]*conjunctiveAction),
+			featureNetworkPolicy:  featureNetworkPolicy,
+			dropFlowEnableLogging: enableLogging,
 		}
 		ctxType = insertion
 
 		// Generate the default drop flow if dropTable is not nil and the default drop flow is not set yet.
 		if c.dropTable != nil && context.dropFlow == nil {
 			dropFlow = &flowChange{
-				flow:       context.featureNetworkPolicy.defaultDropFlow(c.dropTable, match.matchPairs),
+				flow:       context.featureNetworkPolicy.defaultDropFlow(c.dropTable, match.matchPairs, enableLogging),
 				changeType: insertion,
 			}
 		}
+	} else if context.dropFlowEnableLogging != enableLogging {
+		// Logging requirement of the rule has changed, modify default drop flow accordingly.
+		context.dropFlowEnableLogging = enableLogging
+		if c.dropTable != nil && context.dropFlow != nil {
+			dropFlow = &flowChange{
+				flow:       context.featureNetworkPolicy.defaultDropFlow(c.dropTable, match.matchPairs, enableLogging),
+				changeType: modification,
+			}
+		}
 	}
 
 	// Calculate the change on the conjMatchFlowContext.
@@ -923,12 +935,12 @@ func serviceToBitRanges(service v1beta2.Service) []types.BitRange {
 
 // addAddrFlows translates the specified addresses to conjunctiveMatchFlows, and returns the corresponding changes on the
 // conjunctiveMatchFlows.
-func (c *clause) addAddrFlows(featureNetworkPolicy *featureNetworkPolicy, addrType types.AddressType, addresses []types.Address, priority *uint16) []*conjMatchFlowContextChange {
+func (c *clause) addAddrFlows(featureNetworkPolicy *featureNetworkPolicy, addrType types.AddressType, addresses []types.Address, priority *uint16, enableLogging bool) []*conjMatchFlowContextChange {
 	var conjMatchFlowContextChanges []*conjMatchFlowContextChange
 	// Calculate Openflow changes for the added addresses.
 	for _, addr := range addresses {
 		match := generateAddressConjMatch(c.ruleTable.GetID(), addr, addrType, priority)
-		ctxChange := c.addConjunctiveMatchFlow(featureNetworkPolicy, match)
+		ctxChange := c.addConjunctiveMatchFlow(featureNetworkPolicy, match, enableLogging)
 		if ctxChange != nil {
 			conjMatchFlowContextChanges = append(conjMatchFlowContextChanges, ctxChange)
 		}
@@ -938,12 +950,12 @@ func (c *clause) addAddrFlows(featureNetworkPolicy *featureNetworkPolicy, addrTy
 
 // addServiceFlows translates the specified Antrea Service to conjunctiveMatchFlow,
 // and returns corresponding conjMatchFlowContextChange.
-func (c *clause) addServiceFlows(featureNetworkPolicy *featureNetworkPolicy, services []v1beta2.Service, priority *uint16, matchSrc bool) []*conjMatchFlowContextChange {
+func (c *clause) addServiceFlows(featureNetworkPolicy *featureNetworkPolicy, services []v1beta2.Service, priority *uint16, matchSrc bool, enableLogging bool) []*conjMatchFlowContextChange {
 	var conjMatchFlowContextChanges []*conjMatchFlowContextChange
 	for _, service := range services {
 		matches := generateServiceConjMatches(c.ruleTable.GetID(), service, priority, featureNetworkPolicy.ipProtocols, matchSrc)
 		for _, match := range matches {
-			ctxChange := c.addConjunctiveMatchFlow(featureNetworkPolicy, match)
+			ctxChange := c.addConjunctiveMatchFlow(featureNetworkPolicy, match, enableLogging)
 			conjMatchFlowContextChanges = append(conjMatchFlowContextChanges, ctxChange)
 		}
 	}
@@ -1143,20 +1155,20 @@ func (f *featureNetworkPolicy) addRuleToConjunctiveMatch(conj *policyRuleConjunc
 	if conj.fromClause != nil {
 		for _, addr := range rule.From {
 			match := generateAddressConjMatch(conj.fromClause.ruleTable.GetID(), addr, types.SrcAddress, rule.Priority)
-			f.addActionToConjunctiveMatch(conj.fromClause, match)
+			f.addActionToConjunctiveMatch(conj.fromClause, match, rule.EnableLogging)
 		}
 	}
 	if conj.toClause != nil {
 		for _, addr := range rule.To {
 			match := generateAddressConjMatch(conj.toClause.ruleTable.GetID(), addr, types.DstAddress, rule.Priority)
-			f.addActionToConjunctiveMatch(conj.toClause, match)
+			f.addActionToConjunctiveMatch(conj.toClause, match, rule.EnableLogging)
 		}
 	}
 	if conj.serviceClause != nil {
 		for _, eachService := range rule.Service {
 			matches := generateServiceConjMatches(conj.serviceClause.ruleTable.GetID(), eachService, rule.Priority, f.ipProtocols, false)
 			for _, match := range matches {
-				f.addActionToConjunctiveMatch(conj.serviceClause, match)
+				f.addActionToConjunctiveMatch(conj.serviceClause, match, rule.EnableLogging)
 			}
 		}
 	}
@@ -1165,7 +1177,7 @@ func (f *featureNetworkPolicy) addRuleToConjunctiveMatch(conj *policyRuleConjunc
 // addActionToConjunctiveMatch adds a clause to corresponding conjunctive match context.
 // It updates the context status directly and doesn't calculate the match flow, which is supposed to be calculated after
 // all actions are added. It's used in initial batch install only.
-func (f *featureNetworkPolicy) addActionToConjunctiveMatch(clause *clause, match *conjunctiveMatch) {
+func (f *featureNetworkPolicy) addActionToConjunctiveMatch(clause *clause, match *conjunctiveMatch, enableLogging bool) {
 	matcherKey := match.generateGlobalMapKey()
 	_, found := clause.matches[matcherKey]
 	if found {
@@ -1178,13 +1190,14 @@ func (f *featureNetworkPolicy) addActionToConjunctiveMatch(clause *clause, match
 	context, found = f.globalConjMatchFlowCache[matcherKey]
 	if !found {
 		context = &conjMatchFlowContext{
-			conjunctiveMatch:     match,
-			actions:              make(map[uint32]*conjunctiveAction),
-			featureNetworkPolicy: f,
+			conjunctiveMatch:      match,
+			actions:               make(map[uint32]*conjunctiveAction),
+			featureNetworkPolicy:  f,
+			dropFlowEnableLogging: enableLogging,
 		}
 		// Generate the default drop flow if dropTable is not nil.
 		if clause.dropTable != nil {
-			context.dropFlow = context.featureNetworkPolicy.defaultDropFlow(clause.dropTable, match.matchPairs)
+			context.dropFlow = context.featureNetworkPolicy.defaultDropFlow(clause.dropTable, match.matchPairs, enableLogging)
 		}
 		f.globalConjMatchFlowCache[matcherKey] = context
 	}
@@ -1371,13 +1384,13 @@ func (c *policyRuleConjunction) calculateClauses(rule *types.PolicyRule) (uint8,
 func (c *policyRuleConjunction) calculateChangesForRuleCreation(featureNetworkPolicy *featureNetworkPolicy, rule *types.PolicyRule) []*conjMatchFlowContextChange {
 	var ctxChanges []*conjMatchFlowContextChange
 	if c.fromClause != nil {
-		ctxChanges = append(ctxChanges, c.fromClause.addAddrFlows(featureNetworkPolicy, types.SrcAddress, rule.From, rule.Priority)...)
+		ctxChanges = append(ctxChanges, c.fromClause.addAddrFlows(featureNetworkPolicy, types.SrcAddress, rule.From, rule.Priority, rule.EnableLogging)...)
 	}
 	if c.toClause != nil {
-		ctxChanges = append(ctxChanges, c.toClause.addAddrFlows(featureNetworkPolicy, types.DstAddress, rule.To, rule.Priority)...)
+		ctxChanges = append(ctxChanges, c.toClause.addAddrFlows(featureNetworkPolicy, types.DstAddress, rule.To, rule.Priority, rule.EnableLogging)...)
 	}
 	if c.serviceClause != nil {
-		ctxChanges = append(ctxChanges, c.serviceClause.addServiceFlows(featureNetworkPolicy, rule.Service, rule.Priority, false)...)
+		ctxChanges = append(ctxChanges, c.serviceClause.addServiceFlows(featureNetworkPolicy, rule.Service, rule.Priority, false, rule.EnableLogging)...)
 	}
 	return ctxChanges
 }
@@ -1550,7 +1563,7 @@ func (f *featureNetworkPolicy) replayFlows() []binding.Flow {
 
 // AddPolicyRuleAddress adds one or multiple addresses to the specified NetworkPolicy rule. If addrType is srcAddress, the
 // addresses are added to PolicyRule.From, else to PolicyRule.To.
-func (c *client) AddPolicyRuleAddress(ruleID uint32, addrType types.AddressType, addresses []types.Address, priority *uint16) error {
+func (c *client) AddPolicyRuleAddress(ruleID uint32, addrType types.AddressType, addresses []types.Address, priority *uint16, enableLogging bool) error {
 	c.replayMutex.RLock()
 	defer c.replayMutex.RUnlock()
 
@@ -1569,7 +1582,7 @@ func (c *client) AddPolicyRuleAddress(ruleID uint32, addrType types.AddressType,
 
 	c.featureNetworkPolicy.conjMatchFlowLock.Lock()
 	defer c.featureNetworkPolicy.conjMatchFlowLock.Unlock()
-	flowChanges := clause.addAddrFlows(c.featureNetworkPolicy, addrType, addresses, priority)
+	flowChanges := clause.addAddrFlows(c.featureNetworkPolicy, addrType, addresses, priority, enableLogging)
 	return c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChanges)
 }
 
diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go
index 0abfae8c1..0bf44bb7e 100644
--- a/pkg/agent/openflow/pipeline.go
+++ b/pkg/agent/openflow/pipeline.go
@@ -2118,22 +2118,28 @@ func (f *featureNetworkPolicy) conjunctiveMatchFlow(tableID uint8, matchPairs []
 }
 
 // defaultDropFlow generates the flow to drop packets if the match condition is matched.
-func (f *featureNetworkPolicy) defaultDropFlow(table binding.Table, matchPairs []matchPair) binding.Flow {
+func (f *featureNetworkPolicy) defaultDropFlow(table binding.Table, matchPairs []matchPair, enableLogging bool) binding.Flow {
 	cookieID := f.cookieAllocator.Request(f.category).Raw()
 	fb := table.BuildFlow(priorityNormal)
 	for _, eachMatchPair := range matchPairs {
 		fb = f.addFlowMatch(fb, eachMatchPair.matchKey, eachMatchPair.matchValue)
 	}
+	fb = fb.Action().Drop()
+
+	var customReason int
 	if f.enableDenyTracking {
-		return fb.Action().Drop().
-			Action().LoadRegMark(DispositionDropRegMark, CustomReasonDenyRegMark).
-			Action().SendToController(uint8(PacketInReasonNP)).
-			Cookie(cookieID).
-			Done()
+		customReason += CustomReasonDeny
 	}
-	return fb.Action().Drop().
-		Cookie(cookieID).
-		Done()
+	if enableLogging {
+		customReason += CustomReasonLogging
+	}
+
+	if enableLogging || f.enableDenyTracking {
+		fb = fb.Action().LoadRegMark(DispositionDropRegMark).
+			Action().LoadToRegField(CustomReasonField, uint32(customReason)).
+			Action().SendToController(uint8(PacketInReasonNP))
+	}
+	return fb.Cookie(cookieID).Done()
 }
 
 // dnsPacketInFlow generates the flow to send dns response packets of fqdn policy selected Pods to the fqdnController for
diff --git a/pkg/config/agent/config.go b/pkg/config/agent/config.go
index 2d0d9d43a..caf4bbe86 100644
--- a/pkg/config/agent/config.go
+++ b/pkg/config/agent/config.go
@@ -71,6 +71,10 @@ type AgentConfig struct {
 	// - gre
 	// - stt
 	TunnelType string `yaml:"tunnelType,omitempty"`
+	// TunnelPort is the destination port for UDP and TCP based tunnel protocols (Geneve, VXLAN, and STT).
+	// If zero, it will use the assigned IANA port for the protocol, i.e. 6081 for Geneve, 4789 for VXLAN,
+	// and 7471 for STT.
+	TunnelPort int32 `yaml:"tunnelPort,omitempty"`
 	// Default MTU to use for the host gateway interface and the network interface of each Pod.
 	// If omitted, antrea-agent will discover the MTU of the Node's primary interface and
 	// also adjust MTU to accommodate for tunnel encapsulation overhead (if applicable).
diff --git a/pkg/controller/networkpolicy/clusternetworkpolicy.go b/pkg/controller/networkpolicy/clusternetworkpolicy.go
index cd6ce44a9..886c5abdf 100644
--- a/pkg/controller/networkpolicy/clusternetworkpolicy.go
+++ b/pkg/controller/networkpolicy/clusternetworkpolicy.go
@@ -214,10 +214,12 @@ func (n *NetworkPolicyController) addNamespace(obj interface{}) {
 
 // updateNamespace receives Namespace UPDATE events and triggers all ClusterNetworkPolicies that have a
 // per-namespace rule applied to either the original or the new Namespace to be re-processed.
+// It also triggers all K8s NetworkPolicies in the new Namespace to be re-processed
+// if the logging Annotation changes.
 func (n *NetworkPolicyController) updateNamespace(oldObj, curObj interface{}) {
 	defer n.heartbeat("updateNamespace")
 	oldNamespace, curNamespace := oldObj.(*v1.Namespace), curObj.(*v1.Namespace)
-	klog.V(2).Infof("Processing Namespace %s UPDATE event, labels: %v", curNamespace.Name, curNamespace.Labels)
+	klog.V(2).Infof("Processing Namespace %s UPDATE event, labels: %v, annotations: %v", curNamespace.Name, curNamespace.Labels, curNamespace.Annotations)
 	oldLabelSet, curLabelSet := labels.Set(oldNamespace.Labels), labels.Set(curNamespace.Labels)
 	affectedACNPsByOldLabels := n.filterPerNamespaceRuleACNPsByNSLabels(oldLabelSet)
 	affectedACNPsByCurLabels := n.filterPerNamespaceRuleACNPsByNSLabels(curLabelSet)
@@ -227,6 +229,12 @@ func (n *NetworkPolicyController) updateNamespace(oldObj, curObj interface{}) {
 			n.reprocessCNP(cnp, false)
 		}
 	}
+	if oldNamespace.Annotations[EnableNPLoggingAnnotationKey] != curNamespace.Annotations[EnableNPLoggingAnnotationKey] {
+		affectedNPs, _ := n.networkPolicyLister.NetworkPolicies(curNamespace.Name).List(labels.Everything())
+		for _, np := range affectedNPs {
+			n.updateNetworkPolicy(np, np)
+		}
+	}
 }
 
 // deleteNamespace receives Namespace DELETE events and triggers all ClusterNetworkPolicies that have a
diff --git a/pkg/controller/networkpolicy/networkpolicy_controller.go b/pkg/controller/networkpolicy/networkpolicy_controller.go
index 02d7856f8..ce5e819ea 100644
--- a/pkg/controller/networkpolicy/networkpolicy_controller.go
+++ b/pkg/controller/networkpolicy/networkpolicy_controller.go
@@ -81,6 +81,8 @@ const (
 	PriorityIndex = "priority"
 	// ClusterGroupIndex is used to index ClusterNetworkPolicies by ClusterGroup names.
 	ClusterGroupIndex = "clustergroup"
+	// EnableNPLoggingAnnotationKey can be added to Namespace to enable logging K8s NP.
+	EnableNPLoggingAnnotationKey = "networkpolicy.antrea.io/enable-logging"
 
 	appliedToGroupType grouping.GroupType = "appliedToGroup"
 	addressGroupType   grouping.GroupType = "addressGroup"
@@ -566,6 +568,12 @@ func (n *NetworkPolicyController) processNetworkPolicy(np *networkingv1.NetworkP
 	appliedToGroupKey := n.createAppliedToGroup(np.Namespace, &np.Spec.PodSelector, nil, nil)
 	appliedToGroupNames := []string{appliedToGroupKey}
 	rules := make([]controlplane.NetworkPolicyRule, 0, len(np.Spec.Ingress)+len(np.Spec.Egress))
+	// Retrieve Namespace logging annotation.
+	enableLogging := false
+	namespace, err := n.namespaceLister.Get(np.Namespace)
+	if err == nil {
+		enableLogging, _ = strconv.ParseBool(namespace.Annotations[EnableNPLoggingAnnotationKey])
+	}
 	var ingressRuleExists, egressRuleExists bool
 	// Compute NetworkPolicyRule for Ingress Rule.
 	for _, ingressRule := range np.Spec.Ingress {
@@ -577,7 +585,7 @@ func (n *NetworkPolicyController) processNetworkPolicy(np *networkingv1.NetworkP
 			Services:      services,
 			Priority:      defaultRulePriority,
 			Action:        &defaultAction,
-			EnableLogging: false,
+			EnableLogging: enableLogging,
 		})
 	}
 	// Compute NetworkPolicyRule for Egress Rule.
@@ -590,7 +598,7 @@ func (n *NetworkPolicyController) processNetworkPolicy(np *networkingv1.NetworkP
 			Services:      services,
 			Priority:      defaultRulePriority,
 			Action:        &defaultAction,
-			EnableLogging: false,
+			EnableLogging: enableLogging,
 		})
 	}
 
diff --git a/pkg/ovs/ovsconfig/ovs_client.go b/pkg/ovs/ovsconfig/ovs_client.go
index 69dbf108e..b214052ba 100644
--- a/pkg/ovs/ovsconfig/ovs_client.go
+++ b/pkg/ovs/ovsconfig/ovs_client.go
@@ -500,15 +500,16 @@ func (br *OVSBridge) SetInterfaceOptions(name string, options map[string]interfa
 
 // ParseTunnelInterfaceOptions reads remote IP, local IP, IPsec PSK, and csum
 // from the tunnel interface options and returns them.
-func ParseTunnelInterfaceOptions(portData *OVSPortData) (net.IP, net.IP, string, string, bool) {
+func ParseTunnelInterfaceOptions(portData *OVSPortData) (net.IP, net.IP, int32, string, string, bool) {
 	if portData.Options == nil {
-		return nil, nil, "", "", false
+		return nil, nil, 0, "", "", false
 	}
 
 	var ok bool
 	var remoteIPStr, localIPStr, psk, remoteName string
 	var remoteIP, localIP net.IP
 	var csum bool
+	var destinationPort int64
 
 	if remoteIPStr, ok = portData.Options["remote_ip"]; ok {
 		if remoteIPStr != "flow" {
@@ -523,7 +524,10 @@ func ParseTunnelInterfaceOptions(portData *OVSPortData) (net.IP, net.IP, string,
 		csum, _ = strconv.ParseBool(csumStr)
 	}
 	remoteName = portData.Options["remote_name"]
-	return remoteIP, localIP, psk, remoteName, csum
+	if destinationPortStr, ok := portData.Options["dst_port"]; ok {
+		destinationPort, _ = strconv.ParseInt(destinationPortStr, 10, 32)
+	}
+	return remoteIP, localIP, int32(destinationPort), psk, remoteName, csum
 }
 
 // CreateUplinkPort creates uplink port.


Test patch:
diff --git a/ci/jenkins/test-vmc.sh b/ci/jenkins/test-vmc.sh
index b7601fc87..4515db2bb 100755
--- a/ci/jenkins/test-vmc.sh
+++ b/ci/jenkins/test-vmc.sh
@@ -40,16 +40,16 @@ DOCKER_REGISTRY=""
 CONTROL_PLANE_NODE_ROLE="master|control-plane"
 
 _usage="Usage: $0 [--cluster-name <VMCClusterNameToUse>] [--kubeconfig <KubeconfigSavePath>] [--workdir <HomePath>]
-                  [--log-mode <SonobuoyResultLogLevel>] [--testcase <e2e|integration|conformance|all-features-conformance|whole-conformance|networkpolicy>]
+                  [--log-mode <SonobuoyResultLogLevel>] [--testcase <e2e|conformance|all-features-conformance|whole-conformance|networkpolicy>]
                   [--garbage-collection] [--setup-only] [--cleanup-only] [--coverage] [--test-only] [--codecov-token] [--registry]
 
-Setup a VMC cluster to run K8s e2e community tests (E2e, Integration, Conformance, all features Conformance, whole Conformance & Network Policy).
+Setup a VMC cluster to run K8s e2e community tests (E2e, Conformance, all features Conformance, whole Conformance & Network Policy).
 
         --cluster-name           The cluster name to be used for the generated VMC cluster.
         --kubeconfig             Path to save kubeconfig of generated VMC cluster.
         --workdir                Home path for Go, vSphere information and antrea_logs during cluster setup. Default is $WORKDIR.
         --log-mode               Use the flag to set either 'report', 'detail', or 'dump' level data for sonobouy results.
-        --testcase               The testcase to run: e2e, integration, conformance, all-features-conformance, whole-conformance or networkpolicy.
+        --testcase               The testcase to run: e2e, conformance, all-features-conformance, whole-conformance or networkpolicy.
         --garbage-collection     Do garbage collection to clean up some unused testbeds.
         --setup-only             Only perform setting up the cluster and run test.
         --cleanup-only           Only perform cleaning up the cluster.
@@ -466,26 +466,6 @@ function deliver_antrea {
     done
 }
 
-function run_integration {
-    flag=$1
-    VM_NAME="antrea-integration-0"
-    export GOVC_INSECURE=1
-    export GOVC_URL=${GOVC_URL}
-    export GOVC_USERNAME=${GOVC_USERNAME}
-    export GOVC_PASSWORD=${GOVC_PASSWORD}
-    VM_IP=$(govc vm.ip ${VM_NAME})
-    govc snapshot.revert -vm.ip ${VM_IP} initial
-    VM_IP=$(govc vm.ip ${VM_NAME}) # wait for VM to be on
-
-    set -x
-    echo "===== Run Integration tests ====="
-    # umask ensures that files are cloned with the correct permissions so that Docker caching can be leveraged
-    ${SSH_WITH_UTILS_KEY} -n jenkins@${VM_IP} "umask 0022 && git clone ${ghprbAuthorRepoGitUrl} antrea && cd antrea && git checkout ${GIT_BRANCH} && DOCKER_REGISTRY=${DOCKER_REGISTRY} ./build/images/ovs/build.sh --pull && NO_PULL=${NO_PULL} make docker-test-integration;cd multicluster && NO_LOCAL=true make test-integration"
-    if [[ "$COVERAGE" == true ]]; then
-      run_codecov "integration-tests" "coverage-integration.txt" "" true ${VM_IP}
-    fi
-}
-
 function run_e2e {
     echo "====== Running Antrea E2E Tests ======"
 
@@ -707,8 +687,8 @@ if [[ "$RUN_CLEANUP_ONLY" == true ]]; then
     exit 0
 fi
 
-if [[ "$TESTCASE" != "e2e" && "$TESTCASE" != "conformance" && "$TESTCASE" != "all-features-conformance" && "$TESTCASE" != "whole-conformance" && "$TESTCASE" != "networkpolicy" && "$TESTCASE" != "integration" ]]; then
-    echoerr "testcase should be e2e, integration, conformance, all-features-conformance, whole-conformance or networkpolicy"
+if [[ "$TESTCASE" != "e2e" && "$TESTCASE" != "conformance" && "$TESTCASE" != "all-features-conformance" && "$TESTCASE" != "whole-conformance" && "$TESTCASE" != "networkpolicy" ]]; then
+    echoerr "testcase should be e2e, conformance, all-features-conformance, whole-conformance or networkpolicy"
     exit 1
 fi
 
@@ -724,11 +704,6 @@ if [[ "$RUN_TEST_ONLY" == true ]]; then
     exit 0
 fi
 
-if [[ "$TESTCASE" == "integration" ]]; then
-    run_integration
-    exit 0
-fi
-
 trap cleanup_cluster EXIT
 if [[ "$TESTCASE" == "e2e" ]]; then
     setup_cluster
diff --git a/ci/test-conformance-aks.sh b/ci/test-conformance-aks.sh
index 8312334ab..ab001a6b6 100755
--- a/ci/test-conformance-aks.sh
+++ b/ci/test-conformance-aks.sh
@@ -247,10 +247,14 @@ function run_conformance() {
     echo "=== Running Antrea Conformance and Network Policy Tests ==="
     # Skip NodePort related cases for AKS since as Nodes in AKS cluster seem not accessible from other Nodes
     # through public IPs by default. See https://github.com/antrea-io/antrea/issues/2409
-    skip_regex="\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|\[sig-cli\]|\[sig-storage\]|\[sig-auth\]|\[sig-api-machinery\]|\[sig-apps\]|\[sig-node\]|NodePort"
-    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance --e2e-network-policy --e2e-skip ${skip_regex} \
+    skip_regex="\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|\[sig-cli\]|\[sig-storage\]|\[sig-auth\]|\[sig-api-machinery\]|\[sig-apps\]|\[sig-node\]|\[sig-instrumentation\]|NodePort"
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance --e2e-skip ${skip_regex} \
       --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
-      --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/aks-test.log || TEST_SCRIPT_RC=$?
+      --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/aks-test.log && \
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-network-policy --e2e-skip "Netpol" \
+      --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
+      --log-mode ${MODE} >> ${GIT_CHECKOUT_DIR}/aks-test.log || \
+    TEST_SCRIPT_RC=$?
 
     if [[ $TEST_SCRIPT_RC -eq 0 ]]; then
         echo "All tests passed."
diff --git a/ci/test-conformance-eks.sh b/ci/test-conformance-eks.sh
index 5d59fd998..a9e34385f 100755
--- a/ci/test-conformance-eks.sh
+++ b/ci/test-conformance-eks.sh
@@ -262,10 +262,14 @@ function run_conformance() {
 
     # Skip NodePort related cases for EKS since by default eksctl does not create security groups for nodeport service
     # access through node external IP. See https://github.com/antrea-io/antrea/issues/690
-    skip_regex="\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|\[sig-cli\]|\[sig-storage\]|\[sig-auth\]|\[sig-api-machinery\]|\[sig-apps\]|\[sig-node\]|NodePort"
-    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance --e2e-network-policy --e2e-skip ${skip_regex} \
-       --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
-       --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/eks-test.log || TEST_SCRIPT_RC=$?
+    skip_regex="\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|\[sig-cli\]|\[sig-storage\]|\[sig-auth\]|\[sig-api-machinery\]|\[sig-apps\]|\[sig-node\]|\[sig-instrumentation\]|NodePort"
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance --e2e-skip ${skip_regex} \
+      --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
+      --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/eks-test.log && \
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-network-policy --e2e-skip "Netpol" \
+      --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
+      --log-mode ${MODE} >> ${GIT_CHECKOUT_DIR}/eks-test.log || \
+    TEST_SCRIPT_RC=$?
 
     if [[ $TEST_SCRIPT_RC -eq 0 ]]; then
         echo "All tests passed."
diff --git a/ci/test-conformance-gke.sh b/ci/test-conformance-gke.sh
index faa47bae8..4a968e430 100755
--- a/ci/test-conformance-gke.sh
+++ b/ci/test-conformance-gke.sh
@@ -31,8 +31,7 @@ RUN_ALL=true
 RUN_SETUP_ONLY=false
 RUN_CLEANUP_ONLY=false
 TEST_SCRIPT_RC=0
-# There is a problem with the netpol suite added in v1.21.0 when running on GKE. See #3762 for details.
-KUBE_CONFORMANCE_IMAGE_VERSION=v1.20.15
+KUBE_CONFORMANCE_IMAGE_VERSION=auto
 
 _usage="Usage: $0 [--cluster-name <GKEClusterNameToUse>]  [--kubeconfig <KubeconfigSavePath>] [--k8s-version <ClusterVersion>] \
                   [--svc-account <Name>] [--user <Name>] [--gke-project <Project>] [--gke-zone <Zone>] [--log-mode <SonobuoyResultLogLevel>] \
@@ -262,9 +261,15 @@ function run_conformance() {
     # Allow nodeport traffic by external IP
     ${GCLOUD_PATH} compute firewall-rules create allow-nodeport --allow tcp:30000-32767
 
-    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance --e2e-network-policy \
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-conformance \
       --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
-      --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/gke-test.log || TEST_SCRIPT_RC=$?
+      --log-mode ${MODE} > ${GIT_CHECKOUT_DIR}/gke-test.log && \
+   # Skip Netpol tests for GKE as the test suite's Namespace creation function is not robust, which leads to test
+   # failures. See https://github.com/antrea-io/antrea/issues/3762#issuecomment-1195865441.
+    ${GIT_CHECKOUT_DIR}/ci/run-k8s-e2e-tests.sh --e2e-network-policy --e2e-skip "Netpol" \
+      --kube-conformance-image-version ${KUBE_CONFORMANCE_IMAGE_VERSION} \
+      --log-mode ${MODE} >> ${GIT_CHECKOUT_DIR}/gke-test.log || \
+    TEST_SCRIPT_RC=$?
 
     if [[ $TEST_SCRIPT_RC -eq 0 ]]; then
         echo "All tests passed."
diff --git a/multicluster/build/images/test/Dockerfile b/multicluster/build/images/test/Dockerfile
deleted file mode 100644
index 7683e1f5e..000000000
--- a/multicluster/build/images/test/Dockerfile
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2022 Antrea Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-FROM ubuntu:20.04
-
-LABEL maintainer="Antrea <projectantrea-dev@googlegroups.com>"
-LABEL description="A Docker image for Antrea Multi-cluster integration tests."
-
-RUN apt-get update && \
-    apt-get install -y --no-install-recommends iproute2 iptables ipset make wget gcc libc6-dev ca-certificates git && \
-    rm -rf /var/cache/apt/* /var/lib/apt/lists/*
-
-ARG GO_VERSION
-ENV GOPATH /go
-
-RUN wget -q -O - https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz | tar xz -C /usr/local/ && \
-    export PATH="/usr/local/go/bin:$PATH" && \
-    mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
-
-ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
-
-WORKDIR $GOPATH
-
-COPY build/images/test/test-integration /usr/local/bin/
diff --git a/multicluster/build/images/test/test-integration b/multicluster/build/images/test/test-integration
deleted file mode 100755
index 5c2aa2ff1..000000000
--- a/multicluster/build/images/test/test-integration
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-go test -coverpkg=antrea.io/antrea/multicluster/controllers/multicluster/... -coverprofile=multicluster/.coverage/coverage-integration.txt -covermode=atomic -cover antrea.io/antrea/multicluster/test/integration/...
diff --git a/multicluster/hack/mc-integration-test.sh b/multicluster/hack/mc-integration-test.sh
index 1ea2eaef7..89b0adde8 100755
--- a/multicluster/hack/mc-integration-test.sh
+++ b/multicluster/hack/mc-integration-test.sh
@@ -36,16 +36,4 @@ kubectl create namespace leader-ns
 # Here we simply create a cluster-admin user for member to access leader
 kubectl apply -f test/integration/cluster-admin.yml
 
-if [[ $NO_LOCAL == "true" ]];then
-  # Run go test in a Docker container
-  container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' antrea-integration-control-plane)
-  sed -i "s|server: https://.*|server: https://${container_ip}:6443|" /tmp/mc-integration-kubeconfig
-  docker run --network kind --privileged --rm \
-	  -w /usr/src/antrea.io/antrea \
-    -v /tmp/mc-integration-kubeconfig:/tmp/mc-integration-kubeconfig \
-	  -v ${BASEDIR}/../.coverage:/usr/src/antrea.io/antrea/multicluster/.coverage \
-	  -v ${BASEDIR}/../..:/usr/src/antrea.io/antrea:ro \
-	  antrea/mc-test test-integration
-else
-    go test -coverpkg=antrea.io/antrea/multicluster/controllers/multicluster/... -coverprofile=../.coverage/coverage-integration.txt -covermode=atomic -cover antrea.io/antrea/multicluster/test/integration/...
-fi
+go test -coverpkg=antrea.io/antrea/multicluster/controllers/multicluster/... -coverprofile=.coverage/coverage-integration.txt -covermode=atomic -cover antrea.io/antrea/multicluster/test/integration/...
diff --git a/pkg/agent/controller/networkpolicy/reconciler_test.go b/pkg/agent/controller/networkpolicy/reconciler_test.go
index 0560113ba..9cfb32266 100644
--- a/pkg/agent/controller/networkpolicy/reconciler_test.go
+++ b/pkg/agent/controller/networkpolicy/reconciler_test.go
@@ -1256,10 +1256,10 @@ func TestReconcilerUpdate(t *testing.T) {
 				mockOFClient.EXPECT().UninstallPolicyRuleFlows(gomock.Any())
 			}
 			if len(tt.expectedAddedFrom) > 0 {
-				mockOFClient.EXPECT().AddPolicyRuleAddress(gomock.Any(), types.SrcAddress, gomock.InAnyOrder(tt.expectedAddedFrom), priority)
+				mockOFClient.EXPECT().AddPolicyRuleAddress(gomock.Any(), types.SrcAddress, gomock.InAnyOrder(tt.expectedAddedFrom), priority, false)
 			}
 			if len(tt.expectedAddedTo) > 0 {
-				mockOFClient.EXPECT().AddPolicyRuleAddress(gomock.Any(), types.DstAddress, gomock.InAnyOrder(tt.expectedAddedTo), priority)
+				mockOFClient.EXPECT().AddPolicyRuleAddress(gomock.Any(), types.DstAddress, gomock.InAnyOrder(tt.expectedAddedTo), priority, false)
 			}
 			if len(tt.expectedDeletedFrom) > 0 {
 				mockOFClient.EXPECT().DeletePolicyRuleAddress(gomock.Any(), types.SrcAddress, gomock.InAnyOrder(tt.expectedDeletedFrom), priority)
diff --git a/pkg/agent/multicast/mcast_discovery_test.go b/pkg/agent/multicast/mcast_discovery_test.go
index 620b69d7c..b23dff218 100644
--- a/pkg/agent/multicast/mcast_discovery_test.go
+++ b/pkg/agent/multicast/mcast_discovery_test.go
@@ -180,7 +180,7 @@ func generatePacketInForRemoteReport(t *testing.T, snooper *IGMPSnooper, groups
 }
 
 func createTunnelInterface(tunnelPort uint32, localNodeIP net.IP) *interfacestore.InterfaceConfig {
-	tunnelInterface := interfacestore.NewTunnelInterface("antrea-tun0", ovsconfig.GeneveTunnel, localNodeIP, false)
+	tunnelInterface := interfacestore.NewTunnelInterface("antrea-tun0", ovsconfig.GeneveTunnel, 6081, localNodeIP, false)
 	tunnelInterface.OVSPortConfig = &interfacestore.OVSPortConfig{OFPort: int32(tunnelPort)}
 	return tunnelInterface
 }
diff --git a/pkg/agent/openflow/client_test.go b/pkg/agent/openflow/client_test.go
index d698ab6fe..b3043a52b 100644
--- a/pkg/agent/openflow/client_test.go
+++ b/pkg/agent/openflow/client_test.go
@@ -437,7 +437,7 @@ func prepareTraceflowFlow(ctrl *gomock.Controller) *client {
 	c.bridge = ovsoftest.NewMockBridge(ctrl)
 
 	mFlow := ovsoftest.NewMockFlow(ctrl)
-	ctx := &conjMatchFlowContext{dropFlow: mFlow}
+	ctx := &conjMatchFlowContext{dropFlow: mFlow, dropFlowEnableLogging: false}
 	mFlow.EXPECT().FlowProtocol().Return(binding.Protocol("ip"))
 	mFlow.EXPECT().CopyToBuilder(priorityNormal+2, false).Return(EgressDefaultTable.ofTable.BuildFlow(priorityNormal + 2)).Times(1)
 	c.featureNetworkPolicy.globalConjMatchFlowCache["mockContext"] = ctx
diff --git a/pkg/agent/openflow/network_policy_test.go b/pkg/agent/openflow/network_policy_test.go
index 89eb37fea..614941811 100644
--- a/pkg/agent/openflow/network_policy_test.go
+++ b/pkg/agent/openflow/network_policy_test.go
@@ -105,7 +105,7 @@ func TestPolicyRuleConjunction(t *testing.T) {
 
 	var addedAddrs = parseAddresses([]string{"192.168.1.3", "192.168.1.30", "192.168.2.0/24", "103", "104"})
 	expectConjunctionsCount([]*expectConjunctionTimes{{5, ruleID1, clauseID, nClause}})
-	flowChanges1 := clause1.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs, nil)
+	flowChanges1 := clause1.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs, nil, false)
 	err := c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChanges1)
 	require.Nil(t, err, "Failed to invoke addAddrFlows")
 	checkFlowCount(t, len(addedAddrs))
@@ -130,7 +130,7 @@ func TestPolicyRuleConjunction(t *testing.T) {
 	var addedAddrs2 = parseAddresses([]string{"192.168.1.30", "192.168.1.50"})
 	expectConjunctionsCount([]*expectConjunctionTimes{{2, ruleID2, clauseID2, nClause}})
 	expectConjunctionsCount([]*expectConjunctionTimes{{1, ruleID1, clauseID, nClause}})
-	flowChanges3 := clause2.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs2, nil)
+	flowChanges3 := clause2.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs2, nil, false)
 	err = c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChanges3)
 	require.Nil(t, err, "Failed to invoke addAddrFlows")
 	testAddr := NewIPAddress(net.ParseIP("192.168.1.30"))
@@ -146,7 +146,7 @@ func TestPolicyRuleConjunction(t *testing.T) {
 	nClause3 := uint8(1)
 	clause3 := conj3.newClause(clauseID3, nClause3, mockEgressRuleTable, mockEgressDefaultTable)
 	var addedAddrs3 = parseAddresses([]string{"192.168.1.30"})
-	flowChanges4 := clause3.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs3, nil)
+	flowChanges4 := clause3.addAddrFlows(c.featureNetworkPolicy, types.SrcAddress, addedAddrs3, nil, false)
 	err = c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChanges4)
 	require.Nil(t, err, "Failed to invoke addAddrFlows")
 	checkConjMatchFlowActions(t, c, clause3, testAddr, types.SrcAddress, 2, 1)
@@ -643,7 +643,7 @@ func TestConjMatchFlowContextKeyConflict(t *testing.T) {
 		id: ruleID1,
 	}
 	clause1 := conj1.newClause(1, 3, mockEgressRuleTable, mockEgressDefaultTable)
-	flowChange1 := clause1.addAddrFlows(c.featureNetworkPolicy, types.DstAddress, parseAddresses([]string{ip.String()}), nil)
+	flowChange1 := clause1.addAddrFlows(c.featureNetworkPolicy, types.DstAddress, parseAddresses([]string{ip.String()}), nil, false)
 	err := c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChange1)
 	require.Nil(t, err, "no error expect in applyConjunctiveMatchFlows")
 
@@ -652,7 +652,7 @@ func TestConjMatchFlowContextKeyConflict(t *testing.T) {
 		id: ruleID2,
 	}
 	clause2 := conj2.newClause(1, 3, mockEgressRuleTable, mockEgressDefaultTable)
-	flowChange2 := clause2.addAddrFlows(c.featureNetworkPolicy, types.DstAddress, parseAddresses([]string{ipNet.String()}), nil)
+	flowChange2 := clause2.addAddrFlows(c.featureNetworkPolicy, types.DstAddress, parseAddresses([]string{ipNet.String()}), nil, false)
 	err = c.featureNetworkPolicy.applyConjunctiveMatchFlows(flowChange2)
 	require.Nil(t, err, "no error expect in applyConjunctiveMatchFlows")
 	expectedMatchKey := fmt.Sprintf("table:%d,priority:%s,matchPair:%s", EgressRuleTable.GetID(), strconv.Itoa(int(priorityNormal)), singleMatchPair.KeyString())
diff --git a/pkg/agent/openflow/testing/mock_openflow.go b/pkg/agent/openflow/testing/mock_openflow.go
index cfb9be05f..d3e7d0ea6 100644
--- a/pkg/agent/openflow/testing/mock_openflow.go
+++ b/pkg/agent/openflow/testing/mock_openflow.go
@@ -71,17 +71,17 @@ func (mr *MockClientMockRecorder) AddAddressToDNSConjunction(arg0, arg1 interfac
 }
 
 // AddPolicyRuleAddress mocks base method
-func (m *MockClient) AddPolicyRuleAddress(arg0 uint32, arg1 types.AddressType, arg2 []types.Address, arg3 *uint16) error {
+func (m *MockClient) AddPolicyRuleAddress(arg0 uint32, arg1 types.AddressType, arg2 []types.Address, arg3 *uint16, arg4 bool) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "AddPolicyRuleAddress", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "AddPolicyRuleAddress", arg0, arg1, arg2, arg3, arg4)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // AddPolicyRuleAddress indicates an expected call of AddPolicyRuleAddress
-func (mr *MockClientMockRecorder) AddPolicyRuleAddress(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockClientMockRecorder) AddPolicyRuleAddress(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPolicyRuleAddress", reflect.TypeOf((*MockClient)(nil).AddPolicyRuleAddress), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPolicyRuleAddress", reflect.TypeOf((*MockClient)(nil).AddPolicyRuleAddress), arg0, arg1, arg2, arg3, arg4)
 }
 
 // BatchInstallPolicyRuleFlows mocks base method
diff --git a/pkg/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/controller/networkpolicy/networkpolicy_controller_test.go
index b7b4f1ca1..76a85c8a0 100644
--- a/pkg/controller/networkpolicy/networkpolicy_controller_test.go
+++ b/pkg/controller/networkpolicy/networkpolicy_controller_test.go
@@ -158,6 +158,7 @@ func newControllerWithoutEventHandler(k8sObjects, crdObjects []runtime.Object) (
 	addressGroupStore := store.NewAddressGroupStore()
 	internalNetworkPolicyStore := store.NewNetworkPolicyStore()
 	internalGroupStore := store.NewGroupStore()
+	namespaceInformer := informerFactory.Core().V1().Namespaces()
 	networkPolicyInformer := informerFactory.Networking().V1().NetworkPolicies()
 	tierInformer := crdInformerFactory.Crd().V1alpha1().Tiers()
 	cnpInformer := crdInformerFactory.Crd().V1alpha1().ClusterNetworkPolicies()
@@ -167,6 +168,7 @@ func newControllerWithoutEventHandler(k8sObjects, crdObjects []runtime.Object) (
 	npController := &NetworkPolicyController{
 		kubeClient:                 client,
 		crdClient:                  crdClient,
+		namespaceLister:            namespaceInformer.Lister(),
 		networkPolicyInformer:      networkPolicyInformer,
 		networkPolicyLister:        networkPolicyInformer.Lister(),
 		networkPolicyListerSynced:  networkPolicyInformer.Informer().HasSynced,
@@ -2532,11 +2534,12 @@ func TestProcessNetworkPolicy(t *testing.T) {
 					UID:       "uidA",
 				},
 				Rules: []controlplane.NetworkPolicyRule{{
-					Direction: controlplane.DirectionIn,
-					From:      matchAllPeer,
-					Services:  nil,
-					Priority:  defaultRulePriority,
-					Action:    &defaultAction,
+					Direction:     controlplane.DirectionIn,
+					From:          matchAllPeer,
+					Services:      nil,
+					Priority:      defaultRulePriority,
+					Action:        &defaultAction,
+					EnableLogging: false,
 				}},
 				AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsA", &metav1.LabelSelector{}, nil, nil, nil).NormalizedName)},
 			},
@@ -2626,8 +2629,9 @@ func TestProcessNetworkPolicy(t *testing.T) {
 								Port:     &int80,
 							},
 						},
-						Priority: defaultRulePriority,
-						Action:   &defaultAction,
+						Priority:      defaultRulePriority,
+						Action:        &defaultAction,
+						EnableLogging: false,
 					},
 					{
 						Direction: controlplane.DirectionOut,
@@ -2640,8 +2644,9 @@ func TestProcessNetworkPolicy(t *testing.T) {
 								Port:     &int81,
 							},
 						},
-						Priority: defaultRulePriority,
-						Action:   &defaultAction,
+						Priority:      defaultRulePriority,
+						Action:        &defaultAction,
+						EnableLogging: false,
 					},
 				},
 				AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsA", &selectorA, nil, nil, nil).NormalizedName)},
@@ -2704,8 +2709,9 @@ func TestProcessNetworkPolicy(t *testing.T) {
 								Port:     &int80,
 							},
 						},
-						Priority: defaultRulePriority,
-						Action:   &defaultAction,
+						Priority:      defaultRulePriority,
+						Action:        &defaultAction,
+						EnableLogging: false,
 					},
 					{
 						Direction: controlplane.DirectionIn,
@@ -2718,8 +2724,9 @@ func TestProcessNetworkPolicy(t *testing.T) {
 								Port:     &int81,
 							},
 						},
-						Priority: defaultRulePriority,
-						Action:   &defaultAction,
+						Priority:      defaultRulePriority,
+						Action:        &defaultAction,
+						EnableLogging: false,
 					},
 				},
 				AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsA", &selectorA, nil, nil, nil).NormalizedName)},
@@ -2747,6 +2754,69 @@ func TestProcessNetworkPolicy(t *testing.T) {
 	}
 }
 
+func TestProcessNetworkPolicyLogging(t *testing.T) {
+	tests := []struct {
+		name                    string
+		inputPolicy             *networkingv1.NetworkPolicy
+		expectedPolicy          *antreatypes.NetworkPolicy
+		expectedAppliedToGroups int
+		expectedAddressGroups   int
+	}{
+		{
+			name: "default-allow-ingress",
+			inputPolicy: &networkingv1.NetworkPolicy{
+				ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", Name: "npA", UID: "uidA"},
+				Spec: networkingv1.NetworkPolicySpec{
+					PodSelector: metav1.LabelSelector{},
+					PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
+					Ingress:     []networkingv1.NetworkPolicyIngressRule{{}},
+				},
+			},
+			expectedPolicy: &antreatypes.NetworkPolicy{
+				UID:  "uidA",
+				Name: "uidA",
+				SourceRef: &controlplane.NetworkPolicyReference{
+					Type:      controlplane.K8sNetworkPolicy,
+					Namespace: "nsA",
+					Name:      "npA",
+					UID:       "uidA",
+				},
+				Rules: []controlplane.NetworkPolicyRule{{
+					Direction:     controlplane.DirectionIn,
+					From:          matchAllPeer,
+					Services:      nil,
+					Priority:      defaultRulePriority,
+					Action:        &defaultAction,
+					EnableLogging: true,
+				}},
+				AppliedToGroups: []string{getNormalizedUID(antreatypes.NewGroupSelector("nsA", &metav1.LabelSelector{}, nil, nil, nil).NormalizedName)},
+			},
+			expectedAppliedToGroups: 1,
+			expectedAddressGroups:   0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, c := newController()
+			nsA := corev1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:        "nsA",
+					Annotations: map[string]string{"networkpolicy.antrea.io/enable-logging": "true"},
+				},
+			}
+			c.namespaceStore.Add(&nsA)
+
+			if actualPolicy := c.processNetworkPolicy(tt.inputPolicy); !reflect.DeepEqual(actualPolicy, tt.expectedPolicy) {
+				t.Errorf("processNetworkPolicy() got %v, want %v", actualPolicy, tt.expectedPolicy)
+			}
+
+			if actualAppliedToGroups := len(c.appliedToGroupStore.List()); actualAppliedToGroups != tt.expectedAppliedToGroups {
+				t.Errorf("len(appliedToGroupStore.List()) got %v, want %v", actualAppliedToGroups, tt.expectedAppliedToGroups)
+			}
+		})
+	}
+}
+
 func TestPodToGroupMember(t *testing.T) {
 	namedPod := getPod("", "", "", "", true)
 	unNamedPod := getPod("", "", "", "", false)
diff --git a/test/e2e/antreapolicy_test.go b/test/e2e/antreapolicy_test.go
index 9667d10af..36e548a3f 100644
--- a/test/e2e/antreapolicy_test.go
+++ b/test/e2e/antreapolicy_test.go
@@ -40,6 +40,7 @@ import (
 	crdv1alpha1 "antrea.io/antrea/pkg/apis/crd/v1alpha1"
 	crdv1alpha2 "antrea.io/antrea/pkg/apis/crd/v1alpha2"
 	crdv1alpha3 "antrea.io/antrea/pkg/apis/crd/v1alpha3"
+	"antrea.io/antrea/pkg/controller/networkpolicy"
 	"antrea.io/antrea/pkg/features"
 	. "antrea.io/antrea/test/e2e/utils"
 )
@@ -1946,7 +1947,7 @@ func testANPMultipleAppliedTo(t *testing.T, data *TestData, singleRule bool) {
 	failOnError(k8sUtils.DeleteANP(builder.Namespace, builder.Name), t)
 }
 
-// testAuditLoggingBasic tests that a audit log is generated when egress drop applied
+// testAuditLoggingBasic tests that audit logs are generated when egress drop applied
 func testAuditLoggingBasic(t *testing.T, data *TestData) {
 	builder := &ClusterNetworkPolicySpecBuilder{}
 	builder = builder.SetName("test-log-acnp-deny").
@@ -2034,6 +2035,99 @@ func testAuditLoggingBasic(t *testing.T, data *TestData) {
 	failOnError(k8sUtils.CleanACNPs(), t)
 }
 
+// testAuditLoggingEnableNP tests that audit logs are generated when K8s NP is applied
+// tests both Allow traffic by K8s NP and Drop traffic by implicit K8s policy drop
+func testAuditLoggingEnableNP(t *testing.T, data *TestData) {
+	data.updateNamespaceWithAnnotations(namespaces["x"], map[string]string{networkpolicy.EnableNPLoggingAnnotationKey: "true"})
+	// Add a K8s namespaced NetworkPolicy in ns x that allow ingress traffic from
+	// Pod x/b to x/a which default denies other ingress including from Pod x/c to x/a
+	k8sNPBuilder := &NetworkPolicySpecBuilder{}
+	k8sNPBuilder = k8sNPBuilder.SetName(namespaces["x"], "allow-x-b-to-x-a").
+		SetPodSelector(map[string]string{"pod": "a"}).
+		SetTypeIngress().
+		AddIngress(v1.ProtocolTCP, &p80, nil, nil, nil,
+			map[string]string{"pod": "b"}, nil, nil, nil)
+
+	knp, err := k8sUtils.CreateOrUpdateNetworkPolicy(k8sNPBuilder.Get())
+	failOnError(err, t)
+	failOnError(waitForResourceReady(t, timeout, knp), t)
+
+	// generate some traffic that will be dropped by implicit K8s policy drop
+	var wg sync.WaitGroup
+	oneProbe := func(ns1, pod1, ns2, pod2 string) {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			k8sUtils.Probe(ns1, pod1, ns2, pod2, p80, ProtocolTCP)
+		}()
+	}
+	oneProbe(namespaces["x"], "b", namespaces["x"], "a")
+	oneProbe(namespaces["x"], "c", namespaces["x"], "a")
+	wg.Wait()
+
+	podXA, err := k8sUtils.GetPodByLabel(namespaces["x"], "a")
+	if err != nil {
+		t.Errorf("Failed to get Pod in Namespace x with label 'pod=a': %v", err)
+	}
+	// nodeName is guaranteed to be set at this stage, since the framework waits for all Pods to be in Running phase
+	nodeName := podXA.Spec.NodeName
+	antreaPodName, err := data.getAntreaPodOnNode(nodeName)
+	if err != nil {
+		t.Errorf("Error occurred when trying to get the Antrea Agent Pod running on Node %s: %v", nodeName, err)
+	}
+	cmd := []string{"cat", logDir + logfileName}
+
+	if err := wait.Poll(1*time.Second, 10*time.Second, func() (bool, error) {
+		stdout, stderr, err := data.RunCommandFromPod(antreaNamespace, antreaPodName, "antrea-agent", cmd)
+		if err != nil || stderr != "" {
+			// file may not exist yet
+			t.Logf("Error when printing the audit log file, err: %v, stderr: %v", err, stderr)
+			return false, nil
+		}
+		if !strings.Contains(stdout, "K8sNetworkPolicy") {
+			t.Logf("Audit log file does not contain entries for 'test-log-acnp-deny' yet")
+			return false, nil
+		}
+
+		var expectedNumEntries, actualNumEntries int
+		srcPods := []string{namespaces["x"] + "/b", namespaces["x"] + "/c"}
+		expectedLogPrefix := []string{"allow-x-b-to-x-a Allow [0-9]+ ", "K8sNetworkPolicy Drop -1 "}
+		destIPs, _ := podIPs[namespaces["x"]+"/a"]
+		for i := 0; i < len(srcPods); i++ {
+			srcIPs, _ := podIPs[srcPods[i]]
+			for _, srcIP := range srcIPs {
+				for _, destIP := range destIPs {
+					// only look for an entry in the audit log file if srcIP and
+					// dstIP are of the same family
+					if strings.Contains(srcIP, ".") != strings.Contains(destIP, ".") {
+						continue
+					}
+					expectedNumEntries += 1
+					// The audit log should contain log entry `... Drop <ofPriority> <x/a IP> <z/* IP> ...`
+					re := regexp.MustCompile(expectedLogPrefix[i] + srcIP + ` [0-9]+ ` + destIP + ` ` + strconv.Itoa(int(p80)))
+					if re.MatchString(stdout) {
+						actualNumEntries += 1
+					} else {
+						t.Logf("Audit log does not contain expected entry from %s (%s) to x/a (%s)", srcPods[i], srcIP, destIP)
+					}
+					break
+				}
+			}
+		}
+		if actualNumEntries != expectedNumEntries {
+			t.Logf("Missing entries in audit log with K8s NP: expected %d but found %d", expectedNumEntries, actualNumEntries)
+			return false, nil
+		}
+		return true, nil
+	}); err != nil {
+		t.Errorf("Error when polling audit log files for required entries: %v", err)
+	}
+	failOnError(k8sUtils.DeleteNetworkPolicy(namespaces["x"], "allow-x-b-to-x-a"), t)
+	data.UpdateNamespace(namespaces["x"], func(namespace *v1.Namespace) {
+		delete(namespace.Annotations, networkpolicy.EnableNPLoggingAnnotationKey)
+	})
+}
+
 func testAppliedToPerRule(t *testing.T) {
 	builder := &AntreaNetworkPolicySpecBuilder{}
 	builder = builder.SetName(namespaces["y"], "np1").SetPriority(1.0)
@@ -3556,6 +3650,7 @@ func TestAntreaPolicy(t *testing.T) {
 
 	t.Run("TestGroupAuditLogging", func(t *testing.T) {
 		t.Run("Case=AuditLoggingBasic", func(t *testing.T) { testAuditLoggingBasic(t, data) })
+		t.Run("Case=AuditLoggingEnableNP", func(t *testing.T) { testAuditLoggingEnableNP(t, data) })
 	})
 
 	t.Run("TestMulticastNP", func(t *testing.T) {
diff --git a/test/e2e/framework.go b/test/e2e/framework.go
index 40d8ee35c..e6a3f8627 100644
--- a/test/e2e/framework.go
+++ b/test/e2e/framework.go
@@ -600,11 +600,11 @@ func (data *TestData) CreateNamespace(namespace string, mutateFunc func(*corev1.
 		mutateFunc(ns)
 	}
 	if ns, err := data.clientset.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil {
-		// Ignore error if the namespace already exists
+		// Ignore error if the Namespace already exists
 		if !errors.IsAlreadyExists(err) {
 			return fmt.Errorf("error when creating '%s' Namespace: %v", namespace, err)
 		}
-		// When namespace already exists, check phase
+		// When Namespace already exists, check phase
 		if ns.Status.Phase == corev1.NamespaceTerminating {
 			return fmt.Errorf("error when creating '%s' Namespace: namespace exists but is in 'Terminating' phase", namespace)
 		}
@@ -612,8 +612,35 @@ func (data *TestData) CreateNamespace(namespace string, mutateFunc func(*corev1.
 	return nil
 }
 
-// createNamespaceWithAnnotations creates the namespace with Annotations.
+func (data *TestData) UpdateNamespace(namespace string, mutateFunc func(*corev1.Namespace)) error {
+	ns, _ := data.clientset.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{})
+	if mutateFunc != nil {
+		mutateFunc(ns)
+	}
+	if ns, err := data.clientset.CoreV1().Namespaces().Update(context.TODO(), ns, metav1.UpdateOptions{}); err != nil {
+		// Check Namespace phase
+		if ns.Status.Phase == corev1.NamespaceTerminating {
+			return fmt.Errorf("error when updating '%s' Namespace: namespace is in 'Terminating' phase", namespace)
+		}
+		return fmt.Errorf("error when updating '%s' Namespace: %v", namespace, err)
+	}
+	return nil
+}
+
+// createNamespaceWithAnnotations creates the Namespace with Annotations.
 func (data *TestData) createNamespaceWithAnnotations(namespace string, annotations map[string]string) error {
+	mutateFunc := data.generateNamespaceAnnotationsMutateFunc(annotations)
+	return data.CreateNamespace(namespace, mutateFunc)
+}
+
+// updateNamespaceWithAnnotations updates the given Namespace with Annotations.
+func (data *TestData) updateNamespaceWithAnnotations(namespace string, annotations map[string]string) error {
+	mutateFunc := data.generateNamespaceAnnotationsMutateFunc(annotations)
+	return data.UpdateNamespace(namespace, mutateFunc)
+}
+
+// generateAnnotationsMutateFunc generates a mutate function to add given Annotations to a Namespace.
+func (data *TestData) generateNamespaceAnnotationsMutateFunc(annotations map[string]string) func(*corev1.Namespace) {
 	var mutateFunc func(*corev1.Namespace)
 	if annotations != nil {
 		mutateFunc = func(namespace *corev1.Namespace) {
@@ -625,7 +652,7 @@ func (data *TestData) createNamespaceWithAnnotations(namespace string, annotatio
 			}
 		}
 	}
-	return data.CreateNamespace(namespace, mutateFunc)
+	return mutateFunc
 }
 
 // DeleteNamespace deletes the provided Namespace, and waits for deletion to actually complete if timeout>=0
diff --git a/test/integration/agent/openflow_test.go b/test/integration/agent/openflow_test.go
index 12cbd83be..34d2536f2 100644
--- a/test/integration/agent/openflow_test.go
+++ b/test/integration/agent/openflow_test.go
@@ -313,10 +313,10 @@ func TestReplayFlowsNetworkPolicyFlows(t *testing.T) {
 	err = c.InstallPolicyRuleFlows(rule)
 	require.Nil(t, err, "Failed to InstallPolicyRuleFlows")
 
-	err = c.AddPolicyRuleAddress(ruleID, types.SrcAddress, prepareIPNetAddresses([]string{"192.168.5.0/24", "192.169.1.0/24"}), nil)
+	err = c.AddPolicyRuleAddress(ruleID, types.SrcAddress, prepareIPNetAddresses([]string{"192.168.5.0/24", "192.169.1.0/24"}), nil, false)
 	require.Nil(t, err, "Failed to AddPolicyRuleAddress")
 	ofport := int32(100)
-	err = c.AddPolicyRuleAddress(ruleID, types.DstAddress, []types.Address{ofClient.NewOFPortAddress(ofport)}, nil)
+	err = c.AddPolicyRuleAddress(ruleID, types.DstAddress, []types.Address{ofClient.NewOFPortAddress(ofport)}, nil, false)
 	require.Nil(t, err, "Failed to AddPolicyRuleAddress")
 
 	testReplayFlows(t)
@@ -496,7 +496,7 @@ func TestNetworkPolicyFlows(t *testing.T) {
 	checkDeleteAddress(t, ingressRuleTable, priorityNormal, ruleID, addedFrom, types.SrcAddress)
 
 	ofport := int32(100)
-	err = c.AddPolicyRuleAddress(ruleID, types.DstAddress, []types.Address{ofClient.NewOFPortAddress(ofport)}, nil)
+	err = c.AddPolicyRuleAddress(ruleID, types.DstAddress, []types.Address{ofClient.NewOFPortAddress(ofport)}, nil, false)
 	require.Nil(t, err, "Failed to AddPolicyRuleAddress")
 
 	// Dump flows.
@@ -795,7 +795,7 @@ func checkDefaultDropFlows(t *testing.T, table string, priority int, addrType ty
 }
 
 func checkAddAddress(t *testing.T, ruleTable string, priority int, ruleID uint32, addedAddress []types.Address, addrType types.AddressType) {
-	err := c.AddPolicyRuleAddress(ruleID, addrType, addedAddress, nil)
+	err := c.AddPolicyRuleAddress(ruleID, addrType, addedAddress, nil, false)
 	require.Nil(t, err, "Failed to AddPolicyRuleAddress")
 
 	// dump flows
diff --git a/test/integration/ovs/ovs_client_test.go b/test/integration/ovs/ovs_client_test.go
index 74a4162ff..9907a91a5 100644
--- a/test/integration/ovs/ovs_client_test.go
+++ b/test/integration/ovs/ovs_client_test.go
@@ -287,6 +287,75 @@ func TestTunnelOptionCsum(t *testing.T) {
 	}
 }
 
+func TestTunnelOptionTunnelPort(t *testing.T) {
+	testCases := map[string]struct {
+		initialTunnelPort int32
+		updatedTunnelPort int32
+	}{
+		"initial zero, kept zero": {
+			initialTunnelPort: 0,
+			updatedTunnelPort: 0,
+		},
+		"initial zero, updated to 8472": {
+			initialTunnelPort: 0,
+			updatedTunnelPort: 8472,
+		},
+		"initial 8472, kept 8473": {
+			initialTunnelPort: 8472,
+			updatedTunnelPort: 8473,
+		},
+		"initial 8472, updated to zero": {
+			initialTunnelPort: 8472,
+			updatedTunnelPort: 0,
+		},
+	}
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			data := &testData{}
+			data.setup(t)
+			defer data.teardown(t)
+
+			name := "vxlan0"
+			extraOptions := map[string]interface{}{}
+			if testCase.initialTunnelPort != 0 {
+				extraOptions["dst_port"] = strconv.Itoa(int(testCase.initialTunnelPort))
+			}
+			_, err := data.br.CreateTunnelPortExt(name, ovsconfig.VXLANTunnel, ofPortRequest, true, "", "", "", "", extraOptions, nil)
+			require.Nil(t, err, "Error when creating tunnel port")
+			options, err := data.br.GetInterfaceOptions(name)
+			require.Nil(t, err, "Error when getting interface options")
+			dstPort, exists := options["dst_port"]
+			if testCase.initialTunnelPort != 0 {
+				actualInitialTunnelPort, _ := strconv.ParseInt(dstPort, 10, 32)
+				require.Equal(t, testCase.initialTunnelPort, int32(actualInitialTunnelPort))
+			} else {
+				require.Equal(t, exists, false)
+			}
+
+			updatedOptions := map[string]interface{}{}
+			for k, v := range options {
+				updatedOptions[k] = v
+			}
+			if testCase.updatedTunnelPort != 0 {
+				updatedOptions["dst_port"] = strconv.Itoa(int(testCase.updatedTunnelPort))
+			} else if _, ok := updatedOptions["dst_port"]; ok {
+				delete(updatedOptions, "dst_port")
+			}
+			err = data.br.SetInterfaceOptions(name, updatedOptions)
+			require.Nil(t, err, "Error when setting interface options")
+			options, err = data.br.GetInterfaceOptions(name)
+			require.Nil(t, err, "Error when getting interface options")
+			dstPort, exists = options["dst_port"]
+			if testCase.updatedTunnelPort != 0 {
+				actualTunnelPort, _ := strconv.ParseInt(dstPort, 10, 32)
+				require.Equal(t, testCase.updatedTunnelPort, int32(actualTunnelPort))
+			} else {
+				require.Equal(t, exists, false)
+			}
+		})
+	}
+}
+
 func deleteAllPorts(t *testing.T, br *ovsconfig.OVSBridge) {
 	portList, err := br.GetPortUUIDList()
 	require.Nil(t, err, "Error when retrieving port list")