3323cb87178cb5d684bfbe916aed0bfd6054b323
[reactos.git] / reactos / drivers / net / tcpip / transport / datagram / datagram.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS TCP/IP protocol driver
4 * FILE: transport/datagram/datagram.c
5 * PURPOSE: Routines for sending and receiving datagrams
6 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7 * REVISIONS:
8 * CSH 01/08-2000 Created
9 */
10 #include <tcpip.h>
11 #include <datagram.h>
12 #include <routines.h>
13 #include <transmit.h>
14 #include <address.h>
15 #include <route.h>
16 #include <pool.h>
17
18
19 /* Pending request queue */
20 LIST_ENTRY DGPendingListHead;
21 KSPIN_LOCK DGPendingListLock;
22 /* Work queue item for pending requests */
23 WORK_QUEUE_ITEM DGWorkItem;
24
25
26 VOID DatagramWorker(
27 PVOID Context)
28 /*
29 * FUNCTION: Handles pending requests
30 * ARGUMENTS:
31 * Context = Pointer to context information (unused)
32 * NOTES:
33 * This routine is called after the driver has run out of resources.
34 * It processes send requests or shedules them to be processed
35 */
36 {
37 PLIST_ENTRY CurrentADFEntry;
38 PLIST_ENTRY CurrentSREntry;
39 PADDRESS_FILE CurrentADF;
40 PDATAGRAM_SEND_REQUEST CurrentSR;
41 KIRQL OldIrql1;
42 KIRQL OldIrql2;
43
44 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
45
46 KeAcquireSpinLock(&DGPendingListLock, &OldIrql1);
47
48 CurrentADFEntry = DGPendingListHead.Flink;
49 while (CurrentADFEntry != &DGPendingListHead) {
50 RemoveEntryList(CurrentADFEntry);
51 CurrentADF = CONTAINING_RECORD(CurrentADFEntry,
52 ADDRESS_FILE,
53 ListEntry);
54
55 KeAcquireSpinLock(&CurrentADF->Lock, &OldIrql2);
56
57 if (AF_IS_BUSY(CurrentADF)) {
58 /* The send worker function is already running so we just
59 set the pending send flag on the address file object */
60
61 AF_SET_PENDING(CurrentADF, AFF_SEND);
62 KeReleaseSpinLock(&CurrentADF->Lock, OldIrql2);
63 } else {
64 if (!IsListEmpty(&CurrentADF->TransmitQueue)) {
65 /* The transmit queue is not empty. Dequeue a send
66 request and process it */
67
68 CurrentSREntry = RemoveHeadList(&CurrentADF->TransmitQueue);
69 CurrentSR = CONTAINING_RECORD(CurrentADFEntry,
70 DATAGRAM_SEND_REQUEST,
71 ListEntry);
72
73 KeReleaseSpinLock(&CurrentADF->Lock, OldIrql2);
74
75 DGSend(CurrentADF, CurrentSR);
76 } else
77 KeReleaseSpinLock(&CurrentADF->Lock, OldIrql2);
78 }
79 CurrentADFEntry = CurrentADFEntry->Flink;
80 }
81
82 KeReleaseSpinLock(&DGPendingListLock, OldIrql1);
83
84 TI_DbgPrint(MAX_TRACE, ("Leaving.\n"));
85 }
86
87
88 VOID SendDatagramComplete(
89 PVOID Context,
90 PNDIS_PACKET Packet,
91 NDIS_STATUS NdisStatus)
92 /*
93 * FUNCTION: Datagram transmit completion handler
94 * ARGUMENTS:
95 * Context = Pointer to context infomation (DATAGRAM_SEND_REQUEST)
96 * Packet = Pointer to NDIS packet
97 * NdisStatus = Status of transmit operation
98 * NOTES:
99 * This routine is called by IP when a datagram send completes.
100 * We shedule the out-of-resource worker function if there
101 * are pending address files in the queue
102 */
103 {
104 KIRQL OldIrql;
105 ULONG BytesSent;
106 PVOID CompleteContext;
107 PNDIS_BUFFER NdisBuffer;
108 PDATAGRAM_SEND_REQUEST SendRequest;
109 DATAGRAM_COMPLETION_ROUTINE Complete;
110 BOOLEAN QueueWorkItem;
111
112 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
113
114 SendRequest = (PDATAGRAM_SEND_REQUEST)Context;
115 Complete = SendRequest->Complete;
116 CompleteContext = SendRequest->Context;
117 BytesSent = SendRequest->BufferSize;
118
119 /* Remove data buffer before releasing memory for packet buffers */
120 NdisQueryPacket(Packet, NULL, NULL, &NdisBuffer, NULL);
121 NdisUnchainBufferAtBack(Packet, &NdisBuffer);
122 FreeNdisPacket(Packet);
123 DereferenceObject(SendRequest->RemoteAddress);
124 PoolFreeBuffer(SendRequest);
125
126 /* If there are pending send requests, shedule worker function */
127 KeAcquireSpinLock(&DGPendingListLock, &OldIrql);
128 QueueWorkItem = (!IsListEmpty(&DGPendingListHead));
129 KeReleaseSpinLock(&DGPendingListLock, OldIrql);
130 if (QueueWorkItem)
131 ExQueueWorkItem(&DGWorkItem, CriticalWorkQueue);
132
133 /* Call completion routine for send request */
134 (*Complete)(CompleteContext, NdisStatus, BytesSent);
135
136 TI_DbgPrint(MAX_TRACE, ("Leaving.\n"));
137 }
138
139
140 VOID DGSend(
141 PVOID Context,
142 PDATAGRAM_SEND_REQUEST SendRequest)
143 /*
144 * FUNCTION: Sends a datagram to IP layer
145 * ARGUMENTS:
146 * Context = Pointer to context information (ADDRESS_FILE)
147 * SendRequest = Pointer to send request
148 */
149 {
150 KIRQL OldIrql;
151 NTSTATUS Status;
152 USHORT LocalPort;
153 PIP_PACKET IPPacket;
154 PROUTE_CACHE_NODE RCN;
155 PLIST_ENTRY CurrentEntry;
156 PADDRESS_FILE AddrFile = Context;
157 PADDRESS_ENTRY ADE;
158
159 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
160
161 /* Get the information we need from the address file
162 now so we minimize the time we hold the spin lock */
163 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
164 LocalPort = AddrFile->Port;
165 ADE = AddrFile->ADE;
166 ReferenceObject(ADE);
167 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
168
169 /* Loop until there are no more send requests in the
170 transmit queue or until we run out of resources */
171 for (;;) {
172 Status = SendRequest->Build(SendRequest, ADE->Address, LocalPort, &IPPacket);
173 if (!NT_SUCCESS(Status)) {
174 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
175 /* An error occurred, enqueue the send request again and return */
176 InsertTailList(&AddrFile->TransmitQueue, &SendRequest->ListEntry);
177 DereferenceObject(ADE);
178 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
179
180 TI_DbgPrint(MIN_TRACE, ("Leaving (insufficient resources).\n"));
181 return;
182 }
183
184 /* Get a route to the destination address */
185 if (RouteGetRouteToDestination(SendRequest->RemoteAddress, ADE->NTE, &RCN) == IP_SUCCESS) {
186 /* Set completion routine and send the packet */
187 PC(IPPacket->NdisPacket)->Complete = SendDatagramComplete;
188 PC(IPPacket->NdisPacket)->Context = SendRequest;
189 if (IPSendDatagram(IPPacket, RCN) != STATUS_SUCCESS)
190 SendDatagramComplete(SendRequest,
191 IPPacket->NdisPacket,
192 NDIS_STATUS_REQUEST_ABORTED);
193 /* We're done with the RCN */
194 DereferenceObject(RCN);
195 } else {
196 /* No route to destination */
197 /* FIXME: Which error code should we use here? */
198 TI_DbgPrint(MIN_TRACE, ("No route to destination address (0x%X).\n",
199 SendRequest->RemoteAddress->Address.IPv4Address));
200 SendDatagramComplete(SendRequest,
201 IPPacket->NdisPacket,
202 NDIS_STATUS_REQUEST_ABORTED);
203 }
204
205 PoolFreeBuffer(IPPacket);
206
207 /* Check transmit queue for more to send */
208
209 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
210
211 if (!IsListEmpty(&AddrFile->TransmitQueue)) {
212 /* Transmit queue is not empty, process one more request */
213 CurrentEntry = RemoveHeadList(&AddrFile->TransmitQueue);
214 SendRequest = CONTAINING_RECORD(CurrentEntry, DATAGRAM_SEND_REQUEST, ListEntry);
215
216 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
217 } else {
218 /* Transmit queue is empty */
219 AF_CLR_PENDING(AddrFile, AFF_SEND);
220 DereferenceObject(ADE);
221 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
222
223 TI_DbgPrint(MAX_TRACE, ("Leaving (empty queue).\n"));
224 return;
225 }
226 }
227 }
228
229
230 VOID DGDeliverData(
231 PADDRESS_FILE AddrFile,
232 PIP_ADDRESS Address,
233 PIP_PACKET IPPacket,
234 UINT DataSize)
235 /*
236 * FUNCTION: Delivers datagram data to a user
237 * ARGUMENTS:
238 * AddrFile = Address file to deliver data to
239 * Address = Remote address the packet came from
240 * IPPacket = Pointer to IP packet to deliver
241 * DataSize = Number of bytes in data area
242 * NOTES:
243 * If there is a receive request, then we copy the data to the
244 * buffer supplied by the user and complete the receive request.
245 * If no suitable receive request exists, then we call the event
246 * handler if it exists, otherwise we drop the packet.
247 */
248 {
249 KIRQL OldIrql;
250 PTDI_IND_RECEIVE_DATAGRAM ReceiveHandler;
251 PVOID HandlerContext;
252 LONG AddressLength;
253 PVOID SourceAddress;
254 ULONG BytesTaken;
255 NTSTATUS Status;
256
257 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
258
259 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
260
261 if (!IsListEmpty(&AddrFile->ReceiveQueue)) {
262 PLIST_ENTRY CurrentEntry;
263 PDATAGRAM_RECEIVE_REQUEST Current;
264 BOOLEAN Found;
265
266 TI_DbgPrint(MAX_TRACE, ("There is a receive request.\n"));
267
268 /* Search receive request list to find a match */
269 Found = FALSE;
270 CurrentEntry = AddrFile->ReceiveQueue.Flink;
271 while ((CurrentEntry != &AddrFile->ReceiveQueue) && (!Found)) {
272 Current = CONTAINING_RECORD(CurrentEntry, DATAGRAM_RECEIVE_REQUEST, ListEntry);
273 if (!Current->RemoteAddress)
274 Found = TRUE;
275 else if (AddrIsEqual(Address, Current->RemoteAddress))
276 Found = TRUE;
277
278 if (Found) {
279 /* FIXME: Maybe we should check if the buffer of this
280 receive request is large enough and if not, search
281 for another */
282
283 /* Remove the request from the queue */
284 RemoveEntryList(&Current->ListEntry);
285 AddrFile->RefCount--;
286 break;
287 }
288 CurrentEntry = CurrentEntry->Flink;
289 }
290
291 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
292
293 if (Found) {
294 TI_DbgPrint(MAX_TRACE, ("Suitable receive request found.\n"));
295
296 /* Copy the data into buffer provided by the user */
297 CopyBufferToBufferChain(Current->Buffer,
298 0,
299 IPPacket->Data,
300 DataSize);
301
302 /* Complete the receive request */
303 (*Current->Complete)(Current->Context, STATUS_SUCCESS, DataSize);
304
305 /* Finally free the receive request */
306 if (Current->RemoteAddress)
307 PoolFreeBuffer(Current->RemoteAddress);
308 PoolFreeBuffer(Current);
309 }
310 } else if (AddrFile->RegisteredReceiveDatagramHandler) {
311 TI_DbgPrint(MAX_TRACE, ("Calling receive event handler.\n"));
312
313 ReceiveHandler = AddrFile->ReceiveDatagramHandler;
314 HandlerContext = AddrFile->ReceiveDatagramHandlerContext;
315
316 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
317
318 if (Address->Type == IP_ADDRESS_V4) {
319 AddressLength = sizeof(IPv4_RAW_ADDRESS);
320 SourceAddress = &Address->Address.IPv4Address;
321 } else /* (Address->Type == IP_ADDRESS_V6) */ {
322 AddressLength = sizeof(IPv6_RAW_ADDRESS);
323 SourceAddress = Address->Address.IPv6Address;
324 }
325
326 Status = (*ReceiveHandler)(HandlerContext,
327 AddressLength,
328 SourceAddress,
329 0,
330 NULL,
331 TDI_RECEIVE_ENTIRE_MESSAGE,
332 DataSize,
333 DataSize,
334 &BytesTaken,
335 IPPacket->Data,
336 NULL);
337 } else {
338 TI_DbgPrint(MAX_TRACE, ("Discarding datagram.\n"));
339 }
340
341 TI_DbgPrint(MAX_TRACE, ("Leaving.\n"));
342 }
343
344
345 VOID DGCancelSendRequest(
346 PADDRESS_FILE AddrFile,
347 PVOID Context)
348 /*
349 * FUNCTION: Cancels a datagram send request
350 * ARGUMENTS:
351 * AddrFile = Pointer to address file of the request
352 * Context = Pointer to context information for completion handler
353 */
354 {
355 KIRQL OldIrql;
356 PLIST_ENTRY CurrentEntry;
357 PDATAGRAM_SEND_REQUEST Current = NULL;
358 BOOLEAN Found = FALSE;
359
360 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
361
362 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
363
364 /* Search the request list for the specified request and remove it */
365 CurrentEntry = AddrFile->TransmitQueue.Flink;
366 while ((CurrentEntry != &AddrFile->TransmitQueue) && (!Found)) {
367 Current = CONTAINING_RECORD(CurrentEntry, DATAGRAM_SEND_REQUEST, ListEntry);
368 if (Context == Current->Context) {
369 /* We've found the request, now remove it from the queue */
370 RemoveEntryList(CurrentEntry);
371 AddrFile->RefCount--;
372 Found = TRUE;
373 break;
374 }
375 CurrentEntry = CurrentEntry->Flink;
376 }
377
378 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
379
380 if (Found) {
381 /* Complete the request and free its resources */
382 (*Current->Complete)(Current->Context, STATUS_CANCELLED, 0);
383 PoolFreeBuffer(Current->RemoteAddress);
384 PoolFreeBuffer(Current);
385 } else {
386 TI_DbgPrint(MID_TRACE, ("Cannot find send request.\n"));
387 }
388 }
389
390
391 VOID DGCancelReceiveRequest(
392 PADDRESS_FILE AddrFile,
393 PVOID Context)
394 /*
395 * FUNCTION: Cancels a datagram receive request
396 * ARGUMENTS:
397 * AddrFile = Pointer to address file of the request
398 * Context = Pointer to context information for completion handler
399 */
400 {
401 KIRQL OldIrql;
402 PLIST_ENTRY CurrentEntry;
403 PDATAGRAM_RECEIVE_REQUEST Current = NULL;
404 BOOLEAN Found = FALSE;
405
406 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
407
408 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
409
410 /* Search the request list for the specified request and remove it */
411 CurrentEntry = AddrFile->ReceiveQueue.Flink;
412 while ((CurrentEntry != &AddrFile->ReceiveQueue) && (!Found)) {
413 Current = CONTAINING_RECORD(CurrentEntry, DATAGRAM_RECEIVE_REQUEST, ListEntry);
414 if (Context == Current->Context) {
415 /* We've found the request, now remove it from the queue */
416 RemoveEntryList(CurrentEntry);
417 AddrFile->RefCount--;
418 Found = TRUE;
419 break;
420 }
421 CurrentEntry = CurrentEntry->Flink;
422 }
423
424 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
425
426 if (Found) {
427 /* Complete the request and free its resources */
428 (*Current->Complete)(Current->Context, STATUS_CANCELLED, 0);
429 /* Remote address can be NULL if the caller wants to receive
430 packets sent from any address */
431 if (Current->RemoteAddress)
432 PoolFreeBuffer(Current->RemoteAddress);
433 PoolFreeBuffer(Current);
434 } else {
435 TI_DbgPrint(MID_TRACE, ("Cannot find receive request.\n"));
436 }
437 }
438
439
440 NTSTATUS DGSendDatagram(
441 PTDI_REQUEST Request,
442 PTDI_CONNECTION_INFORMATION ConnInfo,
443 PNDIS_BUFFER Buffer,
444 ULONG DataSize,
445 DATAGRAM_BUILD_ROUTINE Build)
446 /*
447 * FUNCTION: Sends a datagram to a remote address
448 * ARGUMENTS:
449 * Request = Pointer to TDI request
450 * ConnInfo = Pointer to connection information
451 * Buffer = Pointer to NDIS buffer with data
452 * DataSize = Size in bytes of data to be sent
453 * Build = Pointer to datagram build routine
454 * RETURNS:
455 * Status of operation
456 */
457 {
458 PADDRESS_FILE AddrFile;
459 KIRQL OldIrql;
460 NTSTATUS Status;
461 PDATAGRAM_SEND_REQUEST SendRequest = NULL;
462
463 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
464
465 AddrFile = Request->Handle.AddressHandle;
466
467 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
468
469 if (AF_IS_VALID(AddrFile)) {
470 SendRequest = PoolAllocateBuffer(sizeof(DATAGRAM_SEND_REQUEST));
471 if (SendRequest) {
472 /* Initialize a send request */
473 Status = AddrGetAddress(ConnInfo->RemoteAddress,
474 &SendRequest->RemoteAddress, &SendRequest->RemotePort,
475 &AddrFile->AddrCache);
476 if (NT_SUCCESS(Status)) {
477 SendRequest->Buffer = Buffer;
478 SendRequest->BufferSize = DataSize;
479 SendRequest->Complete = Request->RequestNotifyObject;
480 SendRequest->Context = Request->RequestContext;
481 SendRequest->Build = Build;
482
483 if (AF_IS_BUSY(AddrFile)) {
484 /* Queue send request on the transmit queue */
485 InsertTailList(&AddrFile->TransmitQueue, &SendRequest->ListEntry);
486
487 /* Reference address file and set pending send request flag */
488 AddrFile->RefCount++;
489 AF_SET_PENDING(AddrFile, AFF_SEND);
490
491 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
492
493 TI_DbgPrint(MAX_TRACE, ("Leaving (queued).\n"));
494 } else {
495 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
496
497 /* Send the datagram */
498 DGSend(AddrFile, SendRequest);
499
500 TI_DbgPrint(MAX_TRACE, ("Leaving (pending).\n"));
501 }
502 return STATUS_PENDING;
503 }
504 } else
505 Status = STATUS_INSUFFICIENT_RESOURCES;
506 } else
507 Status = STATUS_ADDRESS_CLOSED;
508
509 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
510
511 TI_DbgPrint(MAX_TRACE, ("Leaving. Status (0x%X)\n", Status));
512
513 return Status;
514 }
515
516
517 NTSTATUS DGReceiveDatagram(
518 PTDI_REQUEST Request,
519 PTDI_CONNECTION_INFORMATION ConnInfo,
520 PNDIS_BUFFER Buffer,
521 ULONG ReceiveLength,
522 ULONG ReceiveFlags,
523 PTDI_CONNECTION_INFORMATION ReturnInfo,
524 PULONG BytesReceived)
525 /*
526 * FUNCTION: Attempts to receive a datagram from a remote address
527 * ARGUMENTS:
528 * Request = Pointer to TDI request
529 * ConnInfo = Pointer to connection information
530 * Buffer = Pointer to NDIS buffer chain to store received data
531 * ReceiveLength = Maximum size to use of buffer (0 if all can be used)
532 * ReceiveFlags = Receive flags (None, Normal, Peek)
533 * ReturnInfo = Pointer to structure for return information
534 * BytesReceive = Pointer to structure for number of bytes received
535 * RETURNS:
536 * Status of operation
537 * NOTES:
538 * This is the high level interface for receiving datagrams
539 */
540 {
541 PADDRESS_FILE AddrFile;
542 KIRQL OldIrql;
543 NTSTATUS Status;
544 PDATAGRAM_RECEIVE_REQUEST ReceiveRequest;
545
546 TI_DbgPrint(MAX_TRACE, ("Called.\n"));
547
548 AddrFile = Request->Handle.AddressHandle;
549
550 KeAcquireSpinLock(&AddrFile->Lock, &OldIrql);
551
552 if (AF_IS_VALID(AddrFile)) {
553 ReceiveRequest = PoolAllocateBuffer(sizeof(DATAGRAM_RECEIVE_REQUEST));
554 if (ReceiveRequest) {
555 /* Initialize a receive request */
556
557 /* Extract the remote address filter from the request (if any) */
558 if (((ConnInfo->RemoteAddressLength != 0)) && (ConnInfo->RemoteAddress)) {
559 Status = AddrGetAddress(ConnInfo->RemoteAddress,
560 &ReceiveRequest->RemoteAddress,
561 &ReceiveRequest->RemotePort,
562 &AddrFile->AddrCache);
563 if (!NT_SUCCESS(Status)) {
564 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
565 PoolFreeBuffer(ReceiveRequest);
566 return Status;
567 }
568 } else {
569 ReceiveRequest->RemotePort = 0;
570 ReceiveRequest->RemoteAddress = NULL;
571 }
572 ReceiveRequest->ReturnInfo = ReturnInfo;
573 ReceiveRequest->Buffer = Buffer;
574 /* If ReceiveLength is 0, the whole buffer is available to us */
575 ReceiveRequest->BufferSize = (ReceiveLength == 0) ?
576 MmGetMdlByteCount(Buffer) : ReceiveLength;
577 ReceiveRequest->Complete = Request->RequestNotifyObject;
578 ReceiveRequest->Context = Request->RequestContext;
579
580 /* Queue receive request */
581 InsertTailList(&AddrFile->ReceiveQueue, &ReceiveRequest->ListEntry);
582
583 /* Reference address file and set pending receive request flag */
584 AddrFile->RefCount++;
585 AF_SET_PENDING(AddrFile, AFF_RECEIVE);
586
587 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
588
589 TI_DbgPrint(MAX_TRACE, ("Leaving (pending).\n"));
590
591 return STATUS_PENDING;
592 } else
593 Status = STATUS_INSUFFICIENT_RESOURCES;
594 } else
595 Status = STATUS_INVALID_ADDRESS;
596
597 KeReleaseSpinLock(&AddrFile->Lock, OldIrql);
598
599 TI_DbgPrint(MAX_TRACE, ("Leaving with errors (0x%X).\n", Status));
600
601 return Status;
602 }
603
604
605 NTSTATUS DGStartup(
606 VOID)
607 /*
608 * FUNCTION: Initializes the datagram subsystem
609 * RETURNS:
610 * Status of operation
611 */
612 {
613 InitializeListHead(&DGPendingListHead);
614
615 KeInitializeSpinLock(&DGPendingListLock);
616
617 ExInitializeWorkItem(&DGWorkItem, DatagramWorker, NULL);
618
619 return STATUS_SUCCESS;
620 }
621
622
623 NTSTATUS DGShutdown(
624 VOID)
625 /*
626 * FUNCTION: Shuts down the datagram subsystem
627 * RETURNS:
628 * Status of operation
629 */
630 {
631 return STATUS_SUCCESS;
632 }
633
634
635 /* EOF */