]> git.proxmox.com Git - mirror_edk2.git/blob - NetworkPkg/Ip4Dxe/Ip4Igmp.c
NetworkPkg: Move Network library and drivers from MdeModulePkg to NetworkPkg
[mirror_edk2.git] / NetworkPkg / Ip4Dxe / Ip4Igmp.c
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
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 *
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
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 destinaton 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
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
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
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 refernce 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
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 malformated.
405 @retval EFI_SUCCESS The IGMP message is successfully processed.
406
407 **/
408 EFI_STATUS
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
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 *
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 nubmer of group addresses in the Groups after remove.
591 It is Count if the Addr isn't in the Groups.
592
593 **/
594 INTN
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 }