]>
Commit | Line | Data |
---|---|---|
85571a3d | 1 | /* |
7b383a56 | 2 | * Copyright (c) 2015, 2016 Cloudbase Solutions Srl |
85571a3d AS |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include "precomp.h" | |
18 | ||
19 | #include "Atomic.h" | |
7b383a56 | 20 | #include "Debug.h" |
85571a3d AS |
21 | #include "Flow.h" |
22 | #include "Gre.h" | |
23 | #include "IpHelper.h" | |
24 | #include "NetProto.h" | |
7b383a56 | 25 | #include "Offload.h" |
85571a3d AS |
26 | #include "PacketIO.h" |
27 | #include "PacketParser.h" | |
28 | #include "Switch.h" | |
29 | #include "User.h" | |
30 | #include "Util.h" | |
31 | #include "Vport.h" | |
32 | ||
33 | #ifdef OVS_DBG_MOD | |
34 | #undef OVS_DBG_MOD | |
35 | #endif | |
36 | #define OVS_DBG_MOD OVS_DBG_GRE | |
85571a3d AS |
37 | |
38 | static NDIS_STATUS | |
39 | OvsDoEncapGre(POVS_VPORT_ENTRY vport, PNET_BUFFER_LIST curNbl, | |
40 | const OvsIPv4TunnelKey *tunKey, | |
41 | const POVS_FWD_INFO fwdInfo, | |
42 | POVS_PACKET_HDR_INFO layers, | |
43 | POVS_SWITCH_CONTEXT switchContext, | |
44 | PNET_BUFFER_LIST *newNbl); | |
45 | ||
46 | /* | |
47 | * -------------------------------------------------------------------------- | |
48 | * OvsInitGreTunnel -- | |
49 | * Initialize GRE tunnel module. | |
50 | * -------------------------------------------------------------------------- | |
51 | */ | |
52 | NTSTATUS | |
53 | OvsInitGreTunnel(POVS_VPORT_ENTRY vport) | |
54 | { | |
55 | POVS_GRE_VPORT grePort; | |
56 | ||
57 | grePort = (POVS_GRE_VPORT)OvsAllocateMemoryWithTag(sizeof(*grePort), | |
58 | OVS_GRE_POOL_TAG); | |
59 | if (!grePort) { | |
60 | OVS_LOG_ERROR("Insufficient memory, can't allocate OVS_GRE_VPORT"); | |
61 | return STATUS_INSUFFICIENT_RESOURCES; | |
62 | } | |
63 | ||
64 | RtlZeroMemory(grePort, sizeof(*grePort)); | |
65 | vport->priv = (PVOID)grePort; | |
66 | return STATUS_SUCCESS; | |
67 | } | |
68 | ||
69 | /* | |
70 | * -------------------------------------------------------------------------- | |
71 | * OvsCleanupGreTunnel -- | |
72 | * Cleanup GRE Tunnel module. | |
73 | * -------------------------------------------------------------------------- | |
74 | */ | |
75 | void | |
76 | OvsCleanupGreTunnel(POVS_VPORT_ENTRY vport) | |
77 | { | |
78 | if (vport->ovsType != OVS_VPORT_TYPE_GRE || | |
79 | vport->priv == NULL) { | |
80 | return; | |
81 | } | |
82 | ||
83 | OvsFreeMemoryWithTag(vport->priv, OVS_GRE_POOL_TAG); | |
84 | vport->priv = NULL; | |
85 | } | |
86 | ||
87 | /* | |
88 | * -------------------------------------------------------------------------- | |
89 | * OvsEncapGre -- | |
90 | * Encapsulates a packet with an GRE header. | |
91 | * -------------------------------------------------------------------------- | |
92 | */ | |
93 | NDIS_STATUS | |
94 | OvsEncapGre(POVS_VPORT_ENTRY vport, | |
95 | PNET_BUFFER_LIST curNbl, | |
96 | OvsIPv4TunnelKey *tunKey, | |
97 | POVS_SWITCH_CONTEXT switchContext, | |
98 | POVS_PACKET_HDR_INFO layers, | |
99 | PNET_BUFFER_LIST *newNbl) | |
100 | { | |
101 | OVS_FWD_INFO fwdInfo; | |
102 | NDIS_STATUS status; | |
103 | ||
104 | status = OvsLookupIPFwdInfo(tunKey->dst, &fwdInfo); | |
105 | if (status != STATUS_SUCCESS) { | |
106 | OvsFwdIPHelperRequest(NULL, 0, tunKey, NULL, NULL, NULL); | |
107 | return NDIS_STATUS_FAILURE; | |
108 | } | |
109 | ||
110 | status = OvsDoEncapGre(vport, curNbl, tunKey, &fwdInfo, layers, | |
111 | switchContext, newNbl); | |
112 | return status; | |
113 | } | |
114 | ||
115 | /* | |
116 | * -------------------------------------------------------------------------- | |
117 | * OvsDoEncapGre -- | |
118 | * Internal utility function which actually does the GRE encap. | |
119 | * -------------------------------------------------------------------------- | |
120 | */ | |
121 | NDIS_STATUS | |
122 | OvsDoEncapGre(POVS_VPORT_ENTRY vport, | |
123 | PNET_BUFFER_LIST curNbl, | |
124 | const OvsIPv4TunnelKey *tunKey, | |
125 | const POVS_FWD_INFO fwdInfo, | |
126 | POVS_PACKET_HDR_INFO layers, | |
127 | POVS_SWITCH_CONTEXT switchContext, | |
128 | PNET_BUFFER_LIST *newNbl) | |
129 | { | |
130 | NDIS_STATUS status; | |
131 | PNET_BUFFER curNb; | |
132 | PMDL curMdl; | |
133 | PUINT8 bufferStart; | |
134 | EthHdr *ethHdr; | |
135 | IPHdr *ipHdr; | |
136 | PGREHdr greHdr; | |
137 | POVS_GRE_VPORT vportGre; | |
9a5b6ddf AS |
138 | PCHAR pChk = NULL; |
139 | UINT32 headRoom = GreTunHdrSize(OvsTunnelFlagsToGreFlags(tunKey->flags)); | |
85571a3d AS |
140 | #if DBG |
141 | UINT32 counterHeadRoom; | |
142 | #endif | |
143 | UINT32 packetLength; | |
144 | ULONG mss = 0; | |
145 | ASSERT(*newNbl == NULL); | |
146 | ||
147 | curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); | |
148 | packetLength = NET_BUFFER_DATA_LENGTH(curNb); | |
149 | ||
150 | if (layers->isTcp) { | |
7b383a56 | 151 | mss = OVSGetTcpMSS(curNbl); |
85571a3d | 152 | |
85571a3d AS |
153 | OVS_LOG_TRACE("MSS %u packet len %u", mss, |
154 | packetLength); | |
155 | if (mss) { | |
156 | OVS_LOG_TRACE("l4Offset %d", layers->l4Offset); | |
157 | *newNbl = OvsTcpSegmentNBL(switchContext, curNbl, layers, | |
158 | mss, headRoom); | |
159 | if (*newNbl == NULL) { | |
160 | OVS_LOG_ERROR("Unable to segment NBL"); | |
161 | return NDIS_STATUS_FAILURE; | |
162 | } | |
163 | /* Clear out LSO flags after this point */ | |
164 | NET_BUFFER_LIST_INFO(*newNbl, TcpLargeSendNetBufferListInfo) = 0; | |
165 | } | |
166 | } | |
167 | ||
168 | vportGre = (POVS_GRE_VPORT)GetOvsVportPriv(vport); | |
169 | ASSERT(vportGre); | |
170 | ||
171 | /* If we didn't split the packet above, make a copy now */ | |
172 | if (*newNbl == NULL) { | |
173 | *newNbl = OvsPartialCopyNBL(switchContext, curNbl, 0, headRoom, | |
174 | FALSE /*NBL info*/); | |
175 | if (*newNbl == NULL) { | |
176 | OVS_LOG_ERROR("Unable to copy NBL"); | |
177 | return NDIS_STATUS_FAILURE; | |
178 | } | |
85571a3d AS |
179 | |
180 | NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo; | |
181 | csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl, | |
182 | TcpIpChecksumNetBufferListInfo); | |
183 | ||
7b383a56 AS |
184 | status = OvsApplySWChecksumOnNB(layers, *newNbl, &csumInfo); |
185 | if (status != NDIS_STATUS_SUCCESS) { | |
186 | goto ret_error; | |
85571a3d | 187 | } |
85571a3d AS |
188 | } |
189 | ||
190 | curNbl = *newNbl; | |
191 | for (curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); curNb != NULL; | |
192 | curNb = curNb->Next) { | |
193 | #if DBG | |
194 | counterHeadRoom = headRoom; | |
195 | #endif | |
196 | status = NdisRetreatNetBufferDataStart(curNb, headRoom, 0, NULL); | |
197 | if (status != NDIS_STATUS_SUCCESS) { | |
198 | goto ret_error; | |
199 | } | |
200 | ||
201 | curMdl = NET_BUFFER_CURRENT_MDL(curNb); | |
202 | bufferStart = (PUINT8)MmGetSystemAddressForMdlSafe(curMdl, | |
203 | LowPagePriority); | |
204 | if (!bufferStart) { | |
205 | status = NDIS_STATUS_RESOURCES; | |
206 | goto ret_error; | |
207 | } | |
208 | ||
209 | bufferStart += NET_BUFFER_CURRENT_MDL_OFFSET(curNb); | |
210 | if (NET_BUFFER_NEXT_NB(curNb)) { | |
211 | OVS_LOG_TRACE("nb length %u next %u", | |
212 | NET_BUFFER_DATA_LENGTH(curNb), | |
213 | NET_BUFFER_DATA_LENGTH(curNb->Next)); | |
214 | } | |
215 | ||
216 | /* L2 header */ | |
217 | ethHdr = (EthHdr *)bufferStart; | |
218 | ASSERT(((PCHAR)&fwdInfo->dstMacAddr + sizeof fwdInfo->dstMacAddr) == | |
219 | (PCHAR)&fwdInfo->srcMacAddr); | |
220 | NdisMoveMemory(ethHdr->Destination, fwdInfo->dstMacAddr, | |
221 | sizeof ethHdr->Destination + sizeof ethHdr->Source); | |
222 | ethHdr->Type = htons(ETH_TYPE_IPV4); | |
223 | #if DBG | |
224 | counterHeadRoom -= sizeof *ethHdr; | |
225 | #endif | |
226 | ||
227 | /* IP header */ | |
228 | ipHdr = (IPHdr *)((PCHAR)ethHdr + sizeof *ethHdr); | |
229 | ||
230 | ipHdr->ihl = sizeof *ipHdr / 4; | |
231 | ipHdr->version = IPPROTO_IPV4; | |
232 | ipHdr->tos = tunKey->tos; | |
233 | ipHdr->tot_len = htons(NET_BUFFER_DATA_LENGTH(curNb) - sizeof *ethHdr); | |
234 | ipHdr->id = (uint16)atomic_add64(&vportGre->ipId, | |
235 | NET_BUFFER_DATA_LENGTH(curNb)); | |
236 | ipHdr->frag_off = (tunKey->flags & OVS_TNL_F_DONT_FRAGMENT) ? | |
237 | IP_DF_NBO : 0; | |
238 | ipHdr->ttl = tunKey->ttl ? tunKey->ttl : 64; | |
239 | ipHdr->protocol = IPPROTO_GRE; | |
240 | ASSERT(tunKey->dst == fwdInfo->dstIpAddr); | |
241 | ASSERT(tunKey->src == fwdInfo->srcIpAddr || tunKey->src == 0); | |
242 | ipHdr->saddr = fwdInfo->srcIpAddr; | |
243 | ipHdr->daddr = fwdInfo->dstIpAddr; | |
244 | ||
245 | ipHdr->check = 0; | |
246 | ipHdr->check = IPChecksum((UINT8 *)ipHdr, sizeof *ipHdr, 0); | |
247 | #if DBG | |
248 | counterHeadRoom -= sizeof *ipHdr; | |
249 | #endif | |
250 | ||
251 | /* GRE header */ | |
252 | greHdr = (GREHdr *)((PCHAR)ipHdr + sizeof *ipHdr); | |
253 | greHdr->flags = OvsTunnelFlagsToGreFlags(tunKey->flags); | |
254 | greHdr->protocolType = GRE_NET_TEB; | |
255 | #if DBG | |
256 | counterHeadRoom -= sizeof *greHdr; | |
257 | #endif | |
258 | ||
259 | PCHAR currentOffset = (PCHAR)greHdr + sizeof *greHdr; | |
260 | ||
261 | if (tunKey->flags & OVS_TNL_F_CSUM) { | |
262 | RtlZeroMemory(currentOffset, 4); | |
9a5b6ddf | 263 | pChk = currentOffset; |
85571a3d AS |
264 | currentOffset += 4; |
265 | #if DBG | |
266 | counterHeadRoom -= 4; | |
267 | #endif | |
268 | } | |
269 | ||
270 | if (tunKey->flags & OVS_TNL_F_KEY) { | |
271 | RtlZeroMemory(currentOffset, 4); | |
272 | UINT32 key = (tunKey->tunnelId >> 32); | |
273 | RtlCopyMemory(currentOffset, &key, sizeof key); | |
274 | currentOffset += 4; | |
275 | #if DBG | |
276 | counterHeadRoom -= 4; | |
277 | #endif | |
278 | } | |
279 | ||
9a5b6ddf AS |
280 | /* Checksum needs to be done after the GRE header has been set */ |
281 | if (tunKey->flags & OVS_TNL_F_CSUM) { | |
282 | ASSERT(pChk); | |
283 | UINT16 chksum = | |
284 | CalculateChecksumNB(curNb, | |
285 | (UINT16)(NET_BUFFER_DATA_LENGTH(curNb) - | |
286 | sizeof *ipHdr - sizeof *ethHdr), | |
287 | sizeof *ipHdr + sizeof *ethHdr); | |
288 | RtlCopyMemory(pChk, &chksum, 2); | |
289 | } | |
290 | ||
85571a3d AS |
291 | #if DBG |
292 | ASSERT(counterHeadRoom == 0); | |
293 | #endif | |
294 | ||
295 | } | |
296 | return STATUS_SUCCESS; | |
297 | ||
298 | ret_error: | |
299 | OvsCompleteNBL(switchContext, *newNbl, TRUE); | |
300 | *newNbl = NULL; | |
301 | return status; | |
302 | } | |
303 | ||
304 | NDIS_STATUS | |
305 | OvsDecapGre(POVS_SWITCH_CONTEXT switchContext, | |
306 | PNET_BUFFER_LIST curNbl, | |
307 | OvsIPv4TunnelKey *tunKey, | |
308 | PNET_BUFFER_LIST *newNbl) | |
309 | { | |
310 | PNET_BUFFER curNb; | |
311 | PMDL curMdl; | |
312 | EthHdr *ethHdr; | |
313 | IPHdr *ipHdr; | |
314 | GREHdr *greHdr; | |
9a5b6ddf | 315 | UINT32 tunnelSize, packetLength; |
85571a3d AS |
316 | UINT32 headRoom = 0; |
317 | PUINT8 bufferStart; | |
9a5b6ddf AS |
318 | NDIS_STATUS status = NDIS_STATUS_SUCCESS; |
319 | PCHAR tempBuf = NULL; | |
320 | ||
321 | ASSERT(*newNbl == NULL); | |
322 | ||
323 | *newNbl = NULL; | |
85571a3d AS |
324 | |
325 | curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); | |
326 | packetLength = NET_BUFFER_DATA_LENGTH(curNb); | |
9a5b6ddf AS |
327 | curMdl = NET_BUFFER_CURRENT_MDL(curNb); |
328 | tunnelSize = GreTunHdrSize(0); | |
85571a3d AS |
329 | if (packetLength <= tunnelSize) { |
330 | return NDIS_STATUS_INVALID_LENGTH; | |
331 | } | |
332 | ||
9a5b6ddf AS |
333 | /* Get a contiguous buffer for the maximum length of a GRE header */ |
334 | bufferStart = NdisGetDataBuffer(curNb, OVS_MAX_GRE_LGTH, NULL, 1, 0); | |
85571a3d | 335 | if (!bufferStart) { |
9a5b6ddf AS |
336 | /* Documentation is unclear on where the packet can be fragmented. |
337 | * For the moment allocate the buffer needed to get the maximum length | |
338 | * of a GRE header contiguous */ | |
339 | tempBuf = OvsAllocateMemoryWithTag(OVS_MAX_GRE_LGTH, OVS_GRE_POOL_TAG); | |
340 | if (!tempBuf) { | |
341 | status = NDIS_STATUS_RESOURCES; | |
342 | goto end; | |
343 | } | |
344 | RtlZeroMemory(tempBuf, OVS_MAX_GRE_LGTH); | |
345 | bufferStart = NdisGetDataBuffer(curNb, OVS_MAX_GRE_LGTH, tempBuf, | |
346 | 1, 0); | |
347 | if (!bufferStart) { | |
348 | status = NDIS_STATUS_RESOURCES; | |
349 | goto end; | |
350 | } | |
85571a3d AS |
351 | } |
352 | ||
353 | ethHdr = (EthHdr *)bufferStart; | |
354 | headRoom += sizeof *ethHdr; | |
355 | ||
356 | ipHdr = (IPHdr *)((PCHAR)ethHdr + sizeof *ethHdr); | |
357 | tunKey->src = ipHdr->saddr; | |
358 | tunKey->dst = ipHdr->daddr; | |
359 | tunKey->tos = ipHdr->tos; | |
360 | tunKey->ttl = ipHdr->ttl; | |
361 | tunKey->pad = 0; | |
362 | headRoom += sizeof *ipHdr; | |
363 | ||
364 | greHdr = (GREHdr *)((PCHAR)ipHdr + sizeof *ipHdr); | |
365 | headRoom += sizeof *greHdr; | |
366 | ||
9a5b6ddf AS |
367 | tunnelSize = GreTunHdrSize(greHdr->flags); |
368 | ||
369 | /* Verify the packet length after looking at the GRE flags*/ | |
370 | if (packetLength <= tunnelSize) { | |
371 | status = NDIS_STATUS_INVALID_LENGTH; | |
372 | goto end; | |
373 | } | |
374 | ||
85571a3d AS |
375 | /* Validate if GRE header protocol type. */ |
376 | if (greHdr->protocolType != GRE_NET_TEB) { | |
377 | status = STATUS_NDIS_INVALID_PACKET; | |
9a5b6ddf | 378 | goto end; |
85571a3d AS |
379 | } |
380 | ||
381 | PCHAR currentOffset = (PCHAR)greHdr + sizeof *greHdr; | |
382 | ||
383 | if (greHdr->flags & GRE_CSUM) { | |
384 | tunKey->flags |= OVS_TNL_F_CSUM; | |
9a5b6ddf AS |
385 | UINT16 prevChksum = *((UINT16 *)currentOffset); |
386 | RtlZeroMemory(currentOffset, 2); | |
387 | UINT16 chksum = | |
388 | CalculateChecksumNB(curNb, | |
389 | (UINT16)(NET_BUFFER_DATA_LENGTH(curNb) - | |
390 | (ipHdr->ihl * 4 + sizeof *ethHdr)), | |
391 | ipHdr->ihl * 4 + sizeof *ethHdr); | |
392 | if (prevChksum != chksum) { | |
393 | status = STATUS_NDIS_INVALID_PACKET; | |
394 | goto end; | |
395 | } | |
396 | RtlCopyMemory(currentOffset, &prevChksum, 2); | |
85571a3d AS |
397 | currentOffset += 4; |
398 | headRoom += 4; | |
399 | } | |
400 | ||
401 | if (greHdr->flags & GRE_KEY) { | |
402 | tunKey->flags |= OVS_TNL_F_KEY; | |
403 | UINT32 key = 0; | |
404 | RtlCopyMemory(&key, currentOffset, 4); | |
405 | tunKey->tunnelId = (UINT64)key << 32; | |
406 | currentOffset += 4; | |
407 | headRoom += 4; | |
408 | } | |
409 | ||
9a5b6ddf AS |
410 | /* |
411 | * Create a copy of the NBL so that we have all the headers in one MDL. | |
412 | */ | |
413 | *newNbl = OvsPartialCopyNBL(switchContext, curNbl, | |
414 | tunnelSize, 0, | |
415 | TRUE /*copy NBL info */); | |
416 | ||
417 | if (*newNbl == NULL) { | |
418 | status = NDIS_STATUS_RESOURCES; | |
419 | goto end; | |
420 | } | |
421 | ||
422 | curNbl = *newNbl; | |
423 | curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); | |
424 | ||
85571a3d AS |
425 | /* Clear out the receive flag for the inner packet. */ |
426 | NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo) = 0; | |
9a5b6ddf | 427 | NdisAdvanceNetBufferDataStart(curNb, GreTunHdrSize(greHdr->flags), FALSE, |
85571a3d | 428 | NULL); |
9a5b6ddf | 429 | ASSERT(headRoom == GreTunHdrSize(greHdr->flags)); |
85571a3d | 430 | |
9a5b6ddf AS |
431 | end: |
432 | if (tempBuf) { | |
433 | OvsFreeMemoryWithTag(tempBuf, OVS_GRE_POOL_TAG); | |
434 | tempBuf = NULL; | |
435 | } | |
85571a3d AS |
436 | return status; |
437 | } |