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