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