text: // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
  //
  // Copyright (c) 1985-2000 Microsoft Corporation
  //
  // This file is part of the Microsoft Research IPv6 Network Protocol Stack.
  // You should have received a copy of the Microsoft End-User License Agreement
  // for this software along with this release; see the file "license.txt".
  // If not, please see http://www.research.microsoft.com/msripv6/license.htm,
  // or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
  //
  // Abstract:
  //
  // Multicast Listener Discovery for Internet Protocol Version 6.
  // See draft-ietf-ipngwg-mld-00.txt for details.
  //
  
  
  #include "oscfg.h"
  #include "ndis.h"
  #include "ip6imp.h"
  #include "ip6def.h"
  #include "icmp.h"
  #include "mld.h"
  #include "ntddip6.h"
  #include "route.h"
  #include "alloca.h"
  #include "info.h"
  
  
  //
  // The QueryListLock may be taken while holding an Interface lock.
  //
  KSPIN_LOCK QueryListLock;
  MulticastAddressEntry *QueryList;
  
  
  //* AddToQueryList
  //
  //  Add an MAE to the front of the QueryList. 
  //  The caller should already have the QueryList and the IF locked.
  //
  void
  AddToQueryList(MulticastAddressEntry *MAE)
  {
      MAE->NextQL = QueryList;
      QueryList = MAE;
  }
  
  
  //* RemoveFromQueryList
  //
  //  Remove an MAE from the QueryList.
  //  The caller should already have the QueryList and the IF locked.
  //
  void
  RemoveFromQueryList(MulticastAddressEntry *MAE)
  {
      MulticastAddressEntry **PrevMAE, *ThisMAE;
  
      for (PrevMAE = &QueryList; ; PrevMAE = &ThisMAE->NextQL) {
          ThisMAE = *PrevMAE;
          ASSERT(ThisMAE != NULL);
  
          if (ThisMAE == MAE) {
              //
              // Remove the entry.
              //
              *PrevMAE = ThisMAE->NextQL;
              break;
          }
      }
  }
  
  
  //* MLDQueryReceive - Process the receipt of a Group Query MLD message. 
  //
  //  Queries for a specific group should be sent to the group address
  //  in question.  General queries are sent to the all nodes address, and 
  //  have the group address set to zero.
  //  Here we need to add the group to the list of groups waiting to send 
  //  membership reports.  Then set the timer value in the ADE entry to a
  //  random value determines by the incoming query.
  //
  void
  MLDQueryReceive(IPv6Packet *Packet)
  {
      Interface *IF = Packet->NTEorIF->IF;
      MLDMessage *Message;
      MulticastAddressEntry *MAE;
      uint MaxResponseDelay;
  
      //
      // Verify that the packet has a link-local source address.
      //
      if (!IsLinkLocal(AlignAddr(&Packet->IP->Source))) {
          KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
                     "MLDQueryReceive: non-link-local source\n"));
          return;
      }
  
      //
      // Verify that we have enough contiguous data to overlay a MLDMessage
      // structure on the incoming packet.  Then do so.
      //
      if (! PacketPullup(Packet, sizeof(MLDMessage),
                         __builtin_alignof(MLDMessage), 0)) {
          // Pullup failed.
          if (Packet->TotalSize < sizeof(MLDMessage))
              KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
                         "MLDQueryReceive: too small to contain MLD message\n"));
          return;
      }
      Message = (MLDMessage *)Packet->Data;
  
      //
      // Get the maximum response value from the received MLD message.
      //
      MaxResponseDelay = net_short(Message->MaxResponseDelay);  // Milliseconds.
      MaxResponseDelay = ConvertMillisToTicks(MaxResponseDelay);
  
      KeAcquireSpinLockAtDpcLevel(&IF->Lock);
  
      //
      // Loop through the ADE list and update the timer for the desired
      // groups.  Note that a general query uses the unspecified address, and
      // sets the timer for all groups.
      //
      for (MAE = (MulticastAddressEntry *)IF->ADE;
           MAE != NULL;
           MAE = (MulticastAddressEntry *)MAE->Next) {
  
          if ((MAE->Type == ADE_MULTICAST) &&
              (MAE->MCastFlags & MAE_REPORTABLE) &&
              (IP6_ADDR_EQUAL(AlignAddr(&Message->GroupAddr),
                              &UnspecifiedAddr) ||
               IP6_ADDR_EQUAL(AlignAddr(&Message->GroupAddr),
                              &MAE->Address))) {
  
              //
              // If the timer is currently off or if the maximum requested
              // response delay is less than the current timer value, draw a
              // random value on the interval(0, MaxResponseDelay) and update
              // the timer to reflect this value.
              //
              KeAcquireSpinLockAtDpcLevel(&QueryListLock);
  
              //
              // Add this MAE to the QueryList, if not already present.
              //
              if (MAE->MCastTimer == 0) {
                  AddToQueryList(MAE);
                  goto UpdateTimerValue;
              }
  
              if (MaxResponseDelay <= MAE->MCastTimer) {
              UpdateTimerValue:
                  //
                  // Update the timer value.
                  //
                  if (MaxResponseDelay == 0)
                      MAE->MCastTimer = 0;
                  else
                      MAE->MCastTimer = (ushort)
                          RandomNumber(0, MaxResponseDelay);
  
                  //
                  // We add 1 because MLDTimeout predecrements.
                  // We must maintain the invariant that ADEs on
                  // the query list have a non-zero timer value.
                  //
                  MAE->MCastTimer += 1;
              }
  
              KeReleaseSpinLockFromDpcLevel(&QueryListLock);
          }
      }
  
      KeReleaseSpinLockFromDpcLevel(&IF->Lock);
  }
  
  
  //* MLDReportReceive - Process the receipt of a Group Report MLD message. 
  //
  //  When another host on the local link sends a group report, we receive
  //  a copy if we also belong to the group.  If we have a timer running for
  //  this group, we can turn it off now.
  //
  //  Callable from DPC context, not from thread context.
  //
  void
  MLDReportReceive(IPv6Packet *Packet)
  {
      Interface *IF = Packet->NTEorIF->IF;
      MLDMessage *Message;
      MulticastAddressEntry *MAE;
  
      //
      // Verify that the packet has a link-local source address.
      // An unspecified source address can also happen during initialization.
      //
      if (!(IsLinkLocal(AlignAddr(&Packet->IP->Source)) ||
            IsUnspecified(AlignAddr(&Packet->IP->Source)))) {
          KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
                     "MLDReportReceive: non-link-local source\n"));
          return;
      }
  
      //
      // Verify that we have enough contiguous data to overlay a MLDMessage
      // structure on the incoming packet.  Then do so.
      //
      if (! PacketPullup(Packet, sizeof(MLDMessage),
                         __builtin_alignof(MLDMessage), 0)) {
          // Pullup failed.
          if (Packet->TotalSize < sizeof(MLDMessage))
              KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
                         "MLDReportReceive: too small to contain MLD message\n"));
          return;
      }
      Message = (MLDMessage *)Packet->Data;
  
      KeAcquireSpinLockAtDpcLevel(&IF->Lock);
  
      //
      // Search for the MAE for this group address.
      //
      MAE = (MulticastAddressEntry *)
          *FindADE(IF, AlignAddr(&Message->GroupAddr));
      if ((MAE != NULL) && (MAE->Type == ADE_MULTICAST)) {
  
          KeAcquireSpinLockAtDpcLevel(&QueryListLock);
          //
          // We ignore the report unless
          // we are in the "Delaying Listener" state.
          //
          if (MAE->MCastTimer != 0) {
              //
              // Stop our timer and clear the last-reporter flag.
              // Note that we only clear the last-reporter flag
              // if our timer is running, as called for in the spec.
              // Although it would make sense to clear the flag
              // when we do not have a timer running.
              //
              MAE->MCastTimer = 0;
              MAE->MCastFlags &= ~MAE_LAST_REPORTER;
              RemoveFromQueryList(MAE);
          }
          KeReleaseSpinLockFromDpcLevel(&QueryListLock);
      }
  
      KeReleaseSpinLockFromDpcLevel(&IF->Lock);
  }
  
  
  //* MLDMessageSend
  //
  //  Primitive function for sending MLD messages.
  //
  //  Note that we can not use RouteToDestination to get an RCE.
  //  There might be no valid source addresses on the sending interface.
  //  We could use IPv6SendND, but it doesn't make sense because
  //  we can't pass in a valid DiscoveryAddress. And it's not needed.
  //
  void
  MLDMessageSend(
      Interface *IF,
      const IPv6Addr *GroupAddr,
      const IPv6Addr *Dest,
      uchar Type)
  {
      PNDIS_PACKET Packet;
      IPv6Header UNALIGNED *IP;
      ICMPv6Header UNALIGNED *ICMP;
      MLDMessage UNALIGNED *MLD;
      MLDRouterAlertOption UNALIGNED *RA;
      uint Offset;
      uint PayloadLength;
      uint MemLen;
      uchar *Mem;
      void *LLDest;
      IP_STATUS Status;
  
      ICMPv6OutStats.icmps_msgs++;
  
      ASSERT(IsMulticast(Dest));
  
      //
      // Calculate the packet size.
      //
      Offset = IF->LinkHeaderSize;
      PayloadLength = sizeof(MLDRouterAlertOption) + sizeof(ICMPv6Header)
          + sizeof(MLDMessage);
      MemLen = Offset + sizeof(IPv6Header) + PayloadLength;
  
      //
      // Allocate the packet.
      //
      Status = IPv6AllocatePacket(MemLen, &Packet, &Mem);
      if (Status != NDIS_STATUS_SUCCESS) {
          ICMPv6OutStats.icmps_errors++;
          return;
      }
  
      //
      // Prepare the IP header.
      //
      IP = (IPv6Header UNALIGNED *)(Mem + Offset);
      IP->VersClassFlow = IP_VERSION;
      IP->PayloadLength = net_short((ushort)PayloadLength);
      IP->NextHeader = IP_PROTOCOL_HOP_BY_HOP;
      IP->HopLimit = 1; 
      IP->Dest = *Dest;
      //
      // This will give us the unspecified address
      // if our link-local address is not valid.
      // (For example if it is still tentative pending DAD.)
      //
      (void) GetLinkLocalAddress(IF, AlignAddr(&IP->Source));
  
      //
      // Prepare the router alert option.
      //
      RA = (MLDRouterAlertOption UNALIGNED *)(IP + 1);
      RA->Header.NextHeader = IP_PROTOCOL_ICMPv6;
      RA->Header.HeaderExtLength = 0;
      RA->Option.Type = OPT6_ROUTER_ALERT;
      RA->Option.Length = 2;
      RA->Option.Value = MLD_ROUTER_ALERT_OPTION_TYPE;
      RA->Pad.Type = 1;
      RA->Pad.DataLength = 0;
  
      //
      // Prepare the ICMP header.
      //
      ICMP = (ICMPv6Header UNALIGNED *)(RA + 1);
      ICMP->Type = Type;
      ICMP->Code = 0;
      ICMP->Checksum = 0; // Calculated below.
  
      //
      // Prepare the MLD message.
      //
      MLD = (MLDMessage UNALIGNED *)(ICMP + 1);
      MLD->MaxResponseDelay = 0;
      MLD->Unused = 0;
      MLD->GroupAddr = *GroupAddr;
  
      //
      // Calculate the ICMP checksum.
      //
      ICMP->Checksum = ChecksumPacket(Packet,
                  Offset + sizeof(IPv6Header) + sizeof(MLDRouterAlertOption),
                  NULL,
                  sizeof(ICMPv6Header) + sizeof(MLDMessage),
                  AlignAddr(&IP->Source), AlignAddr(&IP->Dest),
                  IP_PROTOCOL_ICMPv6);
  
      //
      // Convert the IP-level multicast destination address
      // to a link-layer multicast address.
      //
      LLDest = alloca(IF->LinkAddressLength);
      (*IF->ConvertAddr)(IF->LinkContext, Dest, LLDest);
      PC(Packet)->Flags = NDIS_FLAGS_MULTICAST_PACKET | NDIS_FLAGS_DONT_LOOPBACK;
  
      //
      // Transmit the packet.
      //
      ICMPv6OutStats.icmps_typecount[Type]++;
      IPv6SendLL(IF, Packet, Offset, LLDest);
  }
  
  
  //* MLDReportSend - Send an MLD membership report.
  //
  //  This function is called either when a host first joins a multicast group or
  //  at some point after a membership query message was received, and the timer
  //  for this host has expired.
  //
  void
  MLDReportSend(Interface *IF, const IPv6Addr *GroupAddr)
  {
      MLDMessageSend(IF, GroupAddr, GroupAddr,
                     ICMPv6_MULTICAST_LISTENER_REPORT);
  }
  
  
  //* MLDDoneSend - Send an MLD done message.
  //
  //  This function is called when a host quits a multicast group AND this was
  //  the last host on the local link to report interest in the group.  A host 
  //  quits when either the upper layer explicitly quits or when the interface
  //  is deleted.
  //
  void
  MLDDoneSend(Interface *IF, const IPv6Addr *GroupAddr)
  {
      MLDMessageSend(IF, GroupAddr, &AllRoutersOnLinkAddr,
                     ICMPv6_MULTICAST_LISTENER_DONE);
  }
  
  
  //* MLDAddMCastAddr - Add a multicast group to the specified interface.
  //
  //  This function is called when a user level program has asked to join a 
  //  multicast group.
  //
  //  The Interface number can be supplied as zero,
  //  in which we try to pick a reasonable interface
  //  and then return the interface number that we picked.
  //
  //  Callable from thread context, not from DPC context.
  //  Called with no locks held.
  //
  IP_STATUS
  MLDAddMCastAddr(uint *pInterfaceNo, const IPv6Addr *Addr)
  {
      uint InterfaceNo = *pInterfaceNo;
      Interface *IF;
      MulticastAddressEntry *MAE;
      IP_STATUS status;
      KIRQL OldIrql;
  
      if (!IsMulticast(Addr)) {
          KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR,
                     "MLDAddMCastAddr: Not mcast addr\n"));
          return IP_PARAMETER_PROBLEM;
      }
  
      if (InterfaceNo == 0) {
          RouteCacheEntry *RCE;
  
          //
          // We must pick an interface to use for this multicast address.
          // Look for a multicast route in the routing table.
          //
          status = RouteToDestination(Addr, 0, NULL, RTD_FLAG_NORMAL, &RCE);
          if (status != IP_SUCCESS) {
              KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
                         "MLDAddMCastAddr - no route\n"));
              return status;
          }
  
          //
          // Use the interface associated with the RCE.
          //
          IF = RCE->NTE->IF;
          *pInterfaceNo = IF->Index;
          AddRefIF(IF);
          ReleaseRCE(RCE);
      }
      else {
          //
          // Use the interface requested by the application.
          //
          IF = FindInterfaceFromIndex(InterfaceNo);
          if (IF == NULL)
              return IP_PARAMETER_PROBLEM; 
      }
  
      //
      // Will this interface support multicast addresses?
      //
      if (!(IF->Flags & IF_FLAG_MULTICAST)) {
          KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR,
                     "MLDAddMCastAddr: IF cannot add a mcast addr\n"));
          ReleaseIF(IF);
          return IP_PARAMETER_PROBLEM;
      }
  
      //
      // The real work is all in FindOrCreateMAE.
      //
      KeAcquireSpinLock(&IF->Lock, &OldIrql);
      MAE = FindOrCreateMAE(IF, Addr, NULL);
      if (IsMCastSyncNeeded(IF))
          DeferSynchronizeMulticastAddresses(IF);
      KeReleaseSpinLock(&IF->Lock, OldIrql);
  
      ReleaseIF(IF);
      return (MAE == NULL) ? IP_NO_RESOURCES : IP_SUCCESS;
  }
  
  
  //* MLDDropMCastAddr - remove a multicast address from an interface.
  //
  //  This function is called when a user has indicated that they are no
  //  longer interested in a multicast group.
  //
  //  Callable from thread context, not from DPC context.
  //  Called with no locks held.
  //
  IP_STATUS
  MLDDropMCastAddr(uint InterfaceNo, const IPv6Addr *Addr)
  {
      Interface *IF;
      MulticastAddressEntry *MAE;
      IP_STATUS status;
      KIRQL OldIrql;
  
      //
      // Unlike MLDAddMCastAddr, no need to check
      // if the address is multicast. If it is not,
      // FindAndReleaseMAE will fail to find it.
      //
  
      if (InterfaceNo == 0) {
          RouteCacheEntry *RCE;
  
          //
          // We must pick an interface to use for this multicast address.
          // Look for a multicast route in the routing table.
          //
          status = RouteToDestination(Addr, 0, NULL, RTD_FLAG_NORMAL, &RCE);
          if (status != IP_SUCCESS) {
              KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
                         "MLDDropMCastAddr - no route\n"));
              return status;
          }
  
          //
          // Use the interface associated with the RCE.
          //
          IF = RCE->NTE->IF;
          AddRefIF(IF);
          ReleaseRCE(RCE);
      }
      else {
          //
          // Use the interface requested by the application.
          //
          IF = FindInterfaceFromIndex(InterfaceNo);
          if (IF == NULL)
              return IP_PARAMETER_PROBLEM; 
      }
  
      //
      // Unlike MLDAddMCastAddr, no need to check IF_FLAG_MULTICAST.
      // If the interface does not support multicast addresses,
      // FindAndReleaseMAE will fail to find the address.
      //
  
      //
      // All the real work is in FindAndReleaseMAE.
      //
      KeAcquireSpinLock(&IF->Lock, &OldIrql);
      MAE = FindAndReleaseMAE(IF, Addr);
      if (IsMCastSyncNeeded(IF))
          DeferSynchronizeMulticastAddresses(IF);
      KeReleaseSpinLock(&IF->Lock, OldIrql);
  
      ReleaseIF(IF);
      return (MAE == NULL) ? IP_PARAMETER_PROBLEM : IP_SUCCESS;
  }
  
  
  //* MLDTimeout - Handle MLD timer events.
  //
  //  This function is called periodically by IPv6Timeout.
  //  We decrement the timer value in each MAE on the query list.
  //  If the timer reaches zero, we send a group membership report.
  //  If the timer is already zero, that means we should send
  //  a Done message and then free the MAE. In this case, the MAE
  //  holds an interface reference. See DeleteMAE.
  //
  void
  MLDTimeout(void)
  {
      typedef struct MLDReportRequest {
          struct MLDReportRequest *Next;
          Interface *IF;
          IPv6Addr GroupAddr;
      } MLDReportRequest;
  
      MulticastAddressEntry **PrevMAE, *MAE;
      MLDReportRequest *ReportList = NULL;
      MLDReportRequest *Request;
      MulticastAddressEntry *DoneList = NULL;
  
      //
      // Lock the QueryList so we can traverse it and decrement timers.
      // But we avoid sending messages while holding any locks
      // by building a list of requested reports.
      //
      KeAcquireSpinLockAtDpcLevel(&QueryListLock);
  
      PrevMAE = &QueryList;
      while ((MAE = *PrevMAE) != NULL) {
  
          ASSERT(MAE->Type == ADE_MULTICAST);
  
          if (MAE->MCastTimer == 0) {
              //
              // We need to send a Done message.
              // Remove this MAE from the QueryList
              // and put it on a temporary list.
              //
              *PrevMAE = MAE->NextQL;
              MAE->NextQL = DoneList;
              DoneList = MAE;
              continue;
          }
          else if (--MAE->MCastTimer == 0) {
              //
              // This entry has expired, we need to send a Report.
              //
              Request = ExAllocatePool(NonPagedPool, sizeof *Request);
              if (Request != NULL) {
                  Request->Next = ReportList;
                  ReportList = Request;
  
                  Request->IF = MAE->NTEorIF->IF;
                  AddRefIF(Request->IF);
                  Request->GroupAddr = MAE->Address;
  
                  //
                  // Set the flag indicating we sent the last report
                  // on the link.
                  //
                  MAE->MCastFlags |= MAE_LAST_REPORTER;
              }
  
              if (MAE->MCastCount != 0) {
                  if (MAE->NTEorIF->IF->Flags & IF_FLAG_PERIODICMLD) {
                      // 
                      // On tunnels to 6to4 relays, we continue to generate 
                      // periodic reports since queries cannot be sent over
                      // an NBMA interface.
                      // 
                      MAE->MCastTimer = MLD_QUERY_INTERVAL;
                  }
                  else {
                      //
                      // If we are sending unsolicited reports,
                      // then leave the MAE on the query list
                      // and set a new timer value.
                      //
                      if (--MAE->MCastCount == 0)
                          goto Remove;
      
                      MAE->MCastTimer = (ushort)
                          RandomNumber(0, MLD_UNSOLICITED_REPORT_INTERVAL) + 1;
                  }
              }
              else {
              Remove:
                  //
                  // Remove the MAE from the query list.
                  //
                  *PrevMAE = MAE->NextQL;
                  continue;
              }
          }
  
          //
          // Go on to the next MAE.
          //
          PrevMAE = &MAE->NextQL;
      }
      KeReleaseSpinLockFromDpcLevel(&QueryListLock);
  
      //
      // Send MLD Report messages.
      //
      while ((Request = ReportList) != NULL) {
          ReportList = Request->Next;
  
          //
          // Send the MLD Report message.
          //
          MLDReportSend(Request->IF, &Request->GroupAddr);
  
          //
          // Free this structure.
          //
          ReleaseIF(Request->IF);
          ExFreePool(Request);
      }
  
      //
      // Send MLD Done messages.
      //
      while ((MAE = DoneList) != NULL) {
          Interface *IF = MAE->IF;
  
          DoneList = MAE->NextQL;
  
          //
          // Send the MLD Done message.
          //
          MLDDoneSend(IF, &MAE->Address);
  
          //
          // Free this structure.
          //
          ExFreePool(MAE);
          ReleaseIF(IF);
      }
  }
  
  
  //* MLDInit - Initialize MLD.
  //
  //  Initialize MLD global data structures.
  //
  void
  MLDInit(void)
  {
      KeInitializeSpinLock(&QueryListLock);
      QueryList = NULL;
  }
  