xref: /linux-6.15/include/linux/virtio_net.h (revision 82d00a93)
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 int virtio_net_hdr_set_proto(struct sk_buff *skb,
11 					   const struct virtio_net_hdr *hdr)
12 {
13 	switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
14 	case VIRTIO_NET_HDR_GSO_TCPV4:
15 	case VIRTIO_NET_HDR_GSO_UDP:
16 		skb->protocol = cpu_to_be16(ETH_P_IP);
17 		break;
18 	case VIRTIO_NET_HDR_GSO_TCPV6:
19 		skb->protocol = cpu_to_be16(ETH_P_IPV6);
20 		break;
21 	default:
22 		return -EINVAL;
23 	}
24 
25 	return 0;
26 }
27 
28 static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
29 					const struct virtio_net_hdr *hdr,
30 					bool little_endian)
31 {
32 	unsigned int gso_type = 0;
33 	unsigned int thlen = 0;
34 	unsigned int ip_proto;
35 
36 	if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
37 		switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
38 		case VIRTIO_NET_HDR_GSO_TCPV4:
39 			gso_type = SKB_GSO_TCPV4;
40 			ip_proto = IPPROTO_TCP;
41 			thlen = sizeof(struct tcphdr);
42 			break;
43 		case VIRTIO_NET_HDR_GSO_TCPV6:
44 			gso_type = SKB_GSO_TCPV6;
45 			ip_proto = IPPROTO_TCP;
46 			thlen = sizeof(struct tcphdr);
47 			break;
48 		case VIRTIO_NET_HDR_GSO_UDP:
49 			gso_type = SKB_GSO_UDP;
50 			ip_proto = IPPROTO_UDP;
51 			thlen = sizeof(struct udphdr);
52 			break;
53 		default:
54 			return -EINVAL;
55 		}
56 
57 		if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN)
58 			gso_type |= SKB_GSO_TCP_ECN;
59 
60 		if (hdr->gso_size == 0)
61 			return -EINVAL;
62 	}
63 
64 	if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
65 		u16 start = __virtio16_to_cpu(little_endian, hdr->csum_start);
66 		u16 off = __virtio16_to_cpu(little_endian, hdr->csum_offset);
67 
68 		if (!skb_partial_csum_set(skb, start, off))
69 			return -EINVAL;
70 
71 		if (skb_transport_offset(skb) + thlen > skb_headlen(skb))
72 			return -EINVAL;
73 	} else {
74 		/* gso packets without NEEDS_CSUM do not set transport_offset.
75 		 * probe and drop if does not match one of the above types.
76 		 */
77 		if (gso_type && skb->network_header) {
78 			struct flow_keys_basic keys;
79 
80 			if (!skb->protocol)
81 				virtio_net_hdr_set_proto(skb, hdr);
82 retry:
83 			if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys,
84 							      NULL, 0, 0, 0,
85 							      0)) {
86 				/* UFO does not specify ipv4 or 6: try both */
87 				if (gso_type & SKB_GSO_UDP &&
88 				    skb->protocol == htons(ETH_P_IP)) {
89 					skb->protocol = htons(ETH_P_IPV6);
90 					goto retry;
91 				}
92 				return -EINVAL;
93 			}
94 
95 			if (keys.control.thoff + thlen > skb_headlen(skb) ||
96 			    keys.basic.ip_proto != ip_proto)
97 				return -EINVAL;
98 
99 			skb_set_transport_header(skb, keys.control.thoff);
100 		}
101 	}
102 
103 	if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
104 		u16 gso_size = __virtio16_to_cpu(little_endian, hdr->gso_size);
105 
106 		skb_shinfo(skb)->gso_size = gso_size;
107 		skb_shinfo(skb)->gso_type = gso_type;
108 
109 		/* Header must be checked, and gso_segs computed. */
110 		skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;
111 		skb_shinfo(skb)->gso_segs = 0;
112 	}
113 
114 	return 0;
115 }
116 
117 static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb,
118 					  struct virtio_net_hdr *hdr,
119 					  bool little_endian,
120 					  bool has_data_valid,
121 					  int vlan_hlen)
122 {
123 	memset(hdr, 0, sizeof(*hdr));   /* no info leak */
124 
125 	if (skb_is_gso(skb)) {
126 		struct skb_shared_info *sinfo = skb_shinfo(skb);
127 
128 		/* This is a hint as to how much should be linear. */
129 		hdr->hdr_len = __cpu_to_virtio16(little_endian,
130 						 skb_headlen(skb));
131 		hdr->gso_size = __cpu_to_virtio16(little_endian,
132 						  sinfo->gso_size);
133 		if (sinfo->gso_type & SKB_GSO_TCPV4)
134 			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
135 		else if (sinfo->gso_type & SKB_GSO_TCPV6)
136 			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
137 		else
138 			return -EINVAL;
139 		if (sinfo->gso_type & SKB_GSO_TCP_ECN)
140 			hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
141 	} else
142 		hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
143 
144 	if (skb->ip_summed == CHECKSUM_PARTIAL) {
145 		hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
146 		hdr->csum_start = __cpu_to_virtio16(little_endian,
147 			skb_checksum_start_offset(skb) + vlan_hlen);
148 		hdr->csum_offset = __cpu_to_virtio16(little_endian,
149 				skb->csum_offset);
150 	} else if (has_data_valid &&
151 		   skb->ip_summed == CHECKSUM_UNNECESSARY) {
152 		hdr->flags = VIRTIO_NET_HDR_F_DATA_VALID;
153 	} /* else everything is zero */
154 
155 	return 0;
156 }
157 
158 #endif /* _LINUX_VIRTIO_NET_H */
159