macvtap/tun: cross-endian support for little-endian hosts

The VNET_LE flag was introduced to fix accesses to virtio 1.0 headers
that are always little-endian. It can also be used to handle the special
case of a legacy little-endian device implemented by a big-endian host.

Let's add a flag and ioctls for big-endian devices as well. If both flags
are set, little-endian wins.

Since this is isn't a common usecase, the feature is controlled by a kernel
config option (not set by default).

Both macvtap and tun are covered by this patch since they share the same
API with userland.

Signed-off-by: Greg Kurz <gkurz@linux.vnet.ibm.com>

Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
This commit is contained in:
Greg Kurz 2015-04-24 14:50:36 +02:00 committed by Michael S. Tsirkin
parent 2751c9882b
commit 8b8e658b16
4 changed files with 134 additions and 2 deletions

View File

@ -244,6 +244,20 @@ config TUN
If you don't know what to use this for, you don't need it.
config TUN_VNET_CROSS_LE
bool "Support for cross-endian vnet headers on little-endian kernels"
default n
---help---
This option allows TUN/TAP and MACVTAP device drivers in a
little-endian kernel to parse vnet headers that come from a
big-endian legacy virtio device.
Userspace programs can control the feature using the TUNSETVNETBE
and TUNGETVNETBE ioctls.
Unless you have a little-endian system hosting a big-endian virtual
machine with a legacy virtio NIC, you should say N.
config VETH
tristate "Virtual ethernet pair device"
---help---

View File

@ -48,11 +48,60 @@ struct macvtap_queue {
#define MACVTAP_FEATURES (IFF_VNET_HDR | IFF_MULTI_QUEUE)
#define MACVTAP_VNET_LE 0x80000000
#define MACVTAP_VNET_BE 0x40000000
#ifdef CONFIG_TUN_VNET_CROSS_LE
static inline bool macvtap_legacy_is_little_endian(struct macvtap_queue *q)
{
return q->flags & MACVTAP_VNET_BE ? false :
virtio_legacy_is_little_endian();
}
static long macvtap_get_vnet_be(struct macvtap_queue *q, int __user *sp)
{
int s = !!(q->flags & MACVTAP_VNET_BE);
if (put_user(s, sp))
return -EFAULT;
return 0;
}
static long macvtap_set_vnet_be(struct macvtap_queue *q, int __user *sp)
{
int s;
if (get_user(s, sp))
return -EFAULT;
if (s)
q->flags |= MACVTAP_VNET_BE;
else
q->flags &= ~MACVTAP_VNET_BE;
return 0;
}
#else
static inline bool macvtap_legacy_is_little_endian(struct macvtap_queue *q)
{
return virtio_legacy_is_little_endian();
}
static long macvtap_get_vnet_be(struct macvtap_queue *q, int __user *argp)
{
return -EINVAL;
}
static long macvtap_set_vnet_be(struct macvtap_queue *q, int __user *argp)
{
return -EINVAL;
}
#endif /* CONFIG_TUN_VNET_CROSS_LE */
static inline bool macvtap_is_little_endian(struct macvtap_queue *q)
{
return q->flags & MACVTAP_VNET_LE ||
virtio_legacy_is_little_endian();
macvtap_legacy_is_little_endian(q);
}
static inline u16 macvtap16_to_cpu(struct macvtap_queue *q, __virtio16 val)
@ -1096,6 +1145,12 @@ static long macvtap_ioctl(struct file *file, unsigned int cmd,
q->flags &= ~MACVTAP_VNET_LE;
return 0;
case TUNGETVNETBE:
return macvtap_get_vnet_be(q, sp);
case TUNSETVNETBE:
return macvtap_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 |

View File

@ -111,6 +111,7 @@ do { \
#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)
@ -206,10 +207,58 @@ struct tun_struct {
u32 flow_count;
};
#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 ||
virtio_legacy_is_little_endian();
tun_legacy_is_little_endian(tun);
}
static inline u16 tun16_to_cpu(struct tun_struct *tun, __virtio16 val)
@ -2062,6 +2111,14 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
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;

View File

@ -50,6 +50,12 @@
#define TUNGETFILTER _IOR('T', 219, struct sock_fprog)
#define TUNSETVNETLE _IOW('T', 220, int)
#define TUNGETVNETLE _IOR('T', 221, int)
/* The TUNSETVNETBE and TUNGETVNETBE ioctls are for cross-endian support on
* little-endian hosts. Not all kernel configurations support them, but all
* configurations that support SET also support GET.
*/
#define TUNSETVNETBE _IOW('T', 222, int)
#define TUNGETVNETBE _IOR('T', 223, int)
/* TUNSETIFF ifr flags */
#define IFF_TUN 0x0001