[PATCH RFC v4 6/6] tun: add rx timestamp to the vnet header
From: Steffen Trumtrar
Date: Mon Oct 26 2026 - 04:37:23 EST
Reference implementation of the device side of VIRTIO_NET_F_RX_TSTAMP: if
the configured vnet_hdr_sz covers the tstamp field, write the current
TAI time into the header when a packet is handed to userspace or vhost.
__tun_vnet_hdr_put() now takes the full struct virtio_net_common_hdr
and copies as much of it as vnet_hdr_sz holds.
This is not intended to be merged. A full test needs a QEMU that knows
the feature and sets the matching vnet_hdr_sz in
virtio_net_set_mrg_rx_bufs(); see
https://github.com/strumtrar/qemu/tree/v11.0.1/virtio-rx-stamps
Signed-off-by: Steffen Trumtrar <s.trumtrar@xxxxxxxxxxxxxx>
Assisted-by: Claude:claude-opus-5-5
---
drivers/net/tun.c | 14 +++++++++-----
drivers/net/tun_vnet.h | 18 ++++++++++++------
2 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 242899f7fd071..67699f9fc3d3d 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -57,6 +57,7 @@
#include <linux/math.h>
#include <linux/nsproxy.h>
#include <linux/virtio_net.h>
+#include <linux/unaligned.h>
#include <linux/rcupdate.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
@@ -2160,12 +2161,16 @@ static ssize_t tun_put_user(struct tun_struct *tun,
}
if (vnet_hdr_sz) {
- struct virtio_net_hdr_v1_hash_tunnel hdr;
- struct virtio_net_hdr *gso;
+ struct virtio_net_common_hdr hdr;
memset(&hdr, 0, sizeof(hdr));
+
+ /* The configured header covers the tstamp field */
+ if (vnet_hdr_sz >= offsetofend(struct virtio_net_common_hdr, tstamp))
+ put_unaligned_le64(ktime_get_clocktai_ns(), hdr.tstamp);
+
ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
- &hdr);
+ &hdr.tnl_hdr);
if (ret)
return ret;
@@ -2173,9 +2178,8 @@ static ssize_t tun_put_user(struct tun_struct *tun,
* Drop the packet if the configured header size is too small
* WRT the enabled offloads.
*/
- gso = (struct virtio_net_hdr *)&hdr;
ret = __tun_vnet_hdr_put(vnet_hdr_sz, tun->dev->features,
- iter, gso);
+ iter, &hdr);
if (ret)
return ret;
}
diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h
index f4c652b1fa44d..d4cc56dac98d6 100644
--- a/drivers/net/tun_vnet.h
+++ b/drivers/net/tun_vnet.h
@@ -153,19 +153,21 @@ static inline int tun_vnet_hdr_get(int sz, unsigned int flags,
return __tun_vnet_hdr_get(sz, flags, 0, from, hdr);
}
+/* hdr is fully initialized, copy as much of it as the configured size holds */
static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features,
struct iov_iter *iter,
- const struct virtio_net_hdr *hdr)
+ const struct virtio_net_common_hdr *hdr)
{
- unsigned int parsed_size = tun_vnet_parse_size(features);
+ unsigned int put_size = min_t(unsigned int, sizeof(*hdr), sz);
- if (unlikely(iov_iter_count(iter) < sz))
+ if (unlikely(iov_iter_count(iter) < sz ||
+ sz < tun_vnet_parse_size(features)))
return -EINVAL;
- if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size))
+ if (unlikely(copy_to_iter(hdr, put_size, iter) != put_size))
return -EFAULT;
- if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size)
+ if (iov_iter_zero(sz - put_size, iter) != sz - put_size)
return -EFAULT;
return 0;
@@ -174,7 +176,11 @@ static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features,
static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter,
const struct virtio_net_hdr *hdr)
{
- return __tun_vnet_hdr_put(sz, 0, iter, hdr);
+ struct virtio_net_common_hdr common_hdr = {};
+
+ common_hdr.hdr = *hdr;
+
+ return __tun_vnet_hdr_put(sz, 0, iter, &common_hdr);
}
static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb,
--
2.52.0