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