:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / drivers / net / tcpip / network / route.c
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS TCP/IP protocol driver
4  * FILE:        network/route.c
5  * PURPOSE:     Route cache
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  * NOTES:       The route cache is implemented as a binary search
8  *              tree to obtain fast searches
9  * REVISIONS:
10  *   CSH 01/08-2000 Created
11  */
12 #include <tcpip.h>
13 #include <route.h>
14 #include <router.h>
15
16
17 /* This RCN is shared by all external nodes. It complicates things,
18    but the memory requirements are reduced by approximately 50%.
19    The RCN is protected by the route cache spin lock */
20 PROUTE_CACHE_NODE ExternalRCN;
21 PROUTE_CACHE_NODE RouteCache;
22 KSPIN_LOCK RouteCacheLock;
23 NPAGED_LOOKASIDE_LIST IPRCNList;
24
25
26 #if DBG
27 VOID PrintTree(
28     PROUTE_CACHE_NODE Node)
29 /*
30  * FUNCTION: Prints all nodes on tree
31  * ARGUMENTS:
32  *     Node = Pointer to root node of tree
33  * NOTES:
34  *     This function must be called with the route cache lock held.
35  */
36 {
37     if (IsInternalRCN(Node)) {
38         /* Traverse left subtree */
39         PrintTree(Node->Left);
40
41         /* Traverse right subtree */
42         PrintTree(Node->Right);
43
44         /* Finally check the node itself */
45         TI_DbgPrint(MIN_TRACE, ("(Internal) Self,Parent,Left,Right,Data = (%08X, %08X, %08X, %08X, %08X).\n",
46             Node, Node->Parent, Node->Left, Node->Right, (ULONG_PTR)Node->Destination.Address.IPv4Address));
47     } else
48         TI_DbgPrint(MIN_TRACE, ("(External) Self,Parent,Left,Right = (%08X, %08X, %08X, %08X).\n",
49             Node, Node->Parent, Node->Left, Node->Right));
50 }
51 #endif
52
53
54 VOID FreeRCN(
55     PVOID Object)
56 /*
57  * FUNCTION: Frees an route cache node object
58  * ARGUMENTS:
59  *     Object = Pointer to an route cache node structure
60  */
61 {
62   ExFreeToNPagedLookasideList(&IPRCNList, Object);
63 }
64
65
66 VOID RemoveAboveExternal(VOID)
67 /*
68  * FUNCTION: Removes the parent node of the selected external node from the route cache tree
69  * NOTES:
70  *     This function must be called with the route cache lock held.
71  *     ExternalRCN->Parent must be initialized
72  */
73 {
74     PROUTE_CACHE_NODE Parent;
75     PROUTE_CACHE_NODE Sibling;
76
77     TI_DbgPrint(DEBUG_RCACHE, ("Called.\n"));
78
79 #if 0
80     TI_DbgPrint(MIN_TRACE, ("Displaying tree (before).\n"));
81     PrintTree(RouteCache);
82 #endif
83
84     Parent = ExternalRCN->Parent;
85     /* Find sibling of external node */
86     if (ExternalRCN == Parent->Left)
87         Sibling = Parent->Right;
88     else
89         Sibling = Parent->Left;
90
91     /* Replace parent node with sibling of external node */
92     if (Parent != RouteCache) {
93         if (Parent->Parent->Left == Parent)
94             Parent->Parent->Left = Sibling;
95         else
96             Parent->Parent->Right = Sibling;
97         /* Give sibling a new parent */
98         Sibling->Parent = Parent->Parent;
99     } else {
100         /* This is the root we're removing */
101         RouteCache      = Sibling;
102         Sibling->Parent = NULL;
103     }
104
105     DereferenceObject(Parent);
106
107 #if 0
108     TI_DbgPrint(MIN_TRACE, ("Displaying tree (after).\n"));
109     PrintTree(RouteCache);
110 #endif
111 }
112
113
114 PROUTE_CACHE_NODE SearchRouteCache(
115     PIP_ADDRESS Destination,
116     PROUTE_CACHE_NODE Node)
117 /*
118  * FUNCTION: Searches route cache for a RCN for a destination address
119  * ARGUMENTS:
120  *     Destination = Pointer to destination address (key)
121  *     Node        = Pointer to start route cache node
122  * NOTES:
123  *     This function must be called with the route cache lock held
124  * RETURNS:
125  *     Pointer to internal node if a matching node was found, or
126  *     external node where it should be if none was found
127  */
128 {
129     INT Value;
130
131     TI_DbgPrint(DEBUG_RCACHE, ("Called. Destination (0x%X)  Node (0x%X)\n", Destination, Node));
132
133     /* Is this an external node? */
134     if (IsExternalRCN(Node))
135         return Node;
136
137     /* Is it this node we are looking for? */
138     Value = AddrCompare(Destination, &Node->Destination);
139     if (Value == 0)
140         return Node;
141
142     /* Traverse down the left subtree if the key is smaller than
143        the key of the node, otherwise traverse the right subtree */
144     if (Value < 0) {
145         Node->Left->Parent = Node;
146         ExternalRCN->Left  = (PROUTE_CACHE_NODE)&Node->Left;
147         return SearchRouteCache(Destination, Node->Left);
148     } else {
149         Node->Right->Parent = Node;
150         ExternalRCN->Left   = (PROUTE_CACHE_NODE)&Node->Right;
151         return SearchRouteCache(Destination, Node->Right);
152     }
153 }
154
155
156 PROUTE_CACHE_NODE ExpandExternalRCN(VOID)
157 /*
158  * FUNCTION: Expands an external route cache node
159  * NOTES:
160  *     This function must be called with the route cache lock held.
161  *     We cheat a little here to save memory. We don't actually allocate memory
162  *     for external nodes. We wait until they're turned into internal nodes.
163  *     ExternalRCN->Parent must be initialized
164  *     ExternalRCN->Left must be a pointer to the correct child link of it's parent
165  * RETURNS:
166  *     Pointer to new internal node if the external node was expanded, NULL if not
167  */
168 {
169     PROUTE_CACHE_NODE RCN;
170
171     TI_DbgPrint(DEBUG_RCACHE, ("Called.\n"));
172
173     RCN = ExAllocateFromNPagedLookasideList(&IPRCNList);
174     if (!RCN) {
175         TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
176         return NULL;
177     }
178
179     RCN->Free = FreeRCN;
180
181     if (ExternalRCN->Left)
182         /* Register RCN as a child with it's parent */
183         *(PROUTE_CACHE_NODE*)ExternalRCN->Left = RCN;
184
185     RCN->Parent = ExternalRCN->Parent;
186     RCN->Left   = ExternalRCN;
187     RCN->Right  = ExternalRCN;
188
189     return RCN;
190 }
191
192 #if 0
193 VOID SwapRCN(
194     PROUTE_CACHE_NODE *Node1,
195     PROUTE_CACHE_NODE *Node2)
196 /*
197  * FUNCTION: Swaps two nodes
198  * ARGUMENTS:
199  *     Node1 = Address of pointer to first node
200  *     Node2 = Address of pointer to second node
201  */
202 {
203     PROUTE_CACHE_NODE Temp;
204
205     Temp  = *Node2;
206     *Node2 = *Node1;
207     *Node1 = Temp;
208 }
209 #endif
210
211 /*
212  * FUNCTION: Removes a route to a destination
213  * ARGUMENTS:
214  *     RCN = Pointer to route cache node to remove
215  * NOTES:
216  *     Internal version. Route cache lock must be held
217  */
218 VOID RemoveRouteToDestination(
219     PROUTE_CACHE_NODE RCN)
220 {
221     PROUTE_CACHE_NODE RemNode, Parent, SwapNode;
222
223     TI_DbgPrint(DEBUG_RCACHE, ("Called. RCN (0x%X).\n", RCN));
224
225     if (IsExternalRCN(RCN->Left)) {
226         /* Left node is external */
227         RemNode         = RCN->Left;
228         RemNode->Parent = RCN;
229     } else if (IsExternalRCN(RCN->Right)) {
230         /* Right node is external */
231         RemNode         = RCN->Right;
232         RemNode->Parent = RCN;
233     } else {
234         /* The node has internal children */
235
236         /* Normally we would replace the item of RCN with the item
237            of the leftmost external node on the right subtree of
238            RCN. This we cannot do here because there may be
239            references directly to that node. Instead we swap pointer
240            values (parent, left and right) of the two nodes */
241         RemNode = RCN->Right;
242         do {
243             Parent  = RemNode;
244             RemNode = RemNode->Left;
245         } while (IsInternalRCN(RemNode));
246         RemNode->Parent = Parent;
247
248         SwapNode = RemNode->Parent;
249 #if 0
250         if (RCN != RouteCache) {
251             /* Set SwapNode to be child of RCN's parent instead of RCN */
252             Parent = RCN->Parent;
253             if (RCN == Parent->Left)
254                 Parent->Left = SwapNode;
255             else
256                 Parent->Right = SwapNode;
257         } else
258             /* SwapNode is the new cache root */
259             RouteCache = SwapNode;
260
261         /* Set RCN to be child of SwapNode's parent instead of SwapNode */
262         Parent = SwapNode->Parent;
263         if (SwapNode == Parent->Left)
264             Parent->Left = RCN;
265         else
266             Parent->Right = RCN;
267
268         /* Swap parents */
269         SwapRCN(&SwapNode->Parent, &RCN->Parent);
270         /* Swap children */
271         SwapRCN(&SwapNode->Left, &RCN->Left);
272         SwapRCN(&SwapNode->Right, &RCN->Right);
273 #endif
274     }
275     
276     /* Dereference NTE and NCE */
277     DereferenceObject(RCN->NTE);
278     DereferenceObject(RCN->NCE);
279
280     ExternalRCN->Parent = RemNode->Parent;
281
282     RemoveAboveExternal();
283 }
284
285
286 VOID InvalidateNTEOnSubtree(
287     PNET_TABLE_ENTRY NTE,
288     PROUTE_CACHE_NODE Node)
289 /*
290  * FUNCTION: Removes all RCNs with references to an NTE on a subtree
291  * ARGUMENNTS:
292  *     NTE  = Pointer to NTE to invalidate
293  *     Node = Pointer to RCN to start removing nodes at
294  * NOTES:
295  *     This function must be called with the route cache lock held.
296  */
297 {
298     TI_DbgPrint(DEBUG_RCACHE, ("Called. NTE (0x%X)  Node (0x%X).\n", NTE, Node));
299
300     if (IsInternalRCN(Node)) {
301         /* Traverse left subtree */
302         InvalidateNTEOnSubtree(NTE, Node->Left);
303
304         /* Traverse right subtree */
305         InvalidateNTEOnSubtree(NTE, Node->Right);
306
307         /* Finally check the node itself */
308         if (Node->NTE == NTE)
309             RemoveRouteToDestination(Node);
310     }
311 }
312
313
314 VOID InvalidateNCEOnSubtree(
315     PNEIGHBOR_CACHE_ENTRY NCE,
316     PROUTE_CACHE_NODE Node)
317 /*
318  * FUNCTION: Removes all RCNs with references to an NCE on a subtree
319  * ARGUMENNTS:
320  *     NCE  = Pointer to NCE to invalidate
321  *     Node = Pointer to RCN to start removing nodes at
322  * NOTES:
323  *     This function must be called with the route cache lock held
324  */
325 {
326     TI_DbgPrint(DEBUG_RCACHE, ("Called. NCE (0x%X)  Node (0x%X).\n", NCE, Node));
327
328     if (IsInternalRCN(Node)) {
329         /* Traverse left subtree */
330         InvalidateNCEOnSubtree(NCE, Node->Left);
331
332         /* Traverse right subtree */
333         InvalidateNCEOnSubtree(NCE, Node->Right);
334
335         /* Finally check the node itself */
336         if (Node->NCE == NCE)
337             RemoveRouteToDestination(Node);
338     }
339 }
340
341
342 VOID RemoveSubtree(
343     PROUTE_CACHE_NODE Node)
344 /*
345  * FUNCTION: Removes a subtree from the tree using recursion
346  * ARGUMENNTS:
347  *     Node = Pointer to RCN to start removing nodes at
348  * NOTES:
349  *     This function must be called with the route cache lock held
350  */
351 {
352     TI_DbgPrint(DEBUG_RCACHE, ("Called. Node (0x%X).\n", Node));
353
354     if (IsInternalRCN(Node)) {
355         /* Traverse left subtree */
356         RemoveSubtree(Node->Left);
357
358         /* Traverse right subtree */
359         RemoveSubtree(Node->Right);
360
361         /* Finally remove the node itself */
362
363         /* It's an internal node, so dereference NTE and NCE */
364         DereferenceObject(Node->NTE);
365         DereferenceObject(Node->NCE);
366
367 #if DBG
368         if (Node->RefCount != 1)
369             TI_DbgPrint(MIN_TRACE, ("RCN at (0x%X) has (%d) references (should be 1).\n", Node, Node->RefCount));
370 #endif
371
372         /* Remove reference for being alive */
373         DereferenceObject(Node);
374     }
375 }
376
377
378 NTSTATUS RouteStartup(
379     VOID)
380 /*
381  * FUNCTION: Initializes the routing subsystem
382  * RETURNS:
383  *     Status of operation
384  */
385 {
386     TI_DbgPrint(DEBUG_RCACHE, ("Called.\n"));
387
388     ExInitializeNPagedLookasideList(
389       &IPRCNList,                     /* Lookaside list */
390             NULL,                           /* Allocate routine */
391             NULL,                           /* Free routine */
392             0,                              /* Flags */
393             sizeof(ROUTE_CACHE_NODE),       /* Size of each entry */
394             TAG('I','P','R','C'),           /* Tag */
395             0);                             /* Depth */
396
397     /* Initialize the pseudo external route cache node */
398     ExternalRCN = ExAllocateFromNPagedLookasideList(&IPRCNList);
399     if (!ExternalRCN) {
400         TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
401         return STATUS_INSUFFICIENT_RESOURCES;
402     }
403     INIT_TAG(ExternalRCN, TAG('R','C','N',' '));
404
405     ExternalRCN->Free   = FreeRCN;
406     ExternalRCN->Parent = NULL;
407     ExternalRCN->Left   = NULL;
408     ExternalRCN->Right  = NULL;
409
410     /* Initialize the route cache root */
411     RouteCache = ExternalRCN;
412
413     KeInitializeSpinLock(&RouteCacheLock);
414
415 #if 0
416     TI_DbgPrint(MIN_TRACE, ("Displaying tree.\n"));
417     PrintTree(RouteCache);
418 #endif
419     return STATUS_SUCCESS;
420 }
421
422
423 NTSTATUS RouteShutdown(
424     VOID)
425 /*
426  * FUNCTION: Shuts down the routing subsystem
427  * RETURNS:
428  *     Status of operation
429  */
430 {
431     KIRQL OldIrql;
432
433     TI_DbgPrint(DEBUG_RCACHE, ("Called.\n"));
434
435     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
436 #if 0
437     TI_DbgPrint(MIN_TRACE, ("Displaying tree.\n"));
438     PrintTree(RouteCache);
439 #endif
440     /* Clear route cache */
441     RemoveSubtree(RouteCache);
442
443     FreeRCN(ExternalRCN);
444
445     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
446
447     ExDeleteNPagedLookasideList(&IPRCNList);
448
449     return STATUS_SUCCESS;
450 }
451
452
453 UINT RouteGetRouteToDestination(
454     PIP_ADDRESS Destination,
455     PNET_TABLE_ENTRY NTE,
456     PROUTE_CACHE_NODE *RCN)
457 /*
458  * FUNCTION: Locates an RCN describing a route to a destination address
459  * ARGUMENTS:
460  *     Destination = Pointer to destination address to find route to
461  *     NTE         = Pointer to NTE describing net to send on
462  *                   (NULL means routing module choose NTE to send on)
463  *     RCN         = Address of pointer to an RCN
464  * RETURNS:
465  *     Status of operation
466  * NOTES:
467  *     The RCN is referenced for the caller. The caller is responsible
468  *     for dereferencing it after use
469  */
470 {
471     KIRQL OldIrql;
472     PROUTE_CACHE_NODE RCN2;
473     PNEIGHBOR_CACHE_ENTRY NCE;
474     PIP_INTERFACE Interface;
475
476     TI_DbgPrint(DEBUG_RCACHE, ("Called. Destination (0x%X)  NTE (0x%X).\n",
477         Destination, NTE));
478
479     TI_DbgPrint(DEBUG_RCACHE, ("Destination (%s)  NTE (%s).\n",
480         A2S(Destination), A2S(NTE->Address)));
481
482     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
483
484 #if 0
485     TI_DbgPrint(MIN_TRACE, ("Displaying tree (before).\n"));
486     PrintTree(RouteCache);
487 #endif
488
489     ExternalRCN->Left = NULL;
490     RCN2 = SearchRouteCache(Destination, RouteCache);
491     if (IsExternalRCN(RCN2)) {
492         /* No route was found in the cache */
493
494         /* Check if the destination is on-link */
495         Interface = RouterFindOnLinkInterface(Destination, NTE);
496         if (Interface) {
497             if (!NTE) {
498                 NTE = RouterFindBestNTE(Interface, Destination);
499                 if (!NTE) {
500                     /* We cannot get to the specified destination. Return error */
501                     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
502                     return IP_NO_ROUTE_TO_DESTINATION;
503                 }
504             } else
505                 ReferenceObject(NTE);
506
507             /* The destination address is on-link. Check our neighbor cache */
508             NCE = NBFindOrCreateNeighbor(Interface, Destination);
509             if (!NCE) {
510                 DereferenceObject(NTE);
511                 KeReleaseSpinLock(&RouteCacheLock, OldIrql);
512                 return IP_NO_RESOURCES;
513             }
514         } else {
515             /* Destination is not on any subnets we're on. Find a router to use */
516             NCE = RouterGetRoute(Destination, NTE);
517             if (!NCE) {
518                 /* We cannot get to the specified destination. Return error */
519                 KeReleaseSpinLock(&RouteCacheLock, OldIrql);
520                 return IP_NO_ROUTE_TO_DESTINATION;
521             }
522         }
523
524         /* Add the new route to the route cache */
525         if (RCN2 == RouteCache) {
526             RCN2       = ExpandExternalRCN();
527             RouteCache = RCN2;
528         } else
529             RCN2 = ExpandExternalRCN();
530         if (!RCN2) {
531             DereferenceObject(NTE);
532             DereferenceObject(NCE);
533             KeReleaseSpinLock(&RouteCacheLock, OldIrql);
534             return IP_NO_RESOURCES;
535         }
536
537         RCN2->RefCount    = 1;
538         RCN2->State       = RCN_STATE_COMPUTED;
539         RCN2->NTE         = NTE;
540         RtlCopyMemory(&RCN2->Destination, Destination, sizeof(IP_ADDRESS));
541         RCN2->PathMTU     = NCE->Interface->MTU;
542         RCN2->NCE         = NCE;
543
544         /* The route cache node references the NTE and the NCE. The
545            NTE was referenced before and NCE is already referenced by
546            RouteGetRoute() or NBFindOrCreateNeighbor() so we don't
547            reference them here */
548     }
549
550     /* Reference the RCN for the user */
551     ReferenceObject(RCN2);
552
553 #if 0
554     TI_DbgPrint(MIN_TRACE, ("Displaying tree (after).\n"));
555     PrintTree(RouteCache);
556 #endif
557
558     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
559
560     *RCN = RCN2;
561
562     return IP_SUCCESS;
563 }
564
565
566 PROUTE_CACHE_NODE RouteAddRouteToDestination(
567     PIP_ADDRESS Destination,
568     PNET_TABLE_ENTRY NTE,
569     PIP_INTERFACE IF,
570     PNEIGHBOR_CACHE_ENTRY NCE)
571 /*
572  * FUNCTION: Adds a (permanent) route to a destination
573  * ARGUMENTS:
574  *     Destination = Pointer to destination address
575  *     NTE         = Pointer to net table entry
576  *     IF          = Pointer to interface to use
577  *     NCE         = Pointer to first hop to destination
578  * RETURNS:
579  *     Pointer to RCN if the route was added, NULL if not.
580  *     There can be at most one RCN per destination address / interface pair
581  */
582 {
583     KIRQL OldIrql;
584     PROUTE_CACHE_NODE RCN;
585
586     TI_DbgPrint(DEBUG_RCACHE, ("Called. Destination (0x%X)  NTE (0x%X)  IF (0x%X)  NCE (0x%X).\n",
587         Destination, NTE, IF, NCE));
588
589     TI_DbgPrint(DEBUG_RCACHE, ("Destination (%s)  NTE (%s)  NCE (%s).\n",
590         A2S(Destination), A2S(NTE->Address), A2S(NCE->Address)));
591
592     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
593
594     /* Locate an external RCN we can expand */
595     RCN = RouteCache;
596     ExternalRCN->Left = NULL;
597     for (;;) {
598         RCN = SearchRouteCache(Destination, RCN);
599         if (IsInternalRCN(RCN)) {
600             ExternalRCN->Left = (PROUTE_CACHE_NODE)&RCN->Right;
601             /* This is an internal node, continue the search to the right */
602             RCN = RCN->Right;
603         } else
604             /* This is an external node, we've found an empty spot */
605             break;
606     }
607
608     /* Expand the external node */
609     if (RCN == RouteCache) {
610         RCN = ExpandExternalRCN();
611         RouteCache = RCN;
612     } else
613         RCN = ExpandExternalRCN();
614     if (!RCN) {
615         KeReleaseSpinLock(&RouteCacheLock, OldIrql);
616         return NULL;
617     }
618
619     /* Initialize the newly created internal node */
620
621     INIT_TAG(RCN, TAG('R','C','N',' '));
622
623     /* Reference once for beeing alive */
624     RCN->RefCount    = 1;
625     RCN->State       = RCN_STATE_PERMANENT;
626     RCN->NTE         = NTE;
627     RtlCopyMemory(&RCN->Destination, Destination, sizeof(IP_ADDRESS));
628     RCN->PathMTU     = IF->MTU;
629     RCN->NCE         = NCE;
630
631     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
632
633     /* The route cache node references the NTE and the NCE */
634     ReferenceObject(NTE);
635     if (NCE)
636         ReferenceObject(NCE);
637
638 #if 0
639     TI_DbgPrint(MIN_TRACE, ("Displaying tree.\n"));
640     PrintTree(RouteCache);
641 #endif
642
643     return RCN;
644 }
645
646
647 VOID RouteRemoveRouteToDestination(
648     PROUTE_CACHE_NODE RCN)
649 /*
650  * FUNCTION: Removes a route to a destination
651  * ARGUMENTS:
652  *     RCN = Pointer to route cache node to remove
653  */
654 {
655     KIRQL OldIrql;
656  
657     TI_DbgPrint(DEBUG_RCACHE, ("Called. RCN (0x%X).\n", RCN));
658
659     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
660
661     RemoveRouteToDestination(RCN);
662
663     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
664 }
665
666
667 VOID RouteInvalidateNTE(
668     PNET_TABLE_ENTRY NTE)
669 /*
670  * FUNCTION: Removes all RCNs with references to an NTE
671  * ARGUMENTS:
672  *     NTE = Pointer to net table entry to invalidate
673  */
674 {
675     KIRQL OldIrql;
676  
677     TI_DbgPrint(DEBUG_RCACHE, ("Called. NTE (0x%X).\n", NTE));
678
679     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
680     InvalidateNTEOnSubtree(NTE, RouteCache);
681     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
682 }
683
684
685 VOID RouteInvalidateNCE(
686     PNEIGHBOR_CACHE_ENTRY NCE)
687 /*
688  * FUNCTION: Removes all RCNs with references to an NCE
689  * ARGUMENTS:
690  *     NCE = Pointer to neighbor cache entry to invalidate
691  */
692 {
693     KIRQL OldIrql;
694  
695     TI_DbgPrint(DEBUG_RCACHE, ("Called. NCE (0x%X).\n", NCE));
696
697     KeAcquireSpinLock(&RouteCacheLock, &OldIrql);
698     InvalidateNCEOnSubtree(NCE, RouteCache);
699     KeReleaseSpinLock(&RouteCacheLock, OldIrql);
700 }
701
702 /* EOF */