1b2441318SGreg Kroah-Hartman /* SPDX-License-Identifier: GPL-2.0 */
2fd2a0437SMike Rapoport #ifndef _LINUX_VIRTIO_NET_H
3fd2a0437SMike Rapoport #define _LINUX_VIRTIO_NET_H
4fd2a0437SMike Rapoport
5fd2a0437SMike Rapoport #include <linux/if_vlan.h>
6342c88f4SEric Dumazet #include <linux/ip.h>
7342c88f4SEric Dumazet #include <linux/ipv6.h>
8fc8b2a61SWillem de Bruijn #include <linux/udp.h>
99274124fSWillem de Bruijn #include <uapi/linux/tcp.h>
10fd2a0437SMike Rapoport #include <uapi/linux/virtio_net.h>
11fd2a0437SMike Rapoport
virtio_net_hdr_match_proto(__be16 protocol,__u8 gso_type)127e5cced9SWillem de Bruijn static inline bool virtio_net_hdr_match_proto(__be16 protocol, __u8 gso_type)
137e5cced9SWillem de Bruijn {
147e5cced9SWillem de Bruijn switch (gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
157e5cced9SWillem de Bruijn case VIRTIO_NET_HDR_GSO_TCPV4:
167e5cced9SWillem de Bruijn return protocol == cpu_to_be16(ETH_P_IP);
177e5cced9SWillem de Bruijn case VIRTIO_NET_HDR_GSO_TCPV6:
187e5cced9SWillem de Bruijn return protocol == cpu_to_be16(ETH_P_IPV6);
197e5cced9SWillem de Bruijn case VIRTIO_NET_HDR_GSO_UDP:
20860b7f27SAndrew Melnychenko case VIRTIO_NET_HDR_GSO_UDP_L4:
217e5cced9SWillem de Bruijn return protocol == cpu_to_be16(ETH_P_IP) ||
227e5cced9SWillem de Bruijn protocol == cpu_to_be16(ETH_P_IPV6);
237e5cced9SWillem de Bruijn default:
247e5cced9SWillem de Bruijn return false;
257e5cced9SWillem de Bruijn }
267e5cced9SWillem de Bruijn }
277e5cced9SWillem de Bruijn
virtio_net_hdr_set_proto(struct sk_buff * skb,const struct virtio_net_hdr * hdr)289d2f67e4SJianfeng Tan static inline int virtio_net_hdr_set_proto(struct sk_buff *skb,
299d2f67e4SJianfeng Tan const struct virtio_net_hdr *hdr)
309d2f67e4SJianfeng Tan {
311ed1d592SWillem de Bruijn if (skb->protocol)
321ed1d592SWillem de Bruijn return 0;
331ed1d592SWillem de Bruijn
349d2f67e4SJianfeng Tan switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
359d2f67e4SJianfeng Tan case VIRTIO_NET_HDR_GSO_TCPV4:
369d2f67e4SJianfeng Tan case VIRTIO_NET_HDR_GSO_UDP:
37860b7f27SAndrew Melnychenko case VIRTIO_NET_HDR_GSO_UDP_L4:
389d2f67e4SJianfeng Tan skb->protocol = cpu_to_be16(ETH_P_IP);
399d2f67e4SJianfeng Tan break;
409d2f67e4SJianfeng Tan case VIRTIO_NET_HDR_GSO_TCPV6:
419d2f67e4SJianfeng Tan skb->protocol = cpu_to_be16(ETH_P_IPV6);
429d2f67e4SJianfeng Tan break;
439d2f67e4SJianfeng Tan default:
449d2f67e4SJianfeng Tan return -EINVAL;
459d2f67e4SJianfeng Tan }
469d2f67e4SJianfeng Tan
479d2f67e4SJianfeng Tan return 0;
489d2f67e4SJianfeng Tan }
499d2f67e4SJianfeng Tan
virtio_net_hdr_to_skb(struct sk_buff * skb,const struct virtio_net_hdr * hdr,bool little_endian)50fd2a0437SMike Rapoport static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
51fd2a0437SMike Rapoport const struct virtio_net_hdr *hdr,
52fd2a0437SMike Rapoport bool little_endian)
53fd2a0437SMike Rapoport {
54342c88f4SEric Dumazet unsigned int nh_min_len = sizeof(struct iphdr);
550c19f846SWillem de Bruijn unsigned int gso_type = 0;
569274124fSWillem de Bruijn unsigned int thlen = 0;
576dd912f8SWillem de Bruijn unsigned int p_off = 0;
589274124fSWillem de Bruijn unsigned int ip_proto;
59fd2a0437SMike Rapoport
60fd2a0437SMike Rapoport if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
61fd2a0437SMike Rapoport switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
62fd2a0437SMike Rapoport case VIRTIO_NET_HDR_GSO_TCPV4:
63fd2a0437SMike Rapoport gso_type = SKB_GSO_TCPV4;
649274124fSWillem de Bruijn ip_proto = IPPROTO_TCP;
659274124fSWillem de Bruijn thlen = sizeof(struct tcphdr);
66fd2a0437SMike Rapoport break;
67fd2a0437SMike Rapoport case VIRTIO_NET_HDR_GSO_TCPV6:
68fd2a0437SMike Rapoport gso_type = SKB_GSO_TCPV6;
699274124fSWillem de Bruijn ip_proto = IPPROTO_TCP;
709274124fSWillem de Bruijn thlen = sizeof(struct tcphdr);
71342c88f4SEric Dumazet nh_min_len = sizeof(struct ipv6hdr);
72fd2a0437SMike Rapoport break;
730c19f846SWillem de Bruijn case VIRTIO_NET_HDR_GSO_UDP:
740c19f846SWillem de Bruijn gso_type = SKB_GSO_UDP;
759274124fSWillem de Bruijn ip_proto = IPPROTO_UDP;
769274124fSWillem de Bruijn thlen = sizeof(struct udphdr);
770c19f846SWillem de Bruijn break;
78860b7f27SAndrew Melnychenko case VIRTIO_NET_HDR_GSO_UDP_L4:
79860b7f27SAndrew Melnychenko gso_type = SKB_GSO_UDP_L4;
80860b7f27SAndrew Melnychenko ip_proto = IPPROTO_UDP;
81860b7f27SAndrew Melnychenko thlen = sizeof(struct udphdr);
82860b7f27SAndrew Melnychenko break;
83fd2a0437SMike Rapoport default:
84fd2a0437SMike Rapoport return -EINVAL;
85fd2a0437SMike Rapoport }
86fd2a0437SMike Rapoport
87fd2a0437SMike Rapoport if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN)
88fd2a0437SMike Rapoport gso_type |= SKB_GSO_TCP_ECN;
89fd2a0437SMike Rapoport
90fd2a0437SMike Rapoport if (hdr->gso_size == 0)
91fd2a0437SMike Rapoport return -EINVAL;
92fd2a0437SMike Rapoport }
93fd2a0437SMike Rapoport
9461431a59SEric Dumazet skb_reset_mac_header(skb);
9561431a59SEric Dumazet
96fd2a0437SMike Rapoport if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
970f6925b3SEric Dumazet u32 start = __virtio16_to_cpu(little_endian, hdr->csum_start);
980f6925b3SEric Dumazet u32 off = __virtio16_to_cpu(little_endian, hdr->csum_offset);
990f6925b3SEric Dumazet u32 needed = start + max_t(u32, thlen, off + sizeof(__sum16));
1000f6925b3SEric Dumazet
1010f6925b3SEric Dumazet if (!pskb_may_pull(skb, needed))
1020f6925b3SEric Dumazet return -EINVAL;
103fd2a0437SMike Rapoport
104fd2a0437SMike Rapoport if (!skb_partial_csum_set(skb, start, off))
105fd2a0437SMike Rapoport return -EINVAL;
106*d9dfd41eSEric Dumazet if (skb_transport_offset(skb) < nh_min_len)
107*d9dfd41eSEric Dumazet return -EINVAL;
1089274124fSWillem de Bruijn
109*d9dfd41eSEric Dumazet nh_min_len = skb_transport_offset(skb);
110342c88f4SEric Dumazet p_off = nh_min_len + thlen;
1110f6925b3SEric Dumazet if (!pskb_may_pull(skb, p_off))
1129274124fSWillem de Bruijn return -EINVAL;
113d5be7f63SWillem de Bruijn } else {
114d5be7f63SWillem de Bruijn /* gso packets without NEEDS_CSUM do not set transport_offset.
115d5be7f63SWillem de Bruijn * probe and drop if does not match one of the above types.
116d5be7f63SWillem de Bruijn */
1179e8db591SWillem de Bruijn if (gso_type && skb->network_header) {
1189274124fSWillem de Bruijn struct flow_keys_basic keys;
1199274124fSWillem de Bruijn
120924a9bc3SBalazs Nemeth if (!skb->protocol) {
121924a9bc3SBalazs Nemeth __be16 protocol = dev_parse_header_protocol(skb);
122924a9bc3SBalazs Nemeth
1237e5cced9SWillem de Bruijn if (!protocol)
1249e8db591SWillem de Bruijn virtio_net_hdr_set_proto(skb, hdr);
1257e5cced9SWillem de Bruijn else if (!virtio_net_hdr_match_proto(protocol, hdr->gso_type))
126924a9bc3SBalazs Nemeth return -EINVAL;
1277e5cced9SWillem de Bruijn else
1287e5cced9SWillem de Bruijn skb->protocol = protocol;
129924a9bc3SBalazs Nemeth }
1309e8db591SWillem de Bruijn retry:
1319274124fSWillem de Bruijn if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys,
1329274124fSWillem de Bruijn NULL, 0, 0, 0,
1339274124fSWillem de Bruijn 0)) {
1349e8db591SWillem de Bruijn /* UFO does not specify ipv4 or 6: try both */
1359e8db591SWillem de Bruijn if (gso_type & SKB_GSO_UDP &&
1369e8db591SWillem de Bruijn skb->protocol == htons(ETH_P_IP)) {
1379e8db591SWillem de Bruijn skb->protocol = htons(ETH_P_IPV6);
1389e8db591SWillem de Bruijn goto retry;
1399e8db591SWillem de Bruijn }
140d5be7f63SWillem de Bruijn return -EINVAL;
141d5be7f63SWillem de Bruijn }
1429274124fSWillem de Bruijn
1436dd912f8SWillem de Bruijn p_off = keys.control.thoff + thlen;
1440f6925b3SEric Dumazet if (!pskb_may_pull(skb, p_off) ||
1459274124fSWillem de Bruijn keys.basic.ip_proto != ip_proto)
1469274124fSWillem de Bruijn return -EINVAL;
1479274124fSWillem de Bruijn
1489274124fSWillem de Bruijn skb_set_transport_header(skb, keys.control.thoff);
1496dd912f8SWillem de Bruijn } else if (gso_type) {
150342c88f4SEric Dumazet p_off = nh_min_len + thlen;
1510f6925b3SEric Dumazet if (!pskb_may_pull(skb, p_off))
1526dd912f8SWillem de Bruijn return -EINVAL;
153fd2a0437SMike Rapoport }
1549e8db591SWillem de Bruijn }
155fd2a0437SMike Rapoport
156fd2a0437SMike Rapoport if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
157fd2a0437SMike Rapoport u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size);
158cf9acc90SJonathan Davies unsigned int nh_off = p_off;
1597c6d2ecbSEric Dumazet struct skb_shared_info *shinfo = skb_shinfo(skb);
160fd2a0437SMike Rapoport
161fc8b2a61SWillem de Bruijn switch (gso_type & ~SKB_GSO_TCP_ECN) {
162fc8b2a61SWillem de Bruijn case SKB_GSO_UDP:
163cf9acc90SJonathan Davies /* UFO may not include transport header in gso_size. */
164cf9acc90SJonathan Davies nh_off -= thlen;
165fc8b2a61SWillem de Bruijn break;
166fc8b2a61SWillem de Bruijn case SKB_GSO_UDP_L4:
167fc8b2a61SWillem de Bruijn if (!(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM))
168fc8b2a61SWillem de Bruijn return -EINVAL;
169fc8b2a61SWillem de Bruijn if (skb->csum_offset != offsetof(struct udphdr, check))
170fc8b2a61SWillem de Bruijn return -EINVAL;
171fc8b2a61SWillem de Bruijn if (skb->len - p_off > gso_size * UDP_MAX_SEGMENTS)
172fc8b2a61SWillem de Bruijn return -EINVAL;
173fc8b2a61SWillem de Bruijn if (gso_type != SKB_GSO_UDP_L4)
174fc8b2a61SWillem de Bruijn return -EINVAL;
175fc8b2a61SWillem de Bruijn break;
1766772c486SWillem de Bruijn case SKB_GSO_TCPV4:
1776772c486SWillem de Bruijn case SKB_GSO_TCPV6:
1784ec0d8dbSWillem de Bruijn if (skb->ip_summed == CHECKSUM_PARTIAL &&
1794ec0d8dbSWillem de Bruijn skb->csum_offset != offsetof(struct tcphdr, check))
1806772c486SWillem de Bruijn return -EINVAL;
1816772c486SWillem de Bruijn break;
182fc8b2a61SWillem de Bruijn }
183cf9acc90SJonathan Davies
184b616be6bSEric Dumazet /* Kernel has a special handling for GSO_BY_FRAGS. */
185b616be6bSEric Dumazet if (gso_size == GSO_BY_FRAGS)
186b616be6bSEric Dumazet return -EINVAL;
187b616be6bSEric Dumazet
1887c6d2ecbSEric Dumazet /* Too small packets are not really GSO ones. */
189cf9acc90SJonathan Davies if (skb->len - nh_off > gso_size) {
1907c6d2ecbSEric Dumazet shinfo->gso_size = gso_size;
1917c6d2ecbSEric Dumazet shinfo->gso_type = gso_type;
192fd2a0437SMike Rapoport
193fd2a0437SMike Rapoport /* Header must be checked, and gso_segs computed. */
1947c6d2ecbSEric Dumazet shinfo->gso_type |= SKB_GSO_DODGY;
1957c6d2ecbSEric Dumazet shinfo->gso_segs = 0;
1967c6d2ecbSEric Dumazet }
197fd2a0437SMike Rapoport }
198fd2a0437SMike Rapoport
199fd2a0437SMike Rapoport return 0;
200fd2a0437SMike Rapoport }
201fd2a0437SMike Rapoport
virtio_net_hdr_from_skb(const struct sk_buff * skb,struct virtio_net_hdr * hdr,bool little_endian,bool has_data_valid,int vlan_hlen)202fd2a0437SMike Rapoport static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb,
203fd2a0437SMike Rapoport struct virtio_net_hdr *hdr,
2046391a448SJason Wang bool little_endian,
205fd3a8862SWillem de Bruijn bool has_data_valid,
206fd3a8862SWillem de Bruijn int vlan_hlen)
207fd2a0437SMike Rapoport {
2089403cd7cSJarno Rajahalme memset(hdr, 0, sizeof(*hdr)); /* no info leak */
209fd2a0437SMike Rapoport
210fd2a0437SMike Rapoport if (skb_is_gso(skb)) {
211fd2a0437SMike Rapoport struct skb_shared_info *sinfo = skb_shinfo(skb);
212fd2a0437SMike Rapoport
213fd2a0437SMike Rapoport /* This is a hint as to how much should be linear. */
214fd2a0437SMike Rapoport hdr->hdr_len = __cpu_to_virtio16(little_endian,
215fd2a0437SMike Rapoport skb_headlen(skb));
216fd2a0437SMike Rapoport hdr->gso_size = __cpu_to_virtio16(little_endian,
217fd2a0437SMike Rapoport sinfo->gso_size);
218fd2a0437SMike Rapoport if (sinfo->gso_type & SKB_GSO_TCPV4)
219fd2a0437SMike Rapoport hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
220fd2a0437SMike Rapoport else if (sinfo->gso_type & SKB_GSO_TCPV6)
221fd2a0437SMike Rapoport hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
222860b7f27SAndrew Melnychenko else if (sinfo->gso_type & SKB_GSO_UDP_L4)
223860b7f27SAndrew Melnychenko hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4;
224fd2a0437SMike Rapoport else
225fd2a0437SMike Rapoport return -EINVAL;
226fd2a0437SMike Rapoport if (sinfo->gso_type & SKB_GSO_TCP_ECN)
227fd2a0437SMike Rapoport hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
228fd2a0437SMike Rapoport } else
229fd2a0437SMike Rapoport hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
230fd2a0437SMike Rapoport
231fd2a0437SMike Rapoport if (skb->ip_summed == CHECKSUM_PARTIAL) {
232fd2a0437SMike Rapoport hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
233fd2a0437SMike Rapoport hdr->csum_start = __cpu_to_virtio16(little_endian,
234fd3a8862SWillem de Bruijn skb_checksum_start_offset(skb) + vlan_hlen);
235fd2a0437SMike Rapoport hdr->csum_offset = __cpu_to_virtio16(little_endian,
236fd2a0437SMike Rapoport skb->csum_offset);
2376391a448SJason Wang } else if (has_data_valid &&
2386391a448SJason Wang skb->ip_summed == CHECKSUM_UNNECESSARY) {
2396391a448SJason Wang hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID;
240fd2a0437SMike Rapoport } /* else everything is zero */
241fd2a0437SMike Rapoport
242fd2a0437SMike Rapoport return 0;
243fd2a0437SMike Rapoport }
244fd2a0437SMike Rapoport
245d66016a7SJarno Rajahalme #endif /* _LINUX_VIRTIO_NET_H */
246