xref: /openbmc/linux/include/linux/virtio_net.h (revision 342c88f4)
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>
6*342c88f4SEric Dumazet #include <linux/ip.h>
7*342c88f4SEric 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 {
54*342c88f4SEric 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);
71*342c88f4SEric 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;
1069274124fSWillem de Bruijn 
107*342c88f4SEric Dumazet 		nh_min_len = max_t(u32, nh_min_len, skb_transport_offset(skb));
108*342c88f4SEric Dumazet 		p_off = nh_min_len + thlen;
1090f6925b3SEric Dumazet 		if (!pskb_may_pull(skb, p_off))
1109274124fSWillem de Bruijn 			return -EINVAL;
111d5be7f63SWillem de Bruijn 	} else {
112d5be7f63SWillem de Bruijn 		/* gso packets without NEEDS_CSUM do not set transport_offset.
113d5be7f63SWillem de Bruijn 		 * probe and drop if does not match one of the above types.
114d5be7f63SWillem de Bruijn 		 */
1159e8db591SWillem de Bruijn 		if (gso_type && skb->network_header) {
1169274124fSWillem de Bruijn 			struct flow_keys_basic keys;
1179274124fSWillem de Bruijn 
118924a9bc3SBalazs Nemeth 			if (!skb->protocol) {
119924a9bc3SBalazs Nemeth 				__be16 protocol = dev_parse_header_protocol(skb);
120924a9bc3SBalazs Nemeth 
1217e5cced9SWillem de Bruijn 				if (!protocol)
1229e8db591SWillem de Bruijn 					virtio_net_hdr_set_proto(skb, hdr);
1237e5cced9SWillem de Bruijn 				else if (!virtio_net_hdr_match_proto(protocol, hdr->gso_type))
124924a9bc3SBalazs Nemeth 					return -EINVAL;
1257e5cced9SWillem de Bruijn 				else
1267e5cced9SWillem de Bruijn 					skb->protocol = protocol;
127924a9bc3SBalazs Nemeth 			}
1289e8db591SWillem de Bruijn retry:
1299274124fSWillem de Bruijn 			if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys,
1309274124fSWillem de Bruijn 							      NULL, 0, 0, 0,
1319274124fSWillem de Bruijn 							      0)) {
1329e8db591SWillem de Bruijn 				/* UFO does not specify ipv4 or 6: try both */
1339e8db591SWillem de Bruijn 				if (gso_type & SKB_GSO_UDP &&
1349e8db591SWillem de Bruijn 				    skb->protocol == htons(ETH_P_IP)) {
1359e8db591SWillem de Bruijn 					skb->protocol = htons(ETH_P_IPV6);
1369e8db591SWillem de Bruijn 					goto retry;
1379e8db591SWillem de Bruijn 				}
138d5be7f63SWillem de Bruijn 				return -EINVAL;
139d5be7f63SWillem de Bruijn 			}
1409274124fSWillem de Bruijn 
1416dd912f8SWillem de Bruijn 			p_off = keys.control.thoff + thlen;
1420f6925b3SEric Dumazet 			if (!pskb_may_pull(skb, p_off) ||
1439274124fSWillem de Bruijn 			    keys.basic.ip_proto != ip_proto)
1449274124fSWillem de Bruijn 				return -EINVAL;
1459274124fSWillem de Bruijn 
1469274124fSWillem de Bruijn 			skb_set_transport_header(skb, keys.control.thoff);
1476dd912f8SWillem de Bruijn 		} else if (gso_type) {
148*342c88f4SEric Dumazet 			p_off = nh_min_len + thlen;
1490f6925b3SEric Dumazet 			if (!pskb_may_pull(skb, p_off))
1506dd912f8SWillem de Bruijn 				return -EINVAL;
151fd2a0437SMike Rapoport 		}
1529e8db591SWillem de Bruijn 	}
153fd2a0437SMike Rapoport 
154fd2a0437SMike Rapoport 	if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
155fd2a0437SMike Rapoport 		u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size);
156cf9acc90SJonathan Davies 		unsigned int nh_off = p_off;
1577c6d2ecbSEric Dumazet 		struct skb_shared_info *shinfo = skb_shinfo(skb);
158fd2a0437SMike Rapoport 
159fc8b2a61SWillem de Bruijn 		switch (gso_type & ~SKB_GSO_TCP_ECN) {
160fc8b2a61SWillem de Bruijn 		case SKB_GSO_UDP:
161cf9acc90SJonathan Davies 			/* UFO may not include transport header in gso_size. */
162cf9acc90SJonathan Davies 			nh_off -= thlen;
163fc8b2a61SWillem de Bruijn 			break;
164fc8b2a61SWillem de Bruijn 		case SKB_GSO_UDP_L4:
165fc8b2a61SWillem de Bruijn 			if (!(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM))
166fc8b2a61SWillem de Bruijn 				return -EINVAL;
167fc8b2a61SWillem de Bruijn 			if (skb->csum_offset != offsetof(struct udphdr, check))
168fc8b2a61SWillem de Bruijn 				return -EINVAL;
169fc8b2a61SWillem de Bruijn 			if (skb->len - p_off > gso_size * UDP_MAX_SEGMENTS)
170fc8b2a61SWillem de Bruijn 				return -EINVAL;
171fc8b2a61SWillem de Bruijn 			if (gso_type != SKB_GSO_UDP_L4)
172fc8b2a61SWillem de Bruijn 				return -EINVAL;
173fc8b2a61SWillem de Bruijn 			break;
174fc8b2a61SWillem de Bruijn 		}
175cf9acc90SJonathan Davies 
176b616be6bSEric Dumazet 		/* Kernel has a special handling for GSO_BY_FRAGS. */
177b616be6bSEric Dumazet 		if (gso_size == GSO_BY_FRAGS)
178b616be6bSEric Dumazet 			return -EINVAL;
179b616be6bSEric Dumazet 
1807c6d2ecbSEric Dumazet 		/* Too small packets are not really GSO ones. */
181cf9acc90SJonathan Davies 		if (skb->len - nh_off > gso_size) {
1827c6d2ecbSEric Dumazet 			shinfo->gso_size = gso_size;
1837c6d2ecbSEric Dumazet 			shinfo->gso_type = gso_type;
184fd2a0437SMike Rapoport 
185fd2a0437SMike Rapoport 			/* Header must be checked, and gso_segs computed. */
1867c6d2ecbSEric Dumazet 			shinfo->gso_type |= SKB_GSO_DODGY;
1877c6d2ecbSEric Dumazet 			shinfo->gso_segs = 0;
1887c6d2ecbSEric Dumazet 		}
189fd2a0437SMike Rapoport 	}
190fd2a0437SMike Rapoport 
191fd2a0437SMike Rapoport 	return 0;
192fd2a0437SMike Rapoport }
193fd2a0437SMike 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)194fd2a0437SMike Rapoport static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb,
195fd2a0437SMike Rapoport 					  struct virtio_net_hdr *hdr,
1966391a448SJason Wang 					  bool little_endian,
197fd3a8862SWillem de Bruijn 					  bool has_data_valid,
198fd3a8862SWillem de Bruijn 					  int vlan_hlen)
199fd2a0437SMike Rapoport {
2009403cd7cSJarno Rajahalme 	memset(hdr, 0, sizeof(*hdr));   /* no info leak */
201fd2a0437SMike Rapoport 
202fd2a0437SMike Rapoport 	if (skb_is_gso(skb)) {
203fd2a0437SMike Rapoport 		struct skb_shared_info *sinfo = skb_shinfo(skb);
204fd2a0437SMike Rapoport 
205fd2a0437SMike Rapoport 		/* This is a hint as to how much should be linear. */
206fd2a0437SMike Rapoport 		hdr->hdr_len = __cpu_to_virtio16(little_endian,
207fd2a0437SMike Rapoport 						 skb_headlen(skb));
208fd2a0437SMike Rapoport 		hdr->gso_size = __cpu_to_virtio16(little_endian,
209fd2a0437SMike Rapoport 						  sinfo->gso_size);
210fd2a0437SMike Rapoport 		if (sinfo->gso_type & SKB_GSO_TCPV4)
211fd2a0437SMike Rapoport 			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
212fd2a0437SMike Rapoport 		else if (sinfo->gso_type & SKB_GSO_TCPV6)
213fd2a0437SMike Rapoport 			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
214860b7f27SAndrew Melnychenko 		else if (sinfo->gso_type & SKB_GSO_UDP_L4)
215860b7f27SAndrew Melnychenko 			hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4;
216fd2a0437SMike Rapoport 		else
217fd2a0437SMike Rapoport 			return -EINVAL;
218fd2a0437SMike Rapoport 		if (sinfo->gso_type & SKB_GSO_TCP_ECN)
219fd2a0437SMike Rapoport 			hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
220fd2a0437SMike Rapoport 	} else
221fd2a0437SMike Rapoport 		hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
222fd2a0437SMike Rapoport 
223fd2a0437SMike Rapoport 	if (skb->ip_summed == CHECKSUM_PARTIAL) {
224fd2a0437SMike Rapoport 		hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
225fd2a0437SMike Rapoport 		hdr->csum_start = __cpu_to_virtio16(little_endian,
226fd3a8862SWillem de Bruijn 			skb_checksum_start_offset(skb) + vlan_hlen);
227fd2a0437SMike Rapoport 		hdr->csum_offset = __cpu_to_virtio16(little_endian,
228fd2a0437SMike Rapoport 				skb->csum_offset);
2296391a448SJason Wang 	} else if (has_data_valid &&
2306391a448SJason Wang 		   skb->ip_summed == CHECKSUM_UNNECESSARY) {
2316391a448SJason Wang 		hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID;
232fd2a0437SMike Rapoport 	} /* else everything is zero */
233fd2a0437SMike Rapoport 
234fd2a0437SMike Rapoport 	return 0;
235fd2a0437SMike Rapoport }
236fd2a0437SMike Rapoport 
237d66016a7SJarno Rajahalme #endif /* _LINUX_VIRTIO_NET_H */
238