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