lldp: set interface MAC address when enabled
[vpp.git] / src / plugins / lldp / lldp_cli.c
1 /*
2  * Copyright (c) 2011-2016 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /**
17  * @file
18  * @brief LLDP CLI handling
19  *
20  */
21 #include <vnet/ethernet/ethernet.h>
22 #include <vnet/ip/ip.h>
23 #include <lldp/lldp.h>
24 #include <lldp/lldp_node.h>
25
26 #ifndef ETHER_ADDR_LEN
27 #include <net/ethernet.h>
28 #endif
29
30 static clib_error_t *
31 lldp_cfg_err_to_clib_err (lldp_cfg_err_t e)
32 {
33
34   switch (e)
35     {
36     case lldp_ok:
37       return 0;
38     case lldp_not_supported:
39       return clib_error_return (0, "not supported");
40     case lldp_invalid_arg:
41       return clib_error_return (0, "invalid argument");
42     case lldp_internal_error:
43       return clib_error_return (0, "internal error");
44     }
45   return 0;
46 }
47
48 lldp_cfg_err_t
49 lldp_cfg_intf_set (u32 hw_if_index, u8 ** port_desc, u8 ** mgmt_ip4,
50                    u8 ** mgmt_ip6, u8 ** mgmt_oid, int enable)
51 {
52   lldp_main_t *lm = &lldp_main;
53   vnet_main_t *vnm = lm->vnet_main;
54   ethernet_main_t *em = &ethernet_main;
55   const vnet_hw_interface_t *hi;
56   const ethernet_interface_t *eif;
57
58   if (pool_is_free_index (vnm->interface_main.hw_interfaces, hw_if_index))
59     {
60       return lldp_invalid_arg;
61     }
62
63   hi = vnet_get_hw_interface (vnm, hw_if_index);
64   eif = ethernet_get_interface (em, hw_if_index);
65   if (!eif)
66     {
67       return lldp_not_supported;
68     }
69
70   if (enable)
71     {
72       lldp_intf_t *n = lldp_get_intf (lm, hw_if_index);
73       if (n)
74         {
75           /* already enabled */
76           return lldp_ok;
77         }
78       n = lldp_create_intf (lm, hw_if_index);
79
80       if (port_desc && *port_desc)
81         {
82           n->port_desc = *port_desc;
83           *port_desc = NULL;
84         }
85
86       if (mgmt_ip4 && *mgmt_ip4)
87         {
88           n->mgmt_ip4 = *mgmt_ip4;
89           *mgmt_ip4 = NULL;
90         }
91
92       if (mgmt_ip6 && *mgmt_ip6)
93         {
94           n->mgmt_ip6 = *mgmt_ip6;
95           *mgmt_ip6 = NULL;
96         }
97
98       if (mgmt_oid && *mgmt_oid)
99         {
100           n->mgmt_oid = *mgmt_oid;
101           *mgmt_oid = NULL;
102         }
103
104       if (!vnet_hw_interface_add_del_mac_address (lm->vnet_main, hw_if_index,
105                                                   lldp_mac_addr,
106                                                   1 /* is_add */ ))
107         {
108           return lldp_internal_error;
109         }
110
111       const vnet_sw_interface_t *sw =
112         vnet_get_sw_interface (lm->vnet_main, hi->sw_if_index);
113       if (sw->flags & (VNET_SW_INTERFACE_FLAG_ADMIN_UP))
114         {
115           lldp_schedule_intf (lm, n);
116         }
117     }
118   else
119     {
120       lldp_intf_t *n = lldp_get_intf (lm, hi->sw_if_index);
121       lldp_delete_intf (lm, n);
122       if (n)
123         {
124           vnet_hw_interface_add_del_mac_address (lm->vnet_main, hw_if_index,
125                                                  lldp_mac_addr,
126                                                  0 /* is_add */ );
127         }
128     }
129
130   return lldp_ok;
131 }
132
133 static clib_error_t *
134 lldp_intf_cmd (vlib_main_t * vm, unformat_input_t * input,
135                vlib_cli_command_t * cmd)
136 {
137   lldp_main_t *lm = &lldp_main;
138   vnet_main_t *vnm = lm->vnet_main;
139   u32 sw_if_index = (u32) ~ 0;
140   int enable = 1;
141   u8 *port_desc = NULL;
142   u8 *mgmt_ip4 = NULL, *mgmt_ip6 = NULL, *mgmt_oid = NULL;
143   ip4_address_t ip4_addr;
144   ip6_address_t ip6_addr;
145
146   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
147     {
148       if (unformat (input, "sw_if_index %d", &sw_if_index))
149         ;
150       if (unformat
151           (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
152         ;
153       else if (unformat (input, "disable"))
154         enable = 0;
155       else if (unformat (input, "port-desc %s", &port_desc))
156         ;
157       else
158         if (unformat (input, "mgmt-ip4 %U", unformat_ip4_address, &ip4_addr))
159         {
160           vec_validate (mgmt_ip4, sizeof (ip4_address_t) - 1);
161           clib_memcpy (mgmt_ip4, &ip4_addr, vec_len (mgmt_ip4));
162         }
163       else
164         if (unformat (input, "mgmt-ip6 %U", unformat_ip6_address, &ip6_addr))
165         {
166           vec_validate (mgmt_ip6, sizeof (ip6_address_t) - 1);
167           clib_memcpy (mgmt_ip6, &ip6_addr, vec_len (mgmt_ip6));
168         }
169       else if (unformat (input, "mgmt-oid %s", &mgmt_oid))
170         ;
171       else
172         break;
173     }
174
175   if (sw_if_index == (u32) ~ 0)
176     return clib_error_return (0, "Interface name is invalid!");
177
178   return lldp_cfg_err_to_clib_err (lldp_cfg_intf_set (sw_if_index,
179                                                       &port_desc, &mgmt_ip4,
180                                                       &mgmt_ip6, &mgmt_oid,
181                                                       enable));
182 }
183
184 lldp_cfg_err_t
185 lldp_cfg_set (u8 ** host, int hold_time, int tx_interval)
186 {
187   lldp_main_t *lm = &lldp_main;
188   int reschedule = 0;
189
190   if (host && *host)
191     {
192       vec_free (lm->sys_name);
193       lm->sys_name = *host;
194       *host = NULL;
195     }
196
197   if (hold_time)
198     {
199       if (hold_time < LLDP_MIN_TX_HOLD || hold_time > LLDP_MAX_TX_HOLD)
200         {
201           return lldp_invalid_arg;
202         }
203       if (lm->msg_tx_hold != hold_time)
204         {
205           lm->msg_tx_hold = hold_time;
206           reschedule = 1;
207         }
208     }
209
210   if (tx_interval)
211     {
212       if (tx_interval < LLDP_MIN_TX_INTERVAL ||
213           tx_interval > LLDP_MAX_TX_INTERVAL)
214         {
215           return lldp_invalid_arg;
216         }
217       if (lm->msg_tx_interval != tx_interval)
218         {
219           reschedule = 1;
220           lm->msg_tx_interval = tx_interval;
221         }
222     }
223
224   if (reschedule)
225     {
226       vlib_process_signal_event (lm->vlib_main, lm->lldp_process_node_index,
227                                  LLDP_EVENT_RESCHEDULE, 0);
228     }
229
230   return lldp_ok;
231 }
232
233 static clib_error_t *
234 lldp_cfg_cmd (vlib_main_t * vm, unformat_input_t * input,
235               vlib_cli_command_t * cmd)
236 {
237   int hold_time = 0;
238   int tx_interval = 0;
239   u8 *host = NULL;
240   clib_error_t *ret = NULL;
241
242   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
243     {
244       if (unformat (input, "system-name %s", &host))
245         {
246         }
247       else if (unformat (input, "tx-hold %d", &hold_time))
248         {
249           if (hold_time < LLDP_MIN_TX_HOLD || hold_time > LLDP_MAX_TX_HOLD)
250             {
251               ret =
252                 clib_error_return (0,
253                                    "invalid tx-hold `%d' (out of range <%d,%d>)",
254                                    hold_time, LLDP_MIN_TX_HOLD,
255                                    LLDP_MAX_TX_HOLD);
256               goto out;
257             }
258         }
259       else if (unformat (input, "tx-interval %d", &tx_interval))
260         {
261           if (tx_interval < LLDP_MIN_TX_INTERVAL ||
262               tx_interval > LLDP_MAX_TX_INTERVAL)
263             {
264               ret =
265                 clib_error_return (0,
266                                    "invalid tx-interval `%d' (out of range <%d,%d>)",
267                                    tx_interval, LLDP_MIN_TX_INTERVAL,
268                                    LLDP_MAX_TX_INTERVAL);
269               goto out;
270             }
271         }
272       else
273         {
274           break;
275         }
276     }
277   ret =
278     lldp_cfg_err_to_clib_err (lldp_cfg_set (&host, hold_time, tx_interval));
279 out:
280   vec_free (host);
281   return ret;
282 }
283
284 /* *INDENT-OFF* */
285 VLIB_CLI_COMMAND(set_interface_lldp_cmd, static) = {
286   .path = "set interface lldp",
287   .short_help = "set interface lldp <interface> | sw_if_index <idx>"
288                 " [port-desc <string>] [mgmt-ip4 <string>]"
289                 " [mgmt-ip6 <string>] [mgmt-oid <string>] [disable]",
290   .function = lldp_intf_cmd,
291 };
292
293 VLIB_CLI_COMMAND(set_lldp_cmd, static) = {
294   .path = "set lldp",
295   .short_help = "set lldp [system-name <string>] [tx-hold <value>] "
296                 "[tx-interval <value>]",
297   .function = lldp_cfg_cmd,
298 };
299 /* *INDENT-ON* */
300
301 static const char *
302 lldp_chassis_id_subtype_str (lldp_chassis_id_subtype_t t)
303 {
304   switch (t)
305     {
306 #define F(num, val, str) \
307   case num:              \
308     return str;
309       foreach_chassis_id_subtype (F)
310 #undef F
311     }
312   return "unknown chassis subtype";
313 }
314
315 static const char *
316 lldp_port_id_subtype_str (lldp_port_id_subtype_t t)
317 {
318   switch (t)
319     {
320 #define F(num, val, str) \
321   case num:              \
322     return str;
323       foreach_port_id_subtype (F)
324 #undef F
325     }
326   return "unknown port subtype";
327 }
328
329 /*
330  * format port id subtype&value
331  *
332  * @param va - 1st argument - unsigned - port id subtype
333  * @param va - 2nd argument - u8* - port id
334  * @param va - 3rd argument - unsigned - port id length
335  * @param va - 4th argument - int - 1 for detailed output, 0 for simple
336  */
337 u8 *
338 format_lldp_port_id (u8 * s, va_list * va)
339 {
340   const lldp_port_id_subtype_t subtype = va_arg (*va, unsigned);
341   const u8 *id = va_arg (*va, u8 *);
342   const unsigned len = va_arg (*va, unsigned);
343   const int detail = va_arg (*va, int);
344   if (!id)
345     {
346       return s;
347     }
348   switch (subtype)
349     {
350     case LLDP_PORT_ID_SUBTYPE_NAME (intf_alias):
351       /* fallthrough */
352     case LLDP_PORT_ID_SUBTYPE_NAME (port_comp):
353       /* fallthrough */
354     case LLDP_PORT_ID_SUBTYPE_NAME (local):
355       /* fallthrough */
356     case LLDP_PORT_ID_SUBTYPE_NAME (intf_name):
357       if (detail)
358         {
359           s = format (s, "%U(%s)", format_ascii_bytes, id, len,
360                       lldp_port_id_subtype_str (subtype));
361         }
362       else
363         {
364           s = format (s, "%U", format_ascii_bytes, id, len);
365         }
366       break;
367     case LLDP_PORT_ID_SUBTYPE_NAME (mac_addr):
368       if (ETHER_ADDR_LEN == len)
369         {
370           if (detail)
371             {
372               s = format (s, "%U(%s)", format_mac_address, id,
373                           lldp_port_id_subtype_str (subtype));
374             }
375           else
376             {
377               s = format (s, "%U", format_mac_address, id);
378             }
379           break;
380         }
381       /* fallthrough */
382     case LLDP_PORT_ID_SUBTYPE_NAME (net_addr):
383       /* TODO */
384       /* fallthrough */
385     default:
386       if (detail)
387         {
388           s = format (s, "%U(%s)", format_hex_bytes, id, len,
389                       lldp_port_id_subtype_str (subtype));
390         }
391       else
392         {
393           s = format (s, "%U", format_hex_bytes, id, len);
394         }
395       break;
396     }
397   return s;
398 }
399
400 /*
401  * format chassis id subtype&value
402  *
403  * @param s format string
404  * @param va - 1st argument - unsigned - chassis id subtype
405  * @param va - 2nd argument - u8* - chassis id
406  * @param va - 3rd argument - unsigned - chassis id length
407  * @param va - 4th argument - int - 1 for detailed output, 0 for simple
408  */
409 u8 *
410 format_lldp_chassis_id (u8 * s, va_list * va)
411 {
412   const lldp_chassis_id_subtype_t subtype =
413     va_arg (*va, lldp_chassis_id_subtype_t);
414   const u8 *id = va_arg (*va, u8 *);
415   const unsigned len = va_arg (*va, unsigned);
416   const int detail = va_arg (*va, int);
417   if (!id)
418     {
419       return s;
420     }
421   switch (subtype)
422     {
423     case LLDP_CHASS_ID_SUBTYPE_NAME (chassis_comp):
424       /* fallthrough */
425     case LLDP_CHASS_ID_SUBTYPE_NAME (intf_alias):
426       /* fallthrough */
427     case LLDP_CHASS_ID_SUBTYPE_NAME (port_comp):
428       /* fallthrough */
429     case LLDP_PORT_ID_SUBTYPE_NAME (local):
430       /* fallthrough */
431     case LLDP_CHASS_ID_SUBTYPE_NAME (intf_name):
432       if (detail)
433         {
434           s = format (s, "%U(%s)", format_ascii_bytes, id, len,
435                       lldp_chassis_id_subtype_str (subtype));
436         }
437       else
438         {
439           s = format (s, "%U", format_ascii_bytes, id, len);
440         }
441       break;
442     case LLDP_CHASS_ID_SUBTYPE_NAME (mac_addr):
443       if (ETHER_ADDR_LEN == len)
444         {
445           if (detail)
446             {
447               s = format (s, "%U(%s)", format_mac_address, id,
448                           lldp_chassis_id_subtype_str (subtype));
449             }
450           else
451             {
452               s = format (s, "%U", format_mac_address, id);
453             }
454           break;
455         }
456       /* fallthrough */
457     case LLDP_CHASS_ID_SUBTYPE_NAME (net_addr):
458       /* TODO */
459     default:
460       if (detail)
461         {
462           s = format (s, "%U(%s)", format_hex_bytes, id, len,
463                       lldp_chassis_id_subtype_str (subtype));
464         }
465       else
466         {
467           s = format (s, "%U", format_hex_bytes, id, len);
468         }
469       break;
470     }
471   return s;
472 }
473
474 /*
475  * convert a tlv code to human-readable string
476  */
477 static const char *
478 lldp_tlv_code_str (lldp_tlv_code_t t)
479 {
480   switch (t)
481     {
482 #define F(n, t, s) \
483   case n:          \
484     return s;
485       foreach_lldp_tlv_type (F)
486 #undef F
487     }
488   return "unknown lldp tlv";
489 }
490
491 /*
492  * format a single LLDP TLV
493  *
494  * @param s format string
495  * @param va variable list - pointer to lldp_tlv_t is expected
496  */
497 u8 *
498 format_lldp_tlv (u8 * s, va_list * va)
499 {
500   const lldp_tlv_t *tlv = va_arg (*va, lldp_tlv_t *);
501   if (!tlv)
502     {
503       return s;
504     }
505   u16 l = lldp_tlv_get_length (tlv);
506   switch (lldp_tlv_get_code (tlv))
507     {
508     case LLDP_TLV_NAME (chassis_id):
509       s = format (s, "%U", format_lldp_chassis_id,
510                   ((lldp_chassis_id_tlv_t *) tlv)->subtype,
511                   ((lldp_chassis_id_tlv_t *) tlv)->id,
512                   l - STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype), 1);
513       break;
514     case LLDP_TLV_NAME (port_id):
515       s = format (s, "%U", format_lldp_port_id,
516                   ((lldp_port_id_tlv_t *) tlv)->subtype,
517                   ((lldp_port_id_tlv_t *) tlv)->id,
518                   l - STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype), 1);
519       break;
520     case LLDP_TLV_NAME (ttl):
521       s = format (s, "%d", ntohs (((lldp_ttl_tlv_t *) tlv)->ttl));
522       break;
523     case LLDP_TLV_NAME (sys_name):
524       /* fallthrough */
525     case LLDP_TLV_NAME (sys_desc):
526       s = format (s, "%U", format_ascii_bytes, tlv->v, l);
527       break;
528     default:
529       s = format (s, "%U", format_hex_bytes, tlv->v, l);
530     }
531
532   return s;
533 }
534
535 static u8 *
536 format_time_ago (u8 * s, va_list * va)
537 {
538   f64 ago = va_arg (*va, double);
539   f64 now = va_arg (*va, double);
540   if (ago < 0.01)
541     {
542       return format (s, "never");
543     }
544   return format (s, "%.1fs ago", now - ago);
545 }
546
547 static u8 *
548 format_lldp_intfs_detail (u8 * s, vlib_main_t * vm, const lldp_main_t * lm)
549 {
550   vnet_main_t *vnm = &vnet_main;
551   const lldp_intf_t *n;
552   const vnet_hw_interface_t *hw;
553   const vnet_sw_interface_t *sw;
554   s = format (s, "LLDP configuration:\n");
555   if (lm->sys_name)
556     {
557       s = format (s, "Configured system name: %U\n", format_ascii_bytes,
558                   lm->sys_name, vec_len (lm->sys_name));
559     }
560
561   s = format (s, "Configured tx-hold: %d\n", (int) lm->msg_tx_hold);
562   s = format (s, "Configured tx-interval: %d\n", (int) lm->msg_tx_interval);
563   s = format (s, "\nLLDP-enabled interface table:\n");
564   f64 now = vlib_time_now (vm);
565
566   /* *INDENT-OFF* */
567   pool_foreach(
568       n, lm->intfs, ({
569         hw = vnet_get_hw_interface(vnm, n->hw_if_index);
570         sw = vnet_get_sw_interface(lm->vnet_main, hw->sw_if_index);
571
572         s = format(s, "\nLocal Interface name: %v\n"
573                       "Local Port Description: %s\n",
574                        hw->name, n->port_desc);
575         if (n->mgmt_ip4)
576           {
577             s = format (s, "Local Management address: %U\n",
578                         format_ip4_address, n->mgmt_ip4, vec_len (n->mgmt_ip4));
579           }
580
581         if (n->mgmt_ip6)
582           {
583             s = format (s, "Local Management address IPV6: %U\n",
584                         format_ip6_address, n->mgmt_ip6, vec_len (n->mgmt_ip6));
585           }
586
587         if (n->mgmt_oid)
588           {
589             s = format (s, "Local Management address OID: %U\n",
590                         format_ascii_bytes, n->mgmt_oid, vec_len (n->mgmt_oid));
591           }
592
593         /* Interface shutdown */
594         if (!(sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
595           {
596             s = format(s, "Interface/peer state: interface down\n"
597                        "Last packet sent: %U\n",
598                        format_time_ago, n->last_sent, now);
599           }
600         else if (now < n->last_heard + n->ttl)
601           {
602             s = format(s,
603                        "Interface/peer state: active\n"
604                        "Peer chassis ID: %U\nRemote port ID: %U\n"
605                        "Last packet sent: %U\nLast packet received: %U\n",
606                        format_lldp_chassis_id, n->chassis_id_subtype,
607                        n->chassis_id, vec_len(n->chassis_id), 1,
608                        format_lldp_port_id, n->port_id_subtype, n->port_id,
609                        vec_len(n->port_id), 1, format_time_ago, n->last_sent,
610                        now, format_time_ago, n->last_heard, now);
611           }
612         else
613           {
614             s = format(s,
615                        "Interface/peer state: inactive(timeout)\n"
616                        "Last known peer chassis ID: %U\n"
617                        "Last known peer port ID: %U\nLast packet sent: %U\n"
618                        "Last packet received: %U\n",
619                        format_lldp_chassis_id, n->chassis_id_subtype,
620                        n->chassis_id, vec_len(n->chassis_id), 1,
621                        format_lldp_port_id, n->port_id_subtype, n->port_id,
622                        vec_len(n->port_id), 1, format_time_ago, n->last_sent,
623                        now, format_time_ago, n->last_heard, now);
624           }
625       }));
626   /* *INDENT-ON* */
627   return s;
628 }
629
630 static u8 *
631 format_lldp_intfs (u8 * s, va_list * va)
632 {
633   vlib_main_t *vm = va_arg (*va, vlib_main_t *);
634   const lldp_main_t *lm = va_arg (*va, lldp_main_t *);
635   const int detail = va_arg (*va, int);
636   vnet_main_t *vnm = &vnet_main;
637   const lldp_intf_t *n;
638
639   if (detail)
640     {
641       return format_lldp_intfs_detail (s, vm, lm);
642     }
643
644   f64 now = vlib_time_now (vm);
645   s = format (s, "%-25s %-25s %-25s %=15s %=15s %=10s\n", "Local interface",
646               "Peer chassis ID", "Remote port ID", "Last heard", "Last sent",
647               "Status");
648
649   /* *INDENT-OFF* */
650   pool_foreach(
651       n, lm->intfs, ({
652         const vnet_hw_interface_t *hw =
653             vnet_get_hw_interface(vnm, n->hw_if_index);
654         const vnet_sw_interface_t *sw =
655             vnet_get_sw_interface(lm->vnet_main, hw->sw_if_index);
656         /* Interface shutdown */
657         if (!(sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
658           continue;
659         if (now < n->last_heard + n->ttl)
660           {
661             s = format(s, "%-25v %-25U %-25U %=15U %=15U %=10s\n", hw->name,
662                        format_lldp_chassis_id, n->chassis_id_subtype,
663                        n->chassis_id, vec_len(n->chassis_id), 0,
664                        format_lldp_port_id, n->port_id_subtype, n->port_id,
665                        vec_len(n->port_id), 0, format_time_ago, n->last_heard,
666                        now, format_time_ago, n->last_sent, now, "active");
667           }
668         else
669           {
670             s = format(s, "%-25v %-25s %-25s %=15U %=15U %=10s\n", hw->name,
671                        "", "", format_time_ago, n->last_heard, now,
672                        format_time_ago, n->last_sent, now, "inactive");
673           }
674       }));
675   /* *INDENT-ON* */
676   return s;
677 }
678
679 static clib_error_t *
680 show_lldp (vlib_main_t * vm, unformat_input_t * input,
681            CLIB_UNUSED (vlib_cli_command_t * lmd))
682 {
683   lldp_main_t *lm = &lldp_main;
684
685   if (unformat (input, "detail"))
686     {
687       vlib_cli_output (vm, "%U\n", format_lldp_intfs, vm, lm, 1);
688     }
689   else
690     {
691       vlib_cli_output (vm, "%U\n", format_lldp_intfs, vm, lm, 0);
692     }
693   return 0;
694 }
695
696 /* *INDENT-OFF* */
697 VLIB_CLI_COMMAND(show_lldp_command, static) = {
698   .path = "show lldp",
699   .short_help = "show lldp [detail]",
700   .function = show_lldp,
701 };
702 /* *INDENT-ON* */
703
704 /*
705  * packet trace format function, very similar to
706  * lldp_packet_scan except that we call the per TLV format
707  * functions instead of the per TLV processing functions
708  */
709 u8 *
710 lldp_input_format_trace (u8 * s, va_list * args)
711 {
712   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
713   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
714   const lldp_input_trace_t *t = va_arg (*args, lldp_input_trace_t *);
715   const u8 *cur;
716   const lldp_tlv_t *tlv;
717   cur = t->data;
718   while (((cur + lldp_tlv_get_length ((lldp_tlv_t *) cur)) <
719           t->data + t->len))
720     {
721       tlv = (lldp_tlv_t *) cur;
722       if (cur == t->data)
723         {
724           s = format (s, "TLV #%d(%s): %U\n", lldp_tlv_get_code (tlv),
725                       lldp_tlv_code_str (lldp_tlv_get_code (tlv)),
726                       format_lldp_tlv, tlv);
727         }
728       else
729         {
730           s = format (s, "  TLV #%d(%s): %U\n", lldp_tlv_get_code (tlv),
731                       lldp_tlv_code_str (lldp_tlv_get_code (tlv)),
732                       format_lldp_tlv, tlv);
733         }
734       cur += STRUCT_SIZE_OF (lldp_tlv_t, head) + lldp_tlv_get_length (tlv);
735     }
736
737   return s;
738 }
739
740 /*
741  * fd.io coding-style-patch-verification: ON
742  *
743  * Local Variables:
744  * eval: (c-set-style "gnu")
745  * End:
746  */