1 /** @file
2 * @brief IPv6 MLD related functions
3 */
4
5 /*
6 * Copyright (c) 2018 Intel Corporation
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL);
13
14 #include <errno.h>
15 #include <zephyr/net/mld.h>
16 #include <zephyr/net/net_core.h>
17 #include <zephyr/net/net_pkt.h>
18 #include <zephyr/net/net_stats.h>
19 #include <zephyr/net/net_context.h>
20 #include <zephyr/net/net_mgmt.h>
21 #include <zephyr/net/icmp.h>
22 #include "net_private.h"
23 #include "connection.h"
24 #include "icmpv6.h"
25 #include "udp_internal.h"
26 #include "tcp_internal.h"
27 #include "ipv6.h"
28 #include "nbr.h"
29 #include "6lo.h"
30 #include "route.h"
31 #include "net_stats.h"
32
33 /* Timeout for various buffer allocations in this file. */
34 #define PKT_WAIT_TIME K_MSEC(50)
35
36 #define MLDv2_MCAST_RECORD_LEN sizeof(struct net_icmpv6_mld_mcast_record)
37 #define IPV6_OPT_HDR_ROUTER_ALERT_LEN 8
38 #define MLDV2_REPORT_RESERVED_BYTES 2
39
40 #define MLDv2_LEN (MLDv2_MCAST_RECORD_LEN + sizeof(struct in6_addr))
41
42 /* Internal structure used for appending multicast routes to MLDv2 reports */
43 struct mcast_route_appending_info {
44 int status;
45 struct net_pkt *pkt;
46 struct net_if *iface;
47 size_t skipped;
48 };
49
mld_create(struct net_pkt * pkt,const struct in6_addr * addr,uint8_t record_type)50 static int mld_create(struct net_pkt *pkt,
51 const struct in6_addr *addr,
52 uint8_t record_type)
53 {
54 NET_PKT_DATA_ACCESS_DEFINE(mld_access,
55 struct net_icmpv6_mld_mcast_record);
56 struct net_icmpv6_mld_mcast_record *mld;
57
58 mld = (struct net_icmpv6_mld_mcast_record *)
59 net_pkt_get_data(pkt, &mld_access);
60 if (!mld) {
61 return -ENOBUFS;
62 }
63
64 mld->record_type = record_type;
65 mld->aux_data_len = 0U;
66 mld->num_sources = 0U;
67
68 net_ipv6_addr_copy_raw(mld->mcast_address, (uint8_t *)addr);
69
70 if (net_pkt_set_data(pkt, &mld_access)) {
71 return -ENOBUFS;
72 }
73
74 return 0;
75 }
76
mld_create_packet(struct net_pkt * pkt,uint16_t count)77 static int mld_create_packet(struct net_pkt *pkt, uint16_t count)
78 {
79 struct in6_addr dst;
80
81 /* Sent to all MLDv2-capable routers */
82 net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
83
84 net_pkt_set_ipv6_hop_limit(pkt, 1); /* RFC 3810 ch 7.4 */
85
86 if (net_ipv6_create(pkt, net_if_ipv6_select_src_addr(
87 net_pkt_iface(pkt), &dst),
88 &dst)) {
89 return -ENOBUFS;
90 }
91
92 /* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
93 if (net_pkt_write_u8(pkt, IPPROTO_ICMPV6) ||
94 net_pkt_write_u8(pkt, 0)) {
95 return -ENOBUFS;
96 }
97
98 /* IPv6 router alert option is described in RFC 2711.
99 * - 0x0502 RFC 2711 ch 2.1
100 * - MLD (value 0)
101 * - 2 bytes of padding
102 */
103 if (net_pkt_write_be16(pkt, 0x0502) ||
104 net_pkt_write_be16(pkt, 0) ||
105 net_pkt_write_be16(pkt, 0)) {
106 return -ENOBUFS;
107 }
108
109 net_pkt_set_ipv6_ext_len(pkt, IPV6_OPT_HDR_ROUTER_ALERT_LEN);
110
111 /* ICMPv6 header + reserved space + count.
112 * MLDv6 stuff will come right after
113 */
114 if (net_icmpv6_create(pkt, NET_ICMPV6_MLDv2, 0) ||
115 net_pkt_write_be16(pkt, 0) ||
116 net_pkt_write_be16(pkt, count)) {
117 return -ENOBUFS;
118 }
119
120 net_pkt_set_ipv6_next_hdr(pkt, NET_IPV6_NEXTHDR_HBHO);
121
122 return 0;
123 }
124
mld_send(struct net_pkt * pkt)125 static int mld_send(struct net_pkt *pkt)
126 {
127 int ret;
128
129 net_pkt_cursor_init(pkt);
130 net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
131
132 ret = net_send_data(pkt);
133 if (ret < 0) {
134 net_stats_update_icmp_drop(net_pkt_iface(pkt));
135 net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
136
137 net_pkt_unref(pkt);
138
139 return ret;
140 }
141
142 net_stats_update_icmp_sent(net_pkt_iface(pkt));
143 net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
144
145 return 0;
146 }
147
148 #if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
count_mcast_routes(struct net_route_entry_mcast * entry,void * user_data)149 static void count_mcast_routes(struct net_route_entry_mcast *entry, void *user_data)
150 {
151 (*((int *)user_data))++;
152 }
153
append_mcast_routes(struct net_route_entry_mcast * entry,void * user_data)154 static void append_mcast_routes(struct net_route_entry_mcast *entry, void *user_data)
155 {
156 struct mcast_route_appending_info *info = (struct mcast_route_appending_info *)user_data;
157 struct net_if_mcast_addr *mcasts = info->iface->config.ip.ipv6->mcast;
158
159 if (info->status != 0 || entry->prefix_len != 128) {
160 return;
161 }
162
163 for (int i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
164 if (!mcasts[i].is_used || !mcasts[i].is_joined) {
165 continue;
166 }
167
168 if (net_ipv6_addr_cmp(&entry->group, &mcasts[i].address.in6_addr)) {
169 /* Address was already added to the report */
170 info->skipped++;
171 return;
172 }
173 }
174
175 info->status = mld_create(info->pkt, &entry->group, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
176 }
177 #endif
178
net_ipv6_mld_send_single(struct net_if * iface,const struct in6_addr * addr,uint8_t mode)179 int net_ipv6_mld_send_single(struct net_if *iface, const struct in6_addr *addr, uint8_t mode)
180 {
181 struct net_pkt *pkt;
182 int ret;
183
184 pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
185 NET_ICMPV6_UNUSED_LEN +
186 MLDv2_MCAST_RECORD_LEN +
187 sizeof(struct in6_addr),
188 AF_INET6, IPPROTO_ICMPV6,
189 PKT_WAIT_TIME);
190 if (!pkt) {
191 return -ENOMEM;
192 }
193
194 if (mld_create_packet(pkt, 1) ||
195 mld_create(pkt, addr, mode)) {
196 ret = -ENOBUFS;
197 goto drop;
198 }
199
200 ret = mld_send(pkt);
201 if (ret) {
202 goto drop;
203 }
204
205 return 0;
206
207 drop:
208 net_pkt_unref(pkt);
209
210 return ret;
211 }
212
net_ipv6_mld_join(struct net_if * iface,const struct in6_addr * addr)213 int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr)
214 {
215 struct net_if_mcast_addr *maddr;
216 int ret;
217
218 maddr = net_if_ipv6_maddr_lookup(addr, &iface);
219 if (maddr && net_if_ipv6_maddr_is_joined(maddr)) {
220 return -EALREADY;
221 }
222
223 if (!maddr) {
224 maddr = net_if_ipv6_maddr_add(iface, addr);
225 if (!maddr) {
226 return -ENOMEM;
227 }
228 }
229
230 if (net_if_flag_is_set(iface, NET_IF_IPV6_NO_MLD)) {
231 return 0;
232 }
233
234 if (!net_if_is_up(iface)) {
235 return -ENETDOWN;
236 }
237
238 ret = net_ipv6_mld_send_single(iface, addr, NET_IPV6_MLDv2_CHANGE_TO_EXCLUDE_MODE);
239 if (ret < 0) {
240 return ret;
241 }
242
243 net_if_ipv6_maddr_join(iface, maddr);
244
245 net_if_mcast_monitor(iface, &maddr->address, true);
246
247 net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_JOIN, iface,
248 &maddr->address.in6_addr,
249 sizeof(struct in6_addr));
250
251 return ret;
252 }
253
net_ipv6_mld_leave(struct net_if * iface,const struct in6_addr * addr)254 int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr)
255 {
256 struct net_if_mcast_addr *maddr;
257 int ret;
258
259 maddr = net_if_ipv6_maddr_lookup(addr, &iface);
260 if (!maddr) {
261 return -ENOENT;
262 }
263
264 if (!net_if_ipv6_maddr_rm(iface, addr)) {
265 return -EINVAL;
266 }
267
268 if (net_if_flag_is_set(iface, NET_IF_IPV6_NO_MLD)) {
269 return 0;
270 }
271
272 ret = net_ipv6_mld_send_single(iface, addr, NET_IPV6_MLDv2_CHANGE_TO_INCLUDE_MODE);
273 if (ret < 0) {
274 return ret;
275 }
276
277 net_if_mcast_monitor(iface, &maddr->address, false);
278
279 net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_LEAVE, iface,
280 &maddr->address.in6_addr,
281 sizeof(struct in6_addr));
282
283 return ret;
284 }
285
send_mld_report(struct net_if * iface)286 static int send_mld_report(struct net_if *iface)
287 {
288 struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
289 struct net_pkt *pkt;
290 int i, count = 0;
291 int ret;
292
293 NET_ASSERT(ipv6);
294
295 for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
296 if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
297 continue;
298 }
299
300 count++;
301 }
302
303 #if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
304 /* Increase number of slots by a number of multicast routes that
305 * can be later added to the report. Checking for duplicates is done
306 * while appending an entry.
307 */
308 net_route_mcast_foreach(count_mcast_routes, NULL, (void *)&count);
309 #endif
310
311 pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
312 NET_ICMPV6_UNUSED_LEN +
313 count * MLDv2_MCAST_RECORD_LEN,
314 AF_INET6, IPPROTO_ICMPV6,
315 PKT_WAIT_TIME);
316 if (!pkt) {
317 return -ENOBUFS;
318 }
319
320 ret = mld_create_packet(pkt, count);
321 if (ret < 0) {
322 goto drop;
323 }
324
325 for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
326 if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
327 continue;
328 }
329
330 ret = mld_create(pkt, &ipv6->mcast[i].address.in6_addr,
331 NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
332 if (ret < 0) {
333 goto drop;
334 }
335 }
336
337 #if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
338 /* Append information about multicast routes as packets will be
339 * forwarded to these interfaces on reception.
340 */
341 struct mcast_route_appending_info info;
342
343 info.status = 0;
344 info.pkt = pkt;
345 info.iface = iface;
346 info.skipped = 0;
347
348 net_route_mcast_foreach(append_mcast_routes, NULL, &info);
349
350 ret = info.status;
351 if (ret < 0) {
352 goto drop;
353 }
354
355 /* We may have skipped duplicated addresses that we reserved space for,
356 * modify number of records.
357 */
358 if (info.skipped) {
359 net_pkt_cursor_init(pkt);
360 net_pkt_set_overwrite(pkt, true);
361
362 net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt) +
363 sizeof(struct net_icmp_hdr) + MLDV2_REPORT_RESERVED_BYTES);
364
365 count -= info.skipped;
366
367 ret = net_pkt_write_be16(pkt, count);
368 if (ret < 0) {
369 goto drop;
370 }
371
372 net_pkt_remove_tail(pkt, info.skipped * sizeof(struct net_icmpv6_mld_mcast_record));
373 }
374 #endif
375
376 ret = mld_send(pkt);
377 if (ret < 0) {
378 goto drop;
379 }
380
381 return 0;
382
383 drop:
384 net_pkt_unref(pkt);
385
386 return ret;
387 }
388
389 #define dbg_addr(action, pkt_str, src, dst) \
390 do { \
391 NET_DBG("%s %s from %s to %s", action, pkt_str, \
392 net_sprint_ipv6_addr(src), \
393 net_sprint_ipv6_addr(dst)); \
394 } while (0)
395
396 #define dbg_addr_recv(pkt_str, src, dst) \
397 dbg_addr("Received", pkt_str, src, dst)
398
handle_mld_query(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)399 static int handle_mld_query(struct net_icmp_ctx *ctx,
400 struct net_pkt *pkt,
401 struct net_icmp_ip_hdr *hdr,
402 struct net_icmp_hdr *icmp_hdr,
403 void *user_data)
404 {
405 NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(mld_access,
406 struct net_icmpv6_mld_query);
407 struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
408 uint16_t length = net_pkt_get_len(pkt);
409 struct net_icmpv6_mld_query *mld_query;
410 uint16_t pkt_len;
411 int ret = -EIO;
412
413 if (net_pkt_remaining_data(pkt) < sizeof(struct net_icmpv6_mld_query)) {
414 /* MLDv1 query, drop. */
415 ret = 0;
416 goto drop;
417 }
418
419 mld_query = (struct net_icmpv6_mld_query *)
420 net_pkt_get_data(pkt, &mld_access);
421 if (!mld_query) {
422 NET_DBG("DROP: NULL MLD query");
423 goto drop;
424 }
425
426 net_pkt_acknowledge_data(pkt, &mld_access);
427
428 dbg_addr_recv("Multicast Listener Query", &ip_hdr->src, &ip_hdr->dst);
429
430 net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
431
432 mld_query->num_sources = ntohs(mld_query->num_sources);
433
434 pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
435 sizeof(struct net_icmp_hdr) +
436 sizeof(struct net_icmpv6_mld_query) +
437 sizeof(struct in6_addr) * mld_query->num_sources;
438
439 if (length < pkt_len || pkt_len > NET_IPV6_MTU ||
440 ip_hdr->hop_limit != 1U || icmp_hdr->code != 0U) {
441 goto drop;
442 }
443
444 /* Currently we only support an unspecified address query. */
445 if (!net_ipv6_addr_cmp_raw(mld_query->mcast_address,
446 (uint8_t *)net_ipv6_unspecified_address())) {
447 NET_DBG("DROP: only supporting unspecified address query");
448 goto drop;
449 }
450
451 return send_mld_report(net_pkt_iface(pkt));
452
453 drop:
454 net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
455
456 return ret;
457 }
458
net_ipv6_mld_init(void)459 void net_ipv6_mld_init(void)
460 {
461 static struct net_icmp_ctx ctx;
462 int ret;
463
464 ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_MLD_QUERY, 0, handle_mld_query);
465 if (ret < 0) {
466 NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_MLD_QUERY),
467 ret);
468 }
469 }
470