Repository: cilium/cilium
Pull request #13586 (2020-10-15 13:35:55)

cli: Add cilium bpf lb maglev get $SVC_ID
Maglev lookup tables can be HUGE. So, instead of dumping them all, give a tool to users to dump an individual table by a service ID (aka revNAT ID).



Patch:
diff --git a/CODEOWNERS b/CODEOWNERS
index ec88bf1ab2..e7b2ded2f7 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,27 +1,27 @@
 # Code owners groups and a brief description of their areas:
-# @cilium/janitors      Catch-all for code not otherwise owned
-# @cilium/agent         Cilium Agent
-# @cilium/aws           Integration with AWS
-# @cilium/azure         Integration with Azure
-# @cilium/bpf           BPF Data Path
-# @cilium/build         Building and packaging
-# @cilium/ci            Continuous integration, testing
-# @cilium/cli           Commandline interfaces
-# @cilium/contributing  Developer documentation & tools
-# @cilium/docker        Docker plugin
-# @cilium/docs          Documentation, examples
-# @cilium/endpoint      Endpoint package
-# @cilium/health        Cilium cluster health tool
-# @cilium/hubble        Hubble integration
-# @cilium/ipam          IPAM
-# @cilium/kubernetes    K8s integration, K8s CNI plugin
-# @cilium/kvstore       Key/Value store: Consul, etcd
-# @cilium/loadbalancer  Load balancer
-# @cilium/monitor       Cilium node monitor tool
-# @cilium/operator      Cilium operator
-# @cilium/policy        Policy behaviour
-# @cilium/proxy         L7 proxy, Envoy
-# @cilium/vendor        Vendoring, dependency management
+# @cilium/janitors           Catch-all for code not otherwise owned
+# @cilium/agent              Cilium Agent
+# @cilium/aws                Integration with AWS
+# @cilium/azure              Integration with Azure
+# @cilium/bpf                BPF Data Path
+# @cilium/build              Building and packaging
+# @cilium/ci                 Continuous integration, testing
+# @cilium/cli                Commandline interfaces
+# @cilium/contributing       Developer documentation & tools
+# @cilium/docker             Docker plugin
+# @cilium/docs-structure     Documentation, examples
+# @cilium/endpoint           Endpoint package
+# @cilium/health             Cilium cluster health tool
+# @cilium/hubble             Hubble integration
+# @cilium/ipam               IPAM
+# @cilium/kubernetes         K8s integration, K8s CNI plugin
+# @cilium/kvstore            Key/Value store: Consul, etcd
+# @cilium/loadbalancer       Load balancer
+# @cilium/monitor            Cilium node monitor tool
+# @cilium/operator           Cilium operator
+# @cilium/policy             Policy behaviour
+# @cilium/proxy              L7 proxy, Envoy
+# @cilium/vendor             Vendoring, dependency management
 
 # The following filepaths should be sorted so that more specific paths occur
 # after the less specific paths, otherwise the ownership for the specific paths
@@ -30,7 +30,7 @@
 /.github/ @cilium/contributing
 /.github/workflows/ @cilium/ci
 /.github/workflows/coccicheck.yaml @cilium/ci @cilium/bpf
-/.github/workflows/docs.yaml @cilium/ci @cilium/docs
+/.github/workflows/docs.yaml @cilium/ci @cilium/docs-structure
 /.github/workflows/images.yaml @cilium/ci @cilium/build
 /api/ @cilium/api
 /api/v1/flow/ @cilium/api @cilium/hubble
@@ -64,7 +64,7 @@ Dockerfile* @cilium/build
 /daemon/cmd/proxy.go @cilium/proxy
 /daemon/cmd/state.go @cilium/endpoint
 /daemon/cmd/sysctl_linux.go @cilium/bpf
-/Documentation/ @cilium/docs
+/Documentation/ @cilium/docs-structure
 /Documentation/cmdref @cilium/nonexistantteam
 /Documentation/bpf.rst @cilium/bpf
 /Documentation/contributing/ @cilium/contributing
@@ -74,7 +74,7 @@ Dockerfile* @cilium/build
 /Documentation/gettingstarted/hubble.rst @cilium/hubble
 /Documentation/hubble.rst @cilium/hubble
 /envoy/ @cilium/proxy
-/examples/ @cilium/docs
+/examples/ @cilium/docs-structure
 /examples/crds/ @cilium/kubernetes
 /examples/kubernetes/ @cilium/kubernetes
 /examples/minikube/ @cilium/kubernetes
@@ -82,8 +82,8 @@ Dockerfile* @cilium/build
 /hubble-relay/ @cilium/hubble
 /hubble-relay.Dockerfile @cilium/hubble
 /images @cilium/build
-/install/kubernetes/ @cilium/kubernetes @cilium/docs @cilium/helm
-/install/kubernetes/cilium/templates/hubble* @cilium/kubernetes @cilium/docs @cilium/helm @cilium/hubble
+/install/kubernetes/ @cilium/kubernetes @cilium/docs-structure @cilium/helm
+/install/kubernetes/cilium/templates/hubble* @cilium/kubernetes @cilium/docs-structure @cilium/helm @cilium/hubble
 jenkinsfiles @cilium/ci
 Jenkinsfile.nightly @cilium/ci
 /operator/ @cilium/operator
@@ -154,7 +154,7 @@ Jenkinsfile.nightly @cilium/ci
 /plugins/cilium-cni/ @cilium/kubernetes
 /plugins/cilium-docker/ @cilium/docker
 /proxylib/ @cilium/proxy
-/README.rst @cilium/docs
+/README.rst @cilium/docs-structure
 /test/ @cilium/ci
 /test/bpf/ @cilium/ci @cilium/bpf
 /test/Makefile* @cilium/ci @cilium/build
diff --git a/Documentation/cmdref/cilium-agent.md b/Documentation/cmdref/cilium-agent.md
index b208c4f56f..babb33a5b6 100644
--- a/Documentation/cmdref/cilium-agent.md
+++ b/Documentation/cmdref/cilium-agent.md
@@ -15,202 +15,202 @@ cilium-agent [flags]
 ### Options
 
 ```
-      --agent-health-port int                         TCP port for agent health status API (default 9876)
-      --agent-labels strings                          Additional labels to identify this agent
-      --allow-icmp-frag-needed                        Allow ICMP Fragmentation Needed type packets for purposes like TCP Path MTU. (default true)
-      --allow-localhost string                        Policy when to allow local stack to reach local endpoints { auto | always | policy } (default "auto")
-      --annotate-k8s-node                             Annotate Kubernetes node (default true)
-      --api-rate-limit map                            API rate limiting configuration (example: --rate-limit endpoint-create=rate-limit:10/m,rate-burst:2) (default map[])
-      --auto-create-cilium-node-resource              Automatically create CiliumNode resource for own node on startup (default true)
-      --auto-direct-node-routes                       Enable automatic L2 routing between nodes
-      --bpf-compile-debug                             Enable debugging of the BPF compilation process
-      --bpf-ct-global-any-max int                     Maximum number of entries in non-TCP CT table (default 262144)
-      --bpf-ct-global-tcp-max int                     Maximum number of entries in TCP CT table (default 524288)
-      --bpf-ct-timeout-regular-any duration           Timeout for entries in non-TCP CT table (default 1m0s)
-      --bpf-ct-timeout-regular-tcp duration           Timeout for established entries in TCP CT table (default 6h0m0s)
-      --bpf-ct-timeout-regular-tcp-fin duration       Teardown timeout for entries in TCP CT table (default 10s)
-      --bpf-ct-timeout-regular-tcp-syn duration       Establishment timeout for entries in TCP CT table (default 1m0s)
-      --bpf-ct-timeout-service-any duration           Timeout for service entries in non-TCP CT table (default 1m0s)
-      --bpf-ct-timeout-service-tcp duration           Timeout for established service entries in TCP CT table (default 6h0m0s)
-      --bpf-fragments-map-max int                     Maximum number of entries in fragments tracking map (default 8192)
-      --bpf-lb-maglev-hash-seed string                Maglev cluster-wide hash seed (base64 encoded) (default "JLfvgnHc2kaSUFaI")
-      --bpf-lb-maglev-table-size uint                 Maglev per service backend table size (parameter M) (default 16381)
-      --bpf-lb-map-max int                            Maximum number of entries in Cilium BPF lbmap (default 65536)
-      --bpf-map-dynamic-size-ratio float              Ratio (0.0-1.0) of total system memory to use for dynamic sizing of CT, NAT and policy BPF maps. Set to 0.0 to disable dynamic BPF map sizing (default: 0.0)
-      --bpf-nat-global-max int                        Maximum number of entries for the global BPF NAT table (default 524288)
-      --bpf-neigh-global-max int                      Maximum number of entries for the global BPF neighbor table (default 524288)
-      --bpf-policy-map-max int                        Maximum number of entries in endpoint policy map (per endpoint) (default 16384)
-      --bpf-root string                               Path to BPF filesystem
-      --bpf-sock-rev-map-max int                      Maximum number of entries for the SockRevNAT BPF map (default 262144)
-      --certificates-directory string                 Root directory to find certificates specified in L7 TLS policy enforcement (default "/var/run/cilium/certs")
-      --cgroup-root string                            Path to Cgroup2 filesystem
-      --cluster-id int                                Unique identifier of the cluster
-      --cluster-name string                           Name of the cluster (default "default")
-      --clustermesh-config string                     Path to the ClusterMesh configuration directory
-      --config string                                 Configuration file (default "$HOME/ciliumd.yaml")
-      --config-dir string                             Configuration directory that contains a file for each option
-      --conntrack-gc-interval duration                Overwrite the connection-tracking garbage collection interval
-      --crd-wait-timeout duration                     Cilium will exit if CRDs are not available within this duration upon startup (default 5m0s)
-      --datapath-mode string                          Datapath mode name (default "veth")
-  -D, --debug                                         Enable debugging mode
-      --debug-verbose strings                         List of enabled verbose debug groups
-      --devices strings                               List of devices facing cluster/external network (used for BPF NodePort, BPF masquerading and host firewall)
-      --direct-routing-device string                  Device name used to connect nodes in direct routing mode (required only by BPF NodePort; if empty, automatically set to a device with k8s InternalIP/ExternalIP or with a default route)
-      --disable-cnp-status-updates                    Do not send CNP NodeStatus updates to the Kubernetes api-server (recommended to run with "cnp-node-status-gc-interval=0" in cilium-operator)
-      --disable-conntrack                             Disable connection tracking
-      --disable-endpoint-crd                          Disable use of CiliumEndpoint CRD
-      --disable-iptables-feeder-rules strings         Chains to ignore when installing feeder rules.
-      --egress-masquerade-interfaces string           Limit egress masquerading to interface selector
-      --enable-auto-protect-node-port-range           Append NodePort range to net.ipv4.ip_local_reserved_ports if it overlaps with ephemeral port range (net.ipv4.ip_local_port_range) (default true)
-      --enable-bandwidth-manager                      Enable BPF bandwidth manager
-      --enable-bpf-clock-probe                        Enable BPF clock source probing for more efficient tick retrieval
-      --enable-bpf-masquerade                         Masquerade packets from endpoints leaving the host with BPF instead of iptables
-      --enable-bpf-tproxy                             Enable BPF-based proxy redirection, if support available
-      --enable-endpoint-health-checking               Enable connectivity health checking between virtual endpoints (default true)
-      --enable-endpoint-routes                        Use per endpoint routes instead of routing via cilium_host
-      --enable-external-ips                           Enable k8s service externalIPs feature (requires enabling enable-node-port) (default true)
-      --enable-health-check-nodeport                  Enables a healthcheck nodePort server for NodePort services with 'healthCheckNodePort' being set (default true)
-      --enable-health-checking                        Enable connectivity health checking (default true)
-      --enable-host-firewall                          Enable host network policies (beta)
-      --enable-host-legacy-routing                    Enable the legacy host forwarding model which does not bypass upper stack in host namespace
-      --enable-host-port                              Enable k8s hostPort mapping feature (requires enabling enable-node-port) (default true)
-      --enable-host-reachable-services                Enable reachability of services for host applications (beta)
-      --enable-hubble                                 Enable hubble server
-      --enable-identity-mark                          Enable setting identity mark for local traffic (default true)
-      --enable-ip-masq-agent                          Enable BPF ip-masq-agent
-      --enable-ipsec                                  Enable IPSec support
-      --enable-ipv4                                   Enable IPv4 support (default true)
-      --enable-ipv4-fragment-tracking                 Enable IPv4 fragments tracking for L4-based lookups (default true)
-      --enable-ipv6                                   Enable IPv6 support (default true)
-      --enable-ipv6-ndp                               Enable IPv6 NDP support
-      --enable-k8s-api-discovery                      Enable discovery of Kubernetes API groups and resources with the discovery API
-      --enable-k8s-endpoint-slice                     Enables k8s EndpointSlice feature in Cilium if the k8s cluster supports it (default true)
-      --enable-k8s-event-handover                     Enable k8s event handover to kvstore for improved scalability
-      --enable-l7-proxy                               Enable L7 proxy for L7 policy enforcement (default true)
-      --enable-local-node-route                       Enable installation of the route which points the allocation prefix of the local node (default true)
-      --enable-monitor                                Enable the monitor unix domain socket server (default true)
-      --enable-node-port                              Enable NodePort type services by Cilium (beta)
-      --enable-policy string                          Enable policy enforcement (default "default")
-      --enable-remote-node-identity                   Enable use of remote node identity
-      --enable-session-affinity                       Enable support for service session affinity
-      --enable-svc-source-range-check                 Enable check of service source ranges (currently, only for LoadBalancer) (default true)
-      --enable-tracing                                Enable tracing while determining policy (debugging)
-      --enable-well-known-identities                  Enable well-known identities for known Kubernetes components (default true)
-      --enable-xt-socket-fallback                     Enable fallback for missing xt_socket module (default true)
-      --encrypt-interface string                      Transparent encryption interface
-      --encrypt-node                                  Enables encrypting traffic from non-Cilium pods and host networking
-      --endpoint-interface-name-prefix string         Prefix of interface name shared by all endpoints (default "lxc+")
-      --endpoint-queue-size int                       size of EventQueue per-endpoint (default 25)
-      --endpoint-status strings                       Enable additional CiliumEndpoint status features (controllers,health,log,policy,state)
-      --envoy-log string                              Path to a separate Envoy log file, if any
-      --exclude-local-address strings                 Exclude CIDR from being recognized as local address
-      --fixed-identity-mapping map                    Key-value for the fixed identity mapping which allows to use reserved label for fixed identities (default map[])
-      --flannel-master-device string                  Installs a BPF program to allow for policy enforcement in the given network interface. Allows to run Cilium on top of other CNI plugins that provide networking, e.g. flannel, where for flannel, this value should be set with 'cni0'. [EXPERIMENTAL]
-      --flannel-uninstall-on-exit                     When used along the flannel-master-device flag, it cleans up all BPF programs installed when Cilium agent is terminated.
-      --force-local-policy-eval-at-source             Force policy evaluation of all local communication at the source endpoint (default true)
-  -h, --help                                          help for cilium-agent
-      --host-reachable-services-protos strings        Only enable reachability of services for host applications for specific protocols (default [tcp,udp])
-      --http-idle-timeout uint                        Time after which a non-gRPC HTTP stream is considered failed unless traffic in the stream has been processed (in seconds); defaults to 0 (unlimited)
-      --http-max-grpc-timeout uint                    Time after which a forwarded gRPC request is considered failed unless completed (in seconds). A "grpc-timeout" header may override this with a shorter value; defaults to 0 (unlimited)
-      --http-request-timeout uint                     Time after which a forwarded HTTP request is considered failed unless completed (in seconds); Use 0 for unlimited (default 3600)
-      --http-retry-count uint                         Number of retries performed after a forwarded request attempt fails (default 3)
-      --http-retry-timeout uint                       Time after which a forwarded but uncompleted request is retried (connection failures are retried immediately); defaults to 0 (never)
-      --hubble-disable-tls                            Allow Hubble server to run on the given listen address without TLS.
-      --hubble-event-queue-size int                   Buffer size of the channel to receive monitor events.
-      --hubble-flow-buffer-size int                   Maximum number of flows in Hubble's buffer. The actual buffer size gets rounded up to the next power of 2, e.g. 4095 => 4096 (default 4095)
-      --hubble-listen-address string                  An additional address for Hubble server to listen to, e.g. ":4244"
-      --hubble-metrics strings                        List of Hubble metrics to enable.
-      --hubble-metrics-server string                  Address to serve Hubble metrics on.
-      --hubble-socket-path string                     Set hubble's socket path to listen for connections (default "/var/run/cilium/hubble.sock")
-      --hubble-tls-cert-file string                   Path to the public key file for the Hubble server. The file must contain PEM encoded data.
-      --hubble-tls-client-ca-files strings            Paths to one or more public key files of client CA certificates to use for TLS with mutual authentication (mTLS). The files must contain PEM encoded data. When provided, this option effectively enables mTLS.
-      --hubble-tls-key-file string                    Path to the private key file for the Hubble server. The file must contain PEM encoded data.
-      --identity-allocation-mode string               Method to use for identity allocation (default "kvstore")
-      --identity-change-grace-period duration         Time to wait before using new identity on endpoint identity change (default 5s)
-      --install-iptables-rules                        Install base iptables rules for cilium to mainly interact with kube-proxy (and masquerading) (default true)
-      --ip-allocation-timeout duration                Time after which an incomplete CIDR allocation is considered failed (default 2m0s)
-      --ip-masq-agent-config-path string              ip-masq-agent configuration file path (default "/etc/config/ip-masq-agent")
-      --ipam string                                   Backend to use for IPAM (default "cluster-pool")
-      --ipsec-key-file string                         Path to IPSec key file
-      --iptables-lock-timeout duration                Time to pass to each iptables invocation to wait for xtables lock acquisition (default 5s)
-      --iptables-random-fully                         Set iptables flag random-fully on masquerading rules
-      --ipv4-node string                              IPv4 address of node (default "auto")
-      --ipv4-pod-subnets strings                      List of IPv4 pod subnets to preconfigure for encryption
-      --ipv4-range string                             Per-node IPv4 endpoint prefix, e.g. 10.16.0.0/16 (default "auto")
-      --ipv4-service-loopback-address string          IPv4 address for service loopback SNAT (default "169.254.42.1")
-      --ipv4-service-range string                     Kubernetes IPv4 services CIDR if not inside cluster prefix (default "auto")
-      --ipv6-cluster-alloc-cidr string                IPv6 /64 CIDR used to allocate per node endpoint /96 CIDR (default "f00d::/64")
-      --ipv6-mcast-device string                      Device that joins a Solicited-Node multicast group for IPv6
-      --ipv6-node string                              IPv6 address of node (default "auto")
-      --ipv6-pod-subnets strings                      List of IPv6 pod subnets to preconfigure for encryption
-      --ipv6-range string                             Per-node IPv6 endpoint prefix, e.g. fd02:1:1::/96 (default "auto")
-      --ipv6-service-range string                     Kubernetes IPv6 services CIDR if not inside cluster prefix (default "auto")
-      --ipvlan-master-device string                   Device facing external network acting as ipvlan master (default "undefined")
-      --join-cluster                                  Join a Cilium cluster via kvstore registration
-      --k8s-api-server string                         Kubernetes API server URL
-      --k8s-heartbeat-timeout duration                Configures the timeout for api-server heartbeat, set to 0 to disable (default 30s)
-      --k8s-kubeconfig-path string                    Absolute path of the kubernetes kubeconfig file
-      --k8s-namespace string                          Name of the Kubernetes namespace in which Cilium is deployed in
-      --k8s-require-ipv4-pod-cidr                     Require IPv4 PodCIDR to be specified in node resource
-      --k8s-require-ipv6-pod-cidr                     Require IPv6 PodCIDR to be specified in node resource
-      --k8s-service-proxy-name string                 Value of K8s service-proxy-name label for which Cilium handles the services (empty = all services without service.kubernetes.io/service-proxy-name label)
-      --k8s-watcher-endpoint-selector string          K8s endpoint watcher will watch for these k8s endpoints (default "metadata.name!=kube-scheduler,metadata.name!=kube-controller-manager,metadata.name!=etcd-operator,metadata.name!=gcp-controller-manager")
-      --k8s-watcher-queue-size uint                   Queue size used to serialize each k8s event type (default 1024)
-      --keep-config                                   When restoring state, keeps containers' configuration in place
-      --kube-proxy-replacement string                 auto-enable available features for kube-proxy replacement ("probe"), or enable only selected features (will panic if any selected feature cannot be enabled) ("partial") or enable all features (will panic if any feature cannot be enabled) ("strict"), or completely disable it (ignores any selected feature) ("disabled") (default "partial")
-      --kvstore string                                Key-value store type
-      --kvstore-connectivity-timeout duration         Time after which an incomplete kvstore operation  is considered failed (default 2m0s)
-      --kvstore-opt map                               Key-value store options (default map[])
-      --kvstore-periodic-sync duration                Periodic KVstore synchronization interval (default 5m0s)
-      --label-prefix-file string                      Valid label prefixes file path
-      --labels strings                                List of label prefixes used to determine identity of an endpoint
-      --lib-dir string                                Directory path to store runtime build environment (default "/var/lib/cilium")
-      --log-driver strings                            Logging endpoints to use for example syslog
-      --log-opt map                                   Log driver options for cilium (default map[])
-      --log-system-load                               Enable periodic logging of system load
-      --masquerade                                    Masquerade packets from endpoints leaving the host (default true)
-      --metrics strings                               Metrics that should be enabled or disabled from the default metric list. (+metric_foo to enable metric_foo , -metric_bar to disable metric_bar)
-      --monitor-aggregation string                    Level of monitor aggregation for traces from the datapath (default "None")
-      --monitor-aggregation-flags strings             TCP flags that trigger monitor reports when monitor aggregation is enabled (default [syn,fin,rst])
-      --monitor-aggregation-interval duration         Monitor report interval when monitor aggregation is enabled (default 5s)
-      --monitor-queue-size int                        Size of the event queue when reading monitor events
-      --mtu int                                       Overwrite auto-detected MTU of underlying network
-      --nat46-range string                            IPv6 prefix to map IPv4 addresses to (default "0:0:0:0:0:FFFF::/96")
-      --native-routing-cidr string                    Allows to explicitly specify the CIDR for native routing. This value corresponds to the configured cluster-cidr.
-      --node-port-acceleration string                 BPF NodePort acceleration via XDP ("native", "disabled") (default "disabled")
-      --node-port-algorithm string                    BPF load balancing algorithm ("random", "maglev") (default "random")
-      --node-port-bind-protection                     Reject application bind(2) requests to service ports in the NodePort range (default true)
-      --node-port-mode string                         BPF NodePort mode ("snat", "dsr", "hybrid") (default "snat")
-      --node-port-range strings                       Set the min/max NodePort port range (default [30000,32767])
-      --policy-audit-mode                             Enable policy audit (non-drop) mode
-      --policy-queue-size int                         size of queues for policy-related events (default 100)
-      --pprof                                         Enable serving the pprof debugging API
-      --preallocate-bpf-maps                          Enable BPF map pre-allocation (default true)
-      --prefilter-device string                       Device facing external network for XDP prefiltering (default "undefined")
-      --prefilter-mode string                         Prefilter mode via XDP ("native", "generic") (default "native")
-      --prepend-iptables-chains                       Prepend custom iptables chains instead of appending (default true)
-      --prometheus-serve-addr string                  IP:Port on which to serve prometheus metrics (pass ":Port" to bind on all interfaces, "" is off)
-      --proxy-connect-timeout uint                    Time after which a TCP connect attempt is considered failed unless completed (in seconds) (default 1)
-      --proxy-prometheus-port int                     Port to serve Envoy metrics on. Default 0 (disabled).
-      --read-cni-conf string                          Read to the CNI configuration at specified path to extract per node configuration
-      --restore                                       Restores state, if possible, from previous daemon (default true)
-      --sidecar-istio-proxy-image string              Regular expression matching compatible Istio sidecar istio-proxy container image names (default "cilium/istio_proxy")
-      --single-cluster-route                          Use a single cluster route instead of per node routes
-      --skip-crd-creation                             Skip Kubernetes Custom Resource Definitions creations
-      --socket-path string                            Sets daemon's socket path to listen for connections (default "/var/run/cilium/cilium.sock")
-      --sockops-enable                                Enable sockops when kernel supported
-      --state-dir string                              Directory path to store runtime state (default "/var/run/cilium")
-      --tofqdns-dns-reject-response-code string       DNS response code for rejecting DNS requests, available options are '[nameError refused]' (default "refused")
-      --tofqdns-enable-dns-compression                Allow the DNS proxy to compress responses to endpoints that are larger than 512 Bytes or the EDNS0 option, if present (default true)
-      --tofqdns-endpoint-max-ip-per-hostname int      Maximum number of IPs to maintain per FQDN name for each endpoint (default 50)
-      --tofqdns-max-deferred-connection-deletes int   Maximum number of IPs to retain for expired DNS lookups with still-active connections (default 10000)
-      --tofqdns-min-ttl int                           The minimum time, in seconds, to use DNS data for toFQDNs policies. (default 3600 )
-      --tofqdns-pre-cache string                      DNS cache data at this path is preloaded on agent startup
-      --tofqdns-proxy-port int                        Global port on which the in-agent DNS proxy should listen. Default 0 is a OS-assigned port.
-      --tofqdns-proxy-response-max-delay duration     The maximum time the DNS proxy holds an allowed DNS response before sending it along. Responses are sent as soon as the datapath is updated with the new IP information. (default 100ms)
-      --trace-payloadlen int                          Length of payload to capture when tracing (default 128)
-  -t, --tunnel string                                 Tunnel mode {vxlan, geneve, disabled} (default "vxlan" for the "veth" datapath mode)
-      --version                                       Print version information
-      --write-cni-conf-when-ready string              Write the CNI configuration as specified via --read-cni-conf to path when agent is ready
+      --agent-health-port int                                TCP port for agent health status API (default 9876)
+      --agent-labels strings                                 Additional labels to identify this agent
+      --allow-icmp-frag-needed                               Allow ICMP Fragmentation Needed type packets for purposes like TCP Path MTU. (default true)
+      --allow-localhost string                               Policy when to allow local stack to reach local endpoints { auto | always | policy } (default "auto")
+      --annotate-k8s-node                                    Annotate Kubernetes node (default true)
+      --api-rate-limit map                                   API rate limiting configuration (example: --rate-limit endpoint-create=rate-limit:10/m,rate-burst:2) (default map[])
+      --auto-create-cilium-node-resource                     Automatically create CiliumNode resource for own node on startup (default true)
+      --auto-direct-node-routes                              Enable automatic L2 routing between nodes
+      --bpf-compile-debug                                    Enable debugging of the BPF compilation process
+      --bpf-ct-global-any-max int                            Maximum number of entries in non-TCP CT table (default 262144)
+      --bpf-ct-global-tcp-max int                            Maximum number of entries in TCP CT table (default 524288)
+      --bpf-ct-timeout-regular-any duration                  Timeout for entries in non-TCP CT table (default 1m0s)
+      --bpf-ct-timeout-regular-tcp duration                  Timeout for established entries in TCP CT table (default 6h0m0s)
+      --bpf-ct-timeout-regular-tcp-fin duration              Teardown timeout for entries in TCP CT table (default 10s)
+      --bpf-ct-timeout-regular-tcp-syn duration              Establishment timeout for entries in TCP CT table (default 1m0s)
+      --bpf-ct-timeout-service-any duration                  Timeout for service entries in non-TCP CT table (default 1m0s)
+      --bpf-ct-timeout-service-tcp duration                  Timeout for established service entries in TCP CT table (default 6h0m0s)
+      --bpf-fragments-map-max int                            Maximum number of entries in fragments tracking map (default 8192)
+      --bpf-lb-maglev-hash-seed string                       Maglev cluster-wide hash seed (base64 encoded) (default "JLfvgnHc2kaSUFaI")
+      --bpf-lb-maglev-table-size uint                        Maglev per service backend table size (parameter M) (default 16381)
+      --bpf-lb-map-max int                                   Maximum number of entries in Cilium BPF lbmap (default 65536)
+      --bpf-map-dynamic-size-ratio float                     Ratio (0.0-1.0) of total system memory to use for dynamic sizing of CT, NAT and policy BPF maps. Set to 0.0 to disable dynamic BPF map sizing (default: 0.0)
+      --bpf-nat-global-max int                               Maximum number of entries for the global BPF NAT table (default 524288)
+      --bpf-neigh-global-max int                             Maximum number of entries for the global BPF neighbor table (default 524288)
+      --bpf-policy-map-max int                               Maximum number of entries in endpoint policy map (per endpoint) (default 16384)
+      --bpf-root string                                      Path to BPF filesystem
+      --bpf-sock-rev-map-max int                             Maximum number of entries for the SockRevNAT BPF map (default 262144)
+      --certificates-directory string                        Root directory to find certificates specified in L7 TLS policy enforcement (default "/var/run/cilium/certs")
+      --cgroup-root string                                   Path to Cgroup2 filesystem
+      --cluster-id int                                       Unique identifier of the cluster
+      --cluster-name string                                  Name of the cluster (default "default")
+      --clustermesh-config string                            Path to the ClusterMesh configuration directory
+      --config string                                        Configuration file (default "$HOME/ciliumd.yaml")
+      --config-dir string                                    Configuration directory that contains a file for each option
+      --conntrack-gc-interval duration                       Overwrite the connection-tracking garbage collection interval
+      --crd-wait-timeout duration                            Cilium will exit if CRDs are not available within this duration upon startup (default 5m0s)
+      --datapath-mode string                                 Datapath mode name (default "veth")
+  -D, --debug                                                Enable debugging mode
+      --debug-verbose strings                                List of enabled verbose debug groups
+      --devices strings                                      List of devices facing cluster/external network (used for BPF NodePort, BPF masquerading and host firewall)
+      --direct-routing-device string                         Device name used to connect nodes in direct routing mode (required only by BPF NodePort; if empty, automatically set to a device with k8s InternalIP/ExternalIP or with a default route)
+      --disable-cnp-status-updates                           Do not send CNP NodeStatus updates to the Kubernetes api-server (recommended to run with "cnp-node-status-gc-interval=0" in cilium-operator)
+      --disable-conntrack                                    Disable connection tracking
+      --disable-endpoint-crd                                 Disable use of CiliumEndpoint CRD
+      --disable-iptables-feeder-rules strings                Chains to ignore when installing feeder rules.
+      --egress-masquerade-interfaces string                  Limit egress masquerading to interface selector
+      --enable-auto-protect-node-port-range                  Append NodePort range to net.ipv4.ip_local_reserved_ports if it overlaps with ephemeral port range (net.ipv4.ip_local_port_range) (default true)
+      --enable-bandwidth-manager                             Enable BPF bandwidth manager
+      --enable-bpf-clock-probe                               Enable BPF clock source probing for more efficient tick retrieval
+      --enable-bpf-masquerade                                Masquerade packets from endpoints leaving the host with BPF instead of iptables
+      --enable-bpf-tproxy                                    Enable BPF-based proxy redirection, if support available
+      --enable-endpoint-health-checking                      Enable connectivity health checking between virtual endpoints (default true)
+      --enable-endpoint-routes                               Use per endpoint routes instead of routing via cilium_host
+      --enable-external-ips                                  Enable k8s service externalIPs feature (requires enabling enable-node-port) (default true)
+      --enable-health-check-nodeport                         Enables a healthcheck nodePort server for NodePort services with 'healthCheckNodePort' being set (default true)
+      --enable-health-checking                               Enable connectivity health checking (default true)
+      --enable-host-firewall                                 Enable host network policies (beta)
+      --enable-host-legacy-routing                           Enable the legacy host forwarding model which does not bypass upper stack in host namespace
+      --enable-host-port                                     Enable k8s hostPort mapping feature (requires enabling enable-node-port) (default true)
+      --enable-host-reachable-services                       Enable reachability of services for host applications (beta)
+      --enable-hubble                                        Enable hubble server
+      --enable-identity-mark                                 Enable setting identity mark for local traffic (default true)
+      --enable-ip-masq-agent                                 Enable BPF ip-masq-agent
+      --enable-ipsec                                         Enable IPSec support
+      --enable-ipv4                                          Enable IPv4 support (default true)
+      --enable-ipv4-fragment-tracking                        Enable IPv4 fragments tracking for L4-based lookups (default true)
+      --enable-ipv6                                          Enable IPv6 support (default true)
+      --enable-ipv6-ndp                                      Enable IPv6 NDP support
+      --enable-k8s-api-discovery                             Enable discovery of Kubernetes API groups and resources with the discovery API
+      --enable-k8s-endpoint-slice                            Enables k8s EndpointSlice feature in Cilium if the k8s cluster supports it (default true)
+      --enable-k8s-event-handover                            Enable k8s event handover to kvstore for improved scalability
+      --enable-l7-proxy                                      Enable L7 proxy for L7 policy enforcement (default true)
+      --enable-local-node-route                              Enable installation of the route which points the allocation prefix of the local node (default true)
+      --enable-monitor                                       Enable the monitor unix domain socket server (default true)
+      --enable-node-port                                     Enable NodePort type services by Cilium (beta)
+      --enable-policy string                                 Enable policy enforcement (default "default")
+      --enable-remote-node-identity                          Enable use of remote node identity
+      --enable-session-affinity                              Enable support for service session affinity
+      --enable-svc-source-range-check                        Enable check of service source ranges (currently, only for LoadBalancer) (default true)
+      --enable-tracing                                       Enable tracing while determining policy (debugging)
+      --enable-well-known-identities                         Enable well-known identities for known Kubernetes components (default true)
+      --enable-xt-socket-fallback                            Enable fallback for missing xt_socket module (default true)
+      --encrypt-interface string                             Transparent encryption interface
+      --encrypt-node                                         Enables encrypting traffic from non-Cilium pods and host networking
+      --endpoint-interface-name-prefix string                Prefix of interface name shared by all endpoints (default "lxc+")
+      --endpoint-queue-size int                              size of EventQueue per-endpoint (default 25)
+      --endpoint-status strings                              Enable additional CiliumEndpoint status features (controllers,health,log,policy,state)
+      --envoy-log string                                     Path to a separate Envoy log file, if any
+      --exclude-local-address strings                        Exclude CIDR from being recognized as local address
+      --fixed-identity-mapping map                           Key-value for the fixed identity mapping which allows to use reserved label for fixed identities (default map[])
+      --flannel-master-device string                         Installs a BPF program to allow for policy enforcement in the given network interface. Allows to run Cilium on top of other CNI plugins that provide networking, e.g. flannel, where for flannel, this value should be set with 'cni0'. [EXPERIMENTAL]
+      --flannel-uninstall-on-exit                            When used along the flannel-master-device flag, it cleans up all BPF programs installed when Cilium agent is terminated.
+      --force-local-policy-eval-at-source                    Force policy evaluation of all local communication at the source endpoint (default true)
+  -h, --help                                                 help for cilium-agent
+      --host-reachable-services-protos strings               Only enable reachability of services for host applications for specific protocols (default [tcp,udp])
+      --http-idle-timeout uint                               Time after which a non-gRPC HTTP stream is considered failed unless traffic in the stream has been processed (in seconds); defaults to 0 (unlimited)
+      --http-max-grpc-timeout uint                           Time after which a forwarded gRPC request is considered failed unless completed (in seconds). A "grpc-timeout" header may override this with a shorter value; defaults to 0 (unlimited)
+      --http-request-timeout uint                            Time after which a forwarded HTTP request is considered failed unless completed (in seconds); Use 0 for unlimited (default 3600)
+      --http-retry-count uint                                Number of retries performed after a forwarded request attempt fails (default 3)
+      --http-retry-timeout uint                              Time after which a forwarded but uncompleted request is retried (connection failures are retried immediately); defaults to 0 (never)
+      --hubble-disable-tls                                   Allow Hubble server to run on the given listen address without TLS.
+      --hubble-event-queue-size int                          Buffer size of the channel to receive monitor events.
+      --hubble-flow-buffer-size int                          Maximum number of flows in Hubble's buffer. The actual buffer size gets rounded up to the next power of 2, e.g. 4095 => 4096 (default 4095)
+      --hubble-listen-address string                         An additional address for Hubble server to listen to, e.g. ":4244"
+      --hubble-metrics strings                               List of Hubble metrics to enable.
+      --hubble-metrics-server string                         Address to serve Hubble metrics on.
+      --hubble-socket-path string                            Set hubble's socket path to listen for connections (default "/var/run/cilium/hubble.sock")
+      --hubble-tls-cert-file string                          Path to the public key file for the Hubble server. The file must contain PEM encoded data.
+      --hubble-tls-client-ca-files strings                   Paths to one or more public key files of client CA certificates to use for TLS with mutual authentication (mTLS). The files must contain PEM encoded data. When provided, this option effectively enables mTLS.
+      --hubble-tls-key-file string                           Path to the private key file for the Hubble server. The file must contain PEM encoded data.
+      --identity-allocation-mode string                      Method to use for identity allocation (default "kvstore")
+      --identity-change-grace-period duration                Time to wait before using new identity on endpoint identity change (default 5s)
+      --install-iptables-rules                               Install base iptables rules for cilium to mainly interact with kube-proxy (and masquerading) (default true)
+      --ip-allocation-timeout duration                       Time after which an incomplete CIDR allocation is considered failed (default 2m0s)
+      --ip-masq-agent-config-path string                     ip-masq-agent configuration file path (default "/etc/config/ip-masq-agent")
+      --ipam string                                          Backend to use for IPAM (default "cluster-pool")
+      --ipsec-key-file string                                Path to IPSec key file
+      --iptables-lock-timeout duration                       Time to pass to each iptables invocation to wait for xtables lock acquisition (default 5s)
+      --iptables-random-fully                                Set iptables flag random-fully on masquerading rules
+      --ipv4-node string                                     IPv4 address of node (default "auto")
+      --ipv4-pod-subnets strings                             List of IPv4 pod subnets to preconfigure for encryption
+      --ipv4-range string                                    Per-node IPv4 endpoint prefix, e.g. 10.16.0.0/16 (default "auto")
+      --ipv4-service-loopback-address string                 IPv4 address for service loopback SNAT (default "169.254.42.1")
+      --ipv4-service-range string                            Kubernetes IPv4 services CIDR if not inside cluster prefix (default "auto")
+      --ipv6-cluster-alloc-cidr string                       IPv6 /64 CIDR used to allocate per node endpoint /96 CIDR (default "f00d::/64")
+      --ipv6-mcast-device string                             Device that joins a Solicited-Node multicast group for IPv6
+      --ipv6-node string                                     IPv6 address of node (default "auto")
+      --ipv6-pod-subnets strings                             List of IPv6 pod subnets to preconfigure for encryption
+      --ipv6-range string                                    Per-node IPv6 endpoint prefix, e.g. fd02:1:1::/96 (default "auto")
+      --ipv6-service-range string                            Kubernetes IPv6 services CIDR if not inside cluster prefix (default "auto")
+      --ipvlan-master-device string                          Device facing external network acting as ipvlan master (default "undefined")
+      --join-cluster                                         Join a Cilium cluster via kvstore registration
+      --k8s-api-server string                                Kubernetes API server URL
+      --k8s-heartbeat-timeout duration                       Configures the timeout for api-server heartbeat, set to 0 to disable (default 30s)
+      --k8s-kubeconfig-path string                           Absolute path of the kubernetes kubeconfig file
+      --k8s-namespace string                                 Name of the Kubernetes namespace in which Cilium is deployed in
+      --k8s-require-ipv4-pod-cidr                            Require IPv4 PodCIDR to be specified in node resource
+      --k8s-require-ipv6-pod-cidr                            Require IPv6 PodCIDR to be specified in node resource
+      --k8s-service-proxy-name string                        Value of K8s service-proxy-name label for which Cilium handles the services (empty = all services without service.kubernetes.io/service-proxy-name label)
+      --k8s-watcher-endpoint-selector string                 K8s endpoint watcher will watch for these k8s endpoints (default "metadata.name!=kube-scheduler,metadata.name!=kube-controller-manager,metadata.name!=etcd-operator,metadata.name!=gcp-controller-manager")
+      --keep-config                                          When restoring state, keeps containers' configuration in place
+      --kube-proxy-replacement string                        auto-enable available features for kube-proxy replacement ("probe"), or enable only selected features (will panic if any selected feature cannot be enabled) ("partial") or enable all features (will panic if any feature cannot be enabled) ("strict"), or completely disable it (ignores any selected feature) ("disabled") (default "partial")
+      --kube-proxy-replacement-healthz-bind-address string   The IP address with port for kube-proxy replacement health check server to serve on (set to '0.0.0.0:10256' for all IPv4 interfaces and '[::]:10256' for all IPv6 interfaces). Set empty to disable.
+      --kvstore string                                       Key-value store type
+      --kvstore-connectivity-timeout duration                Time after which an incomplete kvstore operation  is considered failed (default 2m0s)
+      --kvstore-opt map                                      Key-value store options (default map[])
+      --kvstore-periodic-sync duration                       Periodic KVstore synchronization interval (default 5m0s)
+      --label-prefix-file string                             Valid label prefixes file path
+      --labels strings                                       List of label prefixes used to determine identity of an endpoint
+      --lib-dir string                                       Directory path to store runtime build environment (default "/var/lib/cilium")
+      --log-driver strings                                   Logging endpoints to use for example syslog
+      --log-opt map                                          Log driver options for cilium (default map[])
+      --log-system-load                                      Enable periodic logging of system load
+      --masquerade                                           Masquerade packets from endpoints leaving the host (default true)
+      --metrics strings                                      Metrics that should be enabled or disabled from the default metric list. (+metric_foo to enable metric_foo , -metric_bar to disable metric_bar)
+      --monitor-aggregation string                           Level of monitor aggregation for traces from the datapath (default "None")
+      --monitor-aggregation-flags strings                    TCP flags that trigger monitor reports when monitor aggregation is enabled (default [syn,fin,rst])
+      --monitor-aggregation-interval duration                Monitor report interval when monitor aggregation is enabled (default 5s)
+      --monitor-queue-size int                               Size of the event queue when reading monitor events
+      --mtu int                                              Overwrite auto-detected MTU of underlying network
+      --nat46-range string                                   IPv6 prefix to map IPv4 addresses to (default "0:0:0:0:0:FFFF::/96")
+      --native-routing-cidr string                           Allows to explicitly specify the CIDR for native routing. This value corresponds to the configured cluster-cidr.
+      --node-port-acceleration string                        BPF NodePort acceleration via XDP ("native", "disabled") (default "disabled")
+      --node-port-algorithm string                           BPF load balancing algorithm ("random", "maglev") (default "random")
+      --node-port-bind-protection                            Reject application bind(2) requests to service ports in the NodePort range (default true)
+      --node-port-mode string                                BPF NodePort mode ("snat", "dsr", "hybrid") (default "snat")
+      --node-port-range strings                              Set the min/max NodePort port range (default [30000,32767])
+      --policy-audit-mode                                    Enable policy audit (non-drop) mode
+      --policy-queue-size int                                size of queues for policy-related events (default 100)
+      --pprof                                                Enable serving the pprof debugging API
+      --preallocate-bpf-maps                                 Enable BPF map pre-allocation (default true)
+      --prefilter-device string                              Device facing external network for XDP prefiltering (default "undefined")
+      --prefilter-mode string                                Prefilter mode via XDP ("native", "generic") (default "native")
+      --prepend-iptables-chains                              Prepend custom iptables chains instead of appending (default true)
+      --prometheus-serve-addr string                         IP:Port on which to serve prometheus metrics (pass ":Port" to bind on all interfaces, "" is off)
+      --proxy-connect-timeout uint                           Time after which a TCP connect attempt is considered failed unless completed (in seconds) (default 1)
+      --proxy-prometheus-port int                            Port to serve Envoy metrics on. Default 0 (disabled).
+      --read-cni-conf string                                 Read to the CNI configuration at specified path to extract per node configuration
+      --restore                                              Restores state, if possible, from previous daemon (default true)
+      --sidecar-istio-proxy-image string                     Regular expression matching compatible Istio sidecar istio-proxy container image names (default "cilium/istio_proxy")
+      --single-cluster-route                                 Use a single cluster route instead of per node routes
+      --skip-crd-creation                                    Skip Kubernetes Custom Resource Definitions creations
+      --socket-path string                                   Sets daemon's socket path to listen for connections (default "/var/run/cilium/cilium.sock")
+      --sockops-enable                                       Enable sockops when kernel supported
+      --state-dir string                                     Directory path to store runtime state (default "/var/run/cilium")
+      --tofqdns-dns-reject-response-code string              DNS response code for rejecting DNS requests, available options are '[nameError refused]' (default "refused")
+      --tofqdns-enable-dns-compression                       Allow the DNS proxy to compress responses to endpoints that are larger than 512 Bytes or the EDNS0 option, if present (default true)
+      --tofqdns-endpoint-max-ip-per-hostname int             Maximum number of IPs to maintain per FQDN name for each endpoint (default 50)
+      --tofqdns-max-deferred-connection-deletes int          Maximum number of IPs to retain for expired DNS lookups with still-active connections (default 10000)
+      --tofqdns-min-ttl int                                  The minimum time, in seconds, to use DNS data for toFQDNs policies. (default 3600 )
+      --tofqdns-pre-cache string                             DNS cache data at this path is preloaded on agent startup
+      --tofqdns-proxy-port int                               Global port on which the in-agent DNS proxy should listen. Default 0 is a OS-assigned port.
+      --tofqdns-proxy-response-max-delay duration            The maximum time the DNS proxy holds an allowed DNS response before sending it along. Responses are sent as soon as the datapath is updated with the new IP information. (default 100ms)
+      --trace-payloadlen int                                 Length of payload to capture when tracing (default 128)
+  -t, --tunnel string                                        Tunnel mode {vxlan, geneve, disabled} (default "vxlan" for the "veth" datapath mode)
+      --version                                              Print version information
+      --write-cni-conf-when-ready string                     Write the CNI configuration as specified via --read-cni-conf to path when agent is ready
 ```
 
diff --git a/Documentation/cmdref/cilium-operator-azure.md b/Documentation/cmdref/cilium-operator-azure.md
index 1f57a78ee2..45f81ca4d2 100644
--- a/Documentation/cmdref/cilium-operator-azure.md
+++ b/Documentation/cmdref/cilium-operator-azure.md
@@ -18,6 +18,7 @@ cilium-operator-azure [flags]
       --azure-cloud-name string                   Name of the Azure cloud being used (default "AzurePublicCloud")
       --azure-resource-group string               Resource group to use for Azure IPAM
       --azure-subscription-id string              Subscription ID to access Azure API
+      --azure-use-primary-address                 Use Azure IP address from interface's primary IPConfigurations (default true)
       --azure-user-assigned-identity-id string    ID of the user assigned identity used to auth with the Azure API
       --cilium-endpoint-gc-interval duration      GC interval for cilium endpoints (default 5m0s)
       --cluster-id int                            Unique identifier of the cluster
diff --git a/Documentation/cmdref/cilium-operator.md b/Documentation/cmdref/cilium-operator.md
index b1f9ded74b..fdf73f1c6b 100644
--- a/Documentation/cmdref/cilium-operator.md
+++ b/Documentation/cmdref/cilium-operator.md
@@ -20,6 +20,7 @@ cilium-operator [flags]
       --azure-cloud-name string                   Name of the Azure cloud being used (default "AzurePublicCloud")
       --azure-resource-group string               Resource group to use for Azure IPAM
       --azure-subscription-id string              Subscription ID to access Azure API
+      --azure-use-primary-address                 Use Azure IP address from interface's primary IPConfigurations (default true)
       --azure-user-assigned-identity-id string    ID of the user assigned identity used to auth with the Azure API
       --cilium-endpoint-gc-interval duration      GC interval for cilium endpoints (default 5m0s)
       --cluster-id int                            Unique identifier of the cluster
diff --git a/Documentation/cmdref/cilium_bpf_lb_maglev.md b/Documentation/cmdref/cilium_bpf_lb_maglev.md
index 350e82c843..9dad2308ec 100644
--- a/Documentation/cmdref/cilium_bpf_lb_maglev.md
+++ b/Documentation/cmdref/cilium_bpf_lb_maglev.md
@@ -25,5 +25,6 @@ Maglev lookup table
 ### SEE ALSO
 
 * [cilium bpf lb](../cilium_bpf_lb)	 - Load-balancing configuration
+* [cilium bpf lb maglev get](../cilium_bpf_lb_maglev_get)	 - Get Maglev lookup table for given service by ID
 * [cilium bpf lb maglev list](../cilium_bpf_lb_maglev_list)	 - List Maglev lookup tables
 
diff --git a/Documentation/cmdref/cilium_bpf_lb_maglev_get.md b/Documentation/cmdref/cilium_bpf_lb_maglev_get.md
new file mode 100644
index 0000000000..f9cb481149
--- /dev/null
+++ b/Documentation/cmdref/cilium_bpf_lb_maglev_get.md
@@ -0,0 +1,33 @@
+<!-- This file was autogenerated via cilium cmdref, do not edit manually-->
+
+## cilium bpf lb maglev get
+
+Get Maglev lookup table for given service by ID
+
+### Synopsis
+
+Get Maglev lookup table for given service by ID
+
+```
+cilium bpf lb maglev get <service id> [flags]
+```
+
+### Options
+
+```
+  -h, --help            help for get
+  -o, --output string   json| jsonpath='{}'
+```
+
+### Options inherited from parent commands
+
+```
+      --config string   config file (default is $HOME/.cilium.yaml)
+  -D, --debug           Enable debug messages
+  -H, --host string     URI to server-side API
+```
+
+### SEE ALSO
+
+* [cilium bpf lb maglev](../cilium_bpf_lb_maglev)	 - Maglev lookup table
+
diff --git a/Documentation/cmdref/cilium_service_update.md b/Documentation/cmdref/cilium_service_update.md
index 849df74b6f..e76c3832c6 100644
--- a/Documentation/cmdref/cilium_service_update.md
+++ b/Documentation/cmdref/cilium_service_update.md
@@ -26,7 +26,6 @@ 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/concepts/kubernetes/policy.rst b/Documentation/concepts/kubernetes/policy.rst
index 7299635e16..f1d2b09ecd 100644
--- a/Documentation/concepts/kubernetes/policy.rst
+++ b/Documentation/concepts/kubernetes/policy.rst
@@ -103,7 +103,8 @@ Status
 Examples
 ========
 
-See :ref:`policy_examples` for a detailed list of example policies.
+See :ref:`policy_examples`, :ref:`l4_policy` and :ref:`l7_policy` for
+detailed lists of example policies.
 
 
 .. _CiliumClusterwideNetworkPolicy:
diff --git a/Documentation/gettingstarted/http.rst b/Documentation/gettingstarted/http.rst
index 242acd88b1..6c18c76ed5 100644
--- a/Documentation/gettingstarted/http.rst
+++ b/Documentation/gettingstarted/http.rst
@@ -242,7 +242,12 @@ As this rule builds on the identity-aware rule, traffic from pods without the la
 As you can see, with Cilium L7 security policies, we are able to permit
 *tiefighter* to access only the required API resources on *deathstar*, thereby
 implementing a "least privilege" security approach for communication between
-microservices.
+microservices. Note that ``path`` matches the exact url, if for example you want
+to allow anything under /v1/, you need to use a regular expression:
+
+::
+
+    path: "/v1/.*"
 
 You can observe the L7 policy via ``kubectl``:
 
@@ -371,6 +376,15 @@ and ``cilium`` CLI:
     ]
     Revision: 11
 
+It is also possible to monitor the HTTP requests live by using ``cilium monitor``:
+
+::
+
+    $ kubectl exec -it -n kube-system cilium-kzgdx -- cilium monitor -v --type l7
+    <- Response http to 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) from 2756 ([k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default]), identity 8876->43854, verdict Forwarded POST http://deathstar.default.svc.cluster.local/v1/request-landing => 200
+    <- Request http from 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) to 2756 ([k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default]), identity 8876->43854, verdict Denied PUT http://deathstar.default.svc.cluster.local/v1/request-landing => 403
+
+The above output demonstrates a successful response to a POST request followed by a PUT request that is denied by the L7 policy.
 
 We hope you enjoyed the tutorial.  Feel free to play more with the setup, read
 the rest of the documentation, and reach out to us on the `Cilium
diff --git a/Documentation/gettingstarted/kubeproxy-free.rst b/Documentation/gettingstarted/kubeproxy-free.rst
index 15a55c00e5..17bcea5f83 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/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
+    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       
 
 At the same time we can inspect through ``iptables`` in the host namespace
 that no ``iptables`` rule for the service is present:
@@ -1098,6 +1098,16 @@ a fixed cookie value as a trade-off. This makes all applications on the host to
 select the same service endpoint for a given service with session affinity configured.
 To disable the feature, set ``config.sessionAffinity=false``.
 
+kube-proxy replacement health check server
+******************************************
+To enable health check server for the kube-proxy replacement, the
+``kubeProxyReplacementHealthzBindAddr`` option has to be set (disabled by
+default). The option accepts the IP address with port for the health check server
+to serve on.
+E.g. to enable for IPv4 interfaces set ``kubeProxyReplacementHealthzBindAddr='0.0.0.0:10256'``,
+for IPv6 - ``kubeProxyReplacementHealthzBindAddr='[::]:10256'``. The health check server is
+accessible via the HTTP ``/healthz`` endpoint.
+
 LoadBalancer Source Ranges Checks
 *********************************
 
diff --git a/Documentation/operations/upgrade.rst b/Documentation/operations/upgrade.rst
index c519884955..660f6edfe3 100644
--- a/Documentation/operations/upgrade.rst
+++ b/Documentation/operations/upgrade.rst
@@ -718,6 +718,12 @@ Deprecated Metrics/Labels
   * Label ``scope`` in ``cilium_endpoint_regeneration_time_stats_seconds`` had its ``buildDuration`` value renamed to ``total``.
   * Label ``scope`` in ``cilium_policy_regeneration_time_stats_seconds`` had its ``buildDuration`` value renamed to ``total``.
 
+Deprecated options
+~~~~~~~~~~~~~~~~~~
+
+* ``k8s-watcher-queue-size``: This option does not have any effect since 1.8
+  and is planned to be removed in 1.10.
+
 Removed options
 ~~~~~~~~~~~~~~~
 
diff --git a/api/v1/models/backend_address.go b/api/v1/models/backend_address.go
index d305c02d8b..4675a9767f 100644
--- a/api/v1/models/backend_address.go
+++ b/api/v1/models/backend_address.go
@@ -29,9 +29,6 @@ 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/openapi.yaml b/api/v1/openapi.yaml
index c045df68b8..f9c552725d 100644
--- a/api/v1/openapi.yaml
+++ b/api/v1/openapi.yaml
@@ -2250,9 +2250,6 @@ 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 1d8b94e1a1..375d4011fb 100644
--- a/api/v1/server/embedded_spec.go
+++ b/api/v1/server/embedded_spec.go
@@ -1429,10 +1429,6 @@ func init() {
           "description": "Layer 4 port number",
           "type": "integer",
           "format": "uint16"
-        },
-        "protocol": {
-          "description": "Layer 4 protocol (TCP, UDP, etc)",
-          "type": "string"
         }
       }
     },
@@ -5151,10 +5147,6 @@ 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 2a746c53f3..9be45f4656 100644
--- a/bpf/bpf_sock.c
+++ b/bpf/bpf_sock.c
@@ -264,25 +264,79 @@ sock4_wildcard_lookup_full(struct lb4_key *key __maybe_unused,
 	return svc;
 }
 
+/* Service translation logic for a local-redirect service can cause
+ * packets to be looped back to a service node-local backend after translation.
+ * This can happen when the node-local backend itself tries to connect to the
+ * service frontend for which it acts as a backend.
+ * There are cases where this can break traffic flow if the backend needs to
+ * forward the redirected traffic to the actual service frontend.
+ * Hence, allow service translation for pod traffic getting redirected to
+ * backend (across network namespaces), but skip service translation for backend
+ * to itself or another service backend within the same namespace.
+ *
+ * For example, in EKS cluster, a local-redirect service exists with the AWS
+ * metadata IP, port as the frontend <169.254.169.254, 80> and kiam proxy as a
+ * backend pod. When traffic destined to the frontend originates from the kiam pod
+ * in namespace ns1 (host ns when the kiam proxy pod is deployed in hostNetwork
+ * mode or regular pod ns) and the pod is selected as a backend, the traffic
+ * would get looped back to the proxy pod.
+ * Identify such cases by doing a socket lookup for the backend <ip, port> in its
+ * namespace, ns1, and skip service translation.
+ */
+#ifdef BPF_HAVE_SOCKET_LOOKUP
+static __always_inline __maybe_unused bool
+sock4_skip_xlate_for_same_netns_backend(struct bpf_sock_addr *ctx,
+					const struct lb4_backend *backend)
+{
+	struct bpf_sock_tuple tuple = {
+		.ipv4.saddr = 0,
+		.ipv4.sport = 0,
+		.ipv4.daddr = backend->address,
+		.ipv4.dport = backend->port,
+	};
+	struct bpf_sock *sk = NULL;
+
+	switch (ctx->protocol) {
+	case IPPROTO_TCP:
+		sk = sk_lookup_tcp(ctx, &tuple, sizeof(tuple.ipv4), BPF_F_CURRENT_NETNS, 0);
+		break;
+	case IPPROTO_UDP:
+		sk = sk_lookup_udp(ctx, &tuple, sizeof(tuple.ipv4), BPF_F_CURRENT_NETNS, 0);
+		break;
+	default:
+		break;
+	}
+
+	/* There exists a socket listening on the <ip, port> of the backend
+	 * in the same namespace where socket connect call originates from.
+	 */
+	if (sk) {
+		sk_release(sk);
+		return true;
+	}
+
+	return false;
+}
+#endif
+
 static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
 					     struct bpf_sock_addr *ctx_full,
-					     const bool udp_only)
+					     const bool udp_only,
+					     const bool ipv6_context  __maybe_unused)
 {
 	union lb4_affinity_client_id id;
 	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(proto))
+	if (!udp_only && !sock_proto_enabled(ctx->protocol))
 		return -ENOTSUP;
 
 	/* In case a direct match fails, we try to look-up surrogate
@@ -347,6 +401,13 @@ static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
 		return -ENOENT;
 	}
 
+#ifdef BPF_HAVE_SOCKET_LOOKUP
+	if (lb4_svc_is_localredirect(svc) && !ipv6_context &&
+	    sock4_skip_xlate_for_same_netns_backend(ctx_full, backend)) {
+		return 0;
+	}
+#endif
+
 	if (lb4_svc_is_affinity(svc) && !backend_from_affinity)
 		lb4_update_affinity_by_netns(svc, &id, backend_id);
 
@@ -365,7 +426,7 @@ static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
 __section("connect4")
 int sock4_connect(struct bpf_sock_addr *ctx)
 {
-	__sock4_xlate_fwd(ctx, ctx, false);
+	__sock4_xlate_fwd(ctx, ctx, false, false);
 	return SYS_PROCEED;
 }
 
@@ -374,14 +435,12 @@ 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(proto) ||
+	if (!sock_proto_enabled(ctx->protocol) ||
 	    !ctx_in_hostns(ctx_full, NULL))
 		return 0;
 
@@ -432,7 +491,6 @@ 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);
@@ -456,7 +514,7 @@ static __always_inline int __sock4_xlate_rev(struct bpf_sock_addr *ctx,
 __section("sendmsg4")
 int sock4_sendmsg(struct bpf_sock_addr *ctx)
 {
-	__sock4_xlate_fwd(ctx, ctx, true);
+	__sock4_xlate_fwd(ctx, ctx, true, false);
 	return SYS_PROCEED;
 }
 
@@ -639,7 +697,7 @@ int sock6_xlate_v4_in_v6(struct bpf_sock_addr *ctx __maybe_unused,
 	fake_ctx.user_ip4  = addr6.p4;
 	fake_ctx.user_port = ctx_dst_port(ctx);
 
-	ret = __sock4_xlate_fwd(&fake_ctx, ctx, udp_only);
+	ret = __sock4_xlate_fwd(&fake_ctx, ctx, udp_only, true);
 	if (ret < 0)
 		return ret;
 
@@ -677,13 +735,11 @@ 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(proto) ||
+	if (!sock_proto_enabled(ctx->protocol) ||
 	    !ctx_in_hostns(ctx, NULL))
 		return 0;
 
@@ -722,16 +778,14 @@ 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(proto))
+	if (!udp_only && !sock_proto_enabled(ctx->protocol))
 		return -ENOTSUP;
 
 	ctx_get_v6_address(ctx, &key.address);
@@ -848,7 +902,6 @@ 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/include/bpf/features.h b/bpf/include/bpf/features.h
index 9b3bdf1fd7..136ff8f555 100644
--- a/bpf/include/bpf/features.h
+++ b/bpf/include/bpf/features.h
@@ -31,4 +31,9 @@
 # define BPF_HAVE_CSUM_LEVEL 1
 #endif
 
+#if HAVE_PROG_TYPE_HELPER(cgroup_sock_addr, bpf_sk_lookup_tcp) && \
+    HAVE_PROG_TYPE_HELPER(cgroup_sock_addr, bpf_sk_lookup_udp)
+# define BPF_HAVE_SOCKET_LOOKUP 1
+#endif
+
 #endif /* ____BPF_FEATURES____ */
diff --git a/bpf/include/bpf/helpers.h b/bpf/include/bpf/helpers.h
index 7502842bf4..04fa59a838 100644
--- a/bpf/include/bpf/helpers.h
+++ b/bpf/include/bpf/helpers.h
@@ -84,4 +84,12 @@ static int BPF_FUNC(sock_hash_update, struct bpf_sock_ops *skops, void *map,
 static int BPF_FUNC(msg_redirect_hash, struct sk_msg_md *md, void *map,
 		    void *key, __u64 flags);
 
+/* Socket lookup helpers */
+static struct bpf_sock *BPF_FUNC(sk_lookup_tcp, void *ctx,
+				 struct bpf_sock_tuple *tuple, __u32 tuple_size,
+				 __u64 netns, __u64 flags);
+static struct bpf_sock *BPF_FUNC(sk_lookup_udp, void *ctx,
+				 struct bpf_sock_tuple *tuple, __u32 tuple_size,
+				 __u64 netns, __u64 flags);
+
 #endif /* __BPF_HELPERS__ */
diff --git a/bpf/include/bpf/helpers_skb.h b/bpf/include/bpf/helpers_skb.h
index 947555f1fc..d443ae8924 100644
--- a/bpf/include/bpf/helpers_skb.h
+++ b/bpf/include/bpf/helpers_skb.h
@@ -54,9 +54,6 @@ static int BPF_FUNC_REMAP(skb_event_output, struct __sk_buff *skb, void *map,
 static struct bpf_sock *BPF_FUNC(skc_lookup_tcp, struct __sk_buff *skb,
 				 struct bpf_sock_tuple *tuple, __u32 tuple_size,
 				 __u64 netns, __u64 flags);
-static struct bpf_sock *BPF_FUNC(sk_lookup_udp, struct __sk_buff *skb,
-				 struct bpf_sock_tuple *tuple, __u32 tuple_size,
-				 __u64 netns, __u64 flags);
 static int BPF_FUNC(sk_release, struct bpf_sock *sk);
 static int BPF_FUNC(sk_assign, struct __sk_buff *skb, struct bpf_sock *sk,
 		    __u64 flags);
diff --git a/bpf/lib/common.h b/bpf/lib/common.h
index 60a430ed4b..e0612d946f 100644
--- a/bpf/lib/common.h
+++ b/bpf/lib/common.h
@@ -559,7 +559,11 @@ enum {
 	SVC_FLAG_LOADBALANCER = (1 << 5),  /* LoadBalancer service */
 	SVC_FLAG_ROUTABLE     = (1 << 6),  /* Not a surrogate/ClusterIP entry */
 	SVC_FLAG_SOURCE_RANGE = (1 << 7),  /* Check LoadBalancer source range */
-	SVC_FLAG_LOCALREDIRECT = (1 << 8),  /* local redirect */
+};
+
+/* Service flags (lb{4,6}_service->flags2) */
+enum {
+	SVC_FLAG_LOCALREDIRECT = (1 << 0),  /* local redirect */
 };
 
 struct ipv6_ct_tuple {
@@ -632,7 +636,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 */
+	__u8 proto;		/* L4 protocol, currently not used (set to 0) */
 	__u8 scope;		/* LB_LOOKUP_SCOPE_* for externalTrafficPolicy=Local */
 	__u8 pad[2];
 };
@@ -680,7 +684,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 */
+	__u8 proto;		/* L4 protocol, currently not used (set to 0) */
 	__u8 scope;		/* LB_LOOKUP_SCOPE_* for externalTrafficPolicy=Local */
 	__u8 pad[2];
 };
diff --git a/bpf/lib/lb.h b/bpf/lib/lb.h
index 6615518025..42ac11a266 100644
--- a/bpf/lib/lb.h
+++ b/bpf/lib/lb.h
@@ -333,6 +333,12 @@ bool lb6_svc_is_routable(const struct lb6_service *svc)
 	return __lb_svc_is_routable(svc->flags);
 }
 
+static __always_inline
+bool lb4_svc_is_localredirect(const struct lb4_service *svc __maybe_unused)
+{
+	return svc->flags2 & SVC_FLAG_LOCALREDIRECT;
+}
+
 static __always_inline int extract_l4_port(struct __ctx_buff *ctx, __u8 nexthdr,
 					   int l4_off, __be16 *port,
 					   __maybe_unused struct iphdr *ip4)
@@ -500,7 +506,8 @@ static __always_inline int lb6_extract_key(struct __ctx_buff *ctx __maybe_unused
 					   int dir)
 {
 	union v6addr *addr;
-	key->proto = tuple->nexthdr;
+	/* FIXME(brb): set after adding support for different L4 protocols in LB */
+	key->proto = 0;
 	addr = (dir == CT_INGRESS) ? &tuple->saddr : &tuple->daddr;
 	ipv6_addr_copy(&key->address, addr);
 	csum_l4_offset_and_flags(tuple->nexthdr, csum_off);
@@ -1019,7 +1026,8 @@ static __always_inline int lb4_extract_key(struct __ctx_buff *ctx __maybe_unused
 					   struct csum_offset *csum_off,
 					   int dir)
 {
-	key->proto = ip4->protocol;
+	/* FIXME: set after adding support for different L4 protocols in LB */
+	key->proto = 0;
 	key->address = (dir == CT_INGRESS) ? ip4->saddr : ip4->daddr;
 	if (ipv4_has_l4_header(ip4))
 		csum_l4_offset_and_flags(ip4->protocol, csum_off);
diff --git a/bpf/sockops/bpf_redir.c b/bpf/sockops/bpf_redir.c
index f55fdab14a..77855a71ae 100644
--- a/bpf/sockops/bpf_redir.c
+++ b/bpf/sockops/bpf_redir.c
@@ -28,10 +28,6 @@ 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 13545c4e80..a932a3194b 100644
--- a/bpf/sockops/bpf_sockops.c
+++ b/bpf/sockops/bpf_sockops.c
@@ -29,11 +29,6 @@ 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
@@ -50,7 +45,6 @@ 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 f45ad0ffe0..13c4a44025 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 protocol;
+	__u8 pad7;
 	__u16 pad8;
 	__u32 sport;
 	__u32 dport;
diff --git a/cilium/cmd/bpf_lb_maglev_get.go b/cilium/cmd/bpf_lb_maglev_get.go
new file mode 100644
index 0000000000..99ee281a67
--- /dev/null
+++ b/cilium/cmd/bpf_lb_maglev_get.go
@@ -0,0 +1,87 @@
+// 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.
+
+package cmd
+
+import (
+	"errors"
+	"strconv"
+
+	"github.com/cilium/cilium/pkg/bpf"
+	"github.com/cilium/cilium/pkg/command"
+	"github.com/cilium/cilium/pkg/common"
+	"github.com/cilium/cilium/pkg/maps/lbmap"
+
+	"github.com/spf13/cobra"
+	"golang.org/x/sys/unix"
+)
+
+var bpfMaglevGetCmd = &cobra.Command{
+	Use:     "get <service id>",
+	Aliases: []string{"get"},
+	Short:   "Get Maglev lookup table for given service by ID",
+	PreRun:  requireServiceID,
+	Run: func(cmd *cobra.Command, args []string) {
+		common.RequireRootPrivilege("cilium bpf lb maglev get")
+
+		svcID, err := strconv.Atoi(args[0])
+		if err != nil {
+			Fatalf("Unable to parse %s: %s", args[0], err)
+		}
+		key := &lbmap.MaglevOuterKey{RevNatID: uint16(svcID)}
+		key = key.ToNetwork()
+		val := &lbmap.MaglevOuterVal{}
+
+		lookupTables := map[string][]string{}
+		found := false
+
+		for _, name := range []string{lbmap.MaglevOuter4MapName, lbmap.MaglevOuter6MapName} {
+			// See bpf_lb_maglev_list.go comment for why we use low-level routines
+			// to open and access the maps
+			if m, err := bpf.OpenMap(name); err != nil {
+				continue
+			} else {
+				err := bpf.LookupElement(m.GetFd(), key.GetKeyPtr(), val.GetValuePtr())
+				if err != nil {
+					if errors.Is(err, unix.ENOENT) {
+						continue
+					}
+					Fatalf("Unable to retrieve entry from %s with key %s: %s",
+						name, key, err)
+				} else {
+					found = true
+					parseMaglevEntry(key, val, lookupTables)
+				}
+			}
+		}
+
+		if !found {
+			Fatalf("No entry for %d svc is found", svcID)
+		}
+
+		if command.OutputJSON() {
+			if err := command.PrintOutput(lookupTables); err != nil {
+				Fatalf("Unable to generate JSON output: %s", err)
+			}
+			return
+		}
+
+		TablePrinter("SVC ID", "LOOKUP TABLE", lookupTables)
+	},
+}
+
+func init() {
+	bpfMaglevCmd.AddCommand(bpfMaglevGetCmd)
+	command.AddJSONOutput(bpfMaglevGetCmd)
+}
diff --git a/cilium/cmd/bpf_lb_maglev_list.go b/cilium/cmd/bpf_lb_maglev_list.go
index ad1c918ae8..7b7dc7ed1a 100644
--- a/cilium/cmd/bpf_lb_maglev_list.go
+++ b/cilium/cmd/bpf_lb_maglev_list.go
@@ -48,36 +48,40 @@ var bpfMaglevListCmd = &cobra.Command{
 	},
 }
 
-func dumpMaglevTables(tables map[string][]string) {
-	lookupTableSize := 0
-
-	parse := func(key bpf.MapKey, value bpf.MapValue) {
-		v := value.(*lbmap.MaglevOuterVal)
-		k := key.(*lbmap.MaglevOuterKey)
+var lookupTableSize = 0
 
-		// Determine lookup table size by inspecting the first inner map
-		if lookupTableSize == 0 {
-			fd, err := bpf.MapFdFromID(int(v.FD))
-			if err != nil {
-				Fatalf("Unable to get map fd by id %d: %s", v.FD, err)
-			}
-			m, err := bpf.GetMapInfoByFd(uint32(fd))
-			if err != nil {
-				Fatalf("Unable to get map info by fd %d: %s", fd, err)
-			}
-			lookupTableSize = int(m.ValueSize) / 2
-		}
+func parseMaglevEntry(key bpf.MapKey, value bpf.MapValue, tables map[string][]string) {
+	k := key.(*lbmap.MaglevOuterKey)
+	v := value.(*lbmap.MaglevOuterVal)
 
-		table := make([]uint16, lookupTableSize)
-		zero := uint32(0)
+	// Determine lookup table size by inspecting the first inner map
+	if lookupTableSize == 0 {
 		fd, err := bpf.MapFdFromID(int(v.FD))
 		if err != nil {
 			Fatalf("Unable to get map fd by id %d: %s", v.FD, err)
 		}
-		if err := bpf.LookupElement(int(fd), unsafe.Pointer(&zero), unsafe.Pointer(&table[0])); err != nil {
-			Fatalf("Unable to lookup element in map by fd %d: %s", fd, err)
+		m, err := bpf.GetMapInfoByFd(uint32(fd))
+		if err != nil {
+			Fatalf("Unable to get map info by fd %d: %s", fd, err)
 		}
-		tables[k.ToNetwork().String()] = []string{fmt.Sprintf("%v", table)}
+		lookupTableSize = int(m.ValueSize) / 2
+	}
+
+	table := make([]uint16, lookupTableSize)
+	zero := uint32(0)
+	fd, err := bpf.MapFdFromID(int(v.FD))
+	if err != nil {
+		Fatalf("Unable to get map fd by id %d: %s", v.FD, err)
+	}
+	if err := bpf.LookupElement(int(fd), unsafe.Pointer(&zero), unsafe.Pointer(&table[0])); err != nil {
+		Fatalf("Unable to lookup element in map by fd %d: %s", fd, err)
+	}
+	tables[k.ToNetwork().String()] = []string{fmt.Sprintf("%v", table)}
+}
+
+func dumpMaglevTables(tables map[string][]string) {
+	parse := func(key bpf.MapKey, value bpf.MapValue) {
+		parseMaglevEntry(key, value, tables)
 	}
 
 	for _, name := range []string{lbmap.MaglevOuter4MapName, lbmap.MaglevOuter6MapName} {
diff --git a/cilium/cmd/service_update.go b/cilium/cmd/service_update.go
index 76fe88285c..bf9eb225bd 100644
--- a/cilium/cmd/service_update.go
+++ b/cilium/cmd/service_update.go
@@ -37,7 +37,6 @@ var (
 	localRedirect      bool
 	idU                uint64
 	frontend           string
-	protocol           string
 	backends           []string
 )
 
@@ -61,36 +60,11 @@ 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 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)
+func parseFrontendAddress(address string) (*models.FrontendAddress, net.IP) {
+	frontend, err := net.ResolveTCPAddr("tcp", address)
 	if err != nil {
 		Fatalf("Unable to parse frontend address: %s\n", err)
 	}
@@ -100,12 +74,13 @@ func parseFrontendAddress(l4Protocol, address string) (*models.FrontendAddress,
 		scope = models.FrontendAddressScopeInternal
 	}
 
+	// FIXME support more than TCP
 	return &models.FrontendAddress{
-		IP:       ip.String(),
-		Port:     uint16(port),
-		Protocol: proto,
+		IP:       frontend.IP.String(),
+		Port:     uint16(frontend.Port),
+		Protocol: models.FrontendAddressProtocolTCP,
 		Scope:    scope,
-	}, ip
+	}, frontend.IP
 }
 
 func boolToInt(set bool) int {
@@ -117,7 +92,7 @@ func boolToInt(set bool) int {
 
 func updateService(cmd *cobra.Command, args []string) {
 	id := int64(idU)
-	fa, faIP := parseFrontendAddress(protocol, frontend)
+	fa, faIP := parseFrontendAddress(frontend)
 
 	var spec *models.ServiceSpec
 	svc, err := client.GetServiceID(id)
@@ -175,19 +150,20 @@ func updateService(cmd *cobra.Command, args []string) {
 
 	spec.BackendAddresses = nil
 	for _, backend := range backends {
-		ip, port, proto, err := parseAddress(protocol, backend)
+		beAddr, err := net.ResolveTCPAddr("tcp", backend)
 		if err != nil {
-			Fatalf("Cannot parse backend address %q: %s", backend, err)
+			Fatalf("Cannot parse backend address \"%s\": %s", backend, err)
 		}
+
 		// Backend ID will be set by the daemon
-		be := loadbalancer.NewBackend(0, loadbalancer.L4Type(strings.ToUpper(proto)), ip, uint16(port))
+		be := loadbalancer.NewBackend(0, loadbalancer.TCP, beAddr.IP, uint16(beAddr.Port))
 
 		if be.IsIPv6() && faIP.To4() != nil {
 			Fatalf("Address mismatch between frontend and backend %s", backend)
 		}
 
-		if fa.Port == 0 && port != 0 {
-			Fatalf("L4 backend found (%s:%d) with L3 frontend", ip, port)
+		if fa.Port == 0 && beAddr.Port != 0 {
+			Fatalf("L4 backend found (%v) with L3 frontend", beAddr)
 		}
 
 		ba := be.GetBackendModel()
diff --git a/daemon/cmd/agenthealth.go b/daemon/cmd/agenthealth.go
index a467536ec3..7fe7c45f75 100644
--- a/daemon/cmd/agenthealth.go
+++ b/daemon/cmd/agenthealth.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"syscall"
 
 	"github.com/cilium/cilium/api/v1/models"
 	"github.com/cilium/cilium/pkg/option"
@@ -29,26 +28,6 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-func setsockoptReuseAddrAndPort(network, address string, c syscall.RawConn) error {
-	var soerr error
-	if err := c.Control(func(su uintptr) {
-		s := int(su)
-		// Allow reuse of recently-used addresses. This socket option is
-		// set by default on listeners in Go's net package, see
-		// net setDefaultListenerSockopts
-		soerr = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
-		if soerr != nil {
-			return
-		}
-		// Allow reuse of recently-used ports. This gives the agent a
-		// better change to re-bind upon restarts.
-		soerr = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
-	}); err != nil {
-		return err
-	}
-	return soerr
-}
-
 // startAgentHealthHTTPService registers a handler function for the /healthz status HTTP endpoint
 // exposed on localhost (127.0.0.1 and/or ::1, depending on IPv4/IPv6 options). This
 // endpoint reports the agent health status and is equivalent to what the `cilium status --brief`
@@ -71,12 +50,12 @@ func (d *Daemon) startAgentHealthHTTPService() {
 			}
 			return false
 		}
-
 		statusCode := http.StatusOK
 		sr := d.getStatus(true)
 		if isUnhealthy(&sr) {
 			statusCode = http.StatusServiceUnavailable
 		}
+
 		w.WriteHeader(statusCode)
 	}))
 
diff --git a/daemon/cmd/daemon_main.go b/daemon/cmd/daemon_main.go
index 8cde97fa3d..3bf05ad06b 100644
--- a/daemon/cmd/daemon_main.go
+++ b/daemon/cmd/daemon_main.go
@@ -508,6 +508,7 @@ func init() {
 
 	flags.Uint(option.K8sWatcherQueueSize, 1024, "Queue size used to serialize each k8s event type")
 	option.BindEnv(option.K8sWatcherQueueSize)
+	flags.MarkDeprecated(option.K8sWatcherQueueSize, "Unused flag, will be removed in 1.10")
 
 	flags.String(option.LabelPrefixFile, "", "Valid label prefixes file path")
 	option.BindEnv(option.LabelPrefixFile)
@@ -524,6 +525,9 @@ func init() {
 		option.KubeProxyReplacementStrict, option.KubeProxyReplacementDisabled))
 	option.BindEnv(option.KubeProxyReplacement)
 
+	flags.String(option.KubeProxyReplacementHealthzBindAddr, defaults.KubeProxyReplacementHealthzBindAddr, "The IP address with port for kube-proxy replacement health check server to serve on (set to '0.0.0.0:10256' for all IPv4 interfaces and '[::]:10256' for all IPv6 interfaces). Set empty to disable.")
+	option.BindEnv(option.KubeProxyReplacementHealthzBindAddr)
+
 	flags.Bool(option.EnableHostPort, true, fmt.Sprintf("Enable k8s hostPort mapping feature (requires enabling %s)", option.EnableNodePort))
 	option.BindEnv(option.EnableHostPort)
 
@@ -1435,6 +1439,11 @@ func runDaemon() {
 	metricsErrs := initMetrics()
 
 	d.startAgentHealthHTTPService()
+	if option.Config.KubeProxyReplacementHealthzBindAddr != "" {
+		if option.Config.KubeProxyReplacement != option.KubeProxyReplacementDisabled {
+			d.startKubeProxyHealthzHTTPService(fmt.Sprintf("%s", option.Config.KubeProxyReplacementHealthzBindAddr))
+		}
+	}
 
 	bootstrapStats.initAPI.Start()
 	srv := server.NewServer(d.instantiateAPI())
diff --git a/daemon/cmd/kube_proxy_healthz.go b/daemon/cmd/kube_proxy_healthz.go
new file mode 100644
index 0000000000..59b4bd73b7
--- /dev/null
+++ b/daemon/cmd/kube_proxy_healthz.go
@@ -0,0 +1,102 @@
+// 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.
+
+package cmd
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"time"
+
+	"github.com/cilium/cilium/api/v1/models"
+
+	"github.com/sirupsen/logrus"
+	"golang.org/x/sys/unix"
+)
+
+// DaemonInterface to help with testing.
+type DaemonInterface interface {
+	getStatus(bool) models.StatusResponse
+}
+
+// ServiceInterface to help with testing.
+type ServiceInterface interface {
+	GetLastUpdatedTs() time.Time
+	GetCurrentTs() time.Time
+}
+
+type kubeproxyHealthzHandler struct {
+	d   DaemonInterface
+	svc ServiceInterface
+}
+
+// startKubeProxyHealthzHTTPService registers a handler function for the kube-proxy /healthz
+// status HTTP endpoint exposed on addr.
+// This endpoint reports the agent health status with the timestamp.
+func (d *Daemon) startKubeProxyHealthzHTTPService(addr string) {
+	lc := net.ListenConfig{Control: setsockoptReuseAddrAndPort}
+	ln, err := lc.Listen(context.Background(), "tcp", addr)
+	addrField := logrus.Fields{"address": addr}
+	if errors.Is(err, unix.EADDRNOTAVAIL) {
+		log.WithFields(addrField).Info("KubeProxy healthz server not available")
+	} else if err != nil {
+		log.WithFields(addrField).WithError(err).Fatal("hint: kube-proxy should not be running nor listening on the same healthz-bind-address.")
+	}
+
+	mux := http.NewServeMux()
+	mux.Handle("/healthz", kubeproxyHealthzHandler{d: d, svc: d.svc})
+
+	srv := &http.Server{
+		Addr:    addr,
+		Handler: mux,
+	}
+
+	go func() {
+		err := srv.Serve(ln)
+		if errors.Is(err, http.ErrServerClosed) {
+			log.WithFields(addrField).Info("kube-proxy healthz status API server shutdown")
+		} else if err != nil {
+			log.WithFields(addrField).WithError(err).Fatal("Unable to start kube-proxy healthz server")
+		}
+	}()
+	log.WithFields(addrField).Info("Started kube-proxy healthz server")
+}
+
+func (h kubeproxyHealthzHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	isUnhealthy := func(sr *models.StatusResponse) bool {
+		if sr.Cilium != nil {
+			state := sr.Cilium.State
+			return state != models.StatusStateOk && state != models.StatusStateDisabled
+		}
+		return false
+	}
+
+	statusCode := http.StatusOK
+	currentTs := h.svc.GetCurrentTs()
+	var lastUpdateTs = currentTs
+	// We piggy back here on Cilium daemon health. If Cilium is healthy, we can
+	// reasonably assume that the node networking is ready.
+	sr := h.d.getStatus(true)
+	if isUnhealthy(&sr) {
+		statusCode = http.StatusServiceUnavailable
+		lastUpdateTs = h.svc.GetLastUpdatedTs()
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("X-Content-Type-Options", "nosniff")
+	w.WriteHeader(statusCode)
+	fmt.Fprintf(w, `{"lastUpdated": %q,"currentTime": %q}`, lastUpdateTs, currentTs)
+}
diff --git a/daemon/cmd/sockopt.go b/daemon/cmd/sockopt.go
new file mode 100644
index 0000000000..b946dc13d9
--- /dev/null
+++ b/daemon/cmd/sockopt.go
@@ -0,0 +1,44 @@
+// 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.
+
+package cmd
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+// setsockoptReuseAddrAndPort sets the SO_REUSEADDR and SO_REUSEPORT socket options on c's
+// underlying socket in order to improve the chance to re-bind to the same address and port
+// upon agent restart.
+func setsockoptReuseAddrAndPort(network, address string, c syscall.RawConn) error {
+	var soerr error
+	if err := c.Control(func(su uintptr) {
+		s := int(su)
+		// Allow reuse of recently-used addresses. This socket option is
+		// set by default on listeners in Go's net package, see
+		// net setDefaultListenerSockopts
+		soerr = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
+		if soerr != nil {
+			return
+		}
+		// Allow reuse of recently-used ports. This gives the agent a
+		// better chance to re-bind upon restarts.
+		soerr = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+	}); err != nil {
+		return err
+	}
+	return soerr
+}
diff --git a/install/kubernetes/Makefile b/install/kubernetes/Makefile
index e23c4ccde3..c2496a2113 100644
--- a/install/kubernetes/Makefile
+++ b/install/kubernetes/Makefile
@@ -30,6 +30,16 @@ EXPERIMENTAL_OPTIONS := \
     --set hubble.ui.enabled=true \
     --set bandwidthManager=true
 
+DOCKER_RUN := $(CONTAINER_ENGINE) container run --rm \
+	--workdir /src/install/kubernetes \
+	--volume $(CURDIR)/../..:/src \
+	--user "$(shell id -u):$(shell id -g)"
+
+HELM_DOCS_VERSION := "v1.3.0"
+HELM_DOCS_SHA := "12f065115654c9a1d6819b938487d327f1fdb5c32bdc2a5c74afef22ed68f5ca"
+HELM_DOCS_IMAGE := "docker.io/jnorwood/helm-docs:$(HELM_DOCS_VERSION)@sha256:$(HELM_DOCS_SHA)"
+HELM_DOCS := $(DOCKER_RUN) $(HELM_DOCS_IMAGE)
+
 all: update-versions $(QUICK_INSTALL) $(EXPERIMENTAL_INSTALL)
 
 $(QUICK_INSTALL) quick-install: $(shell find cilium/ -type f) update-versions
@@ -76,4 +86,7 @@ lint:
 clean:
 	$(RM) $(QUICK_INSTALL) $(EXPERIMENTAL_INSTALL)
 
-.PHONY: all clean update-versions lint quick-install experimental-install
+docs:
+	$(QUIET)$(HELM_DOCS)
+
+.PHONY: all clean update-versions lint quick-install experimental-install docs
diff --git a/install/kubernetes/cilium/README.md b/install/kubernetes/cilium/README.md
index 300278b597..20d963c004 100644
--- a/install/kubernetes/cilium/README.md
+++ b/install/kubernetes/cilium/README.md
@@ -69,7 +69,7 @@ Helm chart for Cilium
 | etcd.extraInitContainers | list | `[]` |  |
 | etcd.image.pullPolicy | string | `"Always"` |  |
 | etcd.image.repository | string | `"docker.io/cilium/cilium-etcd-operator"` |  |
-| etcd.image.tag | string | `"latest"` |  |
+| etcd.image.tag | string | `"v2.0.7"` |  |
 | etcd.k8sService | bool | `false` |  |
 | etcd.managed | bool | `false` |  |
 | etcd.nodeSelector | object | `{}` |  |
@@ -118,8 +118,6 @@ Helm chart for Cilium
 | hubble.relay.replicas | int | `1` |  |
 | hubble.relay.resources | object | `{}` |  |
 | hubble.relay.retryTimeout | string | `nil` |  |
-| hubble.relay.serviceAccount.annotations | object | `{}` |  |
-| hubble.relay.serviceAccount.create | bool | `true` |  |
 | hubble.relay.sortBufferDrainTimeout | string | `nil` |  |
 | hubble.relay.sortBufferLenMax | string | `nil` |  |
 | hubble.relay.tls.client.cert | string | `""` |  |
@@ -139,12 +137,12 @@ Helm chart for Cilium
 | hubble.tls.server.cert | string | `""` |  |
 | hubble.tls.server.key | string | `""` |  |
 | hubble.ui.backend.image.pullPolicy | string | `"Always"` |  |
-| hubble.ui.backend.image.repository | string | `"quay.io/cilium/hubble-ui-backend"` |  |
+| hubble.ui.backend.image.repository | string | `"docker.io/cilium/hubble-ui-backend"` |  |
 | hubble.ui.backend.image.tag | string | `"latest"` |  |
 | hubble.ui.backend.resources | object | `{}` |  |
 | hubble.ui.enabled | bool | `false` |  |
 | hubble.ui.frontend.image.pullPolicy | string | `"Always"` |  |
-| hubble.ui.frontend.image.repository | string | `"quay.io/cilium/hubble-ui"` |  |
+| hubble.ui.frontend.image.repository | string | `"docker.io/cilium/hubble-ui"` |  |
 | hubble.ui.frontend.image.tag | string | `"latest"` |  |
 | hubble.ui.frontend.resources | object | `{}` |  |
 | hubble.ui.ingress.annotations | object | `{}` |  |
@@ -155,12 +153,10 @@ Helm chart for Cilium
 | hubble.ui.podAnnotations | object | `{}` |  |
 | hubble.ui.proxy.image.pullPolicy | string | `"Always"` |  |
 | hubble.ui.proxy.image.repository | string | `"docker.io/envoyproxy/envoy"` |  |
-| hubble.ui.proxy.image.tag | string | `"v1.14.1"` |  |
+| hubble.ui.proxy.image.tag | string | `"v1.14.5"` |  |
 | hubble.ui.proxy.resources | object | `{}` |  |
 | hubble.ui.replicas | int | `1` |  |
 | hubble.ui.securityContext.enabled | bool | `true` |  |
-| hubble.ui.serviceAccount.annotations | object | `{}` |  |
-| hubble.ui.serviceAccount.create | bool | `true` |  |
 | hubble.ui.tolerations | list | `[]` |  |
 | hubble.ui.updateStrategy.rollingUpdate.maxUnavailable | int | `1` |  |
 | hubble.ui.updateStrategy.type | string | `"RollingUpdate"` |  |
@@ -183,6 +179,7 @@ Helm chart for Cilium
 | keepDeprecatedLabels | bool | `false` |  |
 | keepDeprecatedProbes | bool | `false` |  |
 | kubeProxyReplacement | string | `"probe"` |  |
+| kubeProxyReplacementHealthzBindAddr | string | `""` |  |
 | kvstore.managed | bool | `false` |  |
 | kvstore.ssl | string | `nil` |  |
 | l7Proxy | bool | `false` |  |
@@ -217,6 +214,10 @@ Helm chart for Cilium
 | nodeinit.securityContext | object | `{}` |  |
 | nodeinit.tolerations[0].operator | string | `"Exists"` |  |
 | nodeinit.updateStrategy.type | string | `"RollingUpdate"` |  |
+| operator.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].key | string | `"io.cilium/app"` |  |
+| operator.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].operator | string | `"In"` |  |
+| operator.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values[0] | string | `"operator"` |  |
+| operator.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey | string | `"kubernetes.io/hostname"` |  |
 | operator.enabled | bool | `true` |  |
 | operator.endpointGCInterval | string | `"5m0s"` |  |
 | operator.extraArgs | object | `{}` |  |
@@ -311,3 +312,4 @@ Helm chart for Cilium
 | updateStrategy.rollingUpdate.maxUnavailable | int | `2` |  |
 | updateStrategy.type | string | `"RollingUpdate"` |  |
 | wellKnownIdentities.enabled | bool | `false` |  |
+
diff --git a/install/kubernetes/cilium/README.md.gotmpl b/install/kubernetes/cilium/README.md.gotmpl
new file mode 100644
index 0000000000..6f52a68e95
--- /dev/null
+++ b/install/kubernetes/cilium/README.md.gotmpl
@@ -0,0 +1,18 @@
+{{ template "chart.header" . }}
+
+{{ template "chart.deprecationWarning" . }}
+
+{{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }}
+
+{{ template "chart.description" . }}
+
+{{ template "chart.homepageLine" . }}
+
+{{ template "chart.maintainersSection" . }}
+
+{{ template "chart.sourcesSection" . }}
+
+{{ template "chart.requirementsSection" . }}
+
+{{ template "chart.valuesSection" . }}
+
diff --git a/install/kubernetes/cilium/templates/cilium-configmap.yaml b/install/kubernetes/cilium/templates/cilium-configmap.yaml
index 4727cabad3..423eda9648 100644
--- a/install/kubernetes/cilium/templates/cilium-configmap.yaml
+++ b/install/kubernetes/cilium/templates/cilium-configmap.yaml
@@ -437,6 +437,9 @@ data:
 
 {{- if hasKey .Values "kubeProxyReplacement" }}
   kube-proxy-replacement:  {{ .Values.kubeProxyReplacement | quote }}
+{{- if ne .Values.kubeProxyReplacement "disabled" }}
+  kube-proxy-replacement-healthz-bind-address: {{ default "" .Values.kubeProxyReplacementHealthzBindAddr | quote}}
+{{- end }}
 {{- end }}
 {{- if hasKey .Values "hostServices" }}
 {{- if .Values.hostServices.enabled }}
diff --git a/install/kubernetes/cilium/templates/cilium-operator-deployment.yaml b/install/kubernetes/cilium/templates/cilium-operator-deployment.yaml
index 6e9527de2f..2075591d71 100644
--- a/install/kubernetes/cilium/templates/cilium-operator-deployment.yaml
+++ b/install/kubernetes/cilium/templates/cilium-operator-deployment.yaml
@@ -41,16 +41,10 @@ spec:
 {{- if or (ge .Capabilities.KubeVersion.Minor "14") (gt .Capabilities.KubeVersion.Major "1") }}
       # In HA mode, cilium-operator pods must not be scheduled on the same
       # node as they will clash with each other.
+  {{- if .Values.operator.affinity }}
       affinity:
-        podAntiAffinity:
-          requiredDuringSchedulingIgnoredDuringExecution:
-          - labelSelector:
-              matchExpressions:
-              - key: io.cilium/app
-                operator: In
-                values:
-                - operator
-            topologyKey: "kubernetes.io/hostname"
+  {{- toYaml .Values.operator.affinity | trim | nindent 8 }}
+  {{- end }}
 {{- end }}
 {{- if .Values.imagePullSecrets }}
       imagePullSecrets:
diff --git a/install/kubernetes/cilium/templates/hubble-ui-clusterrole.yaml b/install/kubernetes/cilium/templates/hubble-ui-clusterrole.yaml
index 78b5c39c1c..fc4aa2f69a 100644
--- a/install/kubernetes/cilium/templates/hubble-ui-clusterrole.yaml
+++ b/install/kubernetes/cilium/templates/hubble-ui-clusterrole.yaml
@@ -1,4 +1,4 @@
-{{- if and (.Values.hubble.ui.enabled) (.Values.hubble.ui.serviceAccount.create) }}
+{{- if and (.Values.hubble.ui.enabled) (.Values.serviceAccounts.ui.create) }}
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
diff --git a/install/kubernetes/cilium/templates/hubble-ui-clusterrolebinding.yaml b/install/kubernetes/cilium/templates/hubble-ui-clusterrolebinding.yaml
index ca5792a691..cd2c4cbe50 100644
--- a/install/kubernetes/cilium/templates/hubble-ui-clusterrolebinding.yaml
+++ b/install/kubernetes/cilium/templates/hubble-ui-clusterrolebinding.yaml
@@ -1,4 +1,4 @@
-{{- if and (.Values.hubble.ui.enabled) (.Values.hubble.ui.serviceAccount.create) }}
+{{- if and (.Values.hubble.ui.enabled) (.Values.serviceAccounts.ui.create) }}
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
diff --git a/install/kubernetes/cilium/values.yaml b/install/kubernetes/cilium/values.yaml
index ca6f32d5cb..82f7dcc8c3 100644
--- a/install/kubernetes/cilium/values.yaml
+++ b/install/kubernetes/cilium/values.yaml
@@ -488,10 +488,6 @@ hubble:
     # Enable Hubble Relay (requires hubble.enabled=true)
     enabled: false
 
-    serviceAccount:
-      create: true
-      annotations: {}
-
     image:
       repository: docker.io/cilium/hubble-relay
       tag: latest
@@ -622,10 +618,6 @@ hubble:
       #         memory: 64Mi
       resources: {}
 
-    serviceAccount:
-      create: true
-      annotations: {}
-
     replicas: 1
 
     ## Annotations to be added to hubble-ui pods
@@ -735,6 +727,12 @@ keepDeprecatedProbes: false
 # kubeProxyReplacement enables kube-proxy replacement in Cilium BPF datapath
 kubeProxyReplacement: "probe"
 
+# kubeProxyReplacement healthz server bind address
+# To enable set the value to '0.0.0.0:10256' for all ipv4
+# addresses and this '[::]:10256' for all ipv6 addresses.
+# By default it is disabled.
+kubeProxyReplacementHealthzBindAddr: ""
+
 kvstore:
   # Managed enables/disables the Cilium etcd-operator
   managed: false
@@ -1007,6 +1005,21 @@ operator:
       maxUnavailable: 1
     type: RollingUpdate
 
+  ## Affinity
+  ##
+  affinity:
+    podAntiAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+      - labelSelector:
+          matchExpressions:
+          - key: io.cilium/app
+            operator: In
+            values:
+            - operator
+        topologyKey: kubernetes.io/hostname
+
+
+  
   ## Additional cilium-etcd-operator container arguments
   ##
   extraArgs: {}
diff --git a/install/kubernetes/experimental-install.yaml b/install/kubernetes/experimental-install.yaml
index 36c2cc7af5..ad93b8151b 100644
--- a/install/kubernetes/experimental-install.yaml
+++ b/install/kubernetes/experimental-install.yaml
@@ -132,6 +132,7 @@ data:
   auto-direct-node-routes: "false"
   enable-bandwidth-manager: "true"
   kube-proxy-replacement:  "probe"
+  kube-proxy-replacement-healthz-bind-address: ""
   enable-health-check-nodeport: "true"
   node-port-bind-protection: "true"
   enable-auto-protect-node-port-range: "true"
@@ -904,7 +905,7 @@ spec:
                 operator: In
                 values:
                 - operator
-            topologyKey: "kubernetes.io/hostname"
+            topologyKey: kubernetes.io/hostname
       containers:
       - args:
         - --config-dir=/tmp/cilium/config-map
diff --git a/install/kubernetes/quick-install.yaml b/install/kubernetes/quick-install.yaml
index 12ffcc3a2b..ae8eeff824 100644
--- a/install/kubernetes/quick-install.yaml
+++ b/install/kubernetes/quick-install.yaml
@@ -118,6 +118,7 @@ data:
   auto-direct-node-routes: "false"
   enable-bandwidth-manager: "false"
   kube-proxy-replacement:  "probe"
+  kube-proxy-replacement-healthz-bind-address: ""
   enable-health-check-nodeport: "true"
   node-port-bind-protection: "true"
   enable-auto-protect-node-port-range: "true"
@@ -636,7 +637,7 @@ spec:
                 operator: In
                 values:
                 - operator
-            topologyKey: "kubernetes.io/hostname"
+            topologyKey: kubernetes.io/hostname
       containers:
       - args:
         - --config-dir=/tmp/cilium/config-map
diff --git a/operator/option/config.go b/operator/option/config.go
index ede2ef522f..cbc0e12b8a 100644
--- a/operator/option/config.go
+++ b/operator/option/config.go
@@ -155,6 +155,10 @@ const (
 	// for retrieving Azure API credentials
 	AzureUserAssignedIdentityID = "azure-user-assigned-identity-id"
 
+	// AzureUsePrimaryAddress specify wether we should use or ignore the interface's
+	// primary IPConfiguration
+	AzureUsePrimaryAddress = "azure-use-primary-address"
+
 	// CRDWaitTimeout it the time after which Cilium CRDs have to be available.
 	CRDWaitTimeout = "crd-wait-timeout"
 
@@ -294,6 +298,10 @@ type OperatorConfig struct {
 	// for retrieving Azure API credentials
 	AzureUserAssignedIdentityID string
 
+	// AzureUsePrimaryAddress specify wether we should use or ignore the interface's
+	// primary IPConfiguration
+	AzureUsePrimaryAddress bool
+
 	// CRDWaitTimeout it the time after which Cilium CRDs have to be available.
 	CRDWaitTimeout time.Duration
 
@@ -348,6 +356,7 @@ func (c *OperatorConfig) Populate() {
 	c.AzureCloudName = viper.GetString(AzureCloudName)
 	c.AzureSubscriptionID = viper.GetString(AzureSubscriptionID)
 	c.AzureResourceGroup = viper.GetString(AzureResourceGroup)
+	c.AzureUsePrimaryAddress = viper.GetBool(AzureUsePrimaryAddress)
 	c.AzureUserAssignedIdentityID = viper.GetString(AzureUserAssignedIdentityID)
 
 	// Option maps and slices
diff --git a/operator/provider_azure_flags.go b/operator/provider_azure_flags.go
index c9dc555d5a..4925d2bf76 100644
--- a/operator/provider_azure_flags.go
+++ b/operator/provider_azure_flags.go
@@ -38,5 +38,8 @@ func init() {
 	flags.String(operatorOption.AzureUserAssignedIdentityID, "", "ID of the user assigned identity used to auth with the Azure API")
 	option.BindEnvWithLegacyEnvFallback(operatorOption.AzureUserAssignedIdentityID, "AZURE_USER_ASSIGNED_IDENTITY_ID")
 
+	flags.Bool(operatorOption.AzureUsePrimaryAddress, true, "Use Azure IP address from interface's primary IPConfigurations")
+	option.BindEnvWithLegacyEnvFallback(operatorOption.AzureUsePrimaryAddress, "AZURE_USE_PRIMARY_ADDRESS")
+
 	viper.BindPFlags(flags)
 }
diff --git a/pkg/azure/api/api.go b/pkg/azure/api/api.go
index b65f98717a..2dd72cf233 100644
--- a/pkg/azure/api/api.go
+++ b/pkg/azure/api/api.go
@@ -54,6 +54,7 @@ type Client struct {
 	vmscalesets     compute.VirtualMachineScaleSetsClient
 	limiter         *helpers.ApiLimiter
 	metricsAPI      MetricsAPI
+	usePrimary      bool
 }
 
 // MetricsAPI represents the metrics maintained by the Azure API client
@@ -91,7 +92,7 @@ func constructAuthorizer(cloudName, userAssignedIdentityID string) (autorest.Aut
 }
 
 // NewClient returns a new Azure client
-func NewClient(cloudName, subscriptionID, resourceGroup, userAssignedIdentityID string, metrics MetricsAPI, rateLimit float64, burst int) (*Client, error) {
+func NewClient(cloudName, subscriptionID, resourceGroup, userAssignedIdentityID string, metrics MetricsAPI, rateLimit float64, burst int, usePrimary bool) (*Client, error) {
 	c := &Client{
 		resourceGroup:   resourceGroup,
 		interfaces:      network.NewInterfacesClient(subscriptionID),
@@ -100,6 +101,7 @@ func NewClient(cloudName, subscriptionID, resourceGroup, userAssignedIdentityID
 		vmscalesets:     compute.NewVirtualMachineScaleSetsClient(subscriptionID),
 		metricsAPI:      metrics,
 		limiter:         helpers.NewApiLimiter(metrics, rateLimit, burst),
+		usePrimary:      usePrimary,
 	}
 
 	authorizer, err := constructAuthorizer(cloudName, userAssignedIdentityID)
@@ -219,7 +221,7 @@ func (c *Client) vmssNetworkInterfaces(ctx context.Context) ([]network.Interface
 
 // parseInterfaces parses a network.Interface as returned by the Azure API
 // converts it into a types.AzureInterface
-func parseInterface(iface *network.Interface, subnets ipamTypes.SubnetMap) (instanceID string, i *types.AzureInterface) {
+func parseInterface(iface *network.Interface, subnets ipamTypes.SubnetMap, usePrimary bool) (instanceID string, i *types.AzureInterface) {
 	i = &types.AzureInterface{}
 
 	if iface.VirtualMachine != nil && iface.VirtualMachine.ID != nil {
@@ -247,6 +249,9 @@ func parseInterface(iface *network.Interface, subnets ipamTypes.SubnetMap) (inst
 
 	if iface.IPConfigurations != nil {
 		for _, ip := range *iface.IPConfigurations {
+			if !usePrimary && ip.Primary != nil && *ip.Primary {
+				continue
+			}
 			if ip.PrivateIPAddress != nil {
 				addr := types.AzureAddress{
 					IP:    *ip.PrivateIPAddress,
@@ -289,7 +294,7 @@ func (c *Client) GetInstances(ctx context.Context, subnets ipamTypes.SubnetMap)
 	}
 
 	for _, iface := range networkInterfaces {
-		if id, azureInterface := parseInterface(&iface, subnets); id != "" {
+		if id, azureInterface := parseInterface(&iface, subnets, c.usePrimary); id != "" {
 			instances.Update(id, ipamTypes.InterfaceRevision{Resource: azureInterface})
 		}
 	}
diff --git a/pkg/azure/ipam/node.go b/pkg/azure/ipam/node.go
index 9d5b0fdf97..830fac6f01 100644
--- a/pkg/azure/ipam/node.go
+++ b/pkg/azure/ipam/node.go
@@ -48,6 +48,9 @@ func (n *Node) UpdatedNode(obj *v2.CiliumNode) {
 // resource with Azure specific information
 func (n *Node) PopulateStatusFields(k8sObj *v2.CiliumNode) {
 	k8sObj.Status.Azure.Interfaces = []types.AzureInterface{}
+
+	n.manager.mutex.RLock()
+	defer n.manager.mutex.RUnlock()
 	n.manager.instances.ForeachInterface(n.node.InstanceID(), func(instanceID, interfaceID string, interfaceObj ipamTypes.InterfaceRevision) error {
 		iface, ok := interfaceObj.Resource.(*types.AzureInterface)
 		if ok {
diff --git a/pkg/bpf/bpf_linux.go b/pkg/bpf/bpf_linux.go
index 6bb699bb4e..52b05810f1 100644
--- a/pkg/bpf/bpf_linux.go
+++ b/pkg/bpf/bpf_linux.go
@@ -158,7 +158,7 @@ func LookupElementFromPointers(fd int, structPtr unsafe.Pointer, sizeOfStruct ui
 	}
 
 	if ret != 0 || err != 0 {
-		return fmt.Errorf("Unable to lookup element in map with file descriptor %d: %s", fd, err)
+		return fmt.Errorf("Unable to lookup element in map with file descriptor %d: %w", fd, err)
 	}
 
 	return nil
diff --git a/pkg/datapath/linux/config/config.go b/pkg/datapath/linux/config/config.go
index ca982a852a..7cf6b6d3df 100644
--- a/pkg/datapath/linux/config/config.go
+++ b/pkg/datapath/linux/config/config.go
@@ -356,16 +356,20 @@ func (h *HeaderfileWriter) WriteNodeConfig(w io.Writer, cfg *datapath.LocalNodeC
 		cDefinesMap["ENABLE_HOST_FIREWALL"] = "1"
 	}
 
-	if option.Config.EncryptInterface != "" {
-		link, err := netlink.LinkByName(option.Config.EncryptInterface)
+	if iface := option.Config.EncryptInterface; iface != "" {
+		link, err := netlink.LinkByName(iface)
 		if err == nil {
 			cDefinesMap["ENCRYPT_IFACE"] = fmt.Sprintf("%d", link.Attrs().Index)
 
 			addr, err := netlink.AddrList(link, netlink.FAMILY_V4)
-			if err == nil {
-				a := byteorder.HostSliceToNetwork(addr[0].IPNet.IP, reflect.Uint32).(uint32)
-				cDefinesMap["IPV4_ENCRYPT_IFACE"] = fmt.Sprintf("%d", a)
+			if err != nil {
+				return err
 			}
+			if len(addr) == 0 {
+				return fmt.Errorf("no IPv4 addresses available in encrypt interface %q", iface)
+			}
+			a := byteorder.HostSliceToNetwork(addr[0].IPNet.IP, reflect.Uint32).(uint32)
+			cDefinesMap["IPV4_ENCRYPT_IFACE"] = fmt.Sprintf("%d", a)
 		}
 	}
 	if option.Config.IsPodSubnetsDefined() {
diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go
index 94ccc99ec1..5f1d2bcfdd 100644
--- a/pkg/defaults/defaults.go
+++ b/pkg/defaults/defaults.go
@@ -389,4 +389,7 @@ const (
 	// for the support of Leases in Kubernetes when there is an error in discovering
 	// API groups using Discovery API.
 	K8sEnableLeasesFallbackDiscovery = false
+
+	// KubeProxyReplacementHealthzBindAddr is the default kubeproxyReplacement healthz server bind addr
+	KubeProxyReplacementHealthzBindAddr = ""
 )
diff --git a/pkg/identity/cache/cache.go b/pkg/identity/cache/cache.go
index a30d4ba2aa..5fa84f3ad3 100644
--- a/pkg/identity/cache/cache.go
+++ b/pkg/identity/cache/cache.go
@@ -51,8 +51,7 @@ func (m *CachingIdentityAllocator) GetIdentityCache() IdentityCache {
 	log.Debug("getting identity cache for identity allocator manager")
 	cache := IdentityCache{}
 
-	if m.IdentityAllocator != nil {
-
+	if m.isGlobalIdentityAllocatorInitialized() {
 		m.IdentityAllocator.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) {
 			if val != nil {
 				if gi, ok := val.(GlobalIdentity); ok {
@@ -69,7 +68,7 @@ func (m *CachingIdentityAllocator) GetIdentityCache() IdentityCache {
 		cache[key] = identity.Labels.LabelArray()
 	}
 
-	if m.localIdentities != nil {
+	if m.isLocalIdentityAllocatorInitialized() {
 		for _, identity := range m.localIdentities.GetIdentities() {
 			cache[identity.ID] = identity.Labels.LabelArray()
 		}
@@ -82,22 +81,25 @@ func (m *CachingIdentityAllocator) GetIdentityCache() IdentityCache {
 func (m *CachingIdentityAllocator) GetIdentities() IdentitiesModel {
 	identities := IdentitiesModel{}
 
-	m.IdentityAllocator.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) {
-		if gi, ok := val.(GlobalIdentity); ok {
-			identity := identity.NewIdentityFromLabelArray(identity.NumericIdentity(id), gi.LabelArray)
-			identities = append(identities, identitymodel.CreateModel(identity))
-		}
+	if m.isGlobalIdentityAllocatorInitialized() {
+		m.IdentityAllocator.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) {
+			if gi, ok := val.(GlobalIdentity); ok {
+				identity := identity.NewIdentityFromLabelArray(identity.NumericIdentity(id), gi.LabelArray)
+				identities = append(identities, identitymodel.CreateModel(identity))
+			}
 
-	})
+		})
+	}
 	// append user reserved identities
 	for _, v := range identity.ReservedIdentityCache {
 		identities = append(identities, identitymodel.CreateModel(v))
 	}
 
-	for _, v := range m.localIdentities.GetIdentities() {
-		identities = append(identities, identitymodel.CreateModel(v))
+	if m.isLocalIdentityAllocatorInitialized() {
+		for _, v := range m.localIdentities.GetIdentities() {
+			identities = append(identities, identitymodel.CreateModel(v))
+		}
 	}
-
 	return identities
 }
 
@@ -199,19 +201,46 @@ func (w *identityWatcher) stop() {
 	close(w.stopChan)
 }
 
+// isLocalIdentityAllocatorInitialized returns true if m.localIdentities is not nil.
+func (m *CachingIdentityAllocator) isLocalIdentityAllocatorInitialized() bool {
+	select {
+	case <-m.localIdentityAllocatorInitialized:
+		return m.localIdentities != nil
+	default:
+		return false
+	}
+}
+
+// isGlobalIdentityAllocatorInitialized returns true if m.IdentityAllocator is not nil.
+// Note: This does not mean that the identities have been synchronized,
+// see WaitForInitialGlobalIdentities to wait for a fully populated cache.
+func (m *CachingIdentityAllocator) isGlobalIdentityAllocatorInitialized() bool {
+	select {
+	case <-m.globalIdentityAllocatorInitialized:
+		return m.IdentityAllocator != nil
+	default:
+		return false
+	}
+}
+
 // LookupIdentity looks up the identity by its labels but does not create it.
 // This function will first search through the local cache, then the caches for
-// remote kvstores and finally fall back to the main kvstore
+// remote kvstores and finally fall back to the main kvstore.
+// May return nil for lookups if the allocator has not yet been synchronized.
 func (m *CachingIdentityAllocator) LookupIdentity(ctx context.Context, lbls labels.Labels) *identity.Identity {
 	if reservedIdentity := identity.LookupReservedIdentityByLabels(lbls); reservedIdentity != nil {
 		return reservedIdentity
 	}
 
+	if !m.isLocalIdentityAllocatorInitialized() {
+		return nil
+	}
+
 	if identity := m.localIdentities.lookup(lbls); identity != nil {
 		return identity
 	}
 
-	if !identity.RequiresGlobalIdentity(lbls) || m.IdentityAllocator == nil {
+	if !identity.RequiresGlobalIdentity(lbls) || !m.isGlobalIdentityAllocatorInitialized() {
 		return nil
 	}
 
@@ -233,6 +262,7 @@ var unknownIdentity = identity.NewIdentity(identity.IdentityUnknown, labels.Labe
 // LookupIdentityByID returns the identity by ID. This function will first
 // search through the local cache, then the caches for remote kvstores and
 // finally fall back to the main kvstore
+// May return nil for lookups if the allocator has not yet been synchronized.
 func (m *CachingIdentityAllocator) LookupIdentityByID(ctx context.Context, id identity.NumericIdentity) *identity.Identity {
 	if id == identity.IdentityUnknown {
 		return unknownIdentity
@@ -242,11 +272,15 @@ func (m *CachingIdentityAllocator) LookupIdentityByID(ctx context.Context, id id
 		return identity
 	}
 
+	if !m.isLocalIdentityAllocatorInitialized() {
+		return nil
+	}
+
 	if identity := m.localIdentities.lookupByID(id); identity != nil {
 		return identity
 	}
 
-	if id.HasLocalScope() || m.IdentityAllocator == nil {
+	if !m.isGlobalIdentityAllocatorInitialized() || id.HasLocalScope() {
 		return nil
 	}
 
diff --git a/pkg/ipam/allocator/azure/azure.go b/pkg/ipam/allocator/azure/azure.go
index 6ca696aed7..6213b1dd10 100644
--- a/pkg/ipam/allocator/azure/azure.go
+++ b/pkg/ipam/allocator/azure/azure.go
@@ -78,7 +78,7 @@ func (*AllocatorAzure) Start(getterUpdater ipam.CiliumNodeGetterUpdater) (alloca
 		iMetrics = &ipamMetrics.NoOpMetrics{}
 	}
 
-	azureClient, err := azureAPI.NewClient(operatorOption.Config.AzureCloudName, subscriptionID, resourceGroupName, operatorOption.Config.AzureUserAssignedIdentityID, azMetrics, operatorOption.Config.IPAMAPIQPSLimit, operatorOption.Config.IPAMAPIBurst)
+	azureClient, err := azureAPI.NewClient(operatorOption.Config.AzureCloudName, subscriptionID, resourceGroupName, operatorOption.Config.AzureUserAssignedIdentityID, azMetrics, operatorOption.Config.IPAMAPIQPSLimit, operatorOption.Config.IPAMAPIBurst, operatorOption.Config.AzureUsePrimaryAddress)
 	if err != nil {
 		return nil, fmt.Errorf("unable to create Azure client: %w", err)
 	}
diff --git a/pkg/k8s/apis/cilium.io/v2/clrp_types.go b/pkg/k8s/apis/cilium.io/v2/clrp_types.go
index 7a3aff6d70..fc49c240c1 100644
--- a/pkg/k8s/apis/cilium.io/v2/clrp_types.go
+++ b/pkg/k8s/apis/cilium.io/v2/clrp_types.go
@@ -234,6 +234,11 @@ func (pInfo *PortInfo) SanitizePortInfo(checkNamedPort bool) (uint16, string, lb
 	}
 	pName = strings.ToLower(pInfo.Name) // Normalize for case insensitive comparison
 
-	protocol = lb.NewL4Type(string(pInfo.Protocol))
+	// Sanitize protocol
+	var err error
+	protocol, err = lb.NewL4Type(string(pInfo.Protocol))
+	if err != nil {
+		return pInt, pName, protocol, err
+	}
 	return pInt, pName, protocol, nil
 }
diff --git a/pkg/k8s/service.go b/pkg/k8s/service.go
index 35e7030abf..09a6b3db4a 100644
--- a/pkg/k8s/service.go
+++ b/pkg/k8s/service.go
@@ -442,6 +442,18 @@ 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 e47c725896..7731451ef0 100644
--- a/pkg/k8s/service_cache.go
+++ b/pkg/k8s/service_cache.go
@@ -341,6 +341,28 @@ 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.
@@ -357,12 +379,12 @@ func (s *ServiceCache) UniqueServiceFrontends() FrontendList {
 				L4Addr: *p,
 				Scope:  loadbalancer.ScopeExternal,
 			}
-			uniqueFrontends[address.String()] = struct{}{}
+			uniqueFrontends[address.StringWithProtocol()] = struct{}{}
 		}
 		for _, nodePortFEs := range svc.NodePorts {
 			for _, fe := range nodePortFEs {
 				if fe.Scope == loadbalancer.ScopeExternal {
-					uniqueFrontends[fe.String()] = struct{}{}
+					uniqueFrontends[fe.StringWithProtocol()] = struct{}{}
 				}
 			}
 		}
diff --git a/pkg/k8s/watchers/pod.go b/pkg/k8s/watchers/pod.go
index da46a87b1e..bed5e05a0b 100644
--- a/pkg/k8s/watchers/pod.go
+++ b/pkg/k8s/watchers/pod.go
@@ -491,9 +491,13 @@ 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 574b92f324..b95807b78b 100644
--- a/pkg/k8s/watchers/watcher.go
+++ b/pkg/k8s/watchers/watcher.go
@@ -383,7 +383,7 @@ func (k *K8sWatcher) WaitForCRDsToRegister(ctx context.Context) error {
 // caches essential for daemon are synchronized.
 func (k *K8sWatcher) InitK8sSubsystem(ctx context.Context) <-chan struct{} {
 	cachesSynced := make(chan struct{})
-	if err := k.EnableK8sWatcher(ctx, option.Config.K8sWatcherQueueSize); err != nil {
+	if err := k.EnableK8sWatcher(ctx); err != nil {
 		if !errors.Is(err, context.Canceled) {
 			log.WithError(err).Fatal("Unable to start K8s watchers for Cilium")
 		}
@@ -459,8 +459,7 @@ func (k *K8sWatcher) InitK8sSubsystem(ctx context.Context) <-chan struct{} {
 
 // EnableK8sWatcher watches for policy, services and endpoint changes on the Kubernetes
 // api server defined in the receiver's daemon k8sClient.
-// queueSize specifies the queue length used to serialize k8s events.
-func (k *K8sWatcher) EnableK8sWatcher(ctx context.Context, queueSize uint) error {
+func (k *K8sWatcher) EnableK8sWatcher(ctx context.Context) error {
 	if !k8s.IsEnabled() {
 		log.Debug("Not enabling k8s event listener because k8s is not enabled")
 		return nil
@@ -627,8 +626,16 @@ 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)
 
@@ -742,8 +749,14 @@ 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 {
@@ -853,7 +866,6 @@ 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 4dd2b623ff..508c392aff 100644
--- a/pkg/loadbalancer/loadbalancer.go
+++ b/pkg/loadbalancer/loadbalancer.go
@@ -160,7 +160,6 @@ func (s ServiceFlags) UInt16() uint16 {
 }
 
 const (
-	// NONE type.
 	NONE = L4Type("NONE")
 	// TCP type.
 	TCP = L4Type("TCP")
@@ -266,25 +265,14 @@ func (s *SVC) GetModel() *models.Service {
 	}
 }
 
-func NewL4Type(name string) L4Type {
+func NewL4Type(name string) (L4Type, error) {
 	switch strings.ToLower(name) {
 	case "tcp":
-		return TCP
+		return TCP, nil
 	case "udp":
-		return UDP
+		return UDP, nil
 	default:
-		return NONE
-	}
-}
-
-func NewL4TypeFromNumber(proto uint8) L4Type {
-	switch proto {
-	case 6:
-		return TCP
-	case 17:
-		return UDP
-	default:
-		return NONE
+		return "", fmt.Errorf("unknown L4 protocol")
 	}
 }
 
@@ -319,11 +307,6 @@ 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}.
@@ -353,9 +336,16 @@ func NewL3n4AddrFromModel(base *models.FrontendAddress) (*L3n4Addr, error) {
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	p := NewL4Type(base.Protocol)
+	proto := NONE
+	if base.Protocol != "" {
+		p, err := NewL4Type(base.Protocol)
+		if err != nil {
+			return nil, err
+		}
+		proto = p
+	}
 
-	l4addr := NewL4Addr(p, base.Port)
+	l4addr := NewL4Addr(proto, base.Port)
 	ip := net.ParseIP(base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", base.IP)
@@ -388,8 +378,8 @@ func NewBackendFromBackendModel(base *models.BackendAddress) (*Backend, error) {
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	p := NewL4Type(base.Protocol)
-	l4addr := NewL4Addr(p, base.Port)
+	// FIXME: Should this be NONE ?
+	l4addr := NewL4Addr(NONE, base.Port)
 	ip := net.ParseIP(*base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", *base.IP)
@@ -403,8 +393,8 @@ func NewL3n4AddrFromBackendModel(base *models.BackendAddress) (*L3n4Addr, error)
 		return nil, fmt.Errorf("missing IP address")
 	}
 
-	p := NewL4Type(base.Protocol)
-	l4addr := NewL4Addr(p, base.Port)
+	// FIXME: Should this be NONE ?
+	l4addr := NewL4Addr(NONE, base.Port)
 	ip := net.ParseIP(*base.IP)
 	if ip == nil {
 		return nil, fmt.Errorf("invalid IP address \"%s\"", *base.IP)
@@ -422,10 +412,9 @@ func (a *L3n4Addr) GetModel() *models.FrontendAddress {
 		scope = models.FrontendAddressScopeInternal
 	}
 	return &models.FrontendAddress{
-		IP:       a.IP.String(),
-		Port:     a.Port,
-		Scope:    scope,
-		Protocol: a.Protocol,
+		IP:    a.IP.String(),
+		Port:  a.Port,
+		Scope: scope,
 	}
 }
 
@@ -439,13 +428,25 @@ 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/Protocol[/Scope]" format for IPv4 and
-// "[IPv6]:Port/Protocol[/Scope]" format for IPv6.
+// String returns the L3n4Addr in the "IPv4:Port[/Scope]" format for IPv4 and
+// "[IPv6]:Port[/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"
@@ -476,7 +477,14 @@ func (a *L3n4Addr) DeepCopy() *L3n4Addr {
 
 // Hash calculates L3n4Addr's internal SHA256Sum.
 func (a L3n4Addr) Hash() string {
-	str := []byte(fmt.Sprintf("%s", a.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))
 	return fmt.Sprintf("%x", sha512.Sum512_256(str))
 }
 
diff --git a/pkg/maps/lbmap/ipv4.go b/pkg/maps/lbmap/ipv4.go
index 523f20f908..bc2751e1bd 100644
--- a/pkg/maps/lbmap/ipv4.go
+++ b/pkg/maps/lbmap/ipv4.go
@@ -171,28 +171,11 @@ func NewService4Key(ip net.IP, port uint16, proto u8proto.U8proto, scope uint8,
 }
 
 func (k *Service4Key) String() string {
-	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"
+	addr := net.JoinHostPort(k.Address.String(), fmt.Sprintf("%d", k.Port))
+	if k.Scope == loadbalancer.ScopeInternal {
+		addr += "/i"
 	}
-
-	return fmt.Sprintf("%s/%s%s", a, p, i)
+	return addr
 }
 
 func (k *Service4Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }
@@ -206,7 +189,6 @@ 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 {
@@ -331,7 +313,6 @@ 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 1d20e2dba8..25b3a96cbb 100644
--- a/pkg/maps/lbmap/ipv6.go
+++ b/pkg/maps/lbmap/ipv6.go
@@ -164,12 +164,11 @@ func NewService6Key(ip net.IP, port uint16, proto u8proto.U8proto, scope uint8,
 }
 
 func (k *Service6Key) String() string {
-	return serviceKey(
-		k.Address.String(),
-		k.Port,
-		k.GetProtocol(),
-		k.Scope == loadbalancer.ScopeInternal,
-	)
+	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)
+	}
 }
 
 func (k *Service6Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }
@@ -183,7 +182,6 @@ 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 {
@@ -299,7 +297,6 @@ 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 b04c77d6a7..04059f3e51 100644
--- a/pkg/maps/lbmap/lbmap.go
+++ b/pkg/maps/lbmap/lbmap.go
@@ -64,7 +64,6 @@ type UpsertServiceParams struct {
 	ID                        uint16
 	IP                        net.IP
 	Port                      uint16
-	Protocol                  string
 	Backends                  map[string]uint16
 	PrevBackendCount          int
 	IPv6                      bool
@@ -91,15 +90,10 @@ 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, proto, p.Scope, 0)
+		svcKey = NewService6Key(p.IP, p.Port, u8proto.ANY, p.Scope, 0)
 	} else {
-		svcKey = NewService4Key(p.IP, p.Port, proto, p.Scope, 0)
+		svcKey = NewService4Key(p.IP, p.Port, u8proto.ANY, p.Scope, 0)
 	}
 
 	slot := 1
@@ -182,16 +176,12 @@ 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, proto, svc.Scope, 0)
+		svcKey = NewService6Key(svc.IP, svc.Port, u8proto.ANY, svc.Scope, 0)
 		revNATKey = NewRevNat6Key(uint16(svc.ID))
 	} else {
-		svcKey = NewService4Key(svc.IP, svc.Port, proto, svc.Scope, 0)
+		svcKey = NewService4Key(svc.IP, svc.Port, u8proto.ANY, svc.Scope, 0)
 		revNATKey = NewRevNat4Key(uint16(svc.ID))
 	}
 
@@ -216,7 +206,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, protocol loadbalancer.L4Type, port uint16, ipv6 bool) error {
+func (*LBBPFMap) AddBackend(id uint16, ip net.IP, port uint16, ipv6 bool) error {
 	var (
 		backend Backend
 		err     error
@@ -226,14 +216,10 @@ func (*LBBPFMap) AddBackend(id uint16, ip net.IP, protocol loadbalancer.L4Type,
 		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, p)
+		backend, err = NewBackend6(loadbalancer.BackendID(id), ip, port, u8proto.ANY)
 	} else {
-		backend, err = NewBackend4(loadbalancer.BackendID(id), ip, port, p)
+		backend, err = NewBackend4(loadbalancer.BackendID(id), ip, port, u8proto.ANY)
 	}
 	if err != nil {
 		return fmt.Errorf("Unable to create backend (%d, %s, %d, %t): %s",
@@ -487,7 +473,7 @@ func (*LBBPFMap) DumpBackendMaps() ([]*loadbalancer.Backend, error) {
 	for backendID, backendVal := range backendValueMap {
 		ip := backendVal.GetAddress()
 		port := backendVal.GetPort()
-		proto := loadbalancer.NewL4TypeFromNumber(backendVal.GetProtocol())
+		proto := loadbalancer.NONE
 		lbBackend := loadbalancer.NewBackend(backendID, proto, ip, port)
 		lbBackends = append(lbBackends, lbBackend)
 	}
diff --git a/pkg/maps/lbmap/types.go b/pkg/maps/lbmap/types.go
index 3260ec1d33..f8eefee7ac 100644
--- a/pkg/maps/lbmap/types.go
+++ b/pkg/maps/lbmap/types.go
@@ -53,9 +53,6 @@ type ServiceKey interface {
 	// Get frontend port
 	GetPort() uint16
 
-	// Get protocol
-	GetProtocol() uint8
-
 	// Returns a RevNatValue matching a ServiceKey
 	RevNatValue() RevNatValue
 
@@ -128,9 +125,6 @@ type BackendValue interface {
 	// Get backend port
 	GetPort() uint16
 
-	// Get backend protocol
-	GetProtocol() uint8
-
 	// Convert fields to network byte order.
 	ToNetwork() BackendValue
 }
@@ -174,8 +168,7 @@ 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 {
-	p := loadbalancer.NewL4TypeFromNumber(svcKey.GetProtocol())
-	feL3n4Addr := loadbalancer.NewL3n4Addr(p, svcKey.GetAddress(), svcKey.GetPort(), svcKey.GetScope())
+	feL3n4Addr := loadbalancer.NewL3n4Addr(loadbalancer.NONE, svcKey.GetAddress(), svcKey.GetPort(), svcKey.GetScope())
 	feL3n4AddrID := &loadbalancer.L3n4AddrID{
 		L3n4Addr: *feL3n4Addr,
 		ID:       loadbalancer.ID(svcValue.GetRevNat()),
@@ -186,7 +179,7 @@ func svcFrontend(svcKey ServiceKey, svcValue ServiceValue) *loadbalancer.L3n4Add
 func svcBackend(backendID loadbalancer.BackendID, backend BackendValue) *loadbalancer.Backend {
 	beIP := backend.GetAddress()
 	bePort := backend.GetPort()
-	p := loadbalancer.NewL4TypeFromNumber(backend.GetProtocol())
-	beBackend := loadbalancer.NewBackend(backendID, p, beIP, bePort)
+	beProto := loadbalancer.NONE
+	beBackend := loadbalancer.NewBackend(backendID, beProto, beIP, bePort)
 	return beBackend
 }
diff --git a/pkg/maps/sockmap/sockmap.go b/pkg/maps/sockmap/sockmap.go
index 9fa63e017b..f6731d7fff 100644
--- a/pkg/maps/sockmap/sockmap.go
+++ b/pkg/maps/sockmap/sockmap.go
@@ -23,20 +23,19 @@ 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"`
-	Protocol uint8      `align:"protocol"`
-	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"`
+	Pad7   uint8      `align:"pad7"`
+	Pad8   uint16     `align:"pad8"`
+	SPort  uint32     `align:"sport"`
+	DPort  uint32     `align:"dport"`
 }
 
 // SockmapValue is the fd of a socket
@@ -48,11 +47,7 @@ type SockmapValue struct {
 
 // String pretty print the 5-tuple as sip:sport->dip:dport
 func (v SockmapKey) String() string {
-	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)
+	return fmt.Sprintf("%s:%d->%s:%d", v.SIP.String(), v.SPort, v.DIP.String(), v.DPort)
 }
 
 // String pretty print the file descriptor value, note this is local to agent.
diff --git a/pkg/option/config.go b/pkg/option/config.go
index a4023a0e41..4af044c928 100644
--- a/pkg/option/config.go
+++ b/pkg/option/config.go
@@ -1240,6 +1240,9 @@ const (
 	// KubeProxyReplacementDisabled specified to completely disable kube-proxy
 	// replacement
 	KubeProxyReplacementDisabled = "disabled"
+
+	// KubeProxyReplacement healthz server bind address
+	KubeProxyReplacementHealthzBindAddr = "kube-proxy-replacement-healthz-bind-address"
 )
 
 // GetTunnelModes returns the list of all tunnel modes
@@ -1396,10 +1399,6 @@ type DaemonConfig struct {
 	// status in kube-apiserver.
 	K8sForceJSONPatch bool
 
-	// K8sWatcherQueueSize is the queue size used to serialize each k8s event
-	// type.
-	K8sWatcherQueueSize uint
-
 	// MTU is the maximum transmission unit of the underlying network
 	MTU int
 
@@ -1511,7 +1510,7 @@ type DaemonConfig struct {
 	// runs in the same container as Cilium.
 	EnvoyLogPath string
 
-	// SockopsEnable specifies whether to enable sockops (socket lookup).
+	// EnableSockOps specifies whether to enable sockops (socket lookup).
 	SockopsEnable bool
 
 	// PrependIptablesChains is the name of the option to enable prepending
@@ -1825,6 +1824,9 @@ type DaemonConfig struct {
 	// EnableBandwidthManager enables EDT-based pacing
 	EnableBandwidthManager bool
 
+	// KubeProxyReplacementHealthzBindAddr is the KubeProxyReplacement healthz server bind addr
+	KubeProxyReplacementHealthzBindAddr string
+
 	// EnableExternalIPs enables implementation of k8s services with externalIPs in datapath
 	EnableExternalIPs bool
 
@@ -2483,7 +2485,6 @@ func (c *DaemonConfig) Populate() {
 	c.K8sForceJSONPatch = viper.GetBool(K8sForceJSONPatch)
 	c.K8sEventHandover = viper.GetBool(K8sEventHandover)
 	c.K8sSyncTimeout = viper.GetDuration(K8sSyncTimeoutName)
-	c.K8sWatcherQueueSize = uint(viper.GetInt(K8sWatcherQueueSize))
 	c.K8sWatcherEndpointSelector = viper.GetString(K8sWatcherEndpointSelector)
 	c.KeepConfig = viper.GetBool(KeepConfig)
 	c.KVStore = viper.GetString(KVStore)
@@ -2707,6 +2708,8 @@ func (c *DaemonConfig) Populate() {
 		}
 	}
 
+	c.KubeProxyReplacementHealthzBindAddr = viper.GetString(KubeProxyReplacementHealthzBindAddr)
+
 	// Hubble options.
 	c.EnableHubble = viper.GetBool(EnableHubble)
 	c.HubbleSocketPath = viper.GetString(HubbleSocketPath)
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 3f914b282d..8f0a948c3d 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -17,6 +17,8 @@ package service
 import (
 	"fmt"
 	"net"
+	"sync/atomic"
+	"time"
 
 	"github.com/cilium/cilium/pkg/bpf"
 	"github.com/cilium/cilium/pkg/cidr"
@@ -44,7 +46,7 @@ var (
 type LBMap interface {
 	UpsertService(*lbmap.UpsertServiceParams) error
 	DeleteService(lb.L3n4AddrID, int, bool) error
-	AddBackend(uint16, net.IP, lb.L4Type, uint16, bool) error
+	AddBackend(uint16, net.IP, uint16, bool) error
 	DeleteBackendByID(uint16, bool) error
 	AddAffinityMatch(uint16, uint16) error
 	DeleteAffinityMatch(uint16, uint16) error
@@ -134,7 +136,8 @@ type Service struct {
 	healthServer  healthServer
 	monitorNotify monitorNotify
 
-	lbmap LBMap
+	lbmap         LBMap
+	lastUpdatedTs atomic.Value
 }
 
 // NewService creates a new instance of the service handler.
@@ -148,7 +151,7 @@ func NewService(monitorNotify monitorNotify) *Service {
 	maglev := option.Config.NodePortAlg == option.NodePortAlgMaglev
 	maglevTableSize := option.Config.MaglevTableSize
 
-	return &Service{
+	svc := &Service{
 		svcByHash:       map[string]*svcInfo{},
 		svcByID:         map[lb.ID]*svcInfo{},
 		backendRefCount: counter.StringCounter{},
@@ -157,6 +160,22 @@ func NewService(monitorNotify monitorNotify) *Service {
 		healthServer:    localHealthServer,
 		lbmap:           lbmap.New(maglev, maglevTableSize),
 	}
+	svc.lastUpdatedTs.Store(time.Now())
+	return svc
+}
+
+func (s *Service) GetLastUpdatedTs() time.Time {
+	if val := s.lastUpdatedTs.Load(); val != nil {
+		ts, ok := val.(time.Time)
+		if ok {
+			return ts
+		}
+	}
+	return time.Now()
+}
+
+func (s *Service) GetCurrentTs() time.Time {
+	return time.Now()
 }
 
 // InitMaps opens or creates BPF maps used by services.
@@ -310,7 +329,6 @@ func (s *Service) UpsertService(params *lb.SVC) (bool, lb.ID, error) {
 
 	s.notifyMonitorServiceUpsert(svc.frontend, svc.backends,
 		svc.svcType, svc.svcTrafficPolicy, svc.svcName, svc.svcNamespace)
-
 	return new, lb.ID(svc.frontend.ID), nil
 }
 
@@ -684,7 +702,6 @@ 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
 		}
@@ -700,7 +717,6 @@ 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 91f26a07eb..b396d2c043 100644
--- a/pkg/u8proto/u8proto.go
+++ b/pkg/u8proto/u8proto.go
@@ -43,7 +43,6 @@ var protoNames = map[U8proto]string{
 var ProtoIDs = map[string]U8proto{
 	"all":    0,
 	"any":    0,
-	"none":   0,
 	"icmp":   1,
 	"tcp":    6,
 	"udp":    17,
@@ -63,13 +62,5 @@ func ParseProtocol(proto string) (U8proto, error) {
 	if u, ok := ProtoIDs[strings.ToLower(proto)]; ok {
 		return u, nil
 	}
-	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
+	return 0, fmt.Errorf("unknown protocol '%s'", proto)
 }


Test patch:
diff --git a/.github/workflows/smoke-test.yaml b/.github/workflows/smoke-test.yaml
index d5189825c9..e8e0db5516 100644
--- a/.github/workflows/smoke-test.yaml
+++ b/.github/workflows/smoke-test.yaml
@@ -38,6 +38,17 @@ jobs:
         run: |
           make -C install/kubernetes lint
 
+  helm-docs:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Run helm-docs
+        run: |
+          make -C install/kubernetes docs
+          git diff --exit-code
+
   quick-install:
     strategy:
       matrix:
diff --git a/daemon/cmd/kube_proxy_healthz_test.go b/daemon/cmd/kube_proxy_healthz_test.go
new file mode 100644
index 0000000000..6cfca0af40
--- /dev/null
+++ b/daemon/cmd/kube_proxy_healthz_test.go
@@ -0,0 +1,110 @@
+// 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 cmd
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"time"
+
+	"github.com/cilium/cilium/api/v1/models"
+
+	. "gopkg.in/check.v1"
+)
+
+// 'check' testing suite scaffolding.
+type KubeProxyHealthzTestSuite struct{}
+
+var _ = Suite(&KubeProxyHealthzTestSuite{})
+
+// Injected fake service.
+type FakeService struct {
+	injectedCurrentTs     time.Time
+	injectedLastUpdatedTs time.Time
+}
+
+func (s *FakeService) GetCurrentTs() time.Time {
+	return s.injectedCurrentTs
+}
+
+func (s *FakeService) GetLastUpdatedTs() time.Time {
+	return s.injectedLastUpdatedTs
+}
+
+// Injected fake daemon.
+type FakeDaemon struct {
+	injectedStatusResponse models.StatusResponse
+}
+
+func (d *FakeDaemon) getStatus(blah bool) models.StatusResponse {
+	return d.injectedStatusResponse
+}
+
+type healthzPayload struct {
+	LastUpdated string
+	CurrentTime string
+}
+
+func (s *KubeProxyHealthzTestSuite) TestKubeProxyHealth(c *C) {
+	s.healthTestHelper(c, models.StatusStateOk, http.StatusOK, true)
+	s.healthTestHelper(c, models.StatusStateWarning,
+		http.StatusServiceUnavailable, false)
+	s.healthTestHelper(c, models.StatusStateFailure,
+		http.StatusServiceUnavailable, false)
+}
+
+func (s *KubeProxyHealthzTestSuite) healthTestHelper(c *C, ciliumStatus string,
+	expectedHttpStatus int, testcasepositive bool) {
+	var lastUpdateTs, currentTs, expectedTs time.Time
+	lastUpdateTs = time.Unix(100, 0) // Fake 100 seconds after Unix.
+	currentTs = time.Unix(200, 0)    // Fake 200 seconds after Unix.
+	expectedTs = lastUpdateTs
+	if testcasepositive {
+		expectedTs = currentTs
+	}
+	// Create handler with injected behavior.
+	h := kubeproxyHealthzHandler{
+		d: &FakeDaemon{injectedStatusResponse: models.StatusResponse{
+			Cilium: &models.Status{State: ciliumStatus}}},
+		svc: &FakeService{
+			injectedCurrentTs:     currentTs,
+			injectedLastUpdatedTs: lastUpdateTs}}
+
+	// Create a new request.
+	req, err := http.NewRequest("GET", "/healthz", nil)
+	c.Assert(err, IsNil)
+	w := httptest.NewRecorder()
+
+	// Serve.
+	h.ServeHTTP(w, req)
+
+	// Main return code meets expectations.
+	c.Assert(w.Code, Equals, expectedHttpStatus,
+		Commentf("expected status code %v, got %v", expectedHttpStatus, w.Code))
+
+	// Timestamps meet expectations.
+	var payload healthzPayload
+	c.Assert(json.Unmarshal(w.Body.Bytes(), &payload), IsNil)
+	layout := "2006-01-02 15:04:05 -0700 MST"
+	lastUpdateTs, err = time.Parse(layout, payload.LastUpdated)
+	c.Assert(err, IsNil)
+
+	_, err = time.Parse(layout, payload.CurrentTime)
+	c.Assert(err, IsNil)
+	c.Assert(lastUpdateTs.Equal(expectedTs), Equals, true)
+}
diff --git a/pkg/azure/api/api_test.go b/pkg/azure/api/api_test.go
index e0914fa4a7..eb3505a19b 100644
--- a/pkg/azure/api/api_test.go
+++ b/pkg/azure/api/api_test.go
@@ -36,7 +36,7 @@ var _ = check.Suite(&ApiSuite{})
 
 func (a *ApiSuite) TestRateLimit(c *check.C) {
 	metricsAPI := mock.NewMockMetrics()
-	client, err := NewClient("", "dummy-subscription", "dummy-resource-group", "", metricsAPI, 10.0, 4)
+	client, err := NewClient("", "dummy-subscription", "dummy-resource-group", "", metricsAPI, 10.0, 4, true)
 	c.Assert(err, check.IsNil)
 	c.Assert(client, check.Not(check.IsNil))
 
diff --git a/pkg/k8s/endpoints_test.go b/pkg/k8s/endpoints_test.go
index 0e3ebdca18..7bc606ef5a 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.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -67,7 +67,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -86,7 +86,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -100,7 +100,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.2": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -118,7 +118,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -132,7 +132,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foz": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -150,7 +150,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -158,70 +158,6 @@ 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{
@@ -246,7 +182,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
@@ -260,11 +196,11 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 								"baz": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     2,
 								},
 							},
@@ -283,7 +219,7 @@ func TestEndpoints_DeepEqual(t *testing.T) {
 						"172.20.0.1": {
 							Ports: map[string]*loadbalancer.L4Addr{
 								"foo": {
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     1,
 								},
 							},
diff --git a/pkg/k8s/service_cache_test.go b/pkg/k8s/service_cache_test.go
index 531fc96ce9..332248dbdd 100644
--- a/pkg/k8s/service_cache_test.go
+++ b/pkg/k8s/service_cache_test.go
@@ -95,6 +95,33 @@ 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 0c59d9078c..6484c5aed9 100644
--- a/pkg/k8s/service_test.go
+++ b/pkg/k8s/service_test.go
@@ -180,6 +180,41 @@ 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
@@ -197,7 +232,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -206,7 +241,7 @@ func TestService_Equals(t *testing.T) {
 						"0.0.0.0:31000": {
 							L3n4Addr: loadbalancer.L3n4Addr{
 								L4Addr: loadbalancer.L4Addr{
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     31000,
 								},
 								IP: net.IPv4(0, 0, 0, 0),
@@ -229,7 +264,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -238,7 +273,7 @@ func TestService_Equals(t *testing.T) {
 							"0.0.0.0:31000": {
 								L3n4Addr: loadbalancer.L3n4Addr{
 									L4Addr: loadbalancer.L4Addr{
-										Protocol: loadbalancer.TCP,
+										Protocol: loadbalancer.NONE,
 										Port:     31000,
 									},
 									IP: net.IPv4(0, 0, 0, 0),
@@ -265,7 +300,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -280,7 +315,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -301,7 +336,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -314,7 +349,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -333,7 +368,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foz"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -346,7 +381,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -363,73 +398,13 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						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"),
@@ -453,7 +428,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -466,11 +441,11 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 						loadbalancer.FEPortName("baz"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     2,
 						},
 					},
@@ -494,7 +469,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -511,7 +486,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: true,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -520,7 +495,7 @@ func TestService_Equals(t *testing.T) {
 						"1.1.1.1:31000": {
 							L3n4Addr: loadbalancer.L3n4Addr{
 								L4Addr: loadbalancer.L4Addr{
-									Protocol: loadbalancer.TCP,
+									Protocol: loadbalancer.NONE,
 									Port:     31000,
 								},
 								IP: net.IPv4(1, 1, 1, 1),
@@ -543,7 +518,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: true,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
@@ -552,7 +527,7 @@ func TestService_Equals(t *testing.T) {
 							"0.0.0.0:31000": {
 								L3n4Addr: loadbalancer.L3n4Addr{
 									L4Addr: loadbalancer.L4Addr{
-										Protocol: loadbalancer.TCP,
+										Protocol: loadbalancer.NONE,
 										Port:     31000,
 									},
 									IP: net.IPv4(0, 0, 0, 0),
@@ -579,7 +554,7 @@ func TestService_Equals(t *testing.T) {
 				IsHeadless: false,
 				Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 					loadbalancer.FEPortName("foo"): {
-						Protocol: loadbalancer.TCP,
+						Protocol: loadbalancer.NONE,
 						Port:     1,
 					},
 				},
@@ -597,7 +572,7 @@ func TestService_Equals(t *testing.T) {
 					IsHeadless: false,
 					Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{
 						loadbalancer.FEPortName("foo"): {
-							Protocol: loadbalancer.TCP,
+							Protocol: loadbalancer.NONE,
 							Port:     1,
 						},
 					},
diff --git a/pkg/loadbalancer/loadbalancer_test.go b/pkg/loadbalancer/loadbalancer_test.go
index 336e316bbf..64a5685e79 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: TCP,
+				Protocol: NONE,
 				Port:     1,
 			},
 			args: args{
 				o: &L4Addr{
-					Protocol: TCP,
+					Protocol: NONE,
 					Port:     1,
 				},
 			},
@@ -59,12 +59,12 @@ func TestL4Addr_Equals(t *testing.T) {
 		{
 			name: "both different",
 			fields: &L4Addr{
-				Protocol: TCP,
+				Protocol: NONE,
 				Port:     0,
 			},
 			args: args{
 				o: &L4Addr{
-					Protocol: TCP,
+					Protocol: NONE,
 					Port:     1,
 				},
 			},
@@ -78,40 +78,12 @@ func TestL4Addr_Equals(t *testing.T) {
 		{
 			name: "other nil",
 			fields: &L4Addr{
-				Protocol: TCP,
+				Protocol: NONE,
 				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) {
@@ -138,7 +110,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: TCP,
+						Protocol: NONE,
 						Port:     1,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -149,7 +121,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: TCP,
+							Protocol: NONE,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -164,7 +136,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: TCP,
+						Protocol: NONE,
 						Port:     1,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -175,7 +147,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: TCP,
+							Protocol: NONE,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -190,7 +162,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: TCP,
+						Protocol: NONE,
 						Port:     1,
 					},
 					IP: net.IPv4(2, 2, 2, 2),
@@ -201,7 +173,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: TCP,
+							Protocol: NONE,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -216,7 +188,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 			fields: &L3n4AddrID{
 				L3n4Addr: L3n4Addr{
 					L4Addr: L4Addr{
-						Protocol: TCP,
+						Protocol: NONE,
 						Port:     2,
 					},
 					IP: net.IPv4(1, 1, 1, 1),
@@ -227,7 +199,7 @@ func TestL3n4AddrID_Equals(t *testing.T) {
 				o: &L3n4AddrID{
 					L3n4Addr: L3n4Addr{
 						L4Addr: L4Addr{
-							Protocol: TCP,
+							Protocol: NONE,
 							Port:     1,
 						},
 						IP: net.IPv4(1, 1, 1, 1),
@@ -237,32 +209,6 @@ 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 662caa345a..d5091772a0 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/redirectpolicy/manager_test.go b/pkg/redirectpolicy/manager_test.go
index 0b686e1876..684e45e826 100644
--- a/pkg/redirectpolicy/manager_test.go
+++ b/pkg/redirectpolicy/manager_test.go
@@ -99,11 +99,11 @@ func (ps *fakePodStore) Resync() error {
 }
 
 var (
-	tcpStr = "TCP"
-	udpStr = "UDP"
-	proto1 = lb.NewL4Type(tcpStr)
-	proto2 = lb.NewL4Type(udpStr)
-	fe1    = lb.NewL3n4Addr(
+	tcpStr    = "TCP"
+	udpStr    = "UDP"
+	proto1, _ = lb.NewL4Type(tcpStr)
+	proto2, _ = lb.NewL4Type(udpStr)
+	fe1       = lb.NewL3n4Addr(
 		proto1,
 		net.ParseIP("1.1.1.1"),
 		80,
diff --git a/pkg/service/id_test.go b/pkg/service/id_test.go
index 65cda1fca1..f6bc26d2d2 100644
--- a/pkg/service/id_test.go
+++ b/pkg/service/id_test.go
@@ -87,10 +87,11 @@ func (s *IDAllocTestSuite) TestServices(c *C) {
 	c.Assert(err, Equals, nil)
 	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+1))
 
-	// different protocols should change the id
+	// l3n4Addr3 should have the same ID as l3n4Addr2 since we are omitting the
+	// protocol type.
 	l3n4AddrID, err = AcquireID(l3n4Addr3, 0)
 	c.Assert(err, Equals, nil)
-	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+2))
+	c.Assert(l3n4AddrID.ID, Equals, loadbalancer.ID(ffsIDu16+1))
 
 	gotL3n4AddrID, err := GetID(FirstFreeServiceID)
 	c.Assert(err, Equals, nil)
diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go
index e004d0770d..bce716536f 100644
--- a/pkg/service/service_test.go
+++ b/pkg/service/service_test.go
@@ -21,7 +21,6 @@ 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"
 	nodeTypes "github.com/cilium/cilium/pkg/node/types"
 	"github.com/cilium/cilium/pkg/option"
@@ -69,7 +68,6 @@ 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),
@@ -78,10 +76,6 @@ 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) {
@@ -176,50 +170,9 @@ 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)
diff --git a/pkg/service/store/store_test.go b/pkg/service/store/store_test.go
index bff6bb8db0..fdfb0ee470 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.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			want: true,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			b: PortConfiguration{
-				"foz": {Protocol: loadbalancer.TCP, Port: 1},
+				"foz": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 2},
+				"foo": {Protocol: loadbalancer.NONE, Port: 2},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
-				"baz": {Protocol: loadbalancer.TCP, Port: 2},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
+				"baz": {Protocol: loadbalancer.NONE, Port: 2},
 			},
 			want: false,
 		},
 		{
 			a: PortConfiguration{},
 			b: PortConfiguration{
-				"foo": {Protocol: loadbalancer.TCP, Port: 1},
+				"foo": {Protocol: loadbalancer.NONE, Port: 1},
 			},
 			want: false,
 		},
diff --git a/pkg/testutils/mockmaps/lbmap.go b/pkg/testutils/mockmaps/lbmap.go
index 84082f0d07..4eef66e1b5 100644
--- a/pkg/testutils/mockmaps/lbmap.go
+++ b/pkg/testutils/mockmaps/lbmap.go
@@ -19,7 +19,6 @@ import (
 	"net"
 
 	"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"
 )
@@ -52,7 +51,7 @@ func (m *LBMockMap) UpsertService(p *lbmap.UpsertServiceParams) error {
 
 	svc, found := m.ServiceByID[p.ID]
 	if !found {
-		frontend := lb.NewL3n4AddrID(loadbalancer.NewL4Type(p.Protocol), p.IP, p.Port, p.Scope, lb.ID(p.ID))
+		frontend := lb.NewL3n4AddrID(lb.NONE, p.IP, p.Port, p.Scope, lb.ID(p.ID))
 		svc = &lb.SVC{Frontend: *frontend}
 	} else {
 		if p.PrevBackendCount != len(svc.Backends) {
@@ -83,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, protocol lb.L4Type, port uint16, ipv6 bool) error {
+func (m *LBMockMap) AddBackend(id uint16, ip net.IP, 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), protocol, ip, port)
+	m.BackendByID[id] = lb.NewBackend(lb.BackendID(id), lb.NONE, ip, port)
 
 	return nil
 }
diff --git a/test/helpers/cilium.go b/test/helpers/cilium.go
index 307fa748b2..a0aa05c114 100644
--- a/test/helpers/cilium.go
+++ b/test/helpers/cilium.go
@@ -799,12 +799,9 @@ func (s *SSHMeta) ServiceIsSynced(id int) (bool, error) {
 		return false, err
 	}
 
-	frontendAddr := serviceAddressKey(
+	frontendAddr := net.JoinHostPort(
 		svc.Status.Realized.FrontendAddress.IP,
-		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port),
-		svc.Status.Realized.FrontendAddress.Protocol,
-		"",
-	)
+		fmt.Sprintf("%d", svc.Status.Realized.FrontendAddress.Port))
 	lb, ok := bpfLB[frontendAddr]
 	if ok == false {
 		return false, fmt.Errorf(
diff --git a/test/helpers/cons.go b/test/helpers/cons.go
index fd1663c436..d10911de56 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"
-	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
+	"github.com/cilium/cilium/test/ginkgo-ext"
 )
 
 var (
diff --git a/test/helpers/kubectl.go b/test/helpers/kubectl.go
index 27ab84b440..b661c8ddf8 100644
--- a/test/helpers/kubectl.go
+++ b/test/helpers/kubectl.go
@@ -1727,7 +1727,7 @@ func (kub *Kubectl) validateServicePlumbingInCiliumPod(fullName, ciliumPod strin
 	for _, port := range serviceObj.Spec.Ports {
 		var foundPort *v1.ServicePort
 		for _, realizedService := range realizedServices {
-			if compareServicePortToFrontEnd(&port, realizedService.FrontendAddress) {
+			if port.Port == int32(realizedService.FrontendAddress.Port) {
 				foundPort = &port
 				break
 			}
@@ -1736,8 +1736,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)
 		}
-		lKey := serviceAddressKey(serviceObj.Spec.ClusterIP, fmt.Sprintf("%d", port.Port), string(port.Protocol), "")
-		if _, ok := lbMap[lKey]; !ok {
+
+		if _, ok := lbMap[net.JoinHostPort(serviceObj.Spec.ClusterIP, fmt.Sprintf("%d", port.Port))]; !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)
 		}
@@ -1749,11 +1749,10 @@ func (kub *Kubectl) validateServicePlumbingInCiliumPod(fullName, ciliumPod strin
 				foundBackend, foundBackendLB := false, false
 				for _, realizedService := range realizedServices {
 					frontEnd := realizedService.FrontendAddress
-					lbKey := serviceAddressKey(frontEnd.IP, fmt.Sprintf("%d", frontEnd.Port), string(frontEnd.Protocol), "")
-					lb := lbMap[lbKey]
+					lb := lbMap[net.JoinHostPort(frontEnd.IP, fmt.Sprintf("%d", frontEnd.Port))]
+
 					for _, backAddr := range realizedService.BackendAddresses {
-						if addr.IP == *backAddr.IP && uint16(port.Port) == backAddr.Port &&
-							compareProto(string(port.Protocol), backAddr.Protocol) {
+						if addr.IP == *backAddr.IP && uint16(port.Port) == backAddr.Port {
 							foundBackend = true
 							for _, backend := range lb {
 								if strings.Contains(backend, net.JoinHostPort(*backAddr.IP, fmt.Sprintf("%d", port.Port))) {
@@ -3851,7 +3850,7 @@ CILIUM_SERVICES:
 	for _, cSvc := range ciliumSvcs {
 		if cSvc.Status.Realized.FrontendAddress.IP == k8sService.Spec.ClusterIP {
 			for _, port := range k8sService.Spec.Ports {
-				if compareServicePortToFrontEnd(&port, cSvc.Status.Realized.FrontendAddress) {
+				if int32(cSvc.Status.Realized.FrontendAddress.Port) == port.Port {
 					ciliumService = &cSvc
 					break CILIUM_SERVICES
 				}
@@ -3872,11 +3871,7 @@ CILIUM_SERVICES:
 }
 
 // CiliumServiceAdd adds the given service on a 'pod' running Cilium
-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"
-	}
+func (kub *Kubectl) CiliumServiceAdd(pod string, id int64, frontend string, backends []string, svcType, trafficPolicy string) error {
 	var opts []string
 	switch strings.ToLower(svcType) {
 	case "nodeport":
@@ -3903,8 +3898,8 @@ func (kub *Kubectl) CiliumServiceAdd(pod string, id int64, protocol string, fron
 	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 --protocol %q --frontend %q --backends %q %s",
-		id, protocol, frontend, backendsStr, optsStr)).GetErr("cilium service update")
+	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")
 }
 
 // CiliumServiceDel deletes the service with 'id' on a 'pod' running Cilium
@@ -4128,7 +4123,7 @@ func validateCiliumSvc(cSvc models.Service, k8sSvcs []v1.Service, k8sEps []v1.En
 
 	var k8sServicePort *v1.ServicePort
 	for _, k8sPort := range k8sService.Spec.Ports {
-		if compareServicePortToFrontEnd(&k8sPort, cSvc.Status.Realized.FrontendAddress) {
+		if k8sPort.Port == int32(cSvc.Status.Realized.FrontendAddress.Port) {
 			k8sServicePort = &k8sPort
 			k8sServicesFound[serviceKey(*k8sService)] = true
 			break
@@ -4157,16 +4152,14 @@ func validateCiliumSvc(cSvc models.Service, k8sSvcs []v1.Service, k8sEps []v1.En
 }
 
 func validateCiliumSvcLB(cSvc models.Service, lbMap map[string][]string) error {
-	var scope string
+	scope := ""
 	if cSvc.Status.Realized.FrontendAddress.Scope == models.FrontendAddressScopeInternal {
 		scope = "/i"
 	}
-	frontendAddress := serviceAddressKey(
+
+	frontendAddress := net.JoinHostPort(
 		cSvc.Status.Realized.FrontendAddress.IP,
-		strconv.Itoa(int(cSvc.Status.Realized.FrontendAddress.Port)),
-		cSvc.Status.Realized.FrontendAddress.Protocol,
-		scope,
-	)
+		strconv.Itoa(int(cSvc.Status.Realized.FrontendAddress.Port))) + scope
 	bpfBackends, ok := lbMap[frontendAddress]
 	if !ok {
 		return fmt.Errorf("%s bpf lb map entry not found", frontendAddress)
@@ -4203,9 +4196,7 @@ func getK8sEndpointAddresses(ep v1.Endpoints) []*models.BackendAddress {
 }
 
 func addrsEqual(addr1, addr2 *models.BackendAddress) bool {
-	return *addr1.IP == *addr2.IP &&
-		addr1.Port == addr2.Port &&
-		compareProto(addr1.Protocol, addr2.Protocol)
+	return *addr1.IP == *addr2.IP && addr1.Port == addr2.Port
 }
 
 // GenerateNamespaceForTest generates a namespace based off of the current test
@@ -4338,36 +4329,3 @@ 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 7d7ec4aee3..eb5c24337f 100644
--- a/test/helpers/utils.go
+++ b/test/helpers/utils.go
@@ -39,11 +39,8 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-var (
-	// ensure that our random numbers are seeded differently on each run
-	randGen              = rand.NewSafeRand(time.Now().UnixNano())
-	runningCiliumVersion string
-)
+// ensure that our random numbers are seeded differently on each run
+var randGen = rand.NewSafeRand(time.Now().UnixNano())
 
 // IsRunningOnJenkins detects if the currently running Ginkgo application is
 // most likely running in a Jenkins environment. Returns true if certain
@@ -63,35 +60,6 @@ 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)
@@ -250,7 +218,6 @@ 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/Services.go b/test/k8sT/Services.go
index ddb6584248..98ad51005a 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, protocol string, frontend string, backends []string, svcType, trafficPolicy string) {
+	ciliumAddService := func(id int64, 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, protocol, frontend, backends, svcType, trafficPolicy)
+			err := kubectl.CiliumServiceAdd(pod, id, frontend, backends, svcType, trafficPolicy)
 			ExpectWithOffset(1, err).To(BeNil(), "Failed to add cilium service")
 		}
 	}
 
-	ciliumAddServiceOnNode := func(node string, id int64, protocol string, frontend string, backends []string, svcType, trafficPolicy string) {
+	ciliumAddServiceOnNode := func(node string, id int64, 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, protocol, frontend, backends, svcType, trafficPolicy)
+		err = kubectl.CiliumServiceAdd(ciliumPod, id, 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, "tcp", net.JoinHostPort(demoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(10080, net.JoinHostPort(demoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends := ciliumIPv6Backends("-l k8s:id=app1,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(10069, "udp", net.JoinHostPort(demoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(10069, 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, "tcp", net.JoinHostPort(echoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(20080, net.JoinHostPort(echoClusterIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends = ciliumIPv6Backends("-l k8s:name=echo,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(20069, "udp", net.JoinHostPort(echoClusterIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(20069, 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]/hell"`, demoClusterIPv6))
+					helpers.CurlFail(`"tftp://[%s]/hello"`, demoClusterIPv6))
 				status.ExpectSuccess("cannot curl to service IP from host")
 			})
 
@@ -610,9 +610,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, "tcp", net.JoinHostPort(testDSIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(31080, net.JoinHostPort(testDSIPv6, "80"), httpBackends, "ClusterIP", "Cluster")
 				tftpBackends := ciliumIPv6Backends("-l k8s:zgroup=testDS,k8s:io.kubernetes.pod.namespace=default", "69")
-				ciliumAddService(31069, "udp", net.JoinHostPort(testDSIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
+				ciliumAddService(31069, net.JoinHostPort(testDSIPv6, "69"), tftpBackends, "ClusterIP", "Cluster")
 			})
 
 			AfterAll(func() {
@@ -1515,21 +1515,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, 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")
+				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")
 				// Add service corresponding to IPv6 address of the nodes so that they become
 				// reachable from outside the cluster.
-				ciliumAddServiceOnNode(helpers.K8s1, 31082, string(data.Spec.Ports[0].Protocol), net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s1, 31082, net.JoinHostPort(k8s1IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
 					httpBackends, "NodePort", "Cluster")
-				ciliumAddServiceOnNode(helpers.K8s2, 31082, string(data.Spec.Ports[0].Protocol), net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[0].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s2, 31082, 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, 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)),
+				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)),
 					tftpBackends, "NodePort", "Cluster")
-				ciliumAddServiceOnNode(helpers.K8s2, 31071, string(data.Spec.Ports[1].Protocol), net.JoinHostPort(k8s2IPv6, fmt.Sprintf("%d", data.Spec.Ports[1].NodePort)),
+				ciliumAddServiceOnNode(helpers.K8s2, 31071, 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 edeaa2bc06..a588a28d05 100644
--- a/test/k8sT/Updates.go
+++ b/test/k8sT/Updates.go
@@ -143,8 +143,6 @@ 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())
@@ -163,6 +161,9 @@ 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,8 +291,6 @@ 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.
@@ -371,7 +370,7 @@ func InstallAndValidateCiliumUpgrades(kubectl *helpers.Kubectl, oldHelmChartVers
 			if lastCount == -1 {
 				lastCount = currentCount
 			}
-			ExpectWithOffset(1, lastCount).Should(BeIdenticalTo(currentCount),
+			Expect(lastCount).Should(BeIdenticalTo(currentCount),
 				"migrate-svc restart count values do not match")
 		}
 
@@ -381,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)
-		ExpectWithOffset(1, err).Should(BeNil(), "Test pods are not ready after timeout")
+		Expect(err).Should(BeNil(), "Test pods are not ready after timeout")
 
 		_, err = kubectl.CiliumPolicyAction(
 			helpers.DefaultNamespace, l7Policy, helpers.KubectlApply, timeout)
-		ExpectWithOffset(1, err).Should(BeNil(), "cannot import l7 policy: %v", l7Policy)
+		Expect(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)
-		ExpectWithOffset(1, err).Should(BeNil(), "migrate-svc-server pods are not ready after timeout")
+		Expect(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)
-		ExpectWithOffset(1, err).Should(BeNil(), "migrate-svc-client pods are not ready after timeout")
+		Expect(err).Should(BeNil(), "migrate-svc-client pods are not ready after timeout")
 
 		validateEndpointsConnection()
 		checkNoInteruptsInSVCFlows()
@@ -502,7 +501,6 @@ 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(
@@ -537,7 +535,6 @@ 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)