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