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 <linux/udp.h> |
7 | #include <uapi/linux/tcp.h> |
8 | #include <uapi/linux/virtio_net.h> |
9 | |
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: |
18 | case VIRTIO_NET_HDR_GSO_UDP_L4: |
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 | |
26 | static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, |
27 | const struct virtio_net_hdr *hdr) |
28 | { |
29 | if (skb->protocol) |
30 | return 0; |
31 | |
32 | switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { |
33 | case VIRTIO_NET_HDR_GSO_TCPV4: |
34 | case VIRTIO_NET_HDR_GSO_UDP: |
35 | case VIRTIO_NET_HDR_GSO_UDP_L4: |
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 | |
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 | { |
52 | unsigned int gso_type = 0; |
53 | unsigned int thlen = 0; |
54 | unsigned int p_off = 0; |
55 | unsigned int ip_proto; |
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; |
61 | ip_proto = IPPROTO_TCP; |
62 | thlen = sizeof(struct tcphdr); |
63 | break; |
64 | case VIRTIO_NET_HDR_GSO_TCPV6: |
65 | gso_type = SKB_GSO_TCPV6; |
66 | ip_proto = IPPROTO_TCP; |
67 | thlen = sizeof(struct tcphdr); |
68 | break; |
69 | case VIRTIO_NET_HDR_GSO_UDP: |
70 | gso_type = SKB_GSO_UDP; |
71 | ip_proto = IPPROTO_UDP; |
72 | thlen = sizeof(struct udphdr); |
73 | break; |
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; |
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 | |
90 | skb_reset_mac_header(skb); |
91 | |
92 | if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { |
93 | u32 start = __virtio16_to_cpu(little_endian, val: hdr->csum_start); |
94 | u32 off = __virtio16_to_cpu(little_endian, val: hdr->csum_offset); |
95 | u32 needed = start + max_t(u32, thlen, off + sizeof(__sum16)); |
96 | |
97 | if (!pskb_may_pull(skb, len: needed)) |
98 | return -EINVAL; |
99 | |
100 | if (!skb_partial_csum_set(skb, start, off)) |
101 | return -EINVAL; |
102 | |
103 | p_off = skb_transport_offset(skb) + thlen; |
104 | if (!pskb_may_pull(skb, len: p_off)) |
105 | return -EINVAL; |
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 | */ |
110 | if (gso_type && skb->network_header) { |
111 | struct flow_keys_basic keys; |
112 | |
113 | if (!skb->protocol) { |
114 | __be16 protocol = dev_parse_header_protocol(skb); |
115 | |
116 | if (!protocol) |
117 | virtio_net_hdr_set_proto(skb, hdr); |
118 | else if (!virtio_net_hdr_match_proto(protocol, gso_type: hdr->gso_type)) |
119 | return -EINVAL; |
120 | else |
121 | skb->protocol = protocol; |
122 | } |
123 | retry: |
124 | if (!skb_flow_dissect_flow_keys_basic(NULL, skb, flow: &keys, |
125 | NULL, proto: 0, nhoff: 0, hlen: 0, |
126 | flags: 0)) { |
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 | } |
133 | return -EINVAL; |
134 | } |
135 | |
136 | p_off = keys.control.thoff + thlen; |
137 | if (!pskb_may_pull(skb, len: p_off) || |
138 | keys.basic.ip_proto != ip_proto) |
139 | return -EINVAL; |
140 | |
141 | skb_set_transport_header(skb, offset: keys.control.thoff); |
142 | } else if (gso_type) { |
143 | p_off = thlen; |
144 | if (!pskb_may_pull(skb, len: p_off)) |
145 | return -EINVAL; |
146 | } |
147 | } |
148 | |
149 | if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { |
150 | u16 gso_size = __virtio16_to_cpu(little_endian, val: hdr->gso_size); |
151 | unsigned int nh_off = p_off; |
152 | struct skb_shared_info *shinfo = skb_shinfo(skb); |
153 | |
154 | switch (gso_type & ~SKB_GSO_TCP_ECN) { |
155 | case SKB_GSO_UDP: |
156 | /* UFO may not include transport header in gso_size. */ |
157 | nh_off -= thlen; |
158 | break; |
159 | case SKB_GSO_UDP_L4: |
160 | if (!(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) |
161 | return -EINVAL; |
162 | if (skb->csum_offset != offsetof(struct udphdr, check)) |
163 | return -EINVAL; |
164 | if (skb->len - p_off > gso_size * UDP_MAX_SEGMENTS) |
165 | return -EINVAL; |
166 | if (gso_type != SKB_GSO_UDP_L4) |
167 | return -EINVAL; |
168 | break; |
169 | } |
170 | |
171 | /* Kernel has a special handling for GSO_BY_FRAGS. */ |
172 | if (gso_size == GSO_BY_FRAGS) |
173 | return -EINVAL; |
174 | |
175 | /* Too small packets are not really GSO ones. */ |
176 | if (skb->len - nh_off > gso_size) { |
177 | shinfo->gso_size = gso_size; |
178 | shinfo->gso_type = gso_type; |
179 | |
180 | /* Header must be checked, and gso_segs computed. */ |
181 | shinfo->gso_type |= SKB_GSO_DODGY; |
182 | shinfo->gso_segs = 0; |
183 | } |
184 | } |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, |
190 | struct virtio_net_hdr *hdr, |
191 | bool little_endian, |
192 | bool has_data_valid, |
193 | int vlan_hlen) |
194 | { |
195 | memset(hdr, 0, sizeof(*hdr)); /* no info leak */ |
196 | |
197 | if (skb_is_gso(skb)) { |
198 | struct skb_shared_info *sinfo = skb_shinfo(skb); |
199 | |
200 | /* This is a hint as to how much should be linear. */ |
201 | hdr->hdr_len = __cpu_to_virtio16(little_endian, |
202 | val: skb_headlen(skb)); |
203 | hdr->gso_size = __cpu_to_virtio16(little_endian, |
204 | val: sinfo->gso_size); |
205 | if (sinfo->gso_type & SKB_GSO_TCPV4) |
206 | hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; |
207 | else if (sinfo->gso_type & SKB_GSO_TCPV6) |
208 | hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; |
209 | else if (sinfo->gso_type & SKB_GSO_UDP_L4) |
210 | hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4; |
211 | else |
212 | return -EINVAL; |
213 | if (sinfo->gso_type & SKB_GSO_TCP_ECN) |
214 | hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; |
215 | } else |
216 | hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; |
217 | |
218 | if (skb->ip_summed == CHECKSUM_PARTIAL) { |
219 | hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; |
220 | hdr->csum_start = __cpu_to_virtio16(little_endian, |
221 | val: skb_checksum_start_offset(skb) + vlan_hlen); |
222 | hdr->csum_offset = __cpu_to_virtio16(little_endian, |
223 | val: skb->csum_offset); |
224 | } else if (has_data_valid && |
225 | skb->ip_summed == CHECKSUM_UNNECESSARY) { |
226 | hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID; |
227 | } /* else everything is zero */ |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | #endif /* _LINUX_VIRTIO_NET_H */ |
233 | |