Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

707 lines
20 KiB

  1. // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
  2. //
  3. // Copyright (c) 1985-2000 Microsoft Corporation
  4. //
  5. // This file is part of the Microsoft Research IPv6 Network Protocol Stack.
  6. // You should have received a copy of the Microsoft End-User License Agreement
  7. // for this software along with this release; see the file "license.txt".
  8. // If not, please see http://www.research.microsoft.com/msripv6/license.htm,
  9. // or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
  10. //
  11. // Abstract:
  12. //
  13. // Multicast Listener Discovery for Internet Protocol Version 6.
  14. // See draft-ietf-ipngwg-mld-00.txt for details.
  15. //
  16. #include "oscfg.h"
  17. #include "ndis.h"
  18. #include "ip6imp.h"
  19. #include "ip6def.h"
  20. #include "icmp.h"
  21. #include "mld.h"
  22. #include "ntddip6.h"
  23. #include "route.h"
  24. #include "alloca.h"
  25. #include "info.h"
  26. //
  27. // The QueryListLock may be taken while holding an Interface lock.
  28. //
  29. KSPIN_LOCK QueryListLock;
  30. MulticastAddressEntry *QueryList;
  31. //* AddToQueryList
  32. //
  33. // Add an MAE to the front of the QueryList.
  34. // The caller should already have the QueryList and the IF locked.
  35. //
  36. void
  37. AddToQueryList(MulticastAddressEntry *MAE)
  38. {
  39. MAE->NextQL = QueryList;
  40. QueryList = MAE;
  41. }
  42. //* RemoveFromQueryList
  43. //
  44. // Remove an MAE from the QueryList.
  45. // The caller should already have the QueryList and the IF locked.
  46. //
  47. void
  48. RemoveFromQueryList(MulticastAddressEntry *MAE)
  49. {
  50. MulticastAddressEntry **PrevMAE, *ThisMAE;
  51. for (PrevMAE = &QueryList; ; PrevMAE = &ThisMAE->NextQL) {
  52. ThisMAE = *PrevMAE;
  53. ASSERT(ThisMAE != NULL);
  54. if (ThisMAE == MAE) {
  55. //
  56. // Remove the entry.
  57. //
  58. *PrevMAE = ThisMAE->NextQL;
  59. break;
  60. }
  61. }
  62. }
  63. //* MLDQueryReceive - Process the receipt of a Group Query MLD message.
  64. //
  65. // Queries for a specific group should be sent to the group address
  66. // in question. General queries are sent to the all nodes address, and
  67. // have the group address set to zero.
  68. // Here we need to add the group to the list of groups waiting to send
  69. // membership reports. Then set the timer value in the ADE entry to a
  70. // random value determines by the incoming query.
  71. //
  72. void
  73. MLDQueryReceive(IPv6Packet *Packet)
  74. {
  75. Interface *IF = Packet->NTEorIF->IF;
  76. MLDMessage *Message;
  77. MulticastAddressEntry *MAE;
  78. uint MaxResponseDelay;
  79. //
  80. // Verify that the packet has a link-local source address.
  81. //
  82. if (!IsLinkLocal(AlignAddr(&Packet->IP->Source))) {
  83. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
  84. "MLDQueryReceive: non-link-local source\n"));
  85. return;
  86. }
  87. //
  88. // Verify that we have enough contiguous data to overlay a MLDMessage
  89. // structure on the incoming packet. Then do so.
  90. //
  91. if (! PacketPullup(Packet, sizeof(MLDMessage),
  92. __builtin_alignof(MLDMessage), 0)) {
  93. // Pullup failed.
  94. if (Packet->TotalSize < sizeof(MLDMessage))
  95. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
  96. "MLDQueryReceive: too small to contain MLD message\n"));
  97. return;
  98. }
  99. Message = (MLDMessage *)Packet->Data;
  100. //
  101. // Get the maximum response value from the received MLD message.
  102. //
  103. MaxResponseDelay = net_short(Message->MaxResponseDelay); // Milliseconds.
  104. MaxResponseDelay = ConvertMillisToTicks(MaxResponseDelay);
  105. KeAcquireSpinLockAtDpcLevel(&IF->Lock);
  106. //
  107. // Loop through the ADE list and update the timer for the desired
  108. // groups. Note that a general query uses the unspecified address, and
  109. // sets the timer for all groups.
  110. //
  111. for (MAE = (MulticastAddressEntry *)IF->ADE;
  112. MAE != NULL;
  113. MAE = (MulticastAddressEntry *)MAE->Next) {
  114. if ((MAE->Type == ADE_MULTICAST) &&
  115. (MAE->MCastFlags & MAE_REPORTABLE) &&
  116. (IP6_ADDR_EQUAL(AlignAddr(&Message->GroupAddr),
  117. &UnspecifiedAddr) ||
  118. IP6_ADDR_EQUAL(AlignAddr(&Message->GroupAddr),
  119. &MAE->Address))) {
  120. //
  121. // If the timer is currently off or if the maximum requested
  122. // response delay is less than the current timer value, draw a
  123. // random value on the interval(0, MaxResponseDelay) and update
  124. // the timer to reflect this value.
  125. //
  126. KeAcquireSpinLockAtDpcLevel(&QueryListLock);
  127. //
  128. // Add this MAE to the QueryList, if not already present.
  129. //
  130. if (MAE->MCastTimer == 0) {
  131. AddToQueryList(MAE);
  132. goto UpdateTimerValue;
  133. }
  134. if (MaxResponseDelay <= MAE->MCastTimer) {
  135. UpdateTimerValue:
  136. //
  137. // Update the timer value.
  138. //
  139. if (MaxResponseDelay == 0)
  140. MAE->MCastTimer = 0;
  141. else
  142. MAE->MCastTimer = (ushort)
  143. RandomNumber(0, MaxResponseDelay);
  144. //
  145. // We add 1 because MLDTimeout predecrements.
  146. // We must maintain the invariant that ADEs on
  147. // the query list have a non-zero timer value.
  148. //
  149. MAE->MCastTimer += 1;
  150. }
  151. KeReleaseSpinLockFromDpcLevel(&QueryListLock);
  152. }
  153. }
  154. KeReleaseSpinLockFromDpcLevel(&IF->Lock);
  155. }
  156. //* MLDReportReceive - Process the receipt of a Group Report MLD message.
  157. //
  158. // When another host on the local link sends a group report, we receive
  159. // a copy if we also belong to the group. If we have a timer running for
  160. // this group, we can turn it off now.
  161. //
  162. // Callable from DPC context, not from thread context.
  163. //
  164. void
  165. MLDReportReceive(IPv6Packet *Packet)
  166. {
  167. Interface *IF = Packet->NTEorIF->IF;
  168. MLDMessage *Message;
  169. MulticastAddressEntry *MAE;
  170. //
  171. // Verify that the packet has a link-local source address.
  172. // An unspecified source address can also happen during initialization.
  173. //
  174. if (!(IsLinkLocal(AlignAddr(&Packet->IP->Source)) ||
  175. IsUnspecified(AlignAddr(&Packet->IP->Source)))) {
  176. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
  177. "MLDReportReceive: non-link-local source\n"));
  178. return;
  179. }
  180. //
  181. // Verify that we have enough contiguous data to overlay a MLDMessage
  182. // structure on the incoming packet. Then do so.
  183. //
  184. if (! PacketPullup(Packet, sizeof(MLDMessage),
  185. __builtin_alignof(MLDMessage), 0)) {
  186. // Pullup failed.
  187. if (Packet->TotalSize < sizeof(MLDMessage))
  188. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
  189. "MLDReportReceive: too small to contain MLD message\n"));
  190. return;
  191. }
  192. Message = (MLDMessage *)Packet->Data;
  193. KeAcquireSpinLockAtDpcLevel(&IF->Lock);
  194. //
  195. // Search for the MAE for this group address.
  196. //
  197. MAE = (MulticastAddressEntry *)
  198. *FindADE(IF, AlignAddr(&Message->GroupAddr));
  199. if ((MAE != NULL) && (MAE->Type == ADE_MULTICAST)) {
  200. KeAcquireSpinLockAtDpcLevel(&QueryListLock);
  201. //
  202. // We ignore the report unless
  203. // we are in the "Delaying Listener" state.
  204. //
  205. if (MAE->MCastTimer != 0) {
  206. //
  207. // Stop our timer and clear the last-reporter flag.
  208. // Note that we only clear the last-reporter flag
  209. // if our timer is running, as called for in the spec.
  210. // Although it would make sense to clear the flag
  211. // when we do not have a timer running.
  212. //
  213. MAE->MCastTimer = 0;
  214. MAE->MCastFlags &= ~MAE_LAST_REPORTER;
  215. RemoveFromQueryList(MAE);
  216. }
  217. KeReleaseSpinLockFromDpcLevel(&QueryListLock);
  218. }
  219. KeReleaseSpinLockFromDpcLevel(&IF->Lock);
  220. }
  221. //* MLDMessageSend
  222. //
  223. // Primitive function for sending MLD messages.
  224. //
  225. // Note that we can not use RouteToDestination to get an RCE.
  226. // There might be no valid source addresses on the sending interface.
  227. // We could use IPv6SendND, but it doesn't make sense because
  228. // we can't pass in a valid DiscoveryAddress. And it's not needed.
  229. //
  230. void
  231. MLDMessageSend(
  232. Interface *IF,
  233. const IPv6Addr *GroupAddr,
  234. const IPv6Addr *Dest,
  235. uchar Type)
  236. {
  237. PNDIS_PACKET Packet;
  238. IPv6Header UNALIGNED *IP;
  239. ICMPv6Header UNALIGNED *ICMP;
  240. MLDMessage UNALIGNED *MLD;
  241. MLDRouterAlertOption UNALIGNED *RA;
  242. uint Offset;
  243. uint PayloadLength;
  244. uint MemLen;
  245. uchar *Mem;
  246. void *LLDest;
  247. IP_STATUS Status;
  248. NDIS_STATUS NdisStatus;
  249. ICMPv6OutStats.icmps_msgs++;
  250. ASSERT(IsMulticast(Dest));
  251. //
  252. // Calculate the packet size.
  253. //
  254. Offset = IF->LinkHeaderSize;
  255. PayloadLength = sizeof(MLDRouterAlertOption) + sizeof(ICMPv6Header)
  256. + sizeof(MLDMessage);
  257. MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
  258. //
  259. // Allocate the packet.
  260. //
  261. Status = IPv6AllocatePacket(MemLen, &Packet, &Mem);
  262. if (Status != NDIS_STATUS_SUCCESS) {
  263. ICMPv6OutStats.icmps_errors++;
  264. return;
  265. }
  266. //
  267. // Prepare the IP header.
  268. //
  269. IP = (IPv6Header UNALIGNED *)(Mem + Offset);
  270. IP->VersClassFlow = IP_VERSION;
  271. IP->PayloadLength = net_short((ushort)PayloadLength);
  272. IP->NextHeader = IP_PROTOCOL_HOP_BY_HOP;
  273. IP->HopLimit = 1;
  274. IP->Dest = *Dest;
  275. //
  276. // This will give us the unspecified address
  277. // if our link-local address is not valid.
  278. // (For example if it is still tentative pending DAD.)
  279. //
  280. (void) GetLinkLocalAddress(IF, AlignAddr(&IP->Source));
  281. //
  282. // Prepare the router alert option.
  283. //
  284. RA = (MLDRouterAlertOption UNALIGNED *)(IP + 1);
  285. RA->Header.NextHeader = IP_PROTOCOL_ICMPv6;
  286. RA->Header.HeaderExtLength = 0;
  287. RA->Option.Type = OPT6_ROUTER_ALERT;
  288. RA->Option.Length = 2;
  289. RA->Option.Value = MLD_ROUTER_ALERT_OPTION_TYPE;
  290. RA->Pad.Type = 1;
  291. RA->Pad.DataLength = 0;
  292. //
  293. // Prepare the ICMP header.
  294. //
  295. ICMP = (ICMPv6Header UNALIGNED *)(RA + 1);
  296. ICMP->Type = Type;
  297. ICMP->Code = 0;
  298. ICMP->Checksum = 0; // Calculated below.
  299. //
  300. // Prepare the MLD message.
  301. //
  302. MLD = (MLDMessage UNALIGNED *)(ICMP + 1);
  303. MLD->MaxResponseDelay = 0;
  304. MLD->Unused = 0;
  305. MLD->GroupAddr = *GroupAddr;
  306. //
  307. // Calculate the ICMP checksum.
  308. //
  309. ICMP->Checksum = ChecksumPacket(Packet,
  310. Offset + sizeof(IPv6Header) + sizeof(MLDRouterAlertOption),
  311. NULL,
  312. sizeof(ICMPv6Header) + sizeof(MLDMessage),
  313. AlignAddr(&IP->Source), AlignAddr(&IP->Dest),
  314. IP_PROTOCOL_ICMPv6);
  315. //
  316. // Convert the IP-level multicast destination address
  317. // to a link-layer multicast address.
  318. //
  319. LLDest = alloca(IF->LinkAddressLength);
  320. (*IF->ConvertAddr)(IF->LinkContext, Dest, LLDest);
  321. PC(Packet)->Flags = NDIS_FLAGS_MULTICAST_PACKET | NDIS_FLAGS_DONT_LOOPBACK;
  322. //
  323. // Transmit the packet.
  324. //
  325. ICMPv6OutStats.icmps_typecount[Type]++;
  326. IPv6SendLL(IF, Packet, Offset, LLDest);
  327. }
  328. //* MLDReportSend - Send an MLD membership report.
  329. //
  330. // This function is called either when a host first joins a multicast group or
  331. // at some point after a membership query message was received, and the timer
  332. // for this host has expired.
  333. //
  334. void
  335. MLDReportSend(Interface *IF, const IPv6Addr *GroupAddr)
  336. {
  337. MLDMessageSend(IF, GroupAddr, GroupAddr,
  338. ICMPv6_MULTICAST_LISTENER_REPORT);
  339. }
  340. //* MLDDoneSend - Send an MLD done message.
  341. //
  342. // This function is called when a host quits a multicast group AND this was
  343. // the last host on the local link to report interest in the group. A host
  344. // quits when either the upper layer explicitly quits or when the interface
  345. // is deleted.
  346. //
  347. void
  348. MLDDoneSend(Interface *IF, const IPv6Addr *GroupAddr)
  349. {
  350. MLDMessageSend(IF, GroupAddr, &AllRoutersOnLinkAddr,
  351. ICMPv6_MULTICAST_LISTENER_DONE);
  352. }
  353. //* MLDAddMCastAddr - Add a multicast group to the specified interface.
  354. //
  355. // This function is called when a user level program has asked to join a
  356. // multicast group.
  357. //
  358. // The Interface number can be supplied as zero,
  359. // in which we try to pick a reasonable interface
  360. // and then return the interface number that we picked.
  361. //
  362. // Callable from thread context, not from DPC context.
  363. // Called with no locks held.
  364. //
  365. IP_STATUS
  366. MLDAddMCastAddr(uint *pInterfaceNo, const IPv6Addr *Addr)
  367. {
  368. uint InterfaceNo = *pInterfaceNo;
  369. Interface *IF;
  370. MulticastAddressEntry *MAE;
  371. IP_STATUS status;
  372. KIRQL OldIrql;
  373. if (!IsMulticast(Addr)) {
  374. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR,
  375. "MLDAddMCastAddr: Not mcast addr\n"));
  376. return IP_PARAMETER_PROBLEM;
  377. }
  378. if (InterfaceNo == 0) {
  379. RouteCacheEntry *RCE;
  380. //
  381. // We must pick an interface to use for this multicast address.
  382. // Look for a multicast route in the routing table.
  383. //
  384. status = RouteToDestination(Addr, 0, NULL, RTD_FLAG_NORMAL, &RCE);
  385. if (status != IP_SUCCESS) {
  386. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
  387. "MLDAddMCastAddr - no route\n"));
  388. return status;
  389. }
  390. //
  391. // Use the interface associated with the RCE.
  392. //
  393. IF = RCE->NTE->IF;
  394. *pInterfaceNo = IF->Index;
  395. AddRefIF(IF);
  396. ReleaseRCE(RCE);
  397. }
  398. else {
  399. //
  400. // Use the interface requested by the application.
  401. //
  402. IF = FindInterfaceFromIndex(InterfaceNo);
  403. if (IF == NULL)
  404. return IP_PARAMETER_PROBLEM;
  405. }
  406. //
  407. // Will this interface support multicast addresses?
  408. //
  409. if (!(IF->Flags & IF_FLAG_MULTICAST)) {
  410. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR,
  411. "MLDAddMCastAddr: IF cannot add a mcast addr\n"));
  412. ReleaseIF(IF);
  413. return IP_PARAMETER_PROBLEM;
  414. }
  415. //
  416. // The real work is all in FindOrCreateMAE.
  417. //
  418. KeAcquireSpinLock(&IF->Lock, &OldIrql);
  419. MAE = FindOrCreateMAE(IF, Addr, NULL);
  420. if (IsMCastSyncNeeded(IF))
  421. DeferSynchronizeMulticastAddresses(IF);
  422. KeReleaseSpinLock(&IF->Lock, OldIrql);
  423. ReleaseIF(IF);
  424. return (MAE == NULL) ? IP_NO_RESOURCES : IP_SUCCESS;
  425. }
  426. //* MLDDropMCastAddr - remove a multicast address from an interface.
  427. //
  428. // This function is called when a user has indicated that they are no
  429. // longer interested in a multicast group.
  430. //
  431. // Callable from thread context, not from DPC context.
  432. // Called with no locks held.
  433. //
  434. IP_STATUS
  435. MLDDropMCastAddr(uint InterfaceNo, const IPv6Addr *Addr)
  436. {
  437. Interface *IF;
  438. MulticastAddressEntry *MAE;
  439. IP_STATUS status;
  440. KIRQL OldIrql;
  441. //
  442. // Unlike MLDAddMCastAddr, no need to check
  443. // if the address is multicast. If it is not,
  444. // FindAndReleaseMAE will fail to find it.
  445. //
  446. if (InterfaceNo == 0) {
  447. RouteCacheEntry *RCE;
  448. //
  449. // We must pick an interface to use for this multicast address.
  450. // Look for a multicast route in the routing table.
  451. //
  452. status = RouteToDestination(Addr, 0, NULL, RTD_FLAG_NORMAL, &RCE);
  453. if (status != IP_SUCCESS) {
  454. KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
  455. "MLDDropMCastAddr - no route\n"));
  456. return status;
  457. }
  458. //
  459. // Use the interface associated with the RCE.
  460. //
  461. IF = RCE->NTE->IF;
  462. AddRefIF(IF);
  463. ReleaseRCE(RCE);
  464. }
  465. else {
  466. //
  467. // Use the interface requested by the application.
  468. //
  469. IF = FindInterfaceFromIndex(InterfaceNo);
  470. if (IF == NULL)
  471. return IP_PARAMETER_PROBLEM;
  472. }
  473. //
  474. // Unlike MLDAddMCastAddr, no need to check IF_FLAG_MULTICAST.
  475. // If the interface does not support multicast addresses,
  476. // FindAndReleaseMAE will fail to find the address.
  477. //
  478. //
  479. // All the real work is in FindAndReleaseMAE.
  480. //
  481. KeAcquireSpinLock(&IF->Lock, &OldIrql);
  482. MAE = FindAndReleaseMAE(IF, Addr);
  483. if (IsMCastSyncNeeded(IF))
  484. DeferSynchronizeMulticastAddresses(IF);
  485. KeReleaseSpinLock(&IF->Lock, OldIrql);
  486. ReleaseIF(IF);
  487. return (MAE == NULL) ? IP_PARAMETER_PROBLEM : IP_SUCCESS;
  488. }
  489. //* MLDTimeout - Handle MLD timer events.
  490. //
  491. // This function is called periodically by IPv6Timeout.
  492. // We decrement the timer value in each MAE on the query list.
  493. // If the timer reaches zero, we send a group membership report.
  494. //
  495. void
  496. MLDTimeout(void)
  497. {
  498. typedef struct MLDReportRequest {
  499. struct MLDReportRequest *Next;
  500. Interface *IF;
  501. IPv6Addr GroupAddr;
  502. } MLDReportRequest;
  503. MulticastAddressEntry **PrevMAE, *MAE;
  504. MLDReportRequest *ReportList = NULL;
  505. MLDReportRequest *Request;
  506. MulticastAddressEntry *DoneList = NULL;
  507. //
  508. // Lock the QueryList so we can traverse it and decrement timers.
  509. // But we avoid sending messages while holding any locks
  510. // by building a list of requested reports.
  511. //
  512. KeAcquireSpinLockAtDpcLevel(&QueryListLock);
  513. PrevMAE = &QueryList;
  514. while ((MAE = *PrevMAE) != NULL) {
  515. ASSERT(MAE->Type == ADE_MULTICAST);
  516. if (MAE->MCastTimer == 0) {
  517. //
  518. // We need to send a Done message.
  519. // Remove this MAE from the QueryList
  520. // and put it on a temporary list.
  521. //
  522. *PrevMAE = MAE->NextQL;
  523. MAE->NextQL = DoneList;
  524. DoneList = MAE;
  525. continue;
  526. }
  527. else if (--MAE->MCastTimer == 0) {
  528. //
  529. // This entry has expired, we need to send a Report.
  530. //
  531. Request = ExAllocatePool(NonPagedPool, sizeof *Request);
  532. if (Request != NULL) {
  533. Request->Next = ReportList;
  534. ReportList = Request;
  535. Request->IF = MAE->NTEorIF->IF;
  536. Request->GroupAddr = MAE->Address;
  537. //
  538. // Set the flag indicating we sent the last report
  539. // on the link.
  540. //
  541. MAE->MCastFlags |= MAE_LAST_REPORTER;
  542. }
  543. if (MAE->MCastCount != 0) {
  544. if (MAE->NTEorIF->IF->Flags & IF_FLAG_PERIODICMLD) {
  545. //
  546. // On tunnels to 6to4 relays, we continue to generate
  547. // periodic reports since queries cannot be sent over
  548. // an NBMA interface.
  549. //
  550. MAE->MCastTimer = MLD_QUERY_INTERVAL;
  551. }
  552. else {
  553. //
  554. // If we are sending unsolicited reports,
  555. // then leave the MAE on the query list
  556. // and set a new timer value.
  557. //
  558. if (--MAE->MCastCount == 0)
  559. goto Remove;
  560. MAE->MCastTimer =
  561. RandomNumber(0, MLD_UNSOLICITED_REPORT_INTERVAL) + 1;
  562. }
  563. }
  564. else {
  565. Remove:
  566. //
  567. // Remove the MAE from the query list.
  568. //
  569. *PrevMAE = MAE->NextQL;
  570. continue;
  571. }
  572. }
  573. //
  574. // Go on to the next MAE.
  575. //
  576. PrevMAE = &MAE->NextQL;
  577. }
  578. KeReleaseSpinLockFromDpcLevel(&QueryListLock);
  579. //
  580. // Send MLD Report messages.
  581. //
  582. while ((Request = ReportList) != NULL) {
  583. ReportList = Request->Next;
  584. //
  585. // Send the MLD Report message.
  586. //
  587. MLDReportSend(Request->IF, &Request->GroupAddr);
  588. //
  589. // Free this structure.
  590. //
  591. ExFreePool(Request);
  592. }
  593. //
  594. // Send MLD Done messages.
  595. //
  596. while ((MAE = DoneList) != NULL) {
  597. Interface *IF = MAE->IF;
  598. DoneList = MAE->NextQL;
  599. //
  600. // Send the MLD Done message.
  601. //
  602. MLDDoneSend(IF, &MAE->Address);
  603. //
  604. // Free this structure.
  605. //
  606. ExFreePool(MAE);
  607. ReleaseIF(IF);
  608. }
  609. }
  610. //* MLDInit - Initialize MLD.
  611. //
  612. // Initialize MLD global data structures.
  613. //
  614. void
  615. MLDInit(void)
  616. {
  617. KeInitializeSpinLock(&QueryListLock);
  618. QueryList = NULL;
  619. }