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