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