1 /** @file
2   Multicast Listener Discovery support routines.
3 
4   Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>
5 
6   This program and the accompanying materials
7   are licensed and made available under the terms and conditions of the BSD License
8   which accompanies this distribution.  The full text of the license may be found at
9   http://opensource.org/licenses/bsd-license.php.
10 
11   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13 
14 **/
15 
16 #include "Ip6Impl.h"
17 
18 /**
19   Create a IP6_MLD_GROUP list entry node and record to IP6 service binding data.
20 
21   @param[in, out]  IpSb          Points to IP6 service binding instance.
22   @param[in]       MulticastAddr The IPv6 multicast address to be recorded.
23   @param[in]       DelayTimer    The maximum allowed delay before sending a responding
24                                  report, in units of milliseconds.
25   @return The created IP6_ML_GROUP list entry or NULL.
26 
27 **/
28 IP6_MLD_GROUP *
Ip6CreateMldEntry(IN OUT IP6_SERVICE * IpSb,IN EFI_IPv6_ADDRESS * MulticastAddr,IN UINT32 DelayTimer)29 Ip6CreateMldEntry (
30   IN OUT IP6_SERVICE        *IpSb,
31   IN EFI_IPv6_ADDRESS       *MulticastAddr,
32   IN UINT32                 DelayTimer
33   )
34 {
35   IP6_MLD_GROUP             *Entry;
36 
37   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
38   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
39 
40   Entry = AllocatePool (sizeof (IP6_MLD_GROUP));
41   if (Entry != NULL) {
42     Entry->RefCnt     = 1;
43     Entry->DelayTimer = DelayTimer;
44     Entry->SendByUs   = FALSE;
45     IP6_COPY_ADDRESS (&Entry->Address, MulticastAddr);
46     InsertTailList (&IpSb->MldCtrl.Groups, &Entry->Link);
47   }
48 
49   return Entry;
50 }
51 
52 /**
53   Search a IP6_MLD_GROUP list entry node from a list array.
54 
55   @param[in]       IpSb          Points to IP6 service binding instance.
56   @param[in]       MulticastAddr The IPv6 multicast address to be searched.
57 
58   @return The found IP6_ML_GROUP list entry or NULL.
59 
60 **/
61 IP6_MLD_GROUP *
Ip6FindMldEntry(IN IP6_SERVICE * IpSb,IN EFI_IPv6_ADDRESS * MulticastAddr)62 Ip6FindMldEntry (
63   IN IP6_SERVICE            *IpSb,
64   IN EFI_IPv6_ADDRESS       *MulticastAddr
65   )
66 {
67   LIST_ENTRY                *Entry;
68   IP6_MLD_GROUP             *Group;
69 
70   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
71   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
72 
73   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
74     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
75     if (EFI_IP6_EQUAL (MulticastAddr, &Group->Address)) {
76       return Group;
77     }
78   }
79 
80   return NULL;
81 }
82 
83 /**
84   Count the number of IP6 multicast groups that are mapped to the
85   same MAC address. Several IP6 multicast address may be mapped to
86   the same MAC address.
87 
88   @param[in]  MldCtrl              The MLD control block to search in.
89   @param[in]  Mac                  The MAC address to search.
90 
91   @return The number of the IP6 multicast group that mapped to the same
92           multicast group Mac.
93 
94 **/
95 INTN
Ip6FindMac(IN IP6_MLD_SERVICE_DATA * MldCtrl,IN EFI_MAC_ADDRESS * Mac)96 Ip6FindMac (
97   IN IP6_MLD_SERVICE_DATA   *MldCtrl,
98   IN EFI_MAC_ADDRESS        *Mac
99   )
100 {
101   LIST_ENTRY                *Entry;
102   IP6_MLD_GROUP             *Group;
103   INTN                      Count;
104 
105   Count = 0;
106 
107   NET_LIST_FOR_EACH (Entry, &MldCtrl->Groups) {
108     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
109 
110     if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) {
111       Count++;
112     }
113   }
114 
115   return Count;
116 }
117 
118 /**
119   Generate MLD report message and send it out to MulticastAddr.
120 
121   @param[in]  IpSb               The IP service to send the packet.
122   @param[in]  Interface          The IP interface to send the packet.
123                                  If NULL, a system interface will be selected.
124   @param[in]  MulticastAddr      The specific IPv6 multicast address to which
125                                  the message sender is listening.
126 
127   @retval EFI_OUT_OF_RESOURCES   There are not sufficient resources to complete the
128                                  operation.
129   @retval EFI_SUCCESS            The MLD report message was successfully sent out.
130 
131 **/
132 EFI_STATUS
Ip6SendMldReport(IN IP6_SERVICE * IpSb,IN IP6_INTERFACE * Interface OPTIONAL,IN EFI_IPv6_ADDRESS * MulticastAddr)133 Ip6SendMldReport (
134   IN IP6_SERVICE            *IpSb,
135   IN IP6_INTERFACE          *Interface OPTIONAL,
136   IN EFI_IPv6_ADDRESS       *MulticastAddr
137   )
138 {
139   IP6_MLD_HEAD              *MldHead;
140   NET_BUF                   *Packet;
141   EFI_IP6_HEADER            Head;
142   UINT16                    PayloadLen;
143   UINTN                     OptionLen;
144   UINT8                     *Options;
145   EFI_STATUS                Status;
146   UINT16                    HeadChecksum;
147   UINT16                    PseudoChecksum;
148 
149   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
150   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
151 
152   //
153   // Generate the packet to be sent
154   // IPv6 basic header + Hop by Hop option + MLD message
155   //
156 
157   OptionLen = 0;
158   Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP);
159   ASSERT (Status == EFI_BUFFER_TOO_SMALL);
160 
161   PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD));
162   Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
163   if (Packet == NULL) {
164     return EFI_OUT_OF_RESOURCES;
165   }
166 
167   //
168   // Create the basic IPv6 header.
169   // RFC3590: Use link-local address as source address if it is available,
170   // otherwise use the unspecified address.
171   //
172   Head.FlowLabelL     = 0;
173   Head.FlowLabelH     = 0;
174   Head.PayloadLength  = HTONS (PayloadLen);
175   Head.NextHeader     = IP6_HOP_BY_HOP;
176   Head.HopLimit       = 1;
177   IP6_COPY_ADDRESS (&Head.DestinationAddress, MulticastAddr);
178 
179   //
180   // If Link-Local address is not ready, we use unspecified address.
181   //
182   IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr);
183 
184   NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
185 
186   //
187   // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header
188   //
189   Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE);
190   ASSERT (Options != NULL);
191   Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP);
192   if (EFI_ERROR (Status)) {
193     NetbufFree (Packet);
194     Packet = NULL;
195     return Status;
196   }
197 
198   //
199   // Fill in MLD message - Report
200   //
201   MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE);
202   ASSERT (MldHead != NULL);
203   ZeroMem (MldHead, sizeof (IP6_MLD_HEAD));
204   MldHead->Head.Type = ICMP_V6_LISTENER_REPORT;
205   MldHead->Head.Code = 0;
206   IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr);
207 
208   HeadChecksum   = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD));
209   PseudoChecksum = NetIp6PseudoHeadChecksum (
210                      &Head.SourceAddress,
211                      &Head.DestinationAddress,
212                      IP6_ICMP,
213                      sizeof (IP6_MLD_HEAD)
214                      );
215 
216   MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum);
217 
218   //
219   // Transmit the packet
220   //
221   return Ip6Output (IpSb, Interface, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
222 }
223 
224 /**
225   Generate MLD Done message and send it out to MulticastAddr.
226 
227   @param[in]  IpSb               The IP service to send the packet.
228   @param[in]  MulticastAddr      The specific IPv6 multicast address to which
229                                  the message sender is ceasing to listen.
230 
231   @retval EFI_OUT_OF_RESOURCES   There are not sufficient resources to complete the
232                                  operation.
233   @retval EFI_SUCCESS            The MLD report message was successfully sent out.
234 
235 **/
236 EFI_STATUS
Ip6SendMldDone(IN IP6_SERVICE * IpSb,IN EFI_IPv6_ADDRESS * MulticastAddr)237 Ip6SendMldDone (
238   IN IP6_SERVICE            *IpSb,
239   IN EFI_IPv6_ADDRESS       *MulticastAddr
240   )
241 {
242   IP6_MLD_HEAD              *MldHead;
243   NET_BUF                   *Packet;
244   EFI_IP6_HEADER            Head;
245   UINT16                    PayloadLen;
246   UINTN                     OptionLen;
247   UINT8                     *Options;
248   EFI_STATUS                Status;
249   EFI_IPv6_ADDRESS          Destination;
250   UINT16                    HeadChecksum;
251   UINT16                    PseudoChecksum;
252 
253   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
254   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
255 
256   //
257   // Generate the packet to be sent
258   // IPv6 basic header + Hop by Hop option + MLD message
259   //
260 
261   OptionLen = 0;
262   Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP);
263   ASSERT (Status == EFI_BUFFER_TOO_SMALL);
264 
265   PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD));
266   Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
267   if (Packet == NULL) {
268     return EFI_OUT_OF_RESOURCES;
269   }
270 
271   //
272   // Create the basic IPv6 header.
273   //
274   Head.FlowLabelL     = 0;
275   Head.FlowLabelH     = 0;
276   Head.PayloadLength  = HTONS (PayloadLen);
277   Head.NextHeader     = IP6_HOP_BY_HOP;
278   Head.HopLimit       = 1;
279 
280   //
281   // If Link-Local address is not ready, we use unspecified address.
282   //
283   IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr);
284 
285   Ip6SetToAllNodeMulticast (TRUE, IP6_LINK_LOCAL_SCOPE, &Destination);
286   IP6_COPY_ADDRESS (&Head.DestinationAddress, &Destination);
287 
288   NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
289 
290   //
291   // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header
292   //
293   Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE);
294   ASSERT (Options != NULL);
295   Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP);
296   if (EFI_ERROR (Status)) {
297     NetbufFree (Packet);
298     Packet = NULL;
299     return Status;
300   }
301 
302   //
303   // Fill in MLD message - Done
304   //
305   MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE);
306   ASSERT (MldHead != NULL);
307   ZeroMem (MldHead, sizeof (IP6_MLD_HEAD));
308   MldHead->Head.Type = ICMP_V6_LISTENER_DONE;
309   MldHead->Head.Code = 0;
310   IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr);
311 
312   HeadChecksum   = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD));
313   PseudoChecksum = NetIp6PseudoHeadChecksum (
314                      &Head.SourceAddress,
315                      &Head.DestinationAddress,
316                      IP6_ICMP,
317                      sizeof (IP6_MLD_HEAD)
318                      );
319 
320   MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum);
321 
322   //
323   // Transmit the packet
324   //
325   return Ip6Output (IpSb, NULL, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
326 }
327 
328 /**
329   Init the MLD data of the IP6 service instance. Configure
330   MNP to receive ALL SYSTEM multicast.
331 
332   @param[in]  IpSb              The IP6 service whose MLD is to be initialized.
333 
334   @retval EFI_OUT_OF_RESOURCES  There are not sufficient resourcet to complete the
335                                 operation.
336   @retval EFI_SUCCESS           The MLD module successfully initialized.
337 
338 **/
339 EFI_STATUS
Ip6InitMld(IN IP6_SERVICE * IpSb)340 Ip6InitMld (
341   IN IP6_SERVICE            *IpSb
342   )
343 {
344   EFI_IPv6_ADDRESS          AllNodes;
345   IP6_MLD_GROUP             *Group;
346   EFI_STATUS                Status;
347 
348   //
349   // Join the link-scope all-nodes multicast address (FF02::1).
350   // This address is started in Idle Listener state and never transitions to
351   // another state, and never sends a Report or Done for that address.
352   //
353 
354   Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes);
355 
356   Group = Ip6CreateMldEntry (IpSb, &AllNodes, (UINT32) IP6_INFINIT_LIFETIME);
357   if (Group == NULL) {
358     return EFI_OUT_OF_RESOURCES;
359   }
360 
361   Status = Ip6GetMulticastMac (IpSb->Mnp, &AllNodes, &Group->Mac);
362   if (EFI_ERROR (Status)) {
363     goto ERROR;
364   }
365 
366   //
367   // Configure MNP to receive all-nodes multicast
368   //
369   Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac);
370   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
371     goto ERROR;
372   }
373 
374   return EFI_SUCCESS;
375 
376 ERROR:
377   RemoveEntryList (&Group->Link);
378   FreePool (Group);
379   return Status;
380 }
381 
382 /**
383   Add a group address to the array of group addresses.
384   The caller should make sure that no duplicated address
385   existed in the array.
386 
387   @param[in, out]  IpInstance       Points to an IP6_PROTOCOL instance.
388   @param[in]       Group            The IP6 multicast address to add.
389 
390   @retval EFI_OUT_OF_RESOURCES      There are not sufficient resources to complete
391                                     the operation.
392   @retval EFI_SUCESS                The address is added to the group address array.
393 
394 **/
395 EFI_STATUS
Ip6CombineGroups(IN OUT IP6_PROTOCOL * IpInstance,IN EFI_IPv6_ADDRESS * Group)396 Ip6CombineGroups (
397   IN OUT IP6_PROTOCOL *IpInstance,
398   IN EFI_IPv6_ADDRESS *Group
399   )
400 {
401   EFI_IPv6_ADDRESS     *GroupList;
402 
403   NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
404   ASSERT (Group != NULL && IP6_IS_MULTICAST (Group));
405 
406   IpInstance->GroupCount++;
407 
408   GroupList = AllocatePool (IpInstance->GroupCount * sizeof (EFI_IPv6_ADDRESS));
409   if (GroupList == NULL) {
410     return EFI_OUT_OF_RESOURCES;
411   }
412 
413   if (IpInstance->GroupCount > 1) {
414     ASSERT (IpInstance->GroupList != NULL);
415 
416     CopyMem (
417       GroupList,
418       IpInstance->GroupList,
419       (IpInstance->GroupCount - 1) * sizeof (EFI_IPv6_ADDRESS)
420       );
421 
422     FreePool (IpInstance->GroupList);
423   }
424 
425   IP6_COPY_ADDRESS (GroupList + (IpInstance->GroupCount - 1), Group);
426 
427   IpInstance->GroupList = GroupList;
428 
429   return EFI_SUCCESS;
430 }
431 
432 /**
433   Remove a group address from the array of group addresses.
434   Although the function doesn't assume the byte order of Group,
435   the network byte order is used by the caller.
436 
437   @param[in, out]  IpInstance       Points to an IP6_PROTOCOL instance.
438   @param[in]       Group            The IP6 multicast address to remove.
439 
440   @retval EFI_NOT_FOUND             Cannot find the to be removed group address.
441   @retval EFI_SUCCESS               The group address was successfully removed.
442 
443 **/
444 EFI_STATUS
Ip6RemoveGroup(IN OUT IP6_PROTOCOL * IpInstance,IN EFI_IPv6_ADDRESS * Group)445 Ip6RemoveGroup (
446   IN OUT IP6_PROTOCOL *IpInstance,
447   IN EFI_IPv6_ADDRESS *Group
448   )
449 {
450   UINT32                    Index;
451   UINT32                    Count;
452 
453   Count = IpInstance->GroupCount;
454 
455   for (Index = 0; Index < Count; Index++) {
456     if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, Group)) {
457       break;
458     }
459   }
460 
461   if (Index == Count) {
462     return EFI_NOT_FOUND;
463   }
464 
465   while (Index < Count - 1) {
466     IP6_COPY_ADDRESS (IpInstance->GroupList + Index, IpInstance->GroupList + Index + 1);
467     Index++;
468   }
469 
470   ASSERT (IpInstance->GroupCount > 0);
471   IpInstance->GroupCount--;
472 
473   return EFI_SUCCESS;
474 }
475 
476 /**
477   Join the multicast group on behalf of this IP6 service binding instance.
478 
479   @param[in]  IpSb               The IP6 service binding instance.
480   @param[in]  Interface          Points to an IP6_INTERFACE structure.
481   @param[in]  Address            The group address to join.
482 
483   @retval EFI_SUCCESS            Successfully join the multicast group.
484   @retval EFI_OUT_OF_RESOURCES   Failed to allocate resources.
485   @retval Others                 Failed to join the multicast group.
486 
487 **/
488 EFI_STATUS
Ip6JoinGroup(IN IP6_SERVICE * IpSb,IN IP6_INTERFACE * Interface,IN EFI_IPv6_ADDRESS * Address)489 Ip6JoinGroup (
490   IN IP6_SERVICE            *IpSb,
491   IN IP6_INTERFACE          *Interface,
492   IN EFI_IPv6_ADDRESS       *Address
493   )
494 {
495   IP6_MLD_GROUP            *Group;
496   EFI_STATUS               Status;
497 
498   Group = Ip6FindMldEntry (IpSb, Address);
499   if (Group != NULL) {
500     Group->RefCnt++;
501     return EFI_SUCCESS;
502   }
503 
504   //
505   // Repeat the report once or twcie after short delays [Unsolicited Report Interval] (default:10s)
506   // Simulate this operation as a Multicast-Address-Specific Query was received for that addresss.
507   //
508   Group = Ip6CreateMldEntry (IpSb, Address, IP6_UNSOLICITED_REPORT_INTERVAL);
509   if (Group == NULL) {
510     return EFI_OUT_OF_RESOURCES;
511   }
512 
513   Group->SendByUs = TRUE;
514 
515   Status = Ip6GetMulticastMac (IpSb->Mnp, Address, &Group->Mac);
516   if (EFI_ERROR (Status)) {
517     return Status;
518   }
519 
520   Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac);
521   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
522     goto ERROR;
523   }
524 
525   //
526   // Send unsolicited report when a node starts listening to a multicast address
527   //
528   Status = Ip6SendMldReport (IpSb, Interface, Address);
529   if (EFI_ERROR (Status)) {
530     goto ERROR;
531   }
532 
533   return EFI_SUCCESS;
534 
535 ERROR:
536   RemoveEntryList (&Group->Link);
537   FreePool (Group);
538   return Status;
539 }
540 
541 /**
542   Leave the IP6 multicast group.
543 
544   @param[in]  IpSb               The IP6 service binding instance.
545   @param[in]  Address            The group address to leave.
546 
547   @retval EFI_NOT_FOUND          The IP6 service instance isn't in the group.
548   @retval EFI_SUCCESS            Successfully leave the multicast group..
549   @retval Others                 Failed to leave the multicast group.
550 
551 **/
552 EFI_STATUS
Ip6LeaveGroup(IN IP6_SERVICE * IpSb,IN EFI_IPv6_ADDRESS * Address)553 Ip6LeaveGroup (
554  IN IP6_SERVICE            *IpSb,
555  IN EFI_IPv6_ADDRESS       *Address
556   )
557 {
558   IP6_MLD_GROUP            *Group;
559   EFI_STATUS               Status;
560 
561   Group = Ip6FindMldEntry (IpSb, Address);
562   if (Group == NULL) {
563     return EFI_NOT_FOUND;
564   }
565 
566   //
567   // If more than one instance is in the group, decrease
568   // the RefCnt then return.
569   //
570   if ((Group->RefCnt > 0) && (--Group->RefCnt > 0)) {
571     return EFI_SUCCESS;
572   }
573 
574   //
575   // If multiple IP6 group addresses are mapped to the same
576   // multicast MAC address, don't configure the MNP to leave
577   // the MAC.
578   //
579   if (Ip6FindMac (&IpSb->MldCtrl, &Group->Mac) == 1) {
580     Status = IpSb->Mnp->Groups (IpSb->Mnp, FALSE, &Group->Mac);
581     if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
582       return Status;
583     }
584   }
585 
586   //
587   // Send a leave report if we are the last node to report
588   //
589   if (Group->SendByUs) {
590     Status = Ip6SendMldDone (IpSb, Address);
591     if (EFI_ERROR (Status)) {
592       return Status;
593     }
594   }
595 
596   RemoveEntryList (&Group->Link);
597   FreePool (Group);
598 
599   return EFI_SUCCESS;
600 }
601 
602 /**
603   Worker function for EfiIp6Groups(). The caller
604   should make sure that the parameters are valid.
605 
606   @param[in]  IpInstance        The IP6 child to change the setting.
607   @param[in]  JoinFlag          TRUE to join the group, otherwise leave it.
608   @param[in]  GroupAddress      The target group address. If NULL, leave all
609                                 the group addresses.
610 
611   @retval EFI_ALREADY_STARTED   Wants to join the group, but is already a member of it
612   @retval EFI_OUT_OF_RESOURCES  Failed to allocate sufficient resources.
613   @retval EFI_DEVICE_ERROR      Failed to set the group configuraton.
614   @retval EFI_SUCCESS           Successfully updated the group setting.
615   @retval EFI_NOT_FOUND         Try to leave the group which it isn't a member.
616 
617 **/
618 EFI_STATUS
Ip6Groups(IN IP6_PROTOCOL * IpInstance,IN BOOLEAN JoinFlag,IN EFI_IPv6_ADDRESS * GroupAddress OPTIONAL)619 Ip6Groups (
620   IN IP6_PROTOCOL           *IpInstance,
621   IN BOOLEAN                JoinFlag,
622   IN EFI_IPv6_ADDRESS       *GroupAddress       OPTIONAL
623   )
624 {
625   EFI_STATUS                Status;
626   IP6_SERVICE               *IpSb;
627   UINT32                    Index;
628   EFI_IPv6_ADDRESS          *Group;
629 
630   IpSb = IpInstance->Service;
631 
632   if (JoinFlag) {
633     ASSERT (GroupAddress != NULL);
634 
635     for (Index = 0; Index < IpInstance->GroupCount; Index++) {
636       if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, GroupAddress)) {
637         return EFI_ALREADY_STARTED;
638       }
639     }
640 
641     Status = Ip6JoinGroup (IpSb, IpInstance->Interface, GroupAddress);
642     if (!EFI_ERROR (Status)) {
643       return Ip6CombineGroups (IpInstance, GroupAddress);
644     }
645 
646     return Status;
647   }
648 
649   //
650   // Leave the group. Leave all the groups if GroupAddress is NULL.
651   //
652   for (Index = IpInstance->GroupCount; Index > 0; Index--) {
653     Group = IpInstance->GroupList + (Index - 1);
654 
655     if ((GroupAddress == NULL) || EFI_IP6_EQUAL (Group, GroupAddress)) {
656       Status = Ip6LeaveGroup (IpInstance->Service, Group);
657       if (EFI_ERROR (Status)) {
658         return Status;
659       }
660 
661       Ip6RemoveGroup (IpInstance, Group);
662 
663       if (IpInstance->GroupCount == 0) {
664         ASSERT (Index == 1);
665         FreePool (IpInstance->GroupList);
666         IpInstance->GroupList = NULL;
667       }
668 
669       if (GroupAddress != NULL) {
670         return EFI_SUCCESS;
671       }
672     }
673   }
674 
675   return ((GroupAddress != NULL) ? EFI_NOT_FOUND : EFI_SUCCESS);
676 }
677 
678 /**
679   Set a random value of the delay timer for the multicast address from the range
680   [0, Maximum Response Delay]. If a timer for any address is already
681   running, it is reset to the new random value only if the requested
682   Maximum Response Delay is less than the remaining value of the
683   running timer.  If the Query packet specifies a Maximum Response
684   Delay of zero, each timer is effectively set to zero, and the action
685   specified below for timer expiration is performed immediately.
686 
687   @param[in]      IpSb               The IP6 service binding instance.
688   @param[in]      MaxRespDelay       The Maximum Response Delay, in milliseconds.
689   @param[in]      MulticastAddr      The multicast address.
690   @param[in, out] Group              Points to a IP6_MLD_GROUP list entry node.
691 
692   @retval EFI_SUCCESS                The delay timer is successfully updated or
693                                      timer expiration is performed immediately.
694   @retval Others                     Failed to send out MLD report message.
695 
696 **/
697 EFI_STATUS
Ip6UpdateDelayTimer(IN IP6_SERVICE * IpSb,IN UINT16 MaxRespDelay,IN EFI_IPv6_ADDRESS * MulticastAddr,IN OUT IP6_MLD_GROUP * Group)698 Ip6UpdateDelayTimer (
699   IN IP6_SERVICE            *IpSb,
700   IN UINT16                 MaxRespDelay,
701   IN EFI_IPv6_ADDRESS       *MulticastAddr,
702   IN OUT IP6_MLD_GROUP      *Group
703   )
704 {
705   UINT32                    Delay;
706 
707   //
708   // If the Query packet specifies a Maximum Response Delay of zero, perform timer
709   // expiration immediately.
710   //
711   if (MaxRespDelay == 0) {
712     Group->DelayTimer = 0;
713     return Ip6SendMldReport (IpSb, NULL, MulticastAddr);
714   }
715 
716   Delay = (UINT32) (MaxRespDelay / 1000);
717 
718   //
719   // Sets a delay timer to a random value selected from the range [0, Maximum Response Delay]
720   // If a timer is already running, resets it if the request Maximum Response Delay
721   // is less than the remaining value of the running timer.
722   //
723   if (Group->DelayTimer == 0 || Delay < Group->DelayTimer) {
724     Group->DelayTimer = Delay / 4294967295UL * NET_RANDOM (NetRandomInitSeed ());
725   }
726 
727   return EFI_SUCCESS;
728 }
729 
730 /**
731   Process the Multicast Listener Query message.
732 
733   @param[in]  IpSb               The IP service that received the packet.
734   @param[in]  Head               The IP head of the MLD query packet.
735   @param[in]  Packet             The content of the MLD query packet with IP head
736                                  removed.
737 
738   @retval EFI_SUCCESS            The MLD query packet processed successfully.
739   @retval EFI_INVALID_PARAMETER  The packet is invalid.
740   @retval Others                 Failed to process the packet.
741 
742 **/
743 EFI_STATUS
Ip6ProcessMldQuery(IN IP6_SERVICE * IpSb,IN EFI_IP6_HEADER * Head,IN NET_BUF * Packet)744 Ip6ProcessMldQuery (
745   IN IP6_SERVICE            *IpSb,
746   IN EFI_IP6_HEADER         *Head,
747   IN NET_BUF                *Packet
748   )
749 {
750   EFI_IPv6_ADDRESS          AllNodes;
751   IP6_MLD_GROUP             *Group;
752   IP6_MLD_HEAD              MldPacket;
753   LIST_ENTRY                *Entry;
754   EFI_STATUS                Status;
755 
756   Status = EFI_INVALID_PARAMETER;
757 
758   //
759   // Check the validity of the packet, generic query or specific query
760   //
761   if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
762     goto Exit;
763   }
764 
765   if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) {
766     goto Exit;
767   }
768 
769   //
770   // The Packet points to MLD report raw data without Hop-By-Hop option.
771   //
772   NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket);
773   MldPacket.MaxRespDelay = NTOHS (MldPacket.MaxRespDelay);
774 
775   Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes);
776   if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &AllNodes)) {
777     //
778     // Receives a Multicast-Address-Specific Query, check it firstly
779     //
780     if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) {
781       goto Exit;
782     }
783     //
784     // The node is not listening but it receives the specific query. Just return.
785     //
786     Group = Ip6FindMldEntry (IpSb, &MldPacket.Group);
787     if (Group == NULL) {
788       Status = EFI_SUCCESS;
789       goto Exit;
790     }
791 
792     Status = Ip6UpdateDelayTimer (
793                IpSb,
794                MldPacket.MaxRespDelay,
795                &MldPacket.Group,
796                Group
797                );
798     goto Exit;
799   }
800 
801   //
802   // Receives a General Query, sets a delay timer for each multicast address it is listening
803   //
804   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
805     Group  = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
806     Status = Ip6UpdateDelayTimer (IpSb, MldPacket.MaxRespDelay, &Group->Address, Group);
807     if (EFI_ERROR (Status)) {
808       goto Exit;
809     }
810   }
811 
812   Status = EFI_SUCCESS;
813 
814 Exit:
815   NetbufFree (Packet);
816   return Status;
817 }
818 
819 /**
820   Process the Multicast Listener Report message.
821 
822   @param[in]  IpSb               The IP service that received the packet.
823   @param[in]  Head               The IP head of the MLD report packet.
824   @param[in]  Packet             The content of the MLD report packet with IP head
825                                  removed.
826 
827   @retval EFI_SUCCESS            The MLD report packet processed successfully.
828   @retval EFI_INVALID_PARAMETER  The packet is invalid.
829 
830 **/
831 EFI_STATUS
Ip6ProcessMldReport(IN IP6_SERVICE * IpSb,IN EFI_IP6_HEADER * Head,IN NET_BUF * Packet)832 Ip6ProcessMldReport (
833   IN IP6_SERVICE            *IpSb,
834   IN EFI_IP6_HEADER         *Head,
835   IN NET_BUF                *Packet
836   )
837 {
838   IP6_MLD_HEAD              MldPacket;
839   IP6_MLD_GROUP             *Group;
840   EFI_STATUS                Status;
841 
842   Status = EFI_INVALID_PARAMETER;
843 
844   //
845   // Validate the incoming message, if invalid, drop it.
846   //
847   if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
848     goto Exit;
849   }
850 
851   if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) {
852     goto Exit;
853   }
854 
855   //
856   // The Packet points to MLD report raw data without Hop-By-Hop option.
857   //
858   NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket);
859   if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) {
860     goto Exit;
861   }
862 
863   Group = Ip6FindMldEntry (IpSb, &MldPacket.Group);
864   if (Group == NULL) {
865     goto Exit;
866   }
867 
868   //
869   // The report is sent by another node, stop its own timer relates to the multicast address and clear
870   //
871 
872   if (!Group->SendByUs) {
873     Group->DelayTimer = 0;
874   }
875 
876   Status = EFI_SUCCESS;
877 
878 Exit:
879   NetbufFree (Packet);
880   return Status;
881 }
882 
883 /**
884   The heartbeat timer of MLD module. It sends out a solicited MLD report when
885   DelayTimer expires.
886 
887   @param[in]  IpSb              The IP6 service binding instance.
888 
889 **/
890 VOID
Ip6MldTimerTicking(IN IP6_SERVICE * IpSb)891 Ip6MldTimerTicking (
892   IN IP6_SERVICE            *IpSb
893   )
894 {
895   IP6_MLD_GROUP             *Group;
896   LIST_ENTRY                *Entry;
897 
898   //
899   // Send solicited report when timer expires
900   //
901   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
902     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
903     if ((Group->DelayTimer > 0) && (--Group->DelayTimer == 0)) {
904       Ip6SendMldReport (IpSb, NULL, &Group->Address);
905     }
906   }
907 }
908 
909