xref: /freebsd/contrib/tcpdump/print-cfm.c (revision b78ee15e9f04ae15c3e1200df974473167524d17)
1 /*
2  * Copyright (c) 1998-2006 The TCPDUMP project
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that: (1) source code
6  * distributions retain the above copyright notice and this paragraph
7  * in its entirety, and (2) distributions including binary code include
8  * the above copyright notice and this paragraph in its entirety in
9  * the documentation or other materials provided with the distribution.
10  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
11  * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
12  * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13  * FOR A PARTICULAR PURPOSE.
14  *
15  * Support for the IEEE Connectivity Fault Management Protocols as per 802.1ag.
16  *
17  * Original code by Hannes Gredler (hannes@juniper.net)
18  */
19 
20 #define NETDISSECT_REWORKED
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <tcpdump-stdinc.h>
26 
27 #include <stdio.h>
28 
29 #include "interface.h"
30 #include "extract.h"
31 #include "ether.h"
32 #include "addrtoname.h"
33 #include "oui.h"
34 #include "af.h"
35 
36 struct cfm_common_header_t {
37     uint8_t mdlevel_version;
38     uint8_t opcode;
39     uint8_t flags;
40     uint8_t first_tlv_offset;
41 };
42 
43 #define	CFM_VERSION 0
44 #define CFM_EXTRACT_VERSION(x) (((x)&0x1f))
45 #define CFM_EXTRACT_MD_LEVEL(x) (((x)&0xe0)>>5)
46 
47 #define	CFM_OPCODE_CCM 1
48 #define	CFM_OPCODE_LBR 2
49 #define	CFM_OPCODE_LBM 3
50 #define	CFM_OPCODE_LTR 4
51 #define	CFM_OPCODE_LTM 5
52 
53 static const struct tok cfm_opcode_values[] = {
54     { CFM_OPCODE_CCM, "Continouity Check Message"},
55     { CFM_OPCODE_LBR, "Loopback Reply"},
56     { CFM_OPCODE_LBM, "Loopback Message"},
57     { CFM_OPCODE_LTR, "Linktrace Reply"},
58     { CFM_OPCODE_LTM, "Linktrace Message"},
59     { 0, NULL}
60 };
61 
62 /*
63  * Message Formats.
64  */
65 struct cfm_ccm_t {
66     uint8_t sequence[4];
67     uint8_t ma_epi[2];
68     uint8_t md_nameformat;
69     uint8_t md_namelength;
70     uint8_t md_name[46]; /* md name and short ma name */
71     uint8_t reserved_itu[16];
72     uint8_t reserved[6];
73 };
74 
75 /*
76  * Timer Bases for the CCM Interval field.
77  * Expressed in units of seconds.
78  */
79 const float ccm_interval_base[8] = {0, 0.003333, 0.01, 0.1, 1, 10, 60, 600};
80 #define CCM_INTERVAL_MIN_MULTIPLIER 3.25
81 #define CCM_INTERVAL_MAX_MULTIPLIER 3.5
82 
83 #define CFM_CCM_RDI_FLAG 0x80
84 #define CFM_EXTRACT_CCM_INTERVAL(x) (((x)&0x07))
85 
86 #define CFM_CCM_MD_FORMAT_8021 0
87 #define CFM_CCM_MD_FORMAT_NONE 1
88 #define CFM_CCM_MD_FORMAT_DNS  2
89 #define CFM_CCM_MD_FORMAT_MAC  3
90 #define CFM_CCM_MD_FORMAT_CHAR 4
91 
92 static const struct tok cfm_md_nameformat_values[] = {
93     { CFM_CCM_MD_FORMAT_8021, "IEEE 802.1"},
94     { CFM_CCM_MD_FORMAT_NONE, "No MD Name present"},
95     { CFM_CCM_MD_FORMAT_DNS, "DNS string"},
96     { CFM_CCM_MD_FORMAT_MAC, "MAC + 16Bit Integer"},
97     { CFM_CCM_MD_FORMAT_CHAR, "Character string"},
98     { 0, NULL}
99 };
100 
101 #define CFM_CCM_MA_FORMAT_8021 0
102 #define CFM_CCM_MA_FORMAT_VID  1
103 #define CFM_CCM_MA_FORMAT_CHAR 2
104 #define CFM_CCM_MA_FORMAT_INT  3
105 #define CFM_CCM_MA_FORMAT_VPN  4
106 
107 static const struct tok cfm_ma_nameformat_values[] = {
108     { CFM_CCM_MA_FORMAT_8021, "IEEE 802.1"},
109     { CFM_CCM_MA_FORMAT_VID, "Primary VID"},
110     { CFM_CCM_MA_FORMAT_CHAR, "Character string"},
111     { CFM_CCM_MA_FORMAT_INT, "16Bit Integer"},
112     { CFM_CCM_MA_FORMAT_VPN, "RFC2685 VPN-ID"},
113     { 0, NULL}
114 };
115 
116 struct cfm_lbm_t {
117     uint8_t transaction_id[4];
118     uint8_t reserved[4];
119 };
120 
121 struct cfm_ltm_t {
122     uint8_t transaction_id[4];
123     uint8_t egress_id[8];
124     uint8_t ttl;
125     uint8_t original_mac[ETHER_ADDR_LEN];
126     uint8_t target_mac[ETHER_ADDR_LEN];
127     uint8_t reserved[3];
128 };
129 
130 static const struct tok cfm_ltm_flag_values[] = {
131     { 0x80, "Use Forwarding-DB only"},
132     { 0, NULL}
133 };
134 
135 struct cfm_ltr_t {
136     uint8_t transaction_id[4];
137     uint8_t last_egress_id[8];
138     uint8_t next_egress_id[8];
139     uint8_t ttl;
140     uint8_t replay_action;
141     uint8_t reserved[6];
142 };
143 
144 static const struct tok cfm_ltr_flag_values[] = {
145     { 0x80, "UseFDB Only"},
146     { 0x40, "FwdYes"},
147     { 0x20, "Terminal MEP"},
148     { 0, NULL}
149 };
150 
151 static const struct tok cfm_ltr_replay_action_values[] = {
152     { 1, "Exact Match"},
153     { 2, "Filtering DB"},
154     { 3, "MIP CCM DB"},
155     { 0, NULL}
156 };
157 
158 
159 #define CFM_TLV_END 0
160 #define CFM_TLV_SENDER_ID 1
161 #define CFM_TLV_PORT_STATUS 2
162 #define CFM_TLV_INTERFACE_STATUS 3
163 #define CFM_TLV_DATA 4
164 #define CFM_TLV_REPLY_INGRESS 5
165 #define CFM_TLV_REPLY_EGRESS 6
166 #define CFM_TLV_PRIVATE 31
167 
168 static const struct tok cfm_tlv_values[] = {
169     { CFM_TLV_END, "End"},
170     { CFM_TLV_SENDER_ID, "Sender ID"},
171     { CFM_TLV_PORT_STATUS, "Port status"},
172     { CFM_TLV_INTERFACE_STATUS, "Interface status"},
173     { CFM_TLV_DATA, "Data"},
174     { CFM_TLV_REPLY_INGRESS, "Reply Ingress"},
175     { CFM_TLV_REPLY_EGRESS, "Reply Egress"},
176     { CFM_TLV_PRIVATE, "Organization Specific"},
177     { 0, NULL}
178 };
179 
180 /*
181  * TLVs
182  */
183 
184 struct cfm_tlv_header_t {
185     uint8_t type;
186     uint8_t length[2];
187 };
188 
189 /* FIXME define TLV formats */
190 
191 static const struct tok cfm_tlv_port_status_values[] = {
192     { 1, "Blocked"},
193     { 2, "Up"},
194     { 0, NULL}
195 };
196 
197 static const struct tok cfm_tlv_interface_status_values[] = {
198     { 1, "Up"},
199     { 2, "Down"},
200     { 3, "Testing"},
201     { 5, "Dormant"},
202     { 6, "not present"},
203     { 7, "lower Layer down"},
204     { 0, NULL}
205 };
206 
207 #define CFM_CHASSIS_ID_CHASSIS_COMPONENT 1
208 #define CFM_CHASSIS_ID_INTERFACE_ALIAS 2
209 #define CFM_CHASSIS_ID_PORT_COMPONENT 3
210 #define CFM_CHASSIS_ID_MAC_ADDRESS 4
211 #define CFM_CHASSIS_ID_NETWORK_ADDRESS 5
212 #define CFM_CHASSIS_ID_INTERFACE_NAME 6
213 #define CFM_CHASSIS_ID_LOCAL 7
214 
215 static const struct tok cfm_tlv_senderid_chassisid_values[] = {
216     { 0, "Reserved"},
217     { CFM_CHASSIS_ID_CHASSIS_COMPONENT, "Chassis component"},
218     { CFM_CHASSIS_ID_INTERFACE_ALIAS, "Interface alias"},
219     { CFM_CHASSIS_ID_PORT_COMPONENT, "Port component"},
220     { CFM_CHASSIS_ID_MAC_ADDRESS, "MAC address"},
221     { CFM_CHASSIS_ID_NETWORK_ADDRESS, "Network address"},
222     { CFM_CHASSIS_ID_INTERFACE_NAME, "Interface name"},
223     { CFM_CHASSIS_ID_LOCAL, "Locally assigned"},
224     { 0, NULL}
225 };
226 
227 
228 static int
229 cfm_mgmt_addr_print(netdissect_options *ndo,
230                     register const u_char *tptr) {
231 
232     u_int mgmt_addr_type;
233     u_int hexdump =  FALSE;
234 
235     /*
236      * Altough AFIs are tpically 2 octects wide,
237      * 802.1ab specifies that this field width
238      * is only once octet
239      */
240     mgmt_addr_type = *tptr;
241     ND_PRINT((ndo, "\n\t  Management Address Type %s (%u)",
242            tok2str(af_values, "Unknown", mgmt_addr_type),
243            mgmt_addr_type));
244 
245     /*
246      * Resolve the passed in Address.
247      */
248     switch(mgmt_addr_type) {
249     case AFNUM_INET:
250         ND_PRINT((ndo, ", %s", ipaddr_string(ndo, tptr + 1)));
251         break;
252 
253 #ifdef INET6
254     case AFNUM_INET6:
255         ND_PRINT((ndo, ", %s", ip6addr_string(ndo, tptr + 1)));
256         break;
257 #endif
258 
259     default:
260         hexdump = TRUE;
261         break;
262     }
263 
264     return hexdump;
265 }
266 
267 /*
268  * The egress-ID string is a 16-Bit string plus a MAC address.
269  */
270 static const char *
271 cfm_egress_id_string(netdissect_options *ndo, register const u_char *tptr) {
272     static char egress_id_buffer[80];
273 
274     snprintf(egress_id_buffer, sizeof(egress_id_buffer),
275              "MAC 0x%4x-%s",
276              EXTRACT_16BITS(tptr),
277              etheraddr_string(ndo, tptr+2));
278 
279     return egress_id_buffer;
280 }
281 
282 void
283 cfm_print(netdissect_options *ndo,
284           register const u_char *pptr, register u_int length) {
285 
286     const struct cfm_common_header_t *cfm_common_header;
287     const struct cfm_tlv_header_t *cfm_tlv_header;
288     const uint8_t *tptr, *tlv_ptr, *ma_name, *ma_nameformat, *ma_namelength;
289     u_int hexdump, tlen, cfm_tlv_len, cfm_tlv_type, ccm_interval;
290 
291 
292     union {
293         const struct cfm_ccm_t *cfm_ccm;
294         const struct cfm_lbm_t *cfm_lbm;
295         const struct cfm_ltm_t *cfm_ltm;
296         const struct cfm_ltr_t *cfm_ltr;
297     } msg_ptr;
298 
299     tptr=pptr;
300     cfm_common_header = (const struct cfm_common_header_t *)pptr;
301     ND_TCHECK(*cfm_common_header);
302 
303     /*
304      * Sanity checking of the header.
305      */
306     if (CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version) != CFM_VERSION) {
307 	ND_PRINT((ndo, "CFMv%u not supported, length %u",
308                CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version), length));
309 	return;
310     }
311 
312     ND_PRINT((ndo, "CFMv%u %s, MD Level %u, length %u",
313            CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version),
314            tok2str(cfm_opcode_values, "unknown (%u)", cfm_common_header->opcode),
315            CFM_EXTRACT_MD_LEVEL(cfm_common_header->mdlevel_version),
316            length));
317 
318     /*
319      * In non-verbose mode just print the opcode and md-level.
320      */
321     if (ndo->ndo_vflag < 1) {
322         return;
323     }
324 
325     ND_PRINT((ndo, "\n\tFirst TLV offset %u", cfm_common_header->first_tlv_offset));
326 
327     tptr += sizeof(const struct cfm_common_header_t);
328     tlen = length - sizeof(struct cfm_common_header_t);
329 
330     switch (cfm_common_header->opcode) {
331     case CFM_OPCODE_CCM:
332         msg_ptr.cfm_ccm = (const struct cfm_ccm_t *)tptr;
333 
334         ccm_interval = CFM_EXTRACT_CCM_INTERVAL(cfm_common_header->flags);
335         ND_PRINT((ndo, ", Flags [CCM Interval %u%s]",
336                ccm_interval,
337                cfm_common_header->flags & CFM_CCM_RDI_FLAG ?
338                ", RDI" : ""));
339 
340         /*
341          * Resolve the CCM interval field.
342          */
343         if (ccm_interval) {
344             ND_PRINT((ndo, "\n\t  CCM Interval %.3fs"
345                    ", min CCM Lifetime %.3fs, max CCM Lifetime %.3fs",
346                    ccm_interval_base[ccm_interval],
347                    ccm_interval_base[ccm_interval] * CCM_INTERVAL_MIN_MULTIPLIER,
348                    ccm_interval_base[ccm_interval] * CCM_INTERVAL_MAX_MULTIPLIER));
349         }
350 
351         ND_PRINT((ndo, "\n\t  Sequence Number 0x%08x, MA-End-Point-ID 0x%04x",
352                EXTRACT_32BITS(msg_ptr.cfm_ccm->sequence),
353                EXTRACT_16BITS(msg_ptr.cfm_ccm->ma_epi)));
354 
355 
356         /*
357          * Resolve the MD fields.
358          */
359         ND_PRINT((ndo, "\n\t  MD Name Format %s (%u), MD Name length %u",
360                tok2str(cfm_md_nameformat_values, "Unknown",
361                        msg_ptr.cfm_ccm->md_nameformat),
362                msg_ptr.cfm_ccm->md_nameformat,
363                msg_ptr.cfm_ccm->md_namelength));
364 
365         if (msg_ptr.cfm_ccm->md_nameformat != CFM_CCM_MD_FORMAT_NONE) {
366             ND_PRINT((ndo, "\n\t  MD Name: "));
367             switch (msg_ptr.cfm_ccm->md_nameformat) {
368             case CFM_CCM_MD_FORMAT_DNS:
369             case CFM_CCM_MD_FORMAT_CHAR:
370                 safeputs(ndo, msg_ptr.cfm_ccm->md_name, msg_ptr.cfm_ccm->md_namelength);
371                 break;
372 
373             case CFM_CCM_MD_FORMAT_MAC:
374                 ND_PRINT((ndo, "\n\t  MAC %s", etheraddr_string(ndo,
375                            msg_ptr.cfm_ccm->md_name)));
376                 break;
377 
378                 /* FIXME add printers for those MD formats - hexdump for now */
379             case CFM_CCM_MA_FORMAT_8021:
380             default:
381                 print_unknown_data(ndo, msg_ptr.cfm_ccm->md_name, "\n\t    ",
382                                    msg_ptr.cfm_ccm->md_namelength);
383             }
384         }
385 
386 
387         /*
388          * Resolve the MA fields.
389          */
390         ma_nameformat = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength;
391         ma_namelength = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength + 1;
392         ma_name = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength + 2;
393 
394         ND_PRINT((ndo, "\n\t  MA Name-Format %s (%u), MA name length %u",
395                tok2str(cfm_ma_nameformat_values, "Unknown",
396                        *ma_nameformat),
397                *ma_nameformat,
398                *ma_namelength));
399 
400         ND_PRINT((ndo, "\n\t  MA Name: "));
401         switch (*ma_nameformat) {
402         case CFM_CCM_MA_FORMAT_CHAR:
403             safeputs(ndo, ma_name, *ma_namelength);
404             break;
405 
406             /* FIXME add printers for those MA formats - hexdump for now */
407         case CFM_CCM_MA_FORMAT_8021:
408         case CFM_CCM_MA_FORMAT_VID:
409         case CFM_CCM_MA_FORMAT_INT:
410         case CFM_CCM_MA_FORMAT_VPN:
411         default:
412             print_unknown_data(ndo, ma_name, "\n\t    ", *ma_namelength);
413         }
414         break;
415 
416     case CFM_OPCODE_LTM:
417         msg_ptr.cfm_ltm = (const struct cfm_ltm_t *)tptr;
418 
419         ND_PRINT((ndo, ", Flags [%s]",
420                bittok2str(cfm_ltm_flag_values, "none", cfm_common_header->flags)));
421 
422         ND_PRINT((ndo, "\n\t  Transaction-ID 0x%08x, Egress-ID %s, ttl %u",
423                EXTRACT_32BITS(msg_ptr.cfm_ltm->transaction_id),
424                cfm_egress_id_string(ndo, msg_ptr.cfm_ltm->egress_id),
425                msg_ptr.cfm_ltm->ttl));
426 
427         ND_PRINT((ndo, "\n\t  Original-MAC %s, Target-MAC %s",
428                etheraddr_string(ndo, msg_ptr.cfm_ltm->original_mac),
429                etheraddr_string(ndo, msg_ptr.cfm_ltm->target_mac)));
430         break;
431 
432     case CFM_OPCODE_LTR:
433         msg_ptr.cfm_ltr = (const struct cfm_ltr_t *)tptr;
434 
435         ND_PRINT((ndo, ", Flags [%s]",
436                bittok2str(cfm_ltr_flag_values, "none", cfm_common_header->flags)));
437 
438         ND_PRINT((ndo, "\n\t  Transaction-ID 0x%08x, Last-Egress-ID %s",
439                EXTRACT_32BITS(msg_ptr.cfm_ltr->transaction_id),
440                cfm_egress_id_string(ndo, msg_ptr.cfm_ltr->last_egress_id)));
441 
442         ND_PRINT((ndo, "\n\t  Next-Egress-ID %s, ttl %u",
443                cfm_egress_id_string(ndo, msg_ptr.cfm_ltr->next_egress_id),
444                msg_ptr.cfm_ltr->ttl));
445 
446         ND_PRINT((ndo, "\n\t  Replay-Action %s (%u)",
447                tok2str(cfm_ltr_replay_action_values,
448                        "Unknown",
449                        msg_ptr.cfm_ltr->replay_action),
450                msg_ptr.cfm_ltr->replay_action));
451         break;
452 
453         /*
454          * No message decoder yet.
455          * Hexdump everything up until the start of the TLVs
456          */
457     case CFM_OPCODE_LBR:
458     case CFM_OPCODE_LBM:
459     default:
460         if (tlen > cfm_common_header->first_tlv_offset) {
461             print_unknown_data(ndo, tptr, "\n\t  ",
462                                tlen -  cfm_common_header->first_tlv_offset);
463         }
464         break;
465     }
466 
467     /*
468      * Sanity check for not walking off.
469      */
470     if (tlen <= cfm_common_header->first_tlv_offset) {
471         return;
472     }
473 
474     tptr += cfm_common_header->first_tlv_offset;
475     tlen -= cfm_common_header->first_tlv_offset;
476 
477     while (tlen > 0) {
478         cfm_tlv_header = (const struct cfm_tlv_header_t *)tptr;
479 
480         /* Enough to read the tlv type ? */
481         ND_TCHECK2(*tptr, 1);
482         cfm_tlv_type=cfm_tlv_header->type;
483 
484         if (cfm_tlv_type != CFM_TLV_END) {
485             /* did we capture enough for fully decoding the object header ? */
486             ND_TCHECK2(*tptr, sizeof(struct cfm_tlv_header_t));
487             cfm_tlv_len=EXTRACT_16BITS(&cfm_tlv_header->length);
488         } else {
489             cfm_tlv_len = 0;
490         }
491 
492         ND_PRINT((ndo, "\n\t%s TLV (0x%02x), length %u",
493                tok2str(cfm_tlv_values, "Unknown", cfm_tlv_type),
494                cfm_tlv_type,
495                cfm_tlv_len));
496 
497         /* sanity check for not walking off and infinite loop check. */
498         if ((cfm_tlv_type != CFM_TLV_END) &&
499             ((cfm_tlv_len + sizeof(struct cfm_tlv_header_t) > tlen) ||
500              (!cfm_tlv_len))) {
501             print_unknown_data(ndo, tptr, "\n\t  ", tlen);
502             return;
503         }
504 
505         tptr += sizeof(struct cfm_tlv_header_t);
506         tlen -= sizeof(struct cfm_tlv_header_t);
507         tlv_ptr = tptr;
508 
509         /* did we capture enough for fully decoding the object ? */
510         if (cfm_tlv_type != CFM_TLV_END) {
511             ND_TCHECK2(*tptr, cfm_tlv_len);
512         }
513         hexdump = FALSE;
514 
515         switch(cfm_tlv_type) {
516         case CFM_TLV_END:
517             /* we are done - bail out */
518             return;
519 
520         case CFM_TLV_PORT_STATUS:
521             ND_PRINT((ndo, ", Status: %s (%u)",
522                    tok2str(cfm_tlv_port_status_values, "Unknown", *tptr),
523                    *tptr));
524             break;
525 
526         case CFM_TLV_INTERFACE_STATUS:
527             ND_PRINT((ndo, ", Status: %s (%u)",
528                    tok2str(cfm_tlv_interface_status_values, "Unknown", *tptr),
529                    *tptr));
530             break;
531 
532         case CFM_TLV_PRIVATE:
533             ND_PRINT((ndo, ", Vendor: %s (%u), Sub-Type %u",
534                    tok2str(oui_values,"Unknown", EXTRACT_24BITS(tptr)),
535                    EXTRACT_24BITS(tptr),
536                    *(tptr + 3)));
537             hexdump = TRUE;
538             break;
539 
540         case CFM_TLV_SENDER_ID:
541         {
542             u_int chassis_id_type, chassis_id_length;
543             u_int mgmt_addr_length;
544 
545             /*
546              * Check if there is a Chassis-ID.
547              */
548             chassis_id_length = *tptr;
549             if (chassis_id_length > tlen) {
550                 hexdump = TRUE;
551                 break;
552             }
553 
554             tptr++;
555             tlen--;
556 
557             if (chassis_id_length) {
558                 chassis_id_type = *tptr;
559                 ND_PRINT((ndo, "\n\t  Chassis-ID Type %s (%u), Chassis-ID length %u",
560                        tok2str(cfm_tlv_senderid_chassisid_values,
561                                "Unknown",
562                                chassis_id_type),
563                        chassis_id_type,
564                        chassis_id_length));
565 
566                 switch (chassis_id_type) {
567                 case CFM_CHASSIS_ID_MAC_ADDRESS:
568                     ND_PRINT((ndo, "\n\t  MAC %s", etheraddr_string(ndo, tptr + 1)));
569                     break;
570 
571                 case CFM_CHASSIS_ID_NETWORK_ADDRESS:
572                     hexdump |= cfm_mgmt_addr_print(ndo, tptr);
573                     break;
574 
575                 case CFM_CHASSIS_ID_INTERFACE_NAME: /* fall through */
576                 case CFM_CHASSIS_ID_INTERFACE_ALIAS:
577                 case CFM_CHASSIS_ID_LOCAL:
578                 case CFM_CHASSIS_ID_CHASSIS_COMPONENT:
579                 case CFM_CHASSIS_ID_PORT_COMPONENT:
580                     safeputs(ndo, tptr + 1, chassis_id_length);
581                     break;
582 
583                 default:
584                     hexdump = TRUE;
585                     break;
586                 }
587             }
588 
589             tptr += chassis_id_length;
590             tlen -= chassis_id_length;
591 
592             /*
593              * Check if there is a Management Address.
594              */
595             mgmt_addr_length = *tptr;
596             if (mgmt_addr_length > tlen) {
597                 hexdump = TRUE;
598                 break;
599             }
600 
601             tptr++;
602             tlen--;
603 
604             if (mgmt_addr_length) {
605                 hexdump |= cfm_mgmt_addr_print(ndo, tptr);
606             }
607 
608             tptr += mgmt_addr_length;
609             tlen -= mgmt_addr_length;
610 
611         }
612         break;
613 
614             /*
615              * FIXME those are the defined TLVs that lack a decoder
616              * you are welcome to contribute code ;-)
617              */
618 
619         case CFM_TLV_DATA:
620         case CFM_TLV_REPLY_INGRESS:
621         case CFM_TLV_REPLY_EGRESS:
622         default:
623             hexdump = TRUE;
624             break;
625         }
626         /* do we want to see an additional hexdump ? */
627         if (hexdump || ndo->ndo_vflag > 1)
628             print_unknown_data(ndo, tlv_ptr, "\n\t  ", cfm_tlv_len);
629 
630         tptr+=cfm_tlv_len;
631         tlen-=cfm_tlv_len;
632     }
633     return;
634 trunc:
635     ND_PRINT((ndo, "\n\t\t packet exceeded snapshot"));
636 }
637