mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 23:22:31 +02:00
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: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
51b2483b08
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -26,75 +26,10 @@
|
|||
#include <linux/virtio_net.h>
|
||||
#include <linux/skb_array.h>
|
||||
|
||||
#include "tun_vnet.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);
|
||||
}
|
||||
|
||||
static inline __virtio16 cpu_to_tap16(struct tap_queue *q, u16 val)
|
||||
{
|
||||
return __cpu_to_virtio16(tap_is_little_endian(q), val);
|
||||
}
|
||||
|
||||
static struct proto tap_proto = {
|
||||
.name = "tap",
|
||||
.owner = THIS_MODULE,
|
||||
|
|
@ -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();
|
||||
ret = tun_vnet_hdr_from_skb(q->flags, NULL, skb, &vnet_hdr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (copy_to_iter(&vnet_hdr, sizeof(vnet_hdr), iter) !=
|
||||
sizeof(vnet_hdr))
|
||||
return -EFAULT;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
hdr_len = tun_vnet_hdr_get(vnet_hdr_sz, tun->flags, from, &gso);
|
||||
if (hdr_len < 0)
|
||||
return hdr_len;
|
||||
|
||||
len -= vnet_hdr_sz;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
ret = tun_vnet_hdr_from_skb(tun->flags, tun->dev, skb, &gso);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
185
drivers/net/tun_vnet.h
Normal file
185
drivers/net/tun_vnet.h
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/* 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 */
|
||||
Loading…
Reference in New Issue
Block a user