Commit 2506251e authored by Akihiko Odaki's avatar Akihiko Odaki Committed by Jakub Kicinski
Browse files

tun: Decouple vnet handling



Decouple the vnet handling code so that we can reuse it for tap.

Signed-off-by: default avatarAkihiko Odaki <akihiko.odaki@daynix.com>
Reviewed-by: default avatarWillem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20250207-tun-v6-4-fb49cf8b103e@daynix.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 60df67b9
Loading
Loading
Loading
Loading
+139 −98
Original line number Diff line number Diff line
@@ -352,6 +352,127 @@ static inline __virtio16 cpu_to_tun16(unsigned int flags, u16 val)
	return __cpu_to_virtio16(tun_is_little_endian(flags), val);
}

static 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 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 = tun16_to_cpu(flags, hdr->hdr_len);

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

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

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

	return hdr_len;
}

static 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 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_is_little_endian(flags));
}

static 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_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, tun16_to_cpu(flags, hdr->gso_size),
				   tun16_to_cpu(flags, hdr->hdr_len));
			print_hex_dump(KERN_ERR, "tun: ",
				       DUMP_PREFIX_NONE,
				       16, 1, skb->head,
				       min(tun16_to_cpu(flags, hdr->hdr_len), 64), true);
		}
		WARN_ON_ONCE(1);
		return -EINVAL;
	}

	return 0;
}

static inline u32 tun_hashfn(u32 rxhash)
{
	return rxhash & TUN_MASK_FLOW_ENTRIES;
@@ -1765,25 +1886,12 @@ 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);
		int flags = tun->flags;

		if (len < vnet_hdr_sz)
			return -EINVAL;
		len -= vnet_hdr_sz;

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

		hdr_len = tun16_to_cpu(flags, gso.hdr_len);

		if (gso.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
			hdr_len = max(tun16_to_cpu(flags, gso.csum_start) + tun16_to_cpu(flags, gso.csum_offset) + 2, hdr_len);
			gso.hdr_len = cpu_to_tun16(flags, hdr_len);
		}
		hdr_len = tun_vnet_hdr_get(vnet_hdr_sz, tun->flags, from, &gso);
		if (hdr_len < 0)
			return hdr_len;

		if (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) {
@@ -1857,7 +1965,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->flags))) {
	if (tun_vnet_hdr_to_skb(tun->flags, skb, &gso)) {
		atomic_long_inc(&tun->rx_frame_errors);
		err = -EINVAL;
		goto free_skb;
@@ -2052,18 +2160,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;
@@ -2086,6 +2191,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;
@@ -2111,33 +2217,14 @@ static ssize_t tun_put_user(struct tun_struct *tun,

	if (vnet_hdr_sz) {
		struct virtio_net_hdr gso;
		int flags = tun->flags;

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

		if (virtio_net_hdr_from_skb(skb, &gso,
					    tun_is_little_endian(flags), 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(flags, gso.gso_size),
					   tun16_to_cpu(flags, gso.hdr_len));
				print_hex_dump(KERN_ERR, "tun: ",
					       DUMP_PREFIX_NONE,
					       16, 1, skb->head,
					       min((int)tun16_to_cpu(flags, 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) {
@@ -2497,7 +2584,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->flags))) {
	if (tun_vnet_hdr_to_skb(tun->flags, skb, gso)) {
		atomic_long_inc(&tun->rx_frame_errors);
		kfree_skb(skb);
		ret = -EINVAL;
@@ -3081,8 +3168,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;

@@ -3289,50 +3374,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->flags, argp);
		break;

	case TUNSETVNETBE:
		ret = tun_set_vnet_be(&tun->flags, argp);
		break;

	case TUNATTACHFILTER:
		/* Can be set only for TAPs */
		ret = -EINVAL;
@@ -3388,7 +3429,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;
	}