misc: Purge unused pg includes
[vpp.git] / src / vnet / l2 / l2_fib.c
1 /*
2  * l2_fib.c : layer 2 forwarding table (aka mac table)
3  *
4  * Copyright (c) 2013 Cisco and/or its affiliates.
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18
19 #include <vlib/vlib.h>
20 #include <vnet/vnet.h>
21 #include <vnet/ethernet/ethernet.h>
22 #include <vlib/cli.h>
23
24 #include <vppinfra/error.h>
25 #include <vppinfra/hash.h>
26 #include <vnet/l2/l2_input.h>
27 #include <vnet/l2/l2_fib.h>
28 #include <vnet/l2/l2_learn.h>
29 #include <vnet/l2/l2_bd.h>
30
31 #include <vppinfra/bihash_template.c>
32
33 #include <vlibmemory/api.h>
34 #include <vnet/vnet_msg_enum.h>
35
36 #define vl_typedefs             /* define message structures */
37 #include <vnet/vnet_all_api_h.h>
38 #undef vl_typedefs
39
40 #define vl_endianfun            /* define message structures */
41 #include <vnet/vnet_all_api_h.h>
42 #undef vl_endianfun
43
44 /**
45  * @file
46  * @brief Ethernet MAC Address FIB Table Management.
47  *
48  * The MAC Address forwarding table for bridge-domains is called the l2fib.
49  * Entries are added automatically as part of mac learning, but MAC Addresses
50  * entries can also be added manually.
51  *
52  */
53
54 l2fib_main_t l2fib_main;
55
56 u8 *
57 format_l2fib_entry_result_flags (u8 * s, va_list * args)
58 {
59   l2fib_entry_result_flags_t flags = va_arg (*args, int);
60
61   if (L2FIB_ENTRY_RESULT_FLAG_NONE == flags)
62     {
63       s = format (s, "none");
64     }
65   else
66     {
67 #define _(a,v,t) {                              \
68       if (flags & L2FIB_ENTRY_RESULT_FLAG_##a)  \
69         s = format (s, "%s ", t);               \
70     }
71       foreach_l2fib_entry_result_attr
72 #undef _
73     }
74   return (s);
75 }
76
77 static void
78 incr_mac_address (u8 * mac)
79 {
80   u64 tmp = *((u64 *) mac);
81   tmp = clib_net_to_host_u64 (tmp);
82   tmp += 1 << 16;               /* skip unused (least significant) octets */
83   tmp = clib_host_to_net_u64 (tmp);
84
85   clib_memcpy_fast (mac, &tmp, 6);
86 }
87
88 /** Format sw_if_index. If the value is ~0, use the text "N/A" */
89 u8 *
90 format_vnet_sw_if_index_name_with_NA (u8 * s, va_list * args)
91 {
92   vnet_main_t *vnm = va_arg (*args, vnet_main_t *);
93   u32 sw_if_index = va_arg (*args, u32);
94   if (sw_if_index == ~0)
95     return format (s, "N/A");
96
97   vnet_sw_interface_t *swif =
98     vnet_get_sw_interface_or_null (vnm, sw_if_index);
99   if (!swif)
100     return format (s, "Stale");
101
102   return format (s, "%U", format_vnet_sw_interface_name, vnm,
103                  vnet_get_sw_interface_or_null (vnm, sw_if_index));
104 }
105
106 typedef struct l2fib_dump_walk_ctx_t_
107 {
108   u32 bd_index;
109   l2fib_entry_key_t *l2fe_key;
110   l2fib_entry_result_t *l2fe_res;
111 } l2fib_dump_walk_ctx_t;
112
113 static int
114 l2fib_dump_walk_cb (BVT (clib_bihash_kv) * kvp, void *arg)
115 {
116   l2fib_dump_walk_ctx_t *ctx = arg;
117   l2fib_entry_result_t result;
118   l2fib_entry_key_t key;
119
120   key.raw = kvp->key;
121   result.raw = kvp->value;
122
123   if ((ctx->bd_index == ~0) || (ctx->bd_index == key.fields.bd_index))
124     {
125       vec_add1 (ctx->l2fe_key, key);
126       vec_add1 (ctx->l2fe_res, result);
127     }
128
129   return (BIHASH_WALK_CONTINUE);
130 }
131
132 void
133 l2fib_table_dump (u32 bd_index,
134                   l2fib_entry_key_t ** l2fe_key,
135                   l2fib_entry_result_t ** l2fe_res)
136 {
137   l2fib_main_t *msm = &l2fib_main;
138   l2fib_dump_walk_ctx_t ctx = {
139     .bd_index = bd_index,
140   };
141
142   BV (clib_bihash_foreach_key_value_pair)
143     (&msm->mac_table, l2fib_dump_walk_cb, &ctx);
144
145   *l2fe_key = ctx.l2fe_key;
146   *l2fe_res = ctx.l2fe_res;
147 }
148
149 typedef struct l2fib_show_walk_ctx_t_
150 {
151   u8 first_entry;
152   u8 verbose;
153   vlib_main_t *vm;
154   vnet_main_t *vnm;
155   u32 total_entries;
156   u32 bd_index;
157   u8 learn;
158   u8 add;
159   u8 now;
160 } l2fib_show_walk_ctx_t;
161
162 static int
163 l2fib_show_walk_cb (BVT (clib_bihash_kv) * kvp, void *arg)
164 {
165   l2fib_show_walk_ctx_t *ctx = arg;
166   l2_bridge_domain_t *bd_config;
167   l2fib_entry_result_t result;
168   l2fib_entry_key_t key;
169
170   if (ctx->verbose && ctx->first_entry)
171     {
172       ctx->first_entry = 0;
173       vlib_cli_output (ctx->vm,
174                        "%=19s%=7s%=7s%=8s%=9s%=7s%=7s%=5s%=30s",
175                        "Mac-Address", "BD-Idx", "If-Idx",
176                        "BSN-ISN", "Age(min)", "static", "filter",
177                        "bvi", "Interface-Name");
178     }
179
180   key.raw = kvp->key;
181   result.raw = kvp->value;
182   ctx->total_entries++;
183
184   if (ctx->verbose &&
185       ((ctx->bd_index >> 31) || (ctx->bd_index == key.fields.bd_index)))
186     {
187       u8 *s = NULL;
188
189       if (ctx->learn && l2fib_entry_result_is_set_AGE_NOT (&result))
190         return (BIHASH_WALK_CONTINUE);  /* skip provisioned macs */
191
192       if (ctx->add && !l2fib_entry_result_is_set_AGE_NOT (&result))
193         return (BIHASH_WALK_CONTINUE);  /* skip learned macs */
194
195       bd_config = vec_elt_at_index (l2input_main.bd_configs,
196                                     key.fields.bd_index);
197
198       if (l2fib_entry_result_is_set_AGE_NOT (&result))
199         s = format (s, "no");
200       else if (bd_config->mac_age == 0)
201         s = format (s, "-");
202       else
203         {
204           i16 delta = ctx->now - result.fields.timestamp;
205           delta += delta < 0 ? 256 : 0;
206           s = format (s, "%d", delta);
207         }
208
209       vlib_cli_output (ctx->vm,
210                        "%=19U%=7d%=7d %3d/%-3d%=9v%=7s%=7s%=5s%=30U",
211                        format_ethernet_address, key.fields.mac,
212                        key.fields.bd_index,
213                        result.fields.sw_if_index == ~0
214                        ? -1 : result.fields.sw_if_index,
215                        result.fields.sn.bd, result.fields.sn.swif, s,
216                        l2fib_entry_result_is_set_STATIC (&result) ? "*" : "-",
217                        l2fib_entry_result_is_set_FILTER (&result) ? "*" : "-",
218                        l2fib_entry_result_is_set_BVI (&result) ? "*" : "-",
219                        format_vnet_sw_if_index_name_with_NA,
220                        ctx->vnm, result.fields.sw_if_index);
221       vec_free (s);
222     }
223
224   return (BIHASH_WALK_CONTINUE);
225 }
226
227 /** Display the contents of the l2fib. */
228 static clib_error_t *
229 show_l2fib (vlib_main_t * vm,
230             unformat_input_t * input, vlib_cli_command_t * cmd)
231 {
232   bd_main_t *bdm = &bd_main;
233   l2fib_main_t *msm = &l2fib_main;
234   u8 raw = 0;
235   u32 bd_id;
236   l2fib_show_walk_ctx_t ctx = {
237     .first_entry = 1,
238     .bd_index = ~0,
239     .now = (u8) (vlib_time_now (vm) / 60),
240     .vm = vm,
241     .vnm = msm->vnet_main,
242   };
243
244   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
245     {
246       if (unformat (input, "raw"))
247         {
248           raw = 1;
249           ctx.verbose = 0;
250           break;
251         }
252       else if (unformat (input, "verbose"))
253         ctx.verbose = 1;
254       else if (unformat (input, "all"))
255         ctx.verbose = 1;
256       else if (unformat (input, "bd_index %d", &ctx.bd_index))
257         ctx.verbose = 1;
258       else if (unformat (input, "learn"))
259         {
260           ctx.add = 0;
261           ctx.learn = 1;
262           ctx.verbose = 1;
263         }
264       else if (unformat (input, "add"))
265         {
266           ctx.learn = 0;
267           ctx.add = 1;
268           ctx.verbose = 1;
269         }
270       else if (unformat (input, "bd_id %d", &bd_id))
271         {
272           uword *p = hash_get (bdm->bd_index_by_bd_id, bd_id);
273           if (p)
274             {
275               ctx.verbose = 1;
276               ctx.bd_index = p[0];
277             }
278           else
279             return clib_error_return (0,
280                                       "bridge domain id %d doesn't exist\n",
281                                       bd_id);
282         }
283       else
284         break;
285     }
286
287   if (msm->mac_table_initialized == 0)
288     {
289       vlib_cli_output (vm, "no l2fib entries");
290       return 0;
291     }
292
293   BV (clib_bihash_foreach_key_value_pair)
294     (&msm->mac_table, l2fib_show_walk_cb, &ctx);
295
296   if (ctx.total_entries == 0)
297     vlib_cli_output (vm, "no l2fib entries");
298   else
299     {
300       l2learn_main_t *lm = &l2learn_main;
301       vlib_cli_output (vm, "L2FIB total/learned entries: %d/%d  "
302                        "Last scan time: %.4esec  Learn limit: %d ",
303                        ctx.total_entries, lm->global_learn_count,
304                        msm->age_scan_duration, lm->global_learn_limit);
305       if (lm->client_pid)
306         vlib_cli_output (vm, "L2MAC events client PID: %d  "
307                          "Last e-scan time: %.4esec  Delay: %.2esec  "
308                          "Max macs in event: %d",
309                          lm->client_pid, msm->evt_scan_duration,
310                          msm->event_scan_delay, msm->max_macs_in_event);
311     }
312
313   if (raw)
314     vlib_cli_output (vm, "Raw Hash Table:\n%U\n",
315                      BV (format_bihash), &msm->mac_table, 1 /* verbose */ );
316
317   return 0;
318 }
319
320 /*?
321  * This command displays the MAC Address entries of the L2 FIB table.
322  * Output can be filtered to just get the number of MAC Addresses or display
323  * each MAC Address for all bridge domains or just a single bridge domain.
324  *
325  * @cliexpar
326  * Example of how to display the number of MAC Address entries in the L2
327  * FIB table:
328  * @cliexstart{show l2fib}
329  * 3 l2fib entries
330  * @cliexend
331  * Example of how to display all the MAC Address entries in the L2
332  * FIB table:
333  * @cliexstart{show l2fib all}
334  *     Mac Address     BD Idx           Interface           Index  static  filter  bvi  refresh  timestamp
335  *  52:54:00:53:18:33    1      GigabitEthernet0/8/0.200      3       0       0     0      0         0
336  *  52:54:00:53:18:55    1      GigabitEthernet0/8/0.200      3       1       0     0      0         0
337  *  52:54:00:53:18:77    1                 N/A                -1      1       1     0      0         0
338  * 3 l2fib entries
339  * @cliexend
340 ?*/
341 /* *INDENT-OFF* */
342 VLIB_CLI_COMMAND (show_l2fib_cli, static) = {
343   .path = "show l2fib",
344   .short_help = "show l2fib [all] | [bd_id <nn> | bd_index <nn>] [learn | add] | [raw]",
345   .function = show_l2fib,
346 };
347 /* *INDENT-ON* */
348
349 void
350 l2fib_table_init (void)
351 {
352   l2fib_main_t *mp = &l2fib_main;
353
354   if (mp->mac_table_initialized == 1)
355     return;
356
357   BV (clib_bihash_init) (&mp->mac_table, "l2fib mac table",
358                          mp->mac_table_n_buckets, mp->mac_table_memory_size);
359   mp->mac_table_initialized = 1;
360 }
361
362 /* Remove all entries from the l2fib */
363 void
364 l2fib_clear_table (void)
365 {
366   l2fib_main_t *mp = &l2fib_main;
367
368   if (mp->mac_table_initialized == 0)
369     return;
370
371   /* Remove all entries */
372   BV (clib_bihash_free) (&mp->mac_table);
373   l2fib_table_init ();
374   l2learn_main.global_learn_count = 0;
375 }
376
377 /** Clear all entries in L2FIB.
378  * @TODO: Later we may want a way to remove only the non-static entries
379  */
380 static clib_error_t *
381 clear_l2fib (vlib_main_t * vm,
382              unformat_input_t * input, vlib_cli_command_t * cmd)
383 {
384   l2fib_clear_table ();
385   return 0;
386 }
387
388 /*?
389  * This command clears all the MAC Address entries from the L2 FIB table.
390  *
391  * @cliexpar
392  * Example of how to clear the L2 FIB Table:
393  * @cliexcmd{clear l2fib}
394  * Example to show the L2 FIB Table has been cleared:
395  * @cliexstart{show l2fib verbose}
396  * no l2fib entries
397  * @cliexend
398 ?*/
399 /* *INDENT-OFF* */
400 VLIB_CLI_COMMAND (clear_l2fib_cli, static) = {
401   .path = "clear l2fib",
402   .short_help = "clear l2fib",
403   .function = clear_l2fib,
404 };
405 /* *INDENT-ON* */
406
407 static inline l2fib_seq_num_t
408 l2fib_cur_seq_num (u32 bd_index, u32 sw_if_index)
409 {
410   l2_bridge_domain_t *bd_config = l2input_bd_config (bd_index);
411   /* *INDENT-OFF* */
412   return (l2fib_seq_num_t) {
413     .swif = *l2fib_swif_seq_num (sw_if_index),
414     .bd = bd_config->seq_num,
415   };
416   /* *INDENT-ON* */
417 }
418
419 /**
420  * Add an entry to the l2fib.
421  * If the entry already exists then overwrite it
422  */
423 void
424 l2fib_add_entry (const u8 * mac, u32 bd_index,
425                  u32 sw_if_index, l2fib_entry_result_flags_t flags)
426 {
427   l2fib_entry_key_t key;
428   l2fib_entry_result_t result;
429   __attribute__ ((unused)) u32 bucket_contents;
430   l2fib_main_t *fm = &l2fib_main;
431   l2learn_main_t *lm = &l2learn_main;
432   BVT (clib_bihash_kv) kv;
433
434   if (fm->mac_table_initialized == 0)
435     l2fib_table_init ();
436
437   /* set up key */
438   key.raw = l2fib_make_key (mac, bd_index);
439   kv.key = key.raw;
440
441   /* check if entry already exist */
442   if (BV (clib_bihash_search) (&fm->mac_table, &kv, &kv))
443     {
444       /* decrement counter if overwriting a learned mac  */
445       result.raw = kv.value;
446       if ((!l2fib_entry_result_is_set_AGE_NOT (&result))
447           && (lm->global_learn_count))
448         lm->global_learn_count--;
449     }
450
451   /* set up result */
452   result.raw = 0;               /* clear all fields */
453   result.fields.sw_if_index = sw_if_index;
454   result.fields.flags = flags;
455
456   /* no aging for provisioned entry */
457   l2fib_entry_result_set_AGE_NOT (&result);
458
459   kv.value = result.raw;
460
461   BV (clib_bihash_add_del) (&fm->mac_table, &kv, 1 /* is_add */ );
462 }
463
464 /**
465  * Add an entry to the L2FIB.
466  * The CLI format is:
467  *    l2fib add <mac> <bd> <intf> [static] [bvi]
468  *    l2fib add <mac> <bd> filter
469  * Note that filter and bvi entries are always static
470  */
471 static clib_error_t *
472 l2fib_add (vlib_main_t * vm,
473            unformat_input_t * input, vlib_cli_command_t * cmd)
474 {
475   bd_main_t *bdm = &bd_main;
476   vnet_main_t *vnm = vnet_get_main ();
477   clib_error_t *error = 0;
478   u8 mac[6];
479   u32 bd_id;
480   u32 bd_index;
481   u32 sw_if_index = ~0;
482   uword *p;
483   l2fib_entry_result_flags_t flags;
484
485   flags = L2FIB_ENTRY_RESULT_FLAG_NONE;
486
487   if (!unformat (input, "%U", unformat_ethernet_address, mac))
488     {
489       error = clib_error_return (0, "expected mac address `%U'",
490                                  format_unformat_error, input);
491       goto done;
492     }
493
494   if (!unformat (input, "%d", &bd_id))
495     {
496       error = clib_error_return (0, "expected bridge domain ID `%U'",
497                                  format_unformat_error, input);
498       goto done;
499     }
500
501   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
502   if (!p)
503     {
504       error = clib_error_return (0, "bridge domain ID %d invalid", bd_id);
505       goto done;
506     }
507   bd_index = p[0];
508
509   if (unformat (input, "filter"))
510     {
511       l2fib_add_filter_entry (mac, bd_index);
512       return 0;
513     }
514
515   if (!unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
516     {
517       error = clib_error_return (0, "unknown interface `%U'",
518                                  format_unformat_error, input);
519       goto done;
520     }
521
522   if (unformat (input, "static"))
523     flags |= L2FIB_ENTRY_RESULT_FLAG_STATIC;
524   else if (unformat (input, "bvi"))
525     flags |= (L2FIB_ENTRY_RESULT_FLAG_STATIC | L2FIB_ENTRY_RESULT_FLAG_BVI);
526
527   if (vec_len (l2input_main.configs) <= sw_if_index)
528     {
529       error = clib_error_return (0, "Interface sw_if_index %d not in L2 mode",
530                                  sw_if_index);
531       goto done;
532     }
533
534   l2fib_add_entry (mac, bd_index, sw_if_index, flags);
535
536 done:
537   return error;
538 }
539
540 /*?
541  * This command adds a MAC Address entry to the L2 FIB table
542  * of an existing bridge-domain. The MAC Address can be static
543  * or dynamic. This command also allows a filter to be added,
544  * such that packets with given MAC Addresses (source mac or
545  * destination mac match) are dropped.
546  *
547  * @cliexpar
548  * Example of how to add a dynamic MAC Address entry to the L2 FIB table
549  * of a bridge-domain (where 200 is the bridge-domain-id):
550  * @cliexcmd{l2fib add 52:54:00:53:18:33 200 GigabitEthernet0/8/0.200}
551  * Example of how to add a static MAC Address entry to the L2 FIB table
552  * of a bridge-domain (where 200 is the bridge-domain-id):
553  * @cliexcmd{l2fib add 52:54:00:53:18:55 200 GigabitEthernet0/8/0.200 static}
554  * Example of how to add a filter such that a packet with the given MAC
555  * Address will be dropped in a given bridge-domain (where 200 is the
556  * bridge-domain-id):
557  * @cliexcmd{l2fib add 52:54:00:53:18:77 200 filter}
558  * Example of show command of the provisioned MAC Addresses and filters:
559  * @cliexstart{show l2fib verbose}
560  *     Mac Address     BD Idx           Interface           Index  static  filter  bvi  refresh  timestamp
561  *  52:54:00:53:18:33    1      GigabitEthernet0/8/0.200      3       0       0     0      0         0
562  *  52:54:00:53:18:55    1      GigabitEthernet0/8/0.200      3       1       0     0      0         0
563  *  52:54:00:53:18:77    1                 N/A                -1      1       1     0      0         0
564  * 3 l2fib entries
565  * @cliexend
566 ?*/
567 /* *INDENT-OFF* */
568 VLIB_CLI_COMMAND (l2fib_add_cli, static) = {
569   .path = "l2fib add",
570   .short_help = "l2fib add <mac> <bridge-domain-id> filter | <intf> [static | bvi]",
571   .function = l2fib_add,
572 };
573 /* *INDENT-ON* */
574
575
576 static clib_error_t *
577 l2fib_test_command_fn (vlib_main_t * vm,
578                        unformat_input_t * input, vlib_cli_command_t * cmd)
579 {
580   u8 mac[6], save_mac[6];
581   u32 bd_index = 0;
582   u32 sw_if_index = 8;
583   u32 is_add = 0;
584   u32 is_del = 0;
585   u32 is_check = 0;
586   u32 count = 1;
587   int mac_set = 0;
588   int i;
589
590   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
591     {
592       if (unformat (input, "mac %U", unformat_ethernet_address, mac))
593         mac_set = 1;
594       else if (unformat (input, "add"))
595         is_add = 1;
596       else if (unformat (input, "del"))
597         is_del = 1;
598       else if (unformat (input, "check"))
599         is_check = 1;
600       else if (unformat (input, "count %d", &count))
601         ;
602       else
603         break;
604     }
605
606   if (mac_set == 0)
607     return clib_error_return (0, "mac not set");
608
609   if (is_add == 0 && is_del == 0 && is_check == 0)
610     return clib_error_return (0,
611                               "noop: pick at least one of (add,del,check)");
612
613   clib_memcpy_fast (save_mac, mac, 6);
614
615   if (is_add)
616     {
617       for (i = 0; i < count; i++)
618         {
619           l2fib_add_entry (mac, bd_index, sw_if_index,
620                            L2FIB_ENTRY_RESULT_FLAG_NONE);
621           incr_mac_address (mac);
622         }
623     }
624
625   if (is_check)
626     {
627       BVT (clib_bihash_kv) kv;
628       l2fib_main_t *mp = &l2fib_main;
629
630       if (mp->mac_table_initialized == 0)
631         return clib_error_return (0, "mac table is not initialized");
632
633       clib_memcpy_fast (mac, save_mac, 6);
634
635       for (i = 0; i < count; i++)
636         {
637           kv.key = l2fib_make_key (mac, bd_index);
638           if (BV (clib_bihash_search) (&mp->mac_table, &kv, &kv))
639             {
640               clib_warning ("key %U AWOL", format_ethernet_address, mac);
641               break;
642             }
643           incr_mac_address (mac);
644         }
645     }
646
647   if (is_del)
648     {
649       clib_memcpy_fast (mac, save_mac, 6);
650
651       for (i = 0; i < count; i++)
652         {
653           l2fib_del_entry (mac, bd_index, 0);
654           incr_mac_address (mac);
655         }
656     }
657
658   return 0;
659 }
660
661 /*?
662  * The set of '<em>test l2fib</em>' commands allow the L2 FIB table of the default
663  * bridge domain (bridge-domain-id of 0) to be modified.
664  *
665  * @cliexpar
666  * @parblock
667  * Example of how to add a set of 4 sequential MAC Address entries to L2
668  * FIB table of the default bridge-domain:
669  * @cliexcmd{test l2fib add mac 52:54:00:53:00:00 count 4}
670  *
671  * Show the set of 4 sequential MAC Address entries that were added:
672  * @cliexstart{show l2fib verbose}
673  *     Mac Address     BD Idx           Interface           Index  static  filter  bvi  refresh  timestamp
674  * 52:54:00:53:00:00    0       GigabitEthernet0/8/0.300     8       0       0     0      0         0
675  * 52:54:00:53:00:01    0       GigabitEthernet0/8/0.300     8       0       0     0      0         0
676  * 52:54:00:53:00:03    0       GigabitEthernet0/8/0.300     8       0       0     0      0         0
677  * 52:54:00:53:00:02    0       GigabitEthernet0/8/0.300     8       0       0     0      0         0
678  * 4 l2fib entries
679  * @cliexend
680  *
681  * Example of how to check that the set of 4 sequential MAC Address
682  * entries were added to L2 FIB table of the default
683  * bridge-domain. Used a count of 5 to produce an error:
684  *
685  * @cliexcmd{test l2fib check mac 52:54:00:53:00:00 count 5}
686  * The output of the check command is in the log files. Log file
687  * location may vary based on your OS and Version:
688  *
689  * <b><em># tail -f /var/log/messages | grep l2fib_test_command_fn</em></b>
690  *
691  * Sep  7 17:15:24 localhost vnet[4952]: l2fib_test_command_fn:446: key 52:54:00:53:00:04 AWOL
692  *
693  * Example of how to delete a set of 4 sequential MAC Address entries
694  * from L2 FIB table of the default bridge-domain:
695  * @cliexcmd{test l2fib del mac 52:54:00:53:00:00 count 4}
696  * @endparblock
697 ?*/
698 /* *INDENT-OFF* */
699 VLIB_CLI_COMMAND (l2fib_test_command, static) = {
700   .path = "test l2fib",
701   .short_help = "test l2fib [add|del|check] mac <base-addr> count <nn>",
702   .function = l2fib_test_command_fn,
703 };
704 /* *INDENT-ON* */
705
706
707 /**
708  * Delete an entry from the l2fib.
709  * Return 0 if the entry was deleted, or 1 it was not found or if
710  * sw_if_index is non-zero and does not match that in the entry.
711  */
712 u32
713 l2fib_del_entry (const u8 * mac, u32 bd_index, u32 sw_if_index)
714 {
715   l2fib_entry_result_t result;
716   l2fib_main_t *mp = &l2fib_main;
717   BVT (clib_bihash_kv) kv;
718
719   if (mp->mac_table_initialized == 0)
720     return 1;
721
722   /* set up key */
723   kv.key = l2fib_make_key (mac, bd_index);
724
725   if (BV (clib_bihash_search) (&mp->mac_table, &kv, &kv))
726     return 1;
727
728   result.raw = kv.value;
729
730   /*  check if sw_if_index of entry match */
731   if ((sw_if_index != 0) && (sw_if_index != result.fields.sw_if_index))
732     return 1;
733
734   /* decrement counter if dynamically learned mac */
735   if ((!l2fib_entry_result_is_set_AGE_NOT (&result)) &&
736       (l2learn_main.global_learn_count))
737     l2learn_main.global_learn_count--;
738
739   /* Remove entry from hash table */
740   BV (clib_bihash_add_del) (&mp->mac_table, &kv, 0 /* is_add */ );
741   return 0;
742 }
743
744 /**
745  * Delete an entry from the L2FIB.
746  * The CLI format is:
747  *    l2fib del <mac> <bd-id>
748  */
749 static clib_error_t *
750 l2fib_del (vlib_main_t * vm,
751            unformat_input_t * input, vlib_cli_command_t * cmd)
752 {
753   bd_main_t *bdm = &bd_main;
754   clib_error_t *error = 0;
755   u8 mac[6];
756   u32 bd_id;
757   u32 bd_index;
758   uword *p;
759
760   if (!unformat (input, "%U", unformat_ethernet_address, mac))
761     {
762       error = clib_error_return (0, "expected mac address `%U'",
763                                  format_unformat_error, input);
764       goto done;
765     }
766
767   if (!unformat (input, "%d", &bd_id))
768     {
769       error = clib_error_return (0, "expected bridge domain ID `%U'",
770                                  format_unformat_error, input);
771       goto done;
772     }
773
774   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
775   if (!p)
776     {
777       error = clib_error_return (0, "bridge domain ID %d invalid", bd_id);
778       goto done;
779     }
780   bd_index = p[0];
781
782   /* Delete the entry */
783   if (l2fib_del_entry (mac, bd_index, 0))
784     {
785       error = clib_error_return (0, "mac entry not found");
786       goto done;
787     }
788
789 done:
790   return error;
791 }
792
793 /*?
794  * This command deletes an existing MAC Address entry from the L2 FIB
795  * table of an existing bridge-domain.
796  *
797  * @cliexpar
798  * Example of how to delete a MAC Address entry from the L2 FIB table of a bridge-domain (where 200 is the bridge-domain-id):
799  * @cliexcmd{l2fib del 52:54:00:53:18:33 200}
800 ?*/
801 /* *INDENT-OFF* */
802 VLIB_CLI_COMMAND (l2fib_del_cli, static) = {
803   .path = "l2fib del",
804   .short_help = "l2fib del <mac> <bridge-domain-id> []",
805   .function = l2fib_del,
806 };
807 /* *INDENT-ON* */
808
809 /**
810     Kick off ager to scan MACs to age/delete MAC entries
811 */
812 void
813 l2fib_start_ager_scan (vlib_main_t * vm)
814 {
815   uword evt = L2_MAC_AGE_PROCESS_EVENT_ONE_PASS;
816
817   /* check if there is at least one bd with mac aging enabled */
818   l2_bridge_domain_t *bd_config;
819   vec_foreach (bd_config, l2input_main.bd_configs)
820   {
821     if (bd_config->bd_id != ~0 && bd_config->mac_age != 0)
822       {
823         evt = L2_MAC_AGE_PROCESS_EVENT_START;
824         break;
825       }
826   }
827
828   vlib_process_signal_event (vm, l2fib_mac_age_scanner_process_node.index,
829                              evt, 0);
830 }
831
832 /**
833     Flush all non static MACs from an interface
834 */
835 void
836 l2fib_flush_int_mac (vlib_main_t * vm, u32 sw_if_index)
837 {
838   *l2fib_swif_seq_num (sw_if_index) += 1;
839   l2fib_start_ager_scan (vm);
840 }
841
842 /**
843     Flush all non static MACs in a bridge domain
844 */
845 void
846 l2fib_flush_bd_mac (vlib_main_t * vm, u32 bd_index)
847 {
848   l2_bridge_domain_t *bd_config = l2input_bd_config (bd_index);
849   bd_config->seq_num += 1;
850   l2fib_start_ager_scan (vm);
851 }
852
853 /**
854     Flush all non static MACs - flushes all valid BDs
855 */
856 void
857 l2fib_flush_all_mac (vlib_main_t * vm)
858 {
859   l2_bridge_domain_t *bd_config;
860   vec_foreach (bd_config, l2input_main.bd_configs)
861     if (bd_is_valid (bd_config))
862     bd_config->seq_num += 1;
863
864   l2fib_start_ager_scan (vm);
865 }
866
867
868 /**
869     Flush MACs, except static ones, associated with an interface
870     The CLI format is:
871     l2fib flush-mac interface <if-name>
872 */
873 static clib_error_t *
874 l2fib_flush_mac_int (vlib_main_t * vm,
875                      unformat_input_t * input, vlib_cli_command_t * cmd)
876 {
877   vnet_main_t *vnm = vnet_get_main ();
878   clib_error_t *error = 0;
879   u32 sw_if_index;
880
881   if (!unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
882     {
883       error = clib_error_return (0, "unknown interface `%U'",
884                                  format_unformat_error, input);
885       goto done;
886     }
887
888   l2fib_flush_int_mac (vm, sw_if_index);
889
890 done:
891   return error;
892 }
893
894 /**
895     Flush all MACs, except static ones
896     The CLI format is:
897     l2fib flush-mac all
898 */
899 static clib_error_t *
900 l2fib_flush_mac_all (vlib_main_t * vm,
901                      unformat_input_t * input, vlib_cli_command_t * cmd)
902 {
903   l2fib_flush_all_mac (vm);
904   return 0;
905 }
906
907 /*?
908  * This command kick off ager to delete all existing MAC Address entries,
909  * except static ones, associated with an interface from the L2 FIB table.
910  *
911  * @cliexpar
912  * Example of how to flush MAC Address entries learned on an interface from the L2 FIB table:
913  * @cliexcmd{l2fib flush-mac interface GigabitEthernet2/1/0}
914 ?*/
915 /* *INDENT-OFF* */
916 VLIB_CLI_COMMAND (l2fib_flush_mac_all_cli, static) = {
917   .path = "l2fib flush-mac all",
918   .short_help = "l2fib flush-mac all",
919   .function = l2fib_flush_mac_all,
920 };
921 /* *INDENT-ON* */
922
923 /*?
924  * This command kick off ager to delete all existing MAC Address entries,
925  * except static ones, associated with an interface from the L2 FIB table.
926  *
927  * @cliexpar
928  * Example of how to flush MAC Address entries learned on an interface from the L2 FIB table:
929  * @cliexcmd{l2fib flush-mac interface GigabitEthernet2/1/0}
930 ?*/
931 /* *INDENT-OFF* */
932 VLIB_CLI_COMMAND (l2fib_flush_mac_int_cli, static) = {
933   .path = "l2fib flush-mac interface",
934   .short_help = "l2fib flush-mac interface <if-name>",
935   .function = l2fib_flush_mac_int,
936 };
937 /* *INDENT-ON* */
938
939 /**
940     Flush bridge-domain MACs except static ones.
941     The CLI format is:
942     l2fib flush-mac bridge-domain <bd-id>
943 */
944 static clib_error_t *
945 l2fib_flush_mac_bd (vlib_main_t * vm,
946                     unformat_input_t * input, vlib_cli_command_t * cmd)
947 {
948   bd_main_t *bdm = &bd_main;
949   clib_error_t *error = 0;
950   u32 bd_index, bd_id;
951   uword *p;
952
953   if (!unformat (input, "%d", &bd_id))
954     {
955       error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
956                                  format_unformat_error, input);
957       goto done;
958     }
959
960   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
961   if (p)
962     bd_index = *p;
963   else
964     return clib_error_return (0, "No such bridge domain %d", bd_id);
965
966   l2fib_flush_bd_mac (vm, bd_index);
967
968 done:
969   return error;
970 }
971
972 /*?
973  * This command kick off ager to delete all existing MAC Address entries,
974  * except static ones, in a bridge domain from the L2 FIB table.
975  *
976  * @cliexpar
977  * Example of how to flush MAC Address entries learned in a bridge domain from the L2 FIB table:
978  * @cliexcmd{l2fib flush-mac bridge-domain 1000}
979 ?*/
980 /* *INDENT-OFF* */
981 VLIB_CLI_COMMAND (l2fib_flush_mac_bd_cli, static) = {
982   .path = "l2fib flush-mac bridge-domain",
983   .short_help = "l2fib flush-mac bridge-domain <bd-id>",
984   .function = l2fib_flush_mac_bd,
985 };
986 /* *INDENT-ON* */
987
988 clib_error_t *
989 l2fib_sw_interface_up_down (vnet_main_t * vnm, u32 sw_if_index, u32 flags)
990 {
991   l2_input_config_t *config = l2input_intf_config (sw_if_index);
992   if ((flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0 && config->bridge)
993     l2fib_flush_int_mac (vnm->vlib_main, sw_if_index);
994   return 0;
995 }
996
997 VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (l2fib_sw_interface_up_down);
998
999 BVT (clib_bihash) * get_mac_table (void)
1000 {
1001   l2fib_main_t *mp = &l2fib_main;
1002   return &mp->mac_table;
1003 }
1004
1005 static_always_inline void *
1006 allocate_mac_evt_buf (u32 client, u32 client_index)
1007 {
1008   l2fib_main_t *fm = &l2fib_main;
1009   vl_api_l2_macs_event_t *mp = vl_msg_api_alloc
1010     (sizeof (*mp) + (fm->max_macs_in_event * sizeof (vl_api_mac_entry_t)));
1011   mp->_vl_msg_id = htons (VL_API_L2_MACS_EVENT);
1012   mp->pid = htonl (client);
1013   mp->client_index = client_index;
1014   return mp;
1015 }
1016
1017 static_always_inline f64
1018 l2fib_scan (vlib_main_t * vm, f64 start_time, u8 event_only)
1019 {
1020   l2fib_main_t *fm = &l2fib_main;
1021   l2learn_main_t *lm = &l2learn_main;
1022
1023   BVT (clib_bihash) * h = &fm->mac_table;
1024   int i, j, k;
1025   f64 last_start = start_time;
1026   f64 accum_t = 0;
1027   f64 delta_t = 0;
1028   u32 evt_idx = 0;
1029   u32 learn_count = 0;
1030   u32 client = lm->client_pid;
1031   u32 cl_idx = lm->client_index;
1032   vl_api_l2_macs_event_t *mp = 0;
1033   vl_api_registration_t *reg = 0;
1034
1035   /* Don't scan the l2 fib if it hasn't been instantiated yet */
1036   if (alloc_arena (h) == 0)
1037     return 0.0;
1038
1039   if (client)
1040     {
1041       mp = allocate_mac_evt_buf (client, cl_idx);
1042       reg = vl_api_client_index_to_registration (lm->client_index);
1043     }
1044
1045   for (i = 0; i < h->nbuckets; i++)
1046     {
1047       /* allow no more than 20us without a pause */
1048       delta_t = vlib_time_now (vm) - last_start;
1049       if (delta_t > 20e-6)
1050         {
1051           vlib_process_suspend (vm, 100e-6);    /* suspend for 100 us */
1052           last_start = vlib_time_now (vm);
1053           accum_t += delta_t;
1054         }
1055
1056       if (i < (h->nbuckets - 3))
1057         {
1058           BVT (clib_bihash_bucket) * b =
1059             BV (clib_bihash_get_bucket) (h, i + 3);
1060           CLIB_PREFETCH (b, CLIB_CACHE_LINE_BYTES, LOAD);
1061           b = BV (clib_bihash_get_bucket) (h, i + 1);
1062           if (!BV (clib_bihash_bucket_is_empty) (b))
1063             {
1064               BVT (clib_bihash_value) * v =
1065                 BV (clib_bihash_get_value) (h, b->offset);
1066               CLIB_PREFETCH (v, CLIB_CACHE_LINE_BYTES, LOAD);
1067             }
1068         }
1069
1070       BVT (clib_bihash_bucket) * b = BV (clib_bihash_get_bucket) (h, i);
1071       if (BV (clib_bihash_bucket_is_empty) (b))
1072         continue;
1073       BVT (clib_bihash_value) * v = BV (clib_bihash_get_value) (h, b->offset);
1074       for (j = 0; j < (1 << b->log2_pages); j++)
1075         {
1076           for (k = 0; k < BIHASH_KVP_PER_PAGE; k++)
1077             {
1078               if (v->kvp[k].key == ~0ULL && v->kvp[k].value == ~0ULL)
1079                 continue;
1080
1081               l2fib_entry_key_t key = {.raw = v->kvp[k].key };
1082               l2fib_entry_result_t result = {.raw = v->kvp[k].value };
1083
1084               if (!l2fib_entry_result_is_set_AGE_NOT (&result))
1085                 learn_count++;
1086
1087               if (client)
1088                 {
1089                   if (PREDICT_FALSE (evt_idx >= fm->max_macs_in_event))
1090                     {
1091                       /* event message full, send it and start a new one */
1092                       if (reg && vl_api_can_send_msg (reg))
1093                         {
1094                           mp->n_macs = htonl (evt_idx);
1095                           vl_api_send_msg (reg, (u8 *) mp);
1096                           mp = allocate_mac_evt_buf (client, cl_idx);
1097                         }
1098                       else
1099                         {
1100                           if (reg)
1101                             clib_warning ("MAC event to pid %d queue stuffed!"
1102                                           " %d MAC entries lost", client,
1103                                           evt_idx);
1104                         }
1105                       evt_idx = 0;
1106                     }
1107
1108                   if (l2fib_entry_result_is_set_LRN_EVT (&result))
1109                     {
1110                       /* copy mac entry to event msg */
1111                       clib_memcpy_fast (mp->mac[evt_idx].mac_addr,
1112                                         key.fields.mac, 6);
1113                       mp->mac[evt_idx].action =
1114                         l2fib_entry_result_is_set_LRN_MOV (&result) ?
1115                         (vl_api_mac_event_action_t) MAC_EVENT_ACTION_MOVE
1116                         : (vl_api_mac_event_action_t) MAC_EVENT_ACTION_ADD;
1117                       mp->mac[evt_idx].action =
1118                         htonl (mp->mac[evt_idx].action);
1119                       mp->mac[evt_idx].sw_if_index =
1120                         htonl (result.fields.sw_if_index);
1121                       /* clear event bits and update mac entry */
1122                       l2fib_entry_result_clear_LRN_EVT (&result);
1123                       l2fib_entry_result_clear_LRN_MOV (&result);
1124                       BVT (clib_bihash_kv) kv;
1125                       kv.key = key.raw;
1126                       kv.value = result.raw;
1127                       BV (clib_bihash_add_del) (&fm->mac_table, &kv, 1);
1128                       evt_idx++;
1129                       continue; /* skip aging */
1130                     }
1131                 }
1132
1133               if (event_only || l2fib_entry_result_is_set_AGE_NOT (&result))
1134                 continue;       /* skip aging - static_mac always age_not */
1135
1136               /* start aging processing */
1137               u32 bd_index = key.fields.bd_index;
1138               u32 sw_if_index = result.fields.sw_if_index;
1139               u16 sn = l2fib_cur_seq_num (bd_index, sw_if_index).as_u16;
1140               if (result.fields.sn.as_u16 != sn)
1141                 goto age_out;   /* stale mac */
1142
1143               l2_bridge_domain_t *bd_config =
1144                 vec_elt_at_index (l2input_main.bd_configs, bd_index);
1145
1146               if (bd_config->mac_age == 0)
1147                 continue;       /* skip aging */
1148
1149               i16 delta = (u8) (start_time / 60) - result.fields.timestamp;
1150               delta += delta < 0 ? 256 : 0;
1151
1152               if (delta < bd_config->mac_age)
1153                 continue;       /* still valid */
1154
1155             age_out:
1156               if (client)
1157                 {
1158                   /* copy mac entry to event msg */
1159                   clib_memcpy_fast (mp->mac[evt_idx].mac_addr, key.fields.mac,
1160                                     6);
1161                   mp->mac[evt_idx].action =
1162                     (vl_api_mac_event_action_t) MAC_EVENT_ACTION_DELETE;
1163                   mp->mac[evt_idx].action = htonl (mp->mac[evt_idx].action);
1164                   mp->mac[evt_idx].sw_if_index =
1165                     htonl (result.fields.sw_if_index);
1166                   evt_idx++;
1167                 }
1168               /* delete mac entry */
1169               BVT (clib_bihash_kv) kv;
1170               kv.key = key.raw;
1171               BV (clib_bihash_add_del) (&fm->mac_table, &kv, 0);
1172               learn_count--;
1173               /*
1174                * Note: we may have just freed the bucket's backing
1175                * storage, so check right here...
1176                */
1177               if (BV (clib_bihash_bucket_is_empty) (b))
1178                 goto doublebreak;
1179             }
1180           v++;
1181         }
1182     doublebreak:
1183       ;
1184     }
1185
1186   /* keep learn count consistent */
1187   l2learn_main.global_learn_count = learn_count;
1188
1189   if (mp)
1190     {
1191       /*  send any outstanding mac event message else free message buffer */
1192       if (evt_idx)
1193         {
1194           if (reg && vl_api_can_send_msg (reg))
1195             {
1196               mp->n_macs = htonl (evt_idx);
1197               vl_api_send_msg (reg, (u8 *) mp);
1198             }
1199           else
1200             {
1201               if (reg)
1202                 clib_warning ("MAC event to pid %d queue stuffed!"
1203                               " %d MAC entries lost", client, evt_idx);
1204               vl_msg_api_free (mp);
1205             }
1206         }
1207       else
1208         vl_msg_api_free (mp);
1209     }
1210   return delta_t + accum_t;
1211 }
1212
1213 static uword
1214 l2fib_mac_age_scanner_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
1215                                vlib_frame_t * f)
1216 {
1217   uword event_type, *event_data = 0;
1218   l2fib_main_t *fm = &l2fib_main;
1219   l2learn_main_t *lm = &l2learn_main;
1220   bool enabled = 0;
1221   f64 start_time, next_age_scan_time = CLIB_TIME_MAX;
1222
1223   while (1)
1224     {
1225       if (lm->client_pid)
1226         vlib_process_wait_for_event_or_clock (vm, fm->event_scan_delay);
1227       else if (enabled)
1228         {
1229           f64 t = next_age_scan_time - vlib_time_now (vm);
1230           vlib_process_wait_for_event_or_clock (vm, t);
1231         }
1232       else
1233         vlib_process_wait_for_event (vm);
1234
1235       event_type = vlib_process_get_events (vm, &event_data);
1236       vec_reset_length (event_data);
1237
1238       start_time = vlib_time_now (vm);
1239       enum
1240       { SCAN_MAC_AGE, SCAN_MAC_EVENT, SCAN_DISABLE } scan = SCAN_MAC_AGE;
1241
1242       switch (event_type)
1243         {
1244         case ~0:                /* timer expired */
1245           if (lm->client_pid != 0 && start_time < next_age_scan_time)
1246             scan = SCAN_MAC_EVENT;
1247           break;
1248
1249         case L2_MAC_AGE_PROCESS_EVENT_START:
1250           enabled = 1;
1251           break;
1252
1253         case L2_MAC_AGE_PROCESS_EVENT_STOP:
1254           enabled = 0;
1255           scan = SCAN_DISABLE;
1256           break;
1257
1258         case L2_MAC_AGE_PROCESS_EVENT_ONE_PASS:
1259           break;
1260
1261         default:
1262           ASSERT (0);
1263         }
1264
1265       if (scan == SCAN_MAC_EVENT)
1266         l2fib_main.evt_scan_duration = l2fib_scan (vm, start_time, 1);
1267       else
1268         {
1269           if (scan == SCAN_MAC_AGE)
1270             l2fib_main.age_scan_duration = l2fib_scan (vm, start_time, 0);
1271           if (scan == SCAN_DISABLE)
1272             {
1273               l2fib_main.age_scan_duration = 0;
1274               l2fib_main.evt_scan_duration = 0;
1275             }
1276           /* schedule next scan */
1277           if (enabled)
1278             next_age_scan_time = start_time + L2FIB_AGE_SCAN_INTERVAL;
1279           else
1280             next_age_scan_time = CLIB_TIME_MAX;
1281         }
1282     }
1283   return 0;
1284 }
1285
1286 /* *INDENT-OFF* */
1287 VLIB_REGISTER_NODE (l2fib_mac_age_scanner_process_node) = {
1288     .function = l2fib_mac_age_scanner_process,
1289     .type = VLIB_NODE_TYPE_PROCESS,
1290     .name = "l2fib-mac-age-scanner-process",
1291 };
1292 /* *INDENT-ON* */
1293
1294 clib_error_t *
1295 l2fib_init (vlib_main_t * vm)
1296 {
1297   l2fib_main_t *mp = &l2fib_main;
1298   l2fib_entry_key_t test_key;
1299   u8 test_mac[6];
1300
1301   mp->vlib_main = vm;
1302   mp->vnet_main = vnet_get_main ();
1303   if (mp->mac_table_n_buckets == 0)
1304     mp->mac_table_n_buckets = L2FIB_NUM_BUCKETS;
1305   if (mp->mac_table_memory_size == 0)
1306     mp->mac_table_memory_size = L2FIB_MEMORY_SIZE;
1307   mp->mac_table_initialized = 0;
1308
1309   /* verify the key constructor is good, since it is endian-sensitive */
1310   clib_memset (test_mac, 0, sizeof (test_mac));
1311   test_mac[0] = 0x11;
1312   test_key.raw = 0;
1313   test_key.raw = l2fib_make_key ((u8 *) & test_mac, 0x1234);
1314   ASSERT (test_key.fields.mac[0] == 0x11);
1315   ASSERT (test_key.fields.bd_index == 0x1234);
1316
1317   return 0;
1318 }
1319
1320 VLIB_INIT_FUNCTION (l2fib_init);
1321
1322 static clib_error_t *
1323 lfib_config (vlib_main_t * vm, unformat_input_t * input)
1324 {
1325   l2fib_main_t *lm = &l2fib_main;
1326   uword table_size = ~0;
1327   u32 n_buckets = ~0;
1328
1329   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1330     {
1331       if (unformat (input, "table-size %U", unformat_memory_size,
1332                     &table_size))
1333         ;
1334       else if (unformat (input, "num-buckets %u", &n_buckets))
1335         ;
1336       else
1337         return clib_error_return (0, "unknown input `%U'",
1338                                   format_unformat_error, input);
1339     }
1340
1341   if (n_buckets != ~0)
1342     {
1343       if (!is_pow2 (n_buckets))
1344         return clib_error_return (0, "num-buckets must be power of 2");
1345       lm->mac_table_n_buckets = n_buckets;
1346     }
1347
1348   if (table_size != ~0)
1349     lm->mac_table_memory_size = table_size;
1350   return 0;
1351 }
1352
1353 VLIB_CONFIG_FUNCTION (lfib_config, "l2fib");
1354
1355 /*
1356  * fd.io coding-style-patch-verification: ON
1357  *
1358  * Local Variables:
1359  * eval: (c-set-style "gnu")
1360  * End:
1361  */