Repository: cilium/cilium
Pull request #13466 (2020-10-08 14:33:05)

test: Test for vxlan + per-endpoint routes
#13346 enabled combining per-endpoint routes with vxlan encapsulation. This pull request adds a test for that setup.


Patch:
diff --git a/Documentation/cmdref/cilium_service_update.md b/Documentation/cmdref/cilium_service_update.md
index e76c3832c6..849df74b6f 100644
--- a/Documentation/cmdref/cilium_service_update.md
+++ b/Documentation/cmdref/cilium_service_update.md
@@ -26,6 +26,7 @@ cilium service update [flags]
       --k8s-node-port               Set service as a k8s NodePort
       --k8s-traffic-policy string   Set service with k8s externalTrafficPolicy as {Local,Cluster} (default "Cluster")
       --local-redirect              Set service as Local Redirect
+      --protocol string             Protocol for service (e.g. TCP, UDP) (default "tcp")
 ```
 
 ### Options inherited from parent commands
diff --git a/Documentation/contributing/development/contributing_guide.rst b/Documentation/contributing/development/contributing_guide.rst
index 44b23ea9d2..a12e9af761 100644
--- a/Documentation/contributing/development/contributing_guide.rst
+++ b/Documentation/contributing/development/contributing_guide.rst
@@ -212,6 +212,9 @@ Getting a pull request merged
       #. Single node runtime tests
       #. Multi node Kubernetes tests
 
+      If a CI test fails which seems unrelated to your PR, it may be a flaky
+      test. Follow the process described in :ref:`ci_failure_triage`.
+
 #. As part of the submission, GitHub will have requested a review from the
    respective code owners according to the ``CODEOWNERS`` file in the
    repository.
diff --git a/Documentation/contributing/development/dev_setup.rst b/Documentation/contributing/development/dev_setup.rst
index c75400b2bb..4db33f32dd 100644
--- a/Documentation/contributing/development/dev_setup.rst
+++ b/Documentation/contributing/development/dev_setup.rst
@@ -180,6 +180,7 @@ from the main Vagrantfile, for example:
 To start a local k8s 1.18 cluster with one CI VM locally, run:
 
 ::
+
     $ cd test
     $ K8S_VERSION=1.18 K8S_NODES=1 ./vagrant-local-start.sh
 
@@ -193,6 +194,7 @@ can run ``make clean`` in ``tests`` to delete the cached vagrant box.
 To start the CI runtime VM locally, run:
 
 ::
+
     $ cd test
     $ ./vagrant-local-start-runtime.sh
 
diff --git a/Documentation/gettingstarted/kubeproxy-free.rst b/Documentation/gettingstarted/kubeproxy-free.rst
index 057a3ba82a..81ac219e98 100644
--- a/Documentation/gettingstarted/kubeproxy-free.rst
+++ b/Documentation/gettingstarted/kubeproxy-free.rst
@@ -224,16 +224,16 @@ port ``31940`` (one for each of devices ``eth0`` and ``eth1``):
 .. parsed-literal::
 
     kubectl exec -it -n kube-system cilium-fmh8d -- cilium service list
-    ID   Frontend               Service Type   Backend                    
+    ID   Frontend                   Service Type   Backend                    
     [...]
-    4    10.104.239.135:80      ClusterIP      1 => 10.217.0.107:80       
-                                               2 => 10.217.0.149:80       
-    5    0.0.0.0:31940          NodePort       1 => 10.217.0.107:80       
-                                               2 => 10.217.0.149:80       
-    6    192.168.178.29:31940   NodePort       1 => 10.217.0.107:80       
-                                               2 => 10.217.0.149:80       
-    7    172.16.0.29:31940      NodePort       1 => 10.217.0.107:80       
-                                               2 => 10.217.0.149:80       
+    4    10.104.239.135:80/TCP      ClusterIP      1 => 10.217.0.107:80/TCP
+                                                   2 => 10.217.0.149:80/TCP
+    5    0.0.0.0:31940/TCP          NodePort       1 => 10.217.0.107:80/TCP
+                                                   2 => 10.217.0.149:80/TCP
+    6    192.168.178.29:31940/TCP   NodePort       1 => 10.217.0.107:80/TCP
+                                                   2 => 10.217.0.149:80/TCP
+    7    172.16.0.29:31940/TCP      NodePort       1 => 10.217.0.107:80/TCP
+                                                   2 => 10.217.0.149:80/TCP
 
 At the same time we can inspect through ``iptables`` in the host namespace
 that no ``iptables`` rule for the service is present:
diff --git a/Documentation/gettingstarted/local-redirect-policy.rst b/Documentation/gettingstarted/local-redirect-policy.rst
index 8561c5f8e9..3f318936e9 100644
--- a/Documentation/gettingstarted/local-redirect-policy.rst
+++ b/Documentation/gettingstarted/local-redirect-policy.rst
@@ -4,7 +4,7 @@
     Please use the official rendered version released here:
     https://docs.cilium.io
 
-.. _local_redirect_policy:
+.. _local-redirect-policy:
 
 *****************************
 Local Redirect Policy (beta)
@@ -13,10 +13,25 @@ Local Redirect Policy (beta)
 This document explains how to configure Cilium's Local Redirect Policy, that
 enables pod traffic destined to an IP address and port/protocol tuple
 or Kubernetes service to be redirected locally to a backend pod within a node.
-The CiliumLocalRedirectPolicy is configured as a `CustomResourceDefinition`.
+The CiliumLocalRedirectPolicy is configured as a ``CustomResourceDefinition``.
 CiliumLocalRedirectPolicy is namespace-aware, while CiliumClusterwideLocalRedirectPolicy
 is a cluster-scoped version that specifies cluster-wide policies.
 
+There are two types of Local Redirect Policies supported. When traffic for a
+Kubernetes service needs to be redirected, use the `ServiceMatcher` type. The
+service needs to be of type ``clusterIP``.
+When traffic matching IP address and layer port/protocol, that doesn't belong to
+any Kubernetes service, needs to be redirected, use the `AddressMatcher` type.
+
+The policies can be gated by Kubernetes Role-based access control (RBAC)
+framework. See the official `RBAC documentation
+<https://kubernetes.io/docs/reference/access-authn-authz/rbac/>`_.
+
+When policies are applied, matched pod traffic is redirected. If desired, RBAC
+configurations can be used such that application developers can not escape
+the redirection.
+
+
 .. include:: ../beta.rst
 
 Deploy Cilium
@@ -24,13 +39,8 @@ Deploy Cilium
 
 .. include:: k8s-install-download-release.rst
 
-The Cilium Local Redirect Policy feature is enabled by default on new deployments
-via Helm:
-
-.. parsed-literal::
-
-   helm install cilium |CHART_RELEASE| \\
-     --namespace kube-system
+The Cilium Local Redirect Policy feature relies on :ref:`Kube-proxy free
+feature <kubeproxy-free>`, follow the guide to create a new deployment.
 
 Verify that Cilium agent pod is running.
 
@@ -85,7 +95,9 @@ the configurations specified in the CiliumLocalRedirectPolicy.
 Create Cilium Local Redirect Policy Custom Resources
 =====================================================
 There are two types of configurations supported in the CiliumLocalRedirectPolicy
-``CustomResource`` in order to match the traffic that needs to be redirected.
+in order to match the traffic that needs to be redirected.
+
+.. _AddressMatcher:
 
 AddressMatcher
 ---------------
@@ -118,6 +130,22 @@ Verify that the custom resource is created.
     NAME           AGE
     lrp-addr       20h
 
+Verify that Cilium's eBPF kube-proxy replacement created a ``LocalRedirect``
+service entry with the backend IP address of that of the ``lrp-pod`` that was
+selected by the policy.
+
+.. code-block:: bash
+
+    $ kubectl describe pod lrp-pod  | grep 'IP:'
+    IP:           10.16.70.187
+
+.. code-block:: bash
+
+    $ kubectl exec -it -n kube-system cilium-5ngzd -- cilium service list
+    ID   Frontend               Service Type   Backend
+    [...]
+    4    172.20.0.51:80         ClusterIP      1 => 10.16.70.187:80
+
 Invoke a curl command from the client pod to the IP address and port
 configuration specified in the ``lrp-addr`` custom resource above.
 
@@ -136,11 +164,6 @@ configuration specified in the ``lrp-addr`` custom resource above.
 
 Verify that the traffic was redirected to the ``lrp-pod`` that was deployed.
 
-.. code-block:: bash
-
-    $ kubectl describe pod lrp-pod  | grep 'IP:'
-    IP:           10.16.70.187
-
 .. parsed-literal::
 
     $ sudo tcpdump -i any -n port 80
@@ -154,24 +177,54 @@ Verify that the traffic was redirected to the ``lrp-pod`` that was deployed.
     01:36:24.609007 IP 10.16.70.187.80 > 10.16.215.55.60876: Flags [P.], seq 1:239, ack 96, win 219, options [nop,nop,TS val 2962246962 ecr 2541637677], length 238: HTTP: HTTP/1.1 200 OK
     01:36:24.609052 IP 10.16.215.55.60876 > 10.16.70.187.80: Flags [.], ack 239, win 229, options [nop,nop,TS val 2541637677 ecr 2962246962], length 0
 
+.. _ServiceMatcher:
+
 ServiceMatcher
 ---------------
 
 This type of configuration is specified using Kubernetes service name and namespace
-for which traffic needs to be redirected.
+for which traffic needs to be redirected. The service must be of type ``clusterIP``.
 When ``toPorts`` under ``redirectFrontend`` are not specified, traffic for
 all the service ports will be redirected. However, if traffic destined to only
 a subset of ports needs to be redirected, these ports need to be specified in the spec.
 Additionally, when multiple service ports are specified in the spec, they must be
 named. The port names will be used to map frontend ports with backend ports.
-
 Verify that the ports specified in ``toPorts`` under ``redirectBackend``
 exist in the backend pod spec.
 
+When a policy of this type is applied, the existing service entry
+created by Cilium's eBPF kube-proxy replacement will be replaced with a new
+service entry of type ``LocalRedirect``. This entry may only have node-local backend pods.
+
 The example shows how to redirect from traffic matching ``my-service``, to a
 backend pod deployed with labels ``app=proxy`` and Layer 4 port ``80``
 with protocol ``TCP``.
 
+Deploy the Kubernetes service for which traffic needs to be redirected.
+
+.. literalinclude:: ../../examples/kubernetes-local-redirect/k8s-svc.yaml
+
+.. parsed-literal::
+
+    $ kubectl apply -f \ |SCM_WEB|\/examples/kubernetes-local-redirect/k8s-svc.yaml
+
+Verify that the service is created.
+
+.. code-block:: bash
+
+    $ kubectl get service | grep 'my-service'
+    my-service   ClusterIP   172.20.0.51   <none>        80/TCP     2d7h
+
+Verify that Cilium's eBPF kube-proxy replacement created a ``ClusterIP``
+service entry.
+
+.. code-block:: bash
+
+    $ kubectl exec -it -n kube-system cilium-5ngzd -- cilium service list
+    ID   Frontend               Service Type   Backend
+    [...]
+    4    172.20.0.51:80         ClusterIP
+
 Create a custom resource of type CiliumLocalRedirectPolicy with ``serviceMatcher``
 configuration.
 
@@ -189,27 +242,23 @@ Verify that the custom resource is created.
     NAME               AGE
     lrp-svc   20h
 
-Deploy the Kubernetes service for which traffic needs to be redirected.
-
-.. literalinclude:: ../../examples/kubernetes-local-redirect/k8s-svc.yaml
-
-.. parsed-literal::
-
-    $ kubectl apply -f \ |SCM_WEB|\/examples/kubernetes-local-redirect/k8s-svc.yaml
-
-Verify that the service is created.
+Verify that entry Cilium's eBPF kube-proxy replacement updated the
+service entry with type ``LocalRedirect`` and the node-local backend
+selected by the policy.
 
 .. code-block:: bash
 
-    $ kubectl get service | grep 'my-service'
-    my-service   ClusterIP   172.20.0.51   <none>        80/TCP     2d7h
+    $ kubectl exec -it -n kube-system cilium-5ngzd -- cilium service list
+    ID   Frontend               Service Type       Backend
+    [...]
+    4    172.20.0.51:80         LocalRedirect      1 => 10.16.70.187:80
 
 Invoke a curl command from the client pod to the Cluster IP address and port of
 ``my-service`` specified in the ``lrp-svc`` custom resource above.
 
 .. code-block:: bash
 
-    $ kubectl exec mediabot -- curl -I -s http://172.20.0.51:80/index.html
+    $ kubectl exec mediabot -- curl -I -s http://172.20.0.51/index.html
     HTTP/1.1 200 OK
     Server: nginx/1.19.2
     Date: Fri, 28 Aug 2020 01:50:50 GMT
@@ -220,14 +269,37 @@ Invoke a curl command from the client pod to the Cluster IP address and port of
     ETag: "5f32b03b-264"
     Accept-Ranges: bytes
 
+Verify that the traffic was redirected to the ``lrp-pod`` that was deployed.
+
+.. code-block:: bash
+
+    $ kubectl describe pod lrp-pod  | grep 'IP:'
+    IP:           10.16.70.187
+
+.. parsed-literal::
+
+    $ sudo tcpdump -i any -n port 80
+    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
+    listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
+    01:36:24.608566 IP 10.16.215.55.60186 > 10.16.70.187.80: Flags [S], seq 2119454273, win 28200, options [mss 1410,sackOK,TS val 2541637677 ecr 0,nop,wscale 7], length 0
+    01:36:24.608600 IP 10.16.70.187.80 > 10.16.215.55.60876: Flags [S.], seq 1315636594, ack 2119454274, win 27960, options [mss 1410,sackOK,TS val 2962246962 ecr 2541637677,nop,wscale 7], length 0
+    01:36:24.608638 IP 10.16.215.55.60876 > 10.16.70.187.80: Flags [.], ack 1, win 221, options [nop,nop,TS val 2541637677 ecr 2962246962], length 0
+    01:36:24.608867 IP 10.16.215.55.60876 > 10.16.70.187.80: Flags [P.], seq 1:96, ack 1, win 221, options [nop,nop,TS val 2541637677 ecr 2962246962], length 95: HTTP: HEAD /index.html HTTP/1.1
+    01:36:24.608876 IP 10.16.70.187.80 > 10.16.215.55.60876: Flags [.], ack 96, win 219, options [nop,nop,TS val 2962246962 ecr 2541637677], length 0
+    01:36:24.609007 IP 10.16.70.187.80 > 10.16.215.55.60876: Flags [P.], seq 1:239, ack 96, win 219, options [nop,nop,TS val 2962246962 ecr 2541637677], length 238: HTTP: HTTP/1.1 200 OK
+    01:36:24.609052 IP 10.16.215.55.60876 > 10.16.70.187.80: Flags [.], ack 239, win 229, options [nop,nop,TS val 2541637677 ecr 2962246962], length 0
+
 Limitations
 ===========
-When you create a CiliumLocalRedirectPolicy, traffic for all the new connections
+When you create a Local Redirect Policy, traffic for all the new connections
 that get established after the policy is enforced will be redirected. But if
 you have existing active connections to remote pods that match the configurations
 specified in the policy, then these might not get redirected. To ensure all
 such connections are redirected locally, restart the client pods after
 configuring the CiliumLocalRedirectPolicy.
 
+Local Redirect Policy updates are currently not supported. If there are any
+changes to be made, delete the existing policy, and re-create a new one.
+
 
 
diff --git a/Documentation/gettingstarted/tls-visibility.rst b/Documentation/gettingstarted/tls-visibility.rst
index fce55911c6..f84962e070 100644
--- a/Documentation/gettingstarted/tls-visibility.rst
+++ b/Documentation/gettingstarted/tls-visibility.rst
@@ -101,17 +101,17 @@ requires four primary steps:
 1) Create an internal certificate authority by generating a CA private key and CA certificate.
 
 2) For any destination where TLS inspection is desired (e.g., artii.herokuapp.com in the example below),
-generate a private key and certificate signing request with a common name that matches the destination DNS
-name.
+   generate a private key and certificate signing request with a common name that matches the destination DNS
+   name.
 
 3) Use the CA private key to create a signed certificate.
 
 4) Ensure that all clients where TLS inspection is have the CA certificate installed so that they will
-trust all certificates signed by that CA.
+   trust all certificates signed by that CA.
 
 5) Given that Cilium will be terminating the initial TLS connection from the
-client and creating a new TLS connection to the destination, Cilium must be told the set of CAs that it
-should trust when validating the new TLS connection to the destination service.
+   client and creating a new TLS connection to the destination, Cilium must be told the set of CAs that it
+   should trust when validating the new TLS connection to the destination service.
 
 .. note::
 
@@ -247,7 +247,7 @@ certificate bundle and use it to validate outgoing TLS connections.
 
 .. parsed-literal::
 
-    $ kubectl create secret generic tls-orig-data -n kube-system --from-file=./ca-certificates.crt
+    $ kubectl create secret generic tls-orig-data -n kube-system --from-file=ca.crt=./ca-certificates.crt
 
 
 Apply DNS and TLS-aware Egress Policy
@@ -263,15 +263,16 @@ of communication between the ``mediabot`` pod to ``artii.herokuapp.com``.
 .. literalinclude:: ../../examples/kubernetes-tls-inspection/l7-visibility-tls.yaml
 
 Let's take a closer look at the policy:
+
 * The ``endpointSelector`` means that this policy will only apply to pods with labels ``class: mediabot, org:empire`` to have the egress access.
 * The first egress section uses ``toFQDNs: matchName`` specification to allow TCP port 443 egress to ``artii.herokuapp.com``.
 * The ``http`` section below the toFQDNs rule
-indicates that such connections should be parsed as HTTP, with a policy of ``{}`` which will allow all requests.
+  indicates that such connections should be parsed as HTTP, with a policy of ``{}`` which will allow all requests.
 * The ``terminatingTLS`` and ``originatingTLS`` sections indicate that TLS interception should be used to terminate the initial TLS connection
-from mediabot and initiate a new out-bound TLS connection to ``artii.herokuapp.com``.
+  from mediabot and initiate a new out-bound TLS connection to ``artii.herokuapp.com``.
 * The second egress section allows ``mediabot`` pods to access ``kube-dns`` service. Note that
-``rules: dns`` instructs Cilium to inspect and allow DNS lookups matching specified patterns.
-In this case, inspect and allow all DNS queries.
+  ``rules: dns`` instructs Cilium to inspect and allow DNS lookups matching specified patterns.
+  In this case, inspect and allow all DNS queries.
 
 Note that with this policy the ``mediabot`` doesn't have access to any internal cluster service other than ``kube-dns``
 and will have no access to any other external destinations either. Refer to :ref:`Network Policy`
@@ -316,9 +317,8 @@ Looking back at the cilium monitor window, you will see each individual HTTP req
 
 .. parsed-literal::
 
-    -> Request http from 1975 ([k8s:app=public-service k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=tenant-a]) to 0 ([reserved:world]), identity 56418->2, verdict Forwarded GET https://www.lyft.com/privacy => 0
-    -> Response http to 1975 ([k8s:app=public-service k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=tenant-a]) from 0 ([reserved:world]), identity 56418->2, verdict Forwarded GET https://www.lyft.com/privacy => 200
-
+    -> Request http from 2585 ([k8s:class=mediabot k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default]) to 0 ([reserved:world]), identity 24948->2, verdict Forwarded GET https://artii.herokuapp.com/fonts_list => 0
+    -> Response http to 2585 ([k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default k8s:class=mediabot k8s:org=empire]) from 0 ([reserved:world]), identity 24948->2, verdict Forwarded GET https://artii.herokuapp.com/fonts_list => 200
 
 Refer to :ref:`l4_policy` and :ref:`l7_policy` to learn more about Cilium L4 and L7 network policies.
 
@@ -329,4 +329,5 @@ Clean-up
 
    $ kubectl delete -f \ |SCM_WEB|\/examples/kubernetes-dns/dns-sw-app.yaml
    $ kubectl delete cnp l7-visibility-tls
-
+   $ kubectl delete secret -n kube-system tls-orig-data
+   $ kubectl delete secret -n kube-system artii-tls-data
diff --git a/Documentation/operations/system_requirements.rst b/Documentation/operations/system_requirements.rst
index 4521cdb08c..bd85593a63 100644
--- a/Documentation/operations/system_requirements.rst
+++ b/Documentation/operations/system_requirements.rst
@@ -176,6 +176,7 @@ Cilium Feature                           Minimum Kernel Version
 :ref:`host-services`                     >= 4.19.57, >= 5.1.16,  >= 5.2
 :ref:`kubeproxy-free`                    >= 4.19.57, >= 5.1.16,  >= 5.2
 :ref:`bandwidth-manager`                 >= 5.1
+:ref:`local-redirect-policy`             >= 4.19.57, >= 5.1.16,  >= 5.2
 Full support for :ref:`session-affinity` >= 5.7
 BPF-based proxy redirection              >= 5.7
 ======================================== ===============================
diff --git a/USERS.md b/USERS.md
index 4976476fcb..62f835b0de 100644
--- a/USERS.md
+++ b/USERS.md
@@ -60,6 +60,11 @@ Users (Alphabetically)
       U: Networking, NetworkPolicy, Services, IPVLAN
       L: https://www.alibabacloud.com/blog/how-does-alibaba-cloud-build-high-performance-cloud-native-pod-networks-in-production-environments_596590
 
+    * N: Canonical
+      D: Canonical's Kubernetes distribution microk8s uses Cilium as CNI plugin
+      U: Networking, NetworkPolicy, and Kubernetes services
+      L: https://microk8s.io/
+
     * N: CENGN - Centre of Excellence in Next Generation Networks
       D: CENGN is using Cilium in multiple clusters including production and development clusters (self-hosted k8s, On-premises)
       U: L3/L4/L7 network policies, Monitoring via Prometheus metrics & Hubble
diff --git a/api/v1/flow/flow.pb.go b/api/v1/flow/flow.pb.go
index 08c4e8e979..2969778663 100644
--- a/api/v1/flow/flow.pb.go
+++ b/api/v1/flow/flow.pb.go
@@ -329,6 +329,203 @@ func (Verdict) EnumDescriptor() ([]byte, []int) {
 	return file_flow_flow_proto_rawDescGZIP(), []int{4}
 }
 
+// Taken from pkg/monitor/api/drop.go. Note that non-drop reasons (i.e. values
+// less than api.DropMin) are not used here.
+type DropReason int32
+
+const (
+	// non-drop reasons
+	DropReason_DROP_REASON_UNKNOWN DropReason = 0
+	// drop reasons
+	DropReason_INVALID_SOURCE_MAC                                    DropReason = 130
+	DropReason_INVALID_DESTINATION_MAC                               DropReason = 131
+	DropReason_INVALID_SOURCE_IP                                     DropReason = 132
+	DropReason_POLICY_DENIED                                         DropReason = 133
+	DropReason_INVALID_PACKET_DROPPED                                DropReason = 134
+	DropReason_CT_TRUNCATED_OR_INVALID_HEADER                        DropReason = 135
+	DropReason_CT_MISSING_TCP_ACK_FLAG                               DropReason = 136
+	DropReason_CT_UNKNOWN_L4_PROTOCOL                                DropReason = 137
+	DropReason_CT_CANNOT_CREATE_ENTRY_FROM_PACKET                    DropReason = 138
+	DropReason_UNSUPPORTED_L3_PROTOCOL                               DropReason = 139
+	DropReason_MISSED_TAIL_CALL                                      DropReason = 140
+	DropReason_ERROR_WRITING_TO_PACKET                               DropReason = 141
+	DropReason_UNKNOWN_L4_PROTOCOL                                   DropReason = 142
+	DropReason_UNKNOWN_ICMPV4_CODE                                   DropReason = 143
+	DropReason_UNKNOWN_ICMPV4_TYPE                                   DropReason = 144
+	DropReason_UNKNOWN_ICMPV6_CODE                                   DropReason = 145
+	DropReason_UNKNOWN_ICMPV6_TYPE                                   DropReason = 146
+	DropReason_ERROR_RETRIEVING_TUNNEL_KEY                           DropReason = 147
+	DropReason_ERROR_RETRIEVING_TUNNEL_OPTIONS                       DropReason = 148
+	DropReason_INVALID_GENEVE_OPTION                                 DropReason = 149
+	DropReason_UNKNOWN_L3_TARGET_ADDRESS                             DropReason = 150
+	DropReason_STALE_OR_UNROUTABLE_IP                                DropReason = 151
+	DropReason_NO_MATCHING_LOCAL_CONTAINER_FOUND                     DropReason = 152
+	DropReason_ERROR_WHILE_CORRECTING_L3_CHECKSUM                    DropReason = 153
+	DropReason_ERROR_WHILE_CORRECTING_L4_CHECKSUM                    DropReason = 154
+	DropReason_CT_MAP_INSERTION_FAILED                               DropReason = 155
+	DropReason_INVALID_IPV6_EXTENSION_HEADER                         DropReason = 156
+	DropReason_IP_FRAGMENTATION_NOT_SUPPORTED                        DropReason = 157
+	DropReason_SERVICE_BACKEND_NOT_FOUND                             DropReason = 158
+	DropReason_NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT                   DropReason = 160
+	DropReason_FAILED_TO_INSERT_INTO_PROXYMAP                        DropReason = 161
+	DropReason_REACHED_EDT_RATE_LIMITING_DROP_HORIZON                DropReason = 162
+	DropReason_UNKNOWN_CONNECTION_TRACKING_STATE                     DropReason = 163
+	DropReason_LOCAL_HOST_IS_UNREACHABLE                             DropReason = 164
+	DropReason_NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION DropReason = 165
+	DropReason_UNSUPPORTED_L2_PROTOCOL                               DropReason = 166
+	DropReason_NO_MAPPING_FOR_NAT_MASQUERADE                         DropReason = 167
+	DropReason_UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE               DropReason = 168
+	DropReason_FIB_LOOKUP_FAILED                                     DropReason = 169
+	DropReason_ENCAPSULATION_TRAFFIC_IS_PROHIBITED                   DropReason = 170
+	DropReason_INVALID_IDENTITY                                      DropReason = 171
+	DropReason_UNKNOWN_SENDER                                        DropReason = 172
+	DropReason_NAT_NOT_NEEDED                                        DropReason = 173
+	DropReason_IS_A_CLUSTERIP                                        DropReason = 174
+	DropReason_FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND             DropReason = 175
+	DropReason_FORBIDDEN_ICMPV6_MESSAGE                              DropReason = 176
+	DropReason_DENIED_BY_LB_SRC_RANGE_CHECK                          DropReason = 177
+	DropReason_SOCKET_LOOKUP_FAILED                                  DropReason = 178
+	DropReason_SOCKET_ASSIGN_FAILED                                  DropReason = 179
+	DropReason_PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL          DropReason = 180
+)
+
+// Enum value maps for DropReason.
+var (
+	DropReason_name = map[int32]string{
+		0:   "DROP_REASON_UNKNOWN",
+		130: "INVALID_SOURCE_MAC",
+		131: "INVALID_DESTINATION_MAC",
+		132: "INVALID_SOURCE_IP",
+		133: "POLICY_DENIED",
+		134: "INVALID_PACKET_DROPPED",
+		135: "CT_TRUNCATED_OR_INVALID_HEADER",
+		136: "CT_MISSING_TCP_ACK_FLAG",
+		137: "CT_UNKNOWN_L4_PROTOCOL",
+		138: "CT_CANNOT_CREATE_ENTRY_FROM_PACKET",
+		139: "UNSUPPORTED_L3_PROTOCOL",
+		140: "MISSED_TAIL_CALL",
+		141: "ERROR_WRITING_TO_PACKET",
+		142: "UNKNOWN_L4_PROTOCOL",
+		143: "UNKNOWN_ICMPV4_CODE",
+		144: "UNKNOWN_ICMPV4_TYPE",
+		145: "UNKNOWN_ICMPV6_CODE",
+		146: "UNKNOWN_ICMPV6_TYPE",
+		147: "ERROR_RETRIEVING_TUNNEL_KEY",
+		148: "ERROR_RETRIEVING_TUNNEL_OPTIONS",
+		149: "INVALID_GENEVE_OPTION",
+		150: "UNKNOWN_L3_TARGET_ADDRESS",
+		151: "STALE_OR_UNROUTABLE_IP",
+		152: "NO_MATCHING_LOCAL_CONTAINER_FOUND",
+		153: "ERROR_WHILE_CORRECTING_L3_CHECKSUM",
+		154: "ERROR_WHILE_CORRECTING_L4_CHECKSUM",
+		155: "CT_MAP_INSERTION_FAILED",
+		156: "INVALID_IPV6_EXTENSION_HEADER",
+		157: "IP_FRAGMENTATION_NOT_SUPPORTED",
+		158: "SERVICE_BACKEND_NOT_FOUND",
+		160: "NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT",
+		161: "FAILED_TO_INSERT_INTO_PROXYMAP",
+		162: "REACHED_EDT_RATE_LIMITING_DROP_HORIZON",
+		163: "UNKNOWN_CONNECTION_TRACKING_STATE",
+		164: "LOCAL_HOST_IS_UNREACHABLE",
+		165: "NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION",
+		166: "UNSUPPORTED_L2_PROTOCOL",
+		167: "NO_MAPPING_FOR_NAT_MASQUERADE",
+		168: "UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE",
+		169: "FIB_LOOKUP_FAILED",
+		170: "ENCAPSULATION_TRAFFIC_IS_PROHIBITED",
+		171: "INVALID_IDENTITY",
+		172: "UNKNOWN_SENDER",
+		173: "NAT_NOT_NEEDED",
+		174: "IS_A_CLUSTERIP",
+		175: "FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND",
+		176: "FORBIDDEN_ICMPV6_MESSAGE",
+		177: "DENIED_BY_LB_SRC_RANGE_CHECK",
+		178: "SOCKET_LOOKUP_FAILED",
+		179: "SOCKET_ASSIGN_FAILED",
+		180: "PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL",
+	}
+	DropReason_value = map[string]int32{
+		"DROP_REASON_UNKNOWN":                                   0,
+		"INVALID_SOURCE_MAC":                                    130,
+		"INVALID_DESTINATION_MAC":                               131,
+		"INVALID_SOURCE_IP":                                     132,
+		"POLICY_DENIED":                                         133,
+		"INVALID_PACKET_DROPPED":                                134,
+		"CT_TRUNCATED_OR_INVALID_HEADER":                        135,
+		"CT_MISSING_TCP_ACK_FLAG":                               136,
+		"CT_UNKNOWN_L4_PROTOCOL":                                137,
+		"CT_CANNOT_CREATE_ENTRY_FROM_PACKET":                    138,
+		"UNSUPPORTED_L3_PROTOCOL":                               139,
+		"MISSED_TAIL_CALL":                                      140,
+		"ERROR_WRITING_TO_PACKET":                               141,
+		"UNKNOWN_L4_PROTOCOL":                                   142,
+		"UNKNOWN_ICMPV4_CODE":                                   143,
+		"UNKNOWN_ICMPV4_TYPE":                                   144,
+		"UNKNOWN_ICMPV6_CODE":                                   145,
+		"UNKNOWN_ICMPV6_TYPE":                                   146,
+		"ERROR_RETRIEVING_TUNNEL_KEY":                           147,
+		"ERROR_RETRIEVING_TUNNEL_OPTIONS":                       148,
+		"INVALID_GENEVE_OPTION":                                 149,
+		"UNKNOWN_L3_TARGET_ADDRESS":                             150,
+		"STALE_OR_UNROUTABLE_IP":                                151,
+		"NO_MATCHING_LOCAL_CONTAINER_FOUND":                     152,
+		"ERROR_WHILE_CORRECTING_L3_CHECKSUM":                    153,
+		"ERROR_WHILE_CORRECTING_L4_CHECKSUM":                    154,
+		"CT_MAP_INSERTION_FAILED":                               155,
+		"INVALID_IPV6_EXTENSION_HEADER":                         156,
+		"IP_FRAGMENTATION_NOT_SUPPORTED":                        157,
+		"SERVICE_BACKEND_NOT_FOUND":                             158,
+		"NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT":                   160,
+		"FAILED_TO_INSERT_INTO_PROXYMAP":                        161,
+		"REACHED_EDT_RATE_LIMITING_DROP_HORIZON":                162,
+		"UNKNOWN_CONNECTION_TRACKING_STATE":                     163,
+		"LOCAL_HOST_IS_UNREACHABLE":                             164,
+		"NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION": 165,
+		"UNSUPPORTED_L2_PROTOCOL":                               166,
+		"NO_MAPPING_FOR_NAT_MASQUERADE":                         167,
+		"UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE":               168,
+		"FIB_LOOKUP_FAILED":                                     169,
+		"ENCAPSULATION_TRAFFIC_IS_PROHIBITED":                   170,
+		"INVALID_IDENTITY":                                      171,
+		"UNKNOWN_SENDER":                                        172,
+		"NAT_NOT_NEEDED":                                        173,
+		"IS_A_CLUSTERIP":                                        174,
+		"FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND":             175,
+		"FORBIDDEN_ICMPV6_MESSAGE":                              176,
+		"DENIED_BY_LB_SRC_RANGE_CHECK":                          177,
+		"SOCKET_LOOKUP_FAILED":                                  178,
+		"SOCKET_ASSIGN_FAILED":                                  179,
+		"PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL":          180,
+	}
+)
+
+func (x DropReason) Enum() *DropReason {
+	p := new(DropReason)
+	*p = x
+	return p
+}
+
+func (x DropReason) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (DropReason) Descriptor() protoreflect.EnumDescriptor {
+	return file_flow_flow_proto_enumTypes[5].Descriptor()
+}
+
+func (DropReason) Type() protoreflect.EnumType {
+	return &file_flow_flow_proto_enumTypes[5]
+}
+
+func (x DropReason) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use DropReason.Descriptor instead.
+func (DropReason) EnumDescriptor() ([]byte, []int) {
+	return file_flow_flow_proto_rawDescGZIP(), []int{5}
+}
+
 type TrafficDirection int32
 
 const (
@@ -362,11 +559,11 @@ func (x TrafficDirection) String() string {
 }
 
 func (TrafficDirection) Descriptor() protoreflect.EnumDescriptor {
-	return file_flow_flow_proto_enumTypes[5].Descriptor()
+	return file_flow_flow_proto_enumTypes[6].Descriptor()
 }
 
 func (TrafficDirection) Type() protoreflect.EnumType {
-	return &file_flow_flow_proto_enumTypes[5]
+	return &file_flow_flow_proto_enumTypes[6]
 }
 
 func (x TrafficDirection) Number() protoreflect.EnumNumber {
@@ -375,7 +572,7 @@ func (x TrafficDirection) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use TrafficDirection.Descriptor instead.
 func (TrafficDirection) EnumDescriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{5}
+	return file_flow_flow_proto_rawDescGZIP(), []int{6}
 }
 
 // EventType are constants are based on the ones from <linux/perf_event.h>.
@@ -414,11 +611,11 @@ func (x EventType) String() string {
 }
 
 func (EventType) Descriptor() protoreflect.EnumDescriptor {
-	return file_flow_flow_proto_enumTypes[6].Descriptor()
+	return file_flow_flow_proto_enumTypes[7].Descriptor()
 }
 
 func (EventType) Type() protoreflect.EnumType {
-	return &file_flow_flow_proto_enumTypes[6]
+	return &file_flow_flow_proto_enumTypes[7]
 }
 
 func (x EventType) Number() protoreflect.EnumNumber {
@@ -427,7 +624,7 @@ func (x EventType) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use EventType.Descriptor instead.
 func (EventType) EnumDescriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{6}
+	return file_flow_flow_proto_rawDescGZIP(), []int{7}
 }
 
 type LostEventSource int32
@@ -469,11 +666,11 @@ func (x LostEventSource) String() string {
 }
 
 func (LostEventSource) Descriptor() protoreflect.EnumDescriptor {
-	return file_flow_flow_proto_enumTypes[7].Descriptor()
+	return file_flow_flow_proto_enumTypes[8].Descriptor()
 }
 
 func (LostEventSource) Type() protoreflect.EnumType {
-	return &file_flow_flow_proto_enumTypes[7]
+	return &file_flow_flow_proto_enumTypes[8]
 }
 
 func (x LostEventSource) Number() protoreflect.EnumNumber {
@@ -482,7 +679,7 @@ func (x LostEventSource) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use LostEventSource.Descriptor instead.
 func (LostEventSource) EnumDescriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{7}
+	return file_flow_flow_proto_rawDescGZIP(), []int{8}
 }
 
 type Flow struct {
@@ -493,6 +690,9 @@ type Flow struct {
 	Time    *timestamp.Timestamp `protobuf:"bytes,1,opt,name=time,proto3" json:"time,omitempty"`
 	Verdict Verdict              `protobuf:"varint,2,opt,name=verdict,proto3,enum=flow.Verdict" json:"verdict,omitempty"`
 	// only applicable to Verdict = DROPPED.
+	// deprecated in favor of drop_reason_desc.
+	//
+	// Deprecated: Do not use.
 	DropReason uint32 `protobuf:"varint,3,opt,name=drop_reason,json=dropReason,proto3" json:"drop_reason,omitempty"`
 	// l2
 	Ethernet *Ethernet `protobuf:"bytes,4,opt,name=ethernet,proto3" json:"ethernet,omitempty"`
@@ -525,6 +725,8 @@ type Flow struct {
 	PolicyMatchType uint32 `protobuf:"varint,23,opt,name=policy_match_type,json=policyMatchType,proto3" json:"policy_match_type,omitempty"`
 	// Only applicable to cilium trace notifications, blank for other types.
 	TraceObservationPoint TraceObservationPoint `protobuf:"varint,24,opt,name=trace_observation_point,json=traceObservationPoint,proto3,enum=flow.TraceObservationPoint" json:"trace_observation_point,omitempty"`
+	// only applicable to Verdict = DROPPED.
+	DropReasonDesc DropReason `protobuf:"varint,25,opt,name=drop_reason_desc,json=dropReasonDesc,proto3,enum=flow.DropReason" json:"drop_reason_desc,omitempty"`
 	// This is a temporary workaround to support summary field for pb.Flow without
 	// duplicating logic from the old parser. This field will be removed once we
 	// fully migrate to the new parser.
@@ -579,6 +781,7 @@ func (x *Flow) GetVerdict() Verdict {
 	return Verdict_VERDICT_UNKNOWN
 }
 
+// Deprecated: Do not use.
 func (x *Flow) GetDropReason() uint32 {
 	if x != nil {
 		return x.DropReason
@@ -705,6 +908,13 @@ func (x *Flow) GetTraceObservationPoint() TraceObservationPoint {
 	return TraceObservationPoint_UNKNOWN_POINT
 }
 
+func (x *Flow) GetDropReasonDesc() DropReason {
+	if x != nil {
+		return x.DropReasonDesc
+	}
+	return DropReason_DROP_REASON_UNKNOWN
+}
+
 // Deprecated: Do not use.
 func (x *Flow) GetSummary() string {
 	if x != nil {
@@ -1861,100 +2071,6 @@ func (x *FlowFilter) GetHttpPath() []string {
 	return nil
 }
 
-// Payload is the payload, i.e. the data the monitor returns.
-type Payload struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// type is the event type perf returns.
-	Type EventType `protobuf:"varint,1,opt,name=type,proto3,enum=flow.EventType" json:"type,omitempty"`
-	// CPU is the CPU id for this event.
-	CPU int32 `protobuf:"varint,2,opt,name=CPU,proto3" json:"CPU,omitempty"`
-	// lost is the number of events lost for a given CPU.
-	Lost uint64 `protobuf:"varint,3,opt,name=lost,proto3" json:"lost,omitempty"`
-	// data is the sample data returned by perf.
-	Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
-	// time when the event was observed
-	Time *timestamp.Timestamp `protobuf:"bytes,5,opt,name=time,proto3" json:"time,omitempty"`
-	// the host name from which the payload was captured.
-	HostName string `protobuf:"bytes,6,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"`
-}
-
-func (x *Payload) Reset() {
-	*x = Payload{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[14]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Payload) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Payload) ProtoMessage() {}
-
-func (x *Payload) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[14]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Payload.ProtoReflect.Descriptor instead.
-func (*Payload) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{14}
-}
-
-func (x *Payload) GetType() EventType {
-	if x != nil {
-		return x.Type
-	}
-	return EventType_UNKNOWN
-}
-
-func (x *Payload) GetCPU() int32 {
-	if x != nil {
-		return x.CPU
-	}
-	return 0
-}
-
-func (x *Payload) GetLost() uint64 {
-	if x != nil {
-		return x.Lost
-	}
-	return 0
-}
-
-func (x *Payload) GetData() []byte {
-	if x != nil {
-		return x.Data
-	}
-	return nil
-}
-
-func (x *Payload) GetTime() *timestamp.Timestamp {
-	if x != nil {
-		return x.Time
-	}
-	return nil
-}
-
-func (x *Payload) GetHostName() string {
-	if x != nil {
-		return x.HostName
-	}
-	return ""
-}
-
 // DNS flow. This is basically directly mapped from Cilium's LogRecordDNS:
 //     https://github.com/cilium/cilium/blob/04f3889d627774f79e56d14ddbc165b3169e2d01/pkg/proxy/accesslog/record.go#L264
 type DNS struct {
@@ -1987,7 +2103,7 @@ type DNS struct {
 func (x *DNS) Reset() {
 	*x = DNS{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[15]
+		mi := &file_flow_flow_proto_msgTypes[14]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2000,7 +2116,7 @@ func (x *DNS) String() string {
 func (*DNS) ProtoMessage() {}
 
 func (x *DNS) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[15]
+	mi := &file_flow_flow_proto_msgTypes[14]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2013,7 +2129,7 @@ func (x *DNS) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DNS.ProtoReflect.Descriptor instead.
 func (*DNS) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{15}
+	return file_flow_flow_proto_rawDescGZIP(), []int{14}
 }
 
 func (x *DNS) GetQuery() string {
@@ -2084,7 +2200,7 @@ type HTTPHeader struct {
 func (x *HTTPHeader) Reset() {
 	*x = HTTPHeader{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[16]
+		mi := &file_flow_flow_proto_msgTypes[15]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2097,7 +2213,7 @@ func (x *HTTPHeader) String() string {
 func (*HTTPHeader) ProtoMessage() {}
 
 func (x *HTTPHeader) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[16]
+	mi := &file_flow_flow_proto_msgTypes[15]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2110,7 +2226,7 @@ func (x *HTTPHeader) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use HTTPHeader.ProtoReflect.Descriptor instead.
 func (*HTTPHeader) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{16}
+	return file_flow_flow_proto_rawDescGZIP(), []int{15}
 }
 
 func (x *HTTPHeader) GetKey() string {
@@ -2144,7 +2260,7 @@ type HTTP struct {
 func (x *HTTP) Reset() {
 	*x = HTTP{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[17]
+		mi := &file_flow_flow_proto_msgTypes[16]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2157,7 +2273,7 @@ func (x *HTTP) String() string {
 func (*HTTP) ProtoMessage() {}
 
 func (x *HTTP) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[17]
+	mi := &file_flow_flow_proto_msgTypes[16]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2170,7 +2286,7 @@ func (x *HTTP) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use HTTP.ProtoReflect.Descriptor instead.
 func (*HTTP) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{17}
+	return file_flow_flow_proto_rawDescGZIP(), []int{16}
 }
 
 func (x *HTTP) GetCode() uint32 {
@@ -2225,7 +2341,7 @@ type Kafka struct {
 func (x *Kafka) Reset() {
 	*x = Kafka{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[18]
+		mi := &file_flow_flow_proto_msgTypes[17]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2238,7 +2354,7 @@ func (x *Kafka) String() string {
 func (*Kafka) ProtoMessage() {}
 
 func (x *Kafka) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[18]
+	mi := &file_flow_flow_proto_msgTypes[17]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2251,7 +2367,7 @@ func (x *Kafka) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Kafka.ProtoReflect.Descriptor instead.
 func (*Kafka) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{18}
+	return file_flow_flow_proto_rawDescGZIP(), []int{17}
 }
 
 func (x *Kafka) GetErrorCode() int32 {
@@ -2301,7 +2417,7 @@ type Service struct {
 func (x *Service) Reset() {
 	*x = Service{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[19]
+		mi := &file_flow_flow_proto_msgTypes[18]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2314,7 +2430,7 @@ func (x *Service) String() string {
 func (*Service) ProtoMessage() {}
 
 func (x *Service) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[19]
+	mi := &file_flow_flow_proto_msgTypes[18]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2327,7 +2443,7 @@ func (x *Service) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Service.ProtoReflect.Descriptor instead.
 func (*Service) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{19}
+	return file_flow_flow_proto_rawDescGZIP(), []int{18}
 }
 
 func (x *Service) GetName() string {
@@ -2363,7 +2479,7 @@ type LostEvent struct {
 func (x *LostEvent) Reset() {
 	*x = LostEvent{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_flow_flow_proto_msgTypes[20]
+		mi := &file_flow_flow_proto_msgTypes[19]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2376,7 +2492,7 @@ func (x *LostEvent) String() string {
 func (*LostEvent) ProtoMessage() {}
 
 func (x *LostEvent) ProtoReflect() protoreflect.Message {
-	mi := &file_flow_flow_proto_msgTypes[20]
+	mi := &file_flow_flow_proto_msgTypes[19]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2389,7 +2505,7 @@ func (x *LostEvent) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use LostEvent.ProtoReflect.Descriptor instead.
 func (*LostEvent) Descriptor() ([]byte, []int) {
-	return file_flow_flow_proto_rawDescGZIP(), []int{20}
+	return file_flow_flow_proto_rawDescGZIP(), []int{19}
 }
 
 func (x *LostEvent) GetSource() LostEventSource {
@@ -2421,62 +2537,66 @@ var file_flow_flow_proto_rawDesc = []byte{
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72,
 	0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x95, 0x07, 0x0a, 0x04, 0x46, 0x6c, 0x6f,
+	0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd5, 0x07, 0x0a, 0x04, 0x46, 0x6c, 0x6f,
 	0x77, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
 	0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
 	0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d,
 	0x65, 0x12, 0x27, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x64, 0x69, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01,
 	0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x56, 0x65, 0x72, 0x64, 0x69, 0x63,
-	0x74, 0x52, 0x07, 0x76, 0x65, 0x72, 0x64, 0x69, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x72,
-	0x6f, 0x70, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
-	0x0a, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x08, 0x65,
-	0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
-	0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x52, 0x08, 0x65,
-	0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x49,
-	0x50, 0x12, 0x1c, 0x0a, 0x02, 0x6c, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
-	0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x34, 0x52, 0x02, 0x6c, 0x34, 0x12,
-	0x26, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52,
-	0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69,
-	0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66,
-	0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x64, 0x65,
-	0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x04, 0x54, 0x79, 0x70,
-	0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46,
-	0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a,
-	0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2b, 0x0a,
-	0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d,
-	0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x02, 0x6c, 0x37,
-	0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x4c, 0x61,
-	0x79, 0x65, 0x72, 0x37, 0x52, 0x02, 0x6c, 0x37, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c,
-	0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x34,
-	0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x13, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x43, 0x69, 0x6c, 0x69, 0x75, 0x6d,
-	0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73,
-	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x66,
-	0x6c, 0x6f, 0x77, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x13, 0x64, 0x65,
-	0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
-	0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x53,
-	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x74, 0x72,
-	0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18,
-	0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x72, 0x61,
-	0x66, 0x66, 0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x74,
-	0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x2a, 0x0a, 0x11, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f,
-	0x74, 0x79, 0x70, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x70, 0x6f, 0x6c, 0x69,
-	0x63, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x53, 0x0a, 0x17, 0x74,
-	0x72, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x66,
-	0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x15, 0x74, 0x72, 0x61, 0x63, 0x65,
-	0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74,
+	0x74, 0x52, 0x07, 0x76, 0x65, 0x72, 0x64, 0x69, 0x63, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x64, 0x72,
+	0x6f, 0x70, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42,
+	0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
+	0x2a, 0x0a, 0x08, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65,
+	0x74, 0x52, 0x08, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x49,
+	0x50, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x49,
+	0x50, 0x52, 0x02, 0x49, 0x50, 0x12, 0x1c, 0x0a, 0x02, 0x6c, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x0c, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x34, 0x52,
+	0x02, 0x6c, 0x34, 0x12, 0x26, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
+	0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x0b, 0x64,
+	0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
+	0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a,
+	0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x66, 0x6c,
+	0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70,
+	0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21,
+	0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0d,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65,
+	0x73, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65,
+	0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1c,
+	0x0a, 0x02, 0x6c, 0x37, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x66, 0x6c, 0x6f,
+	0x77, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x37, 0x52, 0x02, 0x6c, 0x37, 0x12, 0x14, 0x0a, 0x05,
+	0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x70,
+	0x6c, 0x79, 0x12, 0x34, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65,
+	0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x43, 0x69,
+	0x6c, 0x69, 0x75, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65,
+	0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x0d, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
+	0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e,
+	0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65,
+	0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x66, 0x6c,
+	0x6f, 0x77, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43,
+	0x0a, 0x11, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x66, 0x6c, 0x6f, 0x77,
+	0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
+	0x6e, 0x52, 0x10, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6d, 0x61,
+	0x74, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f,
+	0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12,
+	0x53, 0x0a, 0x17, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x1b, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4f, 0x62, 0x73,
+	0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x15, 0x74,
+	0x72, 0x61, 0x63, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
+	0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x10, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x61,
+	0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10,
+	0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
+	0x52, 0x0e, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x44, 0x65, 0x73, 0x63,
 	0x12, 0x1e, 0x0a, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0xa0, 0x8d, 0x06, 0x20,
 	0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
 	0x22, 0xa2, 0x01, 0x0a, 0x06, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x34, 0x12, 0x1d, 0x0a, 0x03, 0x54,
@@ -2616,115 +2736,208 @@ var file_flow_flow_proto_rawDesc = []byte{
 	0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x15, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,
 	0x68, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74,
 	0x74, 0x70, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x68,
-	0x74, 0x74, 0x70, 0x50, 0x61, 0x74, 0x68, 0x22, 0xb5, 0x01, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6c,
-	0x6f, 0x61, 0x64, 0x12, 0x23, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79,
-	0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x43, 0x50, 0x55, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f,
-	0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x6c, 0x6f, 0x73, 0x74, 0x12, 0x12,
-	0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61,
-	0x74, 0x61, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69,
-	0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22,
-	0xce, 0x01, 0x0a, 0x03, 0x44, 0x4e, 0x53, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x10, 0x0a,
-	0x03, 0x69, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x70, 0x73, 0x12,
-	0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x74, 0x74,
-	0x6c, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x06, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73,
-	0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x63, 0x6f, 0x64,
-	0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16,
-	0x0a, 0x06, 0x71, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06,
-	0x71, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x72, 0x74, 0x79, 0x70, 0x65,
-	0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x72, 0x74, 0x79, 0x70, 0x65, 0x73,
-	0x22, 0x34, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10,
-	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
-	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x12,
-	0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63,
-	0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75,
-	0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a,
-	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x2a, 0x0a, 0x07, 0x68, 0x65, 0x61,
-	0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x6f,
-	0x77, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65,
-	0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x05, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x12,
-	0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f,
-	0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12,
-	0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72,
-	0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
-	0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
-	0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x3b, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
-	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x09, 0x4c, 0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74,
-	0x12, 0x2d, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x15, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x4c, 0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e,
-	0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
-	0x26, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6c, 0x6f,
-	0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x45, 0x76, 0x65,
-	0x6e, 0x74, 0x73, 0x4c, 0x6f, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x52, 0x03, 0x63, 0x70, 0x75, 0x2a, 0x2f, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x54, 0x79,
-	0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x59,
-	0x50, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x33, 0x5f, 0x4c, 0x34, 0x10, 0x01, 0x12,
-	0x06, 0x0a, 0x02, 0x4c, 0x37, 0x10, 0x02, 0x2a, 0xea, 0x01, 0x0a, 0x15, 0x54, 0x72, 0x61, 0x63,
-	0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e,
-	0x74, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x4f, 0x49,
-	0x4e, 0x54, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59,
-	0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x4f, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x12,
-	0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0e, 0x0a,
-	0x0a, 0x54, 0x4f, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x04, 0x12, 0x0f, 0x0a,
-	0x0b, 0x54, 0x4f, 0x5f, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0x65, 0x12, 0x11,
-	0x0a, 0x0d, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10,
-	0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10,
-	0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x07,
-	0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10, 0x08,
-	0x12, 0x10, 0x0a, 0x0c, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59,
-	0x10, 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f,
-	0x52, 0x4b, 0x10, 0x0a, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f,
-	0x52, 0x4b, 0x10, 0x0b, 0x2a, 0x48, 0x0a, 0x0a, 0x4c, 0x37, 0x46, 0x6c, 0x6f, 0x77, 0x54, 0x79,
-	0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c, 0x37,
-	0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x51, 0x55, 0x45,
-	0x53, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45,
-	0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x41, 0x4d, 0x50, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x30,
-	0x0a, 0x09, 0x49, 0x50, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x49,
-	0x50, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,
-	0x49, 0x50, 0x76, 0x34, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x10, 0x02,
-	0x2a, 0x45, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x64, 0x69, 0x63, 0x74, 0x12, 0x13, 0x0a, 0x0f, 0x56,
-	0x45, 0x52, 0x44, 0x49, 0x43, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
-	0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12,
-	0x0b, 0x0a, 0x07, 0x44, 0x52, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05,
-	0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x2a, 0x4a, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x66, 0x66,
-	0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x54,
-	0x52, 0x41, 0x46, 0x46, 0x49, 0x43, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
-	0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e,
-	0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53,
-	0x53, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
-	0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a,
-	0x0b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x10, 0x09, 0x12, 0x0e,
-	0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x67,
-	0x0a, 0x0f, 0x4c, 0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c, 0x4f, 0x53,
-	0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x00,
-	0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x46, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x52,
-	0x49, 0x4e, 0x47, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15,
-	0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x53, 0x5f,
-	0x51, 0x55, 0x45, 0x55, 0x45, 0x10, 0x02, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
-	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x69, 0x6c, 0x69, 0x75, 0x6d, 0x2f, 0x63, 0x69, 0x6c,
-	0x69, 0x75, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x6c, 0x6f, 0x77, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x74, 0x74, 0x70, 0x50, 0x61, 0x74, 0x68, 0x22, 0xce, 0x01, 0x0a, 0x03, 0x44, 0x4e, 0x53, 0x12,
+	0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x03, 0x69, 0x70, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6e, 0x61, 0x6d, 0x65,
+	0x73, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f,
+	0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x12, 0x14, 0x0a, 0x05, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x05, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x71, 0x74, 0x79, 0x70, 0x65, 0x73,
+	0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x71, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x18,
+	0x0a, 0x07, 0x72, 0x72, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x07, 0x72, 0x72, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50,
+	0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8c,
+	0x01, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d,
+	0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74,
+	0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
+	0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
+	0x6c, 0x12, 0x2a, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65,
+	0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0x9d, 0x01,
+	0x0a, 0x05, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72,
+	0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x65, 0x72, 0x72,
+	0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x70, 0x69,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b,
+	0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79,
+	0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+	0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x3b, 0x0a,
+	0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x09, 0x4c,
+	0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e,
+	0x4c, 0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
+	0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x5f, 0x65,
+	0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6c, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x4c, 0x6f, 0x73, 0x74, 0x12,
+	0x2d, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49,
+	0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x63, 0x70, 0x75, 0x2a, 0x2f,
+	0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e,
+	0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
+	0x4c, 0x33, 0x5f, 0x4c, 0x34, 0x10, 0x01, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x37, 0x10, 0x02, 0x2a,
+	0xea, 0x01, 0x0a, 0x15, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b,
+	0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08,
+	0x54, 0x4f, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x4f,
+	0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x4f, 0x5f, 0x53, 0x54,
+	0x41, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x4f, 0x5f, 0x4f, 0x56, 0x45, 0x52,
+	0x4c, 0x41, 0x59, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x4f, 0x5f, 0x45, 0x4e, 0x44, 0x50,
+	0x4f, 0x49, 0x4e, 0x54, 0x10, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x45,
+	0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x4f,
+	0x4d, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x4f,
+	0x4d, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x07, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x52, 0x4f, 0x4d,
+	0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10, 0x08, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x52, 0x4f, 0x4d,
+	0x5f, 0x4f, 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x52,
+	0x4f, 0x4d, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x0a, 0x12, 0x0e, 0x0a, 0x0a,
+	0x54, 0x4f, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x0b, 0x2a, 0x48, 0x0a, 0x0a,
+	0x4c, 0x37, 0x46, 0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e,
+	0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c, 0x37, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12,
+	0x0b, 0x0a, 0x07, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08,
+	0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x41,
+	0x4d, 0x50, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x30, 0x0a, 0x09, 0x49, 0x50, 0x56, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x50, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53,
+	0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x34, 0x10, 0x01, 0x12, 0x08,
+	0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x64,
+	0x69, 0x63, 0x74, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x45, 0x52, 0x44, 0x49, 0x43, 0x54, 0x5f, 0x55,
+	0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x57,
+	0x41, 0x52, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x52, 0x4f, 0x50, 0x50,
+	0x45, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x2a,
+	0x88, 0x0d, 0x0a, 0x0a, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17,
+	0x0a, 0x13, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e,
+	0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c,
+	0x49, 0x44, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x10, 0x82, 0x01,
+	0x12, 0x1c, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x44, 0x45, 0x53, 0x54,
+	0x49, 0x4e, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x41, 0x43, 0x10, 0x83, 0x01, 0x12, 0x16,
+	0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45,
+	0x5f, 0x49, 0x50, 0x10, 0x84, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59,
+	0x5f, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, 0x85, 0x01, 0x12, 0x1b, 0x0a, 0x16, 0x49, 0x4e,
+	0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x44, 0x52, 0x4f,
+	0x50, 0x50, 0x45, 0x44, 0x10, 0x86, 0x01, 0x12, 0x23, 0x0a, 0x1e, 0x43, 0x54, 0x5f, 0x54, 0x52,
+	0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c,
+	0x49, 0x44, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x87, 0x01, 0x12, 0x1c, 0x0a, 0x17,
+	0x43, 0x54, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x43, 0x50, 0x5f, 0x41,
+	0x43, 0x4b, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x10, 0x88, 0x01, 0x12, 0x1b, 0x0a, 0x16, 0x43, 0x54,
+	0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c, 0x34, 0x5f, 0x50, 0x52, 0x4f, 0x54,
+	0x4f, 0x43, 0x4f, 0x4c, 0x10, 0x89, 0x01, 0x12, 0x27, 0x0a, 0x22, 0x43, 0x54, 0x5f, 0x43, 0x41,
+	0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x4e, 0x54, 0x52,
+	0x59, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x10, 0x8a, 0x01,
+	0x12, 0x1c, 0x0a, 0x17, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f,
+	0x4c, 0x33, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x10, 0x8b, 0x01, 0x12, 0x15,
+	0x0a, 0x10, 0x4d, 0x49, 0x53, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x49, 0x4c, 0x5f, 0x43, 0x41,
+	0x4c, 0x4c, 0x10, 0x8c, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57,
+	0x52, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x4f, 0x5f, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54,
+	0x10, 0x8d, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c,
+	0x34, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x10, 0x8e, 0x01, 0x12, 0x18, 0x0a,
+	0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x43, 0x4d, 0x50, 0x56, 0x34, 0x5f,
+	0x43, 0x4f, 0x44, 0x45, 0x10, 0x8f, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
+	0x57, 0x4e, 0x5f, 0x49, 0x43, 0x4d, 0x50, 0x56, 0x34, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x90,
+	0x01, 0x12, 0x18, 0x0a, 0x13, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x43, 0x4d,
+	0x50, 0x56, 0x36, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x91, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x55,
+	0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x43, 0x4d, 0x50, 0x56, 0x36, 0x5f, 0x54, 0x59,
+	0x50, 0x45, 0x10, 0x92, 0x01, 0x12, 0x20, 0x0a, 0x1b, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52,
+	0x45, 0x54, 0x52, 0x49, 0x45, 0x56, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x55, 0x4e, 0x4e, 0x45, 0x4c,
+	0x5f, 0x4b, 0x45, 0x59, 0x10, 0x93, 0x01, 0x12, 0x24, 0x0a, 0x1f, 0x45, 0x52, 0x52, 0x4f, 0x52,
+	0x5f, 0x52, 0x45, 0x54, 0x52, 0x49, 0x45, 0x56, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x55, 0x4e, 0x4e,
+	0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x94, 0x01, 0x12, 0x1a, 0x0a,
+	0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x56, 0x45, 0x5f,
+	0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x95, 0x01, 0x12, 0x1e, 0x0a, 0x19, 0x55, 0x4e, 0x4b,
+	0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4c, 0x33, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x41,
+	0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x96, 0x01, 0x12, 0x1b, 0x0a, 0x16, 0x53, 0x54, 0x41,
+	0x4c, 0x45, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x52, 0x4f, 0x55, 0x54, 0x41, 0x42, 0x4c, 0x45,
+	0x5f, 0x49, 0x50, 0x10, 0x97, 0x01, 0x12, 0x26, 0x0a, 0x21, 0x4e, 0x4f, 0x5f, 0x4d, 0x41, 0x54,
+	0x43, 0x48, 0x49, 0x4e, 0x47, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x54,
+	0x41, 0x49, 0x4e, 0x45, 0x52, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x98, 0x01, 0x12, 0x27,
+	0x0a, 0x22, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x48, 0x49, 0x4c, 0x45, 0x5f, 0x43, 0x4f,
+	0x52, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x4c, 0x33, 0x5f, 0x43, 0x48, 0x45, 0x43,
+	0x4b, 0x53, 0x55, 0x4d, 0x10, 0x99, 0x01, 0x12, 0x27, 0x0a, 0x22, 0x45, 0x52, 0x52, 0x4f, 0x52,
+	0x5f, 0x57, 0x48, 0x49, 0x4c, 0x45, 0x5f, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4e,
+	0x47, 0x5f, 0x4c, 0x34, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x10, 0x9a, 0x01,
+	0x12, 0x1c, 0x0a, 0x17, 0x43, 0x54, 0x5f, 0x4d, 0x41, 0x50, 0x5f, 0x49, 0x4e, 0x53, 0x45, 0x52,
+	0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x9b, 0x01, 0x12, 0x22,
+	0x0a, 0x1d, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x49, 0x50, 0x56, 0x36, 0x5f, 0x45,
+	0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10,
+	0x9c, 0x01, 0x12, 0x23, 0x0a, 0x1e, 0x49, 0x50, 0x5f, 0x46, 0x52, 0x41, 0x47, 0x4d, 0x45, 0x4e,
+	0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4f,
+	0x52, 0x54, 0x45, 0x44, 0x10, 0x9d, 0x01, 0x12, 0x1e, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x49,
+	0x43, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x45, 0x4e, 0x44, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46,
+	0x4f, 0x55, 0x4e, 0x44, 0x10, 0x9e, 0x01, 0x12, 0x28, 0x0a, 0x23, 0x4e, 0x4f, 0x5f, 0x54, 0x55,
+	0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x4e, 0x43, 0x41, 0x50, 0x53, 0x55, 0x4c,
+	0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0xa0,
+	0x01, 0x12, 0x23, 0x0a, 0x1e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x4f, 0x5f, 0x49,
+	0x4e, 0x53, 0x45, 0x52, 0x54, 0x5f, 0x49, 0x4e, 0x54, 0x4f, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59,
+	0x4d, 0x41, 0x50, 0x10, 0xa1, 0x01, 0x12, 0x2b, 0x0a, 0x26, 0x52, 0x45, 0x41, 0x43, 0x48, 0x45,
+	0x44, 0x5f, 0x45, 0x44, 0x54, 0x5f, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54,
+	0x49, 0x4e, 0x47, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x4f, 0x4e,
+	0x10, 0xa2, 0x01, 0x12, 0x26, 0x0a, 0x21, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43,
+	0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x4b, 0x49,
+	0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x10, 0xa3, 0x01, 0x12, 0x1e, 0x0a, 0x19, 0x4c,
+	0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x5f, 0x49, 0x53, 0x5f, 0x55, 0x4e, 0x52,
+	0x45, 0x41, 0x43, 0x48, 0x41, 0x42, 0x4c, 0x45, 0x10, 0xa4, 0x01, 0x12, 0x3a, 0x0a, 0x35, 0x4e,
+	0x4f, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
+	0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x54, 0x4f, 0x5f, 0x50, 0x45, 0x52,
+	0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x44, 0x45, 0x43, 0x49,
+	0x53, 0x49, 0x4f, 0x4e, 0x10, 0xa5, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x55, 0x4e, 0x53, 0x55, 0x50,
+	0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x32, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43,
+	0x4f, 0x4c, 0x10, 0xa6, 0x01, 0x12, 0x22, 0x0a, 0x1d, 0x4e, 0x4f, 0x5f, 0x4d, 0x41, 0x50, 0x50,
+	0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x4e, 0x41, 0x54, 0x5f, 0x4d, 0x41, 0x53, 0x51,
+	0x55, 0x45, 0x52, 0x41, 0x44, 0x45, 0x10, 0xa7, 0x01, 0x12, 0x2c, 0x0a, 0x27, 0x55, 0x4e, 0x53,
+	0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f,
+	0x4c, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x4e, 0x41, 0x54, 0x5f, 0x4d, 0x41, 0x53, 0x51, 0x55, 0x45,
+	0x52, 0x41, 0x44, 0x45, 0x10, 0xa8, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x46, 0x49, 0x42, 0x5f, 0x4c,
+	0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0xa9, 0x01, 0x12,
+	0x28, 0x0a, 0x23, 0x45, 0x4e, 0x43, 0x41, 0x50, 0x53, 0x55, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e,
+	0x5f, 0x54, 0x52, 0x41, 0x46, 0x46, 0x49, 0x43, 0x5f, 0x49, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x48,
+	0x49, 0x42, 0x49, 0x54, 0x45, 0x44, 0x10, 0xaa, 0x01, 0x12, 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x56,
+	0x41, 0x4c, 0x49, 0x44, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0xab, 0x01,
+	0x12, 0x13, 0x0a, 0x0e, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44,
+	0x45, 0x52, 0x10, 0xac, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x4e, 0x41, 0x54, 0x5f, 0x4e, 0x4f, 0x54,
+	0x5f, 0x4e, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0xad, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x49, 0x53,
+	0x5f, 0x41, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x49, 0x50, 0x10, 0xae, 0x01, 0x12,
+	0x2e, 0x0a, 0x29, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x4c, 0x4f, 0x47, 0x49, 0x43, 0x41, 0x4c,
+	0x5f, 0x44, 0x41, 0x54, 0x41, 0x47, 0x52, 0x41, 0x4d, 0x5f, 0x46, 0x52, 0x41, 0x47, 0x4d, 0x45,
+	0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0xaf, 0x01, 0x12,
+	0x1d, 0x0a, 0x18, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, 0x5f, 0x49, 0x43, 0x4d,
+	0x50, 0x56, 0x36, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0xb0, 0x01, 0x12, 0x21,
+	0x0a, 0x1c, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x5f, 0x42, 0x59, 0x5f, 0x4c, 0x42, 0x5f, 0x53,
+	0x52, 0x43, 0x5f, 0x52, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0xb1,
+	0x01, 0x12, 0x19, 0x0a, 0x14, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4c, 0x4f, 0x4f, 0x4b,
+	0x55, 0x50, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0xb2, 0x01, 0x12, 0x19, 0x0a, 0x14,
+	0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x46, 0x41,
+	0x49, 0x4c, 0x45, 0x44, 0x10, 0xb3, 0x01, 0x12, 0x31, 0x0a, 0x2c, 0x50, 0x52, 0x4f, 0x58, 0x59,
+	0x5f, 0x52, 0x45, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x54,
+	0x5f, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x50,
+	0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x10, 0xb4, 0x01, 0x2a, 0x4a, 0x0a, 0x10, 0x54, 0x72,
+	0x61, 0x66, 0x66, 0x69, 0x63, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d,
+	0x0a, 0x19, 0x54, 0x52, 0x41, 0x46, 0x46, 0x49, 0x43, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54,
+	0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a,
+	0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47,
+	0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x2a, 0x39, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54,
+	0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
+	0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x10,
+	0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x73, 0x74, 0x10,
+	0x02, 0x2a, 0x67, 0x0a, 0x0f, 0x4c, 0x6f, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f,
+	0x4c, 0x4f, 0x53, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43,
+	0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x46, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+	0x54, 0x5f, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x10, 0x01, 0x12,
+	0x19, 0x0a, 0x15, 0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+	0x54, 0x53, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x10, 0x02, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x69, 0x6c, 0x69, 0x75, 0x6d, 0x2f,
+	0x63, 0x69, 0x6c, 0x69, 0x75, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x6c,
+	0x6f, 0x77, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2739,32 +2952,32 @@ func file_flow_flow_proto_rawDescGZIP() []byte {
 	return file_flow_flow_proto_rawDescData
 }
 
-var file_flow_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 8)
-var file_flow_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
+var file_flow_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 9)
+var file_flow_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
 var file_flow_flow_proto_goTypes = []interface{}{
 	(FlowType)(0),               // 0: flow.FlowType
 	(TraceObservationPoint)(0),  // 1: flow.TraceObservationPoint
 	(L7FlowType)(0),             // 2: flow.L7FlowType
 	(IPVersion)(0),              // 3: flow.IPVersion
 	(Verdict)(0),                // 4: flow.Verdict
-	(TrafficDirection)(0),       // 5: flow.TrafficDirection
-	(EventType)(0),              // 6: flow.EventType
-	(LostEventSource)(0),        // 7: flow.LostEventSource
-	(*Flow)(nil),                // 8: flow.Flow
-	(*Layer4)(nil),              // 9: flow.Layer4
-	(*Layer7)(nil),              // 10: flow.Layer7
-	(*Endpoint)(nil),            // 11: flow.Endpoint
-	(*TCP)(nil),                 // 12: flow.TCP
-	(*IP)(nil),                  // 13: flow.IP
-	(*Ethernet)(nil),            // 14: flow.Ethernet
-	(*TCPFlags)(nil),            // 15: flow.TCPFlags
-	(*UDP)(nil),                 // 16: flow.UDP
-	(*ICMPv4)(nil),              // 17: flow.ICMPv4
-	(*ICMPv6)(nil),              // 18: flow.ICMPv6
-	(*EventTypeFilter)(nil),     // 19: flow.EventTypeFilter
-	(*CiliumEventType)(nil),     // 20: flow.CiliumEventType
-	(*FlowFilter)(nil),          // 21: flow.FlowFilter
-	(*Payload)(nil),             // 22: flow.Payload
+	(DropReason)(0),             // 5: flow.DropReason
+	(TrafficDirection)(0),       // 6: flow.TrafficDirection
+	(EventType)(0),              // 7: flow.EventType
+	(LostEventSource)(0),        // 8: flow.LostEventSource
+	(*Flow)(nil),                // 9: flow.Flow
+	(*Layer4)(nil),              // 10: flow.Layer4
+	(*Layer7)(nil),              // 11: flow.Layer7
+	(*Endpoint)(nil),            // 12: flow.Endpoint
+	(*TCP)(nil),                 // 13: flow.TCP
+	(*IP)(nil),                  // 14: flow.IP
+	(*Ethernet)(nil),            // 15: flow.Ethernet
+	(*TCPFlags)(nil),            // 16: flow.TCPFlags
+	(*UDP)(nil),                 // 17: flow.UDP
+	(*ICMPv4)(nil),              // 18: flow.ICMPv4
+	(*ICMPv6)(nil),              // 19: flow.ICMPv6
+	(*EventTypeFilter)(nil),     // 20: flow.EventTypeFilter
+	(*CiliumEventType)(nil),     // 21: flow.CiliumEventType
+	(*FlowFilter)(nil),          // 22: flow.FlowFilter
 	(*DNS)(nil),                 // 23: flow.DNS
 	(*HTTPHeader)(nil),          // 24: flow.HTTPHeader
 	(*HTTP)(nil),                // 25: flow.HTTP
@@ -2777,40 +2990,39 @@ var file_flow_flow_proto_goTypes = []interface{}{
 var file_flow_flow_proto_depIdxs = []int32{
 	29, // 0: flow.Flow.time:type_name -> google.protobuf.Timestamp
 	4,  // 1: flow.Flow.verdict:type_name -> flow.Verdict
-	14, // 2: flow.Flow.ethernet:type_name -> flow.Ethernet
-	13, // 3: flow.Flow.IP:type_name -> flow.IP
-	9,  // 4: flow.Flow.l4:type_name -> flow.Layer4
-	11, // 5: flow.Flow.source:type_name -> flow.Endpoint
-	11, // 6: flow.Flow.destination:type_name -> flow.Endpoint
+	15, // 2: flow.Flow.ethernet:type_name -> flow.Ethernet
+	14, // 3: flow.Flow.IP:type_name -> flow.IP
+	10, // 4: flow.Flow.l4:type_name -> flow.Layer4
+	12, // 5: flow.Flow.source:type_name -> flow.Endpoint
+	12, // 6: flow.Flow.destination:type_name -> flow.Endpoint
 	0,  // 7: flow.Flow.Type:type_name -> flow.FlowType
-	10, // 8: flow.Flow.l7:type_name -> flow.Layer7
-	20, // 9: flow.Flow.event_type:type_name -> flow.CiliumEventType
+	11, // 8: flow.Flow.l7:type_name -> flow.Layer7
+	21, // 9: flow.Flow.event_type:type_name -> flow.CiliumEventType
 	27, // 10: flow.Flow.source_service:type_name -> flow.Service
 	27, // 11: flow.Flow.destination_service:type_name -> flow.Service
-	5,  // 12: flow.Flow.traffic_direction:type_name -> flow.TrafficDirection
+	6,  // 12: flow.Flow.traffic_direction:type_name -> flow.TrafficDirection
 	1,  // 13: flow.Flow.trace_observation_point:type_name -> flow.TraceObservationPoint
-	12, // 14: flow.Layer4.TCP:type_name -> flow.TCP
-	16, // 15: flow.Layer4.UDP:type_name -> flow.UDP
-	17, // 16: flow.Layer4.ICMPv4:type_name -> flow.ICMPv4
-	18, // 17: flow.Layer4.ICMPv6:type_name -> flow.ICMPv6
-	2,  // 18: flow.Layer7.type:type_name -> flow.L7FlowType
-	23, // 19: flow.Layer7.dns:type_name -> flow.DNS
-	25, // 20: flow.Layer7.http:type_name -> flow.HTTP
-	26, // 21: flow.Layer7.kafka:type_name -> flow.Kafka
-	15, // 22: flow.TCP.flags:type_name -> flow.TCPFlags
-	3,  // 23: flow.IP.ipVersion:type_name -> flow.IPVersion
-	4,  // 24: flow.FlowFilter.verdict:type_name -> flow.Verdict
-	19, // 25: flow.FlowFilter.event_type:type_name -> flow.EventTypeFilter
-	6,  // 26: flow.Payload.type:type_name -> flow.EventType
-	29, // 27: flow.Payload.time:type_name -> google.protobuf.Timestamp
-	24, // 28: flow.HTTP.headers:type_name -> flow.HTTPHeader
-	7,  // 29: flow.LostEvent.source:type_name -> flow.LostEventSource
-	30, // 30: flow.LostEvent.cpu:type_name -> google.protobuf.Int32Value
-	31, // [31:31] is the sub-list for method output_type
-	31, // [31:31] is the sub-list for method input_type
-	31, // [31:31] is the sub-list for extension type_name
-	31, // [31:31] is the sub-list for extension extendee
-	0,  // [0:31] is the sub-list for field type_name
+	5,  // 14: flow.Flow.drop_reason_desc:type_name -> flow.DropReason
+	13, // 15: flow.Layer4.TCP:type_name -> flow.TCP
+	17, // 16: flow.Layer4.UDP:type_name -> flow.UDP
+	18, // 17: flow.Layer4.ICMPv4:type_name -> flow.ICMPv4
+	19, // 18: flow.Layer4.ICMPv6:type_name -> flow.ICMPv6
+	2,  // 19: flow.Layer7.type:type_name -> flow.L7FlowType
+	23, // 20: flow.Layer7.dns:type_name -> flow.DNS
+	25, // 21: flow.Layer7.http:type_name -> flow.HTTP
+	26, // 22: flow.Layer7.kafka:type_name -> flow.Kafka
+	16, // 23: flow.TCP.flags:type_name -> flow.TCPFlags
+	3,  // 24: flow.IP.ipVersion:type_name -> flow.IPVersion
+	4,  // 25: flow.FlowFilter.verdict:type_name -> flow.Verdict
+	20, // 26: flow.FlowFilter.event_type:type_name -> flow.EventTypeFilter
+	24, // 27: flow.HTTP.headers:type_name -> flow.HTTPHeader
+	8,  // 28: flow.LostEvent.source:type_name -> flow.LostEventSource
+	30, // 29: flow.LostEvent.cpu:type_name -> google.protobuf.Int32Value
+	30, // [30:30] is the sub-list for method output_type
+	30, // [30:30] is the sub-list for method input_type
+	30, // [30:30] is the sub-list for extension type_name
+	30, // [30:30] is the sub-list for extension extendee
+	0,  // [0:30] is the sub-list for field type_name
 }
 
 func init() { file_flow_flow_proto_init() }
@@ -2988,18 +3200,6 @@ func file_flow_flow_proto_init() {
 			}
 		}
 		file_flow_flow_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Payload); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_flow_flow_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*DNS); i {
 			case 0:
 				return &v.state
@@ -3011,7 +3211,7 @@ func file_flow_flow_proto_init() {
 				return nil
 			}
 		}
-		file_flow_flow_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+		file_flow_flow_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*HTTPHeader); i {
 			case 0:
 				return &v.state
@@ -3023,7 +3223,7 @@ func file_flow_flow_proto_init() {
 				return nil
 			}
 		}
-		file_flow_flow_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+		file_flow_flow_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*HTTP); i {
 			case 0:
 				return &v.state
@@ -3035,7 +3235,7 @@ func file_flow_flow_proto_init() {
 				return nil
 			}
 		}
-		file_flow_flow_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
+		file_flow_flow_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Kafka); i {
 			case 0:
 				return &v.state
@@ -3047,7 +3247,7 @@ func file_flow_flow_proto_init() {
 				return nil
 			}
 		}
-		file_flow_flow_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+		file_flow_flow_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Service); i {
 			case 0:
 				return &v.state
@@ -3059,7 +3259,7 @@ func file_flow_flow_proto_init() {
 				return nil
 			}
 		}
-		file_flow_flow_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+		file_flow_flow_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*LostEvent); i {
 			case 0:
 				return &v.state
@@ -3088,8 +3288,8 @@ func file_flow_flow_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_flow_flow_proto_rawDesc,
-			NumEnums:      8,
-			NumMessages:   21,
+			NumEnums:      9,
+			NumMessages:   20,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/v1/flow/flow.pb.json.go b/api/v1/flow/flow.pb.json.go
index 240cdc4490..899651301f 100644
--- a/api/v1/flow/flow.pb.json.go
+++ b/api/v1/flow/flow.pb.json.go
@@ -261,24 +261,6 @@ func (msg *FlowFilter) UnmarshalJSON(b []byte) error {
 	}).Unmarshal(bytes.NewReader(b), msg)
 }
 
-// MarshalJSON implements json.Marshaler
-func (msg *Payload) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-	err := (&jsonpb.Marshaler{
-		EnumsAsInts:  false,
-		EmitDefaults: false,
-		OrigName:     true,
-	}).Marshal(&buf, msg)
-	return buf.Bytes(), err
-}
-
-// UnmarshalJSON implements json.Unmarshaler
-func (msg *Payload) UnmarshalJSON(b []byte) error {
-	return (&jsonpb.Unmarshaler{
-		AllowUnknownFields: false,
-	}).Unmarshal(bytes.NewReader(b), msg)
-}
-
 // MarshalJSON implements json.Marshaler
 func (msg *DNS) MarshalJSON() ([]byte, error) {
 	var buf bytes.Buffer
diff --git a/api/v1/flow/flow.proto b/api/v1/flow/flow.proto
index 3cc15a095b..6f4810082a 100644
--- a/api/v1/flow/flow.proto
+++ b/api/v1/flow/flow.proto
@@ -26,7 +26,8 @@ message Flow {
     Verdict verdict = 2;
 
     // only applicable to Verdict = DROPPED.
-    uint32 drop_reason = 3;
+    // deprecated in favor of drop_reason_desc.
+    uint32 drop_reason = 3 [deprecated=true];
 
     // l2
     Ethernet ethernet = 4;
@@ -77,6 +78,9 @@ message Flow {
     // Only applicable to cilium trace notifications, blank for other types.
     TraceObservationPoint trace_observation_point = 24;
 
+    // only applicable to Verdict = DROPPED.
+    DropReason drop_reason_desc = 25;
+
     // This is a temporary workaround to support summary field for pb.Flow without
     // duplicating logic from the old parser. This field will be removed once we
     // fully migrate to the new parser.
@@ -214,6 +218,64 @@ enum Verdict {
     ERROR = 3;
 }
 
+// Taken from pkg/monitor/api/drop.go. Note that non-drop reasons (i.e. values
+// less than api.DropMin) are not used here.
+enum DropReason {
+    // non-drop reasons
+    DROP_REASON_UNKNOWN = 0;
+    // drop reasons
+    INVALID_SOURCE_MAC = 130;
+    INVALID_DESTINATION_MAC = 131;
+    INVALID_SOURCE_IP = 132;
+    POLICY_DENIED = 133;
+    INVALID_PACKET_DROPPED = 134;
+    CT_TRUNCATED_OR_INVALID_HEADER = 135;
+    CT_MISSING_TCP_ACK_FLAG = 136;
+    CT_UNKNOWN_L4_PROTOCOL = 137;
+    CT_CANNOT_CREATE_ENTRY_FROM_PACKET = 138;
+    UNSUPPORTED_L3_PROTOCOL = 139;
+    MISSED_TAIL_CALL = 140;
+    ERROR_WRITING_TO_PACKET = 141;
+    UNKNOWN_L4_PROTOCOL = 142;
+    UNKNOWN_ICMPV4_CODE = 143;
+    UNKNOWN_ICMPV4_TYPE = 144;
+    UNKNOWN_ICMPV6_CODE = 145;
+    UNKNOWN_ICMPV6_TYPE = 146;
+    ERROR_RETRIEVING_TUNNEL_KEY = 147;
+    ERROR_RETRIEVING_TUNNEL_OPTIONS = 148;
+    INVALID_GENEVE_OPTION = 149;
+    UNKNOWN_L3_TARGET_ADDRESS = 150;
+    STALE_OR_UNROUTABLE_IP = 151;
+    NO_MATCHING_LOCAL_CONTAINER_FOUND = 152;
+    ERROR_WHILE_CORRECTING_L3_CHECKSUM = 153;
+    ERROR_WHILE_CORRECTING_L4_CHECKSUM = 154;
+    CT_MAP_INSERTION_FAILED = 155;
+    INVALID_IPV6_EXTENSION_HEADER = 156;
+    IP_FRAGMENTATION_NOT_SUPPORTED = 157;
+    SERVICE_BACKEND_NOT_FOUND = 158;
+    NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT = 160;
+    FAILED_TO_INSERT_INTO_PROXYMAP = 161;
+    REACHED_EDT_RATE_LIMITING_DROP_HORIZON = 162;
+    UNKNOWN_CONNECTION_TRACKING_STATE = 163;
+    LOCAL_HOST_IS_UNREACHABLE = 164;
+    NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION = 165;
+    UNSUPPORTED_L2_PROTOCOL = 166;
+    NO_MAPPING_FOR_NAT_MASQUERADE = 167;
+    UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE = 168;
+    FIB_LOOKUP_FAILED = 169;
+    ENCAPSULATION_TRAFFIC_IS_PROHIBITED = 170;
+    INVALID_IDENTITY = 171;
+    UNKNOWN_SENDER = 172;
+    NAT_NOT_NEEDED = 173;
+    IS_A_CLUSTERIP = 174;
+    FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND = 175;
+    FORBIDDEN_ICMPV6_MESSAGE = 176;
+    DENIED_BY_LB_SRC_RANGE_CHECK = 177;
+    SOCKET_LOOKUP_FAILED = 178;
+    SOCKET_ASSIGN_FAILED = 179;
+    PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL = 180;
+}
+
 enum TrafficDirection {
     TRAFFIC_DIRECTION_UNKNOWN = 0;
     INGRESS = 1;
@@ -316,22 +378,6 @@ enum EventType {
     RecordLost = 2;
 }
 
-// Payload is the payload, i.e. the data the monitor returns.
-message Payload {
-    // type is the event type perf returns.
-    EventType type = 1;
-    // CPU is the CPU id for this event.
-    int32 CPU = 2;
-    // lost is the number of events lost for a given CPU.
-    uint64 lost = 3;
-    // data is the sample data returned by perf.
-    bytes data = 4;
-    // time when the event was observed
-    google.protobuf.Timestamp time = 5;
-    // the host name from which the payload was captured.
-    string host_name = 6;
-}
-
 // DNS flow. This is basically directly mapped from Cilium's LogRecordDNS:
 //     https://github.com/cilium/cilium/blob/04f3889d627774f79e56d14ddbc165b3169e2d01/pkg/proxy/accesslog/record.go#L264
 message DNS {
diff --git a/api/v1/models/backend_address.go b/api/v1/models/backend_address.go
index 4675a9767f..d305c02d8b 100644
--- a/api/v1/models/backend_address.go
+++ b/api/v1/models/backend_address.go
@@ -29,6 +29,9 @@ type BackendAddress struct {
 
 	// Layer 4 port number
 	Port uint16 `json:"port,omitempty"`
+
+	// Layer 4 protocol (TCP, UDP, etc)
+	Protocol string `json:"protocol,omitempty"`
 }
 
 // Validate validates this backend address
diff --git a/api/v1/observer/observer.pb.go b/api/v1/observer/observer.pb.go
index 3208bc875f..193a6771f4 100644
--- a/api/v1/observer/observer.pb.go
+++ b/api/v1/observer/observer.pb.go
@@ -102,6 +102,63 @@ const Verdict_ERROR = flow.Verdict_ERROR
 var Verdict_name = flow.Verdict_name
 var Verdict_value = flow.Verdict_value
 
+type DropReason = flow.DropReason
+
+const DropReason_DROP_REASON_UNKNOWN = flow.DropReason_DROP_REASON_UNKNOWN
+const DropReason_INVALID_SOURCE_MAC = flow.DropReason_INVALID_SOURCE_MAC
+const DropReason_INVALID_DESTINATION_MAC = flow.DropReason_INVALID_DESTINATION_MAC
+const DropReason_INVALID_SOURCE_IP = flow.DropReason_INVALID_SOURCE_IP
+const DropReason_POLICY_DENIED = flow.DropReason_POLICY_DENIED
+const DropReason_INVALID_PACKET_DROPPED = flow.DropReason_INVALID_PACKET_DROPPED
+const DropReason_CT_TRUNCATED_OR_INVALID_HEADER = flow.DropReason_CT_TRUNCATED_OR_INVALID_HEADER
+const DropReason_CT_MISSING_TCP_ACK_FLAG = flow.DropReason_CT_MISSING_TCP_ACK_FLAG
+const DropReason_CT_UNKNOWN_L4_PROTOCOL = flow.DropReason_CT_UNKNOWN_L4_PROTOCOL
+const DropReason_CT_CANNOT_CREATE_ENTRY_FROM_PACKET = flow.DropReason_CT_CANNOT_CREATE_ENTRY_FROM_PACKET
+const DropReason_UNSUPPORTED_L3_PROTOCOL = flow.DropReason_UNSUPPORTED_L3_PROTOCOL
+const DropReason_MISSED_TAIL_CALL = flow.DropReason_MISSED_TAIL_CALL
+const DropReason_ERROR_WRITING_TO_PACKET = flow.DropReason_ERROR_WRITING_TO_PACKET
+const DropReason_UNKNOWN_L4_PROTOCOL = flow.DropReason_UNKNOWN_L4_PROTOCOL
+const DropReason_UNKNOWN_ICMPV4_CODE = flow.DropReason_UNKNOWN_ICMPV4_CODE
+const DropReason_UNKNOWN_ICMPV4_TYPE = flow.DropReason_UNKNOWN_ICMPV4_TYPE
+const DropReason_UNKNOWN_ICMPV6_CODE = flow.DropReason_UNKNOWN_ICMPV6_CODE
+const DropReason_UNKNOWN_ICMPV6_TYPE = flow.DropReason_UNKNOWN_ICMPV6_TYPE
+const DropReason_ERROR_RETRIEVING_TUNNEL_KEY = flow.DropReason_ERROR_RETRIEVING_TUNNEL_KEY
+const DropReason_ERROR_RETRIEVING_TUNNEL_OPTIONS = flow.DropReason_ERROR_RETRIEVING_TUNNEL_OPTIONS
+const DropReason_INVALID_GENEVE_OPTION = flow.DropReason_INVALID_GENEVE_OPTION
+const DropReason_UNKNOWN_L3_TARGET_ADDRESS = flow.DropReason_UNKNOWN_L3_TARGET_ADDRESS
+const DropReason_STALE_OR_UNROUTABLE_IP = flow.DropReason_STALE_OR_UNROUTABLE_IP
+const DropReason_NO_MATCHING_LOCAL_CONTAINER_FOUND = flow.DropReason_NO_MATCHING_LOCAL_CONTAINER_FOUND
+const DropReason_ERROR_WHILE_CORRECTING_L3_CHECKSUM = flow.DropReason_ERROR_WHILE_CORRECTING_L3_CHECKSUM
+const DropReason_ERROR_WHILE_CORRECTING_L4_CHECKSUM = flow.DropReason_ERROR_WHILE_CORRECTING_L4_CHECKSUM
+const DropReason_CT_MAP_INSERTION_FAILED = flow.DropReason_CT_MAP_INSERTION_FAILED
+const DropReason_INVALID_IPV6_EXTENSION_HEADER = flow.DropReason_INVALID_IPV6_EXTENSION_HEADER
+const DropReason_IP_FRAGMENTATION_NOT_SUPPORTED = flow.DropReason_IP_FRAGMENTATION_NOT_SUPPORTED
+const DropReason_SERVICE_BACKEND_NOT_FOUND = flow.DropReason_SERVICE_BACKEND_NOT_FOUND
+const DropReason_NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT = flow.DropReason_NO_TUNNEL_OR_ENCAPSULATION_ENDPOINT
+const DropReason_FAILED_TO_INSERT_INTO_PROXYMAP = flow.DropReason_FAILED_TO_INSERT_INTO_PROXYMAP
+const DropReason_REACHED_EDT_RATE_LIMITING_DROP_HORIZON = flow.DropReason_REACHED_EDT_RATE_LIMITING_DROP_HORIZON
+const DropReason_UNKNOWN_CONNECTION_TRACKING_STATE = flow.DropReason_UNKNOWN_CONNECTION_TRACKING_STATE
+const DropReason_LOCAL_HOST_IS_UNREACHABLE = flow.DropReason_LOCAL_HOST_IS_UNREACHABLE
+const DropReason_NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION = flow.DropReason_NO_CONFIGURATION_AVAILABLE_TO_PERFORM_POLICY_DECISION
+const DropReason_UNSUPPORTED_L2_PROTOCOL = flow.DropReason_UNSUPPORTED_L2_PROTOCOL
+const DropReason_NO_MAPPING_FOR_NAT_MASQUERADE = flow.DropReason_NO_MAPPING_FOR_NAT_MASQUERADE
+const DropReason_UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE = flow.DropReason_UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE
+const DropReason_FIB_LOOKUP_FAILED = flow.DropReason_FIB_LOOKUP_FAILED
+const DropReason_ENCAPSULATION_TRAFFIC_IS_PROHIBITED = flow.DropReason_ENCAPSULATION_TRAFFIC_IS_PROHIBITED
+const DropReason_INVALID_IDENTITY = flow.DropReason_INVALID_IDENTITY
+const DropReason_UNKNOWN_SENDER = flow.DropReason_UNKNOWN_SENDER
+const DropReason_NAT_NOT_NEEDED = flow.DropReason_NAT_NOT_NEEDED
+const DropReason_IS_A_CLUSTERIP = flow.DropReason_IS_A_CLUSTERIP
+const DropReason_FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND = flow.DropReason_FIRST_LOGICAL_DATAGRAM_FRAGMENT_NOT_FOUND
+const DropReason_FORBIDDEN_ICMPV6_MESSAGE = flow.DropReason_FORBIDDEN_ICMPV6_MESSAGE
+const DropReason_DENIED_BY_LB_SRC_RANGE_CHECK = flow.DropReason_DENIED_BY_LB_SRC_RANGE_CHECK
+const DropReason_SOCKET_LOOKUP_FAILED = flow.DropReason_SOCKET_LOOKUP_FAILED
+const DropReason_SOCKET_ASSIGN_FAILED = flow.DropReason_SOCKET_ASSIGN_FAILED
+const DropReason_PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL = flow.DropReason_PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL
+
+var DropReason_name = flow.DropReason_name
+var DropReason_value = flow.DropReason_value
+
 type TrafficDirection = flow.TrafficDirection
 
 const TrafficDirection_TRAFFIC_DIRECTION_UNKNOWN = flow.TrafficDirection_TRAFFIC_DIRECTION_UNKNOWN
@@ -150,7 +207,6 @@ type ICMPv6 = flow.ICMPv6
 type EventTypeFilter = flow.EventTypeFilter
 type CiliumEventType = flow.CiliumEventType
 type FlowFilter = flow.FlowFilter
-type Payload = flow.Payload
 type DNS = flow.DNS
 type HTTPHeader = flow.HTTPHeader
 type HTTP = flow.HTTP
diff --git a/api/v1/observer/observer.proto b/api/v1/observer/observer.proto
index 5b0905988b..dfce1b9b77 100644
--- a/api/v1/observer/observer.proto
+++ b/api/v1/observer/observer.proto
@@ -23,8 +23,8 @@ package observer;
 
 option go_package = "github.com/cilium/cilium/api/v1/observer";
 
-// Observer returns a stream of Payload depending on which filter the user
-// want to observer.
+// Observer returns a stream of Flows depending on which filter the user want
+// to observe.
 service Observer {
     // GetFlows returning structured data, meant to eventually obsolete GetLastNFlows.
     rpc GetFlows(GetFlowsRequest) returns (stream GetFlowsResponse) {}
diff --git a/api/v1/openapi.yaml b/api/v1/openapi.yaml
index f9c552725d..c045df68b8 100644
--- a/api/v1/openapi.yaml
+++ b/api/v1/openapi.yaml
@@ -2250,6 +2250,9 @@ definitions:
         description: Layer 4 port number
         type: integer
         format: uint16
+      protocol:
+        description: Layer 4 protocol (TCP, UDP, etc)
+        type: string
       nodeName:
         description: Optional name of the node on which this backend runs
         type: string
diff --git a/api/v1/server/embedded_spec.go b/api/v1/server/embedded_spec.go
index 375d4011fb..1d8b94e1a1 100644
--- a/api/v1/server/embedded_spec.go
+++ b/api/v1/server/embedded_spec.go
@@ -1429,6 +1429,10 @@ func init() {
           "description": "Layer 4 port number",
           "type": "integer",
           "format": "uint16"
+        },
+        "protocol": {
+          "description": "Layer 4 protocol (TCP, UDP, etc)",
+          "type": "string"
         }
       }
     },
@@ -5147,6 +5151,10 @@ func init() {
           "description": "Layer 4 port number",
           "type": "integer",
           "format": "uint16"
+        },
+        "protocol": {
+          "description": "Layer 4 protocol (TCP, UDP, etc)",
+          "type": "string"
         }
       }
     },
diff --git a/bpf/bpf_sock.c b/bpf/bpf_sock.c
index afa690534a..2a746c53f3 100644
--- a/bpf/bpf_sock.c
+++ b/bpf/bpf_sock.c
@@ -272,15 +272,17 @@ static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
 	const bool in_hostns = ctx_in_hostns(ctx_full, &id.client_cookie);
 	struct lb4_backend *backend;
 	struct lb4_service *svc;
+	__u32 proto = ctx->protocol;
 	struct lb4_key key = {
 		.address	= ctx->user_ip4,
 		.dport		= ctx_dst_port(ctx),
+		.proto		= proto,
 	}, orig_key = key;
 	struct lb4_service *backend_slot;
 	bool backend_from_affinity = false;
 	__u32 backend_id = 0;
 
-	if (!udp_only && !sock_proto_enabled(ctx->protocol))
+	if (!udp_only && !sock_proto_enabled(proto))
 		return -ENOTSUP;
 
 	/* In case a direct match fails, we try to look-up surrogate
@@ -372,12 +374,14 @@ static __always_inline int __sock4_bind(struct bpf_sock *ctx,
 					struct bpf_sock *ctx_full)
 {
 	struct lb4_service *svc;
+	__u32 proto = ctx->protocol;
 	struct lb4_key key = {
 		.address	= ctx->src_ip4,
 		.dport		= ctx_src_port(ctx),
+		.proto		= proto,
 	};
 
-	if (!sock_proto_enabled(ctx->protocol) ||
+	if (!sock_proto_enabled(proto) ||
 	    !ctx_in_hostns(ctx_full, NULL))
 		return 0;
 
@@ -428,6 +432,7 @@ static __always_inline int __sock4_xlate_rev(struct bpf_sock_addr *ctx,
 		struct lb4_key svc_key = {
 			.address	= val->address,
 			.dport		= val->port,
+			.proto		= ctx->protocol,
 		};
 
 		svc = lb4_lookup_service(&svc_key, true);
@@ -672,11 +677,13 @@ sock6_bind_v4_in_v6(struct bpf_sock *ctx __maybe_unused)
 static __always_inline int __sock6_bind(struct bpf_sock *ctx)
 {
 	struct lb6_service *svc;
+	__u32 proto = ctx->protocol;
 	struct lb6_key key = {
 		.dport		= ctx_src_port(ctx),
+		.proto		= proto,
 	};
 
-	if (!sock_proto_enabled(ctx->protocol) ||
+	if (!sock_proto_enabled(proto) ||
 	    !ctx_in_hostns(ctx, NULL))
 		return 0;
 
@@ -715,14 +722,16 @@ static __always_inline int __sock6_xlate_fwd(struct bpf_sock_addr *ctx,
 	const bool in_hostns = ctx_in_hostns(ctx, &id.client_cookie);
 	struct lb6_backend *backend;
 	struct lb6_service *svc;
+	__u32 proto = ctx->protocol;
 	struct lb6_key key = {
 		.dport		= ctx_dst_port(ctx),
+		.proto		= proto,
 	}, orig_key;
 	struct lb6_service *backend_slot;
 	bool backend_from_affinity = false;
 	__u32 backend_id = 0;
 
-	if (!udp_only && !sock_proto_enabled(ctx->protocol))
+	if (!udp_only && !sock_proto_enabled(proto))
 		return -ENOTSUP;
 
 	ctx_get_v6_address(ctx, &key.address);
@@ -839,6 +848,7 @@ static __always_inline int __sock6_xlate_rev(struct bpf_sock_addr *ctx)
 		struct lb6_key svc_key = {
 			.address	= val->address,
 			.dport		= val->port,
+			.proto		= ctx->protocol,
 		};
 
 		svc = lb6_lookup_service(&svc_key, true);
diff --git a/bpf/lib/common.h b/bpf/lib/common.h
index ae2edcae89..60a430ed4b 100644
--- a/bpf/lib/common.h
+++ b/bpf/lib/common.h
@@ -632,7 +632,7 @@ struct lb6_key {
 	union v6addr address;	/* Service virtual IPv6 address */
 	__be16 dport;		/* L4 port filter, if unset, all ports apply */
 	__u16 backend_slot;	/* Backend iterator, 0 indicates the svc frontend */
-	__u8 proto;		/* L4 protocol, currently not used (set to 0) */
+	__u8 proto;		/* L4 protocol */
 	__u8 scope;		/* LB_LOOKUP_SCOPE_* for externalTrafficPolicy=Local */
 	__u8 pad[2];
 };
@@ -680,7 +680,7 @@ struct lb4_key {
 	__be32 address;		/* Service virtual IPv4 address */
 	__be16 dport;		/* L4 port filter, if unset, all ports apply */
 	__u16 backend_slot;	/* Backend iterator, 0 indicates the svc frontend */
-	__u8 proto;		/* L4 protocol, currently not used (set to 0) */
+	__u8 proto;		/* L4 protocol */
 	__u8 scope;		/* LB_LOOKUP_SCOPE_* for externalTrafficPolicy=Local */
 	__u8 pad[2];
 };
diff --git a/bpf/lib/lb.h b/bpf/lib/lb.h
index 7b0d0db61b..dd499105cd 100644
--- a/bpf/lib/lb.h
+++ b/bpf/lib/lb.h
@@ -500,8 +500,7 @@ static __always_inline int lb6_extract_key(struct __ctx_buff *ctx __maybe_unused
 					   int dir)
 {
 	union v6addr *addr;
-	/* FIXME(brb): set after adding support for different L4 protocols in LB */
-	key->proto = 0;
+	key->proto = tuple->nexthdr;
 	addr = (dir == CT_INGRESS) ? &tuple->saddr : &tuple->daddr;
 	ipv6_addr_copy(&key->address, addr);
 	csum_l4_offset_and_flags(tuple->nexthdr, csum_off);
@@ -1020,8 +1019,7 @@ static __always_inline int lb4_extract_key(struct __ctx_buff *ctx __maybe_unused
 					   struct csum_offset *csum_off,
 					   int dir)
 {
-	/* FIXME: set after adding support for different L4 protocols in LB */
-	key->proto = 0;
+	key->proto = ip4->protocol;
 	key->address = (dir == CT_INGRESS) ? ip4->saddr : ip4->daddr;
 	csum_l4_offset_and_flags(ip4->protocol, csum_off);
 
diff --git a/bpf/sockops/bpf_redir.c b/bpf/sockops/bpf_redir.c
index 77855a71ae..f55fdab14a 100644
--- a/bpf/sockops/bpf_redir.c
+++ b/bpf/sockops/bpf_redir.c
@@ -28,6 +28,10 @@ static __always_inline void sk_msg_extract4_key(const struct sk_msg_md *msg,
 	key->dip4 = msg->remote_ip4;
 	key->sip4 = msg->local_ip4;
 	key->family = ENDPOINT_KEY_IPV4;
+	/* Only TCP packets flow through sk_msg progs.
+	 * cf GH issue #13490 to fix
+	 */
+	key->protocol = IPPROTO_TCP;
 
 	key->sport = (bpf_ntohl(msg->local_port) >> 16);
 	/* clang-7.1 or higher seems to think it can do a 16-bit read here
diff --git a/bpf/sockops/bpf_sockops.c b/bpf/sockops/bpf_sockops.c
index a932a3194b..13545c4e80 100644
--- a/bpf/sockops/bpf_sockops.c
+++ b/bpf/sockops/bpf_sockops.c
@@ -29,6 +29,11 @@ static __always_inline void sk_extract4_key(const struct bpf_sock_ops *ops,
 	key->dip4 = ops->remote_ip4;
 	key->sip4 = ops->local_ip4;
 	key->family = ENDPOINT_KEY_IPV4;
+	/* We will get misses on UDP, but the verifier
+	 * does not allow access to the socket field until >= 5.3
+	 * cf GH issue #13490 to fix
+	 */
+	key->protocol = IPPROTO_TCP;
 
 	key->sport = (bpf_ntohl(ops->local_port) >> 16);
 	/* clang-7.1 or higher seems to think it can do a 16-bit read here
@@ -45,6 +50,7 @@ static __always_inline void sk_lb4_key(struct lb4_key *lb4,
 	/* SK MSG is always egress, so use daddr */
 	lb4->address = key->dip4;
 	lb4->dport = key->dport;
+	lb4->proto = key->protocol;
 }
 
 static __always_inline bool redirect_to_proxy(int verdict)
diff --git a/bpf/sockops/bpf_sockops.h b/bpf/sockops/bpf_sockops.h
index 13c4a44025..f45ad0ffe0 100644
--- a/bpf/sockops/bpf_sockops.h
+++ b/bpf/sockops/bpf_sockops.h
@@ -24,7 +24,7 @@ struct sock_key {
 		union v6addr	dip6;
 	};
 	__u8 family;
-	__u8 pad7;
+	__u8 protocol;
 	__u16 pad8;
 	__u32 sport;
 	__u32 dport;
diff --git a/cilium/cmd/service_update.go b/cilium/cmd/service_update.go
index bf9eb225bd..76fe88285c 100644
--- a/cilium/cmd/service_update.go
+++ b/cilium/cmd/service_update.go
@@ -37,6 +37,7 @@ var (
 	localRedirect      bool
 	idU                uint64
 	frontend           string
+	protocol           string
 	backends           []string
 )
 
@@ -60,11 +61,36 @@ func init() {
 	serviceUpdateCmd.Flags().StringVarP(&k8sTrafficPolicy, "k8s-traffic-policy", "", "Cluster", "Set service with k8s externalTrafficPolicy as {Local,Cluster}")
 	serviceUpdateCmd.Flags().BoolVarP(&k8sClusterInternal, "k8s-cluster-internal", "", false, "Set service as cluster-internal for externalTrafficPolicy=Local")
 	serviceUpdateCmd.Flags().StringVarP(&frontend, "frontend", "", "", "Frontend address")
+	serviceUpdateCmd.Flags().StringVarP(&protocol, "protocol", "", "tcp", "Protocol for service (e.g. TCP, UDP)")
 	serviceUpdateCmd.Flags().StringSliceVarP(&backends, "backends", "", []string{}, "Backend address or addresses (<IP:Port>)")
 }
 
-func parseFrontendAddress(address string) (*models.FrontendAddress, net.IP) {
-	frontend, err := net.ResolveTCPAddr("tcp", address)
+func parseAddress(l4Protocol, address string) (ip net.IP, port int, proto string, err error) {
+	switch proto = strings.ToLower(l4Protocol); proto {
+	case "tcp":
+		var tcpAddr *net.TCPAddr
+		tcpAddr, err = net.ResolveTCPAddr(proto, address)
+		if err != nil {
+			return
+		}
+		ip = tcpAddr.IP
+		port = tcpAddr.Port
+	case "udp":
+		var udpAddr *net.UDPAddr
+		udpAddr, err = net.ResolveUDPAddr(proto, address)
+		if err != nil {
+			return
+		}
+		ip = udpAddr.IP
+		port = udpAddr.Port
+	default:
+		err = fmt.Errorf("unrecognized protocol %q", l4Protocol)
+	}
+	return
+}
+
+func parseFrontendAddress(l4Protocol, address string) (*models.FrontendAddress, net.IP) {
+	ip, port, proto, err := parseAddress(l4Protocol, address)
 	if err != nil {
 		Fatalf("Unable to parse frontend address: %s\n", err)
 	}
@@ -74,13 +100,12 @@ func parseFrontendAddress(address string) (*models.FrontendAddress, net.IP) {
 		scope = models.FrontendAddressScopeInternal
 	}
 
-	// FIXME support more than TCP
 	return &models.FrontendAddress{
-		IP:       frontend.IP.String(),
-		Port:     uint16(frontend.Port),
-		Protocol: models.FrontendAddressProtocolTCP,
+		IP:       ip.String(),
+		Port:     uint16(port),
+		Protocol: proto,
 		Scope:    scope,
-	}, frontend.IP
+	}, ip
 }
 
 func boolToInt(set bool) int {
@@ -92,7 +117,7 @@ func boolToInt(set bool) int {
 
 func updateService(cmd *cobra.Command, args []string) {
 	id := int64(idU)
-	fa, faIP := parseFrontendAddress(frontend)
+	fa, faIP := parseFrontendAddress(protocol, frontend)
 
 	var spec *models.ServiceSpec
 	svc, err := client.GetServiceID(id)
@@ -150,20 +175,19 @@ func updateService(cmd *cobra.Command, args []string) {
 
 	spec.BackendAddresses = nil
 	for _, backend := range backends {
-		beAddr, err := net.ResolveTCPAddr("tcp", backend)
+		ip, port, proto, err := parseAddress(protocol, backend)
 		if err != nil {
-			Fatalf("Cannot parse backend address \"%s\": %s", backend, err)
+			Fatalf("Cannot parse backend address %q: %s", backend, err)
 		}
-
 		// Backend ID will be set by the daemon
-		be := loadbalancer.NewBackend(0, loadbalancer.TCP, beAddr.IP, uint16(beAddr.Port))
+		be := loadbalancer.NewBackend(0, loadbalancer.L4Type(strings.ToUpper(proto)), ip, uint16(port))
 
 		if be.IsIPv6() && faIP.To4() != nil {
 			Fatalf("Address mismatch between frontend and backend %s", backend)
 		}
 
-		if fa.Port == 0 && beAddr.Port != 0 {
-			Fatalf("L4 backend found (%v) with L3 frontend", beAddr)
+		if fa.Port == 0 && port != 0 {
+			Fatalf("L4 backend found (%s:%d) with L3 frontend", ip, port)
 		}
 
 		ba := be.GetBackendModel()
diff --git a/daemon/cmd/api_limits.go b/daemon/cmd/api_limits.go
index 0336b0678d..c127af80eb 100644
--- a/daemon/cmd/api_limits.go
+++ b/daemon/cmd/api_limits.go
@@ -119,6 +119,11 @@ func (a *apiRateLimitingMetrics) ProcessedRequest(name string, v rate.MetricsVal
 	metrics.APILimiterRateLimit.WithLabelValues(name, "limit").Set(float64(v.Limit))
 	metrics.APILimiterRateLimit.WithLabelValues(name, "burst").Set(float64(v.Burst))
 	metrics.APILimiterAdjustmentFactor.WithLabelValues(name).Set(v.AdjustmentFactor)
-	metrics.APILimiterWaitHistoryDuration.WithLabelValues(name).Observe(v.WaitDuration.Seconds())
-	metrics.APILimiterProcessedRequests.WithLabelValues(name, metrics.Error2Outcome(v.Error)).Inc()
+
+	if v.Outcome == "" {
+		metrics.APILimiterWaitHistoryDuration.WithLabelValues(name).Observe(v.WaitDuration.Seconds())
+		v.Outcome = metrics.Error2Outcome(v.Error)
+	}
+
+	metrics.APILimiterProcessedRequests.WithLabelValues(name, v.Outcome).Inc()
 }
diff --git a/pkg/bpf/bpf_linux.go b/pkg/bpf/bpf_linux.go
index ce8ca2d894..6bb699bb4e 100644
--- a/pkg/bpf/bpf_linux.go
+++ b/pkg/bpf/bpf_linux.go
@@ -464,23 +464,31 @@ func objCheck(fd int, path string, mapType MapType, keySize, valueSize, maxEntri
 
 func OpenOrCreateMap(path string, mapType MapType, keySize, valueSize, maxEntries, flags uint32, innerID uint32, pin bool) (int, bool, error) {
 	var fd int
+	var err error
 
 	redo := false
 	isNewMap := false
 
 recreate:
-	if _, err := os.Stat(path); os.IsNotExist(err) || redo {
-		mapDir := filepath.Dir(path)
-		if _, err = os.Stat(mapDir); os.IsNotExist(err) {
-			if err = os.MkdirAll(mapDir, 0755); err != nil {
-				return 0, isNewMap, &os.PathError{
-					Op:   "Unable create map base directory",
-					Path: path,
-					Err:  err,
+	create := true
+	if pin {
+		if _, err := os.Stat(path); os.IsNotExist(err) || redo {
+			mapDir := filepath.Dir(path)
+			if _, err = os.Stat(mapDir); os.IsNotExist(err) {
+				if err = os.MkdirAll(mapDir, 0755); err != nil {
+					return 0, isNewMap, &os.PathError{
+						Op:   "Unable create map base directory",
+						Path: path,
+						Err:  err,
+					}
 				}
 			}
+		} else {
+			create = false
 		}
+	}
 
+	if create {
 		fd, err = CreateMap(
 			mapType,
 			keySize,
@@ -515,7 +523,7 @@ recreate:
 		return fd, isNewMap, nil
 	}
 
-	fd, err := ObjGet(path)
+	fd, err = ObjGet(path)
 	if err == nil {
 		redo = objCheck(
 			fd,
diff --git a/pkg/bpf/map_linux.go b/pkg/bpf/map_linux.go
index 535490e609..7596d48c59 100644
--- a/pkg/bpf/map_linux.go
+++ b/pkg/bpf/map_linux.go
@@ -467,13 +467,13 @@ func (m *Map) OpenOrCreate() (bool, error) {
 	return m.openOrCreate(true)
 }
 
-// OpenOrCreateUnpinned is similar to OpenOrCreate (see above) but without
-// pinning the map to the file system if it had to be created.
-func (m *Map) OpenOrCreateUnpinned() (bool, error) {
+// CreateUnpinned creates the map without pinning it to the file system.
+func (m *Map) CreateUnpinned() error {
 	m.lock.Lock()
 	defer m.lock.Unlock()
 
-	return m.openOrCreate(false)
+	_, err := m.openOrCreate(false)
+	return err
 }
 
 // Create is similar to OpenOrCreate, but closes the map after creating or
diff --git a/pkg/comparator/comparator.go b/pkg/comparator/comparator.go
index 7c8cde61a7..ccf1192744 100644
--- a/pkg/comparator/comparator.go
+++ b/pkg/comparator/comparator.go
@@ -1,4 +1,4 @@
-// Copyright 2017-2018 Authors of Cilium
+// Copyright 2017-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -78,3 +78,43 @@ func MapBoolEquals(m1, m2 map[string]bool) bool {
 	}
 	return true
 }
+
+// MapStringEqualsIgnoreKeys returns true if both maps have the same values for
+// the keys that are not present in the 'ignoreKeys'.
+func MapStringEqualsIgnoreKeys(m1, m2 map[string]string, ignoreKeys []string) bool {
+	switch {
+	case m1 == nil && m2 == nil:
+		return true
+	case m1 == nil && m2 != nil,
+		m1 != nil && m2 == nil:
+		return false
+	}
+	ignoredM1 := 0
+	for k1, v1 := range m1 {
+		var ignore bool
+		for _, ig := range ignoreKeys {
+			if k1 == ig {
+				ignore = true
+				break
+			}
+		}
+		if ignore {
+			ignoredM1++
+			continue
+		}
+		if v2, ok := m2[k1]; !ok || v2 != v1 {
+			return false
+		}
+	}
+
+	ignoredM2 := 0
+	for _, ig := range ignoreKeys {
+		if _, ok := m2[ig]; ok {
+			ignoredM2++
+		}
+	}
+	if len(m1)-ignoredM1 != len(m2)-ignoredM2 {
+		return false
+	}
+	return true
+}
diff --git a/pkg/hubble/api/v1/interface.go b/pkg/hubble/api/v1/interface.go
index fc7ba34927..9432dd89b9 100644
--- a/pkg/hubble/api/v1/interface.go
+++ b/pkg/hubble/api/v1/interface.go
@@ -46,6 +46,7 @@ type Flow interface {
 	GetTrafficDirection() flowpb.TrafficDirection
 	GetPolicyMatchType() uint32
 	GetSummary() string
+	GetDropReasonDesc() flowpb.DropReason
 }
 
 // This ensures that the protobuf definition implements the interface
diff --git a/pkg/hubble/parser/seven/parser.go b/pkg/hubble/parser/seven/parser.go
index cd77b416ca..6a0b5ef47d 100644
--- a/pkg/hubble/parser/seven/parser.go
+++ b/pkg/hubble/parser/seven/parser.go
@@ -72,6 +72,9 @@ func New(log logrus.FieldLogger, dnsGetter getters.DNSGetter, ipGetter getters.I
 
 // Decode decodes the data from 'payload' into 'decoded'
 func (p *Parser) Decode(r *accesslog.LogRecord, decoded *pb.Flow) error {
+	// Safety: This function and all the helpers it invokes are not allowed to
+	// mutate r in any way. We only have read access to the LogRecord, as it
+	// may be shared with other consumers
 	if r == nil {
 		return errors.ErrEmptyData
 	}
@@ -129,6 +132,7 @@ func (p *Parser) Decode(r *accesslog.LogRecord, decoded *pb.Flow) error {
 	decoded.Time = pbTimestamp
 	decoded.Verdict = decodeVerdict(r.Verdict)
 	decoded.DropReason = 0
+	decoded.DropReasonDesc = pb.DropReason_DROP_REASON_UNKNOWN
 	decoded.IP = ip
 	decoded.L4 = l4
 	decoded.Source = decodeEndpoint(sourceEndpoint, sourceNamespace, sourcePod)
@@ -265,7 +269,10 @@ func decodeLayer4(protocol accesslog.TransportProtocol, source, destination acce
 }
 
 func decodeEndpoint(endpoint accesslog.EndpointInfo, namespace, podName string) *pb.Endpoint {
-	labels := endpoint.Labels
+	// Safety: We only have read access to endpoint, therefore we need to create
+	// a copy of the label list before we can sort it
+	labels := make([]string, len(endpoint.Labels))
+	copy(labels, endpoint.Labels)
 	sort.Strings(labels)
 	return &pb.Endpoint{
 		ID:        uint32(endpoint.ID),
diff --git a/pkg/hubble/parser/threefour/parser.go b/pkg/hubble/parser/threefour/parser.go
index 9d971d797d..c890bc2ef6 100644
--- a/pkg/hubble/parser/threefour/parser.go
+++ b/pkg/hubble/parser/threefour/parser.go
@@ -173,6 +173,7 @@ func (p *Parser) Decode(data []byte, decoded *pb.Flow) error {
 
 	decoded.Verdict = decodeVerdict(dn, tn, pvn)
 	decoded.DropReason = decodeDropReason(dn, pvn)
+	decoded.DropReasonDesc = pb.DropReason(decoded.DropReason)
 	decoded.Ethernet = ether
 	decoded.IP = ip
 	decoded.L4 = l4
diff --git a/pkg/k8s/apis/cilium.io/v2/clrp_types.go b/pkg/k8s/apis/cilium.io/v2/clrp_types.go
index fc49c240c1..7a3aff6d70 100644
--- a/pkg/k8s/apis/cilium.io/v2/clrp_types.go
+++ b/pkg/k8s/apis/cilium.io/v2/clrp_types.go
@@ -234,11 +234,6 @@ func (pInfo *PortInfo) SanitizePortInfo(checkNamedPort bool) (uint16, string, lb
 	}
 	pName = strings.ToLower(pInfo.Name) // Normalize for case insensitive comparison
 
-	// Sanitize protocol
-	var err error
-	protocol, err = lb.NewL4Type(string(pInfo.Protocol))
-	if err != nil {
-		return pInt, pName, protocol, err
-	}
+	protocol = lb.NewL4Type(string(pInfo.Protocol))
 	return pInt, pName, protocol, nil
 }
diff --git a/pkg/k8s/apis/cilium.io/v2/cnp_types.go b/pkg/k8s/apis/cilium.io/v2/cnp_types.go
index 03d3c03ebc..1f43629671 100644
--- a/pkg/k8s/apis/cilium.io/v2/cnp_types.go
+++ b/pkg/k8s/apis/cilium.io/v2/cnp_types.go
@@ -80,21 +80,11 @@ func sharedCNPDeepEqual(in, other *CiliumNetworkPolicy) bool {
 		return false
 	}
 
-	// Ignore v1.LastAppliedConfigAnnotation annotation
-	lastAppliedCfgAnnotation1, ok1 := in.GetAnnotations()[v1.LastAppliedConfigAnnotation]
-	lastAppliedCfgAnnotation2, ok2 := other.GetAnnotations()[v1.LastAppliedConfigAnnotation]
-	defer func() {
-		if ok1 && in.GetAnnotations() != nil {
-			in.GetAnnotations()[v1.LastAppliedConfigAnnotation] = lastAppliedCfgAnnotation1
-		}
-		if ok2 && other.GetAnnotations() != nil {
-			other.GetAnnotations()[v1.LastAppliedConfigAnnotation] = lastAppliedCfgAnnotation2
-		}
-	}()
-	delete(in.GetAnnotations(), v1.LastAppliedConfigAnnotation)
-	delete(other.GetAnnotations(), v1.LastAppliedConfigAnnotation)
-
-	return comparator.MapStringEquals(in.GetAnnotations(), other.GetAnnotations())
+	return comparator.MapStringEqualsIgnoreKeys(
+		in.GetAnnotations(),
+		other.GetAnnotations(),
+		// Ignore v1.LastAppliedConfigAnnotation annotation
+		[]string{v1.LastAppliedConfigAnnotation})
 }
 
 // +deepequal-gen=true
diff --git a/pkg/k8s/cnp.go b/pkg/k8s/cnp.go
index 41f4ec7688..732704fd58 100644
--- a/pkg/k8s/cnp.go
+++ b/pkg/k8s/cnp.go
@@ -140,11 +140,8 @@ func (c *CNPStatusUpdateContext) prepareUpdate(cnp *types.SlimCNP, scopedLog *lo
 		return
 	}
 
-	// Make a copy since the rule is a pointer, and any of its fields
-	// which are also pointers could be modified outside of this
-	// function or with the 'cnp.Parse()' which can also overwrite fields.
-	serverRule = cnp.DeepCopy()
-	_, err = serverRule.Parse()
+	serverRule = cnp
+	_, err = cnp.Parse()
 	if err != nil {
 		log.WithError(err).WithField(logfields.Object, logfields.Repr(serverRule)).
 			Warn("Error parsing new CiliumNetworkPolicy rule")
diff --git a/pkg/k8s/service.go b/pkg/k8s/service.go
index 09a6b3db4a..35e7030abf 100644
--- a/pkg/k8s/service.go
+++ b/pkg/k8s/service.go
@@ -442,18 +442,6 @@ func NewService(ip net.IP, externalIPs, loadBalancerIPs, loadBalancerSourceRange
 	}
 }
 
-// UniquePorts returns a map of all unique ports configured in the service
-func (s *Service) UniquePorts() map[uint16]bool {
-	// We are not discriminating the different L4 protocols on the same L4
-	// port so we create the number of unique sets of service IP + service
-	// port.
-	uniqPorts := map[uint16]bool{}
-	for _, p := range s.Ports {
-		uniqPorts[p.Port] = true
-	}
-	return uniqPorts
-}
-
 // NewClusterService returns the serviceStore.ClusterService representing a
 // Kubernetes Service
 func NewClusterService(id ServiceID, k8sService *Service, k8sEndpoints *Endpoints) serviceStore.ClusterService {
diff --git a/pkg/k8s/service_cache.go b/pkg/k8s/service_cache.go
index 7731451ef0..e47c725896 100644
--- a/pkg/k8s/service_cache.go
+++ b/pkg/k8s/service_cache.go
@@ -341,28 +341,6 @@ func (s *ServiceCache) DeleteEndpointSlices(epSlice *slim_discovery_v1beta1.Endp
 // FrontendList is the list of all k8s service frontends
 type FrontendList map[string]struct{}
 
-// LooseMatch returns true if the provided frontend is found in the
-// FrontendList. If the frontend has a protocol value set, it only matches a
-// k8s service with a matching protocol. If no protocol is set, any k8s service
-// matching frontend IP and port is considered a match, regardless of protocol.
-func (l FrontendList) LooseMatch(frontend loadbalancer.L3n4Addr) (exists bool) {
-	switch frontend.Protocol {
-	case loadbalancer.NONE:
-		for _, protocol := range loadbalancer.AllProtocols {
-			frontend.Protocol = protocol
-			_, exists = l[frontend.StringWithProtocol()]
-			if exists {
-				return
-			}
-		}
-
-	// If the protocol is set, perform an exact match
-	default:
-		_, exists = l[frontend.StringWithProtocol()]
-	}
-	return
-}
-
 // UniqueServiceFrontends returns all externally scoped services known to
 // the service cache as a map, indexed by the string representation of a
 // loadbalancer.L3n4Addr. This helper is only used in unit tests.
@@ -379,12 +357,12 @@ func (s *ServiceCache) UniqueServiceFrontends() FrontendList {
 				L4Addr: *p,
 				Scope:  loadbalancer.ScopeExternal,
 			}
-			uniqueFrontends[address.StringWithProtocol()] = struct{}{}
+			uniqueFrontends[address.String()] = struct{}{}
 		}
 		for _, nodePortFEs := range svc.NodePorts {
 			for _, fe := range nodePortFEs {
 				if fe.Scope == loadbalancer.ScopeExternal {
-					uniqueFrontends[fe.StringWithProtocol()] = struct{}{}
+					uniqueFrontends[fe.String()] = struct{}{}
 				}
 			}
 		}
diff --git a/pkg/k8s/watchers/cilium_local_redirect_policy.go b/pkg/k8s/watchers/cilium_local_redirect_policy.go
index e2c750e82c..1fa7eb2894 100644
--- a/pkg/k8s/watchers/cilium_local_redirect_policy.go
+++ b/pkg/k8s/watchers/cilium_local_redirect_policy.go
@@ -72,7 +72,7 @@ func (k *K8sWatcher) ciliumLocalRedirectPolicyInit(ciliumLRPClient *k8s.K8sCiliu
 	)
 
 	go lrpController.Run(wait.NeverStop)
-	k.k8sAPIGroups.addAPI(k8sAPIGroupCiliumClusterwideNetworkPolicyV2)
+	k.k8sAPIGroups.addAPI(k8sAPIGroupCiliumLocalRedirectPolicyV2)
 }
 
 func (k *K8sWatcher) addCiliumLocalRedirectPolicy(clrp *cilium_v2.CiliumLocalRedirectPolicy) error {
diff --git a/pkg/k8s/watchers/cilium_network_policy.go b/pkg/k8s/watchers/cilium_network_policy.go
index 15f6fe5de6..18509b7e41 100644
--- a/pkg/k8s/watchers/cilium_network_policy.go
+++ b/pkg/k8s/watchers/cilium_network_policy.go
@@ -301,24 +301,28 @@ func (k *K8sWatcher) updateCiliumNetworkPolicyV2(ciliumNPClient clientset.Interf
 
 	// Do not add rule into policy repository if the spec remains unchanged.
 	if !option.Config.DisableCNPStatusUpdates {
-		if !oldRuleCpy.AnnotationsEquals(newRuleCpy.CiliumNetworkPolicy) {
-
-			// Update annotations within a controller so the status of the update
-			// is trackable from the list of running controllers, and so we do
-			// not block subsequent policy lifecycle operations from Kubernetes
-			// until the update is complete.
-			oldCtrlName := oldRuleCpy.GetControllerName()
-			newCtrlName := newRuleCpy.GetControllerName()
-
-			// In case the controller name changes between copies of rules,
-			// remove old controller so we do not leak goroutines.
-			if oldCtrlName != newCtrlName {
-				err := k8sCM.RemoveController(oldCtrlName)
-				if err != nil {
-					log.Debugf("Unable to remove controller %s: %s", oldCtrlName, err)
+		if oldRuleCpy.Spec.DeepEqual(newRuleCpy.CiliumNetworkPolicy.Spec) &&
+			oldRuleCpy.Specs.DeepEqual(&newRuleCpy.CiliumNetworkPolicy.Specs) {
+			if !oldRuleCpy.AnnotationsEquals(newRuleCpy.CiliumNetworkPolicy) {
+
+				// Update annotations within a controller so the status of the update
+				// is trackable from the list of running controllers, and so we do
+				// not block subsequent policy lifecycle operations from Kubernetes
+				// until the update is complete.
+				oldCtrlName := oldRuleCpy.GetControllerName()
+				newCtrlName := newRuleCpy.GetControllerName()
+
+				// In case the controller name changes between copies of rules,
+				// remove old controller so we do not leak goroutines.
+				if oldCtrlName != newCtrlName {
+					err := k8sCM.RemoveController(oldCtrlName)
+					if err != nil {
+						log.Debugf("Unable to remove controller %s: %s", oldCtrlName, err)
+					}
 				}
+				k.updateCiliumNetworkPolicyV2AnnotationsOnly(ciliumNPClient, ciliumV2Store, newRuleCpy)
 			}
-			k.updateCiliumNetworkPolicyV2AnnotationsOnly(ciliumNPClient, ciliumV2Store, newRuleCpy)
+			return nil
 		}
 	}
 
diff --git a/pkg/k8s/watchers/pod.go b/pkg/k8s/watchers/pod.go
index 2f1c4683cb..af79d1fb08 100644
--- a/pkg/k8s/watchers/pod.go
+++ b/pkg/k8s/watchers/pod.go
@@ -483,13 +483,9 @@ func (k *K8sWatcher) genServiceMappings(pod *slim_corev1.Pod, podIPs []string, l
 				continue
 			}
 
-			proto, err := loadbalancer.NewL4Type(string(p.Protocol))
-			if err != nil {
-				continue
-			}
-
 			var bes4 []loadbalancer.Backend
 			var bes6 []loadbalancer.Backend
+			proto := loadbalancer.NewL4Type(string(p.Protocol))
 
 			for _, podIP := range podIPs {
 				be := loadbalancer.Backend{
diff --git a/pkg/k8s/watchers/watcher.go b/pkg/k8s/watchers/watcher.go
index aa4bfb860c..7be8255f06 100644
--- a/pkg/k8s/watchers/watcher.go
+++ b/pkg/k8s/watchers/watcher.go
@@ -409,6 +409,9 @@ func (k *K8sWatcher) InitK8sSubsystem() <-chan struct{} {
 			// Pods can contain labels which are essential for endpoints
 			// being restored to have the right identity.
 			K8sAPIGroupPodV1Core,
+			// We need to know about active local redirect policy services
+			// before BPF LB datapath is synced.
+			k8sAPIGroupCiliumLocalRedirectPolicyV2,
 		)
 		// CiliumEndpoint is used to synchronize the ipcache, wait for
 		// it unless it is disabled
@@ -602,16 +605,8 @@ func (k *K8sWatcher) delK8sSVCs(svc k8s.ServiceID, svcInfo *k8s.Service, se *k8s
 		logfields.K8sNamespace: svc.Namespace,
 	})
 
-	repPorts := svcInfo.UniquePorts()
-
 	frontends := []*loadbalancer.L3n4Addr{}
-
 	for portName, svcPort := range svcInfo.Ports {
-		if !repPorts[svcPort.Port] {
-			continue
-		}
-		repPorts[svcPort.Port] = false
-
 		fe := loadbalancer.NewL3n4Addr(svcPort.Protocol, svcInfo.FrontendIP, svcPort.Port, loadbalancer.ScopeExternal)
 		frontends = append(frontends, fe)
 
@@ -725,14 +720,8 @@ func genCartesianProduct(
 
 // datapathSVCs returns all services that should be set in the datapath.
 func datapathSVCs(svc *k8s.Service, endpoints *k8s.Endpoints) (svcs []loadbalancer.SVC) {
-	uniqPorts := svc.UniquePorts()
-
 	clusterIPPorts := map[loadbalancer.FEPortName]*loadbalancer.L4Addr{}
 	for fePortName, fePort := range svc.Ports {
-		if !uniqPorts[fePort.Port] {
-			continue
-		}
-		uniqPorts[fePort.Port] = false
 		clusterIPPorts[fePortName] = fePort
 	}
 	if svc.FrontendIP != nil {
@@ -842,6 +831,7 @@ func (k *K8sWatcher) addK8sSVCs(svcID k8s.ServiceID, oldSvc, svc *k8s.Service, e
 			Name:                      svcID.Name,
 			Namespace:                 svcID.Namespace,
 		}
+		log.WithField(logfields.Object, logfields.Repr(*p)).Debug("upserting loadbalancer repr")
 		if _, _, err := k.svcManager.UpsertService(p); err != nil {
 			scopedLog.WithError(err).Error("Error while inserting service in LB map")
 		}
diff --git a/pkg/loadbalancer/loadbalancer.go b/pkg/loadbalancer/loadbalancer.go
index 508c392aff..4dd2b623ff 100644
--- a/pkg/loadbalancer/loadbalancer.go
+++ b/pkg/loadbalancer/loadbalancer.go
@@ -160,6 +160,7 @@ func (s ServiceFlags) UInt16() uint16 {
 }
 
 const (
+	// NONE type.
 	NONE = L4Type("NONE")
 	// TCP type.
 	TCP = L4Type("TCP")
@@ -265,14 +266,25 @@ func (s *SVC) GetModel() *models.Service {
 	}
 }
 
-func NewL4Type(name string) (L4Type, error) {
+func NewL4Type(name string) L4Type {
 	switch strings.ToLower(name) {
 	case "tcp":
-		return TCP, nil
+		return TCP
 	case "udp":
-		return UDP, nil
+		return UDP
 	default:
-		return "", fmt.Errorf("unknown L4 protocol")
+		return NONE
+	}
+}
+
+func NewL4TypeFromNumber(proto uint8) L4Type {
+	switch proto {
+	case 6:
+		return TCP
+	case 17:
+		return UDP
+	default:
+		return NONE
 	}
 }
 
@@ -307,6 +319,11 @@ func (l *L4Addr) DeepCopy() *L4Addr {
 	}
 }
 
+// String returns a string representation of an L4Addr
+func (l *L4Addr) String() string {
+	return fmt.Sprintf("%d/%s", l.Port, l.Protocol)
+}
+
 // L3n4Addr is used to store, as an unique L3+L4 address in the KVStore. It also
 // includes the lookup scope for frontend addresses which is used in service
 // handling for externalTrafficPolicy=Local, that is, Scope{External,Internal}.
@@ -336,16 +353,9 @@ func NewL3n4AddrFromModel(base *models.FrontendAddress) (*L3n4Addr, error) {
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	proto := NONE
-	if base.Protocol != "" {
-		p, err := NewL4Type(base.Protocol)
-		if err != nil {
-			return nil, err
-		}
-		proto = p
-	}
+	p := NewL4Type(base.Protocol)
 
-	l4addr := NewL4Addr(proto, base.Port)
+	l4addr := NewL4Addr(p, base.Port)
 	ip := net.ParseIP(base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", base.IP)
@@ -378,8 +388,8 @@ func NewBackendFromBackendModel(base *models.BackendAddress) (*Backend, error) {
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	// FIXME: Should this be NONE ?
-	l4addr := NewL4Addr(NONE, base.Port)
+	p := NewL4Type(base.Protocol)
+	l4addr := NewL4Addr(p, base.Port)
 	ip := net.ParseIP(*base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", *base.IP)
@@ -393,8 +403,8 @@ func NewL3n4AddrFromBackendModel(base *models.BackendAddress) (*L3n4Addr, error)
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	// FIXME: Should this be NONE ?
-	l4addr := NewL4Addr(NONE, base.Port)
+	p := NewL4Type(base.Protocol)
+	l4addr := NewL4Addr(p, base.Port)
 	ip := net.ParseIP(*base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", *base.IP)
@@ -412,9 +422,10 @@ func (a *L3n4Addr) GetModel() *models.FrontendAddress {
 		scope = models.FrontendAddressScopeInternal
 	}
 	return &models.FrontendAddress{
-		IP:    a.IP.String(),
-		Port:  a.Port,
-		Scope: scope,
+		IP:       a.IP.String(),
+		Port:     a.Port,
+		Scope:    scope,
+		Protocol: a.Protocol,
 	}
 }
 
@@ -428,25 +439,13 @@ func (b *Backend) GetBackendModel() *models.BackendAddress {
 		IP:       &ip,
 		Port:     b.Port,
 		NodeName: b.NodeName,
+		Protocol: b.Protocol,
 	}
 }
 
-// String returns the L3n4Addr in the "IPv4:Port[/Scope]" format for IPv4 and
-// "[IPv6]:Port[/Scope]" format for IPv6.
+// String returns the L3n4Addr in the "IPv4:Port/Protocol[/Scope]" format for IPv4 and
+// "[IPv6]:Port/Protocol[/Scope]" format for IPv6.
 func (a *L3n4Addr) String() string {
-	var scope string
-	if a.Scope == ScopeInternal {
-		scope = "/i"
-	}
-	if a.IsIPv6() {
-		return fmt.Sprintf("[%s]:%d%s", a.IP.String(), a.Port, scope)
-	}
-	return fmt.Sprintf("%s:%d%s", a.IP.String(), a.Port, scope)
-}
-
-// StringWithProtocol returns the L3n4Addr in the "IPv4:Port/Protocol[/Scope]"
-// format for IPv4 and "[IPv6]:Port/Protocol[/Scope]" format for IPv6.
-func (a *L3n4Addr) StringWithProtocol() string {
 	var scope string
 	if a.Scope == ScopeInternal {
 		scope = "/i"
@@ -477,14 +476,7 @@ func (a *L3n4Addr) DeepCopy() *L3n4Addr {
 
 // Hash calculates L3n4Addr's internal SHA256Sum.
 func (a L3n4Addr) Hash() string {
-	// FIXME: Remove Protocol's omission once we care about protocols.
-	protoBak := a.Protocol
-	a.Protocol = ""
-	defer func() {
-		a.Protocol = protoBak
-	}()
-
-	str := []byte(fmt.Sprintf("%+v", a))
+	str := []byte(fmt.Sprintf("%s", a.String()))
 	return fmt.Sprintf("%x", sha512.Sum512_256(str))
 }
 
diff --git a/pkg/maps/lbmap/ipv4.go b/pkg/maps/lbmap/ipv4.go
index bc2751e1bd..523f20f908 100644
--- a/pkg/maps/lbmap/ipv4.go
+++ b/pkg/maps/lbmap/ipv4.go
@@ -171,11 +171,28 @@ func NewService4Key(ip net.IP, port uint16, proto u8proto.U8proto, scope uint8,
 }
 
 func (k *Service4Key) String() string {
-	addr := net.JoinHostPort(k.Address.String(), fmt.Sprintf("%d", k.Port))
-	if k.Scope == loadbalancer.ScopeInternal {
-		addr += "/i"
+	return serviceKey(
+		k.Address.String(),
+		k.Port,
+		k.GetProtocol(),
+		k.Scope == loadbalancer.ScopeInternal,
+	)
+}
+
+func serviceKey(address string, port uint16, protocol uint8, internal bool) string {
+	a := net.JoinHostPort(address, fmt.Sprintf("%d", port))
+
+	p, err := u8proto.FromNumber(protocol)
+	if err != nil {
+		p = u8proto.ANY
+	}
+
+	var i string
+	if internal {
+		i = "/i"
 	}
-	return addr
+
+	return fmt.Sprintf("%s/%s%s", a, p, i)
 }
 
 func (k *Service4Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }
@@ -189,6 +206,7 @@ func (k *Service4Key) SetScope(scope uint8)      { k.Scope = scope }
 func (k *Service4Key) GetScope() uint8           { return k.Scope }
 func (k *Service4Key) GetAddress() net.IP        { return k.Address.IP() }
 func (k *Service4Key) GetPort() uint16           { return k.Port }
+func (k *Service4Key) GetProtocol() uint8        { return k.Proto }
 func (k *Service4Key) MapDelete() error          { return k.Map().Delete(k.ToNetwork()) }
 
 func (k *Service4Key) RevNatValue() RevNatValue {
@@ -313,6 +331,7 @@ func (v *Backend4Value) GetValuePtr() unsafe.Pointer { return unsafe.Pointer(v)
 
 func (b *Backend4Value) GetAddress() net.IP { return b.Address.IP() }
 func (b *Backend4Value) GetPort() uint16    { return b.Port }
+func (b *Backend4Value) GetProtocol() uint8 { return uint8(b.Proto) }
 
 func (v *Backend4Value) ToNetwork() BackendValue {
 	n := *v
diff --git a/pkg/maps/lbmap/ipv6.go b/pkg/maps/lbmap/ipv6.go
index 25b3a96cbb..1d20e2dba8 100644
--- a/pkg/maps/lbmap/ipv6.go
+++ b/pkg/maps/lbmap/ipv6.go
@@ -164,11 +164,12 @@ func NewService6Key(ip net.IP, port uint16, proto u8proto.U8proto, scope uint8,
 }
 
 func (k *Service6Key) String() string {
-	if k.Scope == loadbalancer.ScopeInternal {
-		return fmt.Sprintf("[%s]:%d/i", k.Address, k.Port)
-	} else {
-		return fmt.Sprintf("[%s]:%d", k.Address, k.Port)
-	}
+	return serviceKey(
+		k.Address.String(),
+		k.Port,
+		k.GetProtocol(),
+		k.Scope == loadbalancer.ScopeInternal,
+	)
 }
 
 func (k *Service6Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }
@@ -182,6 +183,7 @@ func (k *Service6Key) SetScope(scope uint8)      { k.Scope = scope }
 func (k *Service6Key) GetScope() uint8           { return k.Scope }
 func (k *Service6Key) GetAddress() net.IP        { return k.Address.IP() }
 func (k *Service6Key) GetPort() uint16           { return k.Port }
+func (k *Service6Key) GetProtocol() uint8        { return k.Proto }
 func (k *Service6Key) MapDelete() error          { return k.Map().Delete(k.ToNetwork()) }
 
 func (k *Service6Key) RevNatValue() RevNatValue {
@@ -297,6 +299,7 @@ func (v *Backend6Value) GetValuePtr() unsafe.Pointer { return unsafe.Pointer(v)
 
 func (b *Backend6Value) GetAddress() net.IP { return b.Address.IP() }
 func (b *Backend6Value) GetPort() uint16    { return b.Port }
+func (b *Backend6Value) GetProtocol() uint8 { return uint8(b.Proto) }
 
 func (v *Backend6Value) ToNetwork() BackendValue {
 	n := *v
diff --git a/pkg/maps/lbmap/lbmap.go b/pkg/maps/lbmap/lbmap.go
index d8f74528b5..b04c77d6a7 100644
--- a/pkg/maps/lbmap/lbmap.go
+++ b/pkg/maps/lbmap/lbmap.go
@@ -17,6 +17,7 @@ package lbmap
 import (
 	"fmt"
 	"net"
+	"sort"
 	"strconv"
 
 	"github.com/cilium/cilium/pkg/bpf"
@@ -63,6 +64,7 @@ type UpsertServiceParams struct {
 	ID                        uint16
 	IP                        net.IP
 	Port                      uint16
+	Protocol                  string
 	Backends                  map[string]uint16
 	PrevBackendCount          int
 	IPv6                      bool
@@ -89,10 +91,15 @@ func (lbmap *LBBPFMap) UpsertService(p *UpsertServiceParams) error {
 		return fmt.Errorf("Invalid svc ID 0")
 	}
 
+	proto, err := u8proto.ParseProtocol(p.Protocol)
+	if err != nil {
+		return err
+	}
+
 	if p.IPv6 {
-		svcKey = NewService6Key(p.IP, p.Port, u8proto.ANY, p.Scope, 0)
+		svcKey = NewService6Key(p.IP, p.Port, proto, p.Scope, 0)
 	} else {
-		svcKey = NewService4Key(p.IP, p.Port, u8proto.ANY, p.Scope, 0)
+		svcKey = NewService4Key(p.IP, p.Port, proto, p.Scope, 0)
 	}
 
 	slot := 1
@@ -103,6 +110,11 @@ func (lbmap *LBBPFMap) UpsertService(p *UpsertServiceParams) error {
 		for name := range p.Backends {
 			backendNames = append(backendNames, name)
 		}
+		// Maglev algorithm might produce different lookup table for the same
+		// set of backends listed in a different order. To avoid that sort
+		// backends by name, as the names are the same on all nodes (in opposite
+		// to backend IDs which are node-local).
+		sort.Strings(backendNames)
 		table := maglev.GetLookupTable(backendNames, lbmap.maglevTableSize)
 		for i, pos := range table {
 			lbmap.maglevBackendIDsBuffer[i] = p.Backends[backendNames[pos]]
@@ -170,12 +182,16 @@ func (*LBBPFMap) DeleteService(svc loadbalancer.L3n4AddrID, backendCount int, us
 		return fmt.Errorf("Invalid svc ID 0")
 	}
 
+	proto, err := u8proto.ParseProtocol(string(svc.Protocol))
+	if err != nil {
+		return err
+	}
 	ipv6 := svc.IsIPv6()
 	if ipv6 {
-		svcKey = NewService6Key(svc.IP, svc.Port, u8proto.ANY, svc.Scope, 0)
+		svcKey = NewService6Key(svc.IP, svc.Port, proto, svc.Scope, 0)
 		revNATKey = NewRevNat6Key(uint16(svc.ID))
 	} else {
-		svcKey = NewService4Key(svc.IP, svc.Port, u8proto.ANY, svc.Scope, 0)
+		svcKey = NewService4Key(svc.IP, svc.Port, proto, svc.Scope, 0)
 		revNATKey = NewRevNat4Key(uint16(svc.ID))
 	}
 
@@ -200,7 +216,7 @@ func (*LBBPFMap) DeleteService(svc loadbalancer.L3n4AddrID, backendCount int, us
 }
 
 // AddBackend adds a backend into a BPF map.
-func (*LBBPFMap) AddBackend(id uint16, ip net.IP, port uint16, ipv6 bool) error {
+func (*LBBPFMap) AddBackend(id uint16, ip net.IP, protocol loadbalancer.L4Type, port uint16, ipv6 bool) error {
 	var (
 		backend Backend
 		err     error
@@ -210,10 +226,14 @@ func (*LBBPFMap) AddBackend(id uint16, ip net.IP, port uint16, ipv6 bool) error
 		return fmt.Errorf("Invalid backend ID 0")
 	}
 
+	p, err := u8proto.ParseProtocol(string(protocol))
+	if err != nil {
+		return err
+	}
 	if ipv6 {
-		backend, err = NewBackend6(loadbalancer.BackendID(id), ip, port, u8proto.ANY)
+		backend, err = NewBackend6(loadbalancer.BackendID(id), ip, port, p)
 	} else {
-		backend, err = NewBackend4(loadbalancer.BackendID(id), ip, port, u8proto.ANY)
+		backend, err = NewBackend4(loadbalancer.BackendID(id), ip, port, p)
 	}
 	if err != nil {
 		return fmt.Errorf("Unable to create backend (%d, %s, %d, %t): %s",
@@ -467,7 +487,7 @@ func (*LBBPFMap) DumpBackendMaps() ([]*loadbalancer.Backend, error) {
 	for backendID, backendVal := range backendValueMap {
 		ip := backendVal.GetAddress()
 		port := backendVal.GetPort()
-		proto := loadbalancer.NONE
+		proto := loadbalancer.NewL4TypeFromNumber(backendVal.GetProtocol())
 		lbBackend := loadbalancer.NewBackend(backendID, proto, ip, port)
 		lbBackends = append(lbBackends, lbBackend)
 	}
diff --git a/pkg/maps/lbmap/lbmap_mock.go b/pkg/maps/lbmap/lbmap_mock.go
index 969b867c0a..c3aa4022f5 100644
--- a/pkg/maps/lbmap/lbmap_mock.go
+++ b/pkg/maps/lbmap/lbmap_mock.go
@@ -19,6 +19,7 @@ import (
 	"net"
 
 	"github.com/cilium/cilium/pkg/cidr"
+	"github.com/cilium/cilium/pkg/loadbalancer"
 	lb "github.com/cilium/cilium/pkg/loadbalancer"
 )
 
@@ -50,7 +51,7 @@ func (m *LBMockMap) UpsertService(p *UpsertServiceParams) error {
 
 	svc, found := m.ServiceByID[p.ID]
 	if !found {
-		frontend := lb.NewL3n4AddrID(lb.NONE, p.IP, p.Port, p.Scope, lb.ID(p.ID))
+		frontend := lb.NewL3n4AddrID(loadbalancer.NewL4Type(p.Protocol), p.IP, p.Port, p.Scope, lb.ID(p.ID))
 		svc = &lb.SVC{Frontend: *frontend}
 	} else {
 		if p.PrevBackendCount != len(svc.Backends) {
@@ -81,12 +82,12 @@ func (m *LBMockMap) DeleteService(addr lb.L3n4AddrID, backendCount int, maglev b
 	return nil
 }
 
-func (m *LBMockMap) AddBackend(id uint16, ip net.IP, port uint16, ipv6 bool) error {
+func (m *LBMockMap) AddBackend(id uint16, ip net.IP, protocol lb.L4Type, port uint16, ipv6 bool) error {
 	if _, found := m.BackendByID[id]; found {
 		return fmt.Errorf("Backend %d already exists", id)
 	}
 
-	m.BackendByID[id] = lb.NewBackend(lb.BackendID(id), lb.NONE, ip, port)
+	m.BackendByID[id] = lb.NewBackend(lb.BackendID(id), protocol, ip, port)
 
 	return nil
 }
diff --git a/pkg/maps/lbmap/maglev.go b/pkg/maps/lbmap/maglev.go
index 9dd0a09d3d..bfd8821a40 100644
--- a/pkg/maps/lbmap/maglev.go
+++ b/pkg/maps/lbmap/maglev.go
@@ -39,7 +39,7 @@ var MaglevOuter6Map *bpf.Map
 
 func InitMaglevMaps(ipv4 bool, ipv6 bool) error {
 	dummyInnerMap := newInnerMaglevMap("cilium_lb_maglev_dummy")
-	if _, err := dummyInnerMap.OpenOrCreateUnpinned(); err != nil {
+	if err := dummyInnerMap.CreateUnpinned(); err != nil {
 		return err
 	}
 	defer dummyInnerMap.Close()
@@ -93,7 +93,7 @@ func updateMaglevTable(ipv6 bool, revNATID uint16, backendIDs []uint16) error {
 	}
 
 	innerMap := newInnerMaglevMap(innerMapName)
-	if _, err := innerMap.OpenOrCreateUnpinned(); err != nil {
+	if err := innerMap.CreateUnpinned(); err != nil {
 		return err
 	}
 	defer innerMap.Close()
diff --git a/pkg/maps/lbmap/types.go b/pkg/maps/lbmap/types.go
index f8eefee7ac..3260ec1d33 100644
--- a/pkg/maps/lbmap/types.go
+++ b/pkg/maps/lbmap/types.go
@@ -53,6 +53,9 @@ type ServiceKey interface {
 	// Get frontend port
 	GetPort() uint16
 
+	// Get protocol
+	GetProtocol() uint8
+
 	// Returns a RevNatValue matching a ServiceKey
 	RevNatValue() RevNatValue
 
@@ -125,6 +128,9 @@ type BackendValue interface {
 	// Get backend port
 	GetPort() uint16
 
+	// Get backend protocol
+	GetProtocol() uint8
+
 	// Convert fields to network byte order.
 	ToNetwork() BackendValue
 }
@@ -168,7 +174,8 @@ type BackendIDByServiceIDSet map[uint16]map[uint16]struct{} // svc ID => backend
 type SourceRangeSetByServiceID map[uint16][]*cidr.CIDR // svc ID => src range CIDRs
 
 func svcFrontend(svcKey ServiceKey, svcValue ServiceValue) *loadbalancer.L3n4AddrID {
-	feL3n4Addr := loadbalancer.NewL3n4Addr(loadbalancer.NONE, svcKey.GetAddress(), svcKey.GetPort(), svcKey.GetScope())
+	p := loadbalancer.NewL4TypeFromNumber(svcKey.GetProtocol())
+	feL3n4Addr := loadbalancer.NewL3n4Addr(p, svcKey.GetAddress(), svcKey.GetPort(), svcKey.GetScope())
 	feL3n4AddrID := &loadbalancer.L3n4AddrID{
 		L3n4Addr: *feL3n4Addr,
 		ID:       loadbalancer.ID(svcValue.GetRevNat()),
@@ -179,7 +186,7 @@ func svcFrontend(svcKey ServiceKey, svcValue ServiceValue) *loadbalancer.L3n4Add
 func svcBackend(backendID loadbalancer.BackendID, backend BackendValue) *loadbalancer.Backend {
 	beIP := backend.GetAddress()
 	bePort := backend.GetPort()
-	beProto := loadbalancer.NONE
-	beBackend := loadbalancer.NewBackend(backendID, beProto, beIP, bePort)
+	p := loadbalancer.NewL4TypeFromNumber(backend.GetProtocol())
+	beBackend := loadbalancer.NewBackend(backendID, p, beIP, bePort)
 	return beBackend
 }
diff --git a/pkg/maps/sockmap/sockmap.go b/pkg/maps/sockmap/sockmap.go
index f6731d7fff..9fa63e017b 100644
--- a/pkg/maps/sockmap/sockmap.go
+++ b/pkg/maps/sockmap/sockmap.go
@@ -23,19 +23,20 @@ import (
 	"github.com/cilium/cilium/pkg/logging"
 	"github.com/cilium/cilium/pkg/logging/logfields"
 	"github.com/cilium/cilium/pkg/types"
+	"github.com/cilium/cilium/pkg/u8proto"
 )
 
 // SockmapKey is the 5-tuple used to lookup a socket
 // +k8s:deepcopy-gen=true
 // +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapKey
 type SockmapKey struct {
-	DIP    types.IPv6 `align:"$union0"`
-	SIP    types.IPv6 `align:"$union1"`
-	Family uint8      `align:"family"`
-	Pad7   uint8      `align:"pad7"`
-	Pad8   uint16     `align:"pad8"`
-	SPort  uint32     `align:"sport"`
-	DPort  uint32     `align:"dport"`
+	DIP      types.IPv6 `align:"$union0"`
+	SIP      types.IPv6 `align:"$union1"`
+	Family   uint8      `align:"family"`
+	Protocol uint8      `align:"protocol"`
+	Pad8     uint16     `align:"pad8"`
+	SPort    uint32     `align:"sport"`
+	DPort    uint32     `align:"dport"`
 }
 
 // SockmapValue is the fd of a socket
@@ -47,7 +48,11 @@ type SockmapValue struct {
 
 // String pretty print the 5-tuple as sip:sport->dip:dport
 func (v SockmapKey) String() string {
-	return fmt.Sprintf("%s:%d->%s:%d", v.SIP.String(), v.SPort, v.DIP.String(), v.DPort)
+	p, err := u8proto.FromNumber(v.Protocol)
+	if err != nil {
+		p = u8proto.ANY
+	}
+	return fmt.Sprintf("%s:%d/%s->%s:%d/%s", v.SIP.String(), v.SPort, p, v.DIP.String(), v.DPort, p)
 }
 
 // String pretty print the file descriptor value, note this is local to agent.
diff --git a/pkg/monitor/api/drop.go b/pkg/monitor/api/drop.go
index e07a10c94a..b6d3a167f3 100644
--- a/pkg/monitor/api/drop.go
+++ b/pkg/monitor/api/drop.go
@@ -18,7 +18,7 @@ import (
 	"fmt"
 )
 
-// DropMin numbers below this are non-drop reason codes
+// DropMin numbers less than this are non-drop reason codes
 var DropMin uint8 = 130
 
 // DropInvalid is the Invalid packet reason.
diff --git a/pkg/node/address_darwin.go b/pkg/node/address_darwin.go
index c38d77cf56..b4ba6cee72 100644
--- a/pkg/node/address_darwin.go
+++ b/pkg/node/address_darwin.go
@@ -20,11 +20,11 @@ import (
 	"net"
 )
 
-func firstGlobalV4Addr(intf string, preferredIP net.IP) (net.IP, error) {
+func firstGlobalV4Addr(intf string, preferredIP net.IP, preferPublic bool) (net.IP, error) {
 	return net.IP{}, nil
 }
 
-func firstGlobalV6Addr(intf string, preferredIP net.IP) (net.IP, error) {
+func firstGlobalV6Addr(intf string, preferredIP net.IP, preferPublic bool) (net.IP, error) {
 	return net.IP{}, nil
 }
 
diff --git a/pkg/option/config.go b/pkg/option/config.go
index 54a61ba5bb..1a508e8fbd 100644
--- a/pkg/option/config.go
+++ b/pkg/option/config.go
@@ -1503,7 +1503,7 @@ type DaemonConfig struct {
 	// runs in the same container as Cilium.
 	EnvoyLogPath string
 
-	// EnableSockOps specifies whether to enable sockops (socket lookup).
+	// SockopsEnable specifies whether to enable sockops (socket lookup).
 	SockopsEnable bool
 
 	// PrependIptablesChains is the name of the option to enable prepending
diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go
index d1b81b313b..db7899f195 100644
--- a/pkg/probe/probe.go
+++ b/pkg/probe/probe.go
@@ -72,7 +72,7 @@ func HaveFullLPM() bool {
 			&probeKey{}, int(unsafe.Sizeof(probeKey{})),
 			&probeValue{}, int(unsafe.Sizeof(probeValue{})),
 			1, bpf.BPF_F_NO_PREALLOC, 0, bpf.ConvertKeyValue).WithCache()
-		_, err := m.OpenOrCreateUnpinned()
+		err := m.CreateUnpinned()
 		defer m.Close()
 		if err != nil {
 			return
diff --git a/pkg/rate/api_limiter.go b/pkg/rate/api_limiter.go
index 20e4284dba..85814a9e81 100644
--- a/pkg/rate/api_limiter.go
+++ b/pkg/rate/api_limiter.go
@@ -44,20 +44,27 @@ const (
 	// requirement is implemented
 	waitSemaphoreResolution = 10000000
 
-	logUUID                    = "uuid"
-	logAPICallName             = "name"
-	logProcessingDuration      = "processingDuration"
-	logParallelRequests        = "parallelRequests"
-	logMinWaitDuration         = "minWaitDuration"
-	logMaxWaitDuration         = "maxWaitDuration"
-	logMaxWaitDurationParallel = "maxWaitDurationParallel"
-	logWaitDurationLimit       = "waitDurationLimiter"
-	logWaitDurationTotal       = "waitDurationTotal"
-	logLimit                   = "limit"
-	logBurst                   = "burst"
-	logTotalDuration           = "totalDuration"
-	logError                   = "err"
-	logSkipped                 = "rateLimiterSkipped"
+	logUUID                   = "uuid"
+	logAPICallName            = "name"
+	logProcessingDuration     = "processingDuration"
+	logParallelRequests       = "parallelRequests"
+	logMinWaitDuration        = "minWaitDuration"
+	logMaxWaitDuration        = "maxWaitDuration"
+	logMaxWaitDurationLimiter = "maxWaitDurationLimiter"
+	logWaitDurationLimit      = "waitDurationLimiter"
+	logWaitDurationTotal      = "waitDurationTotal"
+	logLimit                  = "limit"
+	logBurst                  = "burst"
+	logTotalDuration          = "totalDuration"
+	logSkipped                = "rateLimiterSkipped"
+)
+
+type outcome string
+
+const (
+	outcomeParallelMaxWait outcome = "fail-parallel-wait"
+	outcomeLimitMaxWait    outcome = "fail-limit-wait"
+	outcomeReqCancelled            = "request-cancelled"
 )
 
 // APILimiter is an extension to x/time/rate.Limiter specifically for Cilium
@@ -415,7 +422,11 @@ func (l *APILimiter) requestFinished(r *limitedRequest, err error) {
 
 	r.finished = true
 
-	processingDuration := time.Since(r.startTime)
+	var processingDuration time.Duration
+	if !r.startTime.IsZero() {
+		processingDuration = time.Since(r.startTime)
+	}
+
 	totalDuration := time.Since(r.scheduleTime)
 
 	scopedLog := log.WithFields(logrus.Fields{
@@ -427,7 +438,7 @@ func (l *APILimiter) requestFinished(r *limitedRequest, err error) {
 	})
 
 	if err != nil {
-		scopedLog = scopedLog.WithField(logError, err)
+		scopedLog = scopedLog.WithError(err)
 	}
 
 	if l.params.Log {
@@ -442,30 +453,35 @@ func (l *APILimiter) requestFinished(r *limitedRequest, err error) {
 
 	l.mutex.Lock()
 
-	l.requestsProcessed++
-	l.currentRequestsInFlight--
-
-	l.processingDurations = append(l.processingDurations, processingDuration)
-	if exceed := len(l.processingDurations) - l.params.MeanOver; exceed > 0 {
-		l.processingDurations = l.processingDurations[exceed:]
+	if !r.startTime.IsZero() {
+		l.requestsProcessed++
+		l.currentRequestsInFlight--
 	}
-	l.meanProcessingDuration = calcMeanDuration(l.processingDurations)
 
-	l.waitDurations = append(l.waitDurations, r.waitDuration)
-	if exceed := len(l.waitDurations) - l.params.MeanOver; exceed > 0 {
-		l.waitDurations = l.waitDurations[exceed:]
-	}
-	l.meanWaitDuration = calcMeanDuration(l.waitDurations)
+	// Only auto-adjust ratelimiter using metrics from successful API requests
+	if err == nil {
+		l.processingDurations = append(l.processingDurations, processingDuration)
+		if exceed := len(l.processingDurations) - l.params.MeanOver; exceed > 0 {
+			l.processingDurations = l.processingDurations[exceed:]
+		}
+		l.meanProcessingDuration = calcMeanDuration(l.processingDurations)
 
-	if l.params.AutoAdjust && l.params.EstimatedProcessingDuration != 0 {
-		l.adjustmentFactor = l.calculateAdjustmentFactor()
-		l.parallelRequests = l.adjustedParallelRequests()
+		l.waitDurations = append(l.waitDurations, r.waitDuration)
+		if exceed := len(l.waitDurations) - l.params.MeanOver; exceed > 0 {
+			l.waitDurations = l.waitDurations[exceed:]
+		}
+		l.meanWaitDuration = calcMeanDuration(l.waitDurations)
 
-		if l.limiter != nil {
-			l.limiter.SetLimit(l.adjustedLimit())
+		if l.params.AutoAdjust && l.params.EstimatedProcessingDuration != 0 {
+			l.adjustmentFactor = l.calculateAdjustmentFactor()
+			l.parallelRequests = l.adjustedParallelRequests()
 
-			newBurst := l.adjustedBurst()
-			l.limiter.SetBurst(newBurst)
+			if l.limiter != nil {
+				l.limiter.SetLimit(l.adjustedLimit())
+
+				newBurst := l.adjustedBurst()
+				l.limiter.SetBurst(newBurst)
+			}
 		}
 	}
 
@@ -480,6 +496,7 @@ func (l *APILimiter) requestFinished(r *limitedRequest, err error) {
 		CurrentRequestsInFlight:     l.currentRequestsInFlight,
 		AdjustmentFactor:            l.adjustmentFactor,
 		Error:                       err,
+		Outcome:                     string(r.outcome),
 	}
 
 	if l.limiter != nil {
@@ -521,6 +538,7 @@ type limitedRequest struct {
 	waitSemaphoreWeight int64
 	uuid                string
 	finished            bool
+	outcome             outcome
 }
 
 // WaitDuration returns the duration the request had to wait
@@ -542,23 +560,34 @@ func (l *limitedRequest) Error(err error) {
 // configured MaxWaitDuration is exceeded, an error is returned. On success, a
 // LimitedRequest is returned on which Done() must be called when the API call
 // has completed or Error() if an error occurred.
-func (l *APILimiter) Wait(ctx context.Context) (req LimitedRequest, err error) {
+func (l *APILimiter) Wait(ctx context.Context) (LimitedRequest, error) {
+	req, err := l.wait(ctx)
+	if err != nil {
+		l.requestFinished(req, err)
+		return nil, err
+	}
+	return req, nil
+}
+
+func (l *APILimiter) wait(ctx context.Context) (req *limitedRequest, err error) {
 	var (
-		limitWaitDuration   time.Duration
-		waitDuration        time.Duration
-		waitSemaphoreWeight int64
-		uuid                = uuid.NewUUID().String()
-		scheduledAt         = time.Now()
-		r                   *rate.Reservation
+		limitWaitDuration time.Duration
+		r                 *rate.Reservation
 	)
 
+	req = &limitedRequest{
+		limiter:      l,
+		scheduleTime: time.Now(),
+		uuid:         uuid.NewUUID().String(),
+	}
+
 	l.mutex.Lock()
 
 	l.requestsScheduled++
 
 	scopedLog := log.WithFields(logrus.Fields{
 		logAPICallName:      l.name,
-		logUUID:             uuid,
+		logUUID:             req.uuid,
 		logParallelRequests: l.parallelRequests,
 	})
 
@@ -576,7 +605,9 @@ func (l *APILimiter) Wait(ctx context.Context) (req LimitedRequest, err error) {
 			scopedLog.Warning("Not processing API request due to cancelled context")
 		}
 		l.mutex.Unlock()
-		return nil, fmt.Errorf("request cancelled while waiting for rate limiting slot: %w", ctx.Err())
+		req.outcome = outcomeReqCancelled
+		err = fmt.Errorf("request cancelled while waiting for rate limiting slot: %w", ctx.Err())
+		return
 	default:
 	}
 
@@ -585,27 +616,8 @@ func (l *APILimiter) Wait(ctx context.Context) (req LimitedRequest, err error) {
 		scopedLog = scopedLog.WithField(logSkipped, skip)
 	}
 
-	if !skip && l.limiter != nil {
-		r = l.limiter.Reserve()
-		limitWaitDuration = r.Delay()
-
-		defer func() {
-			// In case the request is cancelled, also cancel the
-			// reservation so it does not impact the wait duration.
-			// The wait duration should only increase if requests
-			// are actually processed.
-			if err != nil && r != nil {
-				r.Cancel()
-			}
-		}()
-
-		scopedLog = scopedLog.WithFields(logrus.Fields{
-			logLimit:             fmt.Sprintf("%.2f/s", l.limiter.Limit()),
-			logBurst:             l.limiter.Burst(),
-			logWaitDurationLimit: limitWaitDuration,
-		})
-	}
 	parallelRequests := l.parallelRequests
+	meanProcessingDuration := l.meanProcessingDuration
 	l.mutex.Unlock()
 
 	if l.params.Log {
@@ -618,16 +630,69 @@ func (l *APILimiter) Wait(ctx context.Context) (req LimitedRequest, err error) {
 		goto skipRateLimiter
 	}
 
+	if parallelRequests > 0 {
+		waitCtx := ctx
+		if l.params.MaxWaitDuration > 0 {
+			ctx2, cancel := context.WithTimeout(ctx, l.params.MaxWaitDuration)
+			defer cancel()
+			waitCtx = ctx2
+		}
+		w := int64(waitSemaphoreResolution / parallelRequests)
+		err2 := l.parallelWaitSemaphore.Acquire(waitCtx, w)
+		if err2 != nil {
+			if l.params.Log {
+				scopedLog.WithError(err2).Warning("Not processing API request. Wait duration for maximum parallel requests exceeds maximum")
+			}
+			req.outcome = outcomeParallelMaxWait
+			err = fmt.Errorf("timed out while waiting to be served with %d parallel requests: %w", parallelRequests, err2)
+			return
+		}
+		req.waitSemaphoreWeight = w
+	}
+	req.waitDuration = time.Since(req.scheduleTime)
+
+	l.mutex.Lock()
+	if l.limiter != nil {
+		r = l.limiter.Reserve()
+		limitWaitDuration = r.Delay()
+
+		scopedLog = scopedLog.WithFields(logrus.Fields{
+			logLimit:                  fmt.Sprintf("%.2f/s", l.limiter.Limit()),
+			logBurst:                  l.limiter.Burst(),
+			logWaitDurationLimit:      limitWaitDuration,
+			logMaxWaitDurationLimiter: l.params.MaxWaitDuration - req.waitDuration,
+		})
+	}
+	l.mutex.Unlock()
+
 	if l.params.MinWaitDuration > 0 && limitWaitDuration < l.params.MinWaitDuration {
 		limitWaitDuration = l.params.MinWaitDuration
 	}
 
-	if (l.params.MaxWaitDuration > 0 && limitWaitDuration > l.params.MaxWaitDuration) || limitWaitDuration == rate.InfDuration {
+	if (l.params.MaxWaitDuration > 0 && (limitWaitDuration+req.waitDuration) > l.params.MaxWaitDuration) || limitWaitDuration == rate.InfDuration {
 		if l.params.Log {
 			scopedLog.Warning("Not processing API request. Wait duration exceeds maximum")
 		}
-		return nil, fmt.Errorf("request would have to wait %v to be served (maximum wait duration: %v)",
-			limitWaitDuration, l.params.MaxWaitDuration)
+
+		// The rate limiter should only consider a reservation valid if
+		// the request is actually processed. Cancellation of the
+		// reservation should happen before we sleep below.
+		if r != nil {
+			r.Cancel()
+		}
+
+		// Instead of returning immediately, pace the caller by
+		// sleeping for the mean processing duration. This helps
+		// against callers who disrespect 429 error codes and retry
+		// immediately.
+		if meanProcessingDuration > 0.0 {
+			time.Sleep(time.Duration(meanProcessingDuration * float64(time.Second)))
+		}
+
+		req.outcome = outcomeLimitMaxWait
+		err = fmt.Errorf("request would have to wait %v to be served (maximum wait duration: %v)",
+			limitWaitDuration, l.params.MaxWaitDuration-req.waitDuration)
+		return
 	}
 
 	if limitWaitDuration != 0 {
@@ -637,30 +702,19 @@ func (l *APILimiter) Wait(ctx context.Context) (req LimitedRequest, err error) {
 			if l.params.Log {
 				scopedLog.Warning("Not processing API request due to cancelled context while waiting")
 			}
-			return nil, fmt.Errorf("request cancelled while waiting for rate limiting slot: %w", ctx.Err())
-		}
-	}
-
-	if parallelRequests > 0 {
-		waitCtx := ctx
-		if l.params.MaxWaitDuration > 0 {
-			maxWaitDurationParallel := l.params.MaxWaitDuration - limitWaitDuration
-			scopedLog = scopedLog.WithField(logMaxWaitDurationParallel, maxWaitDurationParallel)
-			ctx2, cancel := context.WithTimeout(ctx, maxWaitDurationParallel)
-			defer cancel()
-			waitCtx = ctx2
-		}
-		waitSemaphoreWeight = int64(waitSemaphoreResolution / parallelRequests)
-		err := l.parallelWaitSemaphore.Acquire(waitCtx, waitSemaphoreWeight)
-		if err != nil {
-			if l.params.Log {
-				scopedLog.Warning("Not processing API request. Wait duration for maximum parallel requests exceeds maximum")
+			// The rate limiter should only consider a reservation
+			// valid if the request is actually processed.
+			if r != nil {
+				r.Cancel()
 			}
-			return nil, fmt.Errorf("timed out while waiting to be served with %d parallel requests: %w", parallelRequests, err)
+
+			req.outcome = outcomeReqCancelled
+			err = fmt.Errorf("request cancelled while waiting for rate limiting slot: %w", ctx.Err())
+			return
 		}
 	}
 
-	waitDuration = time.Since(scheduledAt)
+	req.waitDuration = time.Since(req.scheduleTime)
 
 skipRateLimiter:
 
@@ -668,26 +722,17 @@ skipRateLimiter:
 	l.currentRequestsInFlight++
 	l.mutex.Unlock()
 
-	log.WithFields(logrus.Fields{
-		logAPICallName:       l.name,
-		logUUID:              uuid,
-		logWaitDurationTotal: waitDuration,
-	}).Debug("API call is ready to be served")
+	scopedLog = scopedLog.WithField(logWaitDurationTotal, req.waitDuration)
 
 	if l.params.Log {
-		scopedLog.Info("Processing API request with rate limiter")
+		scopedLog.Info("API request released by rate limiter")
 	} else {
-		scopedLog.Debug("Processing API request with rate limiter")
+		scopedLog.Debug("API request released by rate limiter")
 	}
 
-	return &limitedRequest{
-		limiter:             l,
-		startTime:           time.Now(),
-		scheduleTime:        scheduledAt,
-		waitDuration:        waitDuration,
-		waitSemaphoreWeight: waitSemaphoreWeight,
-		uuid:                uuid,
-	}, nil
+	req.startTime = time.Now()
+	return req, nil
+
 }
 
 func parseRate(r string) (rate.Limit, error) {
@@ -732,6 +777,7 @@ type MetricsValues struct {
 	WaitDuration                time.Duration
 	MinWaitDuration             time.Duration
 	MaxWaitDuration             time.Duration
+	Outcome                     string
 	MeanProcessingDuration      float64
 	MeanWaitDuration            float64
 	EstimatedProcessingDuration float64
diff --git a/pkg/redirectpolicy/manager.go b/pkg/redirectpolicy/manager.go
index e8e7231381..6b1115cd2e 100644
--- a/pkg/redirectpolicy/manager.go
+++ b/pkg/redirectpolicy/manager.go
@@ -66,7 +66,7 @@ type Manager struct {
 	svcCache svcCache
 
 	// Mutex to protect against concurrent access to the maps
-	mutex lock.RWMutex
+	mutex lock.Mutex
 
 	// Stores mapping of all the current redirect policy frontend to their
 	// respective policies
@@ -75,9 +75,8 @@ type Manager struct {
 	// Stores mapping of redirect policy serviceID to the corresponding policyID for
 	// easy lookup in policyConfigs
 	policyServices map[k8s.ServiceID]policyID
-	// Stores mapping of podID to redirect policies that select this pod along
-	// with derived pod backends
-	policyPods map[podID][]podPolicyInfo
+	// Stores mapping of pods to redirect policies that select the pods
+	policyPods map[podID][]policyID
 	// Stores redirect policy configs indexed by policyID
 	policyConfigs map[policyID]*LRPConfig
 }
@@ -87,7 +86,7 @@ func NewRedirectPolicyManager(svc svcManager) *Manager {
 		svcManager:            svc,
 		policyFrontendsByHash: make(map[string]policyID),
 		policyServices:        make(map[k8s.ServiceID]policyID),
-		policyPods:            make(map[podID][]podPolicyInfo),
+		policyPods:            make(map[podID][]policyID),
 		policyConfigs:         make(map[policyID]*LRPConfig),
 	}
 }
@@ -132,7 +131,7 @@ func (rpm *Manager) AddRedirectPolicy(config LRPConfig, podStore cache.Store) (b
 		if len(pods) == 0 {
 			return true, nil
 		}
-		rpm.upsertConfig(&config, pods...)
+		rpm.processConfig(&config, pods...)
 
 	case lrpConfigTypeSvc:
 		log.WithFields(logrus.Fields{
@@ -172,10 +171,10 @@ func (rpm *Manager) DeleteRedirectPolicy(config LRPConfig) error {
 	}
 
 	for p, pp := range rpm.policyPods {
-		var newPolicyList []podPolicyInfo
-		for _, info := range pp {
-			if info.policyID != storedConfig.id {
-				newPolicyList = append(newPolicyList, info)
+		var newPolicyList []policyID
+		for _, policy := range pp {
+			if policy != storedConfig.id {
+				newPolicyList = append(newPolicyList, policy)
 			}
 		}
 		if len(newPolicyList) > 0 {
@@ -238,10 +237,10 @@ func (rpm *Manager) OnAddPod(pod *slimcorev1.Pod) {
 	if _, ok := rpm.policyPods[id]; ok {
 		return
 	}
-	rpm.OnUpdatePodLocked(pod)
+	rpm.onUpdatePodLocked(pod)
 }
 
-func (rpm *Manager) OnUpdatePodLocked(pod *slimcorev1.Pod) {
+func (rpm *Manager) onUpdatePodLocked(pod *slimcorev1.Pod) {
 	if len(rpm.policyConfigs) == 0 {
 		return
 	}
@@ -250,19 +249,26 @@ func (rpm *Manager) OnUpdatePodLocked(pod *slimcorev1.Pod) {
 	if err != nil {
 		return
 	}
-	podData := rpm.getPodMetadata(pod, podIPs)
+	var podData *podMetadata
+	if podData, err = rpm.getPodMetadata(pod, podIPs); err != nil {
+		log.WithError(err).WithFields(logrus.Fields{
+			logfields.K8sPodName:   pod.Name,
+			logfields.K8sNamespace: pod.Namespace,
+		}).Error("failed to get valid pod metadata")
+		return
+	}
 
 	// Check if the pod was previously selected by any of the policies.
 	if policies, ok := rpm.policyPods[podData.id]; ok {
-		for _, podInfo := range policies {
-			config := rpm.policyConfigs[podInfo.policyID]
-			rpm.deletePolicyBackends(config, podInfo.backends...)
+		for _, policy := range policies {
+			config := rpm.policyConfigs[policy]
+			rpm.deletePolicyBackends(config, podData.id)
 		}
 	}
 	// Check if any of the current redirect policies select this pod.
 	for _, config := range rpm.policyConfigs {
 		if config.policyConfigSelectsPod(podData) {
-			rpm.upsertConfig(config, podData)
+			rpm.processConfig(config, podData)
 		}
 	}
 }
@@ -271,7 +277,7 @@ func (rpm *Manager) OnUpdatePod(pod *slimcorev1.Pod) {
 	rpm.mutex.Lock()
 	defer rpm.mutex.Unlock()
 	// TODO add unit test to validate that we get callbacks only for relevant events
-	rpm.OnUpdatePodLocked(pod)
+	rpm.onUpdatePodLocked(pod)
 }
 
 func (rpm *Manager) OnDeletePod(pod *slimcorev1.Pod) {
@@ -286,9 +292,9 @@ func (rpm *Manager) OnDeletePod(pod *slimcorev1.Pod) {
 	}
 
 	if policies, ok := rpm.policyPods[id]; ok {
-		for _, podInfo := range policies {
-			config := rpm.policyConfigs[podInfo.policyID]
-			rpm.deletePolicyBackends(config, podInfo.backends...)
+		for _, policy := range policies {
+			config := rpm.policyConfigs[policy]
+			rpm.deletePolicyBackends(config, id)
 		}
 		delete(rpm.policyPods, id)
 	}
@@ -317,7 +323,6 @@ type podMetadata struct {
 // getAndUpsertPolicySvcConfig gets service frontends for the given config service
 // and upserts the service frontends.
 func (rpm *Manager) getAndUpsertPolicySvcConfig(config *LRPConfig, podStore cache.Store) {
-	var svcFrontends []*frontend
 	switch config.frontendType {
 	case svcFrontendAll:
 		// Get all the service frontends.
@@ -330,9 +335,6 @@ func (rpm *Manager) getAndUpsertPolicySvcConfig(config *LRPConfig, podStore cach
 				fePort: string(p),
 			}
 			config.frontendMappings = append(config.frontendMappings, feM)
-			svcFrontends = append(svcFrontends, addr)
-		}
-		for _, addr := range svcFrontends {
 			rpm.updateConfigSvcFrontend(config, addr)
 		}
 
@@ -351,16 +353,13 @@ func (rpm *Manager) getAndUpsertPolicySvcConfig(config *LRPConfig, podStore cach
 		ip := rpm.svcCache.GetServiceFrontendIP(*config.serviceID, lb.SVCTypeClusterIP)
 		for _, feM := range config.frontendMappings {
 			feM.feAddr.IP = ip
-			svcFrontends = append(svcFrontends, feM.feAddr)
-		}
-		for _, addr := range svcFrontends {
-			rpm.updateConfigSvcFrontend(config, addr)
+			rpm.updateConfigSvcFrontend(config, feM.feAddr)
 		}
 	}
 
 	pods := rpm.getLocalPodsForPolicy(config, podStore)
 	if len(pods) > 0 {
-		rpm.upsertConfig(config, pods...)
+		rpm.processConfig(config, pods...)
 	}
 
 }
@@ -399,23 +398,17 @@ func (rpm *Manager) updateConfigSvcFrontend(config *LRPConfig, frontends ...*fro
 	rpm.policyConfigs[config.id] = config
 }
 
-func (rpm *Manager) filterBackends(fe *feMapping, backends ...backend) []backend {
-	var newBackends []backend
-	for _, currBk := range fe.backends {
-		for _, removeBk := range backends {
-			if removeBk.StringWithProtocol() != currBk.StringWithProtocol() {
-				newBackends = append(newBackends, currBk)
+func (rpm *Manager) deletePolicyBackends(config *LRPConfig, podID podID) {
+	for _, fe := range config.frontendMappings {
+		newBes := make([]backend, 0, len(fe.podBackends))
+		for _, be := range fe.podBackends {
+			// Remove the pod from the frontend's backends slice, keeping the
+			// order same.
+			if be.podID != podID {
+				newBes = append(newBes, be)
 			}
 		}
-	}
-	return newBackends
-}
-
-func (rpm *Manager) deletePolicyBackends(config *LRPConfig, backends ...backend) {
-	// Currently, we expect number of LRP backends to be a single digit number.
-	// If this scales up, we might need to optimize this using sets.
-	for _, fe := range config.frontendMappings {
-		fe.backends = rpm.filterBackends(fe, backends...)
+		fe.podBackends = newBes
 		rpm.notifyPolicyBackendDelete(config, fe)
 	}
 }
@@ -432,7 +425,7 @@ func (rpm *Manager) deletePolicyFrontend(config *LRPConfig, frontend *frontend)
 
 // Updates service manager with the new set of backends now configured in 'config'.
 func (rpm *Manager) notifyPolicyBackendDelete(config *LRPConfig, frontendMapping *feMapping) {
-	if len(frontendMapping.backends) > 0 {
+	if len(frontendMapping.podBackends) > 0 {
 		rpm.upsertService(config, frontendMapping)
 	} else {
 		// No backends so remove the service entry.
@@ -479,10 +472,10 @@ func (rpm *Manager) upsertService(config *LRPConfig, frontendMapping *feMapping)
 		ID:       lb.ID(0),
 	}
 	var backendAddrs []lb.Backend
-	for _, be := range frontendMapping.backends {
+	for _, be := range frontendMapping.podBackends {
 		backendAddrs = append(backendAddrs, lb.Backend{
 			NodeName: nodeTypes.GetName(),
-			L3n4Addr: be,
+			L3n4Addr: be.L3n4Addr,
 		})
 	}
 	p := &lb.SVC{
@@ -501,7 +494,10 @@ func (rpm *Manager) upsertService(config *LRPConfig, frontendMapping *feMapping)
 
 // Returns a slice of endpoint pods metadata that are selected by the given policy config.
 func (rpm *Manager) getLocalPodsForPolicy(config *LRPConfig, podStore cache.Store) []*podMetadata {
-	var retPods []*podMetadata
+	var (
+		retPods []*podMetadata
+		podData *podMetadata
+	)
 
 	for _, podItem := range podStore.List() {
 		pod, ok := podItem.(*slimcorev1.Pod)
@@ -512,11 +508,17 @@ func (rpm *Manager) getLocalPodsForPolicy(config *LRPConfig, podStore cache.Stor
 		if err != nil {
 			continue
 		}
-		podInfo := rpm.getPodMetadata(pod, podIPs)
-		if !config.policyConfigSelectsPod(podInfo) {
+		if podData, err = rpm.getPodMetadata(pod, podIPs); err != nil {
+			log.WithError(err).WithFields(logrus.Fields{
+				logfields.K8sPodName:   pod.Name,
+				logfields.K8sNamespace: pod.Namespace,
+			}).Error("failed to get valid pod metadata")
 			continue
 		}
-		retPods = append(retPods, podInfo)
+		if !config.policyConfigSelectsPod(podData) {
+			continue
+		}
+		retPods = append(retPods, podData)
 	}
 
 	return retPods
@@ -550,35 +552,35 @@ func (rpm *Manager) isValidConfig(config LRPConfig) error {
 	return nil
 }
 
-func (rpm *Manager) upsertConfig(config *LRPConfig, pods ...*podMetadata) {
+func (rpm *Manager) processConfig(config *LRPConfig, pods ...*podMetadata) {
 	switch config.frontendType {
 	case svcFrontendSinglePort:
 		fallthrough
 	case addrFrontendSinglePort:
-		rpm.upsertConfigWithSinglePort(config, pods...)
+		rpm.processConfigWithSinglePort(config, pods...)
 	case svcFrontendNamedPorts:
 		fallthrough
 	case addrFrontendNamedPorts:
-		rpm.upsertConfigWithNamedPorts(config, pods...)
+		rpm.processConfigWithNamedPorts(config, pods...)
 	case svcFrontendAll:
 		if len(config.frontendMappings) > 1 {
 			// The retrieved service frontend has multiple ports, in which case
 			// Kubernetes mandates that the ports be named.
-			rpm.upsertConfigWithNamedPorts(config, pods...)
+			rpm.processConfigWithNamedPorts(config, pods...)
 		} else {
 			// The retrieved service frontend has only 1 port, in which case
 			// port names are optional.
-			rpm.upsertConfigWithSinglePort(config, pods...)
+			rpm.processConfigWithSinglePort(config, pods...)
 		}
 	}
 }
 
-// upsertConfigWithSinglePort upserts a policy config frontend with the corresponding
+// processConfigWithSinglePort upserts a policy config frontend with the corresponding
 // backends.
 // Frontend <ip, port, protocol> is mapped to backend <ip, port, protocol> entry.
 // If a pod has multiple IPs, then there will be multiple backend entries created
 // for the pod with common <port, protocol>.
-func (rpm *Manager) upsertConfigWithSinglePort(config *LRPConfig, pods ...*podMetadata) {
+func (rpm *Manager) processConfigWithSinglePort(config *LRPConfig, pods ...*podMetadata) {
 	var bes4 []backend
 	var bes6 []backend
 
@@ -596,11 +598,13 @@ func (rpm *Manager) upsertConfigWithSinglePort(config *LRPConfig, pods ...*podMe
 				continue
 			}
 			be := backend{
-				IP: net.ParseIP(ip),
-				L4Addr: lb.L4Addr{
-					Protocol: bePort.l4Addr.Protocol,
-					Port:     bePort.l4Addr.Port,
-				},
+				lb.L3n4Addr{
+					IP: net.ParseIP(ip),
+					L4Addr: lb.L4Addr{
+						Protocol: bePort.l4Addr.Protocol,
+						Port:     bePort.l4Addr.Port,
+					},
+				}, pod.id,
 			}
 			if feM.feAddr.IP.To4() != nil {
 				if option.Config.EnableIPv4 {
@@ -613,22 +617,23 @@ func (rpm *Manager) upsertConfigWithSinglePort(config *LRPConfig, pods ...*podMe
 			}
 		}
 		if len(bes4) > 0 {
-			rpm.upsertServiceWithBackends(config, feM, pod.id, bes4)
+			rpm.updateFrontendMapping(config, feM, pod.id, bes4)
 		} else if len(bes6) > 0 {
-			rpm.upsertServiceWithBackends(config, feM, pod.id, bes6)
+			rpm.updateFrontendMapping(config, feM, pod.id, bes6)
 		}
 	}
-	return
+	rpm.upsertService(config, feM)
 }
 
-// upsertConfigWithNamedPorts upserts policy config frontends to the corresponding
+// processConfigWithNamedPorts upserts policy config frontends to the corresponding
 // backends matched by port names.
-func (rpm *Manager) upsertConfigWithNamedPorts(config *LRPConfig, pods ...*podMetadata) {
+func (rpm *Manager) processConfigWithNamedPorts(config *LRPConfig, pods ...*podMetadata) {
 	// Generate backends for the policy config's backend named ports, and then
 	// map the backends to policy frontends based on the named ports.
 	// We currently don't check which backends are updated before upserting a
 	// a service with the corresponding frontend. This can be optimized if LRPs
 	// are scaled up.
+	upsertFes := make([]*feMapping, 0, len(config.frontendMappings))
 	for _, feM := range config.frontendMappings {
 		namedPort := feM.fePort
 		var (
@@ -641,20 +646,26 @@ func (rpm *Manager) upsertConfigWithNamedPorts(config *LRPConfig, pods ...*podMe
 			// The frontend named port not found in the backend ports map.
 			continue
 		}
+		if bePort.l4Addr.Protocol != feM.feAddr.Protocol {
+			continue
+		}
 		for _, pod := range pods {
 			if _, ok = pod.namedPorts[namedPort]; ok {
 				// Generate pod backends.
 				for _, ip := range pod.ips {
 					beIP := net.ParseIP(ip)
-					if beIP == nil || bePort.l4Addr.Protocol != feM.feAddr.Protocol {
+					if beIP == nil {
 						continue
 					}
 					be := backend{
-						IP: net.ParseIP(ip),
-						L4Addr: lb.L4Addr{
-							Protocol: bePort.l4Addr.Protocol,
-							Port:     bePort.l4Addr.Port,
+						lb.L3n4Addr{
+							IP: net.ParseIP(ip),
+							L4Addr: lb.L4Addr{
+								Protocol: bePort.l4Addr.Protocol,
+								Port:     bePort.l4Addr.Port,
+							},
 						},
+						pod.id,
 					}
 					if feM.feAddr.IP.To4() != nil {
 						if option.Config.EnableIPv4 {
@@ -668,42 +679,82 @@ func (rpm *Manager) upsertConfigWithNamedPorts(config *LRPConfig, pods ...*podMe
 				}
 			}
 			if len(bes4) > 0 {
-				rpm.upsertServiceWithBackends(config, feM, pod.id, bes4)
+				rpm.updateFrontendMapping(config, feM, pod.id, bes4)
 			} else if len(bes6) > 0 {
-				rpm.upsertServiceWithBackends(config, feM, pod.id, bes6)
+				rpm.updateFrontendMapping(config, feM, pod.id, bes6)
 			}
 		}
+		if len(bes4) > 0 || len(bes6) > 0 {
+			upsertFes = append(upsertFes, feM)
+		}
+	}
+	for i := range upsertFes {
+		rpm.upsertService(config, upsertFes[i])
 	}
 }
 
-// upsertServiceWithBackends updates policy config internal state and upserts
-// service with the given pod backends.
-func (rpm *Manager) upsertServiceWithBackends(config *LRPConfig, frontendMapping *feMapping, podID podID, backends []backend) {
-	frontendMapping.backends = backends
-	rpm.policyPods[podID] = append(rpm.policyPods[podID], podPolicyInfo{
-		policyID: config.id,
-		backends: backends,
-	})
-	rpm.upsertService(config, frontendMapping)
+// updateFrontendMapping updates policy config internal state and updates
+// the policy frontend mapped backends.
+func (rpm *Manager) updateFrontendMapping(config *LRPConfig, frontendMapping *feMapping, podID podID, backends []backend) {
+	newFePods := make([]backend, 0, len(frontendMapping.podBackends)+len(backends))
+	updatePodBes := true
+	// Update the frontend mapped backends slice, keeping the order same.
+	for _, be := range frontendMapping.podBackends {
+		if be.podID == podID {
+			if updatePodBes {
+				updatePodBes = false
+				// Get the updated backends for the given pod.
+				newFePods = append(newFePods, backends...)
+			}
+		} else {
+			// Collect the unchanged backends for other pods.
+			newFePods = append(newFePods, be)
+		}
+	}
+	if updatePodBes {
+		// New backend pod for the frontend
+		newFePods = append(newFePods, backends...)
+	}
+	frontendMapping.podBackends = newFePods
+
+	if podPolicies, ok := rpm.policyPods[podID]; ok {
+		newPodPolicy := true
+		for _, poID := range podPolicies {
+			// Existing pod policy update
+			if poID == config.id {
+				newPodPolicy = false
+				break
+			}
+		}
+		if newPodPolicy {
+			// Pod selected by a new policy
+			rpm.policyPods[podID] = append(rpm.policyPods[podID], config.id)
+		}
+	} else {
+		// Pod selected by a policy for the first time
+		pp := []policyID{config.id}
+		rpm.policyPods[podID] = pp
+	}
 }
 
 // TODO This function along with podMetadata can potentially be removed. We
 // can directly reference the relevant pod metedata on-site.
-func (rpm *Manager) getPodMetadata(pod *slimcorev1.Pod, podIPs []string) *podMetadata {
+func (rpm *Manager) getPodMetadata(pod *slimcorev1.Pod, podIPs []string) (*podMetadata, error) {
 	namedPorts := make(serviceStore.PortConfiguration)
 	for _, container := range pod.Spec.Containers {
 		for _, port := range container.Ports {
 			if port.Name == "" {
 				continue
 			}
-			_, err := u8proto.ParseProtocol(string(port.Protocol))
-			if err != nil {
-				return nil
+			if _, err := u8proto.ParseProtocol(string(port.Protocol)); err != nil {
+				return nil, err
 			}
 			if port.ContainerPort < 1 || port.ContainerPort > 65535 {
-				return nil
+				return nil, fmt.Errorf("invalid container port %v",
+					port.ContainerPort)
 			}
-			namedPorts[port.Name] = lb.NewL4Addr(lb.L4Type(port.Protocol), uint16(port.ContainerPort))
+			namedPorts[port.Name] = lb.NewL4Addr(lb.L4Type(port.Protocol),
+				uint16(port.ContainerPort))
 		}
 	}
 	return &podMetadata{
@@ -714,5 +765,5 @@ func (rpm *Manager) getPodMetadata(pod *slimcorev1.Pod, podIPs []string) *podMet
 			Name:      pod.GetName(),
 			Namespace: pod.GetNamespace(),
 		},
-	}
+	}, nil
 }
diff --git a/pkg/redirectpolicy/redirectpolicy.go b/pkg/redirectpolicy/redirectpolicy.go
index bcdf2f235e..02266ba8a3 100644
--- a/pkg/redirectpolicy/redirectpolicy.go
+++ b/pkg/redirectpolicy/redirectpolicy.go
@@ -81,15 +81,22 @@ type LRPConfig struct {
 
 type frontend = loadbalancer.L3n4Addr
 
-type backend = loadbalancer.L3n4Addr
+// backend encapsulates loadbalancer.L3n4Addr for a pod along with podID (pod
+// name and namespace). There can be multiple backend pods for an LRP frontend,
+// in such cases, the podID will be used to keep track of updates related to
+// a pod.
+type backend struct {
+	loadbalancer.L3n4Addr
+	podID podID
+}
 
 type portName = string
 
 // feMapping stores frontend address and a list of associated backend addresses.
 type feMapping struct {
-	feAddr   *frontend
-	backends []backend
-	fePort   portName
+	feAddr      *frontend
+	podBackends []backend
+	fePort      portName
 }
 
 type bePortInfo struct {
@@ -107,13 +114,13 @@ type policyID = k8s.ServiceID
 func Parse(clrp *v2.CiliumLocalRedirectPolicy, sanitize bool) (*LRPConfig, error) {
 	name := clrp.ObjectMeta.Name
 	if name == "" {
-		return &LRPConfig{}, fmt.Errorf("CiliumLocalRedirectPolicy must have a name")
+		return nil, fmt.Errorf("CiliumLocalRedirectPolicy must have a name")
 	}
 
 	namespace := k8sUtils.ExtractNamespace(&clrp.ObjectMeta)
 	if namespace == "" {
 		// TODO CCLRP: addressed in follow-up PR. Details - GH-12831
-		return &LRPConfig{}, fmt.Errorf("ClusterwideCiliumLocalRedirectPolicy is currently not supported")
+		return nil, fmt.Errorf("ClusterwideCiliumLocalRedirectPolicy is currently not supported")
 	}
 
 	if sanitize {
diff --git a/pkg/service/service.go b/pkg/service/service.go
index cbd5d3cd25..3f914b282d 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -44,7 +44,7 @@ var (
 type LBMap interface {
 	UpsertService(*lbmap.UpsertServiceParams) error
 	DeleteService(lb.L3n4AddrID, int, bool) error
-	AddBackend(uint16, net.IP, uint16, bool) error
+	AddBackend(uint16, net.IP, lb.L4Type, uint16, bool) error
 	DeleteBackendByID(uint16, bool) error
 	AddAffinityMatch(uint16, uint16) error
 	DeleteAffinityMatch(uint16, uint16) error
@@ -546,11 +546,19 @@ func (s *Service) createSVCInfoIfNotExist(p *lb.SVC) (*svcInfo, bool, bool,
 		// as the service clusterIP type. In such cases, if a Local redirect service
 		// exists, we shouldn't override it with clusterIP type (e.g., k8s event/sync, etc).
 		if svc.svcType == lb.SVCTypeLocalRedirect && p.Type == lb.SVCTypeClusterIP {
-			err := fmt.Errorf("Local redirect service exists for "+
-				"frontend %v, skip update for svc %s", p.Frontend, p.Name)
+			err := fmt.Errorf("local-redirect service exists for "+
+				"frontend %v, skip update for svc %v", p.Frontend, p.Name)
 			return svc, !found, prevSessionAffinity, prevLoadBalancerSourceRanges, err
 
 		}
+		// Local-redirect service can only override clusterIP service type or itself.
+		if p.Type == lb.SVCTypeLocalRedirect &&
+			(svc.svcType != lb.SVCTypeClusterIP && svc.svcType != lb.SVCTypeLocalRedirect) {
+			err := fmt.Errorf("skip local-redirect service for "+
+				"frontend %v as it overlaps with svc %v of type %v",
+				p.Frontend, svc.svcName, svc.svcType)
+			return svc, !found, prevSessionAffinity, prevLoadBalancerSourceRanges, err
+		}
 		prevSessionAffinity = svc.sessionAffinity
 		prevLoadBalancerSourceRanges = svc.loadBalancerSourceRanges
 		svc.svcType = p.Type
@@ -676,6 +684,7 @@ func (s *Service) upsertServiceIntoLBMaps(svc *svcInfo, onlyLocalBackends bool,
 		}).Debug("Adding new backend")
 
 		if err := s.lbmap.AddBackend(uint16(b.ID), b.L3n4Addr.IP,
+			b.L3n4Addr.L4Addr.Protocol,
 			b.L3n4Addr.L4Addr.Port, ipv6); err != nil {
 			return err
 		}
@@ -691,6 +700,7 @@ func (s *Service) upsertServiceIntoLBMaps(svc *svcInfo, onlyLocalBackends bool,
 		ID:                        uint16(svc.frontend.ID),
 		IP:                        svc.frontend.L3n4Addr.IP,
 		Port:                      svc.frontend.L3n4Addr.L4Addr.Port,
+		Protocol:                  string(svc.frontend.L3n4Addr.L4Addr.Protocol),
 		Backends:                  backends,
 		PrevBackendCount:          prevBackendCount,
 		IPv6:                      ipv6,
diff --git a/pkg/u8proto/u8proto.go b/pkg/u8proto/u8proto.go
index b396d2c043..91f26a07eb 100644
--- a/pkg/u8proto/u8proto.go
+++ b/pkg/u8proto/u8proto.go
@@ -43,6 +43,7 @@ var protoNames = map[U8proto]string{
 var ProtoIDs = map[string]U8proto{
 	"all":    0,
 	"any":    0,
+	"none":   0,
 	"icmp":   1,
 	"tcp":    6,
 	"udp":    17,
@@ -62,5 +63,13 @@ func ParseProtocol(proto string) (U8proto, error) {
 	if u, ok := ProtoIDs[strings.ToLower(proto)]; ok {
 		return u, nil
 	}
-	return 0, fmt.Errorf("unknown protocol '%s'", proto)
+	return 0, fmt.Errorf("unknown protocol %q", proto)
+}
+
+func FromNumber(proto uint8) (U8proto, error) {
+	_, ok := protoNames[U8proto(proto)]
+	if !ok {
+		return 0, fmt.Errorf("unknown protocol %d", proto)
+	}
+	return U8proto(proto), nil
 }


Test patch:
diff --git a/Documentation/contributing/testing/ci.rst b/Documentation/contributing/testing/ci.rst
index e04fb4abe6..e06aa441ad 100644
--- a/Documentation/contributing/testing/ci.rst
+++ b/Documentation/contributing/testing/ci.rst
@@ -319,6 +319,8 @@ example patch that shows how this can be achieved.
 
                             //This test should run in each PR for now.
 
+.. _ci_failure_triage:
+
 CI Failure Triage
 ~~~~~~~~~~~~~~~~~
 
diff --git a/pkg/bpf/map_linux_test.go b/pkg/bpf/map_linux_test.go
index e81f3b199c..a205e81d96 100644
--- a/pkg/bpf/map_linux_test.go
+++ b/pkg/bpf/map_linux_test.go
@@ -598,3 +598,31 @@ func (s *BPFPrivilegedTestSuite) TestUnpin(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(exist, Equals, false)
 }
+
+func (s *BPFPrivilegedTestSuite) TestCreateUnpinned(c *C) {
+	m := NewMap("cilium_test_create_unpinned",
+		MapTypeHash,
+		&TestKey{},
+		int(unsafe.Sizeof(TestKey{})),
+		&TestValue{},
+		int(unsafe.Sizeof(TestValue{})),
+		maxEntries,
+		BPF_F_NO_PREALLOC,
+		0,
+		ConvertKeyValue).WithCache()
+	err := m.CreateUnpinned()
+	c.Assert(err, IsNil)
+	exist, err := m.exist()
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, false)
+
+	key1 := &TestKey{Key: 105}
+	value1 := &TestValue{Value: 205}
+	err = m.Update(key1, value1)
+	c.Assert(err, IsNil)
+
+	var value2 TestValue
+	err = LookupElement(m.fd, unsafe.Pointer(key1), unsafe.Pointer(&value2))
+	c.Assert(err, IsNil)
+	c.Assert(*value1, Equals, value2)
+}
diff --git a/pkg/comparator/comparator_test.go b/pkg/comparator/comparator_test.go
index cd940ade17..2e08ab034b 100644
--- a/pkg/comparator/comparator_test.go
+++ b/pkg/comparator/comparator_test.go
@@ -152,3 +152,116 @@ func (s *ComparatorSuite) TestMapBoolEquals(c *C) {
 		c.Assert(MapBoolEquals(tt.m1, tt.m2), Equals, tt.want)
 	}
 }
+
+func (s *ComparatorSuite) TestMapStringEqualsIgnoreKeys(c *C) {
+	tests := []struct {
+		name         string
+		m1           map[string]string
+		m2           map[string]string
+		keysToIgnore []string
+		want         bool
+	}{
+		{
+			name: "test-1",
+			m1:   nil,
+			m2:   nil,
+			want: true,
+		},
+		{
+			m1: map[string]string{
+				"foo": "bar",
+			},
+			m2: map[string]string{
+				"foo": "bar",
+			},
+			want: true,
+		},
+		{
+			name: "test-2",
+			m1:   map[string]string{},
+			m2:   map[string]string{},
+			want: true,
+		},
+		{
+			name: "test-3",
+			m1: map[string]string{
+				"fo": "bar",
+			},
+			m2: map[string]string{
+				"foo": "bar",
+			},
+			want: false,
+		},
+		{
+			name: "test-4",
+			m1:   nil,
+			m2: map[string]string{
+				"foo": "bar",
+			},
+			want: false,
+		},
+		{
+			name: "test-5",
+			m1: map[string]string{
+				"foo": "bar",
+			},
+			m2:   nil,
+			want: false,
+		},
+		{
+			name: "test-6",
+			m1: map[string]string{
+				"foo": "bar",
+			},
+			m2:   nil,
+			want: false,
+		},
+		{
+			name: "test-7",
+			m1: map[string]string{
+				"foo": "bar",
+			},
+			m2:           nil,
+			keysToIgnore: []string{"foo"},
+			// Although we are ignoring "foo", m2 is nil.
+			want: false,
+		},
+		{
+			name: "test-8",
+			m1: map[string]string{
+				"foo": "bar",
+			},
+			m2:           map[string]string{},
+			keysToIgnore: []string{"foo"},
+			want:         true,
+		},
+		{
+			name: "test-9",
+			m1:   map[string]string{},
+			m2: map[string]string{
+				"foo": "bar",
+			},
+			keysToIgnore: []string{"foo"},
+			want:         true,
+		},
+		{
+			name:         "test-10",
+			m1:           map[string]string{},
+			m2:           map[string]string{},
+			keysToIgnore: []string{"foo"},
+			want:         true,
+		},
+		{
+			name: "test-10",
+			m1:   nil,
+			m2: map[string]string{
+				"foo": "bar",
+			},
+			keysToIgnore: []string{"foo"},
+			want:         false,
+		},
+	}
+	for _, tt := range tests {
+		c.Assert(MapStringEqualsIgnoreKeys(tt.m1, tt.m2, tt.keysToIgnore), Equals, tt.want, Commentf("%s", tt.name))
+	}
+}
diff --git a/pkg/hubble/parser/threefour/parser_test.go b/pkg/hubble/parser/threefour/parser_test.go
index 006957d458..c8b5c568a8 100644
--- a/pkg/hubble/parser/threefour/parser_test.go
+++ b/pkg/hubble/parser/threefour/parser_test.go
@@ -384,6 +384,7 @@ func TestDecodePolicyVerdictNotify(t *testing.T) {
 	assert.Equal(t, int32(api.MessageTypePolicyVerdict), f.GetEventType().GetType())
 	assert.Equal(t, flowpb.TrafficDirection_INGRESS, f.GetTrafficDirection())
 	assert.Equal(t, uint32(151), f.GetDropReason())
+	assert.Equal(t, flowpb.DropReason(151), f.GetDropReasonDesc())
 	assert.Equal(t, flowpb.Verdict_DROPPED, f.GetVerdict())
 	assert.Equal(t, []string{"dst=label"}, f.GetSource().GetLabels())
 }
@@ -405,6 +406,7 @@ func TestDecodeDropReason(t *testing.T) {
 	require.NoError(t, err)
 
 	assert.Equal(t, uint32(reason), f.GetDropReason())
+	assert.Equal(t, flowpb.DropReason(reason), f.GetDropReasonDesc())
 }
 
 func TestDecodeLocalIdentity(t *testing.T) {
diff --git a/pkg/hubble/testutils/fake.go b/pkg/hubble/testutils/fake.go
index 95dc2ee030..b3dc53cf66 100644
--- a/pkg/hubble/testutils/fake.go
+++ b/pkg/hubble/testutils/fake.go
@@ -375,6 +375,7 @@ type FakeFlow struct {
 	DestinationService *flowpb.Service
 	TrafficDirection   flowpb.TrafficDirection
 	PolicyMatchType    uint32
+	DropReasonDesc     flowpb.DropReason
 }
 
 // Reset implements flowpb.Message for the FakeFlow.
@@ -481,6 +482,11 @@ func (f *FakeFlow) GetPolicyMatchType() uint32 {
 	return f.PolicyMatchType
 }
 
+// GetDropReasonDesc implements v1.Flow for the FakeFlow.
+func (f *FakeFlow) GetDropReasonDesc() flowpb.DropReason {
+	return f.DropReasonDesc
+}
+
 // GetSummary implements v1.Flow for the FakeFlow.
 func (f *FakeFlow) GetSummary() string {
 	return "deprecated"
diff --git a/pkg/k8s/endpoints_test.go b/pkg/k8s/endpoints_test.go
index 7bc606ef5a..0e3ebdca18 100644
--- a/pkg/k8s/endpoints_test.go
+++ b/pkg/k8s/endpoints_test.go
@@ -52,7 +52,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -67,7 +67,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -86,7 +86,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -100,7 +100,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.2": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -118,7 +118,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -132,7 +132,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foz": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -150,7 +150,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -158,6 +158,70 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 					},
 				},
 			},
+			args: args{
+				o: &Endpoints{
+					Backends: map[string]*Backend{
+						"172.20.0.1": {
+							Ports: map[string]*loadbalancer.L4Addr{
+								"foo": {
+									Protocol: loadbalancer.TCP,
+									Port:     2,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "protocols different content",
+			fields: fields{
+				svcEP: &Endpoints{
+					Backends: map[string]*Backend{
+						"172.20.0.1": {
+							Ports: map[string]*loadbalancer.L4Addr{
+								"foo": {
+									Protocol: loadbalancer.TCP,
+									Port:     2,
+								},
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				o: &Endpoints{
+					Backends: map[string]*Backend{
+						"172.20.0.1": {
+							Ports: map[string]*loadbalancer.L4Addr{
+								"foo": {
+									Protocol: loadbalancer.UDP,
+									Port:     2,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "protocols different one none",
+			fields: fields{
+				svcEP: &Endpoints{
+					Backends: map[string]*Backend{
+						"172.20.0.1": {
+							Ports: map[string]*loadbalancer.L4Addr{
+								"foo": {
+									Protocol: loadbalancer.TCP,
+									Port:     2,
+								},
+							},
+						},
+					},
+				},
+			},
 			args: args{
 				o: &Endpoints{
 					Backends: map[string]*Backend{
@@ -182,7 +246,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
@@ -196,11 +260,11 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 								"baz": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     2,
 								},
 							},
@@ -219,7 +283,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     1,
 								},
 							},
diff --git a/pkg/k8s/service_cache_test.go b/pkg/k8s/service_cache_test.go
index 332248dbdd..531fc96ce9 100644
--- a/pkg/k8s/service_cache_test.go
+++ b/pkg/k8s/service_cache_test.go
@@ -95,33 +95,6 @@ func (s *K8sSuite) TestGetUniqueServiceFrontends(c *check.C) {
 		"1.1.1.1:20/TCP": {},
 		"2.2.2.2:20/UDP": {},
 	})
-
-	scopes := []uint8{loadbalancer.ScopeExternal, loadbalancer.ScopeInternal}
-	for _, scope := range scopes {
-		// Validate all frontends as exact matches
-		// These should match only for external scope
-		exact_match_ok := scope == loadbalancer.ScopeExternal
-		frontend := loadbalancer.NewL3n4Addr(loadbalancer.TCP, net.ParseIP("1.1.1.1"), 10, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, exact_match_ok)
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.TCP, net.ParseIP("1.1.1.1"), 20, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, exact_match_ok)
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.UDP, net.ParseIP("2.2.2.2"), 20, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, exact_match_ok)
-
-		// Validate protocol mismatch on exact match
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.TCP, net.ParseIP("2.2.2.2"), 20, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, false)
-
-		// Validate protocol wildcard matching
-		// These should match only for external scope
-		wild_match_ok := scope == loadbalancer.ScopeExternal
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, net.ParseIP("2.2.2.2"), 20, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, wild_match_ok)
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, net.ParseIP("1.1.1.1"), 10, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, wild_match_ok)
-		frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, net.ParseIP("1.1.1.1"), 20, scope)
-		c.Assert(frontends.LooseMatch(*frontend), check.Equals, wild_match_ok)
-	}
 }
 
 func (s *K8sSuite) TestServiceCacheEndpoints(c *check.C) {
diff --git a/pkg/k8s/service_test.go b/pkg/k8s/service_test.go
index 6484c5aed9..0c59d9078c 100644
--- a/pkg/k8s/service_test.go
+++ b/pkg/k8s/service_test.go
@@ -180,41 +180,6 @@ func (s *K8sSuite) TestIsK8ServiceExternal(c *check.C) {
 	c.Assert(si.IsExternal(), check.Equals, false)
 }
 
-func (s *K8sSuite) TestServiceUniquePorts(c *check.C) {
-	type testMatrix struct {
-		input    Service
-		expected map[uint16]bool
-	}
-
-	matrix := []testMatrix{
-		{
-			input:    Service{},
-			expected: map[uint16]bool{},
-		},
-		{
-			input: Service{
-				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
-					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
-						Port:     1,
-					},
-					loadbalancer.FEPortName("bar"): {
-						Protocol: loadbalancer.NONE,
-						Port:     2,
-					},
-				},
-			},
-			expected: map[uint16]bool{
-				1: true,
-				2: true,
-			}},
-	}
-
-	for _, m := range matrix {
-		c.Assert(m.input.UniquePorts(), checker.DeepEquals, m.expected)
-	}
-}
-
 func TestService_Equals(t *testing.T) {
 	type args struct {
 		o *Service
@@ -232,7 +197,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -241,7 +206,7 @@ func TestService_Equals(t *testing.T) {
 						"0.0.0.0:31000": {
 							L3n4Addr: loadbalancer.L3n4Addr{
 								L4Addr: loadbalancer.L4Addr{
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     31000,
 								},
 								IP: net.IPv4(0, 0, 0, 0),
@@ -264,7 +229,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -273,7 +238,7 @@ func TestService_Equals(t *testing.T) {
 							"0.0.0.0:31000": {
 								L3n4Addr: loadbalancer.L3n4Addr{
 									L4Addr: loadbalancer.L4Addr{
-										Protocol: loadbalancer.NONE,
+										Protocol: loadbalancer.TCP,
 										Port:     31000,
 									},
 									IP: net.IPv4(0, 0, 0, 0),
@@ -300,7 +265,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -315,7 +280,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -336,7 +301,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -349,7 +314,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -368,7 +333,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foz"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -381,7 +346,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -398,13 +363,73 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
 				Labels:   map[string]string{},
 				Selector: map[string]string{},
 			},
+			args: args{
+				o: &Service{
+					FrontendIP: net.ParseIP("1.1.1.1"),
+					IsHeadless: true,
+					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
+						loadbalancer.FEPortName("foo"): {
+							Protocol: loadbalancer.TCP,
+							Port:     2,
+						},
+					},
+					Labels:   map[string]string{},
+					Selector: map[string]string{},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "protocols different content",
+			fields: &Service{
+				FrontendIP: net.ParseIP("1.1.1.1"),
+				IsHeadless: true,
+				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
+					loadbalancer.FEPortName("foo"): {
+						Protocol: loadbalancer.TCP,
+						Port:     2,
+					},
+				},
+				Labels:   map[string]string{},
+				Selector: map[string]string{},
+			},
+			args: args{
+				o: &Service{
+					FrontendIP: net.ParseIP("1.1.1.1"),
+					IsHeadless: true,
+					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
+						loadbalancer.FEPortName("foo"): {
+							Protocol: loadbalancer.UDP,
+							Port:     2,
+						},
+					},
+					Labels:   map[string]string{},
+					Selector: map[string]string{},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "protocols different content one is none",
+			fields: &Service{
+				FrontendIP: net.ParseIP("1.1.1.1"),
+				IsHeadless: true,
+				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
+					loadbalancer.FEPortName("foo"): {
+						Protocol: loadbalancer.TCP,
+						Port:     2,
+					},
+				},
+				Labels:   map[string]string{},
+				Selector: map[string]string{},
+			},
 			args: args{
 				o: &Service{
 					FrontendIP: net.ParseIP("1.1.1.1"),
@@ -428,7 +453,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -441,11 +466,11 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 						loadbalancer.FEPortName("baz"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     2,
 						},
 					},
@@ -469,7 +494,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -486,7 +511,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -495,7 +520,7 @@ func TestService_Equals(t *testing.T) {
 						"1.1.1.1:31000": {
 							L3n4Addr: loadbalancer.L3n4Addr{
 								L4Addr: loadbalancer.L4Addr{
-									Protocol: loadbalancer.NONE,
+									Protocol: loadbalancer.TCP,
 									Port:     31000,
 								},
 								IP: net.IPv4(1, 1, 1, 1),
@@ -518,7 +543,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
@@ -527,7 +552,7 @@ func TestService_Equals(t *testing.T) {
 							"0.0.0.0:31000": {
 								L3n4Addr: loadbalancer.L3n4Addr{
 									L4Addr: loadbalancer.L4Addr{
-										Protocol: loadbalancer.NONE,
+										Protocol: loadbalancer.TCP,
 										Port:     31000,
 									},
 									IP: net.IPv4(0, 0, 0, 0),
@@ -554,7 +579,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: false,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.NONE,
+						Protocol: loadbalancer.TCP,
 						Port:     1,
 					},
 				},
@@ -572,7 +597,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: false,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.NONE,
+							Protocol: loadbalancer.TCP,
 							Port:     1,
 						},
 					},
diff --git a/pkg/loadbalancer/loadbalancer_test.go b/pkg/loadbalancer/loadbalancer_test.go
index 64a5685e79..336e316bbf 100644
--- a/pkg/loadbalancer/loadbalancer_test.go
+++ b/pkg/loadbalancer/loadbalancer_test.go
@@ -45,12 +45,12 @@ func TestL4Addr_Equals(t *testing.T) {
 		{
 			name: "both equal",
 			fields: &L4Addr{
-				Protocol: NONE,
+				Protocol: TCP,
 				Port:     1,
 			},
 			args: args{
 				o: &L4Addr{
-					Protocol: NONE,
+					Protocol: TCP,
 					Port:     1,
 				},
 			},
@@ -59,12 +59,12 @@ func TestL4Addr_Equals(t *testing.T) {
 		{
 			name: "both different",
 			fields: &L4Addr{
-				Protocol: NONE,
+				Protocol: TCP,
 				Port:     0,
 			},
 			args: args{
 				o: &L4Addr{
-					Protocol: NONE,
+					Protocol: TCP,
 					Port:     1,
 				},
 			},
@@ -78,12 +78,40 @@ func TestL4Addr_Equals(t *testing.T) {
 		{
 			name: "other nil",
 			fields: &L4Addr{
-				Protocol: NONE,
+				Protocol: TCP,
 				Port:     1,
 			},
 			args: args{},
 			want: false,
 		},
+		{
+			name: "protocol different",
+			fields: &L4Addr{
+				Protocol: TCP,
+				Port:     1,
+			},
+			args: args{
+				o: &L4Addr{
+					Protocol: UDP,
+					Port:     1,
+				},
+			},
+			want: false,
+		},
+		{
+			name: "protocol different one is none",
+			fields: &L4Addr{
+				Protocol: TCP,
+				Port:     1,
+			},
+			args: args{
+				o: &L4Addr{
+					Protocol: NONE,
+					Port:     1,
+				},
+			},
+			want: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -110,7 +138,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: NONE,
+						Protocol: TCP,
 						Port:     1,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -121,7 +149,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: NONE,
+							Protocol: TCP,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -136,7 +164,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: NONE,
+						Protocol: TCP,
 						Port:     1,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -147,7 +175,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: NONE,
+							Protocol: TCP,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -162,7 +190,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: NONE,
+						Protocol: TCP,
 						Port:     1,
 					},
 					IP: net.IPv4(2, 2, 2, 2),
@@ -173,7 +201,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: NONE,
+							Protocol: TCP,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -188,7 +216,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: NONE,
+						Protocol: TCP,
 						Port:     2,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -199,7 +227,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: NONE,
+							Protocol: TCP,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -209,6 +237,32 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			},
 			want: false,
 		},
+		{
+			name: "protocols different",
+			fields: &L3n4AddrID{
+				L3n4Addr: L3n4Addr{
+					L4Addr: L4Addr{
+						Protocol: TCP,
+						Port:     2,
+					},
+					IP: net.IPv4(1, 1, 1, 1),
+				},
+				ID: 1,
+			},
+			args: args{
+				o: &L3n4AddrID{
+					L3n4Addr: L3n4Addr{
+						L4Addr: L4Addr{
+							Protocol: UDP,
+							Port:     2,
+						},
+						IP: net.IPv4(1, 1, 1, 1),
+					},
+					ID: 1,
+				},
+			},
+			want: false,
+		},
 		{
 			name: "both nil",
 			args: args{},
diff --git a/pkg/policy/portmap_test.go b/pkg/policy/portmap_test.go
index d5091772a0..662caa345a 100644
--- a/pkg/policy/portmap_test.go
+++ b/pkg/policy/portmap_test.go
@@ -56,7 +56,7 @@ func (ds *PolicyTestSuite) TestPolicyNewPortProto(c *C) {
 
 	_, err = newPortProto(80, "cccp")
 	c.Assert(err, Not(IsNil))
-	c.Assert(err.Error(), Equals, "unknown protocol 'cccp'")
+	c.Assert(err.Error(), Equals, "unknown protocol \"cccp\"")
 
 	np, err = newPortProto(88, "")
 	c.Assert(err, IsNil)
diff --git a/pkg/rate/api_limiter_test.go b/pkg/rate/api_limiter_test.go
index f9c0e91d37..e649b3dd15 100644
--- a/pkg/rate/api_limiter_test.go
+++ b/pkg/rate/api_limiter_test.go
@@ -19,11 +19,13 @@ package rate
 import (
 	"context"
 	"fmt"
+	"sync/atomic"
 	"time"
 
 	"github.com/cilium/cilium/pkg/lock"
 	"github.com/cilium/cilium/pkg/testutils"
 
+	"golang.org/x/sync/semaphore"
 	"golang.org/x/time/rate"
 	"gopkg.in/check.v1"
 )
@@ -716,3 +718,91 @@ func (b *ControllerSuite) TestAdjustedParallelRequests(c *check.C) {
 	a.adjustmentFactor = 1.0
 	c.Assert(a.adjustedParallelRequests(), check.Equals, 2)
 }
+
+func (b *ControllerSuite) TestStressRateLimiter(c *check.C) {
+	a := NewAPILimiter("foo", APILimiterParameters{
+		EstimatedProcessingDuration: 5 * time.Millisecond,
+		RateLimit:                   1000.0,
+		ParallelRequests:            50,
+		RateBurst:                   1,
+		MaxWaitDuration:             10 * time.Millisecond,
+		AutoAdjust:                  true,
+	}, nil)
+
+	var (
+		sem                = semaphore.NewWeighted(100)
+		completed, retries int32
+	)
+
+	go func() {
+		for i := 0; i < 1000; i++ {
+			sem.Acquire(context.Background(), 1)
+			go func() {
+				var (
+					err error
+					req LimitedRequest
+				)
+				for req == nil {
+					req, err = a.Wait(context.Background())
+					if err == nil {
+						time.Sleep(5 * time.Millisecond)
+						req.Done()
+						atomic.AddInt32(&completed, 1)
+						sem.Release(1)
+						return
+					}
+					atomic.AddInt32(&retries, 1)
+				}
+			}()
+		}
+	}()
+
+	c.Assert(testutils.WaitUntil(func() bool {
+		return atomic.LoadInt32(&completed) == 1000
+	}, 5*time.Second), check.IsNil)
+
+	log.Infof("%+v", a)
+	log.Infof("Total retries: %v", atomic.LoadInt32(&retries))
+}
+
+func (b *ControllerSuite) TestReservationCancel(c *check.C) {
+	a := NewAPILimiter("foo", APILimiterParameters{
+		RateLimit:           50.0,
+		RateBurst:           10,
+		ParallelRequests:    1,
+		MaxParallelRequests: 1,
+		MaxWaitDuration:     500 * time.Millisecond,
+		Log:                 true,
+	}, nil)
+
+	// Process a request but don't complete it, this will occupy the
+	// parallel request slot
+	req, err := a.Wait(context.Background())
+	c.Assert(err, check.IsNil)
+
+	var completed int32
+
+	// All of these requests must fail due to having to wait too long as
+	// the only parallel request slot is occupied. The rate limiter should
+	// not get occupied with these requests though.
+	for i := 0; i < 20; i++ {
+		go func() {
+			_, err := a.Wait(context.Background())
+			c.Assert(err, check.Not(check.IsNil))
+			atomic.AddInt32(&completed, 1)
+		}()
+	}
+
+	c.Assert(testutils.WaitUntil(func() bool {
+		return atomic.LoadInt32(&completed) == 20
+	}, time.Second), check.IsNil)
+
+	req.Done()
+
+	// All of these requests should now succeed
+	for i := 0; i < 10; i++ {
+		req2, err := a.Wait(context.Background())
+		c.Assert(err, check.IsNil)
+		req2.Done()
+	}
+}
diff --git a/pkg/redirectpolicy/manager_test.go b/pkg/redirectpolicy/manager_test.go
new file mode 100644
index 0000000000..7a0b1187ca
--- /dev/null
+++ b/pkg/redirectpolicy/manager_test.go
@@ -0,0 +1,432 @@
+//  Copyright 2020 Authors of Cilium
+//
+//  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.
+
+// +build !privileged_tests
+
+package redirectpolicy
+
+import (
+	"net"
+	"testing"
+
+	"github.com/cilium/cilium/pkg/checker"
+	"github.com/cilium/cilium/pkg/k8s"
+	slimcorev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/core/v1"
+	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
+	"github.com/cilium/cilium/pkg/k8s/utils"
+	lb "github.com/cilium/cilium/pkg/loadbalancer"
+	"github.com/cilium/cilium/pkg/policy/api"
+
+	. "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type ManagerSuite struct {
+	rpm      *Manager
+	svcCache k8s.ServiceCache
+	svc      svcManager
+}
+
+var _ = Suite(&ManagerSuite{})
+
+type fakeSvcManager struct {
+}
+
+func (f *fakeSvcManager) DeleteService(lb.L3n4Addr) (bool, error) {
+	return true, nil
+}
+
+func (f *fakeSvcManager) UpsertService(*lb.SVC) (bool, lb.ID, error) {
+	return true, 1, nil
+}
+
+type fakePodStore struct {
+	OnList func() []interface{}
+}
+
+func (ps *fakePodStore) List() []interface{} {
+	pods := make([]interface{}, 2, 2)
+	pods = append(pods, pod1, pod2)
+	return pods
+}
+
+func (ps *fakePodStore) Add(obj interface{}) error {
+	return nil
+}
+
+func (ps *fakePodStore) Update(obj interface{}) error {
+	return nil
+}
+
+func (ps *fakePodStore) Delete(obj interface{}) error {
+	return nil
+}
+
+func (ps *fakePodStore) ListKeys() []string {
+	return nil
+}
+
+func (ps *fakePodStore) Get(obj interface{}) (item interface{}, exists bool, err error) {
+	return nil, false, nil
+}
+
+func (ps *fakePodStore) GetByKey(key string) (item interface{}, exists bool, err error) {
+	return nil, false, nil
+}
+
+func (ps *fakePodStore) Replace(i []interface{}, s string) error {
+	return nil
+}
+
+func (ps *fakePodStore) Resync() error {
+	return nil
+}
+
+var (
+	tcpStr = "TCP"
+	udpStr = "UDP"
+	proto1 = lb.NewL4Type(tcpStr)
+	proto2 = lb.NewL4Type(udpStr)
+	fe1    = lb.NewL3n4Addr(
+		proto1,
+		net.IP("1.1.1.1"),
+		80,
+		lb.ScopeExternal)
+	fe2 = lb.NewL3n4Addr(
+		proto2,
+		net.IP("2.2.2.2"),
+		81,
+		lb.ScopeExternal)
+	portName1 = "test1"
+	portName2 = "test2"
+	beP1      = bePortInfo{
+		l4Addr: lb.L4Addr{
+			Protocol: tcpStr,
+			Port:     8080,
+		},
+		name: portName1,
+	}
+	beP2 = bePortInfo{
+		l4Addr: lb.L4Addr{
+			Protocol: udpStr,
+			Port:     8081,
+		},
+		name: portName2,
+	}
+	configAddrType LRPConfig
+	configSvcType  LRPConfig
+
+	pod1IP1    = slimcorev1.PodIP{IP: "1.2.3.4"}
+	pod1IP2    = slimcorev1.PodIP{IP: "5.6.7.8"}
+	pod1Port1  = int32(8080)
+	pod1Port2  = int32(8081)
+	pod1Proto1 = slimcorev1.ProtocolTCP
+	pod1Proto2 = slimcorev1.ProtocolUDP
+	pod1       = &slimcorev1.Pod{
+		ObjectMeta: slim_metav1.ObjectMeta{
+			Name:      "foo-be",
+			Namespace: "ns1",
+			Labels:    map[string]string{"test": "foo"},
+		},
+		Spec: slimcorev1.PodSpec{
+			Containers: []slimcorev1.Container{
+				{
+					Ports: []slimcorev1.ContainerPort{
+						{
+							Name:          portName1,
+							ContainerPort: pod1Port1,
+							Protocol:      pod1Proto1,
+						},
+						{
+							Name:          portName2,
+							ContainerPort: pod1Port2,
+							Protocol:      pod1Proto2,
+						},
+					},
+				},
+			},
+		},
+		Status: slimcorev1.PodStatus{
+			PodIP:  pod1IP1.IP,
+			PodIPs: []slimcorev1.PodIP{pod1IP1, pod1IP2},
+		},
+	}
+	pod1ID = k8s.ServiceID{
+		Name:      pod1.Name,
+		Namespace: pod1.Namespace,
+	}
+	pod2IP1    = slimcorev1.PodIP{IP: "5.6.7.9"}
+	pod2IP2    = slimcorev1.PodIP{IP: "5.6.7.10"}
+	pod2Port1  = int32(8080)
+	pod2Port2  = int32(8081)
+	pod2Proto1 = slimcorev1.ProtocolTCP
+	pod2Proto2 = slimcorev1.ProtocolUDP
+	pod2       = &slimcorev1.Pod{
+		ObjectMeta: slim_metav1.ObjectMeta{
+			Name:      "foo-be2",
+			Namespace: "ns1",
+			Labels:    map[string]string{"test": "bar"},
+		},
+		Spec: slimcorev1.PodSpec{
+			Containers: []slimcorev1.Container{
+				{
+					Ports: []slimcorev1.ContainerPort{
+						{
+							Name:          portName1,
+							ContainerPort: pod2Port1,
+							Protocol:      pod2Proto1,
+						},
+						{
+							Name:          portName2,
+							ContainerPort: pod2Port2,
+							Protocol:      pod2Proto2,
+						},
+					},
+				},
+			},
+		},
+		Status: slimcorev1.PodStatus{
+			PodIP:  pod2IP1.IP,
+			PodIPs: []slimcorev1.PodIP{pod2IP1, pod2IP2},
+		},
+	}
+	pod2ID = k8s.ServiceID{
+		Name:      pod2.Name,
+		Namespace: pod2.Namespace,
+	}
+)
+
+func (m *ManagerSuite) SetUpTest(c *C) {
+	m.svc = &fakeSvcManager{}
+	m.rpm = NewRedirectPolicyManager(m.svc)
+	// m.svcCache = k8s.NewServiceCache(fakeDatapath.NewNodeAddressing())
+	configAddrType = LRPConfig{
+		id: k8s.ServiceID{
+			Name:      "test-foo",
+			Namespace: "ns1",
+		},
+		lrpType: lrpConfigTypeAddr,
+		frontendMappings: []*feMapping{{
+			feAddr:      fe1,
+			podBackends: nil,
+			fePort:      portName1,
+		}},
+		backendSelector: api.EndpointSelector{
+			LabelSelector: &slim_metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					"test": "foo",
+				},
+			},
+		},
+	}
+	configSvcType = LRPConfig{
+		id: k8s.ServiceID{
+			Name:      "test-foo",
+			Namespace: "ns1",
+		},
+		lrpType: lrpConfigTypeSvc,
+		backendSelector: api.EndpointSelector{
+			LabelSelector: &slim_metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					"test": "foo",
+				},
+			},
+		},
+	}
+}
+
+// Tests if duplicate addressMatcher configs are not added.
+func (m *ManagerSuite) TestManager_AddRedirectPolicy_AddrMatcherDuplicateConfig(c *C) {
+	configFe := configAddrType
+	m.rpm.policyFrontendsByHash[fe1.Hash()] = configFe.id
+	dupConfigFe := configFe
+	dupConfigFe.id.Name = "test-foo2"
+
+	added, err := m.rpm.AddRedirectPolicy(dupConfigFe, nil)
+
+	c.Assert(added, Equals, false)
+	c.Assert(err, NotNil)
+}
+
+// Tests if duplicate svcMatcher configs are not added.
+func (m *ManagerSuite) TestManager_AddRedirectPolicy_SvcMatcherDuplicateConfig(c *C) {
+	configSvc := configSvcType
+	configSvc.serviceID = &k8s.ServiceID{
+		Name:      "foo",
+		Namespace: "ns1",
+	}
+	m.rpm.policyConfigs[configSvc.id] = &configSvc
+	m.rpm.policyServices[*configSvc.serviceID] = configSvc.id
+	invalidConfigSvc := configSvc
+	invalidConfigSvc.id.Name = "test-foo3"
+
+	added, err := m.rpm.AddRedirectPolicy(invalidConfigSvc, nil)
+
+	c.Assert(added, Equals, false)
+	c.Assert(err, NotNil)
+}
+
+// Tests add redirect policy, add pod, delete pod and delete redirect policy events
+// for an addressMatcher config with a frontend having single port.
+func (m *ManagerSuite) TestManager_AddrMatcherConfigSinglePort(c *C) {
+	// Add an addressMatcher type LRP with single port. The policy config
+	// frontend should have 2 pod backends with each of the podIPs.
+	configAddrType.frontendType = addrFrontendSinglePort
+	configAddrType.backendPorts = append(configAddrType.backendPorts, beP1)
+	podIPs, _ := utils.ValidIPs(pod1.Status)
+	expectedbes := make([]backend, len(podIPs))
+	for i := range podIPs {
+		expectedbes[i] = backend{
+			L3n4Addr: lb.L3n4Addr{IP: net.ParseIP(podIPs[i]), L4Addr: beP1.l4Addr},
+			podID:    pod1ID,
+		}
+	}
+
+	added, err := m.rpm.AddRedirectPolicy(configAddrType, &fakePodStore{})
+
+	c.Assert(added, Equals, true)
+	c.Assert(err, IsNil)
+	c.Assert(len(m.rpm.policyConfigs), Equals, 1)
+	c.Assert(m.rpm.policyConfigs[configAddrType.id].id.Name, Equals, configAddrType.id.Name)
+	c.Assert(m.rpm.policyConfigs[configAddrType.id].id.Namespace, Equals, configAddrType.id.Namespace)
+	c.Assert(len(m.rpm.policyFrontendsByHash), Equals, 1)
+	c.Assert(m.rpm.policyFrontendsByHash[configAddrType.frontendMappings[0].feAddr.Hash()],
+		Equals, configAddrType.id)
+	c.Assert(len(configAddrType.frontendMappings[0].podBackends), Equals, 2)
+	for i := range configAddrType.frontendMappings[0].podBackends {
+		c.Assert(configAddrType.frontendMappings[0].podBackends[i], checker.Equals, expectedbes[i])
+	}
+	c.Assert(len(m.rpm.policyPods), Equals, 1)
+	c.Assert(len(m.rpm.policyPods[pod1ID]), Equals, 1)
+	c.Assert(m.rpm.policyPods[pod1ID][0], Equals, configAddrType.id)
+
+	// Add a new backend pod, this will add 2 more pod backends with each of the podIPs.
+	pod3 := pod2
+	pod3.Labels["test"] = "foo"
+	pod3ID := pod2ID
+	podIPs, _ = utils.ValidIPs(pod3.Status)
+	expectedbes2 := make([]backend, 0, len(expectedbes)+len(podIPs))
+	expectedbes2 = append(expectedbes2, expectedbes...)
+	for i := range podIPs {
+		expectedbes2 = append(expectedbes2, backend{
+			L3n4Addr: lb.L3n4Addr{IP: net.ParseIP(podIPs[i]), L4Addr: beP1.l4Addr},
+			podID:    pod3ID,
+		})
+	}
+
+	m.rpm.OnAddPod(pod3)
+
+	c.Assert(len(m.rpm.policyPods), Equals, 2)
+	c.Assert(len(m.rpm.policyPods[pod3ID]), Equals, 1)
+	c.Assert(m.rpm.policyPods[pod1ID][0], Equals, configAddrType.id)
+	c.Assert(len(configAddrType.frontendMappings[0].podBackends), Equals, 4)
+	for i := range configAddrType.frontendMappings[0].podBackends {
+		c.Assert(configAddrType.frontendMappings[0].podBackends[i], checker.Equals, expectedbes2[i])
+	}
+
+	// Delete the pod. This should delete the pod's backends.
+	m.rpm.OnDeletePod(pod3)
+
+	c.Assert(len(m.rpm.policyPods), Equals, 1)
+	_, found := m.rpm.policyPods[pod3ID]
+	c.Assert(found, Equals, false)
+	c.Assert(len(configAddrType.frontendMappings[0].podBackends), Equals, 2)
+	for i := range configAddrType.frontendMappings[0].podBackends {
+		c.Assert(configAddrType.frontendMappings[0].podBackends[i], checker.Equals, expectedbes[i])
+	}
+
+	// Delete the LRP.
+	err = m.rpm.DeleteRedirectPolicy(configAddrType)
+
+	c.Assert(err, IsNil)
+	c.Assert(len(m.rpm.policyFrontendsByHash), Equals, 0)
+	c.Assert(len(m.rpm.policyPods), Equals, 0)
+	c.Assert(len(m.rpm.policyConfigs), Equals, 0)
+}
+
+// Tests add redirect policy, add pod, delete pod and delete redirect policy events
+// for an addressMatcher config with a frontend having multiple named ports.
+func (m *ManagerSuite) TestManager_AddrMatcherConfigMultiplePorts(c *C) {
+	// Add an addressMatcher type LRP with multiple named ports.
+	configAddrType.frontendType = addrFrontendNamedPorts
+	configAddrType.frontendMappings = append(configAddrType.frontendMappings, &feMapping{
+		feAddr:      fe2,
+		podBackends: nil,
+		fePort:      portName2,
+	})
+	beP1.name = portName1
+	beP2.name = portName2
+	configAddrType.backendPorts = []bePortInfo{beP1, beP2}
+	configAddrType.backendPortsByPortName = map[string]*bePortInfo{
+		beP1.name: &configAddrType.backendPorts[0],
+		beP2.name: &configAddrType.backendPorts[1]}
+	podIPs, _ := utils.ValidIPs(pod1.Status)
+	expectedbes := make([]backend, len(podIPs))
+
+	added, err := m.rpm.AddRedirectPolicy(configAddrType, &fakePodStore{})
+
+	c.Assert(added, Equals, true)
+	c.Assert(err, IsNil)
+	c.Assert(len(m.rpm.policyConfigs), Equals, 1)
+	c.Assert(m.rpm.policyConfigs[configAddrType.id].id.Name, Equals, configAddrType.id.Name)
+	c.Assert(m.rpm.policyConfigs[configAddrType.id].id.Namespace, Equals, configAddrType.id.Namespace)
+	c.Assert(len(m.rpm.policyFrontendsByHash), Equals, 2)
+	for _, id := range m.rpm.policyFrontendsByHash {
+		c.Assert(id, Equals, configAddrType.id)
+	}
+	// Frontend ports should be mapped to the corresponding backend ports.
+	for _, feM := range configAddrType.frontendMappings {
+		switch feM.fePort {
+		case "test1":
+			c.Assert(len(feM.podBackends), Equals, 2)
+			for i := range podIPs {
+				expectedbes[i] = backend{
+					L3n4Addr: lb.L3n4Addr{IP: net.ParseIP(podIPs[i]), L4Addr: beP1.l4Addr},
+					podID:    pod1ID,
+				}
+			}
+			for i := range feM.podBackends {
+				c.Assert(feM.podBackends[i], checker.Equals, expectedbes[i])
+			}
+		case "test2":
+			c.Assert(len(feM.podBackends), Equals, 2)
+			for i := range podIPs {
+				expectedbes[i] = backend{
+					L3n4Addr: lb.L3n4Addr{IP: net.ParseIP(podIPs[i]), L4Addr: beP2.l4Addr},
+					podID:    pod1ID,
+				}
+			}
+			for i := range feM.podBackends {
+				c.Assert(feM.podBackends[i], checker.Equals, expectedbes[i])
+			}
+		default:
+			log.Errorf("Unknown port %s", feM.fePort)
+		}
+	}
+	c.Assert(len(m.rpm.policyPods), Equals, 1)
+	c.Assert(len(m.rpm.policyPods[pod1ID]), Equals, 1)
+	c.Assert(m.rpm.policyPods[pod1ID][0], Equals, configAddrType.id)
+
+	// Delete the LRP.
+	err = m.rpm.DeleteRedirectPolicy(configAddrType)
+
+	c.Assert(err, IsNil)
+	c.Assert(len(m.rpm.policyFrontendsByHash), Equals, 0)
+	c.Assert(len(m.rpm.policyPods), Equals, 0)
+	c.Assert(len(m.rpm.policyConfigs), Equals, 0)
+}
+
+//TODO Tests for svcMatcher
diff --git a/pkg/service/id_test.go b/pkg/service/id_test.go
index f6bc26d2d2..65cda1fca1 100644
--- a/pkg/service/id_test.go
+++ b/pkg/service/id_test.go
@@ -87,11 +87,10 @@ func (s *IDAllocTestSuite) TestServices(c *C) {
 	c.Assert(err, Equals, nil)
 	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+1))
 
-	// l3n4Addr3 should have the same ID as l3n4Addr2 since we are omitting the
-	// protocol type.
+	// different protocols should change the id
 	l3n4AddrID, err = AcquireID(l3n4Addr3, 0)
 	c.Assert(err, Equals, nil)
-	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+1))
+	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+2))
 
 	gotL3n4AddrID, err := GetID(FirstFreeServiceID)
 	c.Assert(err, Equals, nil)
diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go
index 477f35901c..0b5fea4647 100644
--- a/pkg/service/service_test.go
+++ b/pkg/service/service_test.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/cilium/cilium/pkg/checker"
 	"github.com/cilium/cilium/pkg/cidr"
+	"github.com/cilium/cilium/pkg/loadbalancer"
 	lb "github.com/cilium/cilium/pkg/loadbalancer"
 	"github.com/cilium/cilium/pkg/maps/lbmap"
 	nodeTypes "github.com/cilium/cilium/pkg/node/types"
@@ -68,6 +69,7 @@ func (m *ManagerTestSuite) TearDownTest(c *C) {
 var (
 	frontend1 = *lb.NewL3n4AddrID(lb.TCP, net.ParseIP("1.1.1.1"), 80, lb.ScopeExternal, 0)
 	frontend2 = *lb.NewL3n4AddrID(lb.TCP, net.ParseIP("1.1.1.2"), 80, lb.ScopeExternal, 0)
+	frontend3 = *lb.NewL3n4AddrID(lb.UDP, net.ParseIP("1.1.1.2"), 80, lb.ScopeExternal, 0)
 	backends1 = []lb.Backend{
 		*lb.NewBackend(0, lb.TCP, net.ParseIP("10.0.0.1"), 8080),
 		*lb.NewBackend(0, lb.TCP, net.ParseIP("10.0.0.2"), 8080),
@@ -76,6 +78,10 @@ var (
 		*lb.NewBackend(0, lb.TCP, net.ParseIP("10.0.0.2"), 8080),
 		*lb.NewBackend(0, lb.TCP, net.ParseIP("10.0.0.3"), 8080),
 	}
+	backends3 = []lb.Backend{
+		*lb.NewBackend(0, lb.UDP, net.ParseIP("10.0.0.2"), 8080),
+		*lb.NewBackend(0, lb.UDP, net.ParseIP("10.0.0.3"), 8080),
+	}
 )
 
 func (m *ManagerTestSuite) TestUpsertAndDeleteService(c *C) {
@@ -170,9 +176,50 @@ func (m *ManagerTestSuite) TestUpsertAndDeleteService(c *C) {
 	c.Assert(len(m.lbmap.AffinityMatch[uint16(id2)]), Equals, 2)
 	c.Assert(len(m.lbmap.SourceRanges[uint16(id2)]), Equals, 2)
 
+	// Should add a similar service ot the others, different only by protocol;
+	// the other service(s) should not be corrupted/affected.
+	p3 := &lb.SVC{
+		Frontend:                  frontend3,
+		Backends:                  backends3,
+		Type:                      lb.SVCTypeLoadBalancer,
+		TrafficPolicy:             lb.SVCTrafficPolicyCluster,
+		SessionAffinity:           true,
+		SessionAffinityTimeoutSec: 300,
+		Name:                      "svc3",
+		Namespace:                 "ns2",
+		LoadBalancerSourceRanges:  []*cidr.CIDR{cidr1, cidr2},
+	}
+	created, id3, err := m.svc.UpsertService(p3)
+	c.Assert(err, IsNil)
+	c.Assert(created, Equals, true)
+	c.Assert(id3, Equals, lb.ID(3))
+	c.Assert(m.lbmap.ServiceByID[uint16(id3)].Frontend.Protocol, Equals, loadbalancer.UDP)
+	c.Assert(len(m.lbmap.ServiceByID[uint16(id3)].Backends), Equals, 2)
+	c.Assert(m.lbmap.ServiceByID[uint16(id3)].Backends[0].Protocol, Equals, loadbalancer.UDP)
+	c.Assert(m.lbmap.ServiceByID[uint16(id3)].Backends, Not(DeepEquals), m.lbmap.ServiceByID[uint16(id2)].Backends)
+	c.Assert(len(m.lbmap.BackendByID), Equals, 4)
+	c.Assert(m.svc.svcByID[id3].svcName, Equals, "svc3")
+	c.Assert(m.svc.svcByID[id3].svcNamespace, Equals, "ns2")
+	c.Assert(len(m.lbmap.AffinityMatch[uint16(id3)]), Equals, 2)
+	c.Assert(len(m.lbmap.SourceRanges[uint16(id3)]), Equals, 2)
+	// check that the similar service has not been overwritten
+	c.Assert(m.lbmap.ServiceByID[uint16(id2)].Frontend.Protocol, Equals, loadbalancer.TCP)
+	c.Assert(len(m.lbmap.ServiceByID[uint16(id2)].Backends), Equals, 2)
+	c.Assert(m.lbmap.ServiceByID[uint16(id2)].Backends[0].Protocol, Equals, loadbalancer.TCP)
+	c.Assert(m.svc.svcByID[id2].svcName, Equals, "svc2")
+	c.Assert(m.svc.svcByID[id2].svcNamespace, Equals, "ns2")
+	c.Assert(len(m.lbmap.AffinityMatch[uint16(id2)]), Equals, 2)
+	c.Assert(len(m.lbmap.SourceRanges[uint16(id2)]), Equals, 2)
+	// delete newest added service
+	found, err := m.svc.DeleteServiceByID(lb.ServiceID(id3))
+	c.Assert(err, IsNil)
+	c.Assert(found, Equals, true)
+	c.Assert(len(m.lbmap.ServiceByID), Equals, 2)
+	c.Assert(len(m.lbmap.BackendByID), Equals, 2)
+	c.Assert(len(m.lbmap.AffinityMatch[uint16(id3)]), Equals, 0)
 	// Should remove the service and the backend, but keep another service and
 	// its backends. Also, should remove the affinity match.
-	found, err := m.svc.DeleteServiceByID(lb.ServiceID(id1))
+	found, err = m.svc.DeleteServiceByID(lb.ServiceID(id1))
 	c.Assert(err, IsNil)
 	c.Assert(found, Equals, true)
 	c.Assert(len(m.lbmap.ServiceByID), Equals, 1)
@@ -568,7 +615,8 @@ func (m *ManagerTestSuite) TestLocalRedirectLocalBackendSelection(c *C) {
 }
 
 // Local redirect service should be able to override a ClusterIP service with same
-// frontend, but reverse should produce an error.
+// frontend, but reverse should produce an error. Also, it should not override
+// any other type besides itself or clusterIP type.
 func (m *ManagerTestSuite) TestLocalRedirectServiceOverride(c *C) {
 	// Create a node-local backend.
 	localBackend := backends1[0]
@@ -621,4 +669,31 @@ func (m *ManagerTestSuite) TestLocalRedirectServiceOverride(c *C) {
 
 	c.Assert(err, NotNil)
 	c.Assert(created, Equals, false)
+
+	p2 := &lb.SVC{
+		Frontend:      frontend2,
+		Backends:      allBackends,
+		Type:          lb.SVCTypeNodePort,
+		TrafficPolicy: lb.SVCTrafficPolicyCluster,
+		Name:          "svc2",
+		Namespace:     "ns1",
+	}
+
+	// Insert the service entry of type NodePort.
+	created, id, err = m.svc.UpsertService(p2)
+	c.Assert(err, IsNil)
+	c.Assert(created, Equals, true)
+	c.Assert(id, Not(Equals), lb.ID(0))
+
+	svc, ok = m.svc.svcByID[id]
+	c.Assert(len(svc.backends), Equals, len(allBackends))
+	c.Assert(ok, Equals, true)
+
+	// Insert the service entry of type Local Redirect.
+	p2.Type = lb.SVCTypeLocalRedirect
+	created, _, err = m.svc.UpsertService(p2)
+
+	// Local redirect service should not override the NodePort service.
+	c.Assert(err, NotNil)
+	c.Assert(created, Equals, false)
 }
diff --git a/pkg/service/store/store_test.go b/pkg/service/store/store_test.go
index fdfb0ee470..bff6bb8db0 100644
--- a/pkg/service/store/store_test.go
+++ b/pkg/service/store/store_test.go
@@ -61,45 +61,45 @@ func (s *ServiceGenericSuite) TestPortConfigurationDeepEqual(c *check.C) {
 
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			want: true,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			b: PortConfiguration{
-				"foz": {Protocol: loadbalancer.NONE, Port: 1},
+				"foz": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 2},
+				"foo": {Protocol: loadbalancer.TCP, Port: 2},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
-				"baz": {Protocol: loadbalancer.NONE, Port: 2},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"baz": {Protocol: loadbalancer.TCP, Port: 2},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"foo": {Protocol: loadbalancer.TCP, Port: 1},
 			},
 			want: false,
 		},
diff --git a/test/helpers/cilium.go b/test/helpers/cilium.go
index a0aa05c114..307fa748b2 100644
--- a/test/helpers/cilium.go
+++ b/test/helpers/cilium.go
@@ -799,9 +799,12 @@ func (s *SSHMeta) ServiceIsSynced(id int) (bool, error) {
 		return false, err
 	}
 
-	frontendAddr := net.JoinHostPort(
+	frontendAddr := serviceAddressKey(
 		svc.Status.Realized.FrontendAddress.IP,
-		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port))
+		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port),
+		svc.Status.Realized.FrontendAddress.Protocol,
+		"",
+	)
 	lb, ok := bpfLB[frontendAddr]
 	if ok == false {
 		return false, fmt.Errorf(
diff --git a/test/helpers/cons.go b/test/helpers/cons.go
index d10911de56..fd1663c436 100644
--- a/test/helpers/cons.go
+++ b/test/helpers/cons.go
@@ -22,7 +22,7 @@ import (
 
 	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
 	"github.com/cilium/cilium/pkg/versioncheck"
-	"github.com/cilium/cilium/test/ginkgo-ext"
+	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
 )
 
 var (
diff --git a/test/helpers/kubectl.go b/test/helpers/kubectl.go
index 92cf6160da..e8ee77a958 100644
--- a/test/helpers/kubectl.go
+++ b/test/helpers/kubectl.go
@@ -1721,7 +1721,7 @@ func (kub *Kubectl) validateServicePlumbingInCiliumPod(fullName, ciliumPod strin
 	for _, port := range serviceObj.Spec.Ports {
 		var foundPort *v1.ServicePort
 		for _, realizedService := range realizedServices {
-			if port.Port == int32(realizedService.FrontendAddress.Port) {
+			if compareServicePortToFrontEnd(&port, realizedService.FrontendAddress) {
 				foundPort = &port
 				break
 			}
@@ -1730,8 +1730,8 @@ func (kub *Kubectl) validateServicePlumbingInCiliumPod(fullName, ciliumPod strin
 			return fmt.Errorf("port %d of service %s (%s) not found in cilium pod %s",
 				port.Port, fullName, serviceObj.Spec.ClusterIP, ciliumPod)
 		}
-
-		if _, ok := lbMap[net.JoinHostPort(serviceObj.Spec.ClusterIP, fmt.Sprintf("%d", port.Port))]; !ok {
+		lKey := serviceAddressKey(serviceObj.Spec.ClusterIP, fmt.Sprintf("%d", port.Port), string(port.Protocol), "")
+		if _, ok := lbMap[lKey]; !ok {
 			return fmt.Errorf("port %d of service %s (%s) not found in cilium bpf lb list of pod %s",
 				port.Port, fullName, serviceObj.Spec.ClusterIP, ciliumPod)
 		}
@@ -1743,10 +1743,11 @@ func (kub *Kubectl) validateServicePlumbingInCiliumPod(fullName, ciliumPod strin
 				foundBackend, foundBackendLB := false, false
 				for _, realizedService := range realizedServices {
 					frontEnd := realizedService.FrontendAddress
-					lb := lbMap[net.JoinHostPort(frontEnd.IP, fmt.Sprintf("%d", frontEnd.Port))]
-
+					lbKey := serviceAddressKey(frontEnd.IP, fmt.Sprintf("%d", frontEnd.Port), string(frontEnd.Protocol), "")
+					lb := lbMap[lbKey]
 					for _, backAddr := range realizedService.BackendAddresses {
-						if addr.IP == *backAddr.IP && uint16(port.Port) == backAddr.Port {
+						if addr.IP == *backAddr.IP && uint16(port.Port) == backAddr.Port &&
+							compareProto(string(port.Protocol), backAddr.Protocol) {
 							foundBackend = true
 							for _, backend := range lb {
 								if strings.Contains(backend, net.JoinHostPort(*backAddr.IP, fmt.Sprintf("%d", port.Port))) {
@@ -2845,41 +2846,47 @@ func (kub *Kubectl) CiliumPolicyAction(namespace, filepath string, action Resour
 	kub.Logger().Infof("Performing %s action on resource '%s'", action, filepath)
 
 	if status := kub.Action(action, filepath, namespace); !status.WasSuccessful() {
-		return "", status.GetErr(fmt.Sprintf("Cannot perform '%s' on resorce '%s'", action, filepath))
+		return "", status.GetErr(fmt.Sprintf("Cannot perform '%s' on resource '%s'", action, filepath))
 	}
 
 	// If policy is uninstalled we can't require a policy being enforced.
 	if action != KubectlDelete {
 		jqFilter := getPolicyEnforcingJqFilter(numNodes)
 		body := func() bool {
-			var data []map[string]string
-			cmd := fmt.Sprintf("%s get cnp --all-namespaces -o json | jq '%s'",
-				KubectlCmd, jqFilter)
-
-			res := kub.ExecShort(cmd)
-			if !res.WasSuccessful() {
-				kub.Logger().WithError(res.GetErr("")).Error("cannot get cnp status")
-				return false
+			cmds := map[string]string{
+				"CNP":  fmt.Sprintf("%s get cnp --all-namespaces -o json | jq '%s'", KubectlCmd, jqFilter),
+				"CCNP": fmt.Sprintf("%s get ccnp -o json | jq '%s'", KubectlCmd, jqFilter),
 			}
 
-			err := res.Unmarshal(&data)
-			if err != nil {
-				kub.Logger().WithError(err).Error("Cannot unmarshal json")
-				return false
-			}
+			for ctx, cmd := range cmds {
+				var data []map[string]string
 
-			for _, item := range data {
-				if item["enforcing"] != "true" || item["status"] != "true" {
-					kub.Logger().Errorf("Policy '%s' is not enforcing yet", item["name"])
+				res := kub.ExecShort(cmd)
+				if !res.WasSuccessful() {
+					kub.Logger().WithError(res.GetErr("")).Errorf("cannot get %s status", ctx)
 					return false
 				}
+
+				err := res.Unmarshal(&data)
+				if err != nil {
+					kub.Logger().WithError(err).Errorf("Cannot unmarshal json for %s status", ctx)
+					return false
+				}
+
+				for _, item := range data {
+					if item["enforcing"] != "true" || item["status"] != "true" {
+						kub.Logger().Errorf("%s policy '%s' is not enforcing yet", ctx, item["name"])
+						return false
+					}
+				}
 			}
+
 			return true
 		}
 
 		err = WithTimeout(
 			body,
-			"Timed out while waiting CNP to be enforced",
+			"Timed out while waiting for policies to be enforced",
 			&TimeoutConfig{Timeout: timeout})
 
 		if err != nil {
@@ -3747,7 +3754,7 @@ CILIUM_SERVICES:
 	for _, cSvc := range ciliumSvcs {
 		if cSvc.Status.Realized.FrontendAddress.IP == k8sService.Spec.ClusterIP {
 			for _, port := range k8sService.Spec.Ports {
-				if int32(cSvc.Status.Realized.FrontendAddress.Port) == port.Port {
+				if compareServicePortToFrontEnd(&port, cSvc.Status.Realized.FrontendAddress) {
 					ciliumService = &cSvc
 					break CILIUM_SERVICES
 				}
@@ -3768,7 +3775,11 @@ CILIUM_SERVICES:
 }
 
 // CiliumServiceAdd adds the given service on a 'pod' running Cilium
-func (kub *Kubectl) CiliumServiceAdd(pod string, id int64, frontend string, backends []string, svcType, trafficPolicy string) error {
+func (kub *Kubectl) CiliumServiceAdd(pod string, id int64, protocol string, frontend string, backends []string, svcType, trafficPolicy string) error {
+	protocol = strings.ToLower(protocol)
+	if protocol == "" || protocol == "any" || protocol == "none" {
+		protocol = "tcp"
+	}
 	var opts []string
 	switch strings.ToLower(svcType) {
 	case "nodeport":
@@ -3795,8 +3806,8 @@ func (kub *Kubectl) CiliumServiceAdd(pod string, id int64, frontend string, back
 	backendsStr := strings.Join(backends, ",")
 	ctx, cancel := context.WithTimeout(context.Background(), ShortCommandTimeout)
 	defer cancel()
-	return kub.CiliumExecContext(ctx, pod, fmt.Sprintf("cilium service update --id %d --frontend %q --backends %q %s",
-		id, frontend, backendsStr, optsStr)).GetErr("cilium service update")
+	return kub.CiliumExecContext(ctx, pod, fmt.Sprintf("cilium service update --id %d --protocol %q --frontend %q --backends %q %s",
+		id, protocol, frontend, backendsStr, optsStr)).GetErr("cilium service update")
 }
 
 // CiliumServiceDel deletes the service with 'id' on a 'pod' running Cilium
@@ -4020,7 +4031,7 @@ func validateCiliumSvc(cSvc models.Service, k8sSvcs []v1.Service, k8sEps []v1.En
 
 	var k8sServicePort *v1.ServicePort
 	for _, k8sPort := range k8sService.Spec.Ports {
-		if k8sPort.Port == int32(cSvc.Status.Realized.FrontendAddress.Port) {
+		if compareServicePortToFrontEnd(&k8sPort, cSvc.Status.Realized.FrontendAddress) {
 			k8sServicePort = &k8sPort
 			k8sServicesFound[serviceKey(*k8sService)] = true
 			break
@@ -4049,14 +4060,16 @@ func validateCiliumSvc(cSvc models.Service, k8sSvcs []v1.Service, k8sEps []v1.En
 }
 
 func validateCiliumSvcLB(cSvc models.Service, lbMap map[string][]string) error {
-	scope := ""
+	var scope string
 	if cSvc.Status.Realized.FrontendAddress.Scope == models.FrontendAddressScopeInternal {
 		scope = "/i"
 	}
-
-	frontendAddress := net.JoinHostPort(
+	frontendAddress := serviceAddressKey(
 		cSvc.Status.Realized.FrontendAddress.IP,
-		strconv.Itoa(int(cSvc.Status.Realized.FrontendAddress.Port))) + scope
+		strconv.Itoa(int(cSvc.Status.Realized.FrontendAddress.Port)),
+		cSvc.Status.Realized.FrontendAddress.Protocol,
+		scope,
+	)
 	bpfBackends, ok := lbMap[frontendAddress]
 	if !ok {
 		return fmt.Errorf("%s bpf lb map entry not found", frontendAddress)
@@ -4093,7 +4106,9 @@ func getK8sEndpointAddresses(ep v1.Endpoints) []*models.BackendAddress {
 }
 
 func addrsEqual(addr1, addr2 *models.BackendAddress) bool {
-	return *addr1.IP == *addr2.IP && addr1.Port == addr2.Port
+	return *addr1.IP == *addr2.IP &&
+		addr1.Port == addr2.Port &&
+		compareProto(addr1.Protocol, addr2.Protocol)
 }
 
 // GenerateNamespaceForTest generates a namespace based off of the current test
@@ -4224,3 +4239,36 @@ func (kub *Kubectl) CleanupCiliumComponents() {
 	}
 	wg.Wait()
 }
+
+func compareProto(proto1, proto2 string) bool {
+	proto1 = strings.ToLower(proto1)
+	if proto1 == "" || proto1 == "none" || proto1 == "any" {
+		return true
+	}
+	proto2 = strings.ToLower(proto2)
+	if proto2 == "" || proto2 == "none" || proto2 == "any" {
+		return true
+	}
+	return proto1 == proto2
+}
+
+func compareServicePortToFrontEnd(sP *v1.ServicePort, fA *models.FrontendAddress) bool {
+	return sP.Port == int32(fA.Port) && compareProto(string(sP.Protocol), fA.Protocol)
+}
+
+func compareBackendToFrontEnd(bA *models.BackendAddress, fA *models.FrontendAddress) bool {
+	return bA.Port == fA.Port && compareProto(bA.Protocol, fA.Protocol)
+}
+
+func serviceAddressKey(ip, port, proto, scope string) string {
+	newOutputStyle := HasNewServiceOutput(GetRunningCiliumVersion())
+	k := net.JoinHostPort(ip, port)
+	if newOutputStyle {
+		p := strings.ToLower(proto)
+		if p == "" || p == "none" || p == "any" {
+			proto = "NONE"
+		}
+		return fmt.Sprintf("%s/%s%s", k, proto, scope)
+	}
+	return fmt.Sprintf("%s%s", k, scope)
+}
diff --git a/test/helpers/utils.go b/test/helpers/utils.go
index 891daa9b39..9872a3ee29 100644
--- a/test/helpers/utils.go
+++ b/test/helpers/utils.go
@@ -39,8 +39,11 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-// ensure that our random numbers are seeded differently on each run
-var randGen = rand.NewSafeRand(time.Now().UnixNano())
+var (
+	// ensure that our random numbers are seeded differently on each run
+	randGen              = rand.NewSafeRand(time.Now().UnixNano())
+	runningCiliumVersion string
+)
 
 // IsRunningOnJenkins detects if the currently running Ginkgo application is
 // most likely running in a Jenkins environment. Returns true if certain
@@ -60,6 +63,35 @@ func IsRunningOnJenkins() bool {
 	return result
 }
 
+// SetCiliumVersion sets the currently running cilium version
+// and returns a function that unsets it.
+func SetRunningCiliumVersion(v string) func() {
+	runningCiliumVersion = v
+	return func() {
+		runningCiliumVersion = ""
+	}
+}
+
+// GetRunningCiliumVersion gets the currently running cilium version.
+func GetRunningCiliumVersion() string {
+	return runningCiliumVersion
+}
+
+// HasNewServiceOutput checks to see if the current running cilium
+// version uses the old style service output (e.g. "0.0.0.0:53") vs
+// the new style (e.g. "0.0.0.0:53/TCP").
+func HasNewServiceOutput(ver string) bool {
+	cst, err := versioncheck.Version(ver)
+	// If the version is not parseable it is probably
+	// someone's custom build  or not set.
+	// Either way, it is probably using the new output
+	// format.
+	if err != nil {
+		return true
+	}
+	return versioncheck.MustCompile(">=1.9.0")(cst)
+}
+
 // Sleep sleeps for the specified duration in seconds
 func Sleep(delay time.Duration) {
 	time.Sleep(delay * time.Second)
@@ -218,6 +250,7 @@ func GetAppPods(apps []string, namespace string, kubectl *Kubectl, appFmt string
 		res, err := kubectl.GetPodNames(namespace, fmt.Sprintf("%s=%s", appFmt, v))
 		Expect(err).Should(BeNil())
 		Expect(res).Should(Not(BeNil()))
+		Expect(len(res)).To(BeNumerically(">", 0))
 		appPods[v] = res[0]
 		log.Infof("GetAppPods: pod=%q assigned to %q", res[0], v)
 	}
diff --git a/test/k8sT/DatapathConfiguration.go b/test/k8sT/DatapathConfiguration.go
index ea02487ce0..41a9734009 100644
--- a/test/k8sT/DatapathConfiguration.go
+++ b/test/k8sT/DatapathConfiguration.go
@@ -254,11 +254,10 @@ var _ = Describe("K8sDatapathConfig", func() {
 			Expect(testPodConnectivityAcrossNodes(kubectl)).Should(BeTrue(), "Connectivity test between nodes failed")
 		})
 
-		It("Check vxlan connectivity with per endpoint routes", func() {
-			Skip("Encapsulation mode is not supported with per-endpoint routes")
-
+		It("Check vxlan connectivity with per-endpoint routes", func() {
 			deploymentManager.DeployCilium(map[string]string{
-				"global.autoDirectNodeRoutes": "true",
+				"global.tunnel":                 "vxlan",
+				"global.endpointRoutes.enabled": "true",
 			}, DeployCiliumOptionsAndDNS)
 			Expect(testPodConnectivityAcrossNodes(kubectl)).Should(BeTrue(), "Connectivity test between nodes failed")
 
diff --git a/test/k8sT/Services.go b/test/k8sT/Services.go
index 6101b0028f..934acb661a 100644
--- a/test/k8sT/Services.go
+++ b/test/k8sT/Services.go
@@ -120,20 +120,20 @@ var _ = Describe("K8sServicesTest", func() {
 		return backends
 	}
 
-	ciliumAddService := func(id int64, frontend string, backends []string, svcType, trafficPolicy string) {
+	ciliumAddService := func(id int64, protocol string, frontend string, backends []string, svcType, trafficPolicy string) {
 		ciliumPods, err := kubectl.GetCiliumPods()
 		ExpectWithOffset(1, err).To(BeNil(), "Cannot get cilium pods")
 		for _, pod := range ciliumPods {
-			err := kubectl.CiliumServiceAdd(pod, id, frontend, backends, svcType, trafficPolicy)
+			err := kubectl.CiliumServiceAdd(pod, id, protocol, frontend, backends, svcType, trafficPolicy)
 			ExpectWithOffset(1, err).To(BeNil(), "Failed to add cilium service")
 		}
 	}
 
-	ciliumAddServiceOnNode := func(node string, id int64, frontend string, backends []string, svcType, trafficPolicy string) {
+	ciliumAddServiceOnNode := func(node string, id int64, protocol string, frontend string, backends []string, svcType, trafficPolicy string) {
 		ciliumPod, err := kubectl.GetCiliumPodOnNode(node)
 		ExpectWithOffset(1, err).To(BeNil(), fmt.Sprintf("Cannot get cilium pod on node %s", node))
 
-		err = kubectl.CiliumServiceAdd(ciliumPod, id, frontend, backends, svcType, trafficPolicy)
+		err = kubectl.CiliumServiceAdd(ciliumPod, id, protocol, frontend, backends, svcType, trafficPolicy)
 		ExpectWithOffset(1, err).To(BeNil(), fmt.Sprintf("Failed to add cilium service on node %s", node))
 	}
 
@@ -324,14 +324,14 @@ var _ = Describe("K8sServicesTest", func() {
 			BeforeAll(func() {
 				// Installs the IPv6 equivalent of app1-service (demo.yaml)
 				httpBackends := ciliumIPv6Backends("-l k8s:id=app1,k8s:io.kubernetes.pod.namespace=default", "80")
-				ciliumAddService(10080, net.JoinHostPort(demoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(10080, "tcp", net.JoinHostPort(demoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends := ciliumIPv6Backends("-l k8s:id=app1,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(10069, net.JoinHostPort(demoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(10069, "udp", net.JoinHostPort(demoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
 				// Installs the IPv6 equivalent of echo (echo-svc.yaml)
 				httpBackends = ciliumIPv6Backends("-l k8s:name=echo,k8s:io.kubernetes.pod.namespace=default", "80")
-				ciliumAddService(20080, net.JoinHostPort(echoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(20080, "tcp", net.JoinHostPort(echoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends = ciliumIPv6Backends("-l k8s:name=echo,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(20069, net.JoinHostPort(echoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(20069, "udp", net.JoinHostPort(echoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
 			})
 
 			AfterAll(func() {
@@ -347,7 +347,7 @@ var _ = Describe("K8sServicesTest", func() {
 				status.ExpectSuccess("cannot curl to service IP from host")
 
 				status = kubectl.ExecInHostNetNS(context.TODO(), k8s1NodeName,
-					helpers.CurlFail(`"tftp://[%s]/hello"`, demoClusterIPv6))
+					helpers.CurlFail(`"tftp://[%s]/hell"`, demoClusterIPv6))
 				status.ExpectSuccess("cannot curl to service IP from host")
 			})
 
@@ -460,9 +460,9 @@ var _ = Describe("K8sServicesTest", func() {
 			BeforeAll(func() {
 				// Install rules for testds-service (demo_ds.yaml)
 				httpBackends := ciliumIPv6Backends("-l k8s:zgroup=testDS,k8s:io.kubernetes.pod.namespace=default", "80")
-				ciliumAddService(31080, net.JoinHostPort(testDSIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(31080, "tcp", net.JoinHostPort(testDSIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends := ciliumIPv6Backends("-l k8s:zgroup=testDS,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(31069, net.JoinHostPort(testDSIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(31069, "udp", net.JoinHostPort(testDSIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
 			})
 
 			AfterAll(func() {
@@ -1365,21 +1365,21 @@ var _ = Describe("K8sServicesTest", func() {
 
 				// Install rules for testds-service NodePort Service(demo_ds.yaml)
 				httpBackends := ciliumIPv6Backends("-l k8s:zgroup=testDS,k8s:io.kubernetes.pod.namespace=default", "80")
-				ciliumAddService(31080, net.JoinHostPort(testDSIPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)), httpBackends, "NodePort", "Cluster")
-				ciliumAddService(31081, net.JoinHostPort("::", fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)), httpBackends, "NodePort", "Cluster")
+				ciliumAddService(31080, string(data.Spec.Ports[0].Protocol), net.JoinHostPort(testDSIPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)), httpBackends, "NodePort", "Cluster")
+				ciliumAddService(31081, string(data.Spec.Ports[0].Protocol), net.JoinHostPort("::", fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)), httpBackends, "NodePort", "Cluster")
 				// Add service corresponding to IPv6 address of the nodes so that they become
 				// reachable from outside the cluster.
-				ciliumAddServiceOnNode(helpers.K8s1, 31082, net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s1, 31082, string(data.Spec.Ports[0].Protocol), net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
 					httpBackends, "NodePort", "Cluster")
-				ciliumAddServiceOnNode(helpers.K8s2, 31082, net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s2, 31082, string(data.Spec.Ports[0].Protocol), net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
 					httpBackends, "NodePort", "Cluster")
 
 				tftpBackends := ciliumIPv6Backends("-l k8s:zgroup=testDS,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(31069, net.JoinHostPort(testDSIPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)), tftpBackends, "NodePort", "Cluster")
-				ciliumAddService(31070, net.JoinHostPort("::", fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)), tftpBackends, "NodePort", "Cluster")
-				ciliumAddServiceOnNode(helpers.K8s1, 31071, net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)),
+				ciliumAddService(31069, string(data.Spec.Ports[1].Protocol), net.JoinHostPort(testDSIPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)), tftpBackends, "NodePort", "Cluster")
+				ciliumAddService(31070, string(data.Spec.Ports[1].Protocol), net.JoinHostPort("::", fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)), tftpBackends, "NodePort", "Cluster")
+				ciliumAddServiceOnNode(helpers.K8s1, 31071, string(data.Spec.Ports[1].Protocol), net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)),
 					tftpBackends, "NodePort", "Cluster")
-				ciliumAddServiceOnNode(helpers.K8s2, 31071, net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s2, 31071, string(data.Spec.Ports[1].Protocol), net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)),
 					tftpBackends, "NodePort", "Cluster")
 			})
 
diff --git a/test/k8sT/Updates.go b/test/k8sT/Updates.go
index efbd56699a..7602fcf936 100644
--- a/test/k8sT/Updates.go
+++ b/test/k8sT/Updates.go
@@ -142,6 +142,8 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 	var (
 		privateIface string // only used when running w/o kube-proxy
 		err          error
+		apps         = []string{helpers.App1, helpers.App2, helpers.App3}
+		app1Service  = "app1-service"
 	)
 
 	canRun, err := helpers.CanRunK8sVersion(oldImageVersion, helpers.GetCurrentK8SEnv())
@@ -160,9 +162,6 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 		ExpectWithOffset(1, err).To(BeNil(), "Unable to determine private iface")
 	}
 
-	apps := []string{helpers.App1, helpers.App2, helpers.App3}
-	app1Service := "app1-service"
-
 	cleanupCiliumState := func(helmPath, chartVersion, imageName, imageTag, registry string) {
 		removeCilium(kubectl)
 
@@ -290,6 +289,8 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 				helpers.CiliumNamespace,
 				opts)
 		}, time.Second*30, time.Second*1).Should(helpers.CMDSuccess(), fmt.Sprintf("Cilium %q was not able to be deployed", oldHelmChartVersion))
+		unsetFn := helpers.SetRunningCiliumVersion(oldImageVersion)
+		defer unsetFn()
 
 		// Cilium is only ready if kvstore is ready, the kvstore is ready if
 		// kube-dns is running.
@@ -369,7 +370,7 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 			if lastCount == -1 {
 				lastCount = currentCount
 			}
-			Expect(lastCount).Should(BeIdenticalTo(currentCount),
+			ExpectWithOffset(1, lastCount).Should(BeIdenticalTo(currentCount),
 				"migrate-svc restart count values do not match")
 		}
 
@@ -379,23 +380,23 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 		ExpectWithOffset(1, res).To(helpers.CMDSuccess(), "cannot apply dempo application")
 
 		err = kubectl.WaitforPods(helpers.DefaultNamespace, "-l zgroup=testapp", timeout)
-		Expect(err).Should(BeNil(), "Test pods are not ready after timeout")
+		ExpectWithOffset(1, err).Should(BeNil(), "Test pods are not ready after timeout")
 
 		_, err = kubectl.CiliumPolicyAction(
 			helpers.DefaultNamespace, l7Policy, helpers.KubectlApply, timeout)
-		Expect(err).Should(BeNil(), "cannot import l7 policy: %v", l7Policy)
+		ExpectWithOffset(1, err).Should(BeNil(), "cannot import l7 policy: %v", l7Policy)
 
 		By("Creating service and clients for migration")
 
 		res = kubectl.ApplyDefault(migrateSVCServer)
 		ExpectWithOffset(1, res).To(helpers.CMDSuccess(), "cannot apply migrate-svc-server")
 		err = kubectl.WaitforPods(helpers.DefaultNamespace, "-l app=migrate-svc-server", timeout)
-		Expect(err).Should(BeNil(), "migrate-svc-server pods are not ready after timeout")
+		ExpectWithOffset(1, err).Should(BeNil(), "migrate-svc-server pods are not ready after timeout")
 
 		res = kubectl.ApplyDefault(migrateSVCClient)
 		ExpectWithOffset(1, res).To(helpers.CMDSuccess(), "cannot apply migrate-svc-client")
 		err = kubectl.WaitforPods(helpers.DefaultNamespace, "-l app=migrate-svc-client", timeout)
-		Expect(err).Should(BeNil(), "migrate-svc-client pods are not ready after timeout")
+		ExpectWithOffset(1, err).Should(BeNil(), "migrate-svc-client pods are not ready after timeout")
 
 		validateEndpointsConnection()
 		checkNoInteruptsInSVCFlows()
@@ -498,6 +499,7 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 				helpers.CiliumNamespace,
 				opts)
 		}, time.Second*30, time.Second*1).Should(helpers.CMDSuccess(), fmt.Sprintf("Cilium %q was not able to be deployed", newHelmChartVersion))
+		helpers.SetRunningCiliumVersion(newImageVersion)
 
 		By("Validating pods have the right image version upgraded")
 		err = helpers.WithTimeout(
@@ -532,6 +534,7 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 			"Cilium Pods are not updating correctly",
 			&helpers.TimeoutConfig{Timeout: timeout})
 		ExpectWithOffset(1, err).To(BeNil(), "Pods are not updating")
+		helpers.SetRunningCiliumVersion(oldImageVersion)
 
 		err = kubectl.WaitforPods(
 			helpers.CiliumNamespace, "-l k8s-app=cilium", timeout)