linux/include/linux/virtio_net.h
<<
>>
Prefs
   1/* SPDX-License-Identifier: GPL-2.0 */
   2#ifndef _LINUX_VIRTIO_NET_H
   3#define _LINUX_VIRTIO_NET_H
   4
   5#include <linux/if_vlan.h>
   6#include <uapi/linux/tcp.h>
   7#include <uapi/linux/udp.h>
   8#include <uapi/linux/virtio_net.h>
   9
  10static inline int virtio_net_hdr_set_proto(struct sk_buff *skb,
  11                                           const struct virtio_net_hdr *hdr)
  12{
  13        switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
  14        case VIRTIO_NET_HDR_GSO_TCPV4:
  15        case VIRTIO_NET_HDR_GSO_UDP:
  16                skb->protocol = cpu_to_be16(ETH_P_IP);
  17                break;
  18        case VIRTIO_NET_HDR_GSO_TCPV6:
  19                skb->protocol = cpu_to_be16(ETH_P_IPV6);
  20                break;
  21        default:
  22                return -EINVAL;
  23        }
  24
  25        return 0;
  26}
  27
  28static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
  29                                        const struct virtio_net_hdr *hdr,
  30                                        bool little_endian)
  31{
  32        unsigned int gso_type = 0;
  33        unsigned int thlen = 0;
  34        unsigned int p_off = 0;
  35        unsigned int ip_proto;
  36
  37        if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
  38                switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
  39                case VIRTIO_NET_HDR_GSO_TCPV4:
  40                        gso_type = SKB_GSO_TCPV4;
  41                        ip_proto = IPPROTO_TCP;
  42                        thlen = sizeof(struct tcphdr);
  43                        break;
  44                case VIRTIO_NET_HDR_GSO_TCPV6:
  45                        gso_type = SKB_GSO_TCPV6;
  46                        ip_proto = IPPROTO_TCP;
  47                        thlen = sizeof(struct tcphdr);
  48                        break;
  49                case VIRTIO_NET_HDR_GSO_UDP:
  50                        gso_type = SKB_GSO_UDP;
  51                        ip_proto = IPPROTO_UDP;
  52                        thlen = sizeof(struct udphdr);
  53                        break;
  54                default:
  55                        return -EINVAL;
  56                }
  57
  58                if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN)
  59                        gso_type |= SKB_GSO_TCP_ECN;
  60
  61                if (hdr->gso_size == 0)
  62                        return -EINVAL;
  63        }
  64
  65        if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
  66                u16 start = __virtio16_to_cpu(little_endian, hdr->csum_start);
  67                u16 off = __virtio16_to_cpu(little_endian, hdr->csum_offset);
  68
  69                if (!skb_partial_csum_set(skb, start, off))
  70                        return -EINVAL;
  71
  72                p_off = skb_transport_offset(skb) + thlen;
  73                if (p_off > skb_headlen(skb))
  74                        return -EINVAL;
  75        } else {
  76                /* gso packets without NEEDS_CSUM do not set transport_offset.
  77                 * probe and drop if does not match one of the above types.
  78                 */
  79                if (gso_type && skb->network_header) {
  80                        struct flow_keys_basic keys;
  81
  82                        if (!skb->protocol)
  83                                virtio_net_hdr_set_proto(skb, hdr);
  84retry:
  85                        if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys,
  86                                                              NULL, 0, 0, 0,
  87                                                              0)) {
  88                                /* UFO does not specify ipv4 or 6: try both */
  89                                if (gso_type & SKB_GSO_UDP &&
  90                                    skb->protocol == htons(ETH_P_IP)) {
  91                                        skb->protocol = htons(ETH_P_IPV6);
  92                                        goto retry;
  93                                }
  94                                return -EINVAL;
  95                        }
  96
  97                        p_off = keys.control.thoff + thlen;
  98                        if (p_off > skb_headlen(skb) ||
  99                            keys.basic.ip_proto != ip_proto)
 100                                return -EINVAL;
 101
 102                        skb_set_transport_header(skb, keys.control.thoff);
 103                } else if (gso_type) {
 104                        p_off = thlen;
 105                        if (p_off > skb_headlen(skb))
 106                                return -EINVAL;
 107                }
 108        }
 109
 110        if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
 111                u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size);
 112                struct skb_shared_info *shinfo = skb_shinfo(skb);
 113
 114                /* Too small packets are not really GSO ones. */
 115                if (skb->len - p_off > gso_size) {
 116                        shinfo->gso_size = gso_size;
 117                        shinfo->gso_type = gso_type;
 118
 119                        /* Header must be checked, and gso_segs computed. */
 120                        shinfo->gso_type |= SKB_GSO_DODGY;
 121                        shinfo->gso_segs = 0;
 122                }
 123        }
 124
 125        return 0;
 126}
 127
 128static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb,
 129                                          struct virtio_net_hdr *hdr,
 130                                          bool little_endian,
 131                                          bool has_data_valid,
 132                                          int vlan_hlen)
 133{
 134        memset(hdr, 0, sizeof(*hdr));   /* no info leak */
 135
 136        if (skb_is_gso(skb)) {
 137                struct skb_shared_info *sinfo = skb_shinfo(skb);
 138
 139                /* This is a hint as to how much should be linear. */
 140                hdr->hdr_len = __cpu_to_virtio16(little_endian,
 141                                                 skb_headlen(skb));
 142                hdr->gso_size = __cpu_to_virtio16(little_endian,
 143                                                  sinfo->gso_size);
 144                if (sinfo->gso_type & SKB_GSO_TCPV4)
 145                        hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
 146                else if (sinfo->gso_type & SKB_GSO_TCPV6)
 147                        hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
 148                else
 149                        return -EINVAL;
 150                if (sinfo->gso_type & SKB_GSO_TCP_ECN)
 151                        hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
 152        } else
 153                hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
 154
 155        if (skb->ip_summed == CHECKSUM_PARTIAL) {
 156                hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
 157                hdr->csum_start = __cpu_to_virtio16(little_endian,
 158                        skb_checksum_start_offset(skb) + vlan_hlen);
 159                hdr->csum_offset = __cpu_to_virtio16(little_endian,
 160                                skb->csum_offset);
 161        } else if (has_data_valid &&
 162                   skb->ip_summed == CHECKSUM_UNNECESSARY) {
 163                hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID;
 164        } /* else everything is zero */
 165
 166        return 0;
 167}
 168
 169#endif /* _LINUX_VIRTIO_NET_H */
 170