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