Skip to contentSkip to conversation

tun: add ioctl to modify vnet header size

RFC PATCH In review Michael S. Tsirkin 3 participants 6 messages Mar 17, 2010 – Mar 22, 2010

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 tun: add ioctl to modify vnet header size

virtio added mergeable buffers mode where 2 bytes of extra info is put after vnet header but before actual data (tun does not need this data). In hindsight, it would have been better to add the new info *before* the
packet: as it is, users need a lot of tricky code to skip the extra 2 bytes in the middle of the iovec, and in fact applications seem to get it wrong, and only work with specific iovec layout. The fact we might need to split iovec also means we might in theory overflow iovec max size.

This patch adds a simpler way for applications to handle this, and future proofs the interface against further extensions, by making the size of the virtio net header configurable from userspace. As a result, tun driver will simply skip the extra 2 bytes on both input and output.

---
 drivers/net/tun.c      |   31 +++++++++++++++++++++++++++----
 include/linux/if_tun.h |    2 ++
 2 files changed, 29 insertions(+), 4 deletions(-)
drivers/net/tun.c +27 -4
index ce1efa4..0b11222 100644
@@ -110,6 +110,8 @@ struct tun_struct {
 	struct tap_filter       txflt;
 	struct socket		socket;
 
+	int			vnet_hdr_sz;
+
 #ifdef TUN_DEBUG
 	int debug;
 #endif
@@ -559,7 +561,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun,
 	}
 
 	if (tun->flags & TUN_VNET_HDR) {
-		if ((len -= sizeof(gso)) > count)
+		if ((len -= tun->vnet_hdr_sz) > count)
 			return -EINVAL;
 
 		if (memcpy_fromiovecend((void *)&gso, iv, offset, sizeof(gso)))
@@ -571,7 +573,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun,
 
 		if (gso.hdr_len > len)
 			return -EINVAL;
-		offset += sizeof(gso);
+		offset += tun->vnet_hdr_sz;
 	}
 
 	if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV) {
@@ -714,7 +716,7 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun,
 
 	if (tun->flags & TUN_VNET_HDR) {
 		struct virtio_net_hdr gso = { 0 }; /* no info leak */
-		if ((len -= sizeof(gso)) < 0)
+		if ((len -= tun->vnet_hdr_sz) < 0)
 			return -EINVAL;
 
 		if (skb_is_gso(skb)) {
@@ -745,7 +747,7 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun,
 		if (unlikely(memcpy_toiovecend(iv, (void *)&gso, total,
 					       sizeof(gso))))
 			return -EFAULT;
-		total += sizeof(gso);
+		total += tun->vnet_hdr_sz;
 	}
 
 	len = min_t(int, skb->len, len);
@@ -1029,6 +1031,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 		tun->dev = dev;
 		tun->flags = flags;
 		tun->txflt.count = 0;
+		tun->vnet_hdr_sz = sizeof(struct virtio_net_hdr);
 
 		err = -ENOMEM;
 		sk = sk_alloc(net, AF_UNSPEC, GFP_KERNEL, &tun_proto);
@@ -1170,6 +1173,7 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 	struct sock_fprog fprog;
 	struct ifreq ifr;
 	int sndbuf;
+	int vnet_hdr_sz;
 	int ret;
 
 	if (cmd == TUNSETIFF || _IOC_TYPE(cmd) == 0x89)
@@ -1315,6 +1319,25 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 		tun->socket.sk->sk_sndbuf = sndbuf;
 		break;
 
+	case TUNGETVNETHDRSZ:
+		vnet_hdr_sz = tun->vnet_hdr_sz;
+		if (copy_to_user(argp, &vnet_hdr_sz, sizeof(vnet_hdr_sz)))
+			ret = -EFAULT;
+		break;
+
+	case TUNSETVNETHDRSZ:
+		if (copy_from_user(&vnet_hdr_sz, argp, sizeof(vnet_hdr_sz))) {
+			ret = -EFAULT;
+			break;
+		}
+		if (vnet_hdr_sz < (int)sizeof(struct virtio_net_hdr)) {
+			ret = -EINVAL;
+			break;
+		}
+
+		tun->vnet_hdr_sz = vnet_hdr_sz;
+		break;
+
 	case TUNATTACHFILTER:
 		/* Can be set only for TAPs */
 		ret = -EINVAL;
include/linux/if_tun.h +2 -0
index 1350a24..06b1829 100644
@@ -51,6 +51,8 @@
 #define TUNSETSNDBUF   _IOW('T', 212, int)
 #define TUNATTACHFILTER _IOW('T', 213, struct sock_fprog)
 #define TUNDETACHFILTER _IOW('T', 214, struct sock_fprog)
+#define TUNGETVNETHDRSZ _IOR('T', 215, int)
+#define TUNSETVNETHDRSZ _IOW('T', 216, int)
 
 /* TUNSETIFF ifr flags */
 #define IFF_TUN		0x0001
Signature · 1 line
1.7.0.18.g0d53a5
#1 lore raw

Shouldn't we enforce a maximum too? Esp. if overflow/underflow will break any of the checks when it's used.

                                +-DLS
#2 lore raw
Quoted 4 lines · David Stevens

Shouldn't we enforce a maximum too? Esp. if overflow/underflow will break any of the checks when it's used.

                                +-DLS

So the maximum is MAX_INT :)
I don't think it can break any checks that aren't
already broken - what do you have in mind?

Signature · 1 line
MST
#3 lore raw

[email protected] wrote on 03/17/2010 02:35:04 PM:

Quoted 9 lines · David Stevens
Quoted 4 lines

Shouldn't we enforce a maximum too? Esp. if overflow/underflow will break any of the checks when it's used.

                                +-DLS

So the maximum is MAX_INT :)
I don't think it can break any checks that aren't
already broken - what do you have in mind?

I was thinking more like a page. At least, it'd be better
to fail when trying to set it large than failing allocations later. As a header, it really ought to be small.
But if it works, or fails gracefully, at 2^31-1 on 32-bit
machines, negative values, etc, then it's ok. Just a suggestion.

                                                        +-DLS
#4 lore raw
Quoted 12 lines · David Stevens

[email protected] wrote on 03/17/2010 02:35:04 PM:

Quoted 9 lines · David Stevens
Quoted 4 lines

Shouldn't we enforce a maximum too? Esp. if overflow/underflow will break any of the checks when it's used.

                                +-DLS

So the maximum is MAX_INT :)
I don't think it can break any checks that aren't
already broken - what do you have in mind?

Quoted 7 lines

I was thinking more like a page. At least, it'd be better
to fail when trying to set it large than failing allocations later. As a header, it really ought to be small.
But if it works, or fails gracefully, at 2^31-1 on 32-bit
machines, negative values, etc, then it's ok. Just a suggestion.

                                                        +-DLS

All this does is set how much of the buffer to skip, this option does not allocate any memory. So if you set it to a value > length that you passed in, you get -EINVAL. Anything else should work. Negative values are checked for and return -EINVAL when you try to set it. At least, all that's by design - pls take a look at the code and if you see any issues, speak up please.

I agree we don't really need to support very large values here, it just seemed less work.

Signature · 1 line
MST
maintainer
#5 lore raw

From: "Michael S. Tsirkin" <[email protected]>
Date: Thu, 18 Mar 2010 00:11:17 +0200

Quoted 9 lines

All this does is set how much of the buffer to skip, this option does not allocate any memory. So if you set it to a value > length that you passed in, you get -EINVAL. Anything else should work. Negative values are checked for and return -EINVAL when you try to set it. At least, all that's by design - pls take a look at the code and if you see any issues, speak up please.

I agree we don't really need to support very large values here, it just seemed less work.

This all looks fine to me, submit a final version to me via whatever means you feel is appropriate.