Commit | Line | Data |
---|---|---|
b2441318 | 1 | /* SPDX-License-Identifier: GPL-2.0 */ |
fd2a0437 MR |
2 | #ifndef _LINUX_VIRTIO_NET_H |
3 | #define _LINUX_VIRTIO_NET_H | |
4 | ||
5 | #include <linux/if_vlan.h> | |
9181d6f8 ED |
6 | #include <linux/ip.h> |
7 | #include <linux/ipv6.h> | |
fc8b2a61 | 8 | #include <linux/udp.h> |
9274124f | 9 | #include <uapi/linux/tcp.h> |
fd2a0437 MR |
10 | #include <uapi/linux/virtio_net.h> |
11 | ||
7e5cced9 WB |
12 | static inline bool virtio_net_hdr_match_proto(__be16 protocol, __u8 gso_type) |
13 | { | |
14 | switch (gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { | |
15 | case VIRTIO_NET_HDR_GSO_TCPV4: | |
16 | return protocol == cpu_to_be16(ETH_P_IP); | |
17 | case VIRTIO_NET_HDR_GSO_TCPV6: | |
18 | return protocol == cpu_to_be16(ETH_P_IPV6); | |
19 | case VIRTIO_NET_HDR_GSO_UDP: | |
860b7f27 | 20 | case VIRTIO_NET_HDR_GSO_UDP_L4: |
7e5cced9 WB |
21 | return protocol == cpu_to_be16(ETH_P_IP) || |
22 | protocol == cpu_to_be16(ETH_P_IPV6); | |
23 | default: | |
24 | return false; | |
25 | } | |
26 | } | |
27 | ||
9d2f67e4 JT |
28 | static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, |
29 | const struct virtio_net_hdr *hdr) | |
30 | { | |
1ed1d592 WB |
31 | if (skb->protocol) |
32 | return 0; | |
33 | ||
9d2f67e4 JT |
34 | switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { |
35 | case VIRTIO_NET_HDR_GSO_TCPV4: | |
36 | case VIRTIO_NET_HDR_GSO_UDP: | |
860b7f27 | 37 | case VIRTIO_NET_HDR_GSO_UDP_L4: |
9d2f67e4 JT |
38 | skb->protocol = cpu_to_be16(ETH_P_IP); |
39 | break; | |
40 | case VIRTIO_NET_HDR_GSO_TCPV6: | |
41 | skb->protocol = cpu_to_be16(ETH_P_IPV6); | |
42 | break; | |
43 | default: | |
44 | return -EINVAL; | |
45 | } | |
46 | ||
47 | return 0; | |
48 | } | |
49 | ||
fd2a0437 MR |
50 | static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, |
51 | const struct virtio_net_hdr *hdr, | |
52 | bool little_endian) | |
53 | { | |
9181d6f8 | 54 | unsigned int nh_min_len = sizeof(struct iphdr); |
0c19f846 | 55 | unsigned int gso_type = 0; |
9274124f | 56 | unsigned int thlen = 0; |
6dd912f8 | 57 | unsigned int p_off = 0; |
9274124f | 58 | unsigned int ip_proto; |
fd2a0437 MR |
59 | |
60 | if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { | |
61 | switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { | |
62 | case VIRTIO_NET_HDR_GSO_TCPV4: | |
63 | gso_type = SKB_GSO_TCPV4; | |
9274124f WB |
64 | ip_proto = IPPROTO_TCP; |
65 | thlen = sizeof(struct tcphdr); | |
fd2a0437 MR |
66 | break; |
67 | case VIRTIO_NET_HDR_GSO_TCPV6: | |
68 | gso_type = SKB_GSO_TCPV6; | |
9274124f WB |
69 | ip_proto = IPPROTO_TCP; |
70 | thlen = sizeof(struct tcphdr); | |
9181d6f8 | 71 | nh_min_len = sizeof(struct ipv6hdr); |
fd2a0437 | 72 | break; |
0c19f846 WB |
73 | case VIRTIO_NET_HDR_GSO_UDP: |
74 | gso_type = SKB_GSO_UDP; | |
9274124f WB |
75 | ip_proto = IPPROTO_UDP; |
76 | thlen = sizeof(struct udphdr); | |
0c19f846 | 77 | break; |
860b7f27 AM |
78 | case VIRTIO_NET_HDR_GSO_UDP_L4: |
79 | gso_type = SKB_GSO_UDP_L4; | |
80 | ip_proto = IPPROTO_UDP; | |
81 | thlen = sizeof(struct udphdr); | |
82 | break; | |
fd2a0437 MR |
83 | default: |
84 | return -EINVAL; | |
85 | } | |
86 | ||
87 | if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN) | |
88 | gso_type |= SKB_GSO_TCP_ECN; | |
89 | ||
90 | if (hdr->gso_size == 0) | |
91 | return -EINVAL; | |
92 | } | |
93 | ||
61431a59 ED |
94 | skb_reset_mac_header(skb); |
95 | ||
fd2a0437 | 96 | if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { |
0f6925b3 ED |
97 | u32 start = __virtio16_to_cpu(little_endian, hdr->csum_start); |
98 | u32 off = __virtio16_to_cpu(little_endian, hdr->csum_offset); | |
99 | u32 needed = start + max_t(u32, thlen, off + sizeof(__sum16)); | |
100 | ||
101 | if (!pskb_may_pull(skb, needed)) | |
102 | return -EINVAL; | |
fd2a0437 MR |
103 | |
104 | if (!skb_partial_csum_set(skb, start, off)) | |
105 | return -EINVAL; | |
49d14b54 ED |
106 | if (skb_transport_offset(skb) < nh_min_len) |
107 | return -EINVAL; | |
9274124f | 108 | |
49d14b54 | 109 | nh_min_len = skb_transport_offset(skb); |
9181d6f8 | 110 | p_off = nh_min_len + thlen; |
0f6925b3 | 111 | if (!pskb_may_pull(skb, p_off)) |
9274124f | 112 | return -EINVAL; |
d5be7f63 WB |
113 | } else { |
114 | /* gso packets without NEEDS_CSUM do not set transport_offset. | |
115 | * probe and drop if does not match one of the above types. | |
116 | */ | |
9e8db591 | 117 | if (gso_type && skb->network_header) { |
9274124f WB |
118 | struct flow_keys_basic keys; |
119 | ||
924a9bc3 BN |
120 | if (!skb->protocol) { |
121 | __be16 protocol = dev_parse_header_protocol(skb); | |
122 | ||
7e5cced9 WB |
123 | if (!protocol) |
124 | virtio_net_hdr_set_proto(skb, hdr); | |
125 | else if (!virtio_net_hdr_match_proto(protocol, hdr->gso_type)) | |
924a9bc3 | 126 | return -EINVAL; |
7e5cced9 WB |
127 | else |
128 | skb->protocol = protocol; | |
924a9bc3 | 129 | } |
9e8db591 | 130 | retry: |
9274124f WB |
131 | if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, |
132 | NULL, 0, 0, 0, | |
133 | 0)) { | |
9e8db591 WB |
134 | /* UFO does not specify ipv4 or 6: try both */ |
135 | if (gso_type & SKB_GSO_UDP && | |
136 | skb->protocol == htons(ETH_P_IP)) { | |
137 | skb->protocol = htons(ETH_P_IPV6); | |
138 | goto retry; | |
139 | } | |
d5be7f63 | 140 | return -EINVAL; |
9e8db591 | 141 | } |
9274124f | 142 | |
6dd912f8 | 143 | p_off = keys.control.thoff + thlen; |
0f6925b3 | 144 | if (!pskb_may_pull(skb, p_off) || |
9274124f WB |
145 | keys.basic.ip_proto != ip_proto) |
146 | return -EINVAL; | |
147 | ||
148 | skb_set_transport_header(skb, keys.control.thoff); | |
6dd912f8 | 149 | } else if (gso_type) { |
9181d6f8 | 150 | p_off = nh_min_len + thlen; |
0f6925b3 | 151 | if (!pskb_may_pull(skb, p_off)) |
6dd912f8 | 152 | return -EINVAL; |
d5be7f63 | 153 | } |
fd2a0437 MR |
154 | } |
155 | ||
156 | if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { | |
157 | u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size); | |
cf9acc90 | 158 | unsigned int nh_off = p_off; |
7c6d2ecb | 159 | struct skb_shared_info *shinfo = skb_shinfo(skb); |
fd2a0437 | 160 | |
fc8b2a61 WB |
161 | switch (gso_type & ~SKB_GSO_TCP_ECN) { |
162 | case SKB_GSO_UDP: | |
163 | /* UFO may not include transport header in gso_size. */ | |
cf9acc90 | 164 | nh_off -= thlen; |
fc8b2a61 WB |
165 | break; |
166 | case SKB_GSO_UDP_L4: | |
167 | if (!(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) | |
168 | return -EINVAL; | |
169 | if (skb->csum_offset != offsetof(struct udphdr, check)) | |
170 | return -EINVAL; | |
171 | if (skb->len - p_off > gso_size * UDP_MAX_SEGMENTS) | |
172 | return -EINVAL; | |
173 | if (gso_type != SKB_GSO_UDP_L4) | |
174 | return -EINVAL; | |
175 | break; | |
89add400 WB |
176 | case SKB_GSO_TCPV4: |
177 | case SKB_GSO_TCPV6: | |
6513eb3d WB |
178 | if (skb->ip_summed == CHECKSUM_PARTIAL && |
179 | skb->csum_offset != offsetof(struct tcphdr, check)) | |
89add400 WB |
180 | return -EINVAL; |
181 | break; | |
fc8b2a61 | 182 | } |
cf9acc90 | 183 | |
b616be6b ED |
184 | /* Kernel has a special handling for GSO_BY_FRAGS. */ |
185 | if (gso_size == GSO_BY_FRAGS) | |
186 | return -EINVAL; | |
187 | ||
7c6d2ecb | 188 | /* Too small packets are not really GSO ones. */ |
cf9acc90 | 189 | if (skb->len - nh_off > gso_size) { |
7c6d2ecb ED |
190 | shinfo->gso_size = gso_size; |
191 | shinfo->gso_type = gso_type; | |
fd2a0437 | 192 | |
7c6d2ecb ED |
193 | /* Header must be checked, and gso_segs computed. */ |
194 | shinfo->gso_type |= SKB_GSO_DODGY; | |
195 | shinfo->gso_segs = 0; | |
196 | } | |
fd2a0437 MR |
197 | } |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, | |
203 | struct virtio_net_hdr *hdr, | |
6391a448 | 204 | bool little_endian, |
fd3a8862 WB |
205 | bool has_data_valid, |
206 | int vlan_hlen) | |
fd2a0437 | 207 | { |
9403cd7c | 208 | memset(hdr, 0, sizeof(*hdr)); /* no info leak */ |
fd2a0437 MR |
209 | |
210 | if (skb_is_gso(skb)) { | |
211 | struct skb_shared_info *sinfo = skb_shinfo(skb); | |
212 | ||
213 | /* This is a hint as to how much should be linear. */ | |
214 | hdr->hdr_len = __cpu_to_virtio16(little_endian, | |
215 | skb_headlen(skb)); | |
216 | hdr->gso_size = __cpu_to_virtio16(little_endian, | |
217 | sinfo->gso_size); | |
218 | if (sinfo->gso_type & SKB_GSO_TCPV4) | |
219 | hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; | |
220 | else if (sinfo->gso_type & SKB_GSO_TCPV6) | |
221 | hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; | |
860b7f27 AM |
222 | else if (sinfo->gso_type & SKB_GSO_UDP_L4) |
223 | hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4; | |
fd2a0437 MR |
224 | else |
225 | return -EINVAL; | |
226 | if (sinfo->gso_type & SKB_GSO_TCP_ECN) | |
227 | hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; | |
228 | } else | |
229 | hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; | |
230 | ||
231 | if (skb->ip_summed == CHECKSUM_PARTIAL) { | |
232 | hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; | |
fd3a8862 WB |
233 | hdr->csum_start = __cpu_to_virtio16(little_endian, |
234 | skb_checksum_start_offset(skb) + vlan_hlen); | |
fd2a0437 MR |
235 | hdr->csum_offset = __cpu_to_virtio16(little_endian, |
236 | skb->csum_offset); | |
6391a448 JW |
237 | } else if (has_data_valid && |
238 | skb->ip_summed == CHECKSUM_UNNECESSARY) { | |
239 | hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID; | |
fd2a0437 MR |
240 | } /* else everything is zero */ |
241 | ||
242 | return 0; | |
243 | } | |
244 | ||
d66016a7 | 245 | #endif /* _LINUX_VIRTIO_NET_H */ |