Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Akihiko Odaki 848 98.38% 3 50.00%
Michael S. Tsirkin 5 0.58% 1 16.67%
Jason (Hui) Wang 5 0.58% 1 16.67%
Greg Kurz 4 0.46% 1 16.67%
Total 862 6


/* 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;

	if (iov_iter_zero(sz - sizeof(*hdr), iter) != sz - sizeof(*hdr))
		return -EFAULT;

	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 */