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