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