Repository: kmesh-net/kmesh
Pull request #1135 (2024-12-21 13:03:30)

refine destination service metrics generation
**What type of PR is this?**

<!--
Add one of the following kinds:

/kind enhancement

-->

**What this PR does / why we need it**:
Currently kmesh get destination service info by looking up from the services of a workload, which may not properly handle circumstances below:
1. multiple services with same workload backend, for example, `example-a.com` and `example-b.com` share the same workload backend, and the ports of them are exactly the same, currently kmesh can only guess the destination service since currently only workload info is passed to the metric controller.
2. for requests redirected to the waypoint, the metrics will be like:
```shell
kmesh_tcp_connections_closed_total{destination_app="reviews-svc-waypoint",destination_service_name="reviews-svc-waypoint"...} 14
```
both `destination_app` and `destination_service` will be the waypoint, and we can not know where the intended destination of the request is, which may be a little bit confusing.

To solve the issues mentioned before, I passed the original destination info to the metric controller so that we can know where the intended destination of the request is instead of the actual destination of the connection.

By doing this, we do not need to guess the destination service when we send request to a service, and the waypoint metric will be like:
```shell
# destination_service_name is `reviews` instead of `reviews-svc-waypoint`
kmesh_tcp_connections_closed_total{destination_app="reviews-svc-waypoint",destination_service_name="reviews"...} 14
```
from where we can know what the intended service is.

**Which issue(s) this PR fixes**:

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required.
-->
```release-note

```


kmesh-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign hzxuzhonghu for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kmesh-net%2Fkmesh).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kmesh-net/kmesh/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["hzxuzhonghu"]} -->
Kuromesi: cc @delavet
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) Report
Attention: Patch coverage is `68.75000%` with `20 lines` in your changes missing coverage. Please review.
> Project coverage is 51.55%. Comparing base [(`4c771a6`)](https://app.codecov.io/gh/kmesh-net/kmesh/commit/4c771a6303eaab839a6f3862bb6aef920ab279e8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) to head [(`8a4d686`)](https://app.codecov.io/gh/kmesh-net/kmesh/commit/8a4d6869379f5cb9ea9fc72a5030c1fd4522e6fb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net).
> Report is 6 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) | Patch % | Lines |
|---|---|---|
| [pkg/controller/telemetry/metric.go](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&filepath=pkg%2Fcontroller%2Ftelemetry%2Fmetric.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net#diff-cGtnL2NvbnRyb2xsZXIvdGVsZW1ldHJ5L21ldHJpYy5nbw==) | 79.62% | [10 Missing and 1 partial :warning: ](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) |
| [pkg/controller/workload/cache/service\_cache.go](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&filepath=pkg%2Fcontroller%2Fworkload%2Fcache%2Fservice_cache.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net#diff-cGtnL2NvbnRyb2xsZXIvd29ya2xvYWQvY2FjaGUvc2VydmljZV9jYWNoZS5nbw==) | 10.00% | [9 Missing :warning: ](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) |

| [Files with missing lines](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net) | Coverage Δ | |
|---|---|---|
| [pkg/controller/workload/cache/service\_cache.go](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&filepath=pkg%2Fcontroller%2Fworkload%2Fcache%2Fservice_cache.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net#diff-cGtnL2NvbnRyb2xsZXIvd29ya2xvYWQvY2FjaGUvc2VydmljZV9jYWNoZS5nbw==) | `25.00% <10.00%> (-5.77%)` | :arrow_down: |
| [pkg/controller/telemetry/metric.go](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?src=pr&el=tree&filepath=pkg%2Fcontroller%2Ftelemetry%2Fmetric.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net#diff-cGtnL2NvbnRyb2xsZXIvdGVsZW1ldHJ5L21ldHJpYy5nbw==) | `53.08% <79.62%> (+0.78%)` | :arrow_up: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net)

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net). Last update [4aac91e...8a4d686](https://app.codecov.io/gh/kmesh-net/kmesh/pull/1135?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kmesh-net).

Kuromesi: I'm not quite understand why this test failed, could you please take a look and maybe provide some information for me about this test to check? @LiZhenCheng9527
```shell
--- FAIL: TestServerSideLB (122.55s)
    --- FAIL: TestServerSideLB/from_service-with-waypoint-at-service-granularity (61.27s)
        --- FAIL: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_service-with-waypoint-at-service-granularity (61.27s)
            --- FAIL: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_service-with-waypoint-at-service-granularity/http (61.27s)
            --- PASS: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_service-with-waypoint-at-service-granularity/tcp (0.00s)
        --- PASS: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_enrolled-to-kmesh (0.00s)
            --- PASS: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_enrolled-to-kmesh/http (0.00s)
            --- PASS: TestServerSideLB/from_service-with-waypoint-at-service-granularity/to_enrolled-to-kmesh/tcp (0.00s)
    --- FAIL: TestServerSideLB/from_enrolled-to-kmesh (61.27s)
        --- FAIL: TestServerSideLB/from_enrolled-to-kmesh/to_service-with-waypoint-at-service-granularity (61.27s)
            --- FAIL: TestServerSideLB/from_enrolled-to-kmesh/to_service-with-waypoint-at-service-granularity/http (61.27s)
            --- PASS: TestServerSideLB/from_enrolled-to-kmesh/to_service-with-waypoint-at-service-granularity/tcp (0.00s)
        --- PASS: TestServerSideLB/from_enrolled-to-kmesh/to_enrolled-to-kmesh (0.00s)
            --- PASS: TestServerSideLB/from_enrolled-to-kmesh/to_enrolled-to-kmesh/http (0.00s)
            --- PASS: TestServerSideLB/from_enrolled-to-kmesh/to_enrolled-to-kmesh/tcp (0.00s)
```
LiZhenCheng9527: /retest
Kuromesi: > /retest

I have tested it on my local machine and it fails every time. :(
Kuromesi: I think e2e failures may related to tcp connection reuse, which I have not considered, I will try to fix this.
LiZhenCheng9527: This case uses the `http2` test waypoint load balance, which is related to http reuse.

Kuromesi: > This case uses the `http2` test waypoint load balance, which is related to http reuse.

Got it, thanks! 😊

Patch:
diff --git a/bpf/include/errno.h b/bpf/include/errno.h
index 4d1b167a..88e266b5 100644
--- a/bpf/include/errno.h
+++ b/bpf/include/errno.h
@@ -20,6 +20,10 @@
 #define EBUSY 16 /* Device or resource busy */
 #endif
 
+#ifndef EEXIST
+#define EEXIST 17 /* Device or resource busy */
+#endif
+
 #ifndef EINVAL
 #define EINVAL 22 /* Invalid argument */
 #endif
diff --git a/bpf/kmesh/probes/tcp_probe.h b/bpf/kmesh/probes/tcp_probe.h
index d23a963b..f4eca4e9 100644
--- a/bpf/kmesh/probes/tcp_probe.h
+++ b/bpf/kmesh/probes/tcp_probe.h
@@ -18,16 +18,30 @@ enum family_type {
     IPV6,
 };
 
+struct orig_dst_info {
+    union {
+        struct {
+            __be32 addr;
+            __be16 port;
+        } ipv4;
+        struct {
+            __be32 addr[4];
+            __be16 port;
+        } ipv6;
+    };
+};
+
 struct tcp_probe_info {
     __u32 type;
     struct bpf_sock_tuple tuple;
+    struct orig_dst_info orig_dst;
     __u32 sent_bytes;
     __u32 received_bytes;
     __u32 conn_success;
     __u32 direction;
+    __u32 state;    /* tcp state */
     __u64 duration; // ns
     __u64 close_ns;
-    __u32 state; /* tcp state */
     __u32 protocol;
     __u32 srtt_us; /* smoothed round trip time << 3 in usecs */
     __u32 rtt_min;
@@ -102,6 +116,33 @@ static inline void get_tcp_probe_info(struct bpf_tcp_sock *tcp_sock, struct tcp_
     return;
 }
 
+// construct_orig_dst_info try to read the dst_info from map_of_orig_dst first
+// if not found, use the tuple info for orig_dst
+static inline void construct_orig_dst_info(struct bpf_sock *sk, struct tcp_probe_info *info)
+{
+    __u64 *current_sk = (__u64 *)sk;
+    struct bpf_sock_tuple *dst;
+    dst = bpf_map_lookup_elem(&map_of_orig_dst, &current_sk);
+
+    // when dst not found, metric controller will read orig dst from actual dst
+    if (!dst) {
+        return;
+    }
+
+    if (sk->family == AF_INET) {
+        info->orig_dst.ipv4.addr = dst->ipv4.daddr;
+        info->orig_dst.ipv4.port = bpf_ntohs(dst->ipv4.dport);
+    } else {
+        bpf_memcpy(info->orig_dst.ipv6.addr, dst->ipv6.daddr, IPV6_ADDR_LEN);
+        info->orig_dst.ipv6.port = bpf_ntohs(dst->ipv6.dport);
+    }
+
+    if (is_ipv4_mapped_addr(info->orig_dst.ipv6.addr)) {
+        info->orig_dst.ipv4.addr = info->orig_dst.ipv6.addr[3];
+        info->orig_dst.ipv4.port = info->orig_dst.ipv6.port;
+    }
+}
+
 static inline void
 tcp_report(struct bpf_sock *sk, struct bpf_tcp_sock *tcp_sock, struct sock_storage_data *storage, __u32 state)
 {
@@ -129,6 +170,7 @@ tcp_report(struct bpf_sock *sk, struct bpf_tcp_sock *tcp_sock, struct sock_stora
         (*info).type = IPV4;
     }
 
+    construct_orig_dst_info(sk, info);
     bpf_ringbuf_submit(info, 0);
 }
 
diff --git a/bpf/kmesh/workload/cgroup_sock.c b/bpf/kmesh/workload/cgroup_sock.c
index d3c6ea41..5033d0a1 100644
--- a/bpf/kmesh/workload/cgroup_sock.c
+++ b/bpf/kmesh/workload/cgroup_sock.c
@@ -59,6 +59,38 @@ static inline int sock_traffic_control(struct kmesh_context *kmesh_ctx)
     return 0;
 }
 
+static inline int set_original_dst_info(struct kmesh_context *kmesh_ctx)
+{
+    int ret;
+    struct bpf_sock_tuple sk_tuple = {0};
+    ctx_buff_t *ctx = (ctx_buff_t *)kmesh_ctx->ctx;
+    __u64 *sk = (__u64 *)ctx->sk;
+
+    if (kmesh_ctx->via_waypoint) {
+        // since this field is never used, we use it
+        // to indicate whether the request will be handled by waypoint
+        sk_tuple.ipv4.saddr = 1;
+    }
+
+    if (ctx->family == AF_INET) {
+        sk_tuple.ipv4.daddr = kmesh_ctx->orig_dst_addr.ip4;
+        sk_tuple.ipv4.dport = ctx->user_port;
+    } else if (ctx->family == AF_INET6) {
+        bpf_memcpy(sk_tuple.ipv6.daddr, kmesh_ctx->orig_dst_addr.ip6, IPV6_ADDR_LEN);
+        sk_tuple.ipv6.dport = ctx->user_port;
+    }
+
+    ret = bpf_map_update_elem(&map_of_orig_dst, &(sk), &sk_tuple, BPF_NOEXIST);
+    if (ret) {
+        // only record the first dst info for each socket
+        if (ret == -EEXIST)
+            return 0;
+        BPF_LOG(ERR, BACKEND, "record original dst address failed: %d\n", ret);
+        return ret;
+    }
+    return 0;
+}
+
 SEC("cgroup/connect4")
 int cgroup_connect4_prog(struct bpf_sock_addr *ctx)
 {
@@ -76,6 +108,11 @@ int cgroup_connect4_prog(struct bpf_sock_addr *ctx)
         BPF_LOG(ERR, KMESH, "sock_traffic_control failed: %d\n", ret);
         return CGROUP_SOCK_OK;
     }
+    ret = set_original_dst_info(&kmesh_ctx);
+    if (ret) {
+        BPF_LOG(ERR, KMESH, "[IPv4]failed to set original destination info, ret is %d\n", ret);
+        return CGROUP_SOCK_OK;
+    }
 
     SET_CTX_ADDRESS4(ctx, &kmesh_ctx.dnat_ip, kmesh_ctx.dnat_port);
     if (kmesh_ctx.via_waypoint) {
@@ -110,6 +147,12 @@ int cgroup_connect6_prog(struct bpf_sock_addr *ctx)
         return CGROUP_SOCK_OK;
     }
 
+    ret = set_original_dst_info(&kmesh_ctx);
+    if (ret) {
+        BPF_LOG(ERR, KMESH, "[IPv6]failed to set original destination info, ret is %d\n", ret);
+        return CGROUP_SOCK_OK;
+    }
+
     if (is_ipv4_mapped_addr(ctx->user_ip6) && !is_ipv4_mapped_addr(kmesh_ctx.dnat_ip.ip6))
         V4_MAPPED_TO_V6(kmesh_ctx.dnat_ip.ip4, kmesh_ctx.dnat_ip.ip6);
     SET_CTX_ADDRESS6(ctx, &kmesh_ctx.dnat_ip, kmesh_ctx.dnat_port);
diff --git a/bpf/kmesh/workload/include/backend.h b/bpf/kmesh/workload/include/backend.h
index cca725a7..bf733ac7 100644
--- a/bpf/kmesh/workload/include/backend.h
+++ b/bpf/kmesh/workload/include/backend.h
@@ -15,25 +15,7 @@ static inline backend_value *map_lookup_backend(const backend_key *key)
 
 static inline int waypoint_manager(struct kmesh_context *kmesh_ctx, struct ip_addr *wp_addr, __u32 port)
 {
-    int ret;
-    address_t target_addr;
     ctx_buff_t *ctx = (ctx_buff_t *)kmesh_ctx->ctx;
-    __u64 *sk = (__u64 *)ctx->sk;
-    struct bpf_sock_tuple sk_tuple = {0};
-
-    if (ctx->family == AF_INET) {
-        sk_tuple.ipv4.daddr = kmesh_ctx->orig_dst_addr.ip4;
-        sk_tuple.ipv4.dport = ctx->user_port;
-    } else if (ctx->family == AF_INET6) {
-        bpf_memcpy(sk_tuple.ipv6.daddr, kmesh_ctx->orig_dst_addr.ip6, IPV6_ADDR_LEN);
-        sk_tuple.ipv6.dport = ctx->user_port;
-    }
-
-    ret = bpf_map_update_elem(&map_of_orig_dst, &(sk), &sk_tuple, BPF_NOEXIST);
-    if (ret) {
-        BPF_LOG(ERR, BACKEND, "record original dst address failed: %d\n", ret);
-        return ret;
-    }
 
     if (ctx->user_family == AF_INET)
         kmesh_ctx->dnat_ip.ip4 = wp_addr->ip4;
diff --git a/bpf/kmesh/workload/include/frontend.h b/bpf/kmesh/workload/include/frontend.h
index 8bd04b50..8bb9a21f 100644
--- a/bpf/kmesh/workload/include/frontend.h
+++ b/bpf/kmesh/workload/include/frontend.h
@@ -35,7 +35,7 @@ static inline int frontend_manager(struct kmesh_context *kmesh_ctx, frontend_val
     }
 
     if (direct_backend) {
-        // For pod direct access, if a pod has watpoint captured, we will redirect to waypoint, otherwise we do nothing.
+        // For pod direct access, if a pod has waypoint captured, we will redirect to waypoint, otherwise we do nothing.
         if (backend_v->waypoint_port != 0) {
             BPF_LOG(
                 DEBUG,
diff --git a/bpf/kmesh/workload/sendmsg.c b/bpf/kmesh/workload/sendmsg.c
index eff6d9b8..edaab9bf 100644
--- a/bpf/kmesh/workload/sendmsg.c
+++ b/bpf/kmesh/workload/sendmsg.c
@@ -85,7 +85,11 @@ static inline int get_origin_dst(struct sk_msg_md *msg, struct ip_addr *dst_ip,
     struct bpf_sock_tuple *dst;
 
     dst = bpf_map_lookup_elem(&map_of_orig_dst, &current_sk);
-    if (!dst)
+
+    // !dst->ipv4.saddr indicates this is not from waypoint
+    // dst->ipv4.sport indicates this connection is already encoded
+    // for circumstances above, we just return
+    if (!dst || !dst->ipv4.saddr || dst->ipv4.sport)
         return -ENOENT;
 
     if (msg->family == AF_INET) {
@@ -96,7 +100,12 @@ static inline int get_origin_dst(struct sk_msg_md *msg, struct ip_addr *dst_ip,
         *dst_port = dst->ipv6.dport;
     }
 
-    bpf_map_delete_elem(&map_of_orig_dst, &current_sk);
+    // since this field is never used, we use it to indicate whether the connection is already encoded
+    dst->ipv4.sport = 1;
+    int ret = bpf_map_update_elem(&map_of_orig_dst, &current_sk, dst, BPF_EXIST);
+    if (ret)
+        BPF_LOG(ERR, SENDMSG, "update dst info failed, ret: %d\n", ret);
+
     return 0;
 }
 
diff --git a/pkg/controller/telemetry/metric.go b/pkg/controller/telemetry/metric.go
index fa922535..860e1b43 100644
--- a/pkg/controller/telemetry/metric.go
+++ b/pkg/controller/telemetry/metric.go
@@ -42,8 +42,6 @@ const (
 
 	connection_success = uint32(1)
 
-	MSG_LEN = 112
-
 	metricFlushInterval = 5 * time.Second
 
 	DEFAULT_UNKNOWN = "-"
@@ -78,33 +76,49 @@ type serviceMetricInfo struct {
 	ServiceConnFailed        float64
 }
 
-type connectionDataV4 struct {
-	SrcAddr        uint32
-	DstAddr        uint32
-	SrcPort        uint16
-	DstPort        uint16
-	RedundantData  [6]uint32
+type statistics struct {
 	SentBytes      uint32
 	ReceivedBytes  uint32
 	ConnectSuccess uint32
 	Direction      uint32
+	State          uint32
 	Duration       uint64
 	CloseTime      uint64
-	State          uint32
+	// TODO: statistics below are not used for now
+	Protocol    uint32
+	SRttTime    uint32
+	RttMin      uint32
+	MssCache    uint32
+	Retransmits uint32
+	SegmentsIn  uint32
+	SegmentsOut uint32
+	LostPackets uint32
 }
 
+// connectionDataV4 read from ebpf ringbuf and padding with `_`
+type connectionDataV4 struct {
+	SrcAddr      uint32
+	DstAddr      uint32
+	SrcPort      uint16
+	DstPort      uint16
+	_            [6]uint32
+	OriginalAddr uint32
+	OriginalPort uint16
+	_            uint16
+	_            [3]uint32
+	statistics
+}
+
+// connectionDataV6 read from ebpf ringbuf and padding with `_`
 type connectionDataV6 struct {
-	SrcAddr        [4]uint32
-	DstAddr        [4]uint32
-	SrcPort        uint16
-	DstPort        uint16
-	SentBytes      uint32
-	ReceivedBytes  uint32
-	ConnectSuccess uint32
-	Direction      uint32
-	Duration       uint64
-	CloseTime      uint64
-	State          uint32
+	SrcAddr      [4]uint32
+	DstAddr      [4]uint32
+	SrcPort      uint16
+	DstPort      uint16
+	OriginalAddr [4]uint32
+	OriginalPort uint16
+	_            uint16
+	statistics
 }
 
 type requestMetric struct {
@@ -112,6 +126,8 @@ type requestMetric struct {
 	dst           [4]uint32
 	srcPort       uint16
 	dstPort       uint16
+	origDstAddr   [4]uint32
+	origDstPort   uint16
 	direction     uint32
 	receivedBytes uint32
 	sentBytes     uint32
@@ -145,7 +161,8 @@ type workloadMetricLabels struct {
 	destinationVersion           string
 	destinationCluster           string
 
-	requestProtocol          string
+	requestProtocol string
+	// TODO: responseFlags is not used for now
 	responseFlags            string
 	connectionSecurityPolicy string
 }
@@ -174,7 +191,8 @@ type serviceMetricLabels struct {
 	destinationVersion           string
 	destinationCluster           string
 
-	requestProtocol          string
+	requestProtocol string
+	// TODO: responseFlags is not used for now
 	responseFlags            string
 	connectionSecurityPolicy string
 }
@@ -323,8 +341,8 @@ func (m *MetricController) Run(ctx context.Context, mapOfTcpInfo *ebpf.Map) {
 				log.Errorf("ringbuf reader FAILED to read, err: %v", err)
 				continue
 			}
-			if len(rec.RawSample) != MSG_LEN {
-				log.Errorf("wrong length %v of a msg, should be %v", len(rec.RawSample), MSG_LEN)
+			if len(rec.RawSample) != int(unsafe.Sizeof(connectionDataV4{})) {
+				log.Errorf("wrong length %v of a msg, should be %v", len(rec.RawSample), int(unsafe.Sizeof(connectionDataV4{})))
 				continue
 			}
 			connectType := binary.LittleEndian.Uint32(rec.RawSample)
@@ -373,6 +391,17 @@ func buildV4Metric(buf *bytes.Buffer) (requestMetric, error) {
 	data.dstPort = connectData.DstPort
 	data.srcPort = connectData.SrcPort
 
+	// original addr is 0 indicates the connection is
+	// workload-type or and not redirected,
+	// so we just take from the actual destination
+	if connectData.OriginalAddr == 0 {
+		data.origDstAddr[0] = data.dst[0]
+		data.origDstPort = data.dstPort
+	} else {
+		data.origDstAddr[0] = connectData.OriginalAddr
+		data.origDstPort = connectData.OriginalPort
+	}
+
 	data.sentBytes = connectData.SentBytes
 	data.receivedBytes = connectData.ReceivedBytes
 	data.state = connectData.State
@@ -395,6 +424,17 @@ func buildV6Metric(buf *bytes.Buffer) (requestMetric, error) {
 	data.dstPort = connectData.DstPort
 	data.srcPort = connectData.SrcPort
 
+	// original addr is 0 indicates the connection is
+	// workload-type or and not redirected,
+	// so we just take from the actual destination
+	if !isOrigDstSet(connectData.OriginalAddr) {
+		data.origDstAddr = data.dst
+		data.origDstPort = data.dstPort
+	} else {
+		data.origDstAddr = connectData.OriginalAddr
+		data.origDstPort = connectData.OriginalPort
+	}
+
 	data.sentBytes = connectData.SentBytes
 	data.receivedBytes = connectData.ReceivedBytes
 	data.state = connectData.State
@@ -425,7 +465,6 @@ func (m *MetricController) buildWorkloadMetric(data *requestMetric) workloadMetr
 	trafficLabels.destinationPodAddress = dstIP
 	trafficLabels.requestProtocol = "tcp"
 	trafficLabels.connectionSecurityPolicy = "mutual_tls"
-	trafficLabels.reporter = DEFAULT_UNKNOWN
 
 	switch data.direction {
 	case constants.INBOUND:
@@ -437,42 +476,78 @@ func (m *MetricController) buildWorkloadMetric(data *requestMetric) workloadMetr
 	return *trafficLabels
 }
 
-func (m *MetricController) buildServiceMetric(data *requestMetric) (serviceMetricLabels, logInfo) {
-	var dstAddr, srcAddr []byte
-	for i := range data.dst {
-		dstAddr = binary.LittleEndian.AppendUint32(dstAddr, data.dst[i])
-		srcAddr = binary.LittleEndian.AppendUint32(srcAddr, data.src[i])
+// guessWorkloadService find the first service of the workload that matches the destination port
+func (m *MetricController) guessWorkloadService(workload *workloadapi.Workload, targetPort uint32) *workloadapi.Service {
+	if workload == nil {
+		return nil
 	}
-
-	dstWorkload, dstIp := m.getWorkloadByAddress(restoreIPv4(dstAddr))
-	srcWorkload, srcIp := m.getWorkloadByAddress(restoreIPv4(srcAddr))
-
 	namespacedhost := ""
-	if dstWorkload != nil {
-		for k, portList := range dstWorkload.Services {
-			for _, port := range portList.Ports {
-				if port.TargetPort == uint32(data.dstPort) {
-					namespacedhost = k
-					break
-				}
-			}
-			if namespacedhost != "" {
+	for k, portList := range workload.Services {
+		for _, port := range portList.Ports {
+			if port.TargetPort == targetPort {
+				namespacedhost = k
 				break
 			}
 		}
-		// Handling a Headless Service that does not specify a target port
-		if namespacedhost == "" {
-			for host := range dstWorkload.Services {
-				if host != "" {
-					namespacedhost = host
-					break
-				}
+		if namespacedhost != "" {
+			break
+		}
+	}
+	// Handling a Headless Service that does not specify a target port
+	if namespacedhost == "" {
+		for host := range workload.Services {
+			if host != "" {
+				namespacedhost = host
+				break
 			}
 		}
 	}
 
-	dstService := m.serviceCache.GetService(namespacedhost)
-	// when service not found, we use the address as hostname for metrics
+	return m.serviceCache.GetService(namespacedhost)
+}
+
+func (m *MetricController) getServiceByAddress(address []byte) (*workloadapi.Service, string) {
+	networkAddr := cache.NetworkAddress{}
+	networkAddr.Address, _ = netip.AddrFromSlice(address)
+	var svc *workloadapi.Service
+	if svc = m.serviceCache.GetServiceByAddr(networkAddr); svc != nil {
+		return svc, networkAddr.Address.String()
+	}
+	return nil, ""
+}
+
+func (m *MetricController) fetchOriginalService(address []byte, port uint32) *workloadapi.Service {
+	// if destination is service-type, we just return
+	svc, _ := m.getServiceByAddress(address)
+	if svc != nil {
+		return svc
+	}
+	// else if it is workload-type, we guess the destination service
+	wld, wldAddr := m.getWorkloadByAddress(address)
+	dstSvc := m.guessWorkloadService(wld, port)
+	// when dst svc not found, we use orig dst workload addr as its hostname, if exists
+	if dstSvc == nil && wld != nil {
+		dstSvc = &workloadapi.Service{
+			Hostname:  wldAddr,
+			Namespace: wld.Namespace,
+		}
+	}
+	return dstSvc
+}
+
+func (m *MetricController) buildServiceMetric(data *requestMetric) (serviceMetricLabels, logInfo) {
+	var dstAddr, srcAddr, origAddr []byte
+	for i := range data.dst {
+		dstAddr = binary.LittleEndian.AppendUint32(dstAddr, data.dst[i])
+		srcAddr = binary.LittleEndian.AppendUint32(srcAddr, data.src[i])
+		origAddr = binary.LittleEndian.AppendUint32(origAddr, data.origDstAddr[i])
+	}
+
+	dstWorkload, dstIp := m.getWorkloadByAddress(restoreIPv4(dstAddr))
+	srcWorkload, srcIp := m.getWorkloadByAddress(restoreIPv4(srcAddr))
+
+	dstService := m.fetchOriginalService(restoreIPv4(origAddr), uint32(data.origDstPort))
+	// if dstService not found, we use the address as hostname for metrics
 	if dstService == nil {
 		dstService = &workloadapi.Service{
 			Hostname: dstIp,
@@ -481,14 +556,11 @@ func (m *MetricController) buildServiceMetric(data *requestMetric) (serviceMetri
 
 	trafficLabels := NewServiceMetricLabel()
 	trafficLabels.withSource(srcWorkload).withDestination(dstWorkload).withDestinationService(dstService)
-
 	trafficLabels.requestProtocol = "tcp"
 	trafficLabels.connectionSecurityPolicy = "mutual_tls"
-	trafficLabels.reporter = DEFAULT_UNKNOWN
 
 	accesslog := NewLogInfo()
 	accesslog.withSource(srcWorkload).withDestination(dstWorkload).withDestinationService(dstService)
-
 	accesslog.destinationAddress = dstIp + ":" + fmt.Sprintf("%d", data.dstPort)
 	accesslog.sourceAddress = srcIp + ":" + fmt.Sprintf("%d", data.srcPort)
 
@@ -657,3 +729,12 @@ func restoreIPv4(bytes []byte) []byte {
 
 	return bytes[:4]
 }
+
+func isOrigDstSet(addr [4]uint32) bool {
+	for _, v := range addr {
+		if v != 0 {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/controller/workload/cache/service_cache.go b/pkg/controller/workload/cache/service_cache.go
index e9933754..f12c411e 100644
--- a/pkg/controller/workload/cache/service_cache.go
+++ b/pkg/controller/workload/cache/service_cache.go
@@ -17,6 +17,7 @@
 package cache
 
 import (
+	"net/netip"
 	"sync"
 
 	"kmesh.net/kmesh/api/v2/workloadapi"
@@ -30,26 +31,42 @@ type ServiceCache interface {
 	AddOrUpdateService(svc *workloadapi.Service)
 	DeleteService(resourceName string)
 	GetService(resourceName string) *workloadapi.Service
+	GetServiceByAddr(address NetworkAddress) *workloadapi.Service
 }
 
+var _ ServiceCache = &serviceCache{}
+
 type serviceCache struct {
 	mutex sync.RWMutex
 	// keyed by namespace/hostname->service
 	servicesByResourceName map[string]*workloadapi.Service
+	servicesByAddr         map[NetworkAddress]*workloadapi.Service
 }
 
 func NewServiceCache() *serviceCache {
 	return &serviceCache{
 		servicesByResourceName: make(map[string]*workloadapi.Service),
+		servicesByAddr:         make(map[NetworkAddress]*workloadapi.Service),
 	}
 }
 
+func (s *serviceCache) GetServiceByAddr(address NetworkAddress) *workloadapi.Service {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.servicesByAddr[address]
+}
+
 func (s *serviceCache) AddOrUpdateService(svc *workloadapi.Service) {
 	s.mutex.Lock()
 	defer s.mutex.Unlock()
 	resourceName := svc.ResourceName()
 
 	s.servicesByResourceName[resourceName] = svc
+	for _, addr := range svc.GetAddresses() {
+		addrStr, _ := netip.AddrFromSlice(addr.GetAddress())
+		networkAddress := composeNetworkAddress(addr.GetNetwork(), addrStr)
+		s.servicesByAddr[networkAddress] = svc
+	}
 }
 
 func (s *serviceCache) DeleteService(resourceName string) {


Test patch:
diff --git a/pkg/controller/telemetry/metric_test.go b/pkg/controller/telemetry/metric_test.go
index 1d1eeeb0..e5ba0315 100644
--- a/pkg/controller/telemetry/metric_test.go
+++ b/pkg/controller/telemetry/metric_test.go
@@ -18,6 +18,7 @@ package telemetry
 
 import (
 	"context"
+	"net"
 	"reflect"
 	"testing"
 
@@ -27,6 +28,7 @@ import (
 
 	"kmesh.net/kmesh/api/v2/workloadapi"
 	"kmesh.net/kmesh/pkg/controller/workload/cache"
+	"kmesh.net/kmesh/pkg/nets"
 )
 
 func TestCommonTrafficLabels2map(t *testing.T) {
@@ -418,7 +420,7 @@ func TestBuildworkloadMetric(t *testing.T) {
 				requestProtocol:              "tcp",
 				responseFlags:                "",
 				connectionSecurityPolicy:     "mutual_tls",
-				reporter:                     "-",
+				reporter:                     "",
 			},
 			wantErr: false,
 		},
@@ -482,11 +484,38 @@ func TestBuildServiceMetric(t *testing.T) {
 		Name:      "kmesh",
 		Addresses: []*workloadapi.NetworkAddress{
 			{
-				Address: []byte("192.168.1.22"),
+				Address: net.ParseIP("192.168.1.22").To4(),
 			},
 		},
 	})
+	serviceCache.AddOrUpdateService(&workloadapi.Service{
+		Hostname:  "httpbin.default.svc.cluster.local",
+		Namespace: "default",
+		Name:      "httpbin",
+		Addresses: []*workloadapi.NetworkAddress{
+			{
+				Address: net.ParseIP("192.168.1.23").To4(),
+			},
+		},
+	})
+
 	workloadCache := cache.NewWorkloadCache()
+
+	workloadCache.AddOrUpdateWorkload(&workloadapi.Workload{
+		Namespace:         "default",
+		Name:              "sleep",
+		WorkloadName:      "sleep",
+		CanonicalName:     "sleepCanonical",
+		CanonicalRevision: "sleepVersion",
+		ClusterId:         "Kubernetes",
+		TrustDomain:       "cluster.local",
+		ServiceAccount:    "default",
+		Addresses: [][]byte{
+			{10, 19, 25, 33},
+		},
+	})
+
+	// kmesh workload with service attached
 	workloadCache.AddOrUpdateWorkload(&workloadapi.Workload{
 		Namespace:         "kmesh-system",
 		Name:              "kmesh",
@@ -507,22 +536,39 @@ func TestBuildServiceMetric(t *testing.T) {
 			},
 		},
 		Addresses: [][]byte{
-			{192, 168, 224, 22},
+			{10, 19, 25, 31},
 		},
 	})
+
+	// a solely workload without service attached
 	workloadCache.AddOrUpdateWorkload(&workloadapi.Workload{
-		Namespace:         "kmesh-system",
-		Name:              "kmesh",
-		WorkloadName:      "kmesh-daemon",
-		CanonicalName:     "srcCanonical",
-		CanonicalRevision: "srcVersion",
+		Namespace:         "default",
+		Name:              "solelyWorkload",
+		WorkloadName:      "solelyWorkload",
+		CanonicalName:     "solelyCanonical",
+		CanonicalRevision: "solelyVersion",
 		ClusterId:         "Kubernetes",
 		TrustDomain:       "cluster.local",
 		ServiceAccount:    "default",
 		Addresses: [][]byte{
-			{10, 19, 25, 31},
+			{10, 19, 25, 34},
+		},
+	})
+
+	workloadCache.AddOrUpdateWorkload(&workloadapi.Workload{
+		Namespace:         "default",
+		Name:              "waypoint",
+		WorkloadName:      "waypoint",
+		CanonicalName:     "waypointCanonical",
+		CanonicalRevision: "waypointVersion",
+		ClusterId:         "Kubernetes",
+		TrustDomain:       "cluster.local",
+		ServiceAccount:    "default",
+		Addresses: [][]byte{
+			{10, 19, 25, 32},
 		},
 	})
+
 	m := MetricController{
 		workloadCache: workloadCache,
 		serviceCache:  serviceCache,
@@ -534,26 +580,31 @@ func TestBuildServiceMetric(t *testing.T) {
 		wantLogInfo logInfo
 	}{
 		{
-			name: "normal capability test",
+			name: "destination exists with service attached, workload-type request",
 			args: args{
 				data: &requestMetric{
-					src:           [4]uint32{521736970, 0, 0, 0},
-					dst:           [4]uint32{383822016, 0, 0, 0},
-					dstPort:       uint16(8000),
-					srcPort:       uint16(8000),
+					// sleep
+					src: [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					// kmesh-daemon
+					dst:     [4]uint32{nets.ConvertIpToUint32("10.19.25.31"), 0, 0, 0},
+					dstPort: uint16(8000),
+					srcPort: uint16(8000),
+					// kmesh-daemon
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("10.19.25.31"), 0, 0, 0},
+					origDstPort:   uint16(8000),
 					direction:     uint32(2),
 					sentBytes:     uint32(156),
 					receivedBytes: uint32(1024),
 				},
 			},
 			want: serviceMetricLabels{
-				sourceWorkload:               "kmesh-daemon",
-				sourceCanonicalService:       "srcCanonical",
-				sourceCanonicalRevision:      "srcVersion",
-				sourceWorkloadNamespace:      "kmesh-system",
-				sourcePrincipal:              "spiffe://cluster.local/ns/kmesh-system/sa/default",
-				sourceApp:                    "srcCanonical",
-				sourceVersion:                "srcVersion",
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
 				sourceCluster:                "Kubernetes",
 				destinationService:           "kmesh.kmesh-system.svc.cluster.local",
 				destinationServiceNamespace:  "kmesh-system",
@@ -573,10 +624,64 @@ func TestBuildServiceMetric(t *testing.T) {
 			},
 			wantLogInfo: logInfo{
 				direction:            "OUTBOUND",
-				sourceAddress:        "10.19.25.31:8000",
-				sourceWorkload:       "kmesh",
-				sourceNamespace:      "kmesh-system",
-				destinationAddress:   "192.168.224.22:8000",
+				sourceAddress:        "10.19.25.33:8000",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
+				destinationAddress:   "10.19.25.31:8000",
+				destinationService:   "kmesh.kmesh-system.svc.cluster.local",
+				destinationWorkload:  "kmesh",
+				destinationNamespace: "kmesh-system",
+			},
+		},
+		{
+			name: "destination exists with service attached, service-type request",
+			args: args{
+				data: &requestMetric{
+					// sleep
+					src: [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					// kmesh-daemon
+					dst:     [4]uint32{nets.ConvertIpToUint32("10.19.25.31"), 0, 0, 0},
+					dstPort: uint16(8000),
+					srcPort: uint16(8000),
+					// kmesh-daemon
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("192.168.1.22"), 0, 0, 0},
+					origDstPort:   uint16(8000),
+					direction:     uint32(2),
+					sentBytes:     uint32(156),
+					receivedBytes: uint32(1024),
+				},
+			},
+			want: serviceMetricLabels{
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
+				sourceCluster:                "Kubernetes",
+				destinationService:           "kmesh.kmesh-system.svc.cluster.local",
+				destinationServiceNamespace:  "kmesh-system",
+				destinationServiceName:       "kmesh",
+				destinationWorkload:          "kmesh-daemon",
+				destinationCanonicalService:  "dstCanonical",
+				destinationCanonicalRevision: "dstVersion",
+				destinationWorkloadNamespace: "kmesh-system",
+				destinationPrincipal:         "spiffe://cluster.local/ns/kmesh-system/sa/default",
+				destinationApp:               "dstCanonical",
+				destinationVersion:           "dstVersion",
+				destinationCluster:           "Kubernetes",
+				requestProtocol:              "tcp",
+				responseFlags:                "",
+				connectionSecurityPolicy:     "mutual_tls",
+				reporter:                     "source",
+			},
+			wantLogInfo: logInfo{
+				direction:            "OUTBOUND",
+				sourceAddress:        "10.19.25.33:8000",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
+				destinationAddress:   "10.19.25.31:8000",
 				destinationService:   "kmesh.kmesh-system.svc.cluster.local",
 				destinationWorkload:  "kmesh",
 				destinationNamespace: "kmesh-system",
@@ -586,23 +691,28 @@ func TestBuildServiceMetric(t *testing.T) {
 			name: "nil destination workload",
 			args: args{
 				data: &requestMetric{
-					src:           [4]uint32{521736970, 0, 0, 0},
-					dst:           [4]uint32{383822015, 0, 0, 0},
-					dstPort:       uint16(80),
-					srcPort:       uint16(8000),
+					// sleep
+					src: [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					// unknown address
+					dst:     [4]uint32{nets.ConvertIpToUint32("191.168.224.22"), 0, 0, 0},
+					dstPort: uint16(80),
+					srcPort: uint16(8000),
+					// unknown address
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("191.168.224.22"), 0, 0, 0},
+					origDstPort:   uint16(80),
 					direction:     uint32(2),
 					sentBytes:     uint32(156),
 					receivedBytes: uint32(1024),
 				},
 			},
 			want: serviceMetricLabels{
-				sourceWorkload:               "kmesh-daemon",
-				sourceCanonicalService:       "srcCanonical",
-				sourceCanonicalRevision:      "srcVersion",
-				sourceWorkloadNamespace:      "kmesh-system",
-				sourcePrincipal:              "spiffe://cluster.local/ns/kmesh-system/sa/default",
-				sourceApp:                    "srcCanonical",
-				sourceVersion:                "srcVersion",
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
 				sourceCluster:                "Kubernetes",
 				destinationService:           "191.168.224.22",
 				destinationServiceNamespace:  "",
@@ -622,15 +732,177 @@ func TestBuildServiceMetric(t *testing.T) {
 			},
 			wantLogInfo: logInfo{
 				direction:            "OUTBOUND",
-				sourceAddress:        "10.19.25.31:8000",
-				sourceWorkload:       "kmesh",
-				sourceNamespace:      "kmesh-system",
+				sourceAddress:        "10.19.25.33:8000",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
 				destinationAddress:   "191.168.224.22:80",
 				destinationService:   "191.168.224.22",
 				destinationWorkload:  "-",
 				destinationNamespace: "-",
 			},
 		},
+		{
+			name: "service-type request, redirected to waypoint",
+			args: args{
+				data: &requestMetric{
+					// sleep
+					src:     [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					srcPort: uint16(49875),
+					// waypoint
+					dst:     [4]uint32{nets.ConvertIpToUint32("10.19.25.32"), 0, 0, 0},
+					dstPort: uint16(80),
+					// httpbin service
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("192.168.1.23"), 0, 0, 0},
+					origDstPort:   uint16(80),
+					direction:     uint32(2),
+					sentBytes:     uint32(156),
+					receivedBytes: uint32(1024),
+				},
+			},
+			want: serviceMetricLabels{
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
+				sourceCluster:                "Kubernetes",
+				destinationService:           "httpbin.default.svc.cluster.local",
+				destinationServiceNamespace:  "default",
+				destinationServiceName:       "httpbin",
+				destinationWorkload:          "waypoint",
+				destinationCanonicalService:  "waypointCanonical",
+				destinationCanonicalRevision: "waypointVersion",
+				destinationWorkloadNamespace: "default",
+				destinationPrincipal:         "spiffe://cluster.local/ns/default/sa/default",
+				destinationApp:               "waypointCanonical",
+				destinationVersion:           "waypointVersion",
+				destinationCluster:           "Kubernetes",
+				requestProtocol:              "tcp",
+				responseFlags:                "",
+				connectionSecurityPolicy:     "mutual_tls",
+				reporter:                     "source",
+			},
+			wantLogInfo: logInfo{
+				direction:            "OUTBOUND",
+				sourceAddress:        "10.19.25.33:49875",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
+				destinationAddress:   "10.19.25.32:80",
+				destinationService:   "httpbin.default.svc.cluster.local",
+				destinationWorkload:  "waypoint",
+				destinationNamespace: "default",
+			},
+		},
+		{
+			name: "workload-type request, redirected to waypoint",
+			args: args{
+				data: &requestMetric{
+					// sleep
+					src:     [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					srcPort: uint16(49875),
+					// waypoint
+					dst:     [4]uint32{nets.ConvertIpToUint32("10.19.25.32"), 0, 0, 0},
+					dstPort: uint16(80),
+					// solelyWorkload
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("10.19.25.34"), 0, 0, 0},
+					origDstPort:   uint16(80),
+					direction:     uint32(2),
+					sentBytes:     uint32(156),
+					receivedBytes: uint32(1024),
+				},
+			},
+			want: serviceMetricLabels{
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
+				sourceCluster:                "Kubernetes",
+				destinationService:           "10.19.25.34",
+				destinationServiceNamespace:  "default",
+				destinationServiceName:       "",
+				destinationWorkload:          "waypoint",
+				destinationCanonicalService:  "waypointCanonical",
+				destinationCanonicalRevision: "waypointVersion",
+				destinationWorkloadNamespace: "default",
+				destinationPrincipal:         "spiffe://cluster.local/ns/default/sa/default",
+				destinationApp:               "waypointCanonical",
+				destinationVersion:           "waypointVersion",
+				destinationCluster:           "Kubernetes",
+				requestProtocol:              "tcp",
+				responseFlags:                "",
+				connectionSecurityPolicy:     "mutual_tls",
+				reporter:                     "source",
+			},
+			wantLogInfo: logInfo{
+				direction:            "OUTBOUND",
+				sourceAddress:        "10.19.25.33:49875",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
+				destinationAddress:   "10.19.25.32:80",
+				destinationService:   "10.19.25.34",
+				destinationWorkload:  "waypoint",
+				destinationNamespace: "default",
+			},
+		},
+		{
+			name: "destination workload exists without service attached",
+			args: args{
+				data: &requestMetric{
+					// sleep
+					src:     [4]uint32{nets.ConvertIpToUint32("10.19.25.33"), 0, 0, 0},
+					srcPort: uint16(49875),
+					// solely workload
+					dst:     [4]uint32{nets.ConvertIpToUint32("10.19.25.34"), 0, 0, 0},
+					dstPort: uint16(80),
+					// httpbin service
+					origDstAddr:   [4]uint32{nets.ConvertIpToUint32("10.19.25.34"), 0, 0, 0},
+					origDstPort:   uint16(80),
+					direction:     uint32(2),
+					sentBytes:     uint32(156),
+					receivedBytes: uint32(1024),
+				},
+			},
+			want: serviceMetricLabels{
+				sourceWorkload:               "sleep",
+				sourceCanonicalService:       "sleepCanonical",
+				sourceCanonicalRevision:      "sleepVersion",
+				sourceWorkloadNamespace:      "default",
+				sourcePrincipal:              "spiffe://cluster.local/ns/default/sa/default",
+				sourceApp:                    "sleepCanonical",
+				sourceVersion:                "sleepVersion",
+				sourceCluster:                "Kubernetes",
+				destinationService:           "10.19.25.34",
+				destinationServiceNamespace:  "default",
+				destinationServiceName:       "",
+				destinationWorkload:          "solelyWorkload",
+				destinationCanonicalService:  "solelyCanonical",
+				destinationCanonicalRevision: "solelyVersion",
+				destinationWorkloadNamespace: "default",
+				destinationPrincipal:         "spiffe://cluster.local/ns/default/sa/default",
+				destinationApp:               "solelyCanonical",
+				destinationVersion:           "solelyVersion",
+				destinationCluster:           "Kubernetes",
+				requestProtocol:              "tcp",
+				responseFlags:                "",
+				connectionSecurityPolicy:     "mutual_tls",
+				reporter:                     "source",
+			},
+			wantLogInfo: logInfo{
+				direction:            "OUTBOUND",
+				sourceAddress:        "10.19.25.33:49875",
+				sourceWorkload:       "sleep",
+				sourceNamespace:      "default",
+				destinationAddress:   "10.19.25.34:80",
+				destinationService:   "10.19.25.34",
+				destinationWorkload:  "solelyWorkload",
+				destinationNamespace: "default",
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {