VPP-69: Create fib or bridge domain with arbitrary ID
[vpp.git] / vnet / vnet / l2 / l2_bd.c
1 /*
2  * l2_bd.c : layer 2 bridge domain
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 #include <vlib/vlib.h>
19 #include <vnet/vnet.h>
20 #include <vlib/cli.h>
21 #include <vnet/ethernet/ethernet.h>
22 #include <vnet/ip/format.h>
23 #include <vnet/l2/l2_input.h>
24 #include <vnet/l2/feat_bitmap.h>
25 #include <vnet/l2/l2_bd.h>
26 #include <vnet/l2/l2_fib.h>
27 #include <vnet/l2/l2_vtr.h>
28 #include <vnet/ip/ip4_packet.h>
29 #include <vnet/ip/ip6_packet.h>
30
31 #include <vppinfra/error.h>
32 #include <vppinfra/hash.h>
33 #include <vppinfra/vec.h>
34
35 bd_main_t bd_main;
36
37 // Init bridge domain if not done already
38 // For feature bitmap, set all bits except ARP termination
39 void
40 bd_validate (l2_bridge_domain_t * bd_config) 
41 {
42   if (!bd_is_valid (bd_config)) {
43     bd_config->feature_bitmap = ~L2INPUT_FEAT_ARP_TERM;
44     bd_config->bvi_sw_if_index = ~0;
45     bd_config->members = 0;
46     bd_config->mac_by_ip4 = 0;
47 //    bd_config->mac_by_ip6 = hash_create_mem (0, sizeof(ip6_address_t), 
48 //                                           sizeof(uword));
49   }
50 }
51
52 u32 bd_find_or_add_bd_index (bd_main_t * bdm, u32 bd_id)
53 {
54   uword * p;
55   u32 rv;
56
57   if (bd_id == ~0) {
58     bd_id = 0;
59     while (hash_get (bdm->bd_index_by_bd_id, bd_id))
60       bd_id++;
61   } else {
62     p = hash_get (bdm->bd_index_by_bd_id, bd_id);
63     if (p)
64       return (p[0]);
65   }
66   
67   rv = clib_bitmap_first_clear (bdm->bd_index_bitmap);
68
69   // mark this index busy
70   bdm->bd_index_bitmap = clib_bitmap_set (bdm->bd_index_bitmap, rv, 1);
71
72   hash_set (bdm->bd_index_by_bd_id, bd_id, rv);
73
74   vec_validate (l2input_main.bd_configs, rv);
75   l2input_main.bd_configs[rv].bd_id = bd_id;
76
77   return rv;
78 }
79
80 int bd_delete_bd_index (bd_main_t * bdm, u32 bd_id)
81 {
82   uword * p;
83   u32 bd_index;
84
85   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
86   if (p == 0)
87     return -1;
88
89   bd_index = p[0];
90   
91   // mark this index clear
92   bdm->bd_index_bitmap = clib_bitmap_set (bdm->bd_index_bitmap, bd_index, 0);
93   hash_unset (bdm->bd_index_by_bd_id, bd_id);
94
95   l2input_main.bd_configs[bd_index].bd_id = ~0;
96   l2input_main.bd_configs[bd_index].feature_bitmap = 0;
97
98   return 0;
99 }
100
101 void
102 bd_add_member (l2_bridge_domain_t * bd_config,
103                l2_flood_member_t * member)
104 {
105   // Add one element to the vector
106
107   // When flooding, the bvi interface (if present) must be the last member
108   // processed due to how BVI processing can change the packet. To enable
109   // this order, we make the bvi interface the first in the vector and 
110   // flooding walks the vector in reverse.
111   if ((member->flags == L2_FLOOD_MEMBER_NORMAL) ||
112       (vec_len(bd_config->members) == 0)) {
113     vec_add1 (bd_config->members, *member);
114
115   } else {
116     // Move 0th element to the end
117     vec_add1 (bd_config->members, bd_config->members[0]);
118     bd_config->members[0] = *member;
119   }
120 }
121
122
123 #define BD_REMOVE_ERROR_OK        0
124 #define BD_REMOVE_ERROR_NOT_FOUND 1
125
126 u32
127 bd_remove_member (l2_bridge_domain_t * bd_config,
128                   u32 sw_if_index) 
129 {
130   u32 ix;
131  
132   // Find and delete the member
133   vec_foreach_index(ix, bd_config->members) {
134     if (vec_elt(bd_config->members, ix).sw_if_index == sw_if_index) {
135       vec_del1 (bd_config->members, ix);
136       return BD_REMOVE_ERROR_OK;
137     }
138   }
139
140   return BD_REMOVE_ERROR_NOT_FOUND;
141 }
142
143
144 clib_error_t *l2bd_init (vlib_main_t *vm)
145 {
146   bd_main_t *bdm = &bd_main;
147   u32 bd_index;
148   bdm->bd_index_by_bd_id = hash_create (0, sizeof(uword));
149   // create a dummy bd with bd_id of 0 and bd_index of 0 with feature set
150   // to packet drop only. Thus, packets received from any L2 interface with 
151   // uninitialized bd_index of 0 can be dropped safely.
152   bd_index = bd_find_or_add_bd_index (bdm, 0);
153   ASSERT (bd_index == 0);
154   l2input_main.bd_configs[0].feature_bitmap =  L2INPUT_FEAT_DROP;
155   return 0;
156 }
157
158 VLIB_INIT_FUNCTION (l2bd_init);
159
160
161 // Set the learn/forward/flood flags for the bridge domain
162 // Return 0 if ok, non-zero if for an error.
163 u32 
164 bd_set_flags (vlib_main_t * vm,
165               u32 bd_index,
166               u32 flags,
167               u32 enable) {
168
169   l2_bridge_domain_t * bd_config;
170   u32 feature_bitmap = 0;
171
172   vec_validate (l2input_main.bd_configs, bd_index);
173   bd_config = vec_elt_at_index(l2input_main.bd_configs, bd_index);
174
175   bd_validate (bd_config);
176
177   if (flags & L2_LEARN) {
178     feature_bitmap |= L2INPUT_FEAT_LEARN;
179   }
180   if (flags & L2_FWD) {
181     feature_bitmap |= L2INPUT_FEAT_FWD;
182   }
183   if (flags & L2_FLOOD) {
184     feature_bitmap |= L2INPUT_FEAT_FLOOD;
185   }
186   if (flags & L2_UU_FLOOD) {
187     feature_bitmap |= L2INPUT_FEAT_UU_FLOOD;
188   }
189   if (flags & L2_ARP_TERM) {
190     feature_bitmap |= L2INPUT_FEAT_ARP_TERM;
191   }
192
193   if (enable) {
194     bd_config->feature_bitmap |= feature_bitmap;
195   } else {
196     bd_config->feature_bitmap &= ~feature_bitmap;
197   }
198
199   return 0;
200 }
201
202 // set bridge-domain learn enable/disable
203 // The CLI format is:
204 //    set bridge-domain learn <bd_id> [disable]
205 static clib_error_t *
206 bd_learn (vlib_main_t * vm,
207            unformat_input_t * input,
208            vlib_cli_command_t * cmd)
209 {
210   bd_main_t * bdm = &bd_main;
211   clib_error_t * error = 0;
212   u32 bd_index, bd_id;
213   u32 enable;
214   uword * p;
215   
216   if (! unformat (input, "%d", &bd_id))
217     {
218       error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
219                                  format_unformat_error, input);
220       goto done;
221     }
222
223   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
224
225   if (p == 0)
226     return clib_error_return (0, "No such bridge domain %d", bd_id);
227   
228   bd_index = p[0];
229
230   enable = 1;
231   if (unformat (input, "disable")) {
232     enable = 0;
233   }
234
235   // set the bridge domain flag
236   if (bd_set_flags(vm, bd_index, L2_LEARN, enable)) {
237     error = clib_error_return (0, "bridge-domain id %d out of range", bd_index);
238     goto done;
239   }
240
241  done:
242   return error;
243 }
244
245 VLIB_CLI_COMMAND (bd_learn_cli, static) = {
246   .path = "set bridge-domain learn",
247   .short_help = "set bridge-domain learn <bridge-domain-id> [disable]",
248   .function = bd_learn,
249 };
250
251 // set bridge-domain forward enable/disable
252 // The CLI format is:
253 //    set bridge-domain forward <bd_index> [disable]
254 static clib_error_t *
255 bd_fwd (vlib_main_t * vm,
256         unformat_input_t * input,
257         vlib_cli_command_t * cmd)
258 {
259   bd_main_t * bdm = &bd_main;
260   clib_error_t * error = 0;
261   u32 bd_index, bd_id;
262   u32 enable;
263   uword * p;
264
265   if (! unformat (input, "%d", &bd_id))
266     {
267       error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
268                                  format_unformat_error, input);
269       goto done;
270     }
271
272   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
273
274   if (p == 0)
275     return clib_error_return (0, "No such bridge domain %d", bd_id);
276   
277   bd_index = p[0];
278
279   enable = 1;
280   if (unformat (input, "disable")) {
281     enable = 0;
282   }
283
284   // set the bridge domain flag
285   if (bd_set_flags(vm, bd_index, L2_FWD, enable)) {
286     error = clib_error_return (0, "bridge-domain id %d out of range", bd_index);
287     goto done;
288   }
289
290  done:
291   return error;
292 }
293
294 VLIB_CLI_COMMAND (bd_fwd_cli, static) = {
295   .path = "set bridge-domain forward",
296   .short_help = "set bridge-domain forward <bridge-domain-id> [disable]",
297   .function = bd_fwd,
298 };
299
300 // set bridge-domain flood enable/disable
301 // The CLI format is:
302 //    set bridge-domain flood <bd_index> [disable]
303 static clib_error_t *
304 bd_flood (vlib_main_t * vm,
305           unformat_input_t * input,
306           vlib_cli_command_t * cmd)
307 {
308   bd_main_t * bdm = &bd_main;
309   clib_error_t * error = 0;
310   u32 bd_index, bd_id;
311   u32 enable;
312   uword * p;
313
314   if (! unformat (input, "%d", &bd_id))
315     {
316       error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
317                                  format_unformat_error, input);
318       goto done;
319     }
320
321   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
322
323   if (p == 0)
324     return clib_error_return (0, "No such bridge domain %d", bd_id);
325   
326   bd_index = p[0];
327
328   enable = 1;
329   if (unformat (input, "disable")) {
330     enable = 0;
331   }
332
333   // set the bridge domain flag
334   if (bd_set_flags(vm, bd_index, L2_FLOOD, enable)) {
335     error = clib_error_return (0, "bridge-domain id %d out of range", bd_index);
336     goto done;
337   }
338
339  done:
340   return error;
341 }
342
343 VLIB_CLI_COMMAND (bd_flood_cli, static) = {
344   .path = "set bridge-domain flood",
345   .short_help = "set bridge-domain flood <bridge-domain-id> [disable]",
346   .function = bd_flood,
347 };
348
349 // set bridge-domain unkown-unicast flood enable/disable
350 // The CLI format is:
351 //    set bridge-domain uu-flood <bd_index> [disable]
352 static clib_error_t *
353 bd_uu_flood (vlib_main_t * vm,
354              unformat_input_t * input,
355              vlib_cli_command_t * cmd)
356 {
357   bd_main_t * bdm = &bd_main;
358   clib_error_t * error = 0;
359   u32 bd_index, bd_id;
360   u32 enable;
361   uword * p;
362
363   if (! unformat (input, "%d", &bd_id))
364     {
365       error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
366                                  format_unformat_error, input);
367       goto done;
368     }
369
370   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
371
372   if (p == 0)
373     return clib_error_return (0, "No such bridge domain %d", bd_id);
374   
375   bd_index = p[0];
376
377   enable = 1;
378   if (unformat (input, "disable")) {
379     enable = 0;
380   }
381
382   // set the bridge domain flag
383   if (bd_set_flags(vm, bd_index, L2_UU_FLOOD, enable)) {
384     error = clib_error_return (0, "bridge-domain id %d out of range", bd_index);
385     goto done;
386   }
387
388  done:
389   return error;
390 }
391
392 VLIB_CLI_COMMAND (bd_uu_flood_cli, static) = {
393   .path = "set bridge-domain uu-flood",
394   .short_help = "set bridge-domain uu-flood <bridge-domain-id> [disable]",
395   .function = bd_uu_flood,
396 };
397
398 // set bridge-domain arp term enable/disable
399 // The CLI format is:
400 //    set bridge-domain arp term <bridge-domain-id> [disable]
401 static clib_error_t *
402 bd_arp_term (vlib_main_t * vm,
403              unformat_input_t * input,
404              vlib_cli_command_t * cmd)
405 {
406   bd_main_t * bdm = &bd_main;
407   clib_error_t * error = 0;
408   u32 bd_index, bd_id;
409   u32 enable;
410   uword * p;
411
412   if (! unformat (input, "%d", &bd_id)) {
413     error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
414                                format_unformat_error, input);
415     goto done;
416   }
417
418   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
419   if (p) bd_index = *p;
420   else return clib_error_return (0, "No such bridge domain %d", bd_id);
421   
422   enable = 1;
423   if (unformat (input, "disable")) enable = 0;
424
425   // set the bridge domain flag
426   if (bd_set_flags(vm, bd_index, L2_ARP_TERM, enable)) {
427     error = clib_error_return (0, "bridge-domain id %d out of range", bd_index);
428     goto done;
429   }
430
431 done:
432   return error;
433 }
434
435 VLIB_CLI_COMMAND (bd_arp_term_cli, static) = {
436   .path = "set bridge-domain arp term",
437   .short_help = "set bridge-domain arp term <bridge-domain-id> [disable]",
438   .function = bd_arp_term,
439 };
440
441
442 // The clib hash implementation stores uword entries in the hash table. 
443 // The hash table mac_by_ip4 is keyed via IP4 address and store the 
444 // 6-byte MAC address directly in the hash table entry uword.
445 // This only works for 64-bit processor with 8-byte uword; which means
446 // this code *WILL NOT WORK* for a 32-bit prcessor with 4-byte uword.
447 u32 bd_add_del_ip_mac(u32 bd_index,
448                       u8 *ip_addr,
449                       u8 *mac_addr,
450                       u8 is_ip6,
451                       u8 is_add)
452 {
453   l2input_main_t * l2im = &l2input_main;
454   l2_bridge_domain_t * bd_cfg = l2input_bd_config_from_index (l2im, bd_index);
455   u64 new_mac = *(u64 *) mac_addr;
456   u64 * old_mac;
457   u16 * mac16 = (u16 *) &new_mac;
458
459   ASSERT (sizeof(uword) == sizeof(u64)); // make sure uword is 8 bytes
460
461   mac16[3] = 0; // Clear last 2 unsed bytes of the 8-byte MAC address
462   if (is_ip6) {
463     // ip6_address_t ip6_addr = *(ip6_address_t *) ip_addr;
464     return 1;   // not yet implemented
465   } else {
466     ip4_address_t ip4_addr = *(ip4_address_t *) ip_addr;
467     old_mac = (u64 *) hash_get (bd_cfg->mac_by_ip4, ip4_addr.as_u32);
468     if (is_add) {
469       if (old_mac && (*old_mac == new_mac)) return 0; // mac entry already exist
470       hash_set (bd_cfg->mac_by_ip4, ip4_addr.as_u32, new_mac);
471     } else {
472       if (old_mac && (*old_mac == new_mac)) { // mac entry match
473         hash_unset (bd_cfg->mac_by_ip4, ip4_addr.as_u32); // clear entry
474       } else {
475         return 1;
476       }
477     }
478     return 0;
479   }
480 }
481
482 // set bridge-domain arp entry add/delete
483 // The CLI format is:
484 //    set bridge-domain arp entry <bd-id> <ip-addr> <mac-addr> [del]
485 static clib_error_t *
486 bd_arp_entry (vlib_main_t * vm,
487               unformat_input_t * input,
488               vlib_cli_command_t * cmd)
489 {
490   bd_main_t * bdm = &bd_main;
491   clib_error_t * error = 0;
492   u32 bd_index, bd_id;
493   u8 is_add = 1;
494   u8 is_ip6 = 0;
495   u8 ip_addr[16];
496   u8 mac_addr[6];
497   uword * p;
498
499   if (! unformat (input, "%d", &bd_id)) {
500     error = clib_error_return (0, "expecting bridge-domain id but got `%U'",
501                                format_unformat_error, input);
502     goto done;
503   }
504
505   p = hash_get (bdm->bd_index_by_bd_id, bd_id);
506
507   if (p) bd_index = *p;
508   else return clib_error_return (0, "No such bridge domain %d", bd_id);
509   
510   if (unformat (input, "%U", unformat_ip4_address, ip_addr)) {
511     is_ip6 = 0;
512   } else if (unformat (input, "%U", unformat_ip6_address, ip_addr)) {
513     is_ip6 = 1;
514   } else {
515     error = clib_error_return (0, "expecting IP address but got `%U'",
516                                format_unformat_error, input);
517     goto done;
518   }
519
520   if (!unformat(input, "%U", unformat_ethernet_address, mac_addr)) {
521     error = clib_error_return (0, "expecting MAC address but got `%U'",
522                                format_unformat_error, input);
523     goto done;
524   }
525           
526   if (unformat (input, "del")) {
527     is_add = 0;
528   }
529
530   // set the bridge domain flagAdd IP-MAC entry into bridge domain
531   if (bd_add_del_ip_mac(bd_index, ip_addr, mac_addr, is_ip6, is_add)) {
532     error = clib_error_return (0, "MAC %s for IP %U and MAC %U failed",
533                                is_add ? "add" : "del", 
534                                format_ip4_address, ip_addr,
535                                format_ethernet_address, mac_addr);
536   }
537
538 done:
539   return error;
540 }
541
542 VLIB_CLI_COMMAND (bd_arp_entry_cli, static) = {
543   .path = "set bridge-domain arp entry",
544   .short_help = "set bridge-domain arp entry <bd-id> <ip-addr> <mac-addr> [del]",
545   .function = bd_arp_entry,
546 };
547
548 u8* format_vtr(u8 * s, va_list *args)
549 {
550     u32 vtr_op = va_arg (*args, u32);
551     u32 dot1q  = va_arg (*args, u32);
552     u32 tag1   = va_arg (*args, u32);
553     u32 tag2   = va_arg (*args, u32);
554     switch (vtr_op) {
555     case L2_VTR_DISABLED:
556         return format (s, "none");
557     case L2_VTR_PUSH_1:
558         return format (s, "push-1 %s %d", dot1q? "dot1q":"dot1ad", tag1);
559     case L2_VTR_PUSH_2:
560         return format (s, "push-2 %s %d %d", dot1q? "dot1q":"dot1ad", tag1, tag2);
561     case L2_VTR_POP_1:
562         return format (s, "pop-1");
563     case L2_VTR_POP_2:
564         return format (s, "pop-2");
565     case L2_VTR_TRANSLATE_1_1:
566         return format (s, "trans-1-1 %s %d", dot1q? "dot1q":"dot1ad", tag1);
567     case L2_VTR_TRANSLATE_1_2:
568         return format (s, "trans-1-2 %s %d %d",dot1q? "dot1q":"dot1ad",  tag1, tag2);
569     case L2_VTR_TRANSLATE_2_1:
570         return format (s, "trans-2-1 %s %d", dot1q? "dot1q":"dot1ad", tag1);
571     case L2_VTR_TRANSLATE_2_2:
572         return format (s, "trans-2-2 %s %d %d", dot1q? "dot1q":"dot1ad", tag1, tag2);
573     default:
574         return format (s, "none");
575     }
576 }
577
578 // show bridge-domain state
579 // The CLI format is:
580 //    show bridge-domain [<bd_index>]
581 static clib_error_t *
582 bd_show (vlib_main_t * vm,
583           unformat_input_t * input,
584           vlib_cli_command_t * cmd)
585 {
586   vnet_main_t * vnm = vnet_get_main();
587   bd_main_t * bdm = &bd_main;
588   clib_error_t * error = 0;
589   u32 bd_index = ~0;
590   l2_bridge_domain_t * bd_config;
591   u32 start, end;
592   u32 printed;
593   u32 detail = 0;
594   u32 intf = 0;
595   u32 arp = 0;
596   u32 bd_id = ~0;
597   uword * p;
598
599   start = 0;
600   end = vec_len(l2input_main.bd_configs);
601
602   if (unformat (input, "%d", &bd_id)) {
603     if (unformat (input, "detail")) detail = 1;
604     else if (unformat (input, "det")) detail = 1;
605     if (unformat (input, "int")) intf = 1;
606     if (unformat (input, "arp")) arp = 1;
607
608     p = hash_get (bdm->bd_index_by_bd_id, bd_id);
609     if (p) bd_index = *p;
610     else return clib_error_return (0, "No such bridge domain %d", bd_id);
611
612     vec_validate (l2input_main.bd_configs, bd_index);
613     bd_config = vec_elt_at_index(l2input_main.bd_configs, bd_index);
614     if (bd_is_valid (bd_config)) {
615       start = bd_index;
616       end = start + 1;
617     } else {
618       vlib_cli_output (vm, "bridge-domain %d not in use", bd_id);
619       goto done;
620     }
621   }
622
623   // Show all bridge-domains that have been initialized
624
625   printed = 0;
626   for (bd_index=start; bd_index<end; bd_index++) {
627     bd_config = vec_elt_at_index(l2input_main.bd_configs, bd_index);
628     if (bd_is_valid(bd_config)) {
629       if (!printed) {
630         printed = 1;
631         vlib_cli_output (vm, "%=5s %=7s %=10s %=10s %=10s %=10s %=10s %=14s", 
632                          "ID",
633                          "Index",
634                          "Learning",
635                          "U-Forwrd",
636                          "UU-Flood",
637                          "Flooding",
638                          "ARP-Term",
639                          "BVI-Intf");
640       }
641
642       vlib_cli_output (
643           vm, "%=5d %=7d %=10s %=10s %=10s %=10s %=10s %=14U", 
644           bd_config->bd_id, bd_index,
645           bd_config->feature_bitmap & L2INPUT_FEAT_LEARN ?    "on" : "off",
646           bd_config->feature_bitmap & L2INPUT_FEAT_FWD ?      "on" : "off",
647           bd_config->feature_bitmap & L2INPUT_FEAT_UU_FLOOD ? "on" : "off",
648           bd_config->feature_bitmap & L2INPUT_FEAT_FLOOD ?    "on" : "off",
649           bd_config->feature_bitmap & L2INPUT_FEAT_ARP_TERM ? "on" : "off",
650           format_vnet_sw_if_index_name_with_NA, vnm, bd_config->bvi_sw_if_index);
651
652       if (detail || intf) {
653         // Show all member interfaces
654
655         l2_flood_member_t * member;
656         u32 header = 0;
657
658         vec_foreach(member, bd_config->members) {
659           u32 vtr_opr, dot1q, tag1, tag2;
660           if (!header) {
661             header = 1;
662             vlib_cli_output (vm, "\n%=30s%=7s%=5s%=5s%=30s", 
663                              "Interface", "Index", "SHG", "BVI","VLAN-Tag-Rewrite");
664           }
665           l2vtr_get(vm, vnm, member->sw_if_index, &vtr_opr, &dot1q, &tag1, &tag2);
666           vlib_cli_output (vm, "%=30U%=7d%=5d%=5s%=30U", 
667                            format_vnet_sw_if_index_name, vnm, member->sw_if_index,
668                            member->sw_if_index,
669                            member->shg,
670                            member->flags & L2_FLOOD_MEMBER_BVI ? "*" : "-",
671                            format_vtr, vtr_opr, dot1q, tag1, tag2);
672         }
673       }
674
675       if ((detail || arp) && 
676           (bd_config->feature_bitmap & L2INPUT_FEAT_ARP_TERM)) {
677         u32 ip4_addr;
678         u64 mac_addr;
679         vlib_cli_output (vm, "\n  IP4 to MAC table for ARP Termination");
680         hash_foreach (ip4_addr, mac_addr, bd_config->mac_by_ip4, ({
681           vlib_cli_output (vm, "%=20U => %=20U", 
682                            format_ip4_address, &ip4_addr,
683                            format_ethernet_address, &mac_addr);
684         }));
685       }
686     }
687   }
688
689   if (!printed) {
690     vlib_cli_output (vm, "no bridge-domains in use");
691   }
692
693  done:
694   return error;
695 }
696
697 VLIB_CLI_COMMAND (bd_show_cli, static) = {
698   .path = "show bridge-domain",
699   .short_help = "show bridge-domain [bridge-domain-id [detail|int|arp]]",
700   .function = bd_show,
701 };