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 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 28 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 ip_proto; 35 36 if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { 37 switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { 38 case VIRTIO_NET_HDR_GSO_TCPV4: 39 gso_type = SKB_GSO_TCPV4; 40 ip_proto = IPPROTO_TCP; 41 thlen = sizeof(struct tcphdr); 42 break; 43 case VIRTIO_NET_HDR_GSO_TCPV6: 44 gso_type = SKB_GSO_TCPV6; 45 ip_proto = IPPROTO_TCP; 46 thlen = sizeof(struct tcphdr); 47 break; 48 case VIRTIO_NET_HDR_GSO_UDP: 49 gso_type = SKB_GSO_UDP; 50 ip_proto = IPPROTO_UDP; 51 thlen = sizeof(struct udphdr); 52 break; 53 default: 54 return -EINVAL; 55 } 56 57 if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN) 58 gso_type |= SKB_GSO_TCP_ECN; 59 60 if (hdr->gso_size == 0) 61 return -EINVAL; 62 } 63 64 if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { 65 u16 start = __virtio16_to_cpu(little_endian, hdr->csum_start); 66 u16 off = __virtio16_to_cpu(little_endian, hdr->csum_offset); 67 68 if (!skb_partial_csum_set(skb, start, off)) 69 return -EINVAL; 70 71 if (skb_transport_offset(skb) + thlen > skb_headlen(skb)) 72 return -EINVAL; 73 } else { 74 /* gso packets without NEEDS_CSUM do not set transport_offset. 75 * probe and drop if does not match one of the above types. 76 */ 77 if (gso_type && skb->network_header) { 78 struct flow_keys_basic keys; 79 80 if (!skb->protocol) 81 virtio_net_hdr_set_proto(skb, hdr); 82 retry: 83 if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, 84 NULL, 0, 0, 0, 85 0)) { 86 /* UFO does not specify ipv4 or 6: try both */ 87 if (gso_type & SKB_GSO_UDP && 88 skb->protocol == htons(ETH_P_IP)) { 89 skb->protocol = htons(ETH_P_IPV6); 90 goto retry; 91 } 92 return -EINVAL; 93 } 94 95 if (keys.control.thoff + thlen > skb_headlen(skb) || 96 keys.basic.ip_proto != ip_proto) 97 return -EINVAL; 98 99 skb_set_transport_header(skb, keys.control.thoff); 100 } 101 } 102 103 if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { 104 u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size); 105 106 skb_shinfo(skb)->gso_size = gso_size; 107 skb_shinfo(skb)->gso_type = gso_type; 108 109 /* Header must be checked, and gso_segs computed. */ 110 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY; 111 skb_shinfo(skb)->gso_segs = 0; 112 } 113 114 return 0; 115 } 116 117 static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, 118 struct virtio_net_hdr *hdr, 119 bool little_endian, 120 bool has_data_valid, 121 int vlan_hlen) 122 { 123 memset(hdr, 0, sizeof(*hdr)); /* no info leak */ 124 125 if (skb_is_gso(skb)) { 126 struct skb_shared_info *sinfo = skb_shinfo(skb); 127 128 /* This is a hint as to how much should be linear. */ 129 hdr->hdr_len = __cpu_to_virtio16(little_endian, 130 skb_headlen(skb)); 131 hdr->gso_size = __cpu_to_virtio16(little_endian, 132 sinfo->gso_size); 133 if (sinfo->gso_type & SKB_GSO_TCPV4) 134 hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; 135 else if (sinfo->gso_type & SKB_GSO_TCPV6) 136 hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; 137 else 138 return -EINVAL; 139 if (sinfo->gso_type & SKB_GSO_TCP_ECN) 140 hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; 141 } else 142 hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; 143 144 if (skb->ip_summed == CHECKSUM_PARTIAL) { 145 hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; 146 hdr->csum_start = __cpu_to_virtio16(little_endian, 147 skb_checksum_start_offset(skb) + vlan_hlen); 148 hdr->csum_offset = __cpu_to_virtio16(little_endian, 149 skb->csum_offset); 150 } else if (has_data_valid && 151 skb->ip_summed == CHECKSUM_UNNECESSARY) { 152 hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID; 153 } /* else everything is zero */ 154 155 return 0; 156 } 157 158 #endif /* _LINUX_VIRTIO_NET_H */ 159