Line data Source code
1 : /*
2 : *------------------------------------------------------------------
3 : * Copyright (c) 2017 Cisco and/or its affiliates.
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at:
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : *------------------------------------------------------------------
16 : */
17 :
18 : #include <igmp/igmp_pkt.h>
19 : #include <vnet/fib/fib_sas.h>
20 :
21 : static void
22 1855 : vlib_buffer_append (vlib_buffer_t * b, uword l)
23 : {
24 1855 : b->current_data += l;
25 1855 : b->current_length += l;
26 1855 : }
27 :
28 : static vlib_buffer_t *
29 68 : igmp_pkt_get_buffer (igmp_pkt_build_t * bk)
30 : {
31 : vlib_main_t *vm;
32 : vlib_buffer_t *b;
33 : u32 bi;
34 :
35 68 : vm = vlib_get_main ();
36 :
37 68 : if (vlib_buffer_alloc (vm, &bi, 1) != 1)
38 0 : return (NULL);
39 :
40 68 : b = vlib_get_buffer (vm, bi);
41 :
42 68 : b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
43 68 : b->flags |= VLIB_BUFFER_IS_TRACED;
44 :
45 : /* clear out stale data */
46 68 : vnet_buffer (b)->sw_if_index[VLIB_RX] = ~0;
47 :
48 : /*
49 : * save progress in the builder
50 : */
51 68 : vec_add1 (bk->buffers, bi);
52 68 : bk->n_avail = vnet_sw_interface_get_mtu (vnet_get_main (),
53 : bk->sw_if_index, VNET_MTU_IP4);
54 :
55 68 : return (b);
56 : }
57 :
58 : static vlib_buffer_t *
59 68 : igmp_pkt_build_ip_header (igmp_pkt_build_t * bk,
60 : igmp_msg_type_t msg_type,
61 : const igmp_group_t * group)
62 : {
63 : ip4_header_t *ip4;
64 : vlib_buffer_t *b;
65 : u8 *option;
66 :
67 68 : b = igmp_pkt_get_buffer (bk);
68 :
69 68 : if (NULL == b)
70 0 : return (NULL);
71 :
72 68 : ip4 = vlib_buffer_get_current (b);
73 68 : clib_memset (ip4, 0, sizeof (ip4_header_t));
74 68 : ip4->ip_version_and_header_length = 0x46;
75 68 : ip4->ttl = 1;
76 68 : ip4->protocol = IP_PROTOCOL_IGMP;
77 68 : ip4->tos = 0xc0;
78 :
79 68 : fib_sas4_get (bk->sw_if_index, NULL, &ip4->src_address);
80 :
81 68 : vlib_buffer_append (b, sizeof (*ip4));
82 68 : bk->n_avail -= sizeof (*ip4);
83 :
84 68 : switch (msg_type)
85 : {
86 50 : case IGMP_MSG_REPORT:
87 50 : ip4->dst_address.as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS;
88 50 : break;
89 18 : case IGMP_MSG_QUERY:
90 18 : if (group != NULL)
91 4 : clib_memcpy_fast (&ip4->dst_address, &group->key->ip4,
92 : sizeof (ip4_address_t));
93 : else
94 14 : ip4->dst_address.as_u32 = IGMP_GENERAL_QUERY_ADDRESS;
95 18 : break;
96 : }
97 :
98 : /* add the router alert options */
99 68 : option = vlib_buffer_get_current (b);
100 68 : option[0] = 0x80 | 20; // IP4_ROUTER_ALERT_OPTION;
101 68 : option[1] = 4; // length
102 68 : option[2] = option[3] = 0;
103 :
104 68 : vlib_buffer_append (b, 4);
105 68 : bk->n_avail -= 4;
106 :
107 68 : return (b);
108 : }
109 :
110 : static vlib_buffer_t *
111 50 : igmp_pkt_build_report_v3 (igmp_pkt_build_report_t * br,
112 : const igmp_group_t * group)
113 : {
114 : igmp_membership_report_v3_t *report;
115 : vlib_buffer_t *b;
116 :
117 50 : b = igmp_pkt_build_ip_header (&br->base, IGMP_MSG_REPORT, group);
118 :
119 50 : if (NULL == b)
120 0 : return (NULL);
121 :
122 50 : report = vlib_buffer_get_current (b);
123 50 : report->header.type = IGMP_TYPE_membership_report_v3;
124 50 : report->header.code = 0;
125 50 : report->header.checksum = 0;
126 50 : report->unused = 0;
127 :
128 50 : vlib_buffer_append (b, sizeof (igmp_membership_report_v3_t));
129 50 : br->base.n_avail -= sizeof (igmp_membership_report_v3_t);
130 50 : br->base.n_bytes += sizeof (igmp_membership_report_v3_t);
131 :
132 50 : return (b);
133 : }
134 :
135 : static void
136 63 : igmp_pkt_tx (igmp_pkt_build_t * bk)
137 : {
138 : const igmp_config_t *config;
139 : vlib_buffer_t *b;
140 : vlib_main_t *vm;
141 : vlib_frame_t *f;
142 : u32 *to_next;
143 : u32 ii;
144 :
145 63 : vm = vlib_get_main ();
146 63 : config = igmp_config_lookup (bk->sw_if_index);
147 :
148 63 : if (NULL == config)
149 0 : return;
150 :
151 63 : f = vlib_get_frame_to_node (vm, ip4_rewrite_mcast_node.index);
152 63 : to_next = vlib_frame_vector_args (f);
153 :
154 131 : vec_foreach_index (ii, bk->buffers)
155 : {
156 68 : b = vlib_get_buffer (vm, bk->buffers[ii]);
157 68 : vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index;
158 68 : to_next[ii] = bk->buffers[ii];
159 68 : f->n_vectors++;
160 : }
161 :
162 63 : vlib_put_frame_to_node (vm, ip4_rewrite_mcast_node.index, f);
163 :
164 63 : IGMP_DBG (" ..tx: %U", format_vnet_sw_if_index_name,
165 : vnet_get_main (), bk->sw_if_index);
166 :
167 63 : vec_free (bk->buffers);
168 63 : bk->buffers = 0;
169 : }
170 :
171 : static vlib_buffer_t *
172 1754 : igmp_pkt_build_report_get_active (igmp_pkt_build_report_t * br)
173 : {
174 1754 : if (NULL == br->base.buffers)
175 45 : return (NULL);
176 :
177 1709 : return (vlib_get_buffer (vlib_get_main (),
178 1709 : br->base.buffers[vec_len (br->base.buffers) - 1]));
179 : }
180 :
181 : static void
182 50 : igmp_pkt_build_report_bake (igmp_pkt_build_report_t * br)
183 : {
184 : igmp_membership_report_v3_t *igmp;
185 : ip4_header_t *ip4;
186 : vlib_buffer_t *b;
187 :
188 50 : b = igmp_pkt_build_report_get_active (br);
189 :
190 50 : b->current_data = 0;
191 :
192 50 : ip4 = vlib_buffer_get_current (b);
193 50 : igmp = (igmp_membership_report_v3_t *) (((u32 *) ip4) + 6);
194 :
195 50 : igmp->n_groups = clib_host_to_net_u16 (br->n_groups);
196 :
197 50 : igmp->header.checksum =
198 50 : ~ip_csum_fold (ip_incremental_checksum (0, igmp, br->base.n_bytes));
199 :
200 50 : ip4->length = clib_host_to_net_u16 (b->current_length);
201 50 : ip4->checksum = ip4_header_checksum (ip4);
202 :
203 50 : br->base.n_bytes = br->base.n_avail = br->n_groups = 0;
204 50 : }
205 :
206 : void
207 48 : igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br)
208 : {
209 48 : if (NULL == br->base.buffers)
210 3 : return;
211 :
212 45 : igmp_pkt_build_report_bake (br);
213 45 : igmp_pkt_tx (&br->base);
214 : }
215 :
216 : static u32
217 12 : igmp_pkt_report_v3_get_size (const igmp_group_t * group)
218 : {
219 12 : ASSERT (IGMP_FILTER_MODE_INCLUDE == group->router_filter_mode);
220 :
221 12 : return ((hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]) *
222 12 : sizeof (ip4_address_t)) + sizeof (igmp_membership_group_v3_t));
223 : }
224 :
225 : static igmp_membership_group_v3_t *
226 60 : igmp_pkt_report_v3_append_group (igmp_pkt_build_report_t * br,
227 : const ip46_address_t * grp,
228 : igmp_membership_group_v3_type_t type)
229 : {
230 : igmp_membership_group_v3_t *igmp_group;
231 : vlib_buffer_t *b;
232 :
233 60 : b = igmp_pkt_build_report_get_active (br);
234 :
235 60 : if (br->base.n_avail < sizeof (igmp_membership_group_v3_t))
236 : {
237 0 : igmp_pkt_build_report_bake (br);
238 0 : b = igmp_pkt_build_report_v3 (br, NULL);
239 0 : if (NULL == b)
240 0 : return (NULL);
241 : }
242 60 : br->base.n_avail -= sizeof (igmp_membership_group_v3_t);
243 60 : br->base.n_bytes += sizeof (igmp_membership_group_v3_t);
244 60 : br->n_groups++;
245 60 : br->n_srcs = 0;
246 :
247 60 : igmp_group = vlib_buffer_get_current (b);
248 60 : vlib_buffer_append (b, sizeof (igmp_membership_group_v3_t));
249 :
250 60 : igmp_group->type = type;
251 60 : igmp_group->n_aux_u32s = 0;
252 60 : igmp_group->n_src_addresses = 0;
253 60 : igmp_group->group_address.as_u32 = grp->ip4.as_u32;
254 :
255 60 : return (igmp_group);
256 : }
257 :
258 : /**
259 : * 4.2.16
260 : " If the set of Group Records required in a Report does not fit within
261 : * the size limit of a single Report message (as determined by the MTU
262 : * of the network on which it will be sent), the Group Records are sent
263 : * in as many Report messages as needed to report the entire set.
264 :
265 : * If a single Group Record contains so many source addresses that it
266 : * does not fit within the size limit of a single Report message, if its
267 : * Type is not MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, it is split
268 : * into multiple Group Records, each containing a different subset of
269 : * the source addresses and each sent in a separate Report message. If
270 : * its Type is MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, a single Group
271 : * Record is sent, containing as many source addresses as can fit, and
272 : * the remaining source addresses are not reported; though the choice of
273 : * which sources to report is arbitrary, it is preferable to report the
274 : * same set of sources in each subsequent report, rather than reporting
275 : * different sources each time."
276 : */
277 : static igmp_membership_group_v3_t *
278 1586 : igmp_pkt_report_v3_append_src (igmp_pkt_build_report_t * br,
279 : igmp_membership_group_v3_t * igmp_group,
280 : const ip46_address_t * grp,
281 : igmp_membership_group_v3_type_t type,
282 : const ip46_address_t * src)
283 : {
284 : vlib_buffer_t *b;
285 :
286 1586 : b = igmp_pkt_build_report_get_active (br);
287 :
288 1586 : if (br->base.n_avail < sizeof (ip4_address_t))
289 : {
290 2 : igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
291 2 : igmp_pkt_build_report_bake (br);
292 2 : b = igmp_pkt_build_report_v3 (br, NULL);
293 2 : if (NULL == b)
294 0 : return (NULL);
295 2 : igmp_group = igmp_pkt_report_v3_append_group (br, grp, type);
296 : }
297 :
298 1586 : igmp_group->src_addresses[br->n_srcs].as_u32 = src->ip4.as_u32;
299 1586 : br->n_srcs++;
300 1586 : br->base.n_avail -= sizeof (ip4_address_t);
301 1586 : br->base.n_bytes += sizeof (ip4_address_t);
302 1586 : vlib_buffer_append (b, sizeof (ip4_address_t));
303 :
304 1586 : return (igmp_group);
305 : }
306 :
307 : void
308 21 : igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br,
309 : const ip46_address_t * grp,
310 : const ip46_address_t * srcs,
311 : igmp_membership_group_v3_type_t type)
312 : {
313 : igmp_membership_group_v3_t *igmp_group;
314 : const ip46_address_t *s;
315 : vlib_buffer_t *b;
316 :
317 21 : b = igmp_pkt_build_report_get_active (br);
318 :
319 21 : if (NULL == b)
320 : {
321 20 : b = igmp_pkt_build_report_v3 (br, NULL);
322 20 : if (NULL == b)
323 : /* failed to allocate buffer */
324 0 : return;
325 : }
326 :
327 21 : igmp_group = igmp_pkt_report_v3_append_group (br, grp, type);
328 :
329 21 : if (NULL == igmp_group)
330 0 : return;
331 :
332 : /* *INDENT-OFF* */
333 452 : vec_foreach(s, srcs)
334 : {
335 431 : igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group,
336 : grp, type, s);
337 431 : if (NULL == igmp_group)
338 0 : return;
339 : };
340 : /* *INDENT-ON* */
341 :
342 21 : igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
343 :
344 21 : IGMP_DBG (" ..add-group: %U", format_ip46_address, grp, IP46_TYPE_IP4);
345 : }
346 :
347 : void
348 37 : igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br,
349 : const igmp_group_t * group,
350 : igmp_membership_group_v3_type_t type)
351 : {
352 : igmp_membership_group_v3_t *igmp_group;
353 : vlib_buffer_t *b;
354 : igmp_src_t *src;
355 :
356 37 : b = igmp_pkt_build_report_get_active (br);
357 :
358 37 : if (NULL == b)
359 : {
360 25 : b = igmp_pkt_build_report_v3 (br, NULL);
361 25 : if (NULL == b)
362 : /* failed to allocate buffer */
363 0 : return;
364 : }
365 :
366 : /*
367 : * if the group won't fit in a partially full buffer, start again
368 : */
369 37 : if ((0 != br->n_groups) &&
370 12 : (igmp_pkt_report_v3_get_size (group) > br->base.n_avail))
371 : {
372 3 : igmp_pkt_build_report_bake (br);
373 3 : b = igmp_pkt_build_report_v3 (br, NULL);
374 3 : if (NULL == b)
375 : /* failed to allocate buffer */
376 0 : return;
377 : }
378 :
379 37 : igmp_group = igmp_pkt_report_v3_append_group (br, group->key, type);
380 :
381 : /* *INDENT-OFF* */
382 5096 : FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE,
383 : ({
384 : igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group,
385 : group->key, type,
386 : src->key);
387 : if (NULL == igmp_group)
388 : return;
389 : }));
390 : /* *INDENT-ON* */
391 37 : igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
392 :
393 37 : IGMP_DBG (" ..add-group: %U srcs:%d",
394 : format_igmp_key, group->key,
395 : hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]));
396 : }
397 :
398 : void
399 48 : igmp_pkt_build_report_init (igmp_pkt_build_report_t * br, u32 sw_if_index)
400 : {
401 48 : clib_memset (br, 0, sizeof (*br));
402 48 : br->base.sw_if_index = sw_if_index;
403 48 : }
404 :
405 : static vlib_buffer_t *
406 36 : igmp_pkt_build_query_get_active (igmp_pkt_build_query_t * bq)
407 : {
408 36 : if (NULL == bq->base.buffers)
409 18 : return (NULL);
410 :
411 18 : return (vlib_get_buffer (vlib_get_main (),
412 18 : bq->base.buffers[vec_len (bq->base.buffers) - 1]));
413 : }
414 :
415 : static vlib_buffer_t *
416 18 : igmp_pkt_build_query_v3 (igmp_pkt_build_query_t * bq,
417 : const igmp_group_t * group)
418 : {
419 : igmp_membership_query_v3_t *query;
420 : vlib_buffer_t *b;
421 :
422 18 : b = igmp_pkt_build_ip_header (&bq->base, IGMP_MSG_QUERY, group);
423 :
424 18 : if (NULL == b)
425 0 : return (NULL);
426 :
427 18 : query = vlib_buffer_get_current (b);
428 18 : query->header.type = IGMP_TYPE_membership_query;
429 18 : query->header.code = 0;
430 18 : query->header.checksum = 0;
431 18 : query->qqi_code = 0;
432 18 : query->resv_s_qrv = 0;
433 :
434 18 : if (NULL != group)
435 4 : query->group_address.as_u32 = group->key->ip4.as_u32;
436 : else
437 14 : query->group_address.as_u32 = 0;
438 :
439 18 : vlib_buffer_append (b, sizeof (igmp_membership_query_v3_t));
440 18 : bq->base.n_avail -= sizeof (igmp_membership_query_v3_t);
441 18 : bq->base.n_bytes += sizeof (igmp_membership_query_v3_t);
442 :
443 18 : return (b);
444 : }
445 :
446 : void
447 18 : igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq,
448 : const igmp_group_t * group,
449 : const ip46_address_t * srcs)
450 : {
451 : vlib_buffer_t *b;
452 :
453 18 : b = igmp_pkt_build_query_get_active (bq);
454 :
455 18 : if (NULL == b)
456 : {
457 18 : b = igmp_pkt_build_query_v3 (bq, group);
458 18 : if (NULL == b)
459 : /* failed to allocate buffer */
460 0 : return;
461 : }
462 :
463 18 : if (NULL != srcs)
464 : {
465 : igmp_membership_query_v3_t *query;
466 : const ip46_address_t *src;
467 :
468 4 : query = vlib_buffer_get_current (b);
469 :
470 9 : vec_foreach (src, srcs)
471 : {
472 5 : query->src_addresses[bq->n_srcs++].as_u32 = src->ip4.as_u32;
473 :
474 5 : vlib_buffer_append (b, sizeof (ip4_address_t));
475 5 : bq->base.n_bytes += sizeof (ip4_address_t);
476 5 : bq->base.n_avail += sizeof (ip4_address_t);
477 : }
478 : }
479 : /*
480 : * else
481 : * general query and we're done
482 : */
483 : }
484 :
485 : static void
486 18 : igmp_pkt_build_query_bake (igmp_pkt_build_query_t * bq)
487 : {
488 : igmp_membership_query_v3_t *igmp;
489 : ip4_header_t *ip4;
490 : vlib_buffer_t *b;
491 :
492 18 : b = igmp_pkt_build_query_get_active (bq);
493 :
494 18 : b->current_data = 0;
495 :
496 18 : ip4 = vlib_buffer_get_current (b);
497 : // account for options
498 18 : igmp = (igmp_membership_query_v3_t *) (((u32 *) ip4) + 6);
499 :
500 18 : igmp->n_src_addresses = clib_host_to_net_u16 (bq->n_srcs);
501 :
502 18 : igmp->header.checksum =
503 18 : ~ip_csum_fold (ip_incremental_checksum (0, igmp, bq->base.n_bytes));
504 :
505 18 : ip4->length = clib_host_to_net_u16 (b->current_length);
506 18 : ip4->checksum = ip4_header_checksum (ip4);
507 :
508 18 : bq->base.n_bytes = bq->base.n_avail = bq->n_srcs = 0;
509 18 : }
510 :
511 : void
512 18 : igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq)
513 : {
514 18 : if (NULL == bq->base.buffers)
515 0 : return;
516 :
517 18 : igmp_pkt_build_query_bake (bq);
518 18 : igmp_pkt_tx (&bq->base);
519 : }
520 :
521 : void
522 18 : igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq, u32 sw_if_index)
523 : {
524 18 : clib_memset (bq, 0, sizeof (*bq));
525 18 : bq->base.sw_if_index = sw_if_index;
526 18 : }
527 :
528 : /*
529 : * fd.io coding-style-patch-verification: ON
530 : *
531 : * Local Variables:
532 : * eval: (c-set-style "gnu")
533 : * End:
534 : */
|