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