Reorganize source tree to use single autotools instance
[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, perprotocol 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 feature_bitmap0;
148   u32 hits = 0;
149   u32 misses = 0;
150   u32 chain_hits = 0;
151   f64 now;
152   u32 n_next_nodes;
153   u32 cached_sw_if_index = (u32) ~ 0;
154   u32 cached_next_index = (u32) ~ 0;
155   u32 sw_if_index0;
156
157   n_next_nodes = node->n_next_nodes;
158
159   now = vlib_time_now (vm);
160
161   n_left_from = frame->n_vectors;
162   from = vlib_frame_vector_args (frame);
163
164   /* First pass: compute hash */
165
166   while (n_left_from > 2)
167     {
168       vlib_buffer_t *b0, *b1;
169       u32 bi0, bi1;
170       ethernet_header_t *h0, *h1;
171       u32 sw_if_index0, sw_if_index1;
172       u16 type0, type1;
173       int type_index0, type_index1;
174       vnet_classify_table_t *t0, *t1;
175       u32 table_index0, table_index1;
176       u64 hash0, hash1;
177
178
179       /* prefetch next iteration */
180       {
181         vlib_buffer_t *p1, *p2;
182
183         p1 = vlib_get_buffer (vm, from[1]);
184         p2 = vlib_get_buffer (vm, from[2]);
185
186         vlib_prefetch_buffer_header (p1, STORE);
187         CLIB_PREFETCH (p1->data, CLIB_CACHE_LINE_BYTES, STORE);
188         vlib_prefetch_buffer_header (p2, STORE);
189         CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
190       }
191
192       bi0 = from[0];
193       b0 = vlib_get_buffer (vm, bi0);
194       h0 = vlib_buffer_get_current (b0);
195
196       bi1 = from[1];
197       b1 = vlib_get_buffer (vm, bi1);
198       h1 = vlib_buffer_get_current (b1);
199
200       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
201       vnet_buffer (b0)->l2_classify.table_index = ~0;
202
203       sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_TX];
204       vnet_buffer (b1)->l2_classify.table_index = ~0;
205
206       /* Select classifier table based on ethertype */
207       type0 = clib_net_to_host_u16 (h0->type);
208       type1 = clib_net_to_host_u16 (h1->type);
209
210       type_index0 = (type0 == ETHERNET_TYPE_IP4)
211         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
212       type_index0 = (type0 == ETHERNET_TYPE_IP6)
213         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
214
215       type_index1 = (type1 == ETHERNET_TYPE_IP4)
216         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
217       type_index1 = (type1 == ETHERNET_TYPE_IP6)
218         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index1;
219
220       vnet_buffer (b0)->l2_classify.table_index =
221         table_index0 =
222         rt->l2cm->classify_table_index_by_sw_if_index
223         [type_index0][sw_if_index0];
224
225       if (table_index0 != ~0)
226         {
227           t0 = pool_elt_at_index (vcm->tables, table_index0);
228
229           vnet_buffer (b0)->l2_classify.hash = hash0 =
230             vnet_classify_hash_packet (t0, (u8 *) h0);
231           vnet_classify_prefetch_bucket (t0, hash0);
232         }
233
234       vnet_buffer (b1)->l2_classify.table_index =
235         table_index1 =
236         rt->l2cm->classify_table_index_by_sw_if_index
237         [type_index1][sw_if_index1];
238
239       if (table_index1 != ~0)
240         {
241           t1 = pool_elt_at_index (vcm->tables, table_index1);
242
243           vnet_buffer (b1)->l2_classify.hash = hash1 =
244             vnet_classify_hash_packet (t1, (u8 *) h1);
245           vnet_classify_prefetch_bucket (t1, hash1);
246         }
247
248       from += 2;
249       n_left_from -= 2;
250     }
251
252   while (n_left_from > 0)
253     {
254       vlib_buffer_t *b0;
255       u32 bi0;
256       ethernet_header_t *h0;
257       u16 type0;
258       u32 type_index0;
259       vnet_classify_table_t *t0;
260       u32 table_index0;
261       u64 hash0;
262
263       bi0 = from[0];
264       b0 = vlib_get_buffer (vm, bi0);
265       h0 = vlib_buffer_get_current (b0);
266
267       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
268       vnet_buffer (b0)->l2_classify.table_index = ~0;
269
270       /* Select classifier table based on ethertype */
271       type0 = clib_net_to_host_u16 (h0->type);
272
273       type_index0 = (type0 == ETHERNET_TYPE_IP4)
274         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
275       type_index0 = (type0 == ETHERNET_TYPE_IP6)
276         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
277
278       vnet_buffer (b0)->l2_classify.table_index =
279         table_index0 = rt->l2cm->classify_table_index_by_sw_if_index
280         [type_index0][sw_if_index0];
281
282       if (table_index0 != ~0)
283         {
284           t0 = pool_elt_at_index (vcm->tables, table_index0);
285
286           vnet_buffer (b0)->l2_classify.hash = hash0 =
287             vnet_classify_hash_packet (t0, (u8 *) h0);
288           vnet_classify_prefetch_bucket (t0, hash0);
289         }
290       from++;
291       n_left_from--;
292     }
293
294   next_index = node->cached_next_index;
295   from = vlib_frame_vector_args (frame);
296   n_left_from = frame->n_vectors;
297
298   while (n_left_from > 0)
299     {
300       u32 n_left_to_next;
301
302       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
303
304       /* Not enough load/store slots to dual loop... */
305       while (n_left_from > 0 && n_left_to_next > 0)
306         {
307           u32 bi0;
308           vlib_buffer_t *b0;
309           u32 next0 = ~0;
310           ethernet_header_t *h0;
311           u32 table_index0;
312           u64 hash0;
313           vnet_classify_table_t *t0;
314           vnet_classify_entry_t *e0;
315
316           if (PREDICT_TRUE (n_left_from > 2))
317             {
318               vlib_buffer_t *p2 = vlib_get_buffer (vm, from[2]);
319               u64 phash2;
320               u32 table_index2;
321               vnet_classify_table_t *tp2;
322
323               /*
324                * Prefetch table entry two ahead. Buffer / data
325                * were prefetched above...
326                */
327               table_index2 = vnet_buffer (p2)->l2_classify.table_index;
328
329               if (PREDICT_TRUE (table_index2 != ~0))
330                 {
331                   tp2 = pool_elt_at_index (vcm->tables, table_index2);
332                   phash2 = vnet_buffer (p2)->l2_classify.hash;
333                   vnet_classify_prefetch_entry (tp2, phash2);
334                 }
335             }
336
337           /* speculatively enqueue b0 to the current next frame */
338           bi0 = from[0];
339           to_next[0] = bi0;
340           from += 1;
341           to_next += 1;
342           n_left_from -= 1;
343           n_left_to_next -= 1;
344
345           b0 = vlib_get_buffer (vm, bi0);
346           h0 = vlib_buffer_get_current (b0);
347           table_index0 = vnet_buffer (b0)->l2_classify.table_index;
348           e0 = 0;
349           vnet_buffer (b0)->l2_classify.opaque_index = ~0;
350           /* Remove ourself from the feature bitmap */
351           feature_bitmap0 = vnet_buffer (b0)->l2.feature_bitmap
352             & ~L2OUTPUT_FEAT_OUTPUT_CLASSIFY;
353
354           /* save for next feature graph nodes */
355           vnet_buffer (b0)->l2.feature_bitmap = feature_bitmap0;
356
357           if (PREDICT_TRUE (table_index0 != ~0))
358             {
359               hash0 = vnet_buffer (b0)->l2_classify.hash;
360               t0 = pool_elt_at_index (vcm->tables, table_index0);
361
362               e0 = vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
363               if (e0)
364                 {
365                   vnet_buffer (b0)->l2_classify.opaque_index
366                     = e0->opaque_index;
367                   vlib_buffer_advance (b0, e0->advance);
368                   next0 = (e0->next_index < n_next_nodes) ?
369                     e0->next_index : next0;
370                   hits++;
371                 }
372               else
373                 {
374                   while (1)
375                     {
376                       if (t0->next_table_index != ~0)
377                         t0 = pool_elt_at_index (vcm->tables,
378                                                 t0->next_table_index);
379                       else
380                         {
381                           next0 = (t0->miss_next_index < n_next_nodes) ?
382                             t0->miss_next_index : next0;
383                           misses++;
384                           break;
385                         }
386
387                       hash0 = vnet_classify_hash_packet (t0, (u8 *) h0);
388                       e0 =
389                         vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
390                       if (e0)
391                         {
392                           vnet_buffer (b0)->l2_classify.opaque_index
393                             = e0->opaque_index;
394                           vlib_buffer_advance (b0, e0->advance);
395                           next0 = (e0->next_index < n_next_nodes) ?
396                             e0->next_index : next0;
397                           hits++;
398                           chain_hits++;
399                           break;
400                         }
401                     }
402                 }
403             }
404
405           if (PREDICT_FALSE (next0 == 0))
406             b0->error = node->errors[L2_OUTPUT_CLASSIFY_ERROR_DROP];
407
408           if (PREDICT_FALSE (next0 == ~0))
409             {
410               sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
411
412               /* Determine next node */
413               l2_output_dispatch (cm->vlib_main,
414                                   cm->vnet_main,
415                                   node,
416                                   l2_output_classify_node.index,
417                                   &cached_sw_if_index,
418                                   &cached_next_index,
419                                   &cm->next_nodes,
420                                   b0, sw_if_index0, feature_bitmap0, &next0);
421             }
422
423           if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
424                              && (b0->flags & VLIB_BUFFER_IS_TRACED)))
425             {
426               l2_output_classify_trace_t *t =
427                 vlib_add_trace (vm, node, b0, sizeof (*t));
428               t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
429               t->table_index = table_index0;
430               t->next_index = next0;
431               t->session_offset = e0 ? vnet_classify_get_offset (t0, e0) : 0;
432             }
433
434           /* verify speculative enqueue, maybe switch current next frame */
435           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
436                                            to_next, n_left_to_next,
437                                            bi0, next0);
438         }
439
440       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
441     }
442
443   vlib_node_increment_counter (vm, node->node_index,
444                                L2_OUTPUT_CLASSIFY_ERROR_MISS, misses);
445   vlib_node_increment_counter (vm, node->node_index,
446                                L2_OUTPUT_CLASSIFY_ERROR_HIT, hits);
447   vlib_node_increment_counter (vm, node->node_index,
448                                L2_OUTPUT_CLASSIFY_ERROR_CHAIN_HIT,
449                                chain_hits);
450   return frame->n_vectors;
451 }
452
453 /* *INDENT-OFF* */
454 VLIB_REGISTER_NODE (l2_output_classify_node) = {
455   .function = l2_output_classify_node_fn,
456   .name = "l2-output-classify",
457   .vector_size = sizeof (u32),
458   .format_trace = format_l2_output_classify_trace,
459   .type = VLIB_NODE_TYPE_INTERNAL,
460
461   .n_errors = ARRAY_LEN(l2_output_classify_error_strings),
462   .error_strings = l2_output_classify_error_strings,
463
464   .runtime_data_bytes = sizeof (l2_output_classify_runtime_t),
465
466   .n_next_nodes = L2_OUTPUT_CLASSIFY_N_NEXT,
467
468   /* edit / add dispositions here */
469   .next_nodes = {
470     [L2_OUTPUT_CLASSIFY_NEXT_DROP]  = "error-drop",
471   },
472 };
473 /* *INDENT-ON* */
474
475 VLIB_NODE_FUNCTION_MULTIARCH (l2_output_classify_node,
476                               l2_output_classify_node_fn);
477
478 /** l2 output classsifier feature initialization. */
479 clib_error_t *
480 l2_output_classify_init (vlib_main_t * vm)
481 {
482   l2_output_classify_main_t *cm = &l2_output_classify_main;
483   l2_output_classify_runtime_t *rt;
484
485   rt = vlib_node_get_runtime_data (vm, l2_output_classify_node.index);
486
487   cm->vlib_main = vm;
488   cm->vnet_main = vnet_get_main ();
489   cm->vnet_classify_main = &vnet_classify_main;
490
491   /* Initialize the feature next-node indexes */
492   feat_bitmap_init_next_nodes (vm,
493                                l2_output_classify_node.index,
494                                L2OUTPUT_N_FEAT,
495                                l2output_get_feat_names (),
496                                cm->feat_next_node_index);
497   rt->l2cm = cm;
498   rt->vcm = cm->vnet_classify_main;
499
500   /* Initialize the output node mapping table */
501   l2output_init_output_node_vec (&cm->next_nodes.output_node_index_vec);
502
503   return 0;
504 }
505
506 VLIB_INIT_FUNCTION (l2_output_classify_init);
507
508 /** Enable/disable l2 input classification on a specific interface. */
509 void
510 vnet_l2_output_classify_enable_disable (u32 sw_if_index, int enable_disable)
511 {
512
513   l2output_intf_bitmap_enable (sw_if_index, L2OUTPUT_FEAT_OUTPUT_CLASSIFY,
514                                (u32) enable_disable);
515 }
516
517 /** @brief Set l2 per-protocol, per-interface output classification tables.
518  *
519  *  @param sw_if_index        interface handle
520  *  @param ip4_table_index    ip4 classification table index, or ~0
521  *  @param ip6_table_index    ip6 classification table index, or ~0
522  *  @param other_table_index  non-ip4, non-ip6 classification table index,
523  *         or ~0
524  *  @returns 0 on success, VNET_API_ERROR_NO_SUCH_TABLE, TABLE2, TABLE3
525  *           if the indicated (non-~0) table does not exist.
526  */
527
528 int
529 vnet_l2_output_classify_set_tables (u32 sw_if_index,
530                                     u32 ip4_table_index,
531                                     u32 ip6_table_index,
532                                     u32 other_table_index)
533 {
534   l2_output_classify_main_t *cm = &l2_output_classify_main;
535   vnet_classify_main_t *vcm = cm->vnet_classify_main;
536
537   /* Assume that we've validated sw_if_index in the API layer */
538
539   if (ip4_table_index != ~0 &&
540       pool_is_free_index (vcm->tables, ip4_table_index))
541     return VNET_API_ERROR_NO_SUCH_TABLE;
542
543   if (ip6_table_index != ~0 &&
544       pool_is_free_index (vcm->tables, ip6_table_index))
545     return VNET_API_ERROR_NO_SUCH_TABLE2;
546
547   if (other_table_index != ~0 &&
548       pool_is_free_index (vcm->tables, other_table_index))
549     return VNET_API_ERROR_NO_SUCH_TABLE3;
550
551   vec_validate
552     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4],
553      sw_if_index);
554
555   vec_validate
556     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6],
557      sw_if_index);
558
559   vec_validate
560     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER],
561      sw_if_index);
562
563   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4]
564     [sw_if_index] = ip4_table_index;
565
566   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6]
567     [sw_if_index] = ip6_table_index;
568
569   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER]
570     [sw_if_index] = other_table_index;
571
572   return 0;
573 }
574
575 static clib_error_t *
576 int_l2_output_classify_command_fn (vlib_main_t * vm,
577                                    unformat_input_t * input,
578                                    vlib_cli_command_t * cmd)
579 {
580   vnet_main_t *vnm = vnet_get_main ();
581   u32 sw_if_index = ~0;
582   u32 ip4_table_index = ~0;
583   u32 ip6_table_index = ~0;
584   u32 other_table_index = ~0;
585   int rv;
586
587   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
588     {
589       if (unformat (input, "intfc %U", unformat_vnet_sw_interface,
590                     vnm, &sw_if_index))
591         ;
592       else if (unformat (input, "ip4-table %d", &ip4_table_index))
593         ;
594       else if (unformat (input, "ip6-table %d", &ip6_table_index))
595         ;
596       else if (unformat (input, "other-table %d", &other_table_index))
597         ;
598       else
599         break;
600     }
601
602   if (sw_if_index == ~0)
603     return clib_error_return (0, "interface must be specified");
604
605
606   if (ip4_table_index == ~0 && ip6_table_index == ~0
607       && other_table_index == ~0)
608     {
609       vlib_cli_output (vm, "L2 classification disabled");
610       vnet_l2_output_classify_enable_disable (sw_if_index, 0 /* enable */ );
611       return 0;
612     }
613
614   rv = vnet_l2_output_classify_set_tables (sw_if_index, ip4_table_index,
615                                            ip6_table_index,
616                                            other_table_index);
617   switch (rv)
618     {
619     case 0:
620       vnet_l2_output_classify_enable_disable (sw_if_index, 1 /* enable */ );
621       break;
622
623     default:
624       return clib_error_return (0, "vnet_l2_output_classify_set_tables: %d",
625                                 rv);
626       break;
627     }
628
629   return 0;
630 }
631
632 /*?
633  * Configure Layer 2 output classification.
634  *
635  * @cliexpar
636  * @cliexstart{set interface l2 output classify intfc <interface-name> [ip4-table <index>] [ip6-table <index>] [other-table <index>]}
637  * @cliexend
638  * @todo This is incomplete. This needs a detailed description and a
639  * practical example.
640 ?*/
641 /* *INDENT-OFF* */
642 VLIB_CLI_COMMAND (int_l2_output_classify_cli, static) = {
643   .path = "set interface l2 output classify",
644   .short_help =
645   "set interface l2 output classify intfc <<interface-name>> [ip4-table <n>]\n"
646   "  [ip6-table <n>] [other-table <n>]",
647   .function = int_l2_output_classify_command_fn,
648 };
649 /* *INDENT-ON* */
650
651 /*
652  * fd.io coding-style-patch-verification: ON
653  *
654  * Local Variables:
655  * eval: (c-set-style "gnu")
656  * End:
657  */