[PATCH RFC v3 2/2] tun: support rx-tstamp
From: Steffen Trumtrar
Date: Mon Sep 21 2026 - 10:47:39 EST
Demonstrate support for new virtio-net features
VIRTIO_NET_HDR_F_TSTAMP
This is not intended to be merged.
A full feature test also requires a patched qemu binary that knows
these features and negotiates correct vnet_hdr_sz in
virtio_net_set_mrg_rx_bufs. See
https://github.com/strumtrar/qemu/tree/v11.0.1/virtio-rx-stamps
Not-yet-signed-off-by: Steffen Trumtrar <s.trumtrar@xxxxxxxxxxxxxx>
---
Changes since v2:
- use tstamp from virtio_net_common_hdr
- use the negotiated vnet_hdr_sz in __tun_vnet_hdr_get
Signed-off-by: Steffen Trumtrar <s.trumtrar@xxxxxxxxxxxxxx>
---
drivers/net/tun.c | 20 +++++++++++++++-----
drivers/net/tun_vnet.h | 27 ++++++++++++++++-----------
drivers/net/virtio_net.c | 11 -----------
include/uapi/linux/virtio_net.h | 11 +++++++++++
4 files changed, 42 insertions(+), 27 deletions(-)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 5a302709a68aa..924784771b6b7 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -2157,12 +2157,23 @@ 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));
+
+ /* hdr has at least the size up to and including the tstamp field. */
+ if (vnet_hdr_sz >= offsetof(struct virtio_net_common_hdr, tstamp) +
+ sizeof_field(struct virtio_net_common_hdr, tstamp)) {
+ u64 tstamp = ktime_get_clocktai_ns();
+
+ hdr.tstamp[0] = (tstamp & 0x000000000000ffffULL) >> 0;
+ hdr.tstamp[1] = (tstamp & 0x00000000ffff0000ULL) >> 16;
+ hdr.tstamp[2] = (tstamp & 0x0000ffff00000000ULL) >> 32;
+ hdr.tstamp[3] = (tstamp & 0xffff000000000000ULL) >> 48;
+ }
+
ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
- &hdr);
+ (struct virtio_net_hdr_v1_hash_tunnel *)&hdr);
if (ret)
return ret;
@@ -2170,9 +2181,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..0a7fe1ce889b5 100644
--- a/drivers/net/tun_vnet.h
+++ b/drivers/net/tun_vnet.h
@@ -109,12 +109,9 @@ static inline long tun_vnet_ioctl(int *vnet_hdr_sz, unsigned int *flags,
}
}
-static inline unsigned int tun_vnet_parse_size(netdev_features_t features)
+static inline unsigned int tun_vnet_parse_size(int vnet_hdr_sz)
{
- if (!(features & NETIF_F_GSO_UDP_TUNNEL))
- return sizeof(struct virtio_net_hdr);
-
- return TUN_VNET_TNL_SIZE;
+ return min_t(unsigned int, sizeof(struct virtio_net_common_hdr), vnet_hdr_sz);
}
static inline int __tun_vnet_hdr_get(int sz, unsigned int flags,
@@ -122,15 +119,20 @@ static inline int __tun_vnet_hdr_get(int sz, unsigned int flags,
struct iov_iter *from,
struct virtio_net_hdr *hdr)
{
- unsigned int parsed_size = tun_vnet_parse_size(features);
+ unsigned int parsed_size = tun_vnet_parse_size(sz);
+ u8 tmp[sizeof(struct virtio_net_common_hdr)]; // temp buffer with known size
u16 hdr_len;
if (iov_iter_count(from) < sz)
return -EINVAL;
- if (!copy_from_iter_full(hdr, parsed_size, from))
+ /* copy parsed size data to the tmp buffer, otherwise compiler will complain */
+ if (!copy_from_iter_full(tmp, parsed_size, from))
return -EFAULT;
+ /* now copy the relevant data from tmp to hdr */
+ memcpy(hdr, tmp, min(parsed_size, sizeof(*hdr)));
+
hdr_len = tun_vnet16_to_cpu(flags, hdr->hdr_len);
if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
@@ -155,14 +157,17 @@ static inline int tun_vnet_hdr_get(int sz, unsigned int flags,
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 parsed_size = tun_vnet_parse_size(sz);
+ u8 buf[sizeof(*hdr)];
if (unlikely(iov_iter_count(iter) < sz))
return -EINVAL;
- if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size))
+ memcpy(buf, hdr, parsed_size);
+
+ if (unlikely(copy_to_iter(buf, parsed_size, iter) != parsed_size))
return -EFAULT;
if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size)
@@ -174,7 +179,7 @@ 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);
+ return __tun_vnet_hdr_put(sz, 0, iter, (struct virtio_net_common_hdr *)hdr);
}
static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb,
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index ef6238cb336b9..60ae9392f9876 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -498,17 +498,6 @@ struct padded_vnet_hdr {
char padding[12];
};
-struct virtio_net_common_hdr {
- union {
- struct virtio_net_hdr hdr;
- struct virtio_net_hdr_mrg_rxbuf mrg_hdr;
- struct virtio_net_hdr_v1_hash hash_v1_hdr;
- struct virtio_net_hdr_v1_hash_tunnel tnl_hdr;
- };
-
- __le16 tstamp[4]; /* 64-bit timestamp, 2-byte aligned */
-};
-
static struct virtio_net_common_hdr xsk_hdr;
static void virtnet_sq_free_unused_buf(struct virtqueue *vq, void *buf);
diff --git a/include/uapi/linux/virtio_net.h b/include/uapi/linux/virtio_net.h
index 39977765b72aa..8fed76755dcb1 100644
--- a/include/uapi/linux/virtio_net.h
+++ b/include/uapi/linux/virtio_net.h
@@ -238,6 +238,17 @@ struct virtio_net_hdr_mrg_rxbuf {
struct virtio_net_hdr hdr;
__virtio16 num_buffers; /* Number of merged rx buffers */
};
+
+struct virtio_net_common_hdr {
+ union {
+ struct virtio_net_hdr hdr;
+ struct virtio_net_hdr_mrg_rxbuf mrg_hdr;
+ struct virtio_net_hdr_v1_hash hash_v1_hdr;
+ struct virtio_net_hdr_v1_hash_tunnel tnl_hdr;
+ };
+
+ __virtio16 tstamp[4]; /* 64-bit timestamp, 2-byte aligned */
+};
#endif /* ...VIRTIO_NET_NO_LEGACY */
/*
--
2.52.0