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**/
28IP6_MLD_GROUP *
29Ip6CreateMldEntry (
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**/
61IP6_MLD_GROUP *
62Ip6FindMldEntry (
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**/
95INTN
96Ip6FindMac (
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**/
132EFI_STATUS
133Ip6SendMldReport (
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**/
236EFI_STATUS
237Ip6SendMldDone (
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**/
339EFI_STATUS
340Ip6InitMld (
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
376ERROR:
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**/
395EFI_STATUS
396Ip6CombineGroups (
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**/
444EFI_STATUS
445Ip6RemoveGroup (
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**/
488EFI_STATUS
489Ip6JoinGroup (
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
535ERROR:
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**/
552EFI_STATUS
553Ip6LeaveGroup (
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**/
618EFI_STATUS
619Ip6Groups (
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**/
697EFI_STATUS
698Ip6UpdateDelayTimer (
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**/
743EFI_STATUS
744Ip6ProcessMldQuery (
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
814Exit:
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**/
831EFI_STATUS
832Ip6ProcessMldReport (
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
878Exit:
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**/
890VOID
891Ip6MldTimerTicking (
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