igmp.c
Go to the documentation of this file.
1 /**
2  * @file igmp.c
3  * @brief IGMP (Internet Group Management Protocol)
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @section Description
26  *
27  * IGMP is used by IP hosts to report their multicast group memberships
28  * to routers. Refer to the following RFCs for complete details:
29  * - RFC 1112: Host Extensions for IP Multicasting
30  * - RFC 2236: Internet Group Management Protocol, Version 2
31  * - RFC 3376: Internet Group Management Protocol, Version 3
32  *
33  * @author Oryx Embedded SARL (www.oryx-embedded.com)
34  * @version 1.9.0
35  **/
36 
37 //Switch to the appropriate trace level
38 #define TRACE_LEVEL IGMP_TRACE_LEVEL
39 
40 //Dependencies
41 #include "core/net.h"
42 #include "core/ip.h"
43 #include "ipv4/ipv4.h"
44 #include "ipv4/igmp.h"
45 #include "debug.h"
46 
47 //Check TCP/IP stack configuration
48 #if (IPV4_SUPPORT == ENABLED && IGMP_SUPPORT == ENABLED)
49 
50 //Tick counter to handle periodic operations
52 
53 
54 /**
55  * @brief IGMP initialization
56  * @param[in] interface Underlying network interface
57  * @return Error code
58  **/
59 
61 {
62  //The default host compatibility mode is IGMPv2
63  interface->igmpv1RouterPresent = FALSE;
64 
65  //Start IGMPv1 router present timer
66  interface->igmpv1RouterPresentTimer =
68 
69  //Successful initialization
70  return NO_ERROR;
71 }
72 
73 
74 /**
75  * @brief Join the specified host group
76  * @param[in] interface Underlying network interface
77  * @param[in] entry IPv4 filter entry identifying the host group to join
78  * @return Error code
79  **/
80 
82 {
83  //The all-systems group (address 224.0.0.1) is handled as a special
84  //case. The host starts in Idle Member state for that group on every
85  //interface and never transitions to another state
86  if(entry->addr == IGMP_ALL_SYSTEMS_ADDR)
87  {
88  //Clear flag
89  entry->flag = FALSE;
90  //Enter the Idle Member state
92  }
93  else
94  {
95  //Link is up?
96  if(interface->linkState)
97  {
98  //When a host joins a multicast group, it should immediately transmit
99  //an unsolicited Membership Report for that group
100  igmpSendReportMessage(interface, entry->addr);
101 
102  //Set flag
103  entry->flag = TRUE;
104  //Start timer
106  //Enter the Delaying Member state
108  }
109  //Link is down?
110  else
111  {
112  //Clear flag
113  entry->flag = FALSE;
114  //Enter the Idle Member state
115  entry->state = IGMP_STATE_IDLE_MEMBER;
116  }
117  }
118 
119  //Successful processing
120  return NO_ERROR;
121 }
122 
123 
124 /**
125  * @brief Leave the specified host group
126  * @param[in] interface Underlying network interface
127  * @param[in] entry IPv4 filter entry identifying the host group to leave
128  * @return Error code
129  **/
130 
132 {
133  //Check link state
134  if(interface->linkState)
135  {
136  //Send a Leave Group message if the flag is set
137  if(entry->flag)
138  igmpSendLeaveGroupMessage(interface, entry->addr);
139  }
140 
141  //Switch to the Non-Member state
142  entry->state = IGMP_STATE_NON_MEMBER;
143 
144  //Successful processing
145  return NO_ERROR;
146 }
147 
148 
149 /**
150  * @brief IGMP timer handler
151  *
152  * This routine must be periodically called by the TCP/IP stack to
153  * handle IGMP related timers
154  *
155  * @param[in] interface Underlying network interface
156  **/
157 
158 void igmpTick(NetInterface *interface)
159 {
160  uint_t i;
161  systime_t time;
162  Ipv4FilterEntry *entry;
163 
164  //Get current time
165  time = osGetSystemTime();
166 
167  //Check IGMPv1 router present timer
168  if(timeCompare(time, interface->igmpv1RouterPresentTimer) >= 0)
169  interface->igmpv1RouterPresent = FALSE;
170 
171  //Go through the multicast filter table
172  for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
173  {
174  //Point to the current entry
175  entry = &interface->ipv4Context.multicastFilter[i];
176 
177  //Valid entry?
178  if(entry->refCount > 0)
179  {
180  //Delaying Member state?
181  if(entry->state == IGMP_STATE_DELAYING_MEMBER)
182  {
183  //Timer expired?
184  if(timeCompare(time, entry->timer) >= 0)
185  {
186  //Send a Membership Report message for the group on the interface
187  igmpSendReportMessage(interface, entry->addr);
188 
189  //Set flag
190  entry->flag = TRUE;
191  //Switch to the Idle Member state
192  entry->state = IGMP_STATE_IDLE_MEMBER;
193  }
194  }
195  }
196  }
197 }
198 
199 
200 /**
201  * @brief Callback function for link change event
202  * @param[in] interface Underlying network interface
203  **/
204 
206 {
207  uint_t i;
208  systime_t time;
209  Ipv4FilterEntry *entry;
210 
211  //Get current time
212  time = osGetSystemTime();
213 
214  //Link up event?
215  if(interface->linkState)
216  {
217  //The default host compatibility mode is IGMPv2
218  interface->igmpv1RouterPresent = FALSE;
219  //Start IGMPv1 router present timer
220  interface->igmpv1RouterPresentTimer = time + IGMP_V1_ROUTER_PRESENT_TIMEOUT;
221 
222  //Go through the multicast filter table
223  for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
224  {
225  //Point to the current entry
226  entry = &interface->ipv4Context.multicastFilter[i];
227 
228  //Valid entry?
229  if(entry->refCount > 0)
230  {
231  //The all-systems group (address 224.0.0.1) is handled as a special
232  //case. The host starts in Idle Member state for that group on every
233  //interface and never transitions to another state
234  if(entry->addr != IGMP_ALL_SYSTEMS_ADDR)
235  {
236  //Send an unsolicited Membership Report for that group
237  igmpSendReportMessage(interface, entry->addr);
238 
239  //Set flag
240  entry->flag = TRUE;
241  //Start timer
243  //Enter the Delaying Member state
245  }
246  }
247  }
248  }
249  //Link down event?
250  else
251  {
252  //Go through the multicast filter table
253  for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
254  {
255  //Point to the current entry
256  entry = &interface->ipv4Context.multicastFilter[i];
257 
258  //Valid entry?
259  if(entry->refCount > 0)
260  {
261  //Clear flag
262  entry->flag = FALSE;
263  //Enter the Idle Member state
264  entry->state = IGMP_STATE_IDLE_MEMBER;
265  }
266  }
267  }
268 }
269 
270 
271 /**
272  * @brief Process incoming IGMP message
273  * @param[in] interface Underlying network interface
274  * @param[in] buffer Multi-part buffer containing the incoming IGMP message
275  * @param[in] offset Offset to the first byte of the IGMP message
276  **/
277 
279  const NetBuffer *buffer, size_t offset)
280 {
281  size_t length;
283 
284  //Retrieve the length of the IGMP message
285  length = netBufferGetLength(buffer) - offset;
286 
287  //Ensure the message length is correct
288  if(length < sizeof(IgmpMessage))
289  {
290  //Debug message
291  TRACE_WARNING("IGMP message length is invalid!\r\n");
292  //Silently discard incoming message
293  return;
294  }
295 
296  //Point to the beginning of the IGMP message
297  message = netBufferAt(buffer, offset);
298  //Sanity check
299  if(message == NULL)
300  return;
301 
302  //Debug message
303  TRACE_INFO("IGMP message received (%" PRIuSIZE " bytes)...\r\n", length);
304  //Dump message contents for debugging purpose
306 
307  //Verify checksum value
308  if(ipCalcChecksumEx(buffer, offset, length) != 0x0000)
309  {
310  //Debug message
311  TRACE_WARNING("Wrong IGMP header checksum!\r\n");
312  //Drop incoming message
313  return;
314  }
315 
316  //Check the type field
317  switch(message->type)
318  {
319  //Membership Query message?
321  //Process Membership Query message
323  break;
324  //Membership Report message?
327  //Process Membership Query message
329  break;
330  //Unknown type?
331  default:
332  //Debug message
333  TRACE_WARNING("Unknown IGMP message type!\r\n");
334  //Discard incoming IGMP message
335  break;
336  }
337 }
338 
339 
340 /**
341  * @brief Process incoming Membership Query message
342  * @param[in] interface Underlying network interface
343  * @param[in] message Incoming Membership Query message
344  * @param[in] length Message length
345  **/
346 
348  const IgmpMessage *message, size_t length)
349 {
350  uint_t i;
351  systime_t time;
353  Ipv4FilterEntry *entry;
354 
355  //Get current time
356  time = osGetSystemTime();
357 
358  //IGMPv1 Membership Query message?
359  if(message->maxRespTime == 0)
360  {
361  //The host receives a query with the Max Response Time field set to 0
362  interface->igmpv1RouterPresent = TRUE;
363  //Restart IGMPv1 router present timer
364  interface->igmpv1RouterPresentTimer = time + IGMP_V1_ROUTER_PRESENT_TIMEOUT;
365  //The maximum response time is 10 seconds by default
367  }
368  //IGMPv2 Membership Query message?
369  else
370  {
371  //The Max Resp Time field specifies the maximum time allowed
372  //before sending a responding report
373  maxRespTime = message->maxRespTime * 10;
374  }
375 
376  //Go through the multicast filter table
377  for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
378  {
379  //Point to the current entry
380  entry = &interface->ipv4Context.multicastFilter[i];
381 
382  //Valid entry?
383  if(entry->refCount > 0)
384  {
385  //The all-systems group (224.0.0.1) is handled as a special case. The
386  //host starts in Idle Member state for that group on every interface
387  //and never transitions to another state
388  if(entry->addr != IGMP_ALL_SYSTEMS_ADDR)
389  {
390  //A General Query applies to all memberships on the interface from which
391  //the Query is received. A Group-Specific Query applies to membership
392  //in a single group on the interface from which the Query is received
393  if(message->groupAddr == IPV4_UNSPECIFIED_ADDR ||
394  message->groupAddr == entry->addr)
395  {
396  //Delaying Member state?
397  if(entry->state == IGMP_STATE_DELAYING_MEMBER)
398  {
399  //The timer has not yet expired?
400  if(timeCompare(time, entry->timer) < 0)
401  {
402  //If a timer for the group is already running, it is reset to
403  //the random value only if the requested Max Response Time is
404  //less than the remaining value of the running timer
405  if(maxRespTime < (entry->timer - time))
406  {
407  //Restart delay timer
408  entry->timer = time + igmpRand(maxRespTime);
409  }
410  }
411  }
412  //Idle Member state?
413  else if(entry->state == IGMP_STATE_IDLE_MEMBER)
414  {
415  //Switch to the Delaying Member state
417  //Delay the response by a random amount of time
418  entry->timer = time + igmpRand(maxRespTime);
419  }
420  }
421  }
422  }
423  }
424 }
425 
426 
427 /**
428  * @brief Process incoming Membership Report message
429  * @param[in] interface Underlying network interface
430  * @param[in] message Incoming Membership Report message
431  * @param[in] length Message length
432  **/
433 
435  const IgmpMessage *message, size_t length)
436 {
437  uint_t i;
438  Ipv4FilterEntry *entry;
439 
440  //Go through the multicast filter table
441  for(i = 0; i < IPV4_MULTICAST_FILTER_SIZE; i++)
442  {
443  //Point to the current entry
444  entry = &interface->ipv4Context.multicastFilter[i];
445 
446  //Valid entry?
447  if(entry->refCount > 0)
448  {
449  //Report messages are ignored for memberships in
450  //the Non-Member or Idle Member state
451  if(entry->state == IGMP_STATE_DELAYING_MEMBER)
452  {
453  //The Membership Report message matches the current entry?
454  if(message->groupAddr == entry->addr)
455  {
456  //Clear flag
457  entry->flag = FALSE;
458  //Switch to the Idle Member state
459  entry->state = IGMP_STATE_IDLE_MEMBER;
460  }
461  }
462  }
463  }
464 }
465 
466 
467 /**
468  * @brief Send Membership Report message
469  * @param[in] interface Underlying network interface
470  * @param[in] ipAddr IPv4 address specifying the group address
471  * @return Error code
472  **/
473 
475 {
476  error_t error;
477  size_t offset;
479  NetBuffer *buffer;
480  Ipv4PseudoHeader pseudoHeader;
481 
482  //Make sure the specified address is a valid multicast address
484  return ERROR_INVALID_ADDRESS;
485 
486  //The all-systems group (224.0.0.1) is handled as a special case.
487  //The host never sends a report for that group
489  return ERROR_INVALID_ADDRESS;
490 
491  //Allocate a memory buffer to hold an IGMP message
492  buffer = ipAllocBuffer(sizeof(IgmpMessage), &offset);
493  //Failed to allocate memory?
494  if(buffer == NULL)
495  return ERROR_OUT_OF_MEMORY;
496 
497  //Point to the beginning of the IGMP message
498  message = netBufferAt(buffer, offset);
499 
500  //The type of report is determined by the state of the interface
501  if(interface->igmpv1RouterPresent)
503  else
505 
506  //Format the Membership Report message
507  message->maxRespTime = 0;
508  message->checksum = 0;
509  message->groupAddr = ipAddr;
510 
511  //Message checksum calculation
512  message->checksum = ipCalcChecksumEx(buffer, offset, sizeof(IgmpMessage));
513 
514  //Format IPv4 pseudo header
515  pseudoHeader.srcAddr = interface->ipv4Context.addr;
516  pseudoHeader.destAddr = ipAddr;
517  pseudoHeader.reserved = 0;
518  pseudoHeader.protocol = IPV4_PROTOCOL_IGMP;
519  pseudoHeader.length = HTONS(sizeof(IgmpMessage));
520 
521  //Debug message
522  TRACE_INFO("Sending IGMP message (%" PRIuSIZE " bytes)...\r\n", sizeof(IgmpMessage));
523  //Dump message contents for debugging purpose
525 
526  //The Membership Report message is sent to the group being reported
527  error = ipv4SendDatagram(interface, &pseudoHeader, buffer, offset, IGMP_TTL);
528 
529  //Free previously allocated memory
530  netBufferFree(buffer);
531  //Return status code
532  return error;
533 }
534 
535 
536 /**
537  * @brief Send Leave Group message
538  * @param[in] interface Underlying network interface
539  * @param[in] ipAddr IPv4 address specifying the group address being left
540  * @return Error code
541  **/
542 
544 {
545  error_t error;
546  size_t offset;
547  NetBuffer *buffer;
549  Ipv4PseudoHeader pseudoHeader;
550 
551  //Make sure the specified address is a valid multicast address
553  return ERROR_INVALID_ADDRESS;
554 
555  //The all-systems group (224.0.0.1) is handled as a special case.
556  //The host never sends a Leave Group message for that group
558  return ERROR_INVALID_ADDRESS;
559 
560  //If the interface state says the querier is running
561  //IGMPv1, this action should be skipped
562  if(interface->igmpv1RouterPresent)
563  return NO_ERROR;
564 
565  //Allocate a memory buffer to hold an IGMP message
566  buffer = ipAllocBuffer(sizeof(IgmpMessage), &offset);
567  //Failed to allocate memory?
568  if(buffer == NULL)
569  return ERROR_OUT_OF_MEMORY;
570 
571  //Point to the beginning of the IGMP message
572  message = netBufferAt(buffer, offset);
573 
574  //Format the Leave Group message
576  message->maxRespTime = 0;
577  message->checksum = 0;
578  message->groupAddr = ipAddr;
579 
580  //Message checksum calculation
581  message->checksum = ipCalcChecksumEx(buffer, offset, sizeof(IgmpMessage));
582 
583  //Format IPv4 pseudo header
584  pseudoHeader.srcAddr = interface->ipv4Context.addr;
585  pseudoHeader.destAddr = IGMP_ALL_ROUTERS_ADDR;
586  pseudoHeader.reserved = 0;
587  pseudoHeader.protocol = IPV4_PROTOCOL_IGMP;
588  pseudoHeader.length = HTONS(sizeof(IgmpMessage));
589 
590  //Debug message
591  TRACE_INFO("Sending IGMP message (%" PRIuSIZE " bytes)...\r\n", sizeof(IgmpMessage));
592  //Dump message contents for debugging purpose
594 
595  //The Leave Group message is sent to the all-routers multicast group
596  error = ipv4SendDatagram(interface, &pseudoHeader, buffer, offset, IGMP_TTL);
597 
598  //Free previously allocated memory
599  netBufferFree(buffer);
600  //Return status code
601  return error;
602 }
603 
604 
605 /**
606  * @brief Get a random value in the specified range
607  * @param[in] max Upper bound
608  * @return Random value in the specified range
609  **/
610 
611 uint32_t igmpRand(uint32_t max)
612 {
613  //Return a random value in the given range
614  return netGetRand() % (max + 1);
615 }
616 
617 
618 /**
619  * @brief Dump IGMP message for debugging purpose
620  * @param[in] message Pointer to the IGMP message
621  **/
622 
624 {
625  //Dump IGMP message
626  TRACE_DEBUG(" Type = 0x%02" PRIX8 "\r\n", message->type);
627  TRACE_DEBUG(" Max Resp Time = 0x%02" PRIX8 "\r\n", message->maxRespTime);
628  TRACE_DEBUG(" Checksum = 0x%04" PRIX16 "\r\n", ntohs(message->checksum));
629  TRACE_DEBUG(" Group Address = %s\r\n", ipv4AddrToString(message->groupAddr, NULL));
630 }
631 
632 #endif
uint32_t Ipv4Addr
IPv4 network address.
Definition: ipv4.h:232
error_t ipv4SendDatagram(NetInterface *interface, Ipv4PseudoHeader *pseudoHeader, NetBuffer *buffer, size_t offset, uint8_t ttl)
Send an IPv4 datagram.
Definition: ipv4.c:774
uint32_t systime_t
Definition: compiler_port.h:44
#define timeCompare(t1, t2)
Definition: os_port.h:40
char_t * ipv4AddrToString(Ipv4Addr ipAddr, char_t *str)
Convert a binary IPv4 address to dot-decimal notation.
Definition: ipv4.c:1785
void igmpProcessReportMessage(NetInterface *interface, const IgmpMessage *message, size_t length)
Process incoming Membership Report message.
Definition: igmp.c:434
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t time
TCP/IP stack core.
void netBufferFree(NetBuffer *buffer)
Dispose a multi-part buffer.
Definition: net_mem.c:280
#define IGMP_ALL_SYSTEMS_ADDR
Definition: igmp.h:74
Debugging facilities.
size_t netBufferGetLength(const NetBuffer *buffer)
Get the actual length of a multi-part buffer.
Definition: net_mem.c:295
#define IGMP_UNSOLICITED_REPORT_INTERVAL
Definition: igmp.h:51
uint8_t message[]
Definition: chap.h:150
bool_t flag
IGMP flag.
Definition: ipv4.h:308
uint32_t igmpRand(uint32_t max)
Get a random value in the specified range.
Definition: igmp.c:611
IPv4 and IPv6 common routines.
#define IGMP_ALL_ROUTERS_ADDR
Definition: igmp.h:76
#define Ipv4PseudoHeader
Definition: ipv4.h:37
uint_t refCount
Reference count for the current entry.
Definition: ipv4.h:306
void igmpDumpMessage(const IgmpMessage *message)
Dump IGMP message for debugging purpose.
Definition: igmp.c:623
#define HTONS(value)
Definition: cpu_endian.h:388
#define TRUE
Definition: os_port.h:48
uint8_t ipAddr[4]
Definition: mib_common.h:185
systime_t igmpTickCounter
Definition: igmp.c:51
#define IGMP_V1_MAX_RESPONSE_TIME
Definition: igmp.h:58
#define ntohs(value)
Definition: cpu_endian.h:396
uint32_t netGetRand(void)
Get a random value.
Definition: net.c:1523
void * netBufferAt(const NetBuffer *buffer, size_t offset)
Returns a pointer to the data at the specified position.
Definition: net_mem.c:411
NetBuffer * ipAllocBuffer(size_t length, size_t *offset)
Allocate a buffer to hold an IP packet.
Definition: ip.c:596
uint16_t ipCalcChecksumEx(const NetBuffer *buffer, size_t offset, size_t length)
Calculate IP checksum over a multi-part buffer.
Definition: ip.c:471
IPv4 (Internet Protocol Version 4)
Ipv4Addr addr
Multicast address.
Definition: ipv4.h:305
error_t igmpLeaveGroup(NetInterface *interface, Ipv4FilterEntry *entry)
Leave the specified host group.
Definition: igmp.c:131
void igmpProcessMessage(NetInterface *interface, const NetBuffer *buffer, size_t offset)
Process incoming IGMP message.
Definition: igmp.c:278
Structure describing a buffer that spans multiple chunks.
Definition: net_mem.h:86
void igmpLinkChangeEvent(NetInterface *interface)
Callback function for link change event.
Definition: igmp.c:205
IGMP (Internet Group Management Protocol)
#define TRACE_INFO(...)
Definition: debug.h:86
void igmpTick(NetInterface *interface)
IGMP timer handler.
Definition: igmp.c:158
Success.
Definition: error.h:42
error_t igmpInit(NetInterface *interface)
IGMP initialization.
Definition: igmp.c:60
error_t
Error codes.
Definition: error.h:40
#define TRACE_WARNING(...)
Definition: debug.h:78
void igmpProcessQueryMessage(NetInterface *interface, const IgmpMessage *message, size_t length)
Process incoming Membership Query message.
Definition: igmp.c:347
unsigned int uint_t
Definition: compiler_port.h:43
error_t igmpSendReportMessage(NetInterface *interface, Ipv4Addr ipAddr)
Send Membership Report message.
Definition: igmp.c:474
#define PRIuSIZE
Definition: compiler_port.h:72
#define NetInterface
Definition: net.h:34
uint_t state
IGMP host state.
Definition: ipv4.h:307
#define ipv4IsMulticastAddr(ipAddr)
Definition: ipv4.h:150
__start_packed struct @173 IgmpMessage
General IGMP message format.
error_t igmpSendLeaveGroupMessage(NetInterface *interface, Ipv4Addr ipAddr)
Send Leave Group message.
Definition: igmp.c:543
#define IPV4_UNSPECIFIED_ADDR
Definition: ipv4.h:95
#define IPV4_MULTICAST_FILTER_SIZE
Definition: ipv4.h:68
uint8_t maxRespTime
Definition: igmp.h:123
uint8_t length
Definition: dtls_misc.h:140
error_t igmpJoinGroup(NetInterface *interface, Ipv4FilterEntry *entry)
Join the specified host group.
Definition: igmp.c:81
IPv4 multicast filter entry.
Definition: ipv4.h:303
#define FALSE
Definition: os_port.h:44
#define IGMP_V1_ROUTER_PRESENT_TIMEOUT
Definition: igmp.h:65
systime_t timer
Delay timer.
Definition: ipv4.h:309
#define IGMP_TTL
Definition: igmp.h:71
#define TRACE_DEBUG(...)
Definition: debug.h:98