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 <logging/log.h>
12 LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL);
13
14 #include <errno.h>
15 #include <net/net_core.h>
16 #include <net/net_pkt.h>
17 #include <net/net_stats.h>
18 #include <net/net_context.h>
19 #include <net/net_mgmt.h>
20 #include "net_private.h"
21 #include "connection.h"
22 #include "icmpv6.h"
23 #include "udp_internal.h"
24 #include "tcp_internal.h"
25 #include "ipv6.h"
26 #include "nbr.h"
27 #include "6lo.h"
28 #include "route.h"
29 #include "net_stats.h"
30
31 /* Timeout for various buffer allocations in this file. */
32 #define PKT_WAIT_TIME K_MSEC(50)
33
34 #define MLDv2_MCAST_RECORD_LEN sizeof(struct net_icmpv6_mld_mcast_record)
35 #define IPV6_OPT_HDR_ROUTER_ALERT_LEN 8
36
37 #define MLDv2_LEN (MLDv2_MCAST_RECORD_LEN + sizeof(struct in6_addr))
38
mld_create(struct net_pkt * pkt,const struct in6_addr * addr,uint8_t record_type,uint16_t num_sources)39 static int mld_create(struct net_pkt *pkt,
40 const struct in6_addr *addr,
41 uint8_t record_type,
42 uint16_t num_sources)
43 {
44 NET_PKT_DATA_ACCESS_DEFINE(mld_access,
45 struct net_icmpv6_mld_mcast_record);
46 struct net_icmpv6_mld_mcast_record *mld;
47
48 mld = (struct net_icmpv6_mld_mcast_record *)
49 net_pkt_get_data(pkt, &mld_access);
50 if (!mld) {
51 return -ENOBUFS;
52 }
53
54 mld->record_type = record_type;
55 mld->aux_data_len = 0U;
56 mld->num_sources = htons(num_sources);
57
58 net_ipaddr_copy(&mld->mcast_address, addr);
59
60 if (net_pkt_set_data(pkt, &mld_access)) {
61 return -ENOBUFS;
62 }
63
64 if (num_sources > 0) {
65 /* All source addresses, RFC 3810 ch 3 */
66 if (net_pkt_write(pkt,
67 net_ipv6_unspecified_address()->s6_addr,
68 sizeof(struct in6_addr))) {
69 return -ENOBUFS;
70 }
71 }
72
73 return 0;
74 }
75
mld_create_packet(struct net_pkt * pkt,uint16_t count)76 static int mld_create_packet(struct net_pkt *pkt, uint16_t count)
77 {
78 struct in6_addr dst;
79
80 /* Sent to all MLDv2-capable routers */
81 net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
82
83 net_pkt_set_ipv6_hop_limit(pkt, 1); /* RFC 3810 ch 7.4 */
84
85 if (net_ipv6_create(pkt, net_if_ipv6_select_src_addr(
86 net_pkt_iface(pkt), &dst),
87 &dst)) {
88 return -ENOBUFS;
89 }
90
91 /* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
92 if (net_pkt_write_u8(pkt, IPPROTO_ICMPV6) ||
93 net_pkt_write_u8(pkt, 0)) {
94 return -ENOBUFS;
95 }
96
97 /* IPv6 router alert option is described in RFC 2711.
98 * - 0x0502 RFC 2711 ch 2.1
99 * - MLD (value 0)
100 * - 2 bytes of padding
101 */
102 if (net_pkt_write_be16(pkt, 0x0502) ||
103 net_pkt_write_be16(pkt, 0) ||
104 net_pkt_write_be16(pkt, 0)) {
105 return -ENOBUFS;
106 }
107
108 net_pkt_set_ipv6_ext_len(pkt, IPV6_OPT_HDR_ROUTER_ALERT_LEN);
109
110 /* ICMPv6 header + reserved space + count.
111 * MLDv6 stuff will come right after
112 */
113 if (net_icmpv6_create(pkt, NET_ICMPV6_MLDv2, 0) ||
114 net_pkt_write_be16(pkt, 0) ||
115 net_pkt_write_be16(pkt, count)) {
116 return -ENOBUFS;
117 }
118
119 net_pkt_set_ipv6_next_hdr(pkt, NET_IPV6_NEXTHDR_HBHO);
120
121 return 0;
122 }
123
mld_send(struct net_pkt * pkt)124 static int mld_send(struct net_pkt *pkt)
125 {
126 net_pkt_cursor_init(pkt);
127 net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
128
129 if (net_send_data(pkt) < 0) {
130 net_stats_update_icmp_drop(net_pkt_iface(pkt));
131 net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
132
133 net_pkt_unref(pkt);
134
135 return -1;
136 }
137
138 net_stats_update_icmp_sent(net_pkt_iface(pkt));
139 net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
140
141 return 0;
142 }
143
mld_send_generic(struct net_if * iface,const struct in6_addr * addr,uint8_t mode)144 static int mld_send_generic(struct net_if *iface,
145 const struct in6_addr *addr,
146 uint8_t mode)
147 {
148 struct net_pkt *pkt;
149 int ret;
150
151 pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
152 NET_ICMPV6_UNUSED_LEN +
153 MLDv2_MCAST_RECORD_LEN +
154 sizeof(struct in6_addr),
155 AF_INET6, IPPROTO_ICMPV6,
156 PKT_WAIT_TIME);
157 if (!pkt) {
158 return -ENOMEM;
159 }
160
161 if (mld_create_packet(pkt, 1) ||
162 mld_create(pkt, addr, mode, 1)) {
163 ret = -ENOBUFS;
164 goto drop;
165 }
166
167 ret = mld_send(pkt);
168 if (ret) {
169 goto drop;
170 }
171
172 return 0;
173
174 drop:
175 net_pkt_unref(pkt);
176
177 return ret;
178 }
179
net_ipv6_mld_join(struct net_if * iface,const struct in6_addr * addr)180 int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr)
181 {
182 struct net_if_mcast_addr *maddr;
183 int ret;
184
185 maddr = net_if_ipv6_maddr_lookup(addr, &iface);
186 if (maddr && net_if_ipv6_maddr_is_joined(maddr)) {
187 return -EALREADY;
188 }
189
190 if (!maddr) {
191 maddr = net_if_ipv6_maddr_add(iface, addr);
192 if (!maddr) {
193 return -ENOMEM;
194 }
195 }
196
197 ret = mld_send_generic(iface, addr, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
198 if (ret < 0) {
199 return ret;
200 }
201
202 net_if_ipv6_maddr_join(maddr);
203
204 net_if_mcast_monitor(iface, addr, true);
205
206 net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_JOIN, iface,
207 &maddr->address.in6_addr,
208 sizeof(struct in6_addr));
209
210 return ret;
211 }
212
net_ipv6_mld_leave(struct net_if * iface,const struct in6_addr * addr)213 int net_ipv6_mld_leave(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) {
220 return -ENOENT;
221 }
222
223 if (!net_if_ipv6_maddr_rm(iface, addr)) {
224 return -EINVAL;
225 }
226
227 ret = mld_send_generic(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE);
228 if (ret < 0) {
229 return ret;
230 }
231
232 net_if_mcast_monitor(iface, addr, false);
233
234 net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_LEAVE, iface,
235 &maddr->address.in6_addr,
236 sizeof(struct in6_addr));
237
238 return ret;
239 }
240
send_mld_report(struct net_if * iface)241 static void send_mld_report(struct net_if *iface)
242 {
243 struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
244 struct net_pkt *pkt;
245 int i, count = 0;
246
247 NET_ASSERT(ipv6);
248
249 for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
250 if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
251 continue;
252 }
253
254 count++;
255 }
256
257 pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
258 NET_ICMPV6_UNUSED_LEN +
259 count * MLDv2_MCAST_RECORD_LEN,
260 AF_INET6, IPPROTO_ICMPV6,
261 PKT_WAIT_TIME);
262 if (!pkt) {
263 return;
264 }
265
266 if (mld_create_packet(pkt, count)) {
267 goto drop;
268 }
269
270 for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
271 if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
272 continue;
273 }
274
275 if (!mld_create(pkt, &ipv6->mcast[i].address.in6_addr,
276 NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0)) {
277 goto drop;
278 }
279 }
280
281 if (!mld_send(pkt)) {
282 return;
283 }
284
285 drop:
286 net_pkt_unref(pkt);
287 }
288
289 #define dbg_addr(action, pkt_str, src, dst) \
290 do { \
291 NET_DBG("%s %s from %s to %s", action, pkt_str, \
292 log_strdup(net_sprint_ipv6_addr(src)), \
293 log_strdup(net_sprint_ipv6_addr(dst))); \
294 } while (0)
295
296 #define dbg_addr_recv(pkt_str, src, dst) \
297 dbg_addr("Received", pkt_str, src, dst)
298
handle_mld_query(struct net_pkt * pkt,struct net_ipv6_hdr * ip_hdr,struct net_icmp_hdr * icmp_hdr)299 static enum net_verdict handle_mld_query(struct net_pkt *pkt,
300 struct net_ipv6_hdr *ip_hdr,
301 struct net_icmp_hdr *icmp_hdr)
302 {
303 NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(mld_access,
304 struct net_icmpv6_mld_query);
305 uint16_t length = net_pkt_get_len(pkt);
306 struct net_icmpv6_mld_query *mld_query;
307 uint16_t pkt_len;
308
309 mld_query = (struct net_icmpv6_mld_query *)
310 net_pkt_get_data(pkt, &mld_access);
311 if (!mld_query) {
312 NET_DBG("DROP: NULL MLD query");
313 goto drop;
314 }
315
316 net_pkt_acknowledge_data(pkt, &mld_access);
317
318 dbg_addr_recv("Multicast Listener Query", &ip_hdr->src, &ip_hdr->dst);
319
320 net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
321
322 mld_query->num_sources = ntohs(mld_query->num_sources);
323
324 pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
325 sizeof(struct net_icmp_hdr) +
326 sizeof(struct net_icmpv6_mld_query) +
327 sizeof(struct in6_addr) * mld_query->num_sources;
328
329 if (length < pkt_len || pkt_len > NET_IPV6_MTU ||
330 ip_hdr->hop_limit != 1U || icmp_hdr->code != 0U) {
331 goto drop;
332 }
333
334 /* Currently we only support an unspecified address query. */
335 if (!net_ipv6_addr_cmp(&mld_query->mcast_address,
336 net_ipv6_unspecified_address())) {
337 NET_DBG("DROP: only supporting unspecified address query");
338 goto drop;
339 }
340
341 send_mld_report(net_pkt_iface(pkt));
342
343 net_pkt_unref(pkt);
344
345 return NET_OK;
346
347 drop:
348 net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
349
350 return NET_DROP;
351 }
352
353 static struct net_icmpv6_handler mld_query_input_handler = {
354 .type = NET_ICMPV6_MLD_QUERY,
355 .code = 0,
356 .handler = handle_mld_query,
357 };
358
net_ipv6_mld_init(void)359 void net_ipv6_mld_init(void)
360 {
361 net_icmpv6_register_handler(&mld_query_input_handler);
362 }
363