vxlan: vxlan/vxlan.api API cleanup
[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       u64 hash0, hash1;
176
177
178       /* prefetch next iteration */
179       {
180         vlib_buffer_t *p2, *p3;
181
182         p2 = vlib_get_buffer (vm, from[2]);
183         p3 = vlib_get_buffer (vm, from[3]);
184
185         vlib_prefetch_buffer_header (p2, STORE);
186         CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
187         vlib_prefetch_buffer_header (p3, STORE);
188         CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
189       }
190
191       bi0 = from[0];
192       b0 = vlib_get_buffer (vm, bi0);
193       h0 = vlib_buffer_get_current (b0);
194
195       bi1 = from[1];
196       b1 = vlib_get_buffer (vm, bi1);
197       h1 = vlib_buffer_get_current (b1);
198
199       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
200       vnet_buffer (b0)->l2_classify.table_index = ~0;
201
202       sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_TX];
203       vnet_buffer (b1)->l2_classify.table_index = ~0;
204
205       /* Select classifier table based on ethertype */
206       type0 = clib_net_to_host_u16 (h0->type);
207       type1 = clib_net_to_host_u16 (h1->type);
208
209       type_index0 = (type0 == ETHERNET_TYPE_IP4)
210         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
211       type_index0 = (type0 == ETHERNET_TYPE_IP6)
212         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
213
214       type_index1 = (type1 == ETHERNET_TYPE_IP4)
215         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
216       type_index1 = (type1 == ETHERNET_TYPE_IP6)
217         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index1;
218
219       vnet_buffer (b0)->l2_classify.table_index =
220         table_index0 =
221         rt->l2cm->classify_table_index_by_sw_if_index
222         [type_index0][sw_if_index0];
223
224       if (table_index0 != ~0)
225         {
226           t0 = pool_elt_at_index (vcm->tables, table_index0);
227
228           vnet_buffer (b0)->l2_classify.hash = hash0 =
229             vnet_classify_hash_packet (t0, (u8 *) h0);
230           vnet_classify_prefetch_bucket (t0, hash0);
231         }
232
233       vnet_buffer (b1)->l2_classify.table_index =
234         table_index1 =
235         rt->l2cm->classify_table_index_by_sw_if_index
236         [type_index1][sw_if_index1];
237
238       if (table_index1 != ~0)
239         {
240           t1 = pool_elt_at_index (vcm->tables, table_index1);
241
242           vnet_buffer (b1)->l2_classify.hash = hash1 =
243             vnet_classify_hash_packet (t1, (u8 *) h1);
244           vnet_classify_prefetch_bucket (t1, hash1);
245         }
246
247       from += 2;
248       n_left_from -= 2;
249     }
250
251   while (n_left_from > 0)
252     {
253       vlib_buffer_t *b0;
254       u32 bi0;
255       ethernet_header_t *h0;
256       u16 type0;
257       u32 type_index0;
258       vnet_classify_table_t *t0;
259       u32 table_index0;
260       u64 hash0;
261
262       bi0 = from[0];
263       b0 = vlib_get_buffer (vm, bi0);
264       h0 = vlib_buffer_get_current (b0);
265
266       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
267       vnet_buffer (b0)->l2_classify.table_index = ~0;
268
269       /* Select classifier table based on ethertype */
270       type0 = clib_net_to_host_u16 (h0->type);
271
272       type_index0 = (type0 == ETHERNET_TYPE_IP4)
273         ? L2_OUTPUT_CLASSIFY_TABLE_IP4 : L2_OUTPUT_CLASSIFY_TABLE_OTHER;
274       type_index0 = (type0 == ETHERNET_TYPE_IP6)
275         ? L2_OUTPUT_CLASSIFY_TABLE_IP6 : type_index0;
276
277       vnet_buffer (b0)->l2_classify.table_index =
278         table_index0 = rt->l2cm->classify_table_index_by_sw_if_index
279         [type_index0][sw_if_index0];
280
281       if (table_index0 != ~0)
282         {
283           t0 = pool_elt_at_index (vcm->tables, table_index0);
284
285           vnet_buffer (b0)->l2_classify.hash = hash0 =
286             vnet_classify_hash_packet (t0, (u8 *) h0);
287           vnet_classify_prefetch_bucket (t0, hash0);
288         }
289       from++;
290       n_left_from--;
291     }
292
293   next_index = node->cached_next_index;
294   from = vlib_frame_vector_args (frame);
295   n_left_from = frame->n_vectors;
296
297   while (n_left_from > 0)
298     {
299       u32 n_left_to_next;
300
301       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
302
303       /* Not enough load/store slots to dual loop... */
304       while (n_left_from > 0 && n_left_to_next > 0)
305         {
306           u32 bi0;
307           vlib_buffer_t *b0;
308           u32 next0 = ~0;
309           ethernet_header_t *h0;
310           u32 table_index0;
311           u64 hash0;
312           vnet_classify_table_t *t0;
313           vnet_classify_entry_t *e0;
314
315           if (PREDICT_TRUE (n_left_from > 2))
316             {
317               vlib_buffer_t *p2 = vlib_get_buffer (vm, from[2]);
318               u64 phash2;
319               u32 table_index2;
320               vnet_classify_table_t *tp2;
321
322               /*
323                * Prefetch table entry two ahead. Buffer / data
324                * were prefetched above...
325                */
326               table_index2 = vnet_buffer (p2)->l2_classify.table_index;
327
328               if (PREDICT_TRUE (table_index2 != ~0))
329                 {
330                   tp2 = pool_elt_at_index (vcm->tables, table_index2);
331                   phash2 = vnet_buffer (p2)->l2_classify.hash;
332                   vnet_classify_prefetch_entry (tp2, phash2);
333                 }
334             }
335
336           /* speculatively enqueue b0 to the current next frame */
337           bi0 = from[0];
338           to_next[0] = bi0;
339           from += 1;
340           to_next += 1;
341           n_left_from -= 1;
342           n_left_to_next -= 1;
343
344           b0 = vlib_get_buffer (vm, bi0);
345           h0 = vlib_buffer_get_current (b0);
346           table_index0 = vnet_buffer (b0)->l2_classify.table_index;
347           e0 = 0;
348           vnet_buffer (b0)->l2_classify.opaque_index = ~0;
349
350           if (PREDICT_TRUE (table_index0 != ~0))
351             {
352               hash0 = vnet_buffer (b0)->l2_classify.hash;
353               t0 = pool_elt_at_index (vcm->tables, table_index0);
354
355               e0 = vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
356               if (e0)
357                 {
358                   vnet_buffer (b0)->l2_classify.opaque_index
359                     = e0->opaque_index;
360                   vlib_buffer_advance (b0, e0->advance);
361                   next0 = (e0->next_index < n_next_nodes) ?
362                     e0->next_index : next0;
363                   hits++;
364                 }
365               else
366                 {
367                   while (1)
368                     {
369                       if (t0->next_table_index != ~0)
370                         t0 = pool_elt_at_index (vcm->tables,
371                                                 t0->next_table_index);
372                       else
373                         {
374                           next0 = (t0->miss_next_index < n_next_nodes) ?
375                             t0->miss_next_index : next0;
376                           misses++;
377                           break;
378                         }
379
380                       hash0 = vnet_classify_hash_packet (t0, (u8 *) h0);
381                       e0 =
382                         vnet_classify_find_entry (t0, (u8 *) h0, hash0, now);
383                       if (e0)
384                         {
385                           vnet_buffer (b0)->l2_classify.opaque_index
386                             = e0->opaque_index;
387                           vlib_buffer_advance (b0, e0->advance);
388                           next0 = (e0->next_index < n_next_nodes) ?
389                             e0->next_index : next0;
390                           hits++;
391                           chain_hits++;
392                           break;
393                         }
394                     }
395                 }
396             }
397
398           if (PREDICT_FALSE (next0 == 0))
399             b0->error = node->errors[L2_OUTPUT_CLASSIFY_ERROR_DROP];
400
401           /* Determine the next node and remove ourself from bitmap */
402           if (PREDICT_FALSE (next0 == ~0))
403             next0 = vnet_l2_feature_next (b0, cm->l2_out_feat_next,
404                                           L2OUTPUT_FEAT_OUTPUT_CLASSIFY);
405           else
406             vnet_buffer (b0)->l2.feature_bitmap &=
407               ~L2OUTPUT_FEAT_OUTPUT_CLASSIFY;
408
409           if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
410                              && (b0->flags & VLIB_BUFFER_IS_TRACED)))
411             {
412               l2_output_classify_trace_t *t =
413                 vlib_add_trace (vm, node, b0, sizeof (*t));
414               t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
415               t->table_index = table_index0;
416               t->next_index = next0;
417               t->session_offset = e0 ? vnet_classify_get_offset (t0, e0) : 0;
418             }
419
420           /* verify speculative enqueue, maybe switch current next frame */
421           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
422                                            to_next, n_left_to_next,
423                                            bi0, next0);
424         }
425
426       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
427     }
428
429   vlib_node_increment_counter (vm, node->node_index,
430                                L2_OUTPUT_CLASSIFY_ERROR_MISS, misses);
431   vlib_node_increment_counter (vm, node->node_index,
432                                L2_OUTPUT_CLASSIFY_ERROR_HIT, hits);
433   vlib_node_increment_counter (vm, node->node_index,
434                                L2_OUTPUT_CLASSIFY_ERROR_CHAIN_HIT,
435                                chain_hits);
436   return frame->n_vectors;
437 }
438
439 /* *INDENT-OFF* */
440 VLIB_REGISTER_NODE (l2_output_classify_node) = {
441   .name = "l2-output-classify",
442   .vector_size = sizeof (u32),
443   .format_trace = format_l2_output_classify_trace,
444   .type = VLIB_NODE_TYPE_INTERNAL,
445
446   .n_errors = ARRAY_LEN(l2_output_classify_error_strings),
447   .error_strings = l2_output_classify_error_strings,
448
449   .runtime_data_bytes = sizeof (l2_output_classify_runtime_t),
450
451   .n_next_nodes = L2_OUTPUT_CLASSIFY_N_NEXT,
452
453   /* edit / add dispositions here */
454   .next_nodes = {
455     [L2_OUTPUT_CLASSIFY_NEXT_DROP]  = "error-drop",
456   },
457 };
458 /* *INDENT-ON* */
459
460 #ifndef CLIB_MARCH_VARIANT
461 /** l2 output classsifier feature initialization. */
462 clib_error_t *
463 l2_output_classify_init (vlib_main_t * vm)
464 {
465   l2_output_classify_main_t *cm = &l2_output_classify_main;
466   l2_output_classify_runtime_t *rt;
467
468   rt = vlib_node_get_runtime_data (vm, l2_output_classify_node.index);
469
470   cm->vlib_main = vm;
471   cm->vnet_main = vnet_get_main ();
472   cm->vnet_classify_main = &vnet_classify_main;
473
474   /* Initialize the feature next-node indexes */
475   feat_bitmap_init_next_nodes (vm,
476                                l2_output_classify_node.index,
477                                L2OUTPUT_N_FEAT,
478                                l2output_get_feat_names (),
479                                cm->l2_out_feat_next);
480   rt->l2cm = cm;
481   rt->vcm = cm->vnet_classify_main;
482
483   return 0;
484 }
485
486 VLIB_INIT_FUNCTION (l2_output_classify_init);
487
488 clib_error_t *
489 l2_output_classify_worker_init (vlib_main_t * vm)
490 {
491   l2_output_classify_main_t *cm = &l2_output_classify_main;
492   l2_output_classify_runtime_t *rt;
493
494   rt = vlib_node_get_runtime_data (vm, l2_output_classify_node.index);
495
496   rt->l2cm = cm;
497   rt->vcm = cm->vnet_classify_main;
498
499   return 0;
500 }
501
502 VLIB_WORKER_INIT_FUNCTION (l2_output_classify_worker_init);
503
504 /** Enable/disable l2 input classification on a specific interface. */
505 void
506 vnet_l2_output_classify_enable_disable (u32 sw_if_index, int enable_disable)
507 {
508
509   l2output_intf_bitmap_enable (sw_if_index, L2OUTPUT_FEAT_OUTPUT_CLASSIFY,
510                                (u32) enable_disable);
511 }
512
513 /** @brief Set l2 per-protocol, per-interface output classification tables.
514  *
515  *  @param sw_if_index        interface handle
516  *  @param ip4_table_index    ip4 classification table index, or ~0
517  *  @param ip6_table_index    ip6 classification table index, or ~0
518  *  @param other_table_index  non-ip4, non-ip6 classification table index,
519  *         or ~0
520  *  @returns 0 on success, VNET_API_ERROR_NO_SUCH_TABLE, TABLE2, TABLE3
521  *           if the indicated (non-~0) table does not exist.
522  */
523
524 int
525 vnet_l2_output_classify_set_tables (u32 sw_if_index,
526                                     u32 ip4_table_index,
527                                     u32 ip6_table_index,
528                                     u32 other_table_index)
529 {
530   l2_output_classify_main_t *cm = &l2_output_classify_main;
531   vnet_classify_main_t *vcm = cm->vnet_classify_main;
532
533   /* Assume that we've validated sw_if_index in the API layer */
534
535   if (ip4_table_index != ~0 &&
536       pool_is_free_index (vcm->tables, ip4_table_index))
537     return VNET_API_ERROR_NO_SUCH_TABLE;
538
539   if (ip6_table_index != ~0 &&
540       pool_is_free_index (vcm->tables, ip6_table_index))
541     return VNET_API_ERROR_NO_SUCH_TABLE2;
542
543   if (other_table_index != ~0 &&
544       pool_is_free_index (vcm->tables, other_table_index))
545     return VNET_API_ERROR_NO_SUCH_TABLE3;
546
547   vec_validate
548     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4],
549      sw_if_index);
550
551   vec_validate
552     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6],
553      sw_if_index);
554
555   vec_validate
556     (cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER],
557      sw_if_index);
558
559   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4]
560     [sw_if_index] = ip4_table_index;
561
562   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6]
563     [sw_if_index] = ip6_table_index;
564
565   cm->classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_OTHER]
566     [sw_if_index] = other_table_index;
567
568   return 0;
569 }
570 #endif /* CLIB_MARCH_VARIANT */
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  */