Trivial: Clean up some typos.
[vpp.git] / src / vnet / l2 / l2_output_classify.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vnet/l2/l2_classify.h>
17 #include <vnet/api_errno.h>
18
19 /**
20  * @file
21  * @brief Layer 2 Output Classifier.
22  *
23  * @sa @ref vnet/vnet/classify/vnet_classify.c
24  * @sa @ref vnet/vnet/classify/vnet_classify.h
25  */
26
27 typedef struct
28 {
29   /** interface handle for the ith packet */
30   u32 sw_if_index;
31   /** graph arc index selected for this packet */
32   u32 next_index;
33   /** classifier table which provided the final result */
34   u32 table_index;
35   /** offset in classifier heap of the corresponding session */
36   u32 session_offset;
37 } l2_output_classify_trace_t;
38
39 typedef struct
40 {
41   /** use-case independent main object pointer */
42   vnet_classify_main_t *vcm;
43   /** l2 input classifier main object pointer */
44   l2_output_classify_main_t *l2cm;
45 } l2_output_classify_runtime_t;
46
47 /** Packet trace format function. */
48 static u8 *
49 format_l2_output_classify_trace (u8 * s, va_list * args)
50 {
51   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
52   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
53   l2_output_classify_trace_t *t =
54     va_arg (*args, l2_output_classify_trace_t *);
55
56   s = format (s, "l2-classify: sw_if_index %d, table %d, offset %x, next %d",
57               t->sw_if_index, t->table_index, t->session_offset,
58               t->next_index);
59   return s;
60 }
61
62 /** l2 output classifier main data structure. */
63 l2_output_classify_main_t l2_output_classify_main;
64
65 vlib_node_registration_t l2_output_classify_node;
66
67 #define foreach_l2_output_classify_error               \
68 _(MISS, "Classify misses")                      \
69 _(HIT, "Classify hits")                         \
70 _(CHAIN_HIT, "Classify hits after chain walk")  \
71 _(DROP, "L2 Classify Drops")
72
73 typedef enum
74 {
75 #define _(sym,str) L2_OUTPUT_CLASSIFY_ERROR_##sym,
76   foreach_l2_output_classify_error
77 #undef _
78     L2_OUTPUT_CLASSIFY_N_ERROR,
79 } l2_output_classify_error_t;
80
81 static char *l2_output_classify_error_strings[] = {
82 #define _(sym,string) string,
83   foreach_l2_output_classify_error
84 #undef _
85 };
86
87 /**
88  * @brief l2 output classifier node.
89  * @node l2-output-classify
90  *
91  * This is the l2 output classifier dispatch node
92  *
93  * @param vm    vlib_main_t corresponding to the current thread.
94  * @param node  vlib_node_runtime_t data for this node.
95  * @param frame vlib_frame_t whose contents should be dispatched.
96  *
97  * @par Graph mechanics: buffer metadata, next index usage
98  *
99  * @em Uses:
100  * - <code>(l2_output_classify_runtime_t *)
101  *         rt->classify_table_index_by_sw_if_index</code>
102  *         Head of the per-interface, per-protocol classifier table chain
103  *         for a specific interface. ~0 => send pkts to the next
104  *         feature in the L2 feature chain.
105  * - <code>vnet_buffer(b)->sw_if_index[VLIB_TX]</code>
106  *      - Indicates the @c sw_if_index value of the interface that the
107  *      packet was received on.
108  * - <code>vnet_buffer (b0)->l2.feature_bitmap</code>
109  *      - Used to steer packets across l2 features enabled on the interface
110  * - <code>(vnet_classify_entry_t) e0->next_index</code>
111  *      - Used to steer traffic when the classifier hits on a session
112  * - <code>(vnet_classify_entry_t) e0->advance</code>
113  *      - Signed quantity applied via <code>vlib_buffer_advance</code>
114  *      when the classifier hits on a session
115  * - <code>(vnet_classify_table_t) t0->miss_next_index</code>
116  *      - Used to steer traffic when the classifier misses
117  *
118  * @em Sets:
119  * - <code>vnet_buffer (b0)->l2_classify.table_index</code>
120  *      - Classifier table index of the first classifier table in
121  *      the classifier table chain
122  * - <code>vnet_buffer (b0)->l2_classify.hash</code>
123  *      - Bounded-index extensible hash corresponding to the
124  *      masked fields in the current packet
125  * - <code>vnet_buffer (b0)->l2.feature_bitmap</code>
126  *      - Used to steer packets across l2 features enabled on the interface
127  * - <code>vnet_buffer (b0)->l2_classify.opaque_index</code>
128  *      - Copied from the classifier session object upon classifier hit
129  *
130  * @em Counters:
131  * - <code>L2_OUTPUT_CLASSIFY_ERROR_MISS</code> Classifier misses
132  * - <code>L2_OUTPUT_CLASSIFY_ERROR_HIT</code> Classifier hits
133  * - <code>L2_OUTPUT_CLASSIFY_ERROR_CHAIN_HIT</code>
134  *   Classifier hits in other than the first table
135  */
136
137 static uword
138 l2_output_classify_node_fn (vlib_main_t * vm,
139                             vlib_node_runtime_t * node, vlib_frame_t * frame)
140 {
141   u32 n_left_from, *from, *to_next;
142   l2_output_classify_next_t next_index;
143   l2_output_classify_main_t *cm = &l2_output_classify_main;
144   vnet_classify_main_t *vcm = cm->vnet_classify_main;
145   l2_output_classify_runtime_t *rt =
146     (l2_output_classify_runtime_t *) node->runtime_data;
147   u32 hits = 0;
148   u32 misses = 0;
149   u32 chain_hits = 0;
150   f64 now;
151   u32 n_next_nodes;
152   u32 sw_if_index0;
153
154   n_next_nodes = node->n_next_nodes;
155
156   now = vlib_time_now (vm);
157
158   n_left_from = frame->n_vectors;
159   from = vlib_frame_vector_args (frame);
160
161   /* First pass: compute hash */
162
163   while (n_left_from >= 4)
164     {
165       vlib_buffer_t *b0, *b1;
166       u32 bi0, bi1;
167       ethernet_header_t *h0, *h1;
168       u32 sw_if_index0, sw_if_index1;
169       u16 type0, type1;
170       int type_index0, type_index1;
171       vnet_classify_table_t *t0, *t1;
172       u32 table_index0, table_index1;
173       u64 hash0, hash1;
174
175
176       /* prefetch next iteration */
177       {
178         vlib_buffer_t *p2, *p3;
179
180         p2 = vlib_get_buffer (vm, from[2]);
181         p3 = vlib_get_buffer (vm, from[3]);
182
183         vlib_prefetch_buffer_header (p2, STORE);
184         CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
185         vlib_prefetch_buffer_header (p3, STORE);
186         CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
187       }
188
189       bi0 = from[0];
190       b0 = vlib_get_buffer (vm, bi0);
191       h0 = vlib_buffer_get_current (b0);
192
193       bi1 = from[1];
194       b1 = vlib_get_buffer (vm, bi1);
195       h1 = vlib_buffer_get_current (b1);
196
197       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
198       vnet_buffer (b0)->l2_classify.table_index = ~0;
199
200       sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_TX];
201       vnet_buffer (b1)->l2_classify.table_index = ~0;
202
203       /* Select classifier table based on ethertype */
204       type0 = clib_net_to_host_u16 (h0->type);
205       type1 = clib_net_to_host_u16 (h1->type);
206
207       type_index0 = (type0 == ETHERNET_TYPE_IP4)
208         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
209       type_index0 = (type0 == ETHERNET_TYPE_IP6)
210         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
211
212       type_index1 = (type1 == ETHERNET_TYPE_IP4)
213         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
214       type_index1 = (type1 == ETHERNET_TYPE_IP6)
215         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index1;
216
217       vnet_buffer (b0)->l2_classify.table_index =
218         table_index0 =
219         rt->l2cm->classify_table_index_by_sw_if_index
220         [type_index0][sw_if_index0];
221
222       if (table_index0 != ~0)
223         {
224           t0 = pool_elt_at_index (vcm->tables, table_index0);
225
226           vnet_buffer (b0)->l2_classify.hash = hash0 =
227             vnet_classify_hash_packet (t0, (u8 *) h0);
228           vnet_classify_prefetch_bucket (t0, hash0);
229         }
230
231       vnet_buffer (b1)->l2_classify.table_index =
232         table_index1 =
233         rt->l2cm->classify_table_index_by_sw_if_index
234         [type_index1][sw_if_index1];
235
236       if (table_index1 != ~0)
237         {
238           t1 = pool_elt_at_index (vcm->tables, table_index1);
239
240           vnet_buffer (b1)->l2_classify.hash = hash1 =
241             vnet_classify_hash_packet (t1, (u8 *) h1);
242           vnet_classify_prefetch_bucket (t1, hash1);
243         }
244
245       from += 2;
246       n_left_from -= 2;
247     }
248
249   while (n_left_from > 0)
250     {
251       vlib_buffer_t *b0;
252       u32 bi0;
253       ethernet_header_t *h0;
254       u16 type0;
255       u32 type_index0;
256       vnet_classify_table_t *t0;
257       u32 table_index0;
258       u64 hash0;
259
260       bi0 = from[0];
261       b0 = vlib_get_buffer (vm, bi0);
262       h0 = vlib_buffer_get_current (b0);
263
264       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
265       vnet_buffer (b0)->l2_classify.table_index = ~0;
266
267       /* Select classifier table based on ethertype */
268       type0 = clib_net_to_host_u16 (h0->type);
269
270       type_index0 = (type0 == ETHERNET_TYPE_IP4)
271         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
272       type_index0 = (type0 == ETHERNET_TYPE_IP6)
273         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
274
275       vnet_buffer (b0)->l2_classify.table_index =
276         table_index0 = rt->l2cm->classify_table_index_by_sw_if_index
277         [type_index0][sw_if_index0];
278
279       if (table_index0 != ~0)
280         {
281           t0 = pool_elt_at_index (vcm->tables, table_index0);
282
283           vnet_buffer (b0)->l2_classify.hash = hash0 =
284             vnet_classify_hash_packet (t0, (u8 *) h0);
285           vnet_classify_prefetch_bucket (t0, hash0);
286         }
287       from++;
288       n_left_from--;
289     }
290
291   next_index = node->cached_next_index;
292   from = vlib_frame_vector_args (frame);
293   n_left_from = frame->n_vectors;
294
295   while (n_left_from > 0)
296     {
297       u32 n_left_to_next;
298
299       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
300
301       /* Not enough load/store slots to dual loop... */
302       while (n_left_from > 0 && n_left_to_next > 0)
303         {
304           u32 bi0;
305           vlib_buffer_t *b0;
306           u32 next0 = ~0;
307           ethernet_header_t *h0;
308           u32 table_index0;
309           u64 hash0;
310           vnet_classify_table_t *t0;
311           vnet_classify_entry_t *e0;
312
313           if (PREDICT_TRUE (n_left_from > 2))
314             {
315               vlib_buffer_t *p2 = vlib_get_buffer (vm, from[2]);
316               u64 phash2;
317               u32 table_index2;
318               vnet_classify_table_t *tp2;
319
320               /*
321                * Prefetch table entry two ahead. Buffer / data
322                * were prefetched above...
323                */
324               table_index2 = vnet_buffer (p2)->l2_classify.table_index;
325
326               if (PREDICT_TRUE (table_index2 != ~0))
327                 {
328                   tp2 = pool_elt_at_index (vcm->tables, table_index2);
329                   phash2 = vnet_buffer (p2)->l2_classify.hash;
330                   vnet_classify_prefetch_entry (tp2, phash2);
331                 }
332             }
333
334           /* speculatively enqueue b0 to the current next frame */
335           bi0 = from[0];
336           to_next[0] = bi0;
337           from += 1;
338           to_next += 1;
339           n_left_from -= 1;
340           n_left_to_next -= 1;
341
342           b0 = vlib_get_buffer (vm, bi0);
343           h0 = vlib_buffer_get_current (b0);
344           table_index0 = vnet_buffer (b0)->l2_classify.table_index;
345           e0 = 0;
346           vnet_buffer (b0)->l2_classify.opaque_index = ~0;
347
348           if (PREDICT_TRUE (table_index0 != ~0))
349             {
350               hash0 = vnet_buffer (b0)->l2_classify.hash;
351               t0 = pool_elt_at_index (vcm->tables, table_index0);
352
353               e0 = vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
354               if (e0)
355                 {
356                   vnet_buffer (b0)->l2_classify.opaque_index
357                     = e0->opaque_index;
358                   vlib_buffer_advance (b0, e0->advance);
359                   next0 = (e0->next_index < n_next_nodes) ?
360                     e0->next_index : next0;
361                   hits++;
362                 }
363               else
364                 {
365                   while (1)
366                     {
367                       if (t0->next_table_index != ~0)
368                         t0 = pool_elt_at_index (vcm->tables,
369                                                 t0->next_table_index);
370                       else
371                         {
372                           next0 = (t0->miss_next_index < n_next_nodes) ?
373                             t0->miss_next_index : next0;
374                           misses++;
375                           break;
376                         }
377
378                       hash0 = vnet_classify_hash_packet (t0, (u8 *) h0);
379                       e0 =
380                         vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
381                       if (e0)
382                         {
383                           vnet_buffer (b0)->l2_classify.opaque_index
384                             = e0->opaque_index;
385                           vlib_buffer_advance (b0, e0->advance);
386                           next0 = (e0->next_index < n_next_nodes) ?
387                             e0->next_index : next0;
388                           hits++;
389                           chain_hits++;
390                           break;
391                         }
392                     }
393                 }
394             }
395
396           if (PREDICT_FALSE (next0 == 0))
397             b0->error = node->errors[L2_OUTPUT_CLASSIFY_ERROR_DROP];
398
399           /* Determine the next node and remove ourself from bitmap */
400           if (PREDICT_FALSE (next0 == ~0))
401             next0 = vnet_l2_feature_next (b0, cm->l2_out_feat_next,
402                                           L2OUTPUT_FEAT_OUTPUT_CLASSIFY);
403           else
404             vnet_buffer (b0)->l2.feature_bitmap &=
405               ~L2OUTPUT_FEAT_OUTPUT_CLASSIFY;
406
407           if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
408                              && (b0->flags & VLIB_BUFFER_IS_TRACED)))
409             {
410               l2_output_classify_trace_t *t =
411                 vlib_add_trace (vm, node, b0, sizeof (*t));
412               t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
413               t->table_index = table_index0;
414               t->next_index = next0;
415               t->session_offset = e0 ? vnet_classify_get_offset (t0, e0) : 0;
416             }
417
418           /* verify speculative enqueue, maybe switch current next frame */
419           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
420                                            to_next, n_left_to_next,
421                                            bi0, next0);
422         }
423
424       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
425     }
426
427   vlib_node_increment_counter (vm, node->node_index,
428                                L2_OUTPUT_CLASSIFY_ERROR_MISS, misses);
429   vlib_node_increment_counter (vm, node->node_index,
430                                L2_OUTPUT_CLASSIFY_ERROR_HIT, hits);
431   vlib_node_increment_counter (vm, node->node_index,
432                                L2_OUTPUT_CLASSIFY_ERROR_CHAIN_HIT,
433                                chain_hits);
434   return frame->n_vectors;
435 }
436
437 /* *INDENT-OFF* */
438 VLIB_REGISTER_NODE (l2_output_classify_node) = {
439   .function = l2_output_classify_node_fn,
440   .name = "l2-output-classify",
441   .vector_size = sizeof (u32),
442   .format_trace = format_l2_output_classify_trace,
443   .type = VLIB_NODE_TYPE_INTERNAL,
444
445   .n_errors = ARRAY_LEN(l2_output_classify_error_strings),
446   .error_strings = l2_output_classify_error_strings,
447
448   .runtime_data_bytes = sizeof (l2_output_classify_runtime_t),
449
450   .n_next_nodes = L2_OUTPUT_CLASSIFY_N_NEXT,
451
452   /* edit / add dispositions here */
453   .next_nodes = {
454     [L2_OUTPUT_CLASSIFY_NEXT_DROP]  = "error-drop",
455   },
456 };
457 /* *INDENT-ON* */
458
459 VLIB_NODE_FUNCTION_MULTIARCH (l2_output_classify_node,
460                               l2_output_classify_node_fn);
461
462 /** l2 output classsifier feature initialization. */
463 clib_error_t *
464 l2_output_classify_init (vlib_main_t * vm)
465 {
466   l2_output_classify_main_t *cm = &l2_output_classify_main;
467   l2_output_classify_runtime_t *rt;
468
469   rt = vlib_node_get_runtime_data (vm, l2_output_classify_node.index);
470
471   cm->vlib_main = vm;
472   cm->vnet_main = vnet_get_main ();
473   cm->vnet_classify_main = &vnet_classify_main;
474
475   /* Initialize the feature next-node indexes */
476   feat_bitmap_init_next_nodes (vm,
477                                l2_output_classify_node.index,
478                                L2OUTPUT_N_FEAT,
479                                l2output_get_feat_names (),
480                                cm->l2_out_feat_next);
481   rt->l2cm = cm;
482   rt->vcm = cm->vnet_classify_main;
483
484   return 0;
485 }
486
487 VLIB_INIT_FUNCTION (l2_output_classify_init);
488
489 clib_error_t *
490 l2_output_classify_worker_init (vlib_main_t * vm)
491 {
492   l2_output_classify_main_t *cm = &l2_output_classify_main;
493   l2_output_classify_runtime_t *rt;
494
495   rt = vlib_node_get_runtime_data (vm, l2_output_classify_node.index);
496
497   rt->l2cm = cm;
498   rt->vcm = cm->vnet_classify_main;
499
500   return 0;
501 }
502
503 VLIB_WORKER_INIT_FUNCTION (l2_output_classify_worker_init);
504
505 /** Enable/disable l2 input classification on a specific interface. */
506 void
507 vnet_l2_output_classify_enable_disable (u32 sw_if_index, int enable_disable)
508 {
509
510   l2output_intf_bitmap_enable (sw_if_index, L2OUTPUT_FEAT_OUTPUT_CLASSIFY,
511                                (u32) enable_disable);
512 }
513
514 /** @brief Set l2 per-protocol, per-interface output classification tables.
515  *
516  *  @param sw_if_index        interface handle
517  *  @param ip4_table_index    ip4 classification table index, or ~0
518  *  @param ip6_table_index    ip6 classification table index, or ~0
519  *  @param other_table_index  non-ip4, non-ip6 classification table index,
520  *         or ~0
521  *  @returns 0 on success, VNET_API_ERROR_NO_SUCH_TABLE, TABLE2, TABLE3
522  *           if the indicated (non-~0) table does not exist.
523  */
524
525 int
526 vnet_l2_output_classify_set_tables (u32 sw_if_index,
527                                     u32 ip4_table_index,
528                                     u32 ip6_table_index,
529                                     u32 other_table_index)
530 {
531   l2_output_classify_main_t *cm = &l2_output_classify_main;
532   vnet_classify_main_t *vcm = cm->vnet_classify_main;
533
534   /* Assume that we've validated sw_if_index in the API layer */
535
536   if (ip4_table_index != ~0 &&
537       pool_is_free_index (vcm->tables, ip4_table_index))
538     return VNET_API_ERROR_NO_SUCH_TABLE;
539
540   if (ip6_table_index != ~0 &&
541       pool_is_free_index (vcm->tables, ip6_table_index))
542     return VNET_API_ERROR_NO_SUCH_TABLE2;
543
544   if (other_table_index != ~0 &&
545       pool_is_free_index (vcm->tables, other_table_index))
546     return VNET_API_ERROR_NO_SUCH_TABLE3;
547
548   vec_validate
549     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4],
550      sw_if_index);
551
552   vec_validate
553     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6],
554      sw_if_index);
555
556   vec_validate
557     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER],
558      sw_if_index);
559
560   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4]
561     [sw_if_index] = ip4_table_index;
562
563   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6]
564     [sw_if_index] = ip6_table_index;
565
566   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER]
567     [sw_if_index] = other_table_index;
568
569   return 0;
570 }
571
572 static clib_error_t *
573 int_l2_output_classify_command_fn (vlib_main_t * vm,
574                                    unformat_input_t * input,
575                                    vlib_cli_command_t * cmd)
576 {
577   vnet_main_t *vnm = vnet_get_main ();
578   u32 sw_if_index = ~0;
579   u32 ip4_table_index = ~0;
580   u32 ip6_table_index = ~0;
581   u32 other_table_index = ~0;
582   int rv;
583
584   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
585     {
586       if (unformat (input, "intfc %U", unformat_vnet_sw_interface,
587                     vnm, &sw_if_index))
588         ;
589       else if (unformat (input, "ip4-table %d", &ip4_table_index))
590         ;
591       else if (unformat (input, "ip6-table %d", &ip6_table_index))
592         ;
593       else if (unformat (input, "other-table %d", &other_table_index))
594         ;
595       else
596         break;
597     }
598
599   if (sw_if_index == ~0)
600     return clib_error_return (0, "interface must be specified");
601
602
603   if (ip4_table_index == ~0 && ip6_table_index == ~0
604       && other_table_index == ~0)
605     {
606       vlib_cli_output (vm, "L2 classification disabled");
607       vnet_l2_output_classify_enable_disable (sw_if_index, 0 /* enable */ );
608       return 0;
609     }
610
611   rv = vnet_l2_output_classify_set_tables (sw_if_index, ip4_table_index,
612                                            ip6_table_index,
613                                            other_table_index);
614   switch (rv)
615     {
616     case 0:
617       vnet_l2_output_classify_enable_disable (sw_if_index, 1 /* enable */ );
618       break;
619
620     default:
621       return clib_error_return (0, "vnet_l2_output_classify_set_tables: %d",
622                                 rv);
623       break;
624     }
625
626   return 0;
627 }
628
629 /*?
630  * Configure Layer 2 output classification.
631  *
632  * @cliexpar
633  * @cliexstart{set interface l2 output classify intfc <interface-name> [ip4-table <index>] [ip6-table <index>] [other-table <index>]}
634  * @cliexend
635  * @todo This is incomplete. This needs a detailed description and a
636  * practical example.
637 ?*/
638 /* *INDENT-OFF* */
639 VLIB_CLI_COMMAND (int_l2_output_classify_cli, static) = {
640   .path = "set interface l2 output classify",
641   .short_help =
642   "set interface l2 output classify intfc <<interface-name>> [ip4-table <n>]\n"
643   "  [ip6-table <n>] [other-table <n>]",
644   .function = int_l2_output_classify_command_fn,
645 };
646 /* *INDENT-ON* */
647
648 /*
649  * fd.io coding-style-patch-verification: ON
650  *
651  * Local Variables:
652  * eval: (c-set-style "gnu")
653  * End:
654  */