diff --git a/bpf/configs.h b/bpf/configs.h
index 208faea1f4b3e96bce3f11b5dab2b7092a8d5bfb..5a9d85384efa1ce3e5ba403975a4c58facb7f7b9 100644
--- a/bpf/configs.h
+++ b/bpf/configs.h
@@ -5,5 +5,6 @@
 // Constant definitions, to be overridden by the invoker
 volatile const u32 sampling = 0;
 volatile const u8 trace_messages = 0;
+volatile const u8 enable_rtt = 0;
 
 #endif //__CONFIGS_H__
diff --git a/bpf/flows.c b/bpf/flows.c
index 5219b808d777c8249f1136ffea33a5e04f7a2c02..e7b857d0c90c6cc07ea0eac629a5a31f110b6045 100644
--- a/bpf/flows.c
+++ b/bpf/flows.c
@@ -1,5 +1,6 @@
 /*
-    Flows v2. A Flow-metric generator using TC.
+    Flows v2.
+    Flow monitor: A Flow-metric generator using TC.
 
     This program can be hooked on to TC ingress/egress hook to monitor packets
     to/from an interface.
@@ -13,38 +14,79 @@
             until an entry is available.
         4) When hash collision is detected, we send the new entry to userpace via ringbuffer.
 */
+#include <vmlinux.h>
+#include <bpf_helpers.h>
+#include "configs.h"
 #include "utils.h"
+
+/* Defines a tcp drops statistics tracker,
+   which attaches at kfree_skb hook. Is optional.
+*/
 #include "tcp_drops.h"
+
+/* Defines a dns tracker,
+   which attaches at net_dev_queue hook. Is optional.
+*/
 #include "dns_tracker.h"
 
+/* Defines an rtt tracker,
+   which runs inside flow_monitor. Is optional.
+*/
+#include "rtt_tracker.h"
 
 static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
     // If sampling is defined, will only parse 1 out of "sampling" flows
     if (sampling != 0 && (bpf_get_prandom_u32() % sampling) != 0) {
         return TC_ACT_OK;
     }
-    void *data_end = (void *)(long)skb->data_end;
-    void *data = (void *)(long)skb->data;
+
+    // Record the current time first.
+    u64 current_time = bpf_ktime_get_ns();
 
     flow_id id;
     __builtin_memset(&id, 0, sizeof(id));
-    u64 current_time = bpf_ktime_get_ns();
-    struct ethhdr *eth = data;
-    u16 flags = 0;
-    if (fill_ethhdr(eth, data_end, &id, &flags) == DISCARD) {
+
+    pkt_info pkt;
+    __builtin_memset(&pkt, 0, sizeof(pkt));
+
+    pkt.id = &id;
+    pkt.current_ts = current_time;
+
+    void *data_end = (void *)(long)skb->data_end;
+    void *data = (void *)(long)skb->data;
+    struct ethhdr *eth = (struct ethhdr *)data;
+
+    if (fill_ethhdr(eth, data_end, &pkt) == DISCARD) {
         return TC_ACT_OK;
     }
+
+    if (enable_rtt) {
+        // This is currently gated as its not to be enabled by default.
+        calculate_flow_rtt(&pkt, direction, data_end);
+    }
+
+    //Set extra fields
     id.if_index = skb->ifindex;
     id.direction = direction;
 
     // TODO: we need to add spinlock here when we deprecate versions prior to 5.1, or provide
     // a spinlocked alternative version and use it selectively https://lwn.net/Articles/779120/
-    flow_metrics *aggregate_flow = bpf_map_lookup_elem(&aggregated_flows, &id);
+    flow_metrics *aggregate_flow = (flow_metrics *)bpf_map_lookup_elem(&aggregated_flows, &id);
     if (aggregate_flow != NULL) {
         aggregate_flow->packets += 1;
         aggregate_flow->bytes += skb->len;
         aggregate_flow->end_mono_time_ts = current_time;
-        aggregate_flow->flags |= flags;
+        aggregate_flow->flags |= pkt.flags;
+
+        // Does not matter the gate. Will be zero if not enabled.
+        if (pkt.rtt > 0) {
+            /* Since RTT is calculated for few packets we need to check if it is non zero value then only we update
+             * the flow. If we remove this check a packet which fails to calculate RTT will override the previous valid
+             * RTT with 0.
+             */
+            aggregate_flow->flow_rtt = pkt.rtt;
+        }
+
         long ret = bpf_map_update_elem(&aggregated_flows, &id, aggregate_flow, BPF_ANY);
         if (trace_messages && ret != 0) {
             // usually error -16 (-EBUSY) is printed here.
@@ -61,7 +103,8 @@ static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
             .bytes = skb->len,
             .start_mono_time_ts = current_time,
             .end_mono_time_ts = current_time,
-            .flags = flags, 
+            .flags = pkt.flags,
+            .flow_rtt = pkt.rtt
         };
 
         // even if we know that the entry is new, another CPU might be concurrently inserting a flow
@@ -78,7 +121,7 @@ static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
             }
 
             new_flow.errno = -ret;
-            flow_record *record = bpf_ringbuf_reserve(&direct_flows, sizeof(flow_record), 0);
+            flow_record *record = (flow_record *)bpf_ringbuf_reserve(&direct_flows, sizeof(flow_record), 0);
             if (!record) {
                 if (trace_messages) {
                     bpf_printk("couldn't reserve space in the ringbuf. Dropping flow");
@@ -92,6 +135,7 @@ static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
     }
     return TC_ACT_OK;
 }
+
 SEC("tc_ingress")
 int ingress_flow_parse(struct __sk_buff *skb) {
     return flow_monitor(skb, INGRESS);
@@ -103,3 +147,4 @@ int egress_flow_parse(struct __sk_buff *skb) {
 }
 
 char _license[] SEC("license") = "GPL";
+
diff --git a/bpf/maps_definition.h b/bpf/maps_definition.h
index 8bd0d01207bdc04977a79aa4351b6b0115d51f25..4c5bf86e718587adc2633c371e3bde27586adcd5 100644
--- a/bpf/maps_definition.h
+++ b/bpf/maps_definition.h
@@ -18,4 +18,17 @@ struct {
     __uint(map_flags, BPF_F_NO_PREALLOC);
 } aggregated_flows SEC(".maps");
 
+// Common hashmap to keep track of all flow sequences.
+// LRU hashmap is used because if some syn packet is received but ack is not
+// then the hashmap entry will need to be evicted
+// Key is flow_seq_id which is standard 4 tuple and a sequence id
+//     sequence id is specific to the type of transport protocol
+// Value is u64 which represents the occurrence timestamp of the packet.
+struct {
+    __uint(type, BPF_MAP_TYPE_LRU_HASH);
+    __uint(max_entries, 1 << 20);   // Will take around 64MB of space.
+    __type(key, flow_seq_id);
+    __type(value, u64);
+} flow_sequences SEC(".maps");
+
 #endif //__MAPS_DEFINITION_H__
diff --git a/bpf/rtt_tracker.h b/bpf/rtt_tracker.h
new file mode 100644
index 0000000000000000000000000000000000000000..d4c81b72b524d097ceb690c9d46a79d3268ca70f
--- /dev/null
+++ b/bpf/rtt_tracker.h
@@ -0,0 +1,87 @@
+/*
+    A simple RTT tracker implemented to be used at the ebpf layer inside the flow_monitor hookpoint.
+    This tracker currently tracks RTT for TCP flows by looking at the TCP start sequence and estimates
+    RTT by perform (timestamp of receiveing ack packet - timestamp of sending syn packet)
+ */
+
+#ifndef __RTT_TRACKER_H__
+#define __RTT_TRACKER_H__
+
+#include "utils.h"
+#include "maps_definition.h"
+
+static __always_inline void fill_flow_seq_id(flow_seq_id *seq_id, pkt_info *pkt, u32 seq, u8 reversed) {
+    flow_id *id = pkt->id;
+    if (reversed) {
+        __builtin_memcpy(seq_id->src_ip, id->dst_ip, IP_MAX_LEN);
+        __builtin_memcpy(seq_id->dst_ip, id->src_ip, IP_MAX_LEN);
+        seq_id->src_port = id->dst_port;
+        seq_id->dst_port = id->src_port;
+    } else {
+        __builtin_memcpy(seq_id->src_ip, id->src_ip, IP_MAX_LEN);
+        __builtin_memcpy(seq_id->dst_ip, id->dst_ip, IP_MAX_LEN);
+        seq_id->src_port = id->src_port;
+        seq_id->dst_port = id->dst_port;
+    }
+    seq_id->seq_id = seq;
+}
+
+static __always_inline void calculate_flow_rtt_tcp(pkt_info *pkt, u8 direction, void *data_end, flow_seq_id *seq_id) {
+    struct tcphdr *tcp = (struct tcphdr *) pkt->l4_hdr;
+    if ( !tcp || ((void *)tcp + sizeof(*tcp) > data_end) ) {
+        return;
+    }
+
+    switch (direction) {
+    case EGRESS: {
+        if (IS_SYN_PACKET(pkt)) {
+            // Record the outgoing syn sequence number
+            u32 seq = bpf_ntohl(tcp->seq);
+            fill_flow_seq_id(seq_id, pkt, seq, 0);
+
+            long ret = bpf_map_update_elem(&flow_sequences, seq_id, &pkt->current_ts, BPF_ANY);
+            if (trace_messages && ret != 0) {
+                bpf_printk("err saving flow sequence record %d", ret);
+            }
+        }
+        break;
+    }
+    case INGRESS: {
+        if (IS_ACK_PACKET(pkt)) {
+            // Stored sequence should be ack_seq - 1
+            u32 seq = bpf_ntohl(tcp->ack_seq) - 1;
+            // check reversed flow
+            fill_flow_seq_id(seq_id, pkt, seq, 1);
+
+            u64 *prev_ts = (u64 *)bpf_map_lookup_elem(&flow_sequences, seq_id);
+            if (prev_ts != NULL) {
+                pkt->rtt = pkt->current_ts - *prev_ts;
+                // Delete the flow from flow sequence map so if it
+                // restarts we have a new RTT calculation.
+                long ret = bpf_map_delete_elem(&flow_sequences, seq_id);
+                if (trace_messages && ret != 0) {
+                    bpf_printk("error evicting flow sequence: %d", ret);
+                }
+            }
+        }
+        break;
+    }
+    }
+}
+
+static __always_inline void calculate_flow_rtt(pkt_info *pkt, u8 direction, void *data_end) {
+    flow_seq_id seq_id;
+    __builtin_memset(&seq_id, 0, sizeof(flow_seq_id));
+
+    switch (pkt->id->transport_protocol)
+    {
+    case IPPROTO_TCP:
+        calculate_flow_rtt_tcp(pkt, direction, data_end, &seq_id);
+        break;
+    default:
+        break;
+    }
+}
+
+#endif /* __RTT_TRACKER_H__ */
+
diff --git a/bpf/flow.h b/bpf/types.h
similarity index 56%
rename from bpf/flow.h
rename to bpf/types.h
index ddb306f8ee0d566f376b9e63a377df8b535533c7..0ce9037590718be2739d70478984ea1647c2ee8c 100644
--- a/bpf/flow.h
+++ b/bpf/types.h
@@ -1,10 +1,46 @@
-#ifndef __FLOW_H__
-#define __FLOW_H__
+#ifndef __TYPES_H__
+#define __TYPES_H__
 
 #define TC_ACT_OK 0
 #define TC_ACT_SHOT 2
 #define IP_MAX_LEN 16
 
+#define DISCARD 1
+#define SUBMIT 0
+
+// Flags according to RFC 9293 & https://www.iana.org/assignments/ipfix/ipfix.xhtml
+#define FIN_FLAG 0x01
+#define SYN_FLAG 0x02
+#define RST_FLAG 0x04
+#define PSH_FLAG 0x08
+#define ACK_FLAG 0x10
+#define URG_FLAG 0x20
+#define ECE_FLAG 0x40
+#define CWR_FLAG 0x80
+// Custom flags exported
+#define SYN_ACK_FLAG 0x100
+#define FIN_ACK_FLAG 0x200
+#define RST_ACK_FLAG 0x400
+
+#define IS_SYN_PACKET(pkt)    ((pkt->flags & SYN_FLAG) || (pkt->flags & SYN_ACK_FLAG))
+#define IS_ACK_PACKET(pkt)    ((pkt->flags & ACK_FLAG) || (pkt->flags & SYN_ACK_FLAG))
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
+    __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define bpf_ntohs(x)        __builtin_bswap16(x)
+#define bpf_htons(x)        __builtin_bswap16(x)
+#define bpf_ntohl(x)        __builtin_bswap32(x)
+#define bpf_htonl(x)        __builtin_bswap32(x)
+#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
+    __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define bpf_ntohs(x)        (x)
+#define bpf_htons(x)        (x)
+#define bpf_ntohl(x)        (x)
+#define bpf_htonl(x)        (x)
+#else
+# error "Endianness detection needs to be set up for your compiler?!"
+#endif
+
 typedef __u8 u8;
 typedef __u16 u16;
 typedef __u32 u32;
@@ -18,6 +54,15 @@ typedef __u64 u64;
 #define ETH_P_ARP 0x0806
 #define IPPROTO_ICMPV6 58
 
+// according to field 61 in https://www.iana.org/assignments/ipfix/ipfix.xhtml
+typedef enum {
+    INGRESS         = 0,
+    EGRESS          = 1,
+    MAX_DIRECTION   = 2,
+} direction_t;
+
+const u8 ip4in6[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
+
 typedef struct flow_metrics_t {
     u32 packets;
     u64 bytes;
@@ -45,6 +90,7 @@ typedef struct flow_metrics_t {
         u64 req_mono_time_ts;
         u64 rsp_mono_time_ts;
     } __attribute__((packed)) dns_record;
+    u64 flow_rtt;
 } __attribute__((packed)) flow_metrics;
 
 // Force emitting struct tcp_drops into the ELF.
@@ -79,6 +125,16 @@ typedef struct flow_id_t {
 // Force emitting struct flow_id into the ELF.
 const struct flow_id_t *unused2 __attribute__((unused));
 
+// Standard 4 tuple and a sequence identifier.
+// No need to emit this struct. It's used only in kernel space
+typedef struct flow_seq_id_t {
+    u16 src_port;
+    u16 dst_port;
+    u8 src_ip[IP_MAX_LEN];
+    u8 dst_ip[IP_MAX_LEN];
+    u32 seq_id;
+} __attribute__((packed)) flow_seq_id;
+
 // Flow record is a tuple containing both flow identifier and metrics. It is used to send
 // a complete flow via ring buffer when only when the accounting hashmap is full.
 // Contents in this struct must match byte-by-byte with Go's pkc/flow/Record struct
@@ -93,4 +149,14 @@ const struct flow_record_t *unused3 __attribute__((unused));
 // Force emitting struct dns_record into the ELF.
 const struct dns_record_t *unused4 __attribute__((unused));
 
-#endif
+// Internal structure: Packet info structure parsed around functions.
+typedef struct pkt_info_t {
+    flow_id *id;
+    u64 current_ts; // ts recorded when pkt came.
+    u16 flags;      // TCP specific
+    void *l4_hdr;   // Stores the actual l4 header
+    u64 rtt;        // rtt calculated from the flow if possible. else zero
+} pkt_info;
+
+#endif /* __TYPES_H__ */
+
diff --git a/bpf/utils.h b/bpf/utils.h
index 5338f1b72ae2d0cd8896dcc193933f2be4222128..8a6a79b7fec312dbdecbbb7cb095221f53fe753c 100644
--- a/bpf/utils.h
+++ b/bpf/utils.h
@@ -1,76 +1,19 @@
 #ifndef __UTILS_H__
 #define __UTILS_H__
 
-#include <vmlinux.h>
-#include <bpf_helpers.h>
-
-#include "flow.h"
+#include "types.h"
 #include "maps_definition.h"
-#include "configs.h"
-
-#define DISCARD 1
-#define SUBMIT 0
-
-// according to field 61 in https://www.iana.org/assignments/ipfix/ipfix.xhtml
-typedef enum {
-    INGRESS         = 0,
-    EGRESS          = 1,
-    MAX_DIRECTION   = 2,
-} direction_t;
-
-// L4_info structure contains L4 headers parsed information.
-struct l4_info_t {
-    // TCP/UDP/SCTP source port in host byte order
-    u16 src_port;
-    // TCP/UDP/SCTP destination port in host byte order
-    u16 dst_port;
-    // ICMPv4/ICMPv6 type value
-    u8 icmp_type;
-    // ICMPv4/ICMPv6 code value
-    u8 icmp_code;
-    // TCP flags
-    u16 flags;
-};
-
-const u8 ip4in6[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
-
-// Flags according to RFC 9293 & https://www.iana.org/assignments/ipfix/ipfix.xhtml
-#define FIN_FLAG 0x01
-#define SYN_FLAG 0x02
-#define RST_FLAG 0x04
-#define PSH_FLAG 0x08
-#define ACK_FLAG 0x10
-#define URG_FLAG 0x20
-#define ECE_FLAG 0x40
-#define CWR_FLAG 0x80
-// Custom flags exported
-#define SYN_ACK_FLAG 0x100
-#define FIN_ACK_FLAG 0x200
-#define RST_ACK_FLAG 0x400
-
-#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
-	__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
-#define bpf_ntohs(x)		__builtin_bswap16(x)
-#define bpf_htons(x)		__builtin_bswap16(x)
-#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
-	__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
-#define bpf_ntohs(x)		(x)
-#define bpf_htons(x)		(x)
-#else
-# error "Endianness detection needs to be set up for your compiler?!"
-#endif
-
 
 // sets the TCP header flags for connection information
 static inline void set_flags(struct tcphdr *th, u16 *flags) {
-    //If both ACK and SYN are set, then it is server -> client communication during 3-way handshake.
+    //If both ACK and SYN are set, then it is server -> client communication during 3-way handshake. 
     if (th->ack && th->syn) {
         *flags |= SYN_ACK_FLAG;
     } else if (th->ack && th->fin ) {
         // If both ACK and FIN are set, then it is graceful termination from server.
         *flags |= FIN_ACK_FLAG;
     } else if (th->ack && th->rst ) {
-        // If both ACK and RST are set, then it is abrupt connection termination.
+        // If both ACK and RST are set, then it is abrupt connection termination. 
         *flags |= RST_ACK_FLAG;
     } else if (th->fin) {
         *flags |= FIN_FLAG;
@@ -93,42 +36,49 @@ static inline void set_flags(struct tcphdr *th, u16 *flags) {
 
 // Extract L4 info for the supported protocols
 static inline void fill_l4info(void *l4_hdr_start, void *data_end, u8 protocol,
-                               struct l4_info_t *l4_info) {
-	switch (protocol) {
+                               pkt_info *pkt) {
+    flow_id *id = pkt->id;
+    id->transport_protocol = protocol;
+    switch (protocol) {
     case IPPROTO_TCP: {
         struct tcphdr *tcp = l4_hdr_start;
         if ((void *)tcp + sizeof(*tcp) <= data_end) {
-            l4_info->src_port = bpf_ntohs(tcp->source);
-            l4_info->dst_port = bpf_ntohs(tcp->dest);
-            set_flags(tcp, &l4_info->flags);
+            id->src_port = bpf_ntohs(tcp->source);
+            id->dst_port = bpf_ntohs(tcp->dest);
+            set_flags(tcp, &pkt->flags);
+            pkt->l4_hdr = (void *) tcp;
         }
     } break;
     case IPPROTO_UDP: {
         struct udphdr *udp = l4_hdr_start;
         if ((void *)udp + sizeof(*udp) <= data_end) {
-            l4_info->src_port = bpf_ntohs(udp->source);
-            l4_info->dst_port = bpf_ntohs(udp->dest);
+            id->src_port = bpf_ntohs(udp->source);
+            id->dst_port = bpf_ntohs(udp->dest);
+            pkt->l4_hdr = (void *) udp;
         }
     } break;
     case IPPROTO_SCTP: {
         struct sctphdr *sctph = l4_hdr_start;
         if ((void *)sctph + sizeof(*sctph) <= data_end) {
-            l4_info->src_port = bpf_ntohs(sctph->source);
-            l4_info->dst_port = bpf_ntohs(sctph->dest);
+            id->src_port = bpf_ntohs(sctph->source);
+            id->dst_port = bpf_ntohs(sctph->dest);
+            pkt->l4_hdr = (void *) sctph;
         }
     } break;
     case IPPROTO_ICMP: {
         struct icmphdr *icmph = l4_hdr_start;
         if ((void *)icmph + sizeof(*icmph) <= data_end) {
-            l4_info->icmp_type = icmph->type;
-            l4_info->icmp_code = icmph->code;
+            id->icmp_type = icmph->type;
+            id->icmp_code = icmph->code;
+            pkt->l4_hdr = (void *) icmph;
         }
     } break;
     case IPPROTO_ICMPV6: {
         struct icmp6hdr *icmp6h = l4_hdr_start;
          if ((void *)icmp6h + sizeof(*icmp6h) <= data_end) {
-            l4_info->icmp_type = icmp6h->icmp6_type;
-            l4_info->icmp_code = icmp6h->icmp6_code;
+            id->icmp_type = icmp6h->icmp6_type;
+            id->icmp_code = icmp6h->icmp6_code;
+            pkt->l4_hdr = (void *) icmp6h;
         }
     } break;
     default:
@@ -137,68 +87,59 @@ static inline void fill_l4info(void *l4_hdr_start, void *data_end, u8 protocol,
 }
 
 // sets flow fields from IPv4 header information
-static inline int fill_iphdr(struct iphdr *ip, void *data_end, flow_id *id, u16 *flags) {
-    struct l4_info_t l4_info;
+static inline int fill_iphdr(struct iphdr *ip, void *data_end, pkt_info *pkt) {
     void *l4_hdr_start;
 
     l4_hdr_start = (void *)ip + sizeof(*ip);
     if (l4_hdr_start > data_end) {
         return DISCARD;
     }
-    __builtin_memset(&l4_info, 0, sizeof(l4_info));
+    flow_id *id = pkt->id;
+    /* Save the IP Address to id directly. copy once. */
     __builtin_memcpy(id->src_ip, ip4in6, sizeof(ip4in6));
     __builtin_memcpy(id->dst_ip, ip4in6, sizeof(ip4in6));
     __builtin_memcpy(id->src_ip + sizeof(ip4in6), &ip->saddr, sizeof(ip->saddr));
     __builtin_memcpy(id->dst_ip + sizeof(ip4in6), &ip->daddr, sizeof(ip->daddr));
-    id->transport_protocol = ip->protocol;
-    fill_l4info(l4_hdr_start, data_end, ip->protocol, &l4_info);
-    id->src_port = l4_info.src_port;
-    id->dst_port = l4_info.dst_port;
-    id->icmp_type = l4_info.icmp_type;
-    id->icmp_code = l4_info.icmp_code;
-    *flags = l4_info.flags;
 
+    /* fill l4 header which will be added to id in flow_monitor function.*/
+    fill_l4info(l4_hdr_start, data_end, ip->protocol, pkt);
     return SUBMIT;
 }
 
 // sets flow fields from IPv6 header information
-static inline int fill_ip6hdr(struct ipv6hdr *ip, void *data_end, flow_id *id, u16 *flags) {
-    struct l4_info_t l4_info;
+static inline int fill_ip6hdr(struct ipv6hdr *ip, void *data_end, pkt_info *pkt) {
     void *l4_hdr_start;
 
     l4_hdr_start = (void *)ip + sizeof(*ip);
     if (l4_hdr_start > data_end) {
         return DISCARD;
     }
-    __builtin_memset(&l4_info, 0, sizeof(l4_info));
+    flow_id *id = pkt->id;
+    /* Save the IP Address to id directly. copy once. */
     __builtin_memcpy(id->src_ip, ip->saddr.in6_u.u6_addr8, IP_MAX_LEN);
     __builtin_memcpy(id->dst_ip, ip->daddr.in6_u.u6_addr8, IP_MAX_LEN);
-    id->transport_protocol = ip->nexthdr;
-    fill_l4info(l4_hdr_start, data_end, ip->nexthdr, &l4_info);
-    id->src_port = l4_info.src_port;
-    id->dst_port = l4_info.dst_port;
-    id->icmp_type = l4_info.icmp_type;
-    id->icmp_code = l4_info.icmp_code;
-    *flags = l4_info.flags;
 
+    /* fill l4 header which will be added to id in flow_monitor function.*/
+    fill_l4info(l4_hdr_start, data_end, ip->nexthdr, pkt);
     return SUBMIT;
 }
 
 // sets flow fields from Ethernet header information
-static inline int fill_ethhdr(struct ethhdr *eth, void *data_end, flow_id *id, u16 *flags) {
+static inline int fill_ethhdr(struct ethhdr *eth, void *data_end, pkt_info *pkt) {
     if ((void *)eth + sizeof(*eth) > data_end) {
         return DISCARD;
     }
+    flow_id *id = pkt->id;
     __builtin_memcpy(id->dst_mac, eth->h_dest, ETH_ALEN);
     __builtin_memcpy(id->src_mac, eth->h_source, ETH_ALEN);
     id->eth_protocol = bpf_ntohs(eth->h_proto);
 
     if (id->eth_protocol == ETH_P_IP) {
         struct iphdr *ip = (void *)eth + sizeof(*eth);
-        return fill_iphdr(ip, data_end, id, flags);
+        return fill_iphdr(ip, data_end, pkt);
     } else if (id->eth_protocol == ETH_P_IPV6) {
         struct ipv6hdr *ip6 = (void *)eth + sizeof(*eth);
-        return fill_ip6hdr(ip6, data_end, id, flags);
+        return fill_ip6hdr(ip6, data_end, pkt);
     } else {
         // TODO : Need to implement other specific ethertypes if needed
         // For now other parts of flow id remain zero
diff --git a/docs/config.md b/docs/config.md
index 22827e9c92becf09238aaf92d19c3c134b5996e9..d456199aec9e3a9c1a0d3fcaacf30903ea032cd5 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -63,6 +63,8 @@ The following environment variables are available to configure the NetObserv eBF
   * `KAFKA_TLS_USER_KEY_PATH` (default: unset). Path to the user (client) private key for mutual TLS connections.
 * `PROFILE_PORT` (default: unset). Sets the listening port for [Go's Pprof tool](https://pkg.go.dev/net/http/pprof).
   If it is not set, profile is disabled.
+* `ENABLE_RTT` (default: `false` disabled). If `true` enables RTT calculations for the captured flows in the ebpf agent.
+  See [docs](./rtt_calculations.md) for more details on this feature.
 
 ## Development-only variables
 
diff --git a/docs/rtt_calculations.md b/docs/rtt_calculations.md
new file mode 100644
index 0000000000000000000000000000000000000000..6d96b12368debbc192dce79a9e98b6abf4393a0b
--- /dev/null
+++ b/docs/rtt_calculations.md
@@ -0,0 +1,27 @@
+# RTT calculations done by ebpf-agent
+
+This agent has the capablity to perform Round-Trip-Time calculations for packet flows. Currently the agent will capture and report RTT for tcp handshake only
+but can be extended to any other protocol.
+
+The design of the system is like this,
+1. For every SYN packet that gets detected at Egress, the agent will capture standard 4-tuple information and packet sequence id and put it into a `flow_sequences` ebpf map as key and the value of which will be set to timestamp the packet was detected.
+
+1. Now for every ACK packet that gets detected at Ingress, the agent will check if the 4-tuple information (reversed for incoming flow) and sequence id (sequence id of ACK - 1) is present in the `flow_sequences` hashmap, if so it will calculate the handshake RTT as,
+`rtt = ack-timestampack - syn-timestamp(from map)`
+
+1. This approach is very simple but can be extended to perform continous RTT tracking for a TCP flow or perform RTT tracking for any other protocol like, ICMP etc.
+
+This rtt in flow logs is reported as, actual RTT for the flow logs which is present and can be calculated (handshake packets), zero for flows where it is not calculated yet (any protocols other than TCP) or is not present (non handshake tcp packets).
+
+## Concerns
+
+### Packet Retransmissions:
+
+In case of packet retransmissions the behavior of tracker is as follows,
+
+1. If SYN packet is retransmitted only the last SYN packet is taken into account which is correct behavior.
+
+1. If ACK packet is retransmitted the last ACK will be considered (the ACK which finally got received by receiver),
+in that case while the behavior of our program is as expected, because receiver will only see one and the last ACK but
+the RTT reported by the receiver will be much higher than the actual number.
+For now, this is an erroneous case and can be fixed later by doing either continous or multiple RTT monitoring per flow.
\ No newline at end of file
diff --git a/examples/flowlogs-dump/server/flowlogs-dump-collector.go b/examples/flowlogs-dump/server/flowlogs-dump-collector.go
index d04af1c022a14067da099dffeef60c656c9a6449..b083a96ee300405b804abd30edc4584b290b7e76 100644
--- a/examples/flowlogs-dump/server/flowlogs-dump-collector.go
+++ b/examples/flowlogs-dump/server/flowlogs-dump-collector.go
@@ -72,7 +72,7 @@ func main() {
 	for records := range receivedRecords {
 		for _, record := range records.Entries {
 			if record.EthProtocol == ipv6 {
-				log.Printf("%s: %v %s IP %s:%d > %s:%d: protocol:%s type: %d code: %d dir:%d bytes:%d packets:%d flags:%d ends: %v dnsId: %d dnsFlags: 0x%04x dnsReq: %v dnsRsp: %v\n",
+				log.Printf("%s: %v %s IP %s:%d > %s:%d: protocol:%s type: %d code: %d dir:%d bytes:%d packets:%d flags:%d ends: %v dnsId: %d dnsFlags: 0x%04x dnsReq: %v dnsRsp: %v rtt %v\n",
 					ipProto[record.EthProtocol],
 					record.TimeFlowStart.AsTime().Local().Format("15:04:05.000000"),
 					record.Interface,
@@ -92,9 +92,10 @@ func main() {
 					record.GetDnsFlags(),
 					record.GetTimeDnsReq(),
 					record.GetTimeDnsRsp(),
+					record.TimeFlowRtt.AsDuration().Microseconds(),
 				)
 			} else {
-				log.Printf("%s: %v %s IP %s:%d > %s:%d: protocol:%s type: %d code: %d dir:%d bytes:%d packets:%d flags:%d ends: %v dnsId: %d dnsFlags: 0x%04x dnsReq: %v dnsRsp: %v\n",
+				log.Printf("%s: %v %s IP %s:%d > %s:%d: protocol:%s type: %d code: %d dir:%d bytes:%d packets:%d flags:%d ends: %v dnsId: %d dnsFlags: 0x%04x dnsReq: %v dnsRsp: %v rtt %v\n",
 					ipProto[record.EthProtocol],
 					record.TimeFlowStart.AsTime().Local().Format("15:04:05.000000"),
 					record.Interface,
@@ -114,6 +115,7 @@ func main() {
 					record.GetDnsFlags(),
 					record.GetTimeDnsReq(),
 					record.GetTimeDnsRsp(),
+					record.TimeFlowRtt.AsDuration().Microseconds(),
 				)
 			}
 		}
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index 2553feef640a830a3fa135160a05140a92327e25..18d71990ce849a4428a17ec2ee9b7a769dce8021 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -116,13 +116,23 @@ func FlowsAgent(cfg *Config) (*Flows, error) {
 	}
 
 	ingress, egress := flowDirections(cfg)
-
 	debug := false
 	if cfg.LogLevel == logrus.TraceLevel.String() || cfg.LogLevel == logrus.DebugLevel.String() {
 		debug = true
 	}
 
-	fetcher, err := ebpf.NewFlowFetcher(debug, cfg.Sampling, cfg.CacheMaxFlows, ingress, egress, cfg.EnableTCPDrops, cfg.EnableDNSTracking)
+	ebpfConfig := &ebpf.FlowFetcherConfig{
+		EnableIngress: ingress,
+		EnableEgress:  egress,
+		Debug:         debug,
+		Sampling:      cfg.Sampling,
+		CacheMaxSize:  cfg.CacheMaxFlows,
+		TCPDrops:      cfg.EnableTCPDrops,
+		DNSTracker:    cfg.EnableDNSTracking,
+		EnableRTT:     cfg.EnableRTT,
+	}
+
+	fetcher, err := ebpf.NewFlowFetcher(ebpfConfig)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/agent/config.go b/pkg/agent/config.go
index 7cc1462954a66af09df047574fa959bac3eb7e3b..71b0b7ddb2d54cc6ebd30be76d3d0936ad4dd219 100644
--- a/pkg/agent/config.go
+++ b/pkg/agent/config.go
@@ -136,6 +136,10 @@ type Config struct {
 	KafkaSASLClientSecretPath string `env:"KAFKA_SASL_CLIENT_SECRET_PATH"`
 	// ProfilePort sets the listening port for Go's Pprof tool. If it is not set, profile is disabled
 	ProfilePort int `env:"PROFILE_PORT"`
+	// Enable RTT calculations for the flows, default is false (disabled), set to true to enable.
+	// This feature requires the flows agent to attach at both Ingress and Egress hookpoints.
+	// If both Ingress and Egress are not enabled then this feature will not be enabled even if set to true via env.
+	EnableRTT bool `env:"ENABLE_RTT" envDefault:"false"`
 	// EnableGC enables golang garbage collection run at the end of every map eviction, default is true
 	EnableGC bool `env:"ENABLE_GARBAGE_COLLECTION" envDefault:"true"`
 	// EnableTcpDrops enable TCP drops eBPF hook to account for tcp dropped flows
diff --git a/pkg/ebpf/bpf_bpfeb.go b/pkg/ebpf/bpf_bpfeb.go
index c1037c2d1e62eef6b72db0810818e1d0f0787a10..3171606c1f2ce6b5a39a90f6bd5ba5bb4094551d 100644
--- a/pkg/ebpf/bpf_bpfeb.go
+++ b/pkg/ebpf/bpf_bpfeb.go
@@ -48,6 +48,7 @@ type BpfFlowMetricsT struct {
 	Errno           uint8
 	TcpDrops        BpfTcpDropsT
 	DnsRecord       BpfDnsRecordT
+	FlowRtt         uint64
 }
 
 type BpfFlowRecordT struct {
@@ -55,6 +56,14 @@ type BpfFlowRecordT struct {
 	Metrics BpfFlowMetrics
 }
 
+type BpfFlowSeqId struct {
+	SrcPort uint16
+	DstPort uint16
+	SrcIp   [16]uint8
+	DstIp   [16]uint8
+	SeqId   uint32
+}
+
 type BpfTcpDropsT struct {
 	Packets         uint32
 	Bytes           uint64
@@ -116,6 +125,7 @@ type BpfProgramSpecs struct {
 type BpfMapSpecs struct {
 	AggregatedFlows *ebpf.MapSpec `ebpf:"aggregated_flows"`
 	DirectFlows     *ebpf.MapSpec `ebpf:"direct_flows"`
+	FlowSequences   *ebpf.MapSpec `ebpf:"flow_sequences"`
 }
 
 // BpfObjects contains all objects after they have been loaded into the kernel.
@@ -139,12 +149,14 @@ func (o *BpfObjects) Close() error {
 type BpfMaps struct {
 	AggregatedFlows *ebpf.Map `ebpf:"aggregated_flows"`
 	DirectFlows     *ebpf.Map `ebpf:"direct_flows"`
+	FlowSequences   *ebpf.Map `ebpf:"flow_sequences"`
 }
 
 func (m *BpfMaps) Close() error {
 	return _BpfClose(
 		m.AggregatedFlows,
 		m.DirectFlows,
+		m.FlowSequences,
 	)
 }
 
@@ -177,6 +189,5 @@ func _BpfClose(closers ...io.Closer) error {
 }
 
 // Do not access this directly.
-//
 //go:embed bpf_bpfeb.o
 var _BpfBytes []byte
diff --git a/pkg/ebpf/bpf_bpfeb.o b/pkg/ebpf/bpf_bpfeb.o
index cdaf6f13945bac619aaad7889be15ae739665777..92303ee5b710b77162cc4a4ebdea73096ff99b66 100644
Binary files a/pkg/ebpf/bpf_bpfeb.o and b/pkg/ebpf/bpf_bpfeb.o differ
diff --git a/pkg/ebpf/bpf_bpfel.go b/pkg/ebpf/bpf_bpfel.go
index 4aa8bfd8ce821b31be21e859b498dacc3541e2f7..32e900b5beba46c86420e33a7d0cadadc4887b13 100644
--- a/pkg/ebpf/bpf_bpfel.go
+++ b/pkg/ebpf/bpf_bpfel.go
@@ -48,6 +48,7 @@ type BpfFlowMetricsT struct {
 	Errno           uint8
 	TcpDrops        BpfTcpDropsT
 	DnsRecord       BpfDnsRecordT
+	FlowRtt         uint64
 }
 
 type BpfFlowRecordT struct {
@@ -55,6 +56,14 @@ type BpfFlowRecordT struct {
 	Metrics BpfFlowMetrics
 }
 
+type BpfFlowSeqId struct {
+	SrcPort uint16
+	DstPort uint16
+	SrcIp   [16]uint8
+	DstIp   [16]uint8
+	SeqId   uint32
+}
+
 type BpfTcpDropsT struct {
 	Packets         uint32
 	Bytes           uint64
@@ -116,6 +125,7 @@ type BpfProgramSpecs struct {
 type BpfMapSpecs struct {
 	AggregatedFlows *ebpf.MapSpec `ebpf:"aggregated_flows"`
 	DirectFlows     *ebpf.MapSpec `ebpf:"direct_flows"`
+	FlowSequences   *ebpf.MapSpec `ebpf:"flow_sequences"`
 }
 
 // BpfObjects contains all objects after they have been loaded into the kernel.
@@ -139,12 +149,14 @@ func (o *BpfObjects) Close() error {
 type BpfMaps struct {
 	AggregatedFlows *ebpf.Map `ebpf:"aggregated_flows"`
 	DirectFlows     *ebpf.Map `ebpf:"direct_flows"`
+	FlowSequences   *ebpf.Map `ebpf:"flow_sequences"`
 }
 
 func (m *BpfMaps) Close() error {
 	return _BpfClose(
 		m.AggregatedFlows,
 		m.DirectFlows,
+		m.FlowSequences,
 	)
 }
 
@@ -177,6 +189,5 @@ func _BpfClose(closers ...io.Closer) error {
 }
 
 // Do not access this directly.
-//
 //go:embed bpf_bpfel.o
 var _BpfBytes []byte
diff --git a/pkg/ebpf/bpf_bpfel.o b/pkg/ebpf/bpf_bpfel.o
index df2199eef85efc66c71fe22bac3642b760bfa572..4e3eb08e6013c0cb8dee3457fb206dfb289988a3 100644
Binary files a/pkg/ebpf/bpf_bpfel.o and b/pkg/ebpf/bpf_bpfel.o differ
diff --git a/pkg/ebpf/tracer.go b/pkg/ebpf/tracer.go
index 29eba8bf1e42da903df43a4ddbf6376c2a20e73d..c59cab71155515ac9e5f7fc0bd10040fce7febb8 100644
--- a/pkg/ebpf/tracer.go
+++ b/pkg/ebpf/tracer.go
@@ -22,10 +22,13 @@ import (
 
 const (
 	qdiscType = "clsact"
+	// ebpf map names as defined in flows.c
+	aggregatedFlowsMap = "aggregated_flows"
+	flowSequencesMap   = "flow_sequences"
 	// constants defined in flows.c as "volatile const"
 	constSampling      = "sampling"
 	constTraceMessages = "trace_messages"
-	aggregatedFlowsMap = "aggregated_flows"
+	constEnableRtt     = "enable_rtt"
 )
 
 var log = logrus.WithField("component", "ebpf.FlowFetcher")
@@ -47,11 +50,18 @@ type FlowFetcher struct {
 	dnsTrackerTracePoint link.Link
 }
 
-func NewFlowFetcher(
-	traceMessages bool,
-	sampling, cacheMaxSize int,
-	ingress, egress, tcpDrops, dnsTracker bool,
-) (*FlowFetcher, error) {
+type FlowFetcherConfig struct {
+	EnableIngress bool
+	EnableEgress  bool
+	Debug         bool
+	Sampling      int
+	CacheMaxSize  int
+	TCPDrops      bool
+	DNSTracker    bool
+	EnableRTT     bool
+}
+
+func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) {
 	if err := rlimit.RemoveMemlock(); err != nil {
 		log.WithError(err).
 			Warn("can't remove mem lock. The agent could not be able to start eBPF programs")
@@ -63,16 +73,34 @@ func NewFlowFetcher(
 		return nil, fmt.Errorf("loading BPF data: %w", err)
 	}
 
-	// Resize aggregated flows map according to user-provided configuration
-	spec.Maps[aggregatedFlowsMap].MaxEntries = uint32(cacheMaxSize)
+	// Resize maps according to user-provided configuration
+	spec.Maps[aggregatedFlowsMap].MaxEntries = uint32(cfg.CacheMaxSize)
+	spec.Maps[flowSequencesMap].MaxEntries = uint32(cfg.CacheMaxSize)
 
 	traceMsgs := 0
-	if traceMessages {
+	if cfg.Debug {
 		traceMsgs = 1
 	}
+
+	enableRtt := 0
+	if cfg.EnableRTT {
+		if !(cfg.EnableEgress && cfg.EnableIngress) {
+			log.Warnf("ENABLE_RTT is set to true. But both Ingress AND Egress are not enabled. Disabling ENABLE_RTT")
+			enableRtt = 0
+		} else {
+			enableRtt = 1
+		}
+	}
+
+	if enableRtt == 0 {
+		// Cannot set the size of map to be 0 so set it to 1.
+		spec.Maps[flowSequencesMap].MaxEntries = uint32(1)
+	}
+
 	if err := spec.RewriteConstants(map[string]interface{}{
-		constSampling:      uint32(sampling),
+		constSampling:      uint32(cfg.Sampling),
 		constTraceMessages: uint8(traceMsgs),
+		constEnableRtt:     uint8(enableRtt),
 	}); err != nil {
 		return nil, fmt.Errorf("rewriting BPF constants definition: %w", err)
 	}
@@ -86,6 +114,7 @@ func NewFlowFetcher(
 		}
 		return nil, fmt.Errorf("loading and assigning BPF objects: %w", err)
 	}
+
 	/*
 	 * since we load the program only when the we start we need to release
 	 * memory used by cached kernel BTF see https://github.com/cilium/ebpf/issues/1063
@@ -94,7 +123,7 @@ func NewFlowFetcher(
 	btf.FlushKernelSpec()
 
 	var tcpDropsLink link.Link
-	if tcpDrops {
+	if cfg.TCPDrops {
 		tcpDropsLink, err = link.Tracepoint("skb", "kfree_skb", objects.KfreeSkb, nil)
 		if err != nil {
 			return nil, fmt.Errorf("failed to attach the BPF program to kfree_skb tracepoint: %w", err)
@@ -102,7 +131,7 @@ func NewFlowFetcher(
 	}
 
 	var dnsTrackerLink link.Link
-	if dnsTracker {
+	if cfg.DNSTracker {
 		dnsTrackerLink, err = link.Tracepoint("net", "net_dev_queue", objects.TraceNetPackets, nil)
 		if err != nil {
 			return nil, fmt.Errorf("failed to attach the BPF program to trace_net_packets: %w", err)
@@ -120,9 +149,9 @@ func NewFlowFetcher(
 		egressFilters:        map[ifaces.Interface]*netlink.BpfFilter{},
 		ingressFilters:       map[ifaces.Interface]*netlink.BpfFilter{},
 		qdiscs:               map[ifaces.Interface]*netlink.GenericQdisc{},
-		cacheMaxSize:         cacheMaxSize,
-		enableIngress:        ingress,
-		enableEgress:         egress,
+		cacheMaxSize:         cfg.CacheMaxSize,
+		enableIngress:        cfg.EnableIngress,
+		enableEgress:         cfg.EnableEgress,
 		tcpDropsTracePoint:   tcpDropsLink,
 		dnsTrackerTracePoint: dnsTrackerLink,
 	}, nil
diff --git a/pkg/exporter/proto.go b/pkg/exporter/proto.go
index df460b1ba615327ec5fc2220158b590528d22af9..1fa61db04a02d7f82026ba50c305ae745a14e17f 100644
--- a/pkg/exporter/proto.go
+++ b/pkg/exporter/proto.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/netobserv/netobserv-ebpf-agent/pkg/flow"
 	"github.com/netobserv/netobserv-ebpf-agent/pkg/pbflow"
+	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
 )
 
@@ -77,6 +78,7 @@ func v4FlowToPB(fr *flow.Record) *pbflow.Record {
 		TcpDropLatestDropCause: fr.Metrics.TcpDrops.LatestDropCause,
 		DnsId:                  uint32(fr.Metrics.DnsRecord.Id),
 		DnsFlags:               uint32(fr.Metrics.DnsRecord.Flags),
+		TimeFlowRtt:            durationpb.New(fr.TimeFlowRtt),
 	}
 	if fr.Metrics.DnsRecord.ReqMonoTimeTs != 0 {
 		pbflowRecord.TimeDnsReq = &timestamppb.Timestamp{
@@ -133,6 +135,7 @@ func v6FlowToPB(fr *flow.Record) *pbflow.Record {
 		TcpDropLatestDropCause: fr.Metrics.TcpDrops.LatestDropCause,
 		DnsId:                  uint32(fr.Metrics.DnsRecord.Id),
 		DnsFlags:               uint32(fr.Metrics.DnsRecord.Flags),
+		TimeFlowRtt:            durationpb.New(fr.TimeFlowRtt),
 	}
 	if fr.Metrics.DnsRecord.ReqMonoTimeTs != 0 {
 		pbflowRecord.TimeDnsReq = &timestamppb.Timestamp{
diff --git a/pkg/flow/record.go b/pkg/flow/record.go
index d8313b1cd2aaafa0940954b07793475570ee67c6..ef9ea23f9c46ba09bc7581a27e8e28005d09bbdb 100644
--- a/pkg/flow/record.go
+++ b/pkg/flow/record.go
@@ -52,6 +52,8 @@ type Record struct {
 
 	// AgentIP provides information about the source of the flow (the Agent that traced it)
 	AgentIP net.IP
+	// Calculated RTT which is set when record is created by calling NewRecord
+	TimeFlowRtt time.Duration
 }
 
 func NewRecord(
@@ -62,7 +64,7 @@ func NewRecord(
 ) *Record {
 	startDelta := time.Duration(monotonicCurrentTime - metrics.StartMonoTimeTs)
 	endDelta := time.Duration(monotonicCurrentTime - metrics.EndMonoTimeTs)
-	var reqDNS, rspDNS time.Duration
+
 	var record = Record{
 		RawRecord: RawRecord{
 			Id:      key,
@@ -71,12 +73,16 @@ func NewRecord(
 		TimeFlowStart: currentTime.Add(-startDelta),
 		TimeFlowEnd:   currentTime.Add(-endDelta),
 	}
+	if metrics.FlowRtt != 0 {
+		rttDelta := time.Duration(metrics.FlowRtt)
+		record.TimeFlowRtt = rttDelta
+	}
 	if metrics.DnsRecord.ReqMonoTimeTs != 0 {
-		reqDNS = time.Duration(monotonicCurrentTime - metrics.DnsRecord.ReqMonoTimeTs)
+		reqDNS := time.Duration(monotonicCurrentTime - metrics.DnsRecord.ReqMonoTimeTs)
 		record.TimeDNSRequest = currentTime.Add(-reqDNS)
 	}
 	if metrics.DnsRecord.RspMonoTimeTs != 0 {
-		rspDNS = time.Duration(monotonicCurrentTime - metrics.DnsRecord.RspMonoTimeTs)
+		rspDNS := time.Duration(monotonicCurrentTime - metrics.DnsRecord.RspMonoTimeTs)
 		record.TimeDNSResponse = currentTime.Add(-rspDNS)
 	}
 	return &record
diff --git a/pkg/flow/record_test.go b/pkg/flow/record_test.go
index fc6b0abed70f97b3aea053824d6ba6fae1a5c92e..772f4c715446f3a521bd52f18a81c70c9b02bdd3 100644
--- a/pkg/flow/record_test.go
+++ b/pkg/flow/record_test.go
@@ -43,6 +43,8 @@ func TestRecordBinaryEncoding(t *testing.T) {
 		0x80, 00, // flags
 		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // req ts
 		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, // rsp ts
+		// u64 flow_rtt
+		0xad, 0xde, 0xef, 0xbe, 0xef, 0xbe, 0xad, 0xde,
 	}))
 	require.NoError(t, err)
 
@@ -81,6 +83,7 @@ func TestRecordBinaryEncoding(t *testing.T) {
 				ReqMonoTimeTs: 0x1817161514131211,
 				RspMonoTimeTs: 0x2827262524232221,
 			},
+			FlowRtt: 0xdeadbeefbeefdead,
 		},
 	}, *fr)
 	// assert that IP addresses are interpreted as IPv4 addresses
diff --git a/pkg/pbflow/flow.pb.go b/pkg/pbflow/flow.pb.go
index 60076f902b7fbd7269942076e72975466c261031..c14ac8d33ad8755c1ee730146baebc13138468d5 100644
--- a/pkg/pbflow/flow.pb.go
+++ b/pkg/pbflow/flow.pb.go
@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.25.0-devel
-// 	protoc        v3.14.0
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.19.4
 // source: proto/flow.proto
 
 package pbflow
@@ -9,6 +9,7 @@ package pbflow
 import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	durationpb "google.golang.org/protobuf/types/known/durationpb"
 	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	sync "sync"
@@ -190,6 +191,7 @@ type Record struct {
 	DnsFlags               uint32                 `protobuf:"varint,22,opt,name=dns_flags,json=dnsFlags,proto3" json:"dns_flags,omitempty"`
 	TimeDnsReq             *timestamppb.Timestamp `protobuf:"bytes,23,opt,name=time_dns_req,json=timeDnsReq,proto3" json:"time_dns_req,omitempty"`
 	TimeDnsRsp             *timestamppb.Timestamp `protobuf:"bytes,24,opt,name=time_dns_rsp,json=timeDnsRsp,proto3" json:"time_dns_rsp,omitempty"`
+	TimeFlowRtt            *durationpb.Duration   `protobuf:"bytes,25,opt,name=time_flow_rtt,json=timeFlowRtt,proto3" json:"time_flow_rtt,omitempty"`
 }
 
 func (x *Record) Reset() {
@@ -392,6 +394,13 @@ func (x *Record) GetTimeDnsRsp() *timestamppb.Timestamp {
 	return nil
 }
 
+func (x *Record) GetTimeFlowRtt() *durationpb.Duration {
+	if x != nil {
+		return x.TimeFlowRtt
+	}
+	return nil
+}
+
 type DataLink struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -653,12 +662,14 @@ var file_proto_flow_proto_rawDesc = []byte{
 	0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f,
 	0x74, 0x6f, 0x12, 0x06, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
 	0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
-	0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x10, 0x0a, 0x0e, 0x43,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x10, 0x0a, 0x0e, 0x43,
 	0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x33, 0x0a,
 	0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72,
 	0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x66, 0x6c,
 	0x6f, 0x77, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69,
-	0x65, 0x73, 0x22, 0xf0, 0x07, 0x0a, 0x06, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a,
+	0x65, 0x73, 0x22, 0xaf, 0x08, 0x0a, 0x06, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a,
 	0x0c, 0x65, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20,
 	0x01, 0x28, 0x0d, 0x52, 0x0b, 0x65, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
 	0x12, 0x2f, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
@@ -721,34 +732,38 @@ var file_proto_flow_proto_rawDesc = []byte{
 	0x64, 0x6e, 0x73, 0x5f, 0x72, 0x73, 0x70, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
 	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
 	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x44,
-	0x6e, 0x73, 0x52, 0x73, 0x70, 0x22, 0x3c, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x69, 0x6e,
-	0x6b, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x72, 0x63, 0x5f, 0x6d, 0x61, 0x63, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x06, 0x73, 0x72, 0x63, 0x4d, 0x61, 0x63, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x73,
-	0x74, 0x5f, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x73, 0x74,
-	0x4d, 0x61, 0x63, 0x22, 0x57, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x25,
-	0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x0a, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x49, 0x50, 0x52, 0x07, 0x73, 0x72,
-	0x63, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64,
-	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77,
-	0x2e, 0x49, 0x50, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x3d, 0x0a, 0x02,
-	0x49, 0x50, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x07,
-	0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x42, 0x0b,
-	0x0a, 0x09, 0x69, 0x70, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x5d, 0x0a, 0x09, 0x54,
-	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f,
-	0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x72, 0x63, 0x50,
-	0x6f, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x64, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a,
-	0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
-	0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2a, 0x24, 0x0a, 0x09, 0x44, 0x69,
-	0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45,
-	0x53, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01,
-	0x32, 0x3e, 0x0a, 0x09, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a,
-	0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x0f, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x52,
-	0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e,
-	0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
-	0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x62, 0x06, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x33,
+	0x6e, 0x73, 0x52, 0x73, 0x70, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x66, 0x6c,
+	0x6f, 0x77, 0x5f, 0x72, 0x74, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44,
+	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x46, 0x6c, 0x6f,
+	0x77, 0x52, 0x74, 0x74, 0x22, 0x3c, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x69, 0x6e, 0x6b,
+	0x12, 0x17, 0x0a, 0x07, 0x73, 0x72, 0x63, 0x5f, 0x6d, 0x61, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x06, 0x73, 0x72, 0x63, 0x4d, 0x61, 0x63, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x73, 0x74,
+	0x5f, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x73, 0x74, 0x4d,
+	0x61, 0x63, 0x22, 0x57, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x25, 0x0a,
+	0x08, 0x73, 0x72, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x0a, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x49, 0x50, 0x52, 0x07, 0x73, 0x72, 0x63,
+	0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e,
+	0x49, 0x50, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x3d, 0x0a, 0x02, 0x49,
+	0x50, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x07, 0x48,
+	0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x42, 0x0b, 0x0a,
+	0x09, 0x69, 0x70, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x5d, 0x0a, 0x09, 0x54, 0x72,
+	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x70,
+	0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x72, 0x63, 0x50, 0x6f,
+	0x72, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x64, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a,
+	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2a, 0x24, 0x0a, 0x09, 0x44, 0x69, 0x72,
+	0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53,
+	0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x32,
+	0x3e, 0x0a, 0x09, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x04,
+	0x53, 0x65, 0x6e, 0x64, 0x12, 0x0f, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x52, 0x65,
+	0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x43,
+	0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42,
+	0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x62, 0x66, 0x6c, 0x6f, 0x77, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
 }
 
 var (
@@ -775,6 +790,7 @@ var file_proto_flow_proto_goTypes = []interface{}{
 	(*IP)(nil),                    // 6: pbflow.IP
 	(*Transport)(nil),             // 7: pbflow.Transport
 	(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
+	(*durationpb.Duration)(nil),   // 9: google.protobuf.Duration
 }
 var file_proto_flow_proto_depIdxs = []int32{
 	3,  // 0: pbflow.Records.entries:type_name -> pbflow.Record
@@ -787,15 +803,16 @@ var file_proto_flow_proto_depIdxs = []int32{
 	6,  // 7: pbflow.Record.agent_ip:type_name -> pbflow.IP
 	8,  // 8: pbflow.Record.time_dns_req:type_name -> google.protobuf.Timestamp
 	8,  // 9: pbflow.Record.time_dns_rsp:type_name -> google.protobuf.Timestamp
-	6,  // 10: pbflow.Network.src_addr:type_name -> pbflow.IP
-	6,  // 11: pbflow.Network.dst_addr:type_name -> pbflow.IP
-	2,  // 12: pbflow.Collector.Send:input_type -> pbflow.Records
-	1,  // 13: pbflow.Collector.Send:output_type -> pbflow.CollectorReply
-	13, // [13:14] is the sub-list for method output_type
-	12, // [12:13] is the sub-list for method input_type
-	12, // [12:12] is the sub-list for extension type_name
-	12, // [12:12] is the sub-list for extension extendee
-	0,  // [0:12] is the sub-list for field type_name
+	9,  // 10: pbflow.Record.time_flow_rtt:type_name -> google.protobuf.Duration
+	6,  // 11: pbflow.Network.src_addr:type_name -> pbflow.IP
+	6,  // 12: pbflow.Network.dst_addr:type_name -> pbflow.IP
+	2,  // 13: pbflow.Collector.Send:input_type -> pbflow.Records
+	1,  // 14: pbflow.Collector.Send:output_type -> pbflow.CollectorReply
+	14, // [14:15] is the sub-list for method output_type
+	13, // [13:14] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
 }
 
 func init() { file_proto_flow_proto_init() }
diff --git a/pkg/pbflow/flow_grpc.pb.go b/pkg/pbflow/flow_grpc.pb.go
index 6b2e9616cc2f543ee6833cfee36292cdfb3a7d2c..fccb880954ed93e893e8284125931954f4962fb1 100644
--- a/pkg/pbflow/flow_grpc.pb.go
+++ b/pkg/pbflow/flow_grpc.pb.go
@@ -1,4 +1,8 @@
 // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             v3.19.4
+// source: proto/flow.proto
 
 package pbflow
 
@@ -14,6 +18,10 @@ import (
 // Requires gRPC-Go v1.32.0 or later.
 const _ = grpc.SupportPackageIsVersion7
 
+const (
+	Collector_Send_FullMethodName = "/pbflow.Collector/Send"
+)
+
 // CollectorClient is the client API for Collector service.
 //
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
@@ -31,7 +39,7 @@ func NewCollectorClient(cc grpc.ClientConnInterface) CollectorClient {
 
 func (c *collectorClient) Send(ctx context.Context, in *Records, opts ...grpc.CallOption) (*CollectorReply, error) {
 	out := new(CollectorReply)
-	err := c.cc.Invoke(ctx, "/pbflow.Collector/Send", in, out, opts...)
+	err := c.cc.Invoke(ctx, Collector_Send_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -76,7 +84,7 @@ func _Collector_Send_Handler(srv interface{}, ctx context.Context, dec func(inte
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/pbflow.Collector/Send",
+		FullMethod: Collector_Send_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(CollectorServer).Send(ctx, req.(*Records))
diff --git a/proto/flow.proto b/proto/flow.proto
index fb854059fb8eb243edf4f30fc53c576605805e66..1b15f0f69d8d56e0b1b75e75899fa059f13a98a0 100644
--- a/proto/flow.proto
+++ b/proto/flow.proto
@@ -3,6 +3,7 @@ syntax = "proto3";
 package pbflow;
 
 import 'google/protobuf/timestamp.proto';
+import 'google/protobuf/duration.proto';
 
 option go_package = "./pbflow";
 
@@ -52,6 +53,7 @@ message Record {
   uint32 dns_flags = 22;
   google.protobuf.Timestamp time_dns_req = 23;
   google.protobuf.Timestamp time_dns_rsp = 24;
+  google.protobuf.Duration time_flow_rtt = 25;
 }
 
 message DataLink {