2025-02-07 15:10:55 +09:00
|
|
|
/* 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
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
#define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel)
|
|
|
|
|
2025-02-07 15:10:55 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
static inline unsigned int tun_vnet_parse_size(netdev_features_t features)
|
|
|
|
{
|
|
|
|
if (!(features & NETIF_F_GSO_UDP_TUNNEL))
|
|
|
|
return sizeof(struct virtio_net_hdr);
|
|
|
|
|
|
|
|
return TUN_VNET_TNL_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int __tun_vnet_hdr_get(int sz, unsigned int flags,
|
|
|
|
netdev_features_t features,
|
|
|
|
struct iov_iter *from,
|
|
|
|
struct virtio_net_hdr *hdr)
|
2025-02-07 15:10:55 +09:00
|
|
|
{
|
2025-07-08 17:55:30 +02:00
|
|
|
unsigned int parsed_size = tun_vnet_parse_size(features);
|
2025-02-07 15:10:55 +09:00
|
|
|
u16 hdr_len;
|
|
|
|
|
|
|
|
if (iov_iter_count(from) < sz)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
if (!copy_from_iter_full(hdr, parsed_size, from))
|
2025-02-07 15:10:55 +09:00
|
|
|
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;
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
iov_iter_advance(from, sz - parsed_size);
|
2025-02-07 15:10:55 +09:00
|
|
|
|
|
|
|
return hdr_len;
|
|
|
|
}
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
static inline int tun_vnet_hdr_get(int sz, unsigned int flags,
|
|
|
|
struct iov_iter *from,
|
|
|
|
struct virtio_net_hdr *hdr)
|
|
|
|
{
|
|
|
|
return __tun_vnet_hdr_get(sz, flags, 0, from, hdr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features,
|
|
|
|
struct iov_iter *iter,
|
|
|
|
const struct virtio_net_hdr *hdr)
|
2025-02-07 15:10:55 +09:00
|
|
|
{
|
2025-07-08 17:55:30 +02:00
|
|
|
unsigned int parsed_size = tun_vnet_parse_size(features);
|
|
|
|
|
2025-02-07 15:10:55 +09:00
|
|
|
if (unlikely(iov_iter_count(iter) < sz))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size))
|
2025-02-07 15:10:55 +09:00
|
|
|
return -EFAULT;
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size)
|
2025-02-15 15:04:50 +09:00
|
|
|
return -EFAULT;
|
2025-02-07 15:10:55 +09:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter,
|
|
|
|
const struct virtio_net_hdr *hdr)
|
|
|
|
{
|
|
|
|
return __tun_vnet_hdr_put(sz, 0, iter, hdr);
|
|
|
|
}
|
|
|
|
|
2025-02-07 15:10:55 +09:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
/*
|
|
|
|
* Tun is not aware of the negotiated guest features, guess them from the
|
|
|
|
* virtio net hdr size
|
|
|
|
*/
|
|
|
|
static inline netdev_features_t tun_vnet_hdr_guest_features(int vnet_hdr_sz)
|
|
|
|
{
|
|
|
|
if (vnet_hdr_sz >= TUN_VNET_TNL_SIZE)
|
|
|
|
return NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_UDP_TUNNEL_CSUM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
tun_vnet_hdr_tnl_to_skb(unsigned int flags, netdev_features_t features,
|
|
|
|
struct sk_buff *skb,
|
|
|
|
const struct virtio_net_hdr_v1_hash_tunnel *hdr)
|
|
|
|
{
|
|
|
|
return virtio_net_hdr_tnl_to_skb(skb, hdr,
|
|
|
|
features & NETIF_F_GSO_UDP_TUNNEL,
|
|
|
|
features & NETIF_F_GSO_UDP_TUNNEL_CSUM,
|
|
|
|
tun_vnet_is_little_endian(flags));
|
|
|
|
}
|
|
|
|
|
2025-02-07 15:10:55 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-07-08 17:55:30 +02:00
|
|
|
static inline int
|
|
|
|
tun_vnet_hdr_tnl_from_skb(unsigned int flags,
|
|
|
|
const struct net_device *dev,
|
|
|
|
const struct sk_buff *skb,
|
|
|
|
struct virtio_net_hdr_v1_hash_tunnel *tnl_hdr)
|
|
|
|
{
|
|
|
|
bool has_tnl_offload = !!(dev->features & NETIF_F_GSO_UDP_TUNNEL);
|
|
|
|
int vlan_hlen = skb_vlan_tag_present(skb) ? VLAN_HLEN : 0;
|
|
|
|
|
|
|
|
if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload,
|
|
|
|
tun_vnet_is_little_endian(flags),
|
|
|
|
vlan_hlen)) {
|
|
|
|
struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr;
|
|
|
|
struct skb_shared_info *sinfo = skb_shinfo(skb);
|
|
|
|
|
|
|
|
if (net_ratelimit()) {
|
|
|
|
int hdr_len = tun_vnet16_to_cpu(flags, hdr->hdr_len);
|
|
|
|
|
|
|
|
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(hdr_len, 64),
|
|
|
|
true);
|
|
|
|
}
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-02-07 15:10:55 +09:00
|
|
|
#endif /* TUN_VNET_H */
|