xref: /freebsd/contrib/tcpdump/print-cfm.c (revision 39ee7a7a6bdd1557b1c3532abf60d139798ac88b)
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 {
273     static char egress_id_buffer[80];
274 
275     snprintf(egress_id_buffer, sizeof(egress_id_buffer),
276              "MAC 0x%4x-%s",
277              EXTRACT_16BITS(tptr),
278              etheraddr_string(ndo, tptr+2));
279 
280     return egress_id_buffer;
281 }
282 
283 void
284 cfm_print(netdissect_options *ndo,
285           register const u_char *pptr, register u_int length)
286 {
287     const struct cfm_common_header_t *cfm_common_header;
288     const struct cfm_tlv_header_t *cfm_tlv_header;
289     const uint8_t *tptr, *tlv_ptr, *ma_name, *ma_nameformat, *ma_namelength;
290     u_int hexdump, tlen, cfm_tlv_len, cfm_tlv_type, ccm_interval;
291 
292 
293     union {
294         const struct cfm_ccm_t *cfm_ccm;
295         const struct cfm_lbm_t *cfm_lbm;
296         const struct cfm_ltm_t *cfm_ltm;
297         const struct cfm_ltr_t *cfm_ltr;
298     } msg_ptr;
299 
300     tptr=pptr;
301     cfm_common_header = (const struct cfm_common_header_t *)pptr;
302     ND_TCHECK(*cfm_common_header);
303 
304     /*
305      * Sanity checking of the header.
306      */
307     if (CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version) != CFM_VERSION) {
308 	ND_PRINT((ndo, "CFMv%u not supported, length %u",
309                CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version), length));
310 	return;
311     }
312 
313     ND_PRINT((ndo, "CFMv%u %s, MD Level %u, length %u",
314            CFM_EXTRACT_VERSION(cfm_common_header->mdlevel_version),
315            tok2str(cfm_opcode_values, "unknown (%u)", cfm_common_header->opcode),
316            CFM_EXTRACT_MD_LEVEL(cfm_common_header->mdlevel_version),
317            length));
318 
319     /*
320      * In non-verbose mode just print the opcode and md-level.
321      */
322     if (ndo->ndo_vflag < 1) {
323         return;
324     }
325 
326     ND_PRINT((ndo, "\n\tFirst TLV offset %u", cfm_common_header->first_tlv_offset));
327 
328     tptr += sizeof(const struct cfm_common_header_t);
329     tlen = length - sizeof(struct cfm_common_header_t);
330 
331     switch (cfm_common_header->opcode) {
332     case CFM_OPCODE_CCM:
333         msg_ptr.cfm_ccm = (const struct cfm_ccm_t *)tptr;
334 
335         ccm_interval = CFM_EXTRACT_CCM_INTERVAL(cfm_common_header->flags);
336         ND_PRINT((ndo, ", Flags [CCM Interval %u%s]",
337                ccm_interval,
338                cfm_common_header->flags & CFM_CCM_RDI_FLAG ?
339                ", RDI" : ""));
340 
341         /*
342          * Resolve the CCM interval field.
343          */
344         if (ccm_interval) {
345             ND_PRINT((ndo, "\n\t  CCM Interval %.3fs"
346                    ", min CCM Lifetime %.3fs, max CCM Lifetime %.3fs",
347                    ccm_interval_base[ccm_interval],
348                    ccm_interval_base[ccm_interval] * CCM_INTERVAL_MIN_MULTIPLIER,
349                    ccm_interval_base[ccm_interval] * CCM_INTERVAL_MAX_MULTIPLIER));
350         }
351 
352         ND_PRINT((ndo, "\n\t  Sequence Number 0x%08x, MA-End-Point-ID 0x%04x",
353                EXTRACT_32BITS(msg_ptr.cfm_ccm->sequence),
354                EXTRACT_16BITS(msg_ptr.cfm_ccm->ma_epi)));
355 
356 
357         /*
358          * Resolve the MD fields.
359          */
360         ND_PRINT((ndo, "\n\t  MD Name Format %s (%u), MD Name length %u",
361                tok2str(cfm_md_nameformat_values, "Unknown",
362                        msg_ptr.cfm_ccm->md_nameformat),
363                msg_ptr.cfm_ccm->md_nameformat,
364                msg_ptr.cfm_ccm->md_namelength));
365 
366         if (msg_ptr.cfm_ccm->md_nameformat != CFM_CCM_MD_FORMAT_NONE) {
367             ND_PRINT((ndo, "\n\t  MD Name: "));
368             switch (msg_ptr.cfm_ccm->md_nameformat) {
369             case CFM_CCM_MD_FORMAT_DNS:
370             case CFM_CCM_MD_FORMAT_CHAR:
371                 safeputs(ndo, msg_ptr.cfm_ccm->md_name, msg_ptr.cfm_ccm->md_namelength);
372                 break;
373 
374             case CFM_CCM_MD_FORMAT_MAC:
375                 ND_PRINT((ndo, "\n\t  MAC %s", etheraddr_string(ndo,
376                            msg_ptr.cfm_ccm->md_name)));
377                 break;
378 
379                 /* FIXME add printers for those MD formats - hexdump for now */
380             case CFM_CCM_MA_FORMAT_8021:
381             default:
382                 print_unknown_data(ndo, msg_ptr.cfm_ccm->md_name, "\n\t    ",
383                                    msg_ptr.cfm_ccm->md_namelength);
384             }
385         }
386 
387 
388         /*
389          * Resolve the MA fields.
390          */
391         ma_nameformat = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength;
392         ma_namelength = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength + 1;
393         ma_name = msg_ptr.cfm_ccm->md_name + msg_ptr.cfm_ccm->md_namelength + 2;
394 
395         ND_PRINT((ndo, "\n\t  MA Name-Format %s (%u), MA name length %u",
396                tok2str(cfm_ma_nameformat_values, "Unknown",
397                        *ma_nameformat),
398                *ma_nameformat,
399                *ma_namelength));
400 
401         ND_PRINT((ndo, "\n\t  MA Name: "));
402         switch (*ma_nameformat) {
403         case CFM_CCM_MA_FORMAT_CHAR:
404             safeputs(ndo, ma_name, *ma_namelength);
405             break;
406 
407             /* FIXME add printers for those MA formats - hexdump for now */
408         case CFM_CCM_MA_FORMAT_8021:
409         case CFM_CCM_MA_FORMAT_VID:
410         case CFM_CCM_MA_FORMAT_INT:
411         case CFM_CCM_MA_FORMAT_VPN:
412         default:
413             print_unknown_data(ndo, ma_name, "\n\t    ", *ma_namelength);
414         }
415         break;
416 
417     case CFM_OPCODE_LTM:
418         msg_ptr.cfm_ltm = (const struct cfm_ltm_t *)tptr;
419 
420         ND_PRINT((ndo, ", Flags [%s]",
421                bittok2str(cfm_ltm_flag_values, "none", cfm_common_header->flags)));
422 
423         ND_PRINT((ndo, "\n\t  Transaction-ID 0x%08x, Egress-ID %s, ttl %u",
424                EXTRACT_32BITS(msg_ptr.cfm_ltm->transaction_id),
425                cfm_egress_id_string(ndo, msg_ptr.cfm_ltm->egress_id),
426                msg_ptr.cfm_ltm->ttl));
427 
428         ND_PRINT((ndo, "\n\t  Original-MAC %s, Target-MAC %s",
429                etheraddr_string(ndo, msg_ptr.cfm_ltm->original_mac),
430                etheraddr_string(ndo, msg_ptr.cfm_ltm->target_mac)));
431         break;
432 
433     case CFM_OPCODE_LTR:
434         msg_ptr.cfm_ltr = (const struct cfm_ltr_t *)tptr;
435 
436         ND_PRINT((ndo, ", Flags [%s]",
437                bittok2str(cfm_ltr_flag_values, "none", cfm_common_header->flags)));
438 
439         ND_PRINT((ndo, "\n\t  Transaction-ID 0x%08x, Last-Egress-ID %s",
440                EXTRACT_32BITS(msg_ptr.cfm_ltr->transaction_id),
441                cfm_egress_id_string(ndo, msg_ptr.cfm_ltr->last_egress_id)));
442 
443         ND_PRINT((ndo, "\n\t  Next-Egress-ID %s, ttl %u",
444                cfm_egress_id_string(ndo, msg_ptr.cfm_ltr->next_egress_id),
445                msg_ptr.cfm_ltr->ttl));
446 
447         ND_PRINT((ndo, "\n\t  Replay-Action %s (%u)",
448                tok2str(cfm_ltr_replay_action_values,
449                        "Unknown",
450                        msg_ptr.cfm_ltr->replay_action),
451                msg_ptr.cfm_ltr->replay_action));
452         break;
453 
454         /*
455          * No message decoder yet.
456          * Hexdump everything up until the start of the TLVs
457          */
458     case CFM_OPCODE_LBR:
459     case CFM_OPCODE_LBM:
460     default:
461         if (tlen > cfm_common_header->first_tlv_offset) {
462             print_unknown_data(ndo, tptr, "\n\t  ",
463                                tlen -  cfm_common_header->first_tlv_offset);
464         }
465         break;
466     }
467 
468     /*
469      * Sanity check for not walking off.
470      */
471     if (tlen <= cfm_common_header->first_tlv_offset) {
472         return;
473     }
474 
475     tptr += cfm_common_header->first_tlv_offset;
476     tlen -= cfm_common_header->first_tlv_offset;
477 
478     while (tlen > 0) {
479         cfm_tlv_header = (const struct cfm_tlv_header_t *)tptr;
480 
481         /* Enough to read the tlv type ? */
482         ND_TCHECK2(*tptr, 1);
483         cfm_tlv_type=cfm_tlv_header->type;
484 
485         if (cfm_tlv_type != CFM_TLV_END) {
486             /* did we capture enough for fully decoding the object header ? */
487             ND_TCHECK2(*tptr, sizeof(struct cfm_tlv_header_t));
488             cfm_tlv_len=EXTRACT_16BITS(&cfm_tlv_header->length);
489         } else {
490             cfm_tlv_len = 0;
491         }
492 
493         ND_PRINT((ndo, "\n\t%s TLV (0x%02x), length %u",
494                tok2str(cfm_tlv_values, "Unknown", cfm_tlv_type),
495                cfm_tlv_type,
496                cfm_tlv_len));
497 
498         /* sanity check for not walking off and infinite loop check. */
499         if ((cfm_tlv_type != CFM_TLV_END) &&
500             ((cfm_tlv_len + sizeof(struct cfm_tlv_header_t) > tlen) ||
501              (!cfm_tlv_len))) {
502             print_unknown_data(ndo, tptr, "\n\t  ", tlen);
503             return;
504         }
505 
506         tptr += sizeof(struct cfm_tlv_header_t);
507         tlen -= sizeof(struct cfm_tlv_header_t);
508         tlv_ptr = tptr;
509 
510         /* did we capture enough for fully decoding the object ? */
511         if (cfm_tlv_type != CFM_TLV_END) {
512             ND_TCHECK2(*tptr, cfm_tlv_len);
513         }
514         hexdump = FALSE;
515 
516         switch(cfm_tlv_type) {
517         case CFM_TLV_END:
518             /* we are done - bail out */
519             return;
520 
521         case CFM_TLV_PORT_STATUS:
522             ND_PRINT((ndo, ", Status: %s (%u)",
523                    tok2str(cfm_tlv_port_status_values, "Unknown", *tptr),
524                    *tptr));
525             break;
526 
527         case CFM_TLV_INTERFACE_STATUS:
528             ND_PRINT((ndo, ", Status: %s (%u)",
529                    tok2str(cfm_tlv_interface_status_values, "Unknown", *tptr),
530                    *tptr));
531             break;
532 
533         case CFM_TLV_PRIVATE:
534             ND_PRINT((ndo, ", Vendor: %s (%u), Sub-Type %u",
535                    tok2str(oui_values,"Unknown", EXTRACT_24BITS(tptr)),
536                    EXTRACT_24BITS(tptr),
537                    *(tptr + 3)));
538             hexdump = TRUE;
539             break;
540 
541         case CFM_TLV_SENDER_ID:
542         {
543             u_int chassis_id_type, chassis_id_length;
544             u_int mgmt_addr_length;
545 
546             /*
547              * Check if there is a Chassis-ID.
548              */
549             chassis_id_length = *tptr;
550             if (chassis_id_length > tlen) {
551                 hexdump = TRUE;
552                 break;
553             }
554 
555             tptr++;
556             tlen--;
557 
558             if (chassis_id_length) {
559                 chassis_id_type = *tptr;
560                 ND_PRINT((ndo, "\n\t  Chassis-ID Type %s (%u), Chassis-ID length %u",
561                        tok2str(cfm_tlv_senderid_chassisid_values,
562                                "Unknown",
563                                chassis_id_type),
564                        chassis_id_type,
565                        chassis_id_length));
566 
567                 switch (chassis_id_type) {
568                 case CFM_CHASSIS_ID_MAC_ADDRESS:
569                     ND_PRINT((ndo, "\n\t  MAC %s", etheraddr_string(ndo, tptr + 1)));
570                     break;
571 
572                 case CFM_CHASSIS_ID_NETWORK_ADDRESS:
573                     hexdump |= cfm_mgmt_addr_print(ndo, tptr);
574                     break;
575 
576                 case CFM_CHASSIS_ID_INTERFACE_NAME: /* fall through */
577                 case CFM_CHASSIS_ID_INTERFACE_ALIAS:
578                 case CFM_CHASSIS_ID_LOCAL:
579                 case CFM_CHASSIS_ID_CHASSIS_COMPONENT:
580                 case CFM_CHASSIS_ID_PORT_COMPONENT:
581                     safeputs(ndo, tptr + 1, chassis_id_length);
582                     break;
583 
584                 default:
585                     hexdump = TRUE;
586                     break;
587                 }
588             }
589 
590             tptr += chassis_id_length;
591             tlen -= chassis_id_length;
592 
593             /*
594              * Check if there is a Management Address.
595              */
596             mgmt_addr_length = *tptr;
597             if (mgmt_addr_length > tlen) {
598                 hexdump = TRUE;
599                 break;
600             }
601 
602             tptr++;
603             tlen--;
604 
605             if (mgmt_addr_length) {
606                 hexdump |= cfm_mgmt_addr_print(ndo, tptr);
607             }
608 
609             tptr += mgmt_addr_length;
610             tlen -= mgmt_addr_length;
611 
612         }
613         break;
614 
615             /*
616              * FIXME those are the defined TLVs that lack a decoder
617              * you are welcome to contribute code ;-)
618              */
619 
620         case CFM_TLV_DATA:
621         case CFM_TLV_REPLY_INGRESS:
622         case CFM_TLV_REPLY_EGRESS:
623         default:
624             hexdump = TRUE;
625             break;
626         }
627         /* do we want to see an additional hexdump ? */
628         if (hexdump || ndo->ndo_vflag > 1)
629             print_unknown_data(ndo, tlv_ptr, "\n\t  ", cfm_tlv_len);
630 
631         tptr+=cfm_tlv_len;
632         tlen-=cfm_tlv_len;
633     }
634     return;
635 trunc:
636     ND_PRINT((ndo, "\n\t\t packet exceeded snapshot"));
637 }
638