netfilter: conntrack: add conntrack event timestamp
authorFlorian Westphal <fw@strlen.de>
Fri, 15 Nov 2024 13:46:09 +0000 (14:46 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 9 Jan 2025 13:42:16 +0000 (14:42 +0100)
Nadia Pinaeva writes:
  I am working on a tool that allows collecting network performance
  metrics by using conntrack events.
  Start time of a conntrack entry is used to evaluate seen_reply
  latency, therefore the sooner it is timestamped, the better the
  precision is.
  In particular, when using this tool to compare the performance of the
  same feature implemented using iptables/nftables/OVS it is crucial
  to have the entry timestamped earlier to see any difference.

At this time, conntrack events can only get timestamped at recv time in
userspace, so there can be some delay between the event being generated
and the userspace process consuming the message.

There is sys/net/netfilter/nf_conntrack_timestamp, which adds a
64bit timestamp (ns resolution) that records start and stop times,
but its not suited for this either, start time is the 'hashtable insertion
time', not 'conntrack allocation time'.

There is concern that moving the start-time moment to conntrack
allocation will add overhead in case of flooding, where conntrack
entries are allocated and released right away without getting inserted
into the hashtable.

Also, even if this was changed it would not with events other than
new (start time) and destroy (stop time).

Pablo suggested to add new CTA_TIMESTAMP_EVENT, this adds this feature.
The timestamp is recorded in case both events are requested and the
sys/net/netfilter/nf_conntrack_timestamp toggle is enabled.

Reported-by: Nadia Pinaeva <n.m.pinaeva@gmail.com>
Suggested-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_conntrack_ecache.h
include/uapi/linux/netfilter/nfnetlink_conntrack.h
net/netfilter/nf_conntrack_ecache.c
net/netfilter/nf_conntrack_netlink.c

index 0c1dac318e02589287764a17257fda1a74521eb0..8dcf7c371ee9bf60fd3631ca29b56abdbf8e7248 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/netfilter/nf_conntrack_common.h>
 #include <linux/netfilter/nf_conntrack_tuple_common.h>
 #include <net/netfilter/nf_conntrack_extend.h>
+#include <asm/local64.h>
 
 enum nf_ct_ecache_state {
        NFCT_ECACHE_DESTROY_FAIL,       /* tried but failed to send destroy event */
@@ -20,6 +21,9 @@ enum nf_ct_ecache_state {
 
 struct nf_conntrack_ecache {
        unsigned long cache;            /* bitops want long */
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+       local64_t timestamp;            /* event timestamp, in nanoseconds */
+#endif
        u16 ctmask;                     /* bitmask of ct events to be delivered */
        u16 expmask;                    /* bitmask of expect events to be delivered */
        u32 missed;                     /* missed events */
@@ -108,6 +112,14 @@ nf_conntrack_event_cache(enum ip_conntrack_events event, struct nf_conn *ct)
        if (e == NULL)
                return;
 
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+       /* renew only if this is the first cached event, so that the
+        * timestamp reflects the first, not the last, generated event.
+        */
+       if (local64_read(&e->timestamp) && READ_ONCE(e->cache) == 0)
+               local64_set(&e->timestamp, ktime_get_real_ns());
+#endif
+
        set_bit(event, &e->cache);
 #endif
 }
index c2ac7269acf7c6dfcbaed4281d9300ad428b035a..43233af75b9d5ba263261caf8b55873e2e21d04d 100644 (file)
@@ -57,6 +57,7 @@ enum ctattr_type {
        CTA_SYNPROXY,
        CTA_FILTER,
        CTA_STATUS_MASK,
+       CTA_TIMESTAMP_EVENT,
        __CTA_MAX
 };
 #define CTA_MAX (__CTA_MAX - 1)
index 69948e1d6974e3509010f7aeffd8487228990b71..af68c64acaab7085997845b0b43de78d8c53ae50 100644 (file)
@@ -162,6 +162,14 @@ static int __nf_conntrack_eventmask_report(struct nf_conntrack_ecache *e,
        return ret;
 }
 
+static void nf_ct_ecache_tstamp_refresh(struct nf_conntrack_ecache *e)
+{
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+       if (local64_read(&e->timestamp))
+               local64_set(&e->timestamp, ktime_get_real_ns());
+#endif
+}
+
 int nf_conntrack_eventmask_report(unsigned int events, struct nf_conn *ct,
                                  u32 portid, int report)
 {
@@ -186,6 +194,8 @@ int nf_conntrack_eventmask_report(unsigned int events, struct nf_conn *ct,
        /* This is a resent of a destroy event? If so, skip missed */
        missed = e->portid ? 0 : e->missed;
 
+       nf_ct_ecache_tstamp_refresh(e);
+
        ret = __nf_conntrack_eventmask_report(e, events, missed, &item);
        if (unlikely(ret < 0 && (events & (1 << IPCT_DESTROY)))) {
                /* This is a destroy event that has been triggered by a process,
@@ -297,6 +307,18 @@ void nf_conntrack_ecache_work(struct net *net, enum nf_ct_ecache_state state)
        }
 }
 
+static void nf_ct_ecache_tstamp_new(const struct nf_conn *ct, struct nf_conntrack_ecache *e)
+{
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+       u64 ts = 0;
+
+       if (nf_ct_ext_exist(ct, NF_CT_EXT_TSTAMP))
+               ts = ktime_get_real_ns();
+
+       local64_set(&e->timestamp, ts);
+#endif
+}
+
 bool nf_ct_ecache_ext_add(struct nf_conn *ct, u16 ctmask, u16 expmask, gfp_t gfp)
 {
        struct net *net = nf_ct_net(ct);
@@ -326,6 +348,7 @@ bool nf_ct_ecache_ext_add(struct nf_conn *ct, u16 ctmask, u16 expmask, gfp_t gfp
 
        e = nf_ct_ext_add(ct, NF_CT_EXT_ECACHE, gfp);
        if (e) {
+               nf_ct_ecache_tstamp_new(ct, e);
                e->ctmask  = ctmask;
                e->expmask = expmask;
        }
index 36168f8b6efa3c7d69320aa091df12e43179aec8..2277b744eb2c0b68ae0569709b23435a607b4075 100644 (file)
@@ -383,6 +383,23 @@ nla_put_failure:
 #endif
 
 #ifdef CONFIG_NF_CONNTRACK_EVENTS
+static int
+ctnetlink_dump_event_timestamp(struct sk_buff *skb, const struct nf_conn *ct)
+{
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+       const struct nf_conntrack_ecache *e = nf_ct_ecache_find(ct);
+
+       if (e) {
+               u64 ts = local64_read(&e->timestamp);
+
+               if (ts)
+                       return nla_put_be64(skb, CTA_TIMESTAMP_EVENT,
+                                           cpu_to_be64(ts), CTA_TIMESTAMP_PAD);
+       }
+#endif
+       return 0;
+}
+
 static inline int ctnetlink_label_size(const struct nf_conn *ct)
 {
        struct nf_conn_labels *labels = nf_ct_labels_find(ct);
@@ -717,6 +734,9 @@ static size_t ctnetlink_nlmsg_size(const struct nf_conn *ct)
 #endif
               + ctnetlink_proto_size(ct)
               + ctnetlink_label_size(ct)
+#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
+              + nla_total_size(sizeof(u64)) /* CTA_TIMESTAMP_EVENT */
+#endif
               ;
 }
 
@@ -838,6 +858,10 @@ ctnetlink_conntrack_event(unsigned int events, const struct nf_ct_event *item)
        if (ctnetlink_dump_mark(skb, ct, events & (1 << IPCT_MARK)))
                goto nla_put_failure;
 #endif
+
+       if (ctnetlink_dump_event_timestamp(skb, ct))
+               goto nla_put_failure;
+
        nlmsg_end(skb, nlh);
        err = nfnetlink_send(skb, net, item->portid, group, item->report,
                             GFP_ATOMIC);
@@ -1557,6 +1581,7 @@ static const struct nla_policy ct_nla_policy[CTA_MAX+1] = {
                                    .len = NF_CT_LABELS_MAX_SIZE },
        [CTA_FILTER]            = { .type = NLA_NESTED },
        [CTA_STATUS_MASK]       = { .type = NLA_U32 },
+       [CTA_TIMESTAMP_EVENT]   = { .type = NLA_REJECT },
 };
 
 static int ctnetlink_flush_iterate(struct nf_conn *ct, void *data)