Branch data Line data Source code
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 : : 10 : : static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, 11 : : const struct virtio_net_hdr *hdr) 12 : : { 13 : 0 : switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { 14 : : case VIRTIO_NET_HDR_GSO_TCPV4: 15 : : case VIRTIO_NET_HDR_GSO_UDP: 16 : 0 : skb->protocol = cpu_to_be16(ETH_P_IP); 17 : : break; 18 : : case VIRTIO_NET_HDR_GSO_TCPV6: 19 : 0 : skb->protocol = cpu_to_be16(ETH_P_IPV6); 20 : : break; 21 : : default: 22 : : return -EINVAL; 23 : : } 24 : : 25 : : return 0; 26 : : } 27 : : 28 : 0 : static 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 : 0 : if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { 38 : 0 : 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 : 0 : 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 : 0 : break; 54 : : default: 55 : : return -EINVAL; 56 : : } 57 : : 58 : 0 : if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN) 59 : 0 : gso_type |= SKB_GSO_TCP_ECN; 60 : : 61 : 0 : if (hdr->gso_size == 0) 62 : : return -EINVAL; 63 : : } 64 : : 65 : 0 : if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { 66 : 0 : u16 start = __virtio16_to_cpu(little_endian, hdr->csum_start); 67 : 0 : u16 off = __virtio16_to_cpu(little_endian, hdr->csum_offset); 68 : : 69 : 0 : if (!skb_partial_csum_set(skb, start, off)) 70 : : return -EINVAL; 71 : : 72 : 0 : p_off = skb_transport_offset(skb) + thlen; 73 : 0 : 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 : 0 : if (gso_type && skb->network_header) { 80 : : struct flow_keys_basic keys; 81 : : 82 : 0 : if (!skb->protocol) 83 : : virtio_net_hdr_set_proto(skb, hdr); 84 : : retry: 85 : 0 : 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 : 0 : if (gso_type & SKB_GSO_UDP && 90 : 0 : skb->protocol == htons(ETH_P_IP)) { 91 : 0 : skb->protocol = htons(ETH_P_IPV6); 92 : 0 : goto retry; 93 : : } 94 : 0 : return -EINVAL; 95 : : } 96 : : 97 : 0 : p_off = keys.control.thoff + thlen; 98 : 0 : if (p_off > skb_headlen(skb) || 99 : 0 : keys.basic.ip_proto != ip_proto) 100 : : return -EINVAL; 101 : : 102 : : skb_set_transport_header(skb, keys.control.thoff); 103 : 0 : } else if (gso_type) { 104 : : p_off = thlen; 105 : 0 : if (p_off > skb_headlen(skb)) 106 : : return -EINVAL; 107 : : } 108 : : } 109 : : 110 : 0 : if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { 111 : 0 : 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 : 0 : if (skb->len - p_off > gso_size) { 116 : 0 : shinfo->gso_size = gso_size; 117 : 0 : shinfo->gso_type = gso_type; 118 : : 119 : : /* Header must be checked, and gso_segs computed. */ 120 : 0 : shinfo->gso_type |= SKB_GSO_DODGY; 121 : 0 : shinfo->gso_segs = 0; 122 : : } 123 : : } 124 : : 125 : : return 0; 126 : : } 127 : : 128 : 0 : static 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 : 0 : memset(hdr, 0, sizeof(*hdr)); /* no info leak */ 135 : : 136 : 0 : 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 : 0 : hdr->hdr_len = __cpu_to_virtio16(little_endian, 141 : : skb_headlen(skb)); 142 : 0 : hdr->gso_size = __cpu_to_virtio16(little_endian, 143 : : sinfo->gso_size); 144 : 0 : if (sinfo->gso_type & SKB_GSO_TCPV4) 145 : 0 : hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; 146 : 0 : else if (sinfo->gso_type & SKB_GSO_TCPV6) 147 : 0 : hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; 148 : : else 149 : : return -EINVAL; 150 : 0 : if (sinfo->gso_type & SKB_GSO_TCP_ECN) 151 : 0 : hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; 152 : : } else 153 : 0 : hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; 154 : : 155 : 0 : if (skb->ip_summed == CHECKSUM_PARTIAL) { 156 : 0 : hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; 157 : 0 : hdr->csum_start = __cpu_to_virtio16(little_endian, 158 : : skb_checksum_start_offset(skb) + vlan_hlen); 159 : 0 : hdr->csum_offset = __cpu_to_virtio16(little_endian, 160 : : skb->csum_offset); 161 : 0 : } else if (has_data_valid && 162 : : skb->ip_summed == CHECKSUM_UNNECESSARY) { 163 : 0 : 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 */