Commit 51b2483b authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'tun-unify-vnet-implementation'

Akihiko Odaki says:

====================
tun: Unify vnet implementation

When I implemented virtio's hash-related features to tun/tap [1],
I found tun/tap does not fill the entire region reserved for the virtio
header, leaving some uninitialized hole in the middle of the buffer
after read()/recvmesg().

This series fills the uninitialized hole. More concretely, the
num_buffers field will be initialized with 1, and the other fields will
be inialized with 0. Setting the num_buffers field to 1 is mandated by
virtio 1.0 [2].

The change to virtio header is preceded by another change that refactors
tun and tap to unify their virtio-related code.

[1]: https://lore.kernel.org/r/20241008-rss-v5-0-f3cf68df005d@daynix.com
[2]: https://lore.kernel.org/r/20241227084256-mutt-send-email-mst@kernel.org/
====================

Link: https://patch.msgid.link/20250207-tun-v6-0-fb49cf8b103e@daynix.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents d28e2d7f 6a53fc5a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -24152,7 +24152,7 @@ W: http://vtun.sourceforge.net/tun
F:	Documentation/networking/tuntap.rst
F:	arch/um/os-Linux/drivers/
F:	drivers/net/tap.c
F:	drivers/net/tun.c
F:	drivers/net/tun*
TURBOCHANNEL SUBSYSTEM
M:	"Maciej W. Rozycki" <macro@orcam.me.uk>
+19 −147
Original line number Diff line number Diff line
@@ -26,74 +26,9 @@
#include <linux/virtio_net.h>
#include <linux/skb_array.h>

#define TAP_IFFEATURES (IFF_VNET_HDR | IFF_MULTI_QUEUE)

#define TAP_VNET_LE 0x80000000
#define TAP_VNET_BE 0x40000000

#ifdef CONFIG_TUN_VNET_CROSS_LE
static inline bool tap_legacy_is_little_endian(struct tap_queue *q)
{
	return q->flags & TAP_VNET_BE ? false :
		virtio_legacy_is_little_endian();
}

static long tap_get_vnet_be(struct tap_queue *q, int __user *sp)
{
	int s = !!(q->flags & TAP_VNET_BE);

	if (put_user(s, sp))
		return -EFAULT;

	return 0;
}

static long tap_set_vnet_be(struct tap_queue *q, int __user *sp)
{
	int s;

	if (get_user(s, sp))
		return -EFAULT;

	if (s)
		q->flags |= TAP_VNET_BE;
	else
		q->flags &= ~TAP_VNET_BE;

	return 0;
}
#else
static inline bool tap_legacy_is_little_endian(struct tap_queue *q)
{
	return virtio_legacy_is_little_endian();
}

static long tap_get_vnet_be(struct tap_queue *q, int __user *argp)
{
	return -EINVAL;
}

static long tap_set_vnet_be(struct tap_queue *q, int __user *argp)
{
	return -EINVAL;
}
#endif /* CONFIG_TUN_VNET_CROSS_LE */

static inline bool tap_is_little_endian(struct tap_queue *q)
{
	return q->flags & TAP_VNET_LE ||
		tap_legacy_is_little_endian(q);
}

static inline u16 tap16_to_cpu(struct tap_queue *q, __virtio16 val)
{
	return __virtio16_to_cpu(tap_is_little_endian(q), val);
}
#include "tun_vnet.h"

static inline __virtio16 cpu_to_tap16(struct tap_queue *q, u16 val)
{
	return __cpu_to_virtio16(tap_is_little_endian(q), val);
}
#define TAP_IFFEATURES (IFF_VNET_HDR | IFF_MULTI_QUEUE)

static struct proto tap_proto = {
	.name = "tap",
@@ -645,6 +580,7 @@ static ssize_t tap_get_user(struct tap_queue *q, void *msg_control,
	int err;
	struct virtio_net_hdr vnet_hdr = { 0 };
	int vnet_hdr_len = 0;
	int hdr_len = 0;
	int copylen = 0;
	int depth;
	bool zerocopy = false;
@@ -654,25 +590,13 @@ static ssize_t tap_get_user(struct tap_queue *q, void *msg_control,
	if (q->flags & IFF_VNET_HDR) {
		vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);

		err = -EINVAL;
		if (len < vnet_hdr_len)
		hdr_len = tun_vnet_hdr_get(vnet_hdr_len, q->flags, from, &vnet_hdr);
		if (hdr_len < 0) {
			err = hdr_len;
			goto err;
		len -= vnet_hdr_len;
		}

		err = -EFAULT;
		if (!copy_from_iter_full(&vnet_hdr, sizeof(vnet_hdr), from))
			goto err;
		iov_iter_advance(from, vnet_hdr_len - sizeof(vnet_hdr));
		if ((vnet_hdr.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) &&
		     tap16_to_cpu(q, vnet_hdr.csum_start) +
		     tap16_to_cpu(q, vnet_hdr.csum_offset) + 2 >
			     tap16_to_cpu(q, vnet_hdr.hdr_len))
			vnet_hdr.hdr_len = cpu_to_tap16(q,
				 tap16_to_cpu(q, vnet_hdr.csum_start) +
				 tap16_to_cpu(q, vnet_hdr.csum_offset) + 2);
		err = -EINVAL;
		if (tap16_to_cpu(q, vnet_hdr.hdr_len) > len)
			goto err;
		len -= vnet_hdr_len;
	}

	err = -EINVAL;
@@ -682,12 +606,7 @@ static ssize_t tap_get_user(struct tap_queue *q, void *msg_control,
	if (msg_control && sock_flag(&q->sk, SOCK_ZEROCOPY)) {
		struct iov_iter i;

		copylen = vnet_hdr.hdr_len ?
			tap16_to_cpu(q, vnet_hdr.hdr_len) : GOODCOPY_LEN;
		if (copylen > good_linear)
			copylen = good_linear;
		else if (copylen < ETH_HLEN)
			copylen = ETH_HLEN;
		copylen = clamp(hdr_len ?: GOODCOPY_LEN, ETH_HLEN, good_linear);
		linear = copylen;
		i = *from;
		iov_iter_advance(&i, copylen);
@@ -697,11 +616,7 @@ static ssize_t tap_get_user(struct tap_queue *q, void *msg_control,

	if (!zerocopy) {
		copylen = len;
		linear = tap16_to_cpu(q, vnet_hdr.hdr_len);
		if (linear > good_linear)
			linear = good_linear;
		else if (linear < ETH_HLEN)
			linear = ETH_HLEN;
		linear = clamp(hdr_len, ETH_HLEN, good_linear);
	}

	skb = tap_alloc_skb(&q->sk, TAP_RESERVE, copylen,
@@ -733,8 +648,7 @@ static ssize_t tap_get_user(struct tap_queue *q, void *msg_control,
	skb->dev = tap->dev;

	if (vnet_hdr_len) {
		err = virtio_net_hdr_to_skb(skb, &vnet_hdr,
					    tap_is_little_endian(q));
		err = tun_vnet_hdr_to_skb(q->flags, skb, &vnet_hdr);
		if (err) {
			rcu_read_unlock();
			drop_reason = SKB_DROP_REASON_DEV_HDR;
@@ -797,23 +711,17 @@ static ssize_t tap_put_user(struct tap_queue *q,
	int total;

	if (q->flags & IFF_VNET_HDR) {
		int vlan_hlen = skb_vlan_tag_present(skb) ? VLAN_HLEN : 0;
		struct virtio_net_hdr vnet_hdr;

		vnet_hdr_len = READ_ONCE(q->vnet_hdr_sz);
		if (iov_iter_count(iter) < vnet_hdr_len)
			return -EINVAL;

		if (virtio_net_hdr_from_skb(skb, &vnet_hdr,
					    tap_is_little_endian(q), true,
					    vlan_hlen))
			BUG();

		if (copy_to_iter(&vnet_hdr, sizeof(vnet_hdr), iter) !=
		    sizeof(vnet_hdr))
			return -EFAULT;
		ret = tun_vnet_hdr_from_skb(q->flags, NULL, skb, &vnet_hdr);
		if (ret)
			return ret;

		iov_iter_advance(iter, vnet_hdr_len - sizeof(vnet_hdr));
		ret = tun_vnet_hdr_put(vnet_hdr_len, iter, &vnet_hdr);
		if (ret)
			return ret;
	}
	total = vnet_hdr_len;
	total += skb->len;
@@ -1072,42 +980,6 @@ static long tap_ioctl(struct file *file, unsigned int cmd,
		q->sk.sk_sndbuf = s;
		return 0;

	case TUNGETVNETHDRSZ:
		s = q->vnet_hdr_sz;
		if (put_user(s, sp))
			return -EFAULT;
		return 0;

	case TUNSETVNETHDRSZ:
		if (get_user(s, sp))
			return -EFAULT;
		if (s < (int)sizeof(struct virtio_net_hdr))
			return -EINVAL;

		q->vnet_hdr_sz = s;
		return 0;

	case TUNGETVNETLE:
		s = !!(q->flags & TAP_VNET_LE);
		if (put_user(s, sp))
			return -EFAULT;
		return 0;

	case TUNSETVNETLE:
		if (get_user(s, sp))
			return -EFAULT;
		if (s)
			q->flags |= TAP_VNET_LE;
		else
			q->flags &= ~TAP_VNET_LE;
		return 0;

	case TUNGETVNETBE:
		return tap_get_vnet_be(q, sp);

	case TUNSETVNETBE:
		return tap_set_vnet_be(q, sp);

	case TUNSETOFFLOAD:
		/* let the user check for future flags */
		if (arg & ~(TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 |
@@ -1151,7 +1023,7 @@ static long tap_ioctl(struct file *file, unsigned int cmd,
		return ret;

	default:
		return -EINVAL;
		return tun_vnet_ioctl(&q->vnet_hdr_sz, &q->flags, cmd, sp);
	}
}

@@ -1198,7 +1070,7 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
	skb->protocol = eth_hdr(skb)->h_proto;

	if (vnet_hdr_len) {
		err = virtio_net_hdr_to_skb(skb, gso, tap_is_little_endian(q));
		err = tun_vnet_hdr_to_skb(q->flags, skb, gso);
		if (err)
			goto err_kfree;
	}
+24 −169
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@
#include <linux/uaccess.h>
#include <linux/proc_fs.h>

#include "tun_vnet.h"

static void tun_default_link_ksettings(struct net_device *dev,
				       struct ethtool_link_ksettings *cmd);

@@ -94,9 +96,6 @@ static void tun_default_link_ksettings(struct net_device *dev,
 * overload it to mean fasync when stored there.
 */
#define TUN_FASYNC	IFF_ATTACH_QUEUE
/* High bits in flags field are unused. */
#define TUN_VNET_LE     0x80000000
#define TUN_VNET_BE     0x40000000

#define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
		      IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
@@ -298,70 +297,6 @@ static bool tun_napi_frags_enabled(const struct tun_file *tfile)
	return tfile->napi_frags_enabled;
}

#ifdef CONFIG_TUN_VNET_CROSS_LE
static inline bool tun_legacy_is_little_endian(struct tun_struct *tun)
{
	return tun->flags & TUN_VNET_BE ? false :
		virtio_legacy_is_little_endian();
}

static long tun_get_vnet_be(struct tun_struct *tun, int __user *argp)
{
	int be = !!(tun->flags & TUN_VNET_BE);

	if (put_user(be, argp))
		return -EFAULT;

	return 0;
}

static long tun_set_vnet_be(struct tun_struct *tun, int __user *argp)
{
	int be;

	if (get_user(be, argp))
		return -EFAULT;

	if (be)
		tun->flags |= TUN_VNET_BE;
	else
		tun->flags &= ~TUN_VNET_BE;

	return 0;
}
#else
static inline bool tun_legacy_is_little_endian(struct tun_struct *tun)
{
	return virtio_legacy_is_little_endian();
}

static long tun_get_vnet_be(struct tun_struct *tun, int __user *argp)
{
	return -EINVAL;
}

static long tun_set_vnet_be(struct tun_struct *tun, int __user *argp)
{
	return -EINVAL;
}
#endif /* CONFIG_TUN_VNET_CROSS_LE */

static inline bool tun_is_little_endian(struct tun_struct *tun)
{
	return tun->flags & TUN_VNET_LE ||
		tun_legacy_is_little_endian(tun);
}

static inline u16 tun16_to_cpu(struct tun_struct *tun, __virtio16 val)
{
	return __virtio16_to_cpu(tun_is_little_endian(tun), val);
}

static inline __virtio16 cpu_to_tun16(struct tun_struct *tun, u16 val)
{
	return __cpu_to_virtio16(tun_is_little_endian(tun), val);
}

static inline u32 tun_hashfn(u32 rxhash)
{
	return rxhash & TUN_MASK_FLOW_ENTRIES;
@@ -1756,6 +1691,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
	struct virtio_net_hdr gso = { 0 };
	int good_linear;
	int copylen;
	int hdr_len = 0;
	bool zerocopy = false;
	int err;
	u32 rxhash = 0;
@@ -1775,26 +1711,16 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
	if (tun->flags & IFF_VNET_HDR) {
		int vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz);

		if (len < vnet_hdr_sz)
			return -EINVAL;
		len -= vnet_hdr_sz;
		hdr_len = tun_vnet_hdr_get(vnet_hdr_sz, tun->flags, from, &gso);
		if (hdr_len < 0)
			return hdr_len;

		if (!copy_from_iter_full(&gso, sizeof(gso), from))
			return -EFAULT;

		if ((gso.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) &&
		    tun16_to_cpu(tun, gso.csum_start) + tun16_to_cpu(tun, gso.csum_offset) + 2 > tun16_to_cpu(tun, gso.hdr_len))
			gso.hdr_len = cpu_to_tun16(tun, tun16_to_cpu(tun, gso.csum_start) + tun16_to_cpu(tun, gso.csum_offset) + 2);

		if (tun16_to_cpu(tun, gso.hdr_len) > len)
			return -EINVAL;
		iov_iter_advance(from, vnet_hdr_sz - sizeof(gso));
		len -= vnet_hdr_sz;
	}

	if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) {
		align += NET_IP_ALIGN;
		if (unlikely(len < ETH_HLEN ||
			     (gso.hdr_len && tun16_to_cpu(tun, gso.hdr_len) < ETH_HLEN)))
		if (unlikely(len < ETH_HLEN || (hdr_len && hdr_len < ETH_HLEN)))
			return -EINVAL;
	}

@@ -1807,9 +1733,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
		 * enough room for skb expand head in case it is used.
		 * The rest of the buffer is mapped from userspace.
		 */
		copylen = gso.hdr_len ? tun16_to_cpu(tun, gso.hdr_len) : GOODCOPY_LEN;
		if (copylen > good_linear)
			copylen = good_linear;
		copylen = min(hdr_len ? hdr_len : GOODCOPY_LEN, good_linear);
		linear = copylen;
		iov_iter_advance(&i, copylen);
		if (iov_iter_npages(&i, INT_MAX) <= MAX_SKB_FRAGS)
@@ -1830,10 +1754,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
	} else {
		if (!zerocopy) {
			copylen = len;
			if (tun16_to_cpu(tun, gso.hdr_len) > good_linear)
				linear = good_linear;
			else
				linear = tun16_to_cpu(tun, gso.hdr_len);
			linear = min(hdr_len, good_linear);
		}

		if (frags) {
@@ -1868,7 +1789,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
		}
	}

	if (virtio_net_hdr_to_skb(skb, &gso, tun_is_little_endian(tun))) {
	if (tun_vnet_hdr_to_skb(tun->flags, skb, &gso)) {
		atomic_long_inc(&tun->rx_frame_errors);
		err = -EINVAL;
		goto free_skb;
@@ -2063,18 +1984,15 @@ static ssize_t tun_put_user_xdp(struct tun_struct *tun,
{
	int vnet_hdr_sz = 0;
	size_t size = xdp_frame->len;
	size_t ret;
	ssize_t ret;

	if (tun->flags & IFF_VNET_HDR) {
		struct virtio_net_hdr gso = { 0 };

		vnet_hdr_sz = READ_ONCE(tun->vnet_hdr_sz);
		if (unlikely(iov_iter_count(iter) < vnet_hdr_sz))
			return -EINVAL;
		if (unlikely(copy_to_iter(&gso, sizeof(gso), iter) !=
			     sizeof(gso)))
			return -EFAULT;
		iov_iter_advance(iter, vnet_hdr_sz - sizeof(gso));
		ret = tun_vnet_hdr_put(vnet_hdr_sz, iter, &gso);
		if (ret)
			return ret;
	}

	ret = copy_to_iter(xdp_frame->data, size, iter) + vnet_hdr_sz;
@@ -2097,6 +2015,7 @@ static ssize_t tun_put_user(struct tun_struct *tun,
	int vlan_offset = 0;
	int vlan_hlen = 0;
	int vnet_hdr_sz = 0;
	int ret;

	if (skb_vlan_tag_present(skb))
		vlan_hlen = VLAN_HLEN;
@@ -2123,31 +2042,13 @@ static ssize_t tun_put_user(struct tun_struct *tun,
	if (vnet_hdr_sz) {
		struct virtio_net_hdr gso;

		if (iov_iter_count(iter) < vnet_hdr_sz)
			return -EINVAL;

		if (virtio_net_hdr_from_skb(skb, &gso,
					    tun_is_little_endian(tun), true,
					    vlan_hlen)) {
			struct skb_shared_info *sinfo = skb_shinfo(skb);

			if (net_ratelimit()) {
				netdev_err(tun->dev, "unexpected GSO type: 0x%x, gso_size %d, hdr_len %d\n",
					   sinfo->gso_type, tun16_to_cpu(tun, gso.gso_size),
					   tun16_to_cpu(tun, gso.hdr_len));
				print_hex_dump(KERN_ERR, "tun: ",
					       DUMP_PREFIX_NONE,
					       16, 1, skb->head,
					       min((int)tun16_to_cpu(tun, gso.hdr_len), 64), true);
			}
			WARN_ON_ONCE(1);
			return -EINVAL;
		}

		if (copy_to_iter(&gso, sizeof(gso), iter) != sizeof(gso))
			return -EFAULT;
		ret = tun_vnet_hdr_from_skb(tun->flags, tun->dev, skb, &gso);
		if (ret)
			return ret;

		iov_iter_advance(iter, vnet_hdr_sz - sizeof(gso));
		ret = tun_vnet_hdr_put(vnet_hdr_sz, iter, &gso);
		if (ret)
			return ret;
	}

	if (vlan_hlen) {
@@ -2507,7 +2408,7 @@ static int tun_xdp_one(struct tun_struct *tun,
	skb_reserve(skb, xdp->data - xdp->data_hard_start);
	skb_put(skb, xdp->data_end - xdp->data);

	if (virtio_net_hdr_to_skb(skb, gso, tun_is_little_endian(tun))) {
	if (tun_vnet_hdr_to_skb(tun->flags, skb, gso)) {
		atomic_long_inc(&tun->rx_frame_errors);
		kfree_skb(skb);
		ret = -EINVAL;
@@ -3091,8 +2992,6 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
	kgid_t group;
	int ifindex;
	int sndbuf;
	int vnet_hdr_sz;
	int le;
	int ret;
	bool do_notify = false;

@@ -3299,50 +3198,6 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
		tun_set_sndbuf(tun);
		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 TUNGETVNETLE:
		le = !!(tun->flags & TUN_VNET_LE);
		if (put_user(le, (int __user *)argp))
			ret = -EFAULT;
		break;

	case TUNSETVNETLE:
		if (get_user(le, (int __user *)argp)) {
			ret = -EFAULT;
			break;
		}
		if (le)
			tun->flags |= TUN_VNET_LE;
		else
			tun->flags &= ~TUN_VNET_LE;
		break;

	case TUNGETVNETBE:
		ret = tun_get_vnet_be(tun, argp);
		break;

	case TUNSETVNETBE:
		ret = tun_set_vnet_be(tun, argp);
		break;

	case TUNATTACHFILTER:
		/* Can be set only for TAPs */
		ret = -EINVAL;
@@ -3398,7 +3253,7 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
		break;

	default:
		ret = -EINVAL;
		ret = tun_vnet_ioctl(&tun->vnet_hdr_sz, &tun->flags, cmd, argp);
		break;
	}

drivers/net/tun_vnet.h

0 → 100644
+185 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef TUN_VNET_H
#define TUN_VNET_H

/* High bits in flags field are unused. */
#define TUN_VNET_LE     0x80000000
#define TUN_VNET_BE     0x40000000

static inline bool tun_vnet_legacy_is_little_endian(unsigned int flags)
{
	bool be = IS_ENABLED(CONFIG_TUN_VNET_CROSS_LE) &&
		  (flags & TUN_VNET_BE);

	return !be && virtio_legacy_is_little_endian();
}

static inline long tun_get_vnet_be(unsigned int flags, int __user *argp)
{
	int be = !!(flags & TUN_VNET_BE);

	if (!IS_ENABLED(CONFIG_TUN_VNET_CROSS_LE))
		return -EINVAL;

	if (put_user(be, argp))
		return -EFAULT;

	return 0;
}

static inline long tun_set_vnet_be(unsigned int *flags, int __user *argp)
{
	int be;

	if (!IS_ENABLED(CONFIG_TUN_VNET_CROSS_LE))
		return -EINVAL;

	if (get_user(be, argp))
		return -EFAULT;

	if (be)
		*flags |= TUN_VNET_BE;
	else
		*flags &= ~TUN_VNET_BE;

	return 0;
}

static inline bool tun_vnet_is_little_endian(unsigned int flags)
{
	return flags & TUN_VNET_LE || tun_vnet_legacy_is_little_endian(flags);
}

static inline u16 tun_vnet16_to_cpu(unsigned int flags, __virtio16 val)
{
	return __virtio16_to_cpu(tun_vnet_is_little_endian(flags), val);
}

static inline __virtio16 cpu_to_tun_vnet16(unsigned int flags, u16 val)
{
	return __cpu_to_virtio16(tun_vnet_is_little_endian(flags), val);
}

static inline long tun_vnet_ioctl(int *vnet_hdr_sz, unsigned int *flags,
				  unsigned int cmd, int __user *sp)
{
	int s;

	switch (cmd) {
	case TUNGETVNETHDRSZ:
		s = *vnet_hdr_sz;
		if (put_user(s, sp))
			return -EFAULT;
		return 0;

	case TUNSETVNETHDRSZ:
		if (get_user(s, sp))
			return -EFAULT;
		if (s < (int)sizeof(struct virtio_net_hdr))
			return -EINVAL;

		*vnet_hdr_sz = s;
		return 0;

	case TUNGETVNETLE:
		s = !!(*flags & TUN_VNET_LE);
		if (put_user(s, sp))
			return -EFAULT;
		return 0;

	case TUNSETVNETLE:
		if (get_user(s, sp))
			return -EFAULT;
		if (s)
			*flags |= TUN_VNET_LE;
		else
			*flags &= ~TUN_VNET_LE;
		return 0;

	case TUNGETVNETBE:
		return tun_get_vnet_be(*flags, sp);

	case TUNSETVNETBE:
		return tun_set_vnet_be(flags, sp);

	default:
		return -EINVAL;
	}
}

static inline int tun_vnet_hdr_get(int sz, unsigned int flags,
				   struct iov_iter *from,
				   struct virtio_net_hdr *hdr)
{
	u16 hdr_len;

	if (iov_iter_count(from) < sz)
		return -EINVAL;

	if (!copy_from_iter_full(hdr, sizeof(*hdr), from))
		return -EFAULT;

	hdr_len = tun_vnet16_to_cpu(flags, hdr->hdr_len);

	if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
		hdr_len = max(tun_vnet16_to_cpu(flags, hdr->csum_start) + tun_vnet16_to_cpu(flags, hdr->csum_offset) + 2, hdr_len);
		hdr->hdr_len = cpu_to_tun_vnet16(flags, hdr_len);
	}

	if (hdr_len > iov_iter_count(from))
		return -EINVAL;

	iov_iter_advance(from, sz - sizeof(*hdr));

	return hdr_len;
}

static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter,
				   const struct virtio_net_hdr *hdr)
{
	if (unlikely(iov_iter_count(iter) < sz))
		return -EINVAL;

	if (unlikely(copy_to_iter(hdr, sizeof(*hdr), iter) != sizeof(*hdr)))
		return -EFAULT;

	iov_iter_advance(iter, sz - sizeof(*hdr));

	return 0;
}

static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb,
				      const struct virtio_net_hdr *hdr)
{
	return virtio_net_hdr_to_skb(skb, hdr, tun_vnet_is_little_endian(flags));
}

static inline int tun_vnet_hdr_from_skb(unsigned int flags,
					const struct net_device *dev,
					const struct sk_buff *skb,
					struct virtio_net_hdr *hdr)
{
	int vlan_hlen = skb_vlan_tag_present(skb) ? VLAN_HLEN : 0;

	if (virtio_net_hdr_from_skb(skb, hdr,
				    tun_vnet_is_little_endian(flags), true,
				    vlan_hlen)) {
		struct skb_shared_info *sinfo = skb_shinfo(skb);

		if (net_ratelimit()) {
			netdev_err(dev, "unexpected GSO type: 0x%x, gso_size %d, hdr_len %d\n",
				   sinfo->gso_type, tun_vnet16_to_cpu(flags, hdr->gso_size),
				   tun_vnet16_to_cpu(flags, hdr->hdr_len));
			print_hex_dump(KERN_ERR, "tun: ",
				       DUMP_PREFIX_NONE,
				       16, 1, skb->head,
				       min(tun_vnet16_to_cpu(flags, hdr->hdr_len), 64), true);
		}
		WARN_ON_ONCE(1);
		return -EINVAL;
	}

	return 0;
}

#endif /* TUN_VNET_H */