97d202099ef44afe5c797b21bf915ab4c29b01bf
[cascardo/ovs.git] / datapath-windows / ovsext / Tunnel.c
1 /*
2  * Copyright (c) 2014 VMware, Inc.
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 /*
18  * OvsTunnel.c
19  *  WFP Classified callback function and Action code for injecting a packet to the vswitch
20  */
21
22 #include "precomp.h"
23
24 #pragma warning(push)
25 #pragma warning(disable:4201)       // unnamed struct/union
26 #include <fwpsk.h>
27 #pragma warning(pop)
28
29 #pragma warning( push )
30 #pragma warning( disable:4127 )
31
32 #include <fwpmk.h>
33 #include "Tunnel.h"
34 #include "Switch.h"
35 #include "Vport.h"
36 #include "Event.h"
37 #include "User.h"
38 #include "Vxlan.h"
39 #include "PacketIO.h"
40 #include "NetProto.h"
41 #include "Flow.h"
42 #include "Actions.h"
43
44 extern POVS_SWITCH_CONTEXT gOvsSwitchContext;
45
46 static NTSTATUS
47 OvsInjectPacketThroughActions(PNET_BUFFER_LIST pNbl,
48                               OVS_TUNNEL_PENDED_PACKET *packet);
49
50 VOID OvsAcquireDatapathRead(OVS_DATAPATH *datapath,
51                             LOCK_STATE_EX *lockState,
52                             BOOLEAN dispatch);
53 VOID OvsAcquireDatapathWrite(OVS_DATAPATH *datapath,
54                              LOCK_STATE_EX *lockState,
55                              BOOLEAN dispatch);
56 VOID OvsReleaseDatapath(OVS_DATAPATH *datapath,
57                         LOCK_STATE_EX *lockState);
58
59
60 NTSTATUS
61 OvsTunnelNotify(FWPS_CALLOUT_NOTIFY_TYPE notifyType,
62                 const GUID *filterKey,
63                 const FWPS_FILTER *filter)
64 {
65     UNREFERENCED_PARAMETER(notifyType);
66     UNREFERENCED_PARAMETER(filterKey);
67     UNREFERENCED_PARAMETER(filter);
68
69     return STATUS_SUCCESS;
70 }
71
72 static NTSTATUS
73 OvsTunnelAnalyzePacket(OVS_TUNNEL_PENDED_PACKET *packet)
74 {
75     NTSTATUS status = STATUS_SUCCESS;
76     UINT32 packetLength = 0;
77     ULONG bytesCopied = 0;
78     NET_BUFFER_LIST *copiedNBL = NULL;
79     NET_BUFFER *netBuffer;
80     NDIS_STATUS ndisStatus;
81
82     /*
83      * For inbound net buffer list, we can assume it contains only one
84      * net buffer (unless it was an re-assembeled fragments). in both cases
85      * the first net buffer should include all headers, we assert if the retreat fails
86      */
87     netBuffer = NET_BUFFER_LIST_FIRST_NB(packet->netBufferList);
88
89     /* Drop the packet from the host stack */
90     packet->classifyOut->actionType = FWP_ACTION_BLOCK;
91     packet->classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
92
93     /* Adjust the net buffer list offset to the start of the IP header */
94     ndisStatus = NdisRetreatNetBufferDataStart(netBuffer,
95                                                packet->ipHeaderSize +
96                                                packet->transportHeaderSize,
97                                                0, NULL);
98     ASSERT(ndisStatus == NDIS_STATUS_SUCCESS);
99
100     /* Single NBL element for WFP */
101     ASSERT(packet->netBufferList->Next == NULL);
102
103     /* Note that the copy will inherit the original net buffer list's offset */
104     packetLength = NET_BUFFER_DATA_LENGTH(netBuffer);
105     copiedNBL = OvsAllocateVariableSizeNBL(gOvsSwitchContext, packetLength,
106                                            OVS_DEFAULT_HEADROOM_SIZE);
107
108     if (copiedNBL == NULL) {
109         goto analyzeDone;
110     }
111
112     status = NdisCopyFromNetBufferToNetBuffer(NET_BUFFER_LIST_FIRST_NB(copiedNBL),
113                                               0, packetLength,
114                                               netBuffer, 0, &bytesCopied);
115     if (status != NDIS_STATUS_SUCCESS || packetLength != bytesCopied) {
116         goto analyzeFreeNBL;
117     }
118
119     status = OvsInjectPacketThroughActions(copiedNBL,
120                                            packet);
121     goto analyzeDone;
122
123     /* Undo the adjustment on the original net buffer list */
124 analyzeFreeNBL:
125     OvsCompleteNBL(gOvsSwitchContext, copiedNBL, TRUE);
126 analyzeDone:
127     NdisAdvanceNetBufferDataStart(netBuffer,
128                                   packet->transportHeaderSize + packet->ipHeaderSize,
129                                   FALSE,
130                                   NULL);
131     return status;
132 }
133
134
135 /*
136  * --------------------------------------------------------------------------
137  * This is the classifyFn function of the datagram-data callout. It
138  * allocates a packet structure to store the classify and meta data and
139  * it references the net buffer list for out-of-band modification and
140  * re-injection. The packet structure will be queued to the global packet
141  * queue. The worker thread will then be signaled, if idle, to process
142  * the queue.
143  * --------------------------------------------------------------------------
144  */
145 VOID
146 OvsTunnelClassify(const FWPS_INCOMING_VALUES *inFixedValues,
147                   const FWPS_INCOMING_METADATA_VALUES *inMetaValues,
148                   VOID *layerData,
149                   const VOID *classifyContext,
150                   const FWPS_FILTER *filter,
151                   UINT64 flowContext,
152                   FWPS_CLASSIFY_OUT *classifyOut)
153 {
154     OVS_TUNNEL_PENDED_PACKET packetStorage;
155     OVS_TUNNEL_PENDED_PACKET *packet = &packetStorage;
156     FWP_DIRECTION  direction;
157
158     UNREFERENCED_PARAMETER(classifyContext);
159     UNREFERENCED_PARAMETER(filter);
160     UNREFERENCED_PARAMETER(flowContext);
161
162     ASSERT(layerData != NULL);
163
164     /* We don't have the necessary right to alter the packet flow */
165     if ((classifyOut->rights & FWPS_RIGHT_ACTION_WRITE) == 0) {
166         /* XXX TBD revisit protect against other filters owning this packet */
167         ASSERT(FALSE);
168         goto Exit;
169     }
170
171     RtlZeroMemory(packet, sizeof(OVS_TUNNEL_PENDED_PACKET));
172
173     /* classifyOut cannot be accessed from a different thread context */
174     packet->classifyOut = classifyOut;
175
176     if (inFixedValues->layerId == FWPS_LAYER_DATAGRAM_DATA_V4) {
177         direction =
178             inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION].\
179             value.uint32;
180     }
181     else {
182         ASSERT(inFixedValues->layerId == FWPS_LAYER_DATAGRAM_DATA_V6);
183         direction =
184             inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V6_DIRECTION].\
185             value.uint32;
186     }
187
188     packet->netBufferList = layerData;
189
190     ASSERT(FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues,
191         FWPS_METADATA_FIELD_COMPARTMENT_ID));
192
193     ASSERT(direction == FWP_DIRECTION_INBOUND);
194
195     ASSERT(FWPS_IS_METADATA_FIELD_PRESENT(
196         inMetaValues,
197         FWPS_METADATA_FIELD_IP_HEADER_SIZE));
198     ASSERT(FWPS_IS_METADATA_FIELD_PRESENT(
199         inMetaValues,
200         FWPS_METADATA_FIELD_TRANSPORT_HEADER_SIZE));
201
202     packet->ipHeaderSize = inMetaValues->ipHeaderSize;
203     packet->transportHeaderSize = inMetaValues->transportHeaderSize;
204
205     ASSERT(inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_IP_PROTOCOL].value.uint8 == IPPROTO_UDP );
206     OvsTunnelAnalyzePacket(packet);
207
208 Exit:
209     ;
210 }
211
212
213 static NTSTATUS
214 OvsInjectPacketThroughActions(PNET_BUFFER_LIST pNbl,
215                               OVS_TUNNEL_PENDED_PACKET *packet)
216 {
217     NTSTATUS status;
218     OvsIPv4TunnelKey tunnelKey;
219     NET_BUFFER *pNb;
220     ULONG sendCompleteFlags = 0;
221     BOOLEAN dispatch;
222     PNDIS_SWITCH_FORWARDING_DETAIL_NET_BUFFER_LIST_INFO fwdDetail;
223     LOCK_STATE_EX lockState, dpLockState;
224     LIST_ENTRY missedPackets;
225     OvsCompletionList completionList;
226     KIRQL irql;
227     ULONG SendFlags = NDIS_SEND_FLAGS_SWITCH_DESTINATION_GROUP;
228     OVS_DATAPATH *datapath = &gOvsSwitchContext->datapath;;
229
230     ASSERT(gOvsSwitchContext);
231
232     /* Fill the tunnel key */
233     status = OvsSlowPathDecapVxlan(pNbl, &tunnelKey);
234
235     if(!NT_SUCCESS(status)) {
236         goto dropit;
237     }
238
239     pNb = NET_BUFFER_LIST_FIRST_NB(pNbl);
240
241     NdisAdvanceNetBufferDataStart(pNb,
242                                   packet->transportHeaderSize + packet->ipHeaderSize +
243                                   sizeof(VXLANHdr),
244                                   FALSE,
245                                   NULL);
246
247     /* Most likely (always) dispatch irql */
248     irql = KeGetCurrentIrql();
249
250     /* dispatch is used for datapath lock as well */
251     dispatch = (irql == DISPATCH_LEVEL) ?  NDIS_RWL_AT_DISPATCH_LEVEL : 0;
252     if (dispatch) {
253         sendCompleteFlags |=  NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL;
254     }
255
256     InitializeListHead(&missedPackets);
257     OvsInitCompletionList(&completionList, gOvsSwitchContext,
258                           sendCompleteFlags);
259
260     {
261         POVS_VPORT_ENTRY vport = NULL;
262         UINT32 portNo = 0;
263         OVS_PACKET_HDR_INFO layers = { 0 };
264         OvsFlowKey key = { 0 };
265         UINT64 hash = 0;
266         PNET_BUFFER curNb = NULL;
267         OvsFlow *flow = NULL;
268
269         fwdDetail = NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(pNbl);
270
271         /*
272          * XXX WFP packets contain a single NBL structure.
273          * Reassembeled packet "may" have multiple NBs, however, a simple test shows
274          * that the packet still has a single NB (after reassemble)
275          * We still need to check if the Ethernet header of the innet packet is in a single MD
276          */
277
278         curNb = NET_BUFFER_LIST_FIRST_NB(pNbl);
279         ASSERT(curNb->Next == NULL);
280
281         NdisAcquireRWLockRead(gOvsSwitchContext->dispatchLock, &lockState, dispatch);
282
283         /* Lock the flowtable for the duration of accessing the flow */
284         OvsAcquireDatapathRead(datapath, &dpLockState, NDIS_RWL_AT_DISPATCH_LEVEL);
285
286         SendFlags |= NDIS_SEND_FLAGS_DISPATCH_LEVEL;
287
288         vport = OvsFindTunnelVportByDstPort(gOvsSwitchContext,
289                                             htons(tunnelKey.dst_port),
290                                             OVS_VPORT_TYPE_VXLAN);
291
292         if (vport == NULL){
293             status = STATUS_UNSUCCESSFUL;
294             goto unlockAndDrop;
295         }
296
297         ASSERT(vport->ovsType == OVS_VPORT_TYPE_VXLAN);
298
299         portNo = vport->portNo;
300
301         status = OvsExtractFlow(pNbl, portNo, &key, &layers, &tunnelKey);
302         if (status != NDIS_STATUS_SUCCESS) {
303             goto unlockAndDrop;
304         }
305
306         flow = OvsLookupFlow(datapath, &key, &hash, FALSE);
307         if (flow) {
308             OvsFlowUsed(flow, pNbl, &layers);
309             datapath->hits++;
310
311             OvsActionsExecute(gOvsSwitchContext, &completionList, pNbl,
312                               portNo, SendFlags, &key, &hash, &layers,
313                               flow->actions, flow->actionsLen);
314
315             OvsReleaseDatapath(datapath, &dpLockState);
316         } else {
317             POVS_PACKET_QUEUE_ELEM elem;
318
319             datapath->misses++;
320             elem = OvsCreateQueueNlPacket(NULL, 0, OVS_PACKET_CMD_MISS,
321                                           vport, &key, pNbl, curNb,
322                                           TRUE, &layers);
323             if (elem) {
324                 /* Complete the packet since it was copied to user buffer. */
325                 InsertTailList(&missedPackets, &elem->link);
326                 OvsQueuePackets(&missedPackets, 1);
327             } else {
328                 status = STATUS_INSUFFICIENT_RESOURCES;
329             }
330             goto unlockAndDrop;
331         }
332
333         NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
334
335     }
336
337     return status;
338
339 unlockAndDrop:
340     OvsReleaseDatapath(datapath, &dpLockState);
341     NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
342 dropit:
343     pNbl = OvsCompleteNBL(gOvsSwitchContext, pNbl, TRUE);
344     ASSERT(pNbl == NULL);
345     return status;
346 }
347
348 #pragma warning(pop)