1/** @file
2  This file implements the RFC2236: IGMP v2.
3
4Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
5This program and the accompanying materials
6are licensed and made available under the terms and conditions of the BSD License
7which accompanies this distribution.  The full text of the license may be found at
8http://opensource.org/licenses/bsd-license.php
9
10THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13**/
14
15#include "Ip4Impl.h"
16
17//
18// Route Alert option in IGMP report to direct routers to
19// examine the packet more closely.
20//
21UINT32  mRouteAlertOption = 0x00000494;
22
23
24/**
25  Init the IGMP control data of the IP4 service instance, configure
26  MNP to receive ALL SYSTEM multicast.
27
28  @param[in, out]  IpSb          The IP4 service whose IGMP is to be initialized.
29
30  @retval EFI_SUCCESS            IGMP of the IpSb is successfully initialized.
31  @retval EFI_OUT_OF_RESOURCES   Failed to allocate resource to initialize IGMP.
32  @retval Others                 Failed to initialize the IGMP of IpSb.
33
34**/
35EFI_STATUS
36Ip4InitIgmp (
37  IN OUT IP4_SERVICE            *IpSb
38  )
39{
40  IGMP_SERVICE_DATA             *IgmpCtrl;
41  EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
42  IGMP_GROUP                    *Group;
43  EFI_STATUS                    Status;
44
45  IgmpCtrl = &IpSb->IgmpCtrl;
46
47  //
48  // Configure MNP to receive ALL_SYSTEM multicast
49  //
50  Group    = AllocatePool (sizeof (IGMP_GROUP));
51
52  if (Group == NULL) {
53    return EFI_OUT_OF_RESOURCES;
54  }
55
56  Mnp               = IpSb->Mnp;
57
58  Group->Address    = IP4_ALLSYSTEM_ADDRESS;
59  Group->RefCnt     = 1;
60  Group->DelayTime  = 0;
61  Group->ReportByUs = FALSE;
62
63  Status = Ip4GetMulticastMac (Mnp, IP4_ALLSYSTEM_ADDRESS, &Group->Mac);
64
65  if (EFI_ERROR (Status)) {
66    goto ON_ERROR;
67  }
68
69  Status = Mnp->Groups (Mnp, TRUE, &Group->Mac);
70
71  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
72    goto ON_ERROR;
73  }
74
75  InsertHeadList (&IgmpCtrl->Groups, &Group->Link);
76  return EFI_SUCCESS;
77
78ON_ERROR:
79  FreePool (Group);
80  return Status;
81}
82
83
84/**
85  Find the IGMP_GROUP structure which contains the status of multicast
86  group Address in this IGMP control block
87
88  @param[in]  IgmpCtrl               The IGMP control block to search from.
89  @param[in]  Address                The multicast address to search.
90
91  @return NULL if the multicast address isn't in the IGMP control block. Otherwise
92          the point to the IGMP_GROUP which contains the status of multicast group
93          for Address.
94
95**/
96IGMP_GROUP *
97Ip4FindGroup (
98  IN IGMP_SERVICE_DATA      *IgmpCtrl,
99  IN IP4_ADDR               Address
100  )
101{
102  LIST_ENTRY                *Entry;
103  IGMP_GROUP                *Group;
104
105  NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
106    Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
107
108    if (Group->Address == Address) {
109      return Group;
110    }
111  }
112
113  return NULL;
114}
115
116
117/**
118  Count the number of IP4 multicast groups that are mapped to the
119  same MAC address. Several IP4 multicast address may be mapped to
120  the same MAC address.
121
122  @param[in]  IgmpCtrl               The IGMP control block to search in.
123  @param[in]  Mac                    The MAC address to search.
124
125  @return The number of the IP4 multicast group that mapped to the same
126          multicast group Mac.
127
128**/
129INTN
130Ip4FindMac (
131  IN IGMP_SERVICE_DATA      *IgmpCtrl,
132  IN EFI_MAC_ADDRESS        *Mac
133  )
134{
135  LIST_ENTRY                *Entry;
136  IGMP_GROUP                *Group;
137  INTN                      Count;
138
139  Count = 0;
140
141  NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
142    Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
143
144    if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) {
145      Count++;
146    }
147  }
148
149  return Count;
150}
151
152
153/**
154  Send an IGMP protocol message to the Dst, such as IGMP v1 membership report.
155
156  @param[in]  IpSb               The IP4 service instance that requests the
157                                 transmission.
158  @param[in]  Dst                The destinaton to send to.
159  @param[in]  Type               The IGMP message type, such as IGMP v1 membership
160                                 report.
161  @param[in]  Group              The group address in the IGMP message head.
162
163  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to build the message.
164  @retval EFI_SUCCESS            The IGMP message is successfully send.
165  @retval Others                 Failed to send the IGMP message.
166
167**/
168EFI_STATUS
169Ip4SendIgmpMessage (
170  IN IP4_SERVICE            *IpSb,
171  IN IP4_ADDR               Dst,
172  IN UINT8                  Type,
173  IN IP4_ADDR               Group
174  )
175{
176  IP4_HEAD                  Head;
177  NET_BUF                   *Packet;
178  IGMP_HEAD                 *Igmp;
179
180  //
181  // Allocate a net buffer to hold the message
182  //
183  Packet = NetbufAlloc (IP4_MAX_HEADLEN + sizeof (IGMP_HEAD));
184
185  if (Packet == NULL) {
186    return EFI_OUT_OF_RESOURCES;
187  }
188
189  //
190  // Fill in the IGMP and IP header, then transmit the message
191  //
192  NetbufReserve (Packet, IP4_MAX_HEADLEN);
193
194  Igmp = (IGMP_HEAD *) NetbufAllocSpace (Packet, sizeof (IGMP_HEAD), FALSE);
195  if (Igmp == NULL) {
196    return EFI_OUT_OF_RESOURCES;
197  }
198
199  Igmp->Type        = Type;
200  Igmp->MaxRespTime = 0;
201  Igmp->Checksum    = 0;
202  Igmp->Group       = HTONL (Group);
203  Igmp->Checksum    = (UINT16) (~NetblockChecksum ((UINT8 *) Igmp, sizeof (IGMP_HEAD)));
204
205  Head.Tos          = 0;
206  Head.Protocol     = IP4_PROTO_IGMP;
207  Head.Ttl          = 1;
208  Head.Fragment     = 0;
209  Head.Dst          = Dst;
210  Head.Src          = IP4_ALLZERO_ADDRESS;
211
212  return Ip4Output (
213           IpSb,
214           NULL,
215           Packet,
216           &Head,
217           (UINT8 *) &mRouteAlertOption,
218           sizeof (UINT32),
219           IP4_ALLZERO_ADDRESS,
220           Ip4SysPacketSent,
221           NULL
222           );
223}
224
225
226/**
227  Send an IGMP membership report. Depends on whether the server is
228  v1 or v2, it will send either a V1 or V2 membership report.
229
230  @param[in]  IpSb               The IP4 service instance that requests the
231                                 transmission.
232  @param[in]  Group              The group address to report.
233
234  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to build the message.
235  @retval EFI_SUCCESS            The IGMP report message is successfully send.
236  @retval Others                 Failed to send the report.
237
238**/
239EFI_STATUS
240Ip4SendIgmpReport (
241  IN IP4_SERVICE            *IpSb,
242  IN IP4_ADDR               Group
243  )
244{
245  if (IpSb->IgmpCtrl.Igmpv1QuerySeen != 0) {
246    return Ip4SendIgmpMessage (IpSb, Group, IGMP_V1_MEMBERSHIP_REPORT, Group);
247  } else {
248    return Ip4SendIgmpMessage (IpSb, Group, IGMP_V2_MEMBERSHIP_REPORT, Group);
249  }
250}
251
252
253/**
254  Join the multicast group on behalf of this IP4 child
255
256  @param[in]  IpInstance         The IP4 child that wants to join the group.
257  @param[in]  Address            The group to join.
258
259  @retval EFI_SUCCESS            Successfully join the multicast group.
260  @retval EFI_OUT_OF_RESOURCES   Failed to allocate resources.
261  @retval Others                 Failed to join the multicast group.
262
263**/
264EFI_STATUS
265Ip4JoinGroup (
266  IN IP4_PROTOCOL           *IpInstance,
267  IN IP4_ADDR               Address
268  )
269{
270  EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
271  IP4_SERVICE                   *IpSb;
272  IGMP_SERVICE_DATA             *IgmpCtrl;
273  IGMP_GROUP                    *Group;
274  EFI_STATUS                    Status;
275
276  IpSb      = IpInstance->Service;
277  IgmpCtrl  = &IpSb->IgmpCtrl;
278  Mnp       = IpSb->Mnp;
279
280  //
281  // If the IP service already is a member in the group, just
282  // increase the refernce count and return.
283  //
284  Group     = Ip4FindGroup (IgmpCtrl, Address);
285
286  if (Group != NULL) {
287    Group->RefCnt++;
288    return EFI_SUCCESS;
289  }
290
291  //
292  // Otherwise, create a new IGMP_GROUP,  Get the multicast's MAC address,
293  // send a report, then direct MNP to receive the multicast.
294  //
295  Group = AllocatePool (sizeof (IGMP_GROUP));
296
297  if (Group == NULL) {
298    return EFI_OUT_OF_RESOURCES;
299  }
300
301  Group->Address    = Address;
302  Group->RefCnt     = 1;
303  Group->DelayTime  = IGMP_UNSOLICIATED_REPORT;
304  Group->ReportByUs = TRUE;
305
306  Status = Ip4GetMulticastMac (Mnp, Address, &Group->Mac);
307
308  if (EFI_ERROR (Status)) {
309    goto ON_ERROR;
310  }
311
312  Status = Ip4SendIgmpReport (IpSb, Address);
313
314  if (EFI_ERROR (Status)) {
315    goto ON_ERROR;
316  }
317
318  Status = Mnp->Groups (Mnp, TRUE, &Group->Mac);
319
320  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
321    goto ON_ERROR;
322  }
323
324  InsertHeadList (&IgmpCtrl->Groups, &Group->Link);
325  return EFI_SUCCESS;
326
327ON_ERROR:
328  FreePool (Group);
329  return Status;
330}
331
332
333/**
334  Leave the IP4 multicast group on behalf of IpInstance.
335
336  @param[in]  IpInstance         The IP4 child that wants to leave the group
337                                 address.
338  @param[in]  Address            The group address to leave.
339
340  @retval EFI_NOT_FOUND          The IP4 service instance isn't in the group.
341  @retval EFI_SUCCESS            Successfully leave the multicast group.
342  @retval Others                 Failed to leave the multicast group.
343
344**/
345EFI_STATUS
346Ip4LeaveGroup (
347  IN IP4_PROTOCOL           *IpInstance,
348  IN IP4_ADDR               Address
349  )
350{
351  EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
352  IP4_SERVICE                   *IpSb;
353  IGMP_SERVICE_DATA             *IgmpCtrl;
354  IGMP_GROUP                    *Group;
355  EFI_STATUS                    Status;
356
357  IpSb      = IpInstance->Service;
358  IgmpCtrl  = &IpSb->IgmpCtrl;
359  Mnp       = IpSb->Mnp;
360
361  Group     = Ip4FindGroup (IgmpCtrl, Address);
362
363  if (Group == NULL) {
364    return EFI_NOT_FOUND;
365  }
366
367  //
368  // If more than one instance is in the group, decrease
369  // the RefCnt then return.
370  //
371  if (--Group->RefCnt > 0) {
372    return EFI_SUCCESS;
373  }
374
375  //
376  // If multiple IP4 group addresses are mapped to the same
377  // multicast MAC address, don't configure the MNP to leave
378  // the MAC.
379  //
380  if (Ip4FindMac (IgmpCtrl, &Group->Mac) == 1) {
381    Status = Mnp->Groups (Mnp, FALSE, &Group->Mac);
382
383    if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
384      return Status;
385    }
386  }
387
388  //
389  // Send a leave report if the membership is reported by us
390  // and we are talking IGMPv2.
391  //
392  if (Group->ReportByUs && IgmpCtrl->Igmpv1QuerySeen == 0) {
393    Ip4SendIgmpMessage (IpSb, IP4_ALLROUTER_ADDRESS, IGMP_LEAVE_GROUP, Group->Address);
394  }
395
396  RemoveEntryList (&Group->Link);
397  FreePool (Group);
398
399  return EFI_SUCCESS;
400}
401
402
403/**
404  Handle the received IGMP message for the IP4 service instance.
405
406  @param[in]  IpSb               The IP4 service instance that received the message.
407  @param[in]  Head               The IP4 header of the received message.
408  @param[in]  Packet             The IGMP message, without IP4 header.
409
410  @retval EFI_INVALID_PARAMETER  The IGMP message is malformated.
411  @retval EFI_SUCCESS            The IGMP message is successfully processed.
412
413**/
414EFI_STATUS
415Ip4IgmpHandle (
416  IN IP4_SERVICE            *IpSb,
417  IN IP4_HEAD               *Head,
418  IN NET_BUF                *Packet
419  )
420{
421  IGMP_SERVICE_DATA         *IgmpCtrl;
422  IGMP_HEAD                 Igmp;
423  IGMP_GROUP                *Group;
424  IP4_ADDR                  Address;
425  LIST_ENTRY                *Entry;
426
427  IgmpCtrl = &IpSb->IgmpCtrl;
428
429  //
430  // Must checksum over the whole packet, later IGMP version
431  // may employ message longer than 8 bytes. IP's header has
432  // already been trimmed off.
433  //
434  if ((Packet->TotalSize < sizeof (Igmp)) || (NetbufChecksum (Packet) != 0)) {
435    NetbufFree (Packet);
436    return EFI_INVALID_PARAMETER;
437  }
438
439  //
440  // Copy the packet in case it is fragmented
441  //
442  NetbufCopy (Packet, 0, sizeof (IGMP_HEAD), (UINT8 *)&Igmp);
443
444  switch (Igmp.Type) {
445  case IGMP_MEMBERSHIP_QUERY:
446    //
447    // If MaxRespTime is zero, it is most likely that we are
448    // talking to a V1 router
449    //
450    if (Igmp.MaxRespTime == 0) {
451      IgmpCtrl->Igmpv1QuerySeen = IGMP_V1ROUTER_PRESENT;
452      Igmp.MaxRespTime          = 100;
453    }
454
455    //
456    // Igmp is ticking once per second but MaxRespTime is in
457    // the unit of 100ms.
458    //
459    Igmp.MaxRespTime /= 10;
460    Address = NTOHL (Igmp.Group);
461
462    if (Address == IP4_ALLSYSTEM_ADDRESS) {
463      break;
464    }
465
466    NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
467      Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
468
469      //
470      // If address is all zero, all the memberships will be reported.
471      // otherwise only one is reported.
472      //
473      if ((Address == IP4_ALLZERO_ADDRESS) || (Address == Group->Address)) {
474        //
475        // If the timer is pending, only update it if the time left
476        // is longer than the MaxRespTime. TODO: randomize the DelayTime.
477        //
478        if ((Group->DelayTime == 0) || (Group->DelayTime > Igmp.MaxRespTime)) {
479          Group->DelayTime = MAX (1, Igmp.MaxRespTime);
480        }
481      }
482    }
483
484    break;
485
486  case IGMP_V1_MEMBERSHIP_REPORT:
487  case IGMP_V2_MEMBERSHIP_REPORT:
488    Address = NTOHL (Igmp.Group);
489    Group   = Ip4FindGroup (IgmpCtrl, Address);
490
491    if ((Group != NULL) && (Group->DelayTime > 0)) {
492      Group->DelayTime  = 0;
493      Group->ReportByUs = FALSE;
494    }
495
496    break;
497  }
498
499  NetbufFree (Packet);
500  return EFI_SUCCESS;
501}
502
503
504/**
505  The periodical timer function for IGMP. It does the following
506  things:
507  1. Decrease the Igmpv1QuerySeen to make it possible to refresh
508     the IGMP server type.
509  2. Decrease the report timer for each IGMP group in "delaying
510     member" state.
511
512  @param[in]  IpSb                   The IP4 service instance that is ticking.
513
514**/
515VOID
516Ip4IgmpTicking (
517  IN IP4_SERVICE            *IpSb
518  )
519{
520  IGMP_SERVICE_DATA         *IgmpCtrl;
521  LIST_ENTRY                *Entry;
522  IGMP_GROUP                *Group;
523
524  IgmpCtrl = &IpSb->IgmpCtrl;
525
526  if (IgmpCtrl->Igmpv1QuerySeen > 0) {
527    IgmpCtrl->Igmpv1QuerySeen--;
528  }
529
530  //
531  // Decrease the report timer for each IGMP group in "delaying member"
532  //
533  NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
534    Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
535    ASSERT (Group->DelayTime >= 0);
536
537    if (Group->DelayTime > 0) {
538      Group->DelayTime--;
539
540      if (Group->DelayTime == 0) {
541        Ip4SendIgmpReport (IpSb, Group->Address);
542        Group->ReportByUs = TRUE;
543      }
544    }
545  }
546}
547
548
549/**
550  Add a group address to the array of group addresses.
551  The caller should make sure that no duplicated address
552  existed in the array. Although the function doesn't
553  assume the byte order of the both Source and Addr, the
554  network byte order is used by the caller.
555
556  @param[in]  Source                 The array of group addresses to add to.
557  @param[in]  Count                  The number of group addresses in the Source.
558  @param[in]  Addr                   The IP4 multicast address to add.
559
560  @return NULL if failed to allocate memory for the new groups,
561          otherwise the new combined group addresses.
562
563**/
564IP4_ADDR *
565Ip4CombineGroups (
566  IN  IP4_ADDR              *Source,
567  IN  UINT32                Count,
568  IN  IP4_ADDR              Addr
569  )
570{
571  IP4_ADDR                  *Groups;
572
573  Groups = AllocatePool (sizeof (IP4_ADDR) * (Count + 1));
574
575  if (Groups == NULL) {
576    return NULL;
577  }
578
579  CopyMem (Groups, Source, Count * sizeof (IP4_ADDR));
580  Groups[Count] = Addr;
581
582  return Groups;
583}
584
585
586/**
587  Remove a group address from the array of group addresses.
588  Although the function doesn't assume the byte order of the
589  both Groups and Addr, the network byte order is used by
590  the caller.
591
592  @param  Groups            The array of group addresses to remove from.
593  @param  Count             The number of group addresses in the Groups.
594  @param  Addr              The IP4 multicast address to remove.
595
596  @return The nubmer of group addresses in the Groups after remove.
597          It is Count if the Addr isn't in the Groups.
598
599**/
600INTN
601Ip4RemoveGroupAddr (
602  IN OUT IP4_ADDR               *Groups,
603  IN     UINT32                 Count,
604  IN     IP4_ADDR               Addr
605  )
606{
607  UINT32                    Index;
608
609  for (Index = 0; Index < Count; Index++) {
610    if (Groups[Index] == Addr) {
611      break;
612    }
613  }
614
615  while (Index < Count - 1) {
616    Groups[Index] = Groups[Index + 1];
617    Index++;
618  }
619
620  return Index;
621}
622