| /* 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 | |
| #define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel) | |
| 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) | |
| WRITE_ONCE(*flags, *flags | TUN_VNET_BE); | |
| else | |
| WRITE_ONCE(*flags, *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) | |
| WRITE_ONCE(*flags, *flags | TUN_VNET_LE); | |
| else | |
| WRITE_ONCE(*flags, *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 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) | |
| { | |
| unsigned int parsed_size = tun_vnet_parse_size(features); | |
| u16 hdr_len; | |
| if (iov_iter_count(from) < sz) | |
| return -EINVAL; | |
| if (!copy_from_iter_full(hdr, parsed_size, 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 - parsed_size); | |
| return hdr_len; | |
| } | |
| 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) | |
| { | |
| unsigned int parsed_size = tun_vnet_parse_size(features); | |
| if (unlikely(iov_iter_count(iter) < sz)) | |
| return -EINVAL; | |
| if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size)) | |
| return -EFAULT; | |
| if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| 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); | |
| } | |
| 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)); | |
| } | |
| /* | |
| * 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)); | |
| } | |
| 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; | |
| } | |
| 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, true, false)) { | |
| 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; | |
| } | |
| #endif /* TUN_VNET_H */ |