1 /** @file
2   This file implements the RFC2236: IGMP v2.
3 
4 Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution.  The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9 
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT 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 //
21 UINT32  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 **/
35 EFI_STATUS
Ip4InitIgmp(IN OUT IP4_SERVICE * IpSb)36 Ip4InitIgmp (
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 
78 ON_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 **/
96 IGMP_GROUP *
Ip4FindGroup(IN IGMP_SERVICE_DATA * IgmpCtrl,IN IP4_ADDR Address)97 Ip4FindGroup (
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 **/
129 INTN
Ip4FindMac(IN IGMP_SERVICE_DATA * IgmpCtrl,IN EFI_MAC_ADDRESS * Mac)130 Ip4FindMac (
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 **/
168 EFI_STATUS
Ip4SendIgmpMessage(IN IP4_SERVICE * IpSb,IN IP4_ADDR Dst,IN UINT8 Type,IN IP4_ADDR Group)169 Ip4SendIgmpMessage (
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 **/
239 EFI_STATUS
Ip4SendIgmpReport(IN IP4_SERVICE * IpSb,IN IP4_ADDR Group)240 Ip4SendIgmpReport (
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 **/
264 EFI_STATUS
Ip4JoinGroup(IN IP4_PROTOCOL * IpInstance,IN IP4_ADDR Address)265 Ip4JoinGroup (
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 
327 ON_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 **/
345 EFI_STATUS
Ip4LeaveGroup(IN IP4_PROTOCOL * IpInstance,IN IP4_ADDR Address)346 Ip4LeaveGroup (
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 **/
414 EFI_STATUS
Ip4IgmpHandle(IN IP4_SERVICE * IpSb,IN IP4_HEAD * Head,IN NET_BUF * Packet)415 Ip4IgmpHandle (
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 **/
515 VOID
Ip4IgmpTicking(IN IP4_SERVICE * IpSb)516 Ip4IgmpTicking (
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 **/
564 IP4_ADDR *
Ip4CombineGroups(IN IP4_ADDR * Source,IN UINT32 Count,IN IP4_ADDR Addr)565 Ip4CombineGroups (
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 **/
600 INTN
Ip4RemoveGroupAddr(IN OUT IP4_ADDR * Groups,IN UINT32 Count,IN IP4_ADDR Addr)601 Ip4RemoveGroupAddr (
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