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