/* Print GRE (Generic Routing Econcapsulation) packets */

#include <sys/param.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>

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

#define GRE_HAS_CHKSUM	0x80000000	/* Checksum Present */
#define GRE_HAS_ROUTE	0x40000000	/* Routing Present */
#define GRE_HAS_KEY	0x20000000	/* Key Present */
#define GRE_HAS_SEQNB	0x10000000	/* Sequence Number Present */
#define GRE_HAS_SSR	0x08000000	/* Strict Source Route */
#define GRE_VER_MASK	0x00070000	/* Version Number (mask) */
#define GRE_VER_NB	0x00000000	/* Version Number (value) */
#define GRE_PT_FIELD	0x0000ffff	/* Protocol Type */

#define GRE_PT_SNA	0x0004		/* SNA protocol type */
#define GRE_PT_SNAP	0x00aa		/* SNAP (OUI follows GRE) */
#define GRE_PT_OSI	0x00fe		/* OSI network layer */
#define GRE_PT_PUP	0x0200		/* PUP */
#define GRE_PT_XNS	0x0600		/* XNS */
#define GRE_PT_IP	0x0800		/* IP */
#define GRE_PT_CHAOS	0x0804		/* Chaos */
#define GRE_PT_ARP	0x0806		/* RFC 826 ARP */
#define GRE_PT_FRARP	0x0808		/* Frame Relay ARP */
#define GRE_PT_VINES	0x0bad		/* VINES */
#define GRE_PT_VINES_E	0x0bae		/* VINES Echo */
#define GRE_PT_VINES_L	0x0baf		/* VINES Loopback */
#define GRE_PT_DECNET	0x6003		/* DECnet (Phase IV) */
#define GRE_PT_ETALK	0x809b		/* EtherTalk */
#define GRE_PT_APOLLO	0x8019		/* Apollo Domain */
#define GRE_PT_IPX	0x8137		/* Novell IPX */
#define GRE_PT_RES0	0x0000		/* Reserved */
#define GRE_PT_TEB	0x6558		/* Transparent Ethernet Bridging */
#define GRE_PT_RFR	0x6559		/* Raw Frame Relay */
#define GRE_PT_IPAS	0xfffe		/* IP Autonomous Systems */
#define GRE_PT_RES1	0xffff		/* Reserved */

struct gre_sre_hdr {	/* Source Route Entry Header */
    u_short sre_af;	/* Address Family */
    u_char sre_off;	/* SRE Offset */
    u_char sre_len;	/* SRE Length */
};

/*
 * print GRE IP packets
 */
void
gre_ip_print(const u_char *cp, int length, const u_char *cip)
{
    register struct ip *ip = (struct ip *)cip;
    register u_long *lp, hdr;
    register struct gre_sre_hdr *sp;
    register int len;

    if (length < 4)
	goto truncated;

    printf("%s > %s: GRE",
	   ipaddr_string(&ip->ip_src),
	   ipaddr_string(&ip->ip_dst));

    lp = (u_long *)cp;
    hdr = *lp++;
    len = sizeof(u_long);
    if ((hdr & GRE_HAS_CHKSUM) || ((hdr & GRE_HAS_ROUTE)))
	len += sizeof(u_long);
    if (hdr & GRE_HAS_KEY)
	len += sizeof(u_long);
    if (hdr & GRE_HAS_SEQNB)
	len += sizeof(u_long);
    if (hdr & GRE_HAS_ROUTE)
	len += sizeof(u_long);

    if (cp + len > snapend)
	return;
    if (len > length)
	goto truncated;
    length -= sizeof(u_long);

    if ((hdr & GRE_VER_MASK) != GRE_VER_NB) {
	printf(" unknown version %d", (hdr & GRE_VER_MASK) >> 15);
	return;
    }

    if (hdr & GRE_HAS_ROUTE)
	len = *lp & 0xffff;
    if ((hdr & GRE_HAS_CHKSUM) || ((hdr & GRE_HAS_ROUTE))) {
	lp++;
	length -= sizeof(u_long);
    }
    if (hdr & GRE_HAS_KEY) {
	printf(" key %d", *lp++);
	length -= sizeof(u_long);
    }
    if (hdr & GRE_HAS_SEQNB) {
	printf(" seq %d", *lp++);
	length -= sizeof(u_long);
    }

    cp = (u_char *)lp;
    while ((hdr & GRE_HAS_ROUTE) && (len > 0)) {
	cp += len;
	length -= len;
	sp = (struct gre_sre_hdr *)cp;
	cp += sizeof(struct gre_sre_hdr);
	length -= sizeof(struct gre_sre_hdr);
	if (cp > snapend)
	    return;
	if (length < 0)
	    goto truncated;
	len = sp->sre_len;
    }

    switch (hdr & GRE_PT_FIELD) {
      case GRE_PT_OSI:
	if (vflag)
	    printf("\n");
	else
	    printf(" ");
	clnp_print(cp, length);
	return;
      case GRE_PT_IP:
	if (vflag)
	    printf("\n");
	else
	    printf(" ");
	ip_print(cp, length);
	return;
      default:
	printf(" ptype %04x", hdr & GRE_PT_FIELD);
	return;
    }

  truncated:
    printf(" truncated-gre %d", length);
}
