/*
 * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>

#include <stdio.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <unistd.h>

#include "interface.h"
#include "addrtoname.h"
#include "igmp.h"

static void
dvmrp_level_print(const struct in_addr *g)
{
	const u_char *p = (const u_char *)g;

	(void)printf(" [v%d.%d]", p[3], p[2]);
}

static void
dvmrp_flags_print(u_char flags)
{
	(void)printf("%s%s%s%s%s",
		      flags & DVMRP_NF_TUNNEL ? "/tunnel" : "",
		      flags & DVMRP_NF_SRCRT ? "/srcrt" : "",
		      flags & DVMRP_NF_QUERIER ? "/querier" : "",
		      flags & DVMRP_NF_DISABLED ? "/disabled" : "",
		      flags & DVMRP_NF_DOWN ? "/down" : "");
}

static void
dvmrp_print(register const u_char *bp,
	    register const struct igmp *igmp,
	    int len)
{
	u_int32 mask, origin;
	u_char metric;
	int width, count, i;

	switch (igmp->igmp_code) {
	    case DVMRP_PROBE:
		(void)printf(" neighbor probe");
		dvmrp_level_print(&igmp->igmp_group);
		break;
	    case DVMRP_REPORT:
		(void)printf(" route report");
		dvmrp_level_print(&igmp->igmp_group);
		mask = 0xff000000;
		mask |= *bp++ << 16;
		mask |= *bp++ << 8;
		mask |= *bp++;
		len -= 3;
		if ((bp > snapend) || (len < 0))
			return;
		(void)printf(" mask=%s", intoa(ntohl(mask)));
		if ((mask & 0xffffff) == 0)
		    width = 1;
		else if ((mask & 0xffff) == 0)
		    width = 2;
		else if ((mask & 0xff) == 0)
		    width = 3;
		else
		    width = 4;
		for (;;) {
			origin = 0;
			for (i = 0; i < width; i++) {
				origin <<= 8;
				origin |= *bp++;
				len--;
			}
			origin <<= 8 * (4 - width);
			metric = *bp++;
			len--;
			if ((bp > snapend) || (len < 0))
				return;
			(void)printf(" (%s,%d)",
				     ipaddr_string(&origin),
				     metric & ~DVMRP_REPORT_LAST);
			if (metric & DVMRP_REPORT_LAST)
				break;
		}
		break;
	    case DVMRP_ASK_NEIGHBORS:
	    case DVMRP_ASK_NEIGHBORS2:
		(void)printf(" neighbor request%s",
			     igmp->igmp_code == DVMRP_ASK_NEIGHBORS ?"":"2");
		dvmrp_level_print(&igmp->igmp_group);
		break;
	    case DVMRP_NEIGHBORS:
	    case DVMRP_NEIGHBORS2:
		(void)printf(" neighbor list%s",
                             igmp->igmp_code == DVMRP_NEIGHBORS ?"":"2");
		dvmrp_level_print(&igmp->igmp_group);
		while (len > 0) {
			if ((bp + 8 > snapend) || (len < 8))
				return;
			(void)printf(" { %s [%d/%d",
				     ipaddr_string(&bp[0]),
				     bp[4], bp[5]);
			bp += 6;
			len -= 6;
			if (igmp->igmp_code == DVMRP_NEIGHBORS2) {
				dvmrp_flags_print(*bp++);
				len--;
			}
			(void)printf("]");
			count = *bp++;
			len--;
			for (i = 0; i < count; i++) {
				if ((bp + 4 > snapend) || (len < 0))
					return;
				(void)printf(" %s }", ipaddr_string(&bp[0]));
				bp += 4;
				len -= 4;
			    }
		}
		break;
	    case DVMRP_PRUNE:
		(void)printf(" prune");
		dvmrp_level_print(&igmp->igmp_group);
		if ((bp + 12 > snapend) || (len < 12))
			return;
		(void)printf(" %s %s %d",
			     ipaddr_string(&bp[0]),
			     ipaddr_string(&bp[4]),
			     *(int *)&bp[8]);
		break;
	    case DVMRP_GRAFT:
		(void)printf(" graft%s",
			     igmp->igmp_code == DVMRP_GRAFT ?"":" ack");
		dvmrp_level_print(&igmp->igmp_group);
		if ((bp + 8 > snapend) || (len < 8))
			return;
		(void)printf(" %s %s",
			     ipaddr_string(&bp[0]),
			     ipaddr_string(&bp[4]));
		break;
	    default:
		(void)printf(" code=%d", igmp->igmp_code);
		dvmrp_level_print(&igmp->igmp_group);
		break;
	}
}

static void
pim_print(register const u_char *bp,
	  register const struct igmp *igmp,
	  int len)
{
	switch(igmp->igmp_code) {
	    case PIM_QUERY:
		(void)printf(" query");
		break;
	    case PIM_REGISTER:
		(void)printf(" register");
		break;
	    case PIM_REGISTER_STOP:
		(void)printf(" register-stop");
		break;
	    case PIM_JOIN_PRUNE:
		(void)printf(" join/prune");
                if (*(u_int32 *)&igmp->igmp_group)
			(void)printf(" %s",
                                     ipaddr_string(&igmp->igmp_group));
		break;
	    case PIM_RP_REACHABLE:
		(void)printf(" RP reachable");
		break;
	    case PIM_ASSERT:
		(void)printf(" assert");
                if (*(u_int32 *)&igmp->igmp_group)
			(void)printf(" %s",
                                     ipaddr_string(&igmp->igmp_group));
                break;
	    case PIM_GRAFT:
		(void)printf(" graft");
                if (*(u_int32 *)&igmp->igmp_group)
			(void)printf(" %s",
                                     ipaddr_string(&igmp->igmp_group));
		break;
	    case PIM_GRAFT_ACK:
		(void)printf(" graft ack");
                if (*(u_int32 *)&igmp->igmp_group)
			(void)printf(" %s",
                                     ipaddr_string(&igmp->igmp_group));
		break;
	    case PIM_MODE:
		(void)printf(" mode");
		(void)printf(" %s", ipaddr_string(&igmp->igmp_group));
		break;
	    default:
		(void)printf(" code=%d", igmp->igmp_code);
		break;
	}
}

void
igmp_print(register const u_char *bp, register int len,
	   register const u_char *bp2)
{
	register const struct ip *ip;
	register const u_char *ep;
	register const struct igmp *igmp;

	ip = (const struct ip *)bp2;
	igmp = (const struct igmp *)bp;
	ep = (const u_char *)snapend;
        (void)printf("%s > %s: ",
		ipaddr_string(&ip->ip_src),
		ipaddr_string(&ip->ip_dst));

	if (bp + IGMP_MINLEN > ep) {
		(void)printf("[|igmp]");
		return;
	}
	switch (igmp->igmp_type) {
	case IGMP_HOST_MEMBERSHIP_QUERY:
		(void)printf("igmp query");
		if ((igmp->igmp_code) &&
		    (vflag || igmp->igmp_code != 100))
			(void)printf(" [intvl %d]", igmp->igmp_code);
		if (*(u_int32 *)&igmp->igmp_group)
			(void)printf(" [gaddr %s]",
				     ipaddr_string(&igmp->igmp_group));
		if (len != IGMP_MINLEN)
			(void)printf(" [len %d]", len);
		break;
	case IGMP_HOST_MEMBERSHIP_REPORT:
		(void)printf("igmp report %s",
			     ipaddr_string(&igmp->igmp_group));
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x?]", igmp->igmp_code);
		if (len != IGMP_MINLEN)
			(void)printf(" [len %d]", len);
		break;
	case IGMP_DVMRP:
		(void)printf("igmp dvmrp");
		if (len < IGMP_MINLEN) {
			(void)printf(" [len %d]", len);
			break;
		}
		dvmrp_print(bp + IGMP_MINLEN, igmp, len - IGMP_MINLEN);
		break;
	case IGMP_PIM:
		(void)printf("igmp pim");
		if (len < IGMP_MINLEN) {
			(void)printf(" [len %d]", len);
			break;
		}
		pim_print(bp + IGMP_MINLEN, igmp, len - IGMP_MINLEN);
		break;
	case IGMP_HOST_NEW_MEMBERSHIP_REPORT:
		(void)printf("igmp newreport");
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x?]", igmp->igmp_code);
		(void)printf(" [gaddr %s]",
			     ipaddr_string(&igmp->igmp_group));
                if (len != IGMP_MINLEN)
			(void)printf(" [len %d]", len);
		break;
	case IGMP_HOST_LEAVE_MESSAGE:
		(void)printf("igmp leave");
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x?]", igmp->igmp_code);
		(void)printf(" [gaddr %s]",
			     ipaddr_string(&igmp->igmp_group));
                if (len != IGMP_MINLEN)
			(void)printf(" [len %d]", len);
		break;
	case IGMP_MTRACE_RESP:
		(void)printf("igmp mtrace-resp %s",
			     ipaddr_string(&igmp->igmp_group));
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x?]", igmp->igmp_code);
		if (len < IGMP_MINLEN) {
			(void)printf(" [len %d]", len);
			break;
		}
		break;
	case IGMP_MTRACE:
		(void)printf("igmp mtrace %s",
			     ipaddr_string(&igmp->igmp_group));
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x?]", igmp->igmp_code);
		if (len < IGMP_MINLEN) {
			(void)printf(" [len %d]", len);
			break;
		}
		break;
	default:
		(void)printf("igmp-0x%02x", igmp->igmp_type);
		if (igmp->igmp_code)
			(void)printf(" [code=0x%x]", igmp->igmp_code);
		break;
	}
}
