1 /** @file
2  * @brief IPv4 IGMP related functions
3  */
4 
5 /*
6  * Copyright (c) 2021 Intel Corporation
7  *
8  * SPDX-License-Identifier: Apache-2.0
9  */
10 
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_DECLARE(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL);
13 
14 #include <errno.h>
15 #include <zephyr/net/net_core.h>
16 #include <zephyr/net/net_pkt.h>
17 #include <zephyr/net/net_stats.h>
18 #include <zephyr/net/net_context.h>
19 #include <zephyr/net/net_mgmt.h>
20 #include <zephyr/net/igmp.h>
21 #include "net_private.h"
22 #include "connection.h"
23 #include "ipv4.h"
24 #include "net_stats.h"
25 #include "igmp.h"
26 
27 /* Timeout for various buffer allocations in this file. */
28 #define PKT_WAIT_TIME K_MSEC(50)
29 
30 #define IPV4_OPT_HDR_ROUTER_ALERT_LEN 4
31 
32 #define IGMPV2_PAYLOAD_MIN_LEN 8
33 #define IGMPV3_PAYLOAD_MIN_LEN 12
34 
35 static const struct in_addr all_systems = { { { 224, 0, 0, 1 } } };
36 #if defined(CONFIG_NET_IPV4_IGMPV3)
37 static const struct in_addr igmp_multicast_addr = { { { 224, 0, 0, 22 } } };
38 #else
39 static const struct in_addr all_routers = { { { 224, 0, 0, 2 } } };
40 #endif
41 
42 #define dbg_addr(action, pkt_str, src, dst)				\
43 	NET_DBG("%s %s from %s to %s", action, pkt_str,			\
44 		net_sprint_ipv4_addr(src),			\
45 		net_sprint_ipv4_addr(dst));
46 
47 #define dbg_addr_recv(pkt_str, src, dst) \
48 	dbg_addr("Received", pkt_str, src, dst)
49 
50 enum igmp_version {
51 	IGMPV1,
52 	IGMPV2,
53 	IGMPV3,
54 };
55 
igmp_v2_create(struct net_pkt * pkt,const struct in_addr * addr,uint8_t type)56 static int igmp_v2_create(struct net_pkt *pkt, const struct in_addr *addr,
57 			  uint8_t type)
58 {
59 	NET_PKT_DATA_ACCESS_DEFINE(igmp_access,
60 				   struct net_ipv4_igmp_v2_report);
61 	struct net_ipv4_igmp_v2_report *igmp;
62 
63 	igmp = (struct net_ipv4_igmp_v2_report *)
64 				net_pkt_get_data(pkt, &igmp_access);
65 	if (!igmp) {
66 		return -ENOBUFS;
67 	}
68 
69 	igmp->type = type;
70 	igmp->max_rsp = 0U;
71 	net_ipaddr_copy(&igmp->address, addr);
72 	igmp->chksum = 0;
73 
74 	if (net_pkt_set_data(pkt, &igmp_access)) {
75 		return -ENOBUFS;
76 	}
77 
78 	igmp->chksum = net_calc_chksum_igmp(pkt);
79 
80 	net_pkt_set_overwrite(pkt, true);
81 	net_pkt_cursor_init(pkt);
82 
83 	net_pkt_skip(pkt, offsetof(struct net_ipv4_igmp_v2_report, chksum));
84 	if (net_pkt_write(pkt, &igmp->chksum, sizeof(igmp->chksum))) {
85 		return -ENOBUFS;
86 	}
87 
88 	return 0;
89 }
90 
91 #if defined(CONFIG_NET_IPV4_IGMPV3)
igmp_v3_create(struct net_pkt * pkt,uint8_t type,struct net_if_mcast_addr mcast[],size_t mcast_len)92 static int igmp_v3_create(struct net_pkt *pkt, uint8_t type, struct net_if_mcast_addr mcast[],
93 			  size_t mcast_len)
94 {
95 	NET_PKT_DATA_ACCESS_DEFINE(igmp_access, struct net_ipv4_igmp_v3_report);
96 	NET_PKT_DATA_ACCESS_DEFINE(group_record_access, struct net_ipv4_igmp_v3_group_record);
97 	struct net_ipv4_igmp_v3_report *igmp;
98 	struct net_ipv4_igmp_v3_group_record *group_record;
99 
100 	uint16_t group_count = 0;
101 
102 	igmp = (struct net_ipv4_igmp_v3_report *)net_pkt_get_data(pkt, &igmp_access);
103 	if (!igmp) {
104 		return -ENOBUFS;
105 	}
106 
107 	for (int i = 0; i < mcast_len; i++) {
108 		/* We don't need to send an IGMP membership report to the IGMP
109 		 * all systems multicast address of 224.0.0.1 so skip over it.
110 		 * Since the IGMP all systems multicast address is marked as
111 		 * used and joined during init time, we have to check this
112 		 * address separately to skip over it.
113 		 */
114 		if (!mcast[i].is_used || !mcast[i].is_joined ||
115 		    net_ipv4_addr_cmp_raw((uint8_t *)&mcast[i].address.in_addr,
116 					  (uint8_t *)&all_systems)) {
117 			continue;
118 		}
119 
120 		group_count++;
121 	}
122 
123 	igmp->type = type;
124 	igmp->reserved_1 = 0U;
125 	igmp->reserved_2 = 0U;
126 	igmp->groups_len = htons(group_count);
127 	/* Setting initial value of chksum to 0 to calculate chksum as described in RFC 3376
128 	 * ch 4.1.2
129 	 */
130 	igmp->chksum = 0;
131 
132 	if (net_pkt_set_data(pkt, &igmp_access)) {
133 		return -ENOBUFS;
134 	}
135 
136 	for (int i = 0; i < mcast_len; i++) {
137 		/* We don't need to send an IGMP membership report to the IGMP
138 		 * all systems multicast address of 224.0.0.1 so skip over it.
139 		 * Since the IGMP all systems multicast address is marked as
140 		 * used and joined during init time, we have to check this
141 		 * address separately to skip over it.
142 		 */
143 		if (!mcast[i].is_used || !mcast[i].is_joined ||
144 		    net_ipv4_addr_cmp_raw((uint8_t *)&mcast[i].address.in_addr,
145 					  (uint8_t *)&all_systems)) {
146 			continue;
147 		}
148 
149 		group_record = (struct net_ipv4_igmp_v3_group_record *)net_pkt_get_data(
150 			pkt, &group_record_access);
151 		if (!group_record) {
152 			return -ENOBUFS;
153 		}
154 
155 		group_record->type = mcast[i].record_type;
156 		group_record->aux_len = 0U;
157 		net_ipaddr_copy(&group_record->address, &mcast[i].address.in_addr);
158 		group_record->sources_len = htons(mcast[i].sources_len);
159 
160 		if (net_pkt_set_data(pkt, &group_record_access)) {
161 			return -ENOBUFS;
162 		}
163 
164 		for (int j = 0; j < mcast[i].sources_len; j++) {
165 			if (net_pkt_write(pkt, &mcast[i].sources[j].in_addr.s_addr,
166 					  sizeof(mcast[i].sources[j].in_addr.s_addr))) {
167 				return -ENOBUFS;
168 			}
169 		}
170 	}
171 
172 	igmp->chksum = net_calc_chksum_igmp(pkt);
173 
174 	net_pkt_set_overwrite(pkt, true);
175 	net_pkt_cursor_init(pkt);
176 
177 	net_pkt_skip(pkt, offsetof(struct net_ipv4_igmp_v3_report, chksum));
178 	if (net_pkt_write(pkt, &igmp->chksum, sizeof(igmp->chksum))) {
179 		return -ENOBUFS;
180 	}
181 
182 	return 0;
183 }
184 #endif
185 
igmp_v2_create_packet(struct net_pkt * pkt,const struct in_addr * dst,const struct in_addr * group,uint8_t type)186 static int igmp_v2_create_packet(struct net_pkt *pkt, const struct in_addr *dst,
187 				 const struct in_addr *group, uint8_t type)
188 {
189 	const uint32_t router_alert = 0x94040000; /* RFC 2213 ch 2.1 */
190 	int ret;
191 
192 	/* TTL set to 1, RFC 3376 ch 2 */
193 	net_pkt_set_ipv4_ttl(pkt, 1U);
194 
195 	ret = net_ipv4_create_full(pkt,
196 				   net_if_ipv4_select_src_addr(
197 							net_pkt_iface(pkt),
198 							dst),
199 				   dst,
200 				   0U,
201 				   0U,
202 				   0U,
203 				   0U);
204 	if (ret) {
205 		return -ENOBUFS;
206 	}
207 
208 	/* Add router alert option, RFC 3376 ch 2 */
209 	if (net_pkt_write_be32(pkt, router_alert)) {
210 		return -ENOBUFS;
211 	}
212 
213 	net_pkt_set_ipv4_opts_len(pkt, IPV4_OPT_HDR_ROUTER_ALERT_LEN);
214 
215 	return igmp_v2_create(pkt, group, type);
216 }
217 
218 #if defined(CONFIG_NET_IPV4_IGMPV3)
igmp_v3_create_packet(struct net_pkt * pkt,const struct in_addr * dst,struct net_if_mcast_addr mcast[],size_t mcast_len,uint8_t type)219 static int igmp_v3_create_packet(struct net_pkt *pkt, const struct in_addr *dst,
220 				 struct net_if_mcast_addr mcast[], size_t mcast_len, uint8_t type)
221 {
222 	const uint32_t router_alert = 0x94040000; /* RFC 2213 ch 2.1 */
223 	int ret;
224 
225 	/* TTL set to 1, RFC 3376 ch 2 */
226 	net_pkt_set_ipv4_ttl(pkt, 1U);
227 
228 	ret = net_ipv4_create_full(pkt, net_if_ipv4_select_src_addr(net_pkt_iface(pkt), dst), dst,
229 				   0U, 0U, 0U, 0U);
230 	if (ret) {
231 		return -ENOBUFS;
232 	}
233 
234 	/* Add router alert option, RFC 3376 ch 2 */
235 	if (net_pkt_write_be32(pkt, router_alert)) {
236 		return -ENOBUFS;
237 	}
238 
239 	net_pkt_set_ipv4_opts_len(pkt, IPV4_OPT_HDR_ROUTER_ALERT_LEN);
240 
241 	return igmp_v3_create(pkt, type, mcast, mcast_len);
242 }
243 #endif
244 
igmp_send(struct net_pkt * pkt)245 static int igmp_send(struct net_pkt *pkt)
246 {
247 	int ret;
248 
249 	net_pkt_cursor_init(pkt);
250 	net_ipv4_finalize(pkt, IPPROTO_IGMP);
251 
252 	ret = net_send_data(pkt);
253 	if (ret < 0) {
254 		net_stats_update_ipv4_igmp_drop(net_pkt_iface(pkt));
255 		return ret;
256 	}
257 
258 	net_stats_update_ipv4_igmp_sent(net_pkt_iface(pkt));
259 
260 	return 0;
261 }
262 
send_igmp_report(struct net_if * iface,struct net_ipv4_igmp_v2_query * igmp_v2_hdr)263 static int send_igmp_report(struct net_if *iface,
264 			    struct net_ipv4_igmp_v2_query *igmp_v2_hdr)
265 {
266 	struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4;
267 	struct net_pkt *pkt = NULL;
268 	int i, count = 0;
269 	int ret = 0;
270 
271 	if (!ipv4) {
272 		return -ENOENT;
273 	}
274 
275 	for (i = 0; i < NET_IF_MAX_IPV4_MADDR; i++) {
276 		/* We don't need to send an IGMP membership report to the IGMP
277 		 * all systems multicast address of 224.0.0.1 so skip over it.
278 		 * Since the IGMP all systems multicast address is marked as
279 		 * used and joined during init time, we have to check this
280 		 * address separately to skip over it.
281 		 */
282 		if (!ipv4->mcast[i].is_used || !ipv4->mcast[i].is_joined ||
283 			net_ipv4_addr_cmp_raw((uint8_t *)&ipv4->mcast[i].address.in_addr,
284 					(uint8_t *)&all_systems)) {
285 			continue;
286 		}
287 
288 		count++;
289 	}
290 
291 	if (count == 0) {
292 		return -ESRCH;
293 	}
294 
295 	for (i = 0; i < NET_IF_MAX_IPV4_MADDR; i++) {
296 		/* We don't need to send an IGMP membership report to the IGMP
297 		 * all systems multicast address of 224.0.0.1 so skip over it.
298 		 * Since the IGMP all systems multicast address is marked as
299 		 * used and joined during init time, we have to check this
300 		 * address separately to skip over it.
301 		 */
302 		if (!ipv4->mcast[i].is_used || !ipv4->mcast[i].is_joined ||
303 			net_ipv4_addr_cmp_raw((uint8_t *)&ipv4->mcast[i].address.in_addr,
304 					(uint8_t *)&all_systems)) {
305 			continue;
306 		}
307 
308 		pkt = net_pkt_alloc_with_buffer(iface,
309 					IPV4_OPT_HDR_ROUTER_ALERT_LEN +
310 					sizeof(struct net_ipv4_igmp_v2_report),
311 					AF_INET, IPPROTO_IGMP,
312 					PKT_WAIT_TIME);
313 		if (!pkt) {
314 			return -ENOMEM;
315 		}
316 
317 		/* Send the IGMP V2 membership report to the group multicast
318 		 * address, as per RFC 2236 Section 9.
319 		 */
320 		ret = igmp_v2_create_packet(pkt, &ipv4->mcast[i].address.in_addr,
321 					    &ipv4->mcast[i].address.in_addr,
322 					    NET_IPV4_IGMP_REPORT_V2);
323 		if (ret < 0) {
324 			goto drop;
325 		}
326 
327 		ret = igmp_send(pkt);
328 		if (ret < 0) {
329 			goto drop;
330 		}
331 
332 		/* So that we do not free the data while it is being sent */
333 		pkt = NULL;
334 	}
335 
336 drop:
337 	if (pkt) {
338 		net_pkt_unref(pkt);
339 	}
340 
341 	return ret;
342 }
343 
344 #if defined(CONFIG_NET_IPV4_IGMPV3)
send_igmp_v3_report(struct net_if * iface,struct net_ipv4_igmp_v3_query * igmp_v3_hdr)345 static int send_igmp_v3_report(struct net_if *iface, struct net_ipv4_igmp_v3_query *igmp_v3_hdr)
346 {
347 	struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4;
348 	struct net_pkt *pkt = NULL;
349 	int i, group_count = 0, source_count = 0;
350 	int ret = 0;
351 
352 	if (!ipv4) {
353 		return -ENOENT;
354 	}
355 
356 	for (i = 0; i < NET_IF_MAX_IPV4_MADDR; i++) {
357 		/* We don't need to send an IGMP membership report to the IGMP
358 		 * all systems multicast address of 224.0.0.1 so skip over it.
359 		 * Since the IGMP all systems multicast address is marked as
360 		 * used and joined during init time, we have to check this
361 		 * address separately to skip over it.
362 		 */
363 		if (!ipv4->mcast[i].is_used || !ipv4->mcast[i].is_joined ||
364 		    net_ipv4_addr_cmp_raw((uint8_t *)&ipv4->mcast[i].address.in_addr,
365 					  (uint8_t *)&all_systems)) {
366 			continue;
367 		}
368 
369 		group_count++;
370 		source_count += ipv4->mcast[i].sources_len;
371 	}
372 
373 	if (group_count == 0) {
374 		return -ESRCH;
375 	}
376 
377 	pkt = net_pkt_alloc_with_buffer(
378 		iface,
379 		IPV4_OPT_HDR_ROUTER_ALERT_LEN + sizeof(struct net_ipv4_igmp_v3_report) +
380 			sizeof(struct net_ipv4_igmp_v3_group_record) * group_count +
381 			sizeof(struct in_addr) * source_count,
382 		AF_INET, IPPROTO_IGMP, PKT_WAIT_TIME);
383 	if (!pkt) {
384 		return -ENOMEM;
385 	}
386 
387 	/* Send the IGMP V3 membership report to the igmp multicast
388 	 * address, as per RFC 3376 Section 4.2.14.
389 	 */
390 
391 	ret = igmp_v3_create_packet(pkt, &igmp_multicast_addr, ipv4->mcast, NET_IF_MAX_IPV4_MADDR,
392 				    NET_IPV4_IGMP_REPORT_V3);
393 	if (ret < 0) {
394 		goto drop;
395 	}
396 
397 	ret = igmp_send(pkt);
398 	if (ret < 0) {
399 		goto drop;
400 	}
401 
402 	/* So that we do not free the data while it is being sent */
403 	pkt = NULL;
404 
405 drop:
406 	if (pkt) {
407 		net_pkt_unref(pkt);
408 	}
409 
410 	return ret;
411 }
412 #endif
413 
net_ipv4_igmp_input(struct net_pkt * pkt,struct net_ipv4_hdr * ip_hdr)414 enum net_verdict net_ipv4_igmp_input(struct net_pkt *pkt, struct net_ipv4_hdr *ip_hdr)
415 {
416 	int ret;
417 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(igmpv2_access, struct net_ipv4_igmp_v2_query);
418 
419 	struct net_ipv4_igmp_v2_query *igmpv2_hdr;
420 	int igmp_buf_len =
421 		pkt->buffer->len - (net_pkt_ip_hdr_len(pkt) + net_pkt_ipv4_opts_len(pkt));
422 #if defined(CONFIG_NET_IPV4_IGMPV3)
423 	struct net_ipv4_igmp_v3_query *igmpv3_hdr;
424 	enum igmp_version version;
425 
426 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(igmpv3_access, struct net_ipv4_igmp_v3_query);
427 
428 	/* Detect IGMP type (RFC 3376 ch 7.1) */
429 	if (igmp_buf_len == IGMPV2_PAYLOAD_MIN_LEN) {
430 		/* IGMPv1/2 detected */
431 		version = IGMPV2;
432 	} else if (igmp_buf_len >= IGMPV3_PAYLOAD_MIN_LEN) {
433 		/* IGMPv3 detected */
434 		version = IGMPV3;
435 	} else {
436 #else
437 	if (igmp_buf_len < IGMPV2_PAYLOAD_MIN_LEN) {
438 #endif
439 		NET_DBG("DROP: unsupported payload length");
440 		return NET_DROP;
441 	}
442 
443 	if (!net_ipv4_addr_cmp_raw(ip_hdr->dst, (uint8_t *)&all_systems)) {
444 		NET_DBG("DROP: Invalid dst address");
445 		return NET_DROP;
446 	}
447 
448 #if defined(CONFIG_NET_IPV4_IGMPV3)
449 	if (version == IGMPV3) {
450 		igmpv3_hdr = (struct net_ipv4_igmp_v3_query *)net_pkt_get_data(pkt, &igmpv3_access);
451 		if (!igmpv3_hdr) {
452 			NET_DBG("DROP: NULL %sv3 header", "IGMP");
453 			return NET_DROP;
454 		}
455 	} else {
456 #endif
457 		igmpv2_hdr = (struct net_ipv4_igmp_v2_query *)net_pkt_get_data(pkt, &igmpv2_access);
458 		if (!igmpv2_hdr) {
459 			NET_DBG("DROP: NULL %s header", "IGMP");
460 			return NET_DROP;
461 		}
462 #if defined(CONFIG_NET_IPV4_IGMPV3)
463 	}
464 #endif
465 
466 	ret = net_calc_chksum_igmp(pkt);
467 	if (ret != 0u) {
468 		NET_DBG("DROP: Invalid checksum");
469 		goto drop;
470 	}
471 
472 #if defined(CONFIG_NET_IPV4_IGMPV3)
473 	if (version == IGMPV3) {
474 		net_pkt_acknowledge_data(pkt, &igmpv3_access);
475 	} else {
476 #endif
477 		net_pkt_acknowledge_data(pkt, &igmpv2_access);
478 #if defined(CONFIG_NET_IPV4_IGMPV3)
479 	}
480 #endif
481 
482 	dbg_addr_recv("Internet Group Management Protocol", &ip_hdr->src, &ip_hdr->dst);
483 
484 	net_stats_update_ipv4_igmp_recv(net_pkt_iface(pkt));
485 
486 #if defined(CONFIG_NET_IPV4_IGMPV3)
487 	if (version == IGMPV3) {
488 		(void)send_igmp_v3_report(net_pkt_iface(pkt), igmpv3_hdr);
489 	} else {
490 #endif
491 		(void)send_igmp_report(net_pkt_iface(pkt), igmpv2_hdr);
492 #if defined(CONFIG_NET_IPV4_IGMPV3)
493 	}
494 #endif
495 
496 	net_pkt_unref(pkt);
497 
498 	return NET_OK;
499 
500 drop:
501 	net_stats_update_ipv4_igmp_drop(net_pkt_iface(pkt));
502 
503 	return NET_DROP;
504 }
505 
506 #if !defined(CONFIG_NET_IPV4_IGMPV3)
507 static int igmp_send_generic(struct net_if *iface,
508 			     const struct in_addr *addr,
509 			     bool join)
510 {
511 	struct net_pkt *pkt;
512 	int ret;
513 
514 	pkt = net_pkt_alloc_with_buffer(iface,
515 					IPV4_OPT_HDR_ROUTER_ALERT_LEN +
516 					sizeof(struct net_ipv4_igmp_v2_report),
517 					AF_INET, IPPROTO_IGMP,
518 					PKT_WAIT_TIME);
519 	if (!pkt) {
520 		return -ENOMEM;
521 	}
522 
523 	/* Send the IGMP V2 membership report to the group multicast
524 	 * address, as per RFC 2236 Section 9. The leave report
525 	 * should be sent to the ALL ROUTERS multicast address (224.0.0.2)
526 	 */
527 	ret = igmp_v2_create_packet(pkt,
528 				join ? addr : &all_routers, addr,
529 				join ? NET_IPV4_IGMP_REPORT_V2 : NET_IPV4_IGMP_LEAVE);
530 	if (ret < 0) {
531 		goto drop;
532 	}
533 
534 	ret = igmp_send(pkt);
535 	if (ret < 0) {
536 		goto drop;
537 	}
538 
539 	return 0;
540 
541 drop:
542 	net_pkt_unref(pkt);
543 
544 	return ret;
545 }
546 #endif
547 
548 #if defined(CONFIG_NET_IPV4_IGMPV3)
549 static int igmpv3_send_generic(struct net_if *iface, struct net_if_mcast_addr *mcast)
550 {
551 	struct net_pkt *pkt;
552 	int ret;
553 
554 	pkt = net_pkt_alloc_with_buffer(iface,
555 					IPV4_OPT_HDR_ROUTER_ALERT_LEN +
556 						sizeof(struct net_ipv4_igmp_v3_report) +
557 						sizeof(struct net_ipv4_igmp_v3_group_record) +
558 						sizeof(struct in_addr) * mcast->sources_len,
559 					AF_INET, IPPROTO_IGMP, PKT_WAIT_TIME);
560 	if (!pkt) {
561 		return -ENOMEM;
562 	}
563 
564 	ret = igmp_v3_create_packet(pkt, &igmp_multicast_addr, mcast, 1, NET_IPV4_IGMP_REPORT_V3);
565 	if (ret < 0) {
566 		goto drop;
567 	}
568 
569 	ret = igmp_send(pkt);
570 	if (ret < 0) {
571 		goto drop;
572 	}
573 
574 	return 0;
575 
576 drop:
577 	net_pkt_unref(pkt);
578 
579 	return ret;
580 }
581 #endif
582 
583 int net_ipv4_igmp_join(struct net_if *iface, const struct in_addr *addr,
584 		       const struct igmp_param *param)
585 {
586 	struct net_if_mcast_addr *maddr;
587 	int ret;
588 
589 #if defined(CONFIG_NET_IPV4_IGMPV3)
590 	if (param != NULL) {
591 		if (param->sources_len > CONFIG_NET_IF_MCAST_IPV4_SOURCE_COUNT) {
592 			return -ENOMEM;
593 		}
594 	}
595 #endif
596 
597 	maddr = net_if_ipv4_maddr_lookup(addr, &iface);
598 	if (maddr && net_if_ipv4_maddr_is_joined(maddr)) {
599 		return -EALREADY;
600 	}
601 
602 	if (!maddr) {
603 		maddr = net_if_ipv4_maddr_add(iface, addr);
604 		if (!maddr) {
605 			return -ENOMEM;
606 		}
607 	}
608 
609 #if defined(CONFIG_NET_IPV4_IGMPV3)
610 	if (param != NULL) {
611 		maddr->record_type = param->include ? IGMPV3_CHANGE_TO_INCLUDE_MODE
612 						    : IGMPV3_CHANGE_TO_EXCLUDE_MODE;
613 		maddr->sources_len = param->sources_len;
614 		for (int i = 0; i < param->sources_len; i++) {
615 			net_ipaddr_copy(&maddr->sources[i].in_addr.s_addr,
616 					&param->source_list[i].s_addr);
617 		}
618 	} else {
619 		maddr->record_type = IGMPV3_CHANGE_TO_EXCLUDE_MODE;
620 	}
621 #endif
622 
623 	net_if_ipv4_maddr_join(iface, maddr);
624 
625 #if defined(CONFIG_NET_IPV4_IGMPV3)
626 	ret = igmpv3_send_generic(iface, maddr);
627 #else
628 	ret = igmp_send_generic(iface, addr, true);
629 #endif
630 	if (ret < 0) {
631 		net_if_ipv4_maddr_leave(iface, maddr);
632 		return ret;
633 	}
634 
635 #if defined(CONFIG_NET_IPV4_IGMPV3)
636 	if (param != NULL) {
637 		/* Updating the record type for further use after sending the join report */
638 		maddr->record_type =
639 			param->include ? IGMPV3_MODE_IS_INCLUDE : IGMPV3_MODE_IS_EXCLUDE;
640 	}
641 #endif
642 
643 	net_if_mcast_monitor(iface, &maddr->address, true);
644 
645 	net_mgmt_event_notify_with_info(NET_EVENT_IPV4_MCAST_JOIN, iface, &maddr->address.in_addr,
646 					sizeof(struct in_addr));
647 
648 	return ret;
649 }
650 
651 int net_ipv4_igmp_leave(struct net_if *iface, const struct in_addr *addr)
652 {
653 	struct net_if_mcast_addr *maddr;
654 	int ret;
655 
656 	maddr = net_if_ipv4_maddr_lookup(addr, &iface);
657 	if (!maddr) {
658 		return -ENOENT;
659 	}
660 
661 #if defined(CONFIG_NET_IPV4_IGMPV3)
662 	maddr->record_type = IGMPV3_CHANGE_TO_INCLUDE_MODE;
663 	maddr->sources_len = 0;
664 
665 	ret = igmpv3_send_generic(iface, maddr);
666 #else
667 	ret = igmp_send_generic(iface, addr, false);
668 #endif
669 	if (ret < 0) {
670 		return ret;
671 	}
672 
673 	if (!net_if_ipv4_maddr_rm(iface, addr)) {
674 		return -EINVAL;
675 	}
676 
677 	net_if_ipv4_maddr_leave(iface, maddr);
678 
679 	net_if_mcast_monitor(iface, &maddr->address, false);
680 
681 	net_mgmt_event_notify_with_info(NET_EVENT_IPV4_MCAST_LEAVE, iface, &maddr->address.in_addr,
682 					sizeof(struct in_addr));
683 	return ret;
684 }
685 
686 void net_ipv4_igmp_init(struct net_if *iface)
687 {
688 	struct net_if_mcast_addr *maddr;
689 
690 	/* Ensure multicast addresses are available */
691 	if (CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT < 1) {
692 		return;
693 	}
694 
695 	/* This code adds the IGMP all systems 224.0.0.1 multicast address
696 	 * to the list of multicast addresses of the given interface.
697 	 * The address is marked as joined. However, an IGMP membership
698 	 * report is not generated for this address. Populating this
699 	 * address in the list of multicast addresses of the interface
700 	 * and marking it as joined is helpful for multicast hash filter
701 	 * implementations that need a list of multicast addresses it needs
702 	 * to add to the multicast hash filter after a multicast address
703 	 * has been removed from the membership list.
704 	 */
705 	maddr = net_if_ipv4_maddr_lookup(&all_systems, &iface);
706 	if (maddr && net_if_ipv4_maddr_is_joined(maddr)) {
707 		return;
708 	}
709 
710 	if (!maddr) {
711 		maddr = net_if_ipv4_maddr_add(iface, &all_systems);
712 		if (!maddr) {
713 			return;
714 		}
715 	}
716 
717 	net_if_ipv4_maddr_join(iface, maddr);
718 
719 	net_if_mcast_monitor(iface, &maddr->address, true);
720 }
721