// -*- 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; }