Example #1
0
static const uint32_t *
parse_wcc_attr(netdissect_options *ndo,
               const uint32_t *dp)
{
	/* Our caller has already checked this */
	ND_PRINT(" sz %" PRIu64, EXTRACT_BE_U_8(dp));
	ND_PRINT(" mtime %u.%06u ctime %u.%06u",
	       EXTRACT_BE_U_4(dp + 2), EXTRACT_BE_U_4(dp + 3),
	       EXTRACT_BE_U_4(dp + 4), EXTRACT_BE_U_4(dp + 5));
	return (dp + 6);
}
Example #2
0
static int
parsestatfs(netdissect_options *ndo,
            const uint32_t *dp, int v3)
{
	const struct nfs_statfs *sfsp;
	u_int er;

	dp = parsestatus(ndo, dp, &er);
	if (dp == NULL)
		return (0);
	if (!v3 && er)
		return (1);

	if (ndo->ndo_qflag)
		return(1);

	if (v3) {
		if (ndo->ndo_vflag)
			ND_PRINT(" POST:");
		if (!(dp = parse_post_op_attr(ndo, dp, ndo->ndo_vflag)))
			return (0);
	}

	ND_TCHECK_LEN(dp, (v3 ? NFSX_V3STATFS : NFSX_V2STATFS));

	sfsp = (const struct nfs_statfs *)dp;

	if (v3) {
		ND_PRINT(" tbytes %" PRIu64 " fbytes %" PRIu64 " abytes %" PRIu64,
			EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_tbytes),
			EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_fbytes),
			EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_abytes));
		if (ndo->ndo_vflag) {
			ND_PRINT(" tfiles %" PRIu64 " ffiles %" PRIu64 " afiles %" PRIu64 " invar %u",
			       EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_tfiles),
			       EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_ffiles),
			       EXTRACT_BE_U_8((const uint32_t *)&sfsp->sf_afiles),
			       EXTRACT_BE_U_4(&sfsp->sf_invarsec));
		}
	} else {
		ND_PRINT(" tsize %u bsize %u blocks %u bfree %u bavail %u",
			EXTRACT_BE_U_4(&sfsp->sf_tsize),
			EXTRACT_BE_U_4(&sfsp->sf_bsize),
			EXTRACT_BE_U_4(&sfsp->sf_blocks),
			EXTRACT_BE_U_4(&sfsp->sf_bfree),
			EXTRACT_BE_U_4(&sfsp->sf_bavail));
	}

	return (1);
trunc:
	return (0);
}
Example #3
0
static int
parsefsinfo(netdissect_options *ndo,
            const uint32_t *dp)
{
	const struct nfsv3_fsinfo *sfp;
	u_int er;

	if (!(dp = parsestatus(ndo, dp, &er)))
		return (0);
	if (ndo->ndo_vflag)
		ND_PRINT(" POST:");
	if (!(dp = parse_post_op_attr(ndo, dp, ndo->ndo_vflag)))
		return (0);
	if (er)
		return (1);

	sfp = (const struct nfsv3_fsinfo *)dp;
	ND_TCHECK_SIZE(sfp);
	ND_PRINT(" rtmax %u rtpref %u wtmax %u wtpref %u dtpref %u",
	       EXTRACT_BE_U_4(&sfp->fs_rtmax),
	       EXTRACT_BE_U_4(&sfp->fs_rtpref),
	       EXTRACT_BE_U_4(&sfp->fs_wtmax),
	       EXTRACT_BE_U_4(&sfp->fs_wtpref),
	       EXTRACT_BE_U_4(&sfp->fs_dtpref));
	if (ndo->ndo_vflag) {
		ND_PRINT(" rtmult %u wtmult %u maxfsz %" PRIu64,
		       EXTRACT_BE_U_4(&sfp->fs_rtmult),
		       EXTRACT_BE_U_4(&sfp->fs_wtmult),
		       EXTRACT_BE_U_8((const uint32_t *)&sfp->fs_maxfilesize));
		ND_PRINT(" delta %u.%06u ",
		       EXTRACT_BE_U_4(&sfp->fs_timedelta.nfsv3_sec),
		       EXTRACT_BE_U_4(&sfp->fs_timedelta.nfsv3_nsec));
	}
	return (1);
trunc:
	return (0);
}
Example #4
0
void
nfsreq_noaddr_print(netdissect_options *ndo,
                    const u_char *bp, u_int length,
                    const u_char *bp2)
{
	const struct sunrpc_msg *rp;
	const uint32_t *dp;
	nfs_type type;
	int v3;
	uint32_t proc;
	uint32_t access_flags;
	struct nfsv3_sattr sa3;

	ND_PRINT("%u", length);
	nfserr = 0;		/* assume no error */
	rp = (const struct sunrpc_msg *)bp;

	if (!xid_map_enter(ndo, rp, bp2))	/* record proc number for later on */
		goto trunc;

	v3 = (EXTRACT_BE_U_4(&rp->rm_call.cb_vers) == NFS_VER3);
	proc = EXTRACT_BE_U_4(&rp->rm_call.cb_proc);

	if (!v3 && proc < NFS_NPROCS)
		proc =  nfsv3_procid[proc];

	ND_PRINT(" %s", tok2str(nfsproc_str, "proc-%u", proc));
	switch (proc) {

	case NFSPROC_GETATTR:
	case NFSPROC_SETATTR:
	case NFSPROC_READLINK:
	case NFSPROC_FSSTAT:
	case NFSPROC_FSINFO:
	case NFSPROC_PATHCONF:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    parsefh(ndo, dp, v3) != NULL)
			return;
		break;

	case NFSPROC_LOOKUP:
	case NFSPROC_CREATE:
	case NFSPROC_MKDIR:
	case NFSPROC_REMOVE:
	case NFSPROC_RMDIR:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    parsefhn(ndo, dp, v3) != NULL)
			return;
		break;

	case NFSPROC_ACCESS:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			ND_TCHECK_4(dp);
			access_flags = EXTRACT_BE_U_4(dp);
			if (access_flags & ~NFSV3ACCESS_FULL) {
				/* NFSV3ACCESS definitions aren't up to date */
				ND_PRINT(" %04x", access_flags);
			} else if ((access_flags & NFSV3ACCESS_FULL) == NFSV3ACCESS_FULL) {
				ND_PRINT(" NFS_ACCESS_FULL");
			} else {
				char separator = ' ';
				if (access_flags & NFSV3ACCESS_READ) {
					ND_PRINT(" NFS_ACCESS_READ");
					separator = '|';
				}
				if (access_flags & NFSV3ACCESS_LOOKUP) {
					ND_PRINT("%cNFS_ACCESS_LOOKUP", separator);
					separator = '|';
				}
				if (access_flags & NFSV3ACCESS_MODIFY) {
					ND_PRINT("%cNFS_ACCESS_MODIFY", separator);
					separator = '|';
				}
				if (access_flags & NFSV3ACCESS_EXTEND) {
					ND_PRINT("%cNFS_ACCESS_EXTEND", separator);
					separator = '|';
				}
				if (access_flags & NFSV3ACCESS_DELETE) {
					ND_PRINT("%cNFS_ACCESS_DELETE", separator);
					separator = '|';
				}
				if (access_flags & NFSV3ACCESS_EXECUTE)
					ND_PRINT("%cNFS_ACCESS_EXECUTE", separator);
			}
			return;
		}
		break;

	case NFSPROC_READ:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			if (v3) {
				ND_TCHECK_4(dp + 2);
				ND_PRINT(" %u bytes @ %" PRIu64,
				       EXTRACT_BE_U_4(dp + 2),
				       EXTRACT_BE_U_8(dp));
			} else {
				ND_TCHECK_4(dp + 1);
				ND_PRINT(" %u bytes @ %u",
				    EXTRACT_BE_U_4(dp + 1),
				    EXTRACT_BE_U_4(dp));
			}
			return;
		}
		break;

	case NFSPROC_WRITE:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			if (v3) {
				ND_TCHECK_4(dp + 4);
				ND_PRINT(" %u (%u) bytes @ %" PRIu64,
						EXTRACT_BE_U_4(dp + 4),
						EXTRACT_BE_U_4(dp + 2),
						EXTRACT_BE_U_8(dp));
				if (ndo->ndo_vflag) {
					ND_PRINT(" <%s>",
						tok2str(nfsv3_writemodes,
							NULL, EXTRACT_BE_U_4(dp + 3)));
				}
			} else {
				ND_TCHECK_4(dp + 3);
				ND_PRINT(" %u (%u) bytes @ %u (%u)",
						EXTRACT_BE_U_4(dp + 3),
						EXTRACT_BE_U_4(dp + 2),
						EXTRACT_BE_U_4(dp + 1),
						EXTRACT_BE_U_4(dp));
			}
			return;
		}
		break;

	case NFSPROC_SYMLINK:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefhn(ndo, dp, v3)) != NULL) {
			ND_PRINT(" ->");
			if (v3 && (dp = parse_sattr3(ndo, dp, &sa3)) == NULL)
				break;
			if (parsefn(ndo, dp) == NULL)
				break;
			if (v3 && ndo->ndo_vflag)
				print_sattr3(ndo, &sa3, ndo->ndo_vflag);
			return;
		}
		break;

	case NFSPROC_MKNOD:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefhn(ndo, dp, v3)) != NULL) {
			ND_TCHECK_4(dp);
			type = (nfs_type) EXTRACT_BE_U_4(dp);
			dp++;
			if ((dp = parse_sattr3(ndo, dp, &sa3)) == NULL)
				break;
			ND_PRINT(" %s", tok2str(type2str, "unk-ft %u", type));
			if (ndo->ndo_vflag && (type == NFCHR || type == NFBLK)) {
				ND_TCHECK_4(dp + 1);
				ND_PRINT(" %u/%u",
				       EXTRACT_BE_U_4(dp),
				       EXTRACT_BE_U_4(dp + 1));
				dp += 2;
			}
			if (ndo->ndo_vflag)
				print_sattr3(ndo, &sa3, ndo->ndo_vflag);
			return;
		}
		break;

	case NFSPROC_RENAME:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefhn(ndo, dp, v3)) != NULL) {
			ND_PRINT(" ->");
			if (parsefhn(ndo, dp, v3) != NULL)
				return;
		}
		break;

	case NFSPROC_LINK:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			ND_PRINT(" ->");
			if (parsefhn(ndo, dp, v3) != NULL)
				return;
		}
		break;

	case NFSPROC_READDIR:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			if (v3) {
				ND_TCHECK_4(dp + 4);
				/*
				 * We shouldn't really try to interpret the
				 * offset cookie here.
				 */
				ND_PRINT(" %u bytes @ %" PRId64,
				    EXTRACT_BE_U_4(dp + 4),
				    EXTRACT_BE_U_8(dp));
				if (ndo->ndo_vflag) {
					/*
					 * This displays the 8 bytes
					 * of the verifier in order,
					 * from the low-order byte
					 * to the high-order byte.
					 */
					ND_PRINT(" verf %08x%08x",
						  EXTRACT_BE_U_4(dp + 2),
						  EXTRACT_BE_U_4(dp + 3));
				}
			} else {
				ND_TCHECK_4(dp + 1);
				/*
				 * Print the offset as signed, since -1 is
				 * common, but offsets > 2^31 aren't.
				 */
				ND_PRINT(" %u bytes @ %u",
				    EXTRACT_BE_U_4(dp + 1),
				    EXTRACT_BE_U_4(dp));
			}
			return;
		}
		break;

	case NFSPROC_READDIRPLUS:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			ND_TCHECK_4(dp + 4);
			/*
			 * We don't try to interpret the offset
			 * cookie here.
			 */
			ND_PRINT(" %u bytes @ %" PRId64,
				EXTRACT_BE_U_4(dp + 4),
				EXTRACT_BE_U_8(dp));
			if (ndo->ndo_vflag) {
				ND_TCHECK_4(dp + 5);
				/*
				 * This displays the 8 bytes
				 * of the verifier in order,
				 * from the low-order byte
				 * to the high-order byte.
				 */
				ND_PRINT(" max %u verf %08x%08x",
				          EXTRACT_BE_U_4(dp + 5),
					  EXTRACT_BE_U_4(dp + 2),
					  EXTRACT_BE_U_4(dp + 3));
			}
			return;
		}
		break;

	case NFSPROC_COMMIT:
		if ((dp = parsereq(ndo, rp, length)) != NULL &&
		    (dp = parsefh(ndo, dp, v3)) != NULL) {
			ND_TCHECK_4(dp + 2);
			ND_PRINT(" %u bytes @ %" PRIu64,
				EXTRACT_BE_U_4(dp + 2),
				EXTRACT_BE_U_8(dp));
			return;
		}
		break;

	default:
		return;
	}

trunc:
	if (!nfserr)
		ND_PRINT("%s", tstr);
}
Example #5
0
static const uint32_t *
parsefattr(netdissect_options *ndo,
           const uint32_t *dp, int verbose, int v3)
{
	const struct nfs_fattr *fap;

	fap = (const struct nfs_fattr *)dp;
	ND_TCHECK(fap->fa_gid);
	if (verbose) {
		/*
		 * XXX - UIDs and GIDs are unsigned in NFS and in
		 * at least some UN*Xes, but we'll show them as
		 * signed because -2 has traditionally been the
		 * UID for "nobody", rather than 4294967294.
		 */
		ND_PRINT(" %s %o ids %d/%d",
		    tok2str(type2str, "unk-ft %u ",
		    EXTRACT_BE_U_4(&fap->fa_type)),
		    EXTRACT_BE_U_4(&fap->fa_mode),
		    EXTRACT_BE_S_4(&fap->fa_uid),
		    EXTRACT_BE_S_4(&fap->fa_gid));
		if (v3) {
			ND_TCHECK(fap->fa3_size);
			ND_PRINT(" sz %" PRIu64,
				EXTRACT_BE_U_8((const uint32_t *)&fap->fa3_size));
		} else {
			ND_TCHECK(fap->fa2_size);
			ND_PRINT(" sz %u", EXTRACT_BE_U_4(&fap->fa2_size));
		}
	}
	/* print lots more stuff */
	if (verbose > 1) {
		if (v3) {
			ND_TCHECK(fap->fa3_ctime);
			ND_PRINT(" nlink %u rdev %u/%u",
			       EXTRACT_BE_U_4(&fap->fa_nlink),
			       EXTRACT_BE_U_4(&fap->fa3_rdev.specdata1),
			       EXTRACT_BE_U_4(&fap->fa3_rdev.specdata2));
			ND_PRINT(" fsid %" PRIx64,
				EXTRACT_BE_U_8((const uint32_t *)&fap->fa3_fsid));
			ND_PRINT(" fileid %" PRIx64,
				EXTRACT_BE_U_8((const uint32_t *)&fap->fa3_fileid));
			ND_PRINT(" a/m/ctime %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa3_atime.nfsv3_sec),
			       EXTRACT_BE_U_4(&fap->fa3_atime.nfsv3_nsec));
			ND_PRINT(" %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa3_mtime.nfsv3_sec),
			       EXTRACT_BE_U_4(&fap->fa3_mtime.nfsv3_nsec));
			ND_PRINT(" %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa3_ctime.nfsv3_sec),
			       EXTRACT_BE_U_4(&fap->fa3_ctime.nfsv3_nsec));
		} else {
			ND_TCHECK(fap->fa2_ctime);
			ND_PRINT(" nlink %u rdev 0x%x fsid 0x%x nodeid 0x%x a/m/ctime",
			       EXTRACT_BE_U_4(&fap->fa_nlink),
			       EXTRACT_BE_U_4(&fap->fa2_rdev),
			       EXTRACT_BE_U_4(&fap->fa2_fsid),
			       EXTRACT_BE_U_4(&fap->fa2_fileid));
			ND_PRINT(" %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa2_atime.nfsv2_sec),
			       EXTRACT_BE_U_4(&fap->fa2_atime.nfsv2_usec));
			ND_PRINT(" %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa2_mtime.nfsv2_sec),
			       EXTRACT_BE_U_4(&fap->fa2_mtime.nfsv2_usec));
			ND_PRINT(" %u.%06u",
			       EXTRACT_BE_U_4(&fap->fa2_ctime.nfsv2_sec),
			       EXTRACT_BE_U_4(&fap->fa2_ctime.nfsv2_usec));
		}
	}
	return ((const uint32_t *)((const unsigned char *)dp +
		(v3 ? NFSX_V3FATTR : NFSX_V2FATTR)));
trunc:
	return (NULL);
}
Example #6
0
static void
slow_oam_print(netdissect_options *ndo,
               const u_char *tptr, u_int tlen)
{
    uint8_t code;
    uint8_t type, length;
    uint8_t state;
    uint8_t command;
    u_int hexdump;

    struct slow_oam_common_header_t {
        nd_uint16_t flags;
        nd_uint8_t code;
    };

    struct slow_oam_tlv_header_t {
        nd_uint8_t type;
        nd_uint8_t length;
    };

    union {
        const struct slow_oam_common_header_t *slow_oam_common_header;
        const struct slow_oam_tlv_header_t *slow_oam_tlv_header;
    } ptr;

    union {
	const struct slow_oam_info_t *slow_oam_info;
        const struct slow_oam_link_event_t *slow_oam_link_event;
        const struct slow_oam_variablerequest_t *slow_oam_variablerequest;
        const struct slow_oam_variableresponse_t *slow_oam_variableresponse;
        const struct slow_oam_loopbackctrl_t *slow_oam_loopbackctrl;
    } tlv;

    ptr.slow_oam_common_header = (const struct slow_oam_common_header_t *)tptr;
    if (tlen < sizeof(*ptr.slow_oam_common_header))
        goto tooshort;
    ND_TCHECK_SIZE(ptr.slow_oam_common_header);
    tptr += sizeof(struct slow_oam_common_header_t);
    tlen -= sizeof(struct slow_oam_common_header_t);

    code = EXTRACT_U_1(ptr.slow_oam_common_header->code);
    ND_PRINT("\n\tCode %s OAM PDU, Flags [%s]",
           tok2str(slow_oam_code_values, "Unknown (%u)", code),
           bittok2str(slow_oam_flag_values,
                      "none",
                      EXTRACT_BE_U_2(ptr.slow_oam_common_header->flags)));

    switch (code) {
    case SLOW_OAM_CODE_INFO:
        while (tlen > 0) {
            ptr.slow_oam_tlv_header = (const struct slow_oam_tlv_header_t *)tptr;
            if (tlen < sizeof(*ptr.slow_oam_tlv_header))
                goto tooshort;
            ND_TCHECK_SIZE(ptr.slow_oam_tlv_header);
            type = EXTRACT_U_1(ptr.slow_oam_tlv_header->type);
            length = EXTRACT_U_1(ptr.slow_oam_tlv_header->length);
            ND_PRINT("\n\t  %s Information Type (%u), length %u",
                   tok2str(slow_oam_info_type_values, "Reserved", type),
                   type,
                   length);

            if (type == SLOW_OAM_INFO_TYPE_END_OF_TLV) {
                /*
                 * As IEEE Std 802.3-2015 says for the End of TLV Marker,
                 * "(the length and value of the Type 0x00 TLV can be ignored)".
                 */
                return;
            }

            /* length includes the type and length fields */
            if (length < sizeof(struct slow_oam_tlv_header_t)) {
                ND_PRINT("\n\t    ERROR: illegal length - should be >= %u",
                       (u_int)sizeof(struct slow_oam_tlv_header_t));
                return;
            }

            if (tlen < length)
                goto tooshort;
            ND_TCHECK_LEN(tptr, length);

            hexdump = FALSE;
            switch (type) {
            case SLOW_OAM_INFO_TYPE_LOCAL: /* identical format - fall through */
            case SLOW_OAM_INFO_TYPE_REMOTE:
                tlv.slow_oam_info = (const struct slow_oam_info_t *)tptr;

                if (EXTRACT_U_1(tlv.slow_oam_info->info_length) !=
                    sizeof(struct slow_oam_info_t)) {
                    ND_PRINT("\n\t    ERROR: illegal length - should be %lu",
                           (unsigned long) sizeof(struct slow_oam_info_t));
                    hexdump = TRUE;
                    goto badlength_code_info;
                }

                ND_PRINT("\n\t    OAM-Version %u, Revision %u",
                       EXTRACT_U_1(tlv.slow_oam_info->oam_version),
                       EXTRACT_BE_U_2(tlv.slow_oam_info->revision));

                state = EXTRACT_U_1(tlv.slow_oam_info->state);
                ND_PRINT("\n\t    State-Parser-Action %s, State-MUX-Action %s",
                       tok2str(slow_oam_info_type_state_parser_values, "Reserved",
                               state & OAM_INFO_TYPE_PARSER_MASK),
                       tok2str(slow_oam_info_type_state_mux_values, "Reserved",
                               state & OAM_INFO_TYPE_MUX_MASK));
                ND_PRINT("\n\t    OAM-Config Flags [%s], OAM-PDU-Config max-PDU size %u",
                       bittok2str(slow_oam_info_type_oam_config_values, "none",
                                  EXTRACT_U_1(tlv.slow_oam_info->oam_config)),
                       EXTRACT_BE_U_2(tlv.slow_oam_info->oam_pdu_config) &
                       OAM_INFO_TYPE_PDU_SIZE_MASK);
                ND_PRINT("\n\t    OUI %s (0x%06x), Vendor-Private 0x%08x",
                       tok2str(oui_values, "Unknown",
                               EXTRACT_BE_U_3(tlv.slow_oam_info->oui)),
                       EXTRACT_BE_U_3(tlv.slow_oam_info->oui),
                       EXTRACT_BE_U_4(tlv.slow_oam_info->vendor_private));
                break;

            case SLOW_OAM_INFO_TYPE_ORG_SPECIFIC:
                hexdump = TRUE;
                break;

            default:
                hexdump = TRUE;
                break;
            }

        badlength_code_info:
            /* do we also want to see a hex dump ? */
            if (ndo->ndo_vflag > 1 || hexdump==TRUE) {
                print_unknown_data(ndo, tptr, "\n\t  ",
                                   length);
            }

            tlen -= length;
            tptr += length;
        }
        break;

    case SLOW_OAM_CODE_EVENT_NOTIF:
        /* Sequence number */
        if (tlen < 2)
            goto tooshort;
        ND_TCHECK_2(tptr);
        ND_PRINT("\n\t  Sequence Number %u", EXTRACT_BE_U_2(tptr));
        tlen -= 2;
        tptr += 2;

        /* TLVs */
        while (tlen > 0) {
            ptr.slow_oam_tlv_header = (const struct slow_oam_tlv_header_t *)tptr;
            if (tlen < sizeof(*ptr.slow_oam_tlv_header))
                goto tooshort;
            ND_TCHECK_SIZE(ptr.slow_oam_tlv_header);
            type = EXTRACT_U_1(ptr.slow_oam_tlv_header->type);
            length = EXTRACT_U_1(ptr.slow_oam_tlv_header->length);
            ND_PRINT("\n\t  %s Link Event Type (%u), length %u",
                   tok2str(slow_oam_link_event_values, "Reserved",
                           type),
                   type,
                   length);

            if (type == SLOW_OAM_INFO_TYPE_END_OF_TLV) {
                /*
                 * As IEEE Std 802.3-2015 says for the End of TLV Marker,
                 * "(the length and value of the Type 0x00 TLV can be ignored)".
                 */
                return;
            }

            /* length includes the type and length fields */
            if (length < sizeof(struct slow_oam_tlv_header_t)) {
                ND_PRINT("\n\t    ERROR: illegal length - should be >= %u",
                       (u_int)sizeof(struct slow_oam_tlv_header_t));
                return;
            }

            if (tlen < length)
                goto tooshort;
            ND_TCHECK_LEN(tptr, length);

            hexdump = FALSE;
            switch (type) {
            case SLOW_OAM_LINK_EVENT_ERR_SYM_PER: /* identical format - fall through */
            case SLOW_OAM_LINK_EVENT_ERR_FRM:
            case SLOW_OAM_LINK_EVENT_ERR_FRM_PER:
            case SLOW_OAM_LINK_EVENT_ERR_FRM_SUMM:
                tlv.slow_oam_link_event = (const struct slow_oam_link_event_t *)tptr;

                if (EXTRACT_U_1(tlv.slow_oam_link_event->event_length) !=
                    sizeof(struct slow_oam_link_event_t)) {
                    ND_PRINT("\n\t    ERROR: illegal length - should be %lu",
                           (unsigned long) sizeof(struct slow_oam_link_event_t));
                    hexdump = TRUE;
                    goto badlength_event_notif;
                }

                ND_PRINT("\n\t    Timestamp %u ms, Errored Window %" PRIu64
                       "\n\t    Errored Threshold %" PRIu64
                       "\n\t    Errors %" PRIu64
                       "\n\t    Error Running Total %" PRIu64
                       "\n\t    Event Running Total %u",
                       EXTRACT_BE_U_2(tlv.slow_oam_link_event->time_stamp)*100,
                       EXTRACT_BE_U_8(tlv.slow_oam_link_event->window),
                       EXTRACT_BE_U_8(tlv.slow_oam_link_event->threshold),
                       EXTRACT_BE_U_8(tlv.slow_oam_link_event->errors),
                       EXTRACT_BE_U_8(tlv.slow_oam_link_event->errors_running_total),
                       EXTRACT_BE_U_4(tlv.slow_oam_link_event->event_running_total));
                break;

            case SLOW_OAM_LINK_EVENT_ORG_SPECIFIC:
                hexdump = TRUE;
                break;

            default:
                hexdump = TRUE;
                break;
            }

        badlength_event_notif:
            /* do we also want to see a hex dump ? */
            if (ndo->ndo_vflag > 1 || hexdump==TRUE) {
                print_unknown_data(ndo, tptr, "\n\t  ",
                                   length);
            }

            tlen -= length;
            tptr += length;
        }
        break;

    case SLOW_OAM_CODE_LOOPBACK_CTRL:
        tlv.slow_oam_loopbackctrl = (const struct slow_oam_loopbackctrl_t *)tptr;
        if (tlen < sizeof(*tlv.slow_oam_loopbackctrl))
            goto tooshort;
        ND_TCHECK_SIZE(tlv.slow_oam_loopbackctrl);
        command = EXTRACT_U_1(tlv.slow_oam_loopbackctrl->command);
        ND_PRINT("\n\t  Command %s (%u)",
               tok2str(slow_oam_loopbackctrl_cmd_values,
                       "Unknown",
                       command),
               command);
        tptr ++;
        tlen --;
        break;

        /*
         * FIXME those are the defined codes that lack a decoder
         * you are welcome to contribute code ;-)
         */
    case SLOW_OAM_CODE_VAR_REQUEST:
    case SLOW_OAM_CODE_VAR_RESPONSE:
    case SLOW_OAM_CODE_PRIVATE:
    default:
        if (ndo->ndo_vflag <= 1) {
            print_unknown_data(ndo, tptr, "\n\t  ", tlen);
        }
        break;
    }
    return;

tooshort:
    ND_PRINT("\n\t\t packet is too short");
    return;

trunc:
    ND_PRINT("%s", tstr);
}