/*
 * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
 *               2002, 2003, 2004
 *	Ohio University.
 *
 * ---
 * 
 * Starting with the release of tcptrace version 6 in 2001, tcptrace
 * is licensed under the GNU General Public License (GPL).  We believe
 * that, among the available licenses, the GPL will do the best job of
 * allowing tcptrace to continue to be a valuable, freely-available
 * and well-maintained tool for the networking community.
 *
 * Previous versions of tcptrace were released under a license that
 * was much less restrictive with respect to how tcptrace could be
 * used in commercial products.  Because of this, I am willing to
 * consider alternate license arrangements as allowed in Section 10 of
 * the GNU GPL.  Before I would consider licensing tcptrace under an
 * alternate agreement with a particular individual or company,
 * however, I would have to be convinced that such an alternative
 * would be to the greater benefit of the networking community.
 * 
 * ---
 *
 * This file is part of Tcptrace.
 *
 * Tcptrace was originally written and continues to be maintained by
 * Shawn Ostermann with the help of a group of devoted students and
 * users (see the file 'THANKS').  The work on tcptrace has been made
 * possible over the years through the generous support of NASA GRC,
 * the National Science Foundation, and Sun Microsystems.
 *
 * Tcptrace is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Tcptrace is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tcptrace (in the file 'COPYING'); if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 * 
 * Author:	Shawn Ostermann
 * 		School of Electrical Engineering and Computer Science
 * 		Ohio University
 * 		Athens, OH
 *		ostermann@cs.ohiou.edu
 *		http://www.tcptrace.org/
 */
#include "tcptrace.h"
static char const GCC_UNUSED copyright[] =
    "@(#)Copyright (c) 2004 -- Ohio University.\n";
static char const GCC_UNUSED rcsid[] =
    "@(#)$Header: /usr/local/cvs/tcptrace/snoop.c,v 5.15 2003/11/19 14:38:05 sdo Exp $";


/* 
 * snoop.c - SNOOP specific file reading stuff
 *	ipv6 addition by Nasseef Abukamail
 */




#ifdef GROK_SNOOP

/* Defining SYS_STDIN which is fp for Windows and stdin for all other systems */
#ifdef __WIN32
static FILE *fp;
#define SYS_STDIN fp
#else
#define SYS_STDIN stdin
#endif /* __WIN32 */

/* information necessary to understand Solaris Snoop output */
struct snoop_file_header {
    char		format_name[8];	/* should be "snoop\0\0\0" */
    tt_uint32		snoop_version;	/* current version is "2" */
    tt_uint32		mac_type;	/* hardware type */
};
/* snoop hardware types that we understand */
/* from sys/dlpi.h */
/*  -- added prefix SNOOP_ to avoid name clash */
#define	SNOOP_DL_ETHER	0x4	/* Ethernet Bus */
#define	SNOOP_DL_FDDI	0x08	/* Fiber Distributed data interface */
#define	SNOOP_DL_ATM	0x12	/* from Sun's "atmsnoop" */

struct snoop_packet_header {
    tt_uint32	len;
    tt_uint32	tlen;
    tt_uint32	blen;
    tt_uint32	unused3;
    tt_uint32	secs;
    tt_uint32	usecs;
};



/* static buffers for reading */
static struct ether_header *pep;
static int *pip_buf;
static int snoop_mac_type;

/* (Courtesy Jeffrey Semke, Pittsburgh Supercomputing Center) */
/* locate ip within FDDI according to RFC 1188 */
static int find_ip_fddi(char* buf, int iplen) {
      char* ptr, *ptr2;
      int i;
      u_char pattern[] = {0xAA, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00};
#define FDDIPATTERNLEN 7

      ptr = ptr2 = buf;

      for (i=0; i < FDDIPATTERNLEN; i++) {
	    ptr2 = memchr(ptr,pattern[i],(iplen - (int)(ptr - buf)));
	    if (!ptr2) 
		  return (-1);
	    if (i && (ptr2 != ptr)) {
		  ptr2 = ptr2 - i - 1;
		  i = -1;
	    }
	    ptr = ptr2 + 1;
      }
      return (ptr2 - buf + 1);
      
}


/* return the next packet header */
/* currently only works for ETHERNET and FDDI */
static int
pread_snoop(
    struct timeval	*ptime,
    int		 	*plen,
    int		 	*ptlen,
    void		**pphys,
    int			*pphystype,
    struct ip		**ppip,
    void		**pplast)
{
    int packlen;
    int rlen;
    int len;
    struct snoop_packet_header hdr;
    int hlen;

    while (1) {
	hlen = sizeof(struct snoop_packet_header);

	/* read the packet header */
	if ((rlen=fread(&hdr,1,hlen,SYS_STDIN)) != hlen) {
	    if (rlen != 0)
		fprintf(stderr,"Bad snoop packet header\n");
	    return(0);
	}

	/* convert some stuff to host byte order */
	hdr.tlen = ntohl(hdr.tlen);
	hdr.len = ntohl(hdr.len);
	hdr.blen = ntohl(hdr.blen);
	hdr.secs = ntohl(hdr.secs);
	hdr.usecs = ntohl(hdr.usecs);

	/* truncated packet length */
	packlen = hdr.tlen;

	/* bug fix from Brian Utterback */
	/* "blen" is the "total length of the packet", header+data+padding */
	len = hdr.blen - hlen;

	if (snoop_mac_type == SNOOP_DL_ETHER) {
	    /* read the ethernet header */
	    rlen=fread(pep,1,sizeof(struct ether_header),SYS_STDIN);
	    if (rlen != sizeof(struct ether_header)) {
		fprintf(stderr,"Couldn't read ether header\n");
		return(0);
	    }

	    /* read the rest of the packet */
	    len -= sizeof(struct ether_header);
	    if (len >= IP_MAXPACKET) {
		/* sanity check */
		fprintf(stderr,
			"pread_snoop: invalid next packet, IP len is %d, return EOF\n", len);

		return(0);
	    }

	    /* add VLAN support for John Tysko */
	    if ((ntohs(pep->ether_type) == ETHERTYPE_VLAN) && (len >= 4)){
		struct {
		    tt_uint16 vlan_num;
		    tt_uint16 vlan_proto;
		} vlanh;

		/* adjust packet length */
		len -= 4;

		/* read the vlan header */
		if ((rlen=fread(&vlanh,1,sizeof(vlanh),SYS_STDIN)) != sizeof(vlanh)) {
		    perror("pread_snoop: seek past vlan header");
		}

		if ((ntohs(vlanh.vlan_proto) == ETHERTYPE_IP) ||
		    (ntohs(vlanh.vlan_proto) == ETHERTYPE_IPV6)) {
		    /* make it appear to have been IP all along */
		    /* (note that both fields are still in N.B.O. */
		    pep->ether_type = vlanh.vlan_proto;
		    if (debug > 2)
			printf("Removing VLAN header (vlan:%x)\n",
			       vlanh.vlan_num);
		} else {
		    if (debug > 2)
			printf("Skipping a VLAN packet (num:%x proto:%x)\n",
			       vlanh.vlan_num, vlanh.vlan_proto);
		}

	    } 


	    /* if it's not IP, then skip it */
	    if ((ntohs(pep->ether_type) != ETHERTYPE_IP) &&
		(ntohs(pep->ether_type) != ETHERTYPE_IPV6)) {


		if (debug > 2)
		    fprintf(stderr,
			    "pread_snoop: not an IP packet (ethertype 0x%x)\n",
			    ntohs(pep->ether_type));
		/* discard the remainder */
		/* N.B. fseek won't work - it could be a pipe! */
		if ((rlen=fread(pip_buf,1,len,SYS_STDIN)) != len) {
		    perror("pread_snoop: seek past non-IP");
		}

		continue;
	    }

	    if ((rlen=fread(pip_buf,1,len,SYS_STDIN)) != len) {
		if (rlen != 0 && debug)
		    fprintf(stderr,
			    "Couldn't read %d more bytes, skipping last packet\n",
			    len);
		return(0);
	    }

	    *ppip  = (struct ip *) pip_buf;
	    /* last byte in the IP packet */
	    *pplast = (char *)pip_buf+packlen-sizeof(struct ether_header)-1;

	} else if (snoop_mac_type == SNOOP_DL_FDDI) {
	    /* FDDI is different */
	    int offset;

	    /* read in the whole frame and search for IP header */
	    /* (assumes sizeof(fddi frame) < IP_MAXPACKET, should be true) */
	    if ((rlen=fread(pip_buf,1,len,SYS_STDIN)) != len) {
		if (debug && rlen != 0)
		    fprintf(stderr,
			    "Couldn't read %d more bytes, skipping last packet\n",
			    len);
		return(0);
	    }

	    /* find the offset of the IP header inside the FDDI frame */
	    if ((offset = find_ip_fddi((void *)pip_buf,len)) == -1) {
		/* not found */
		if (debug)
		    printf("snoop.c: couldn't find next IP within FDDI\n");
		return(-1);
	    }

	    /* copy to avoid alignment problems later (yucc) */
	    /* (we use memmove to make overlaps work) */
	    memmove(pip_buf,(char *)pip_buf+offset,len-offset);

	    /* point to first and last char in IP packet */
	    *ppip  = (struct ip *) ((void *)pip_buf);
	    *pplast = (char *)pip_buf+len-offset-1;

	    /* assume it's IP (else find_ip_fddi would have failed) */
	    pep->ether_type = htons(ETHERTYPE_IP);
	} else if (snoop_mac_type == SNOOP_DL_ATM) {
		/* there's a 12 byte header that we don't care about */
		/* the last 2 of those 12 bytes are the packet type */
		/* we don't care about hardware header, so we just discard */
		struct atm_header {
			u_char junk[10];
			u_short type;
		} atm_header;

		/* grab the 12-byte header */
		rlen=fread(&atm_header,1,sizeof(struct atm_header),SYS_STDIN);
		if (rlen != sizeof(struct atm_header)) {
			fprintf(stderr,"Couldn't read ATM header\n");
			return(0);
		}

		/* fill in the ethernet type */
		/* we'll just assume that they're both in the same network
		   byte order */
		pep->ether_type = atm_header.type;

		/* read the rest of the packet */
		len -= sizeof(struct atm_header);
		if (len >= IP_MAXPACKET) {
			/* sanity check */
			fprintf(stderr,
				"pread_snoop: invalid next packet, IP len is %d, return EOF\n", len);

			return(0);
		}

		/* if it's not IP, then skip it */
		if ((ntohs(pep->ether_type) != ETHERTYPE_IP) &&
		    (ntohs(pep->ether_type) != ETHERTYPE_IPV6)) {
			if (debug > 2)
				fprintf(stderr,
					"pread_snoop: not an IP packet (ethertype 0x%x)\n",
					ntohs(pep->ether_type));
			/* discard the remainder */
			/* N.B. fseek won't work - it could be a pipe! */
			if ((rlen=fread(pip_buf,1,len,SYS_STDIN)) != len) {
				perror("pread_snoop: seek past non-IP");
			}

			continue;
		}

		if ((rlen=fread(pip_buf,1,len,SYS_STDIN)) != len) {
			if (rlen != 0 && debug)
				fprintf(stderr,
					"Couldn't read %d more bytes, skipping last packet\n",
					len);
			return(0);
		}

		*ppip  = (struct ip *) pip_buf;
		/* last byte in the IP packet */
		*pplast = (char *)pip_buf+packlen-sizeof(struct ether_header)-1;
	} else {
	    printf("snoop hardware type %d not understood\n",
		   snoop_mac_type);
	   
	    exit(-1);
	}


	/* save pointer to physical header (always ethernet) */
	*pphys  = pep;
	*pphystype = PHYS_ETHER;


	ptime->tv_sec  = hdr.secs;
	ptime->tv_usec = hdr.usecs;
	*plen          = hdr.len;
	*ptlen         = hdr.tlen;


	return(1);
    }
}



/*
 * is_snoop()   is the input file in snoop format??
 */
pread_f *is_snoop(char *filename)
{
    struct snoop_file_header buf;
    int rlen;

#ifdef __WIN32
    if((fp = fopen(filename, "r")) == NULL) {
       perror(filename);
       exit(-1);
    }
#endif /* __WIN32 */   
   
    /* read the snoop file header */
    if ((rlen=fread(&buf,1,sizeof(buf),SYS_STDIN)) != sizeof(buf)) {
	rewind(SYS_STDIN);
	return(NULL);
    }

    /* first 8 characters should be "snoop\0\0\0" */
    if (strcmp(buf.format_name,"snoop") != 0)
	return(NULL);

    /* OK, it's a snoop file */


    /* convert some stuff to host byte order */
    buf.snoop_version = ntohl(buf.snoop_version);
    buf.mac_type = ntohl(buf.mac_type);
    
    /* sanity check on snoop version */
    if (debug) {
	printf("Snoop version: %ld\n", buf.snoop_version);
    }
    if (buf.snoop_version != 2) {
	printf("\
Warning! snoop file is version %ld.\n\
Tcptrace is only known to work with version 2\n",
	       buf.snoop_version);
    }

    /* sanity check on hardware type */
    snoop_mac_type = buf.mac_type;
    switch (buf.mac_type) {
      case SNOOP_DL_ETHER:
	if (debug)
	    printf("Snoop hw type: %ld (Ethernet)\n", buf.mac_type);
	break;
      case SNOOP_DL_FDDI:
	if (debug)
	    printf("Snoop hw type: %ld (FDDI)\n", buf.mac_type);
	break;
      case SNOOP_DL_ATM:
	if (debug)
	    printf("Snoop hw type: %ld (ATM)\n", buf.mac_type);
	break;
      default:
	if (debug)
	    printf("Snoop hw type: %ld (unknown)\n", buf.mac_type);
	printf("snoop hardware type %ld not understood\n", buf.mac_type);
       
	exit(-1);
    }


    /* OK, it's mine.  Init some stuff */
    pep = MallocZ(sizeof(struct ether_header));
    pip_buf = MallocZ(IP_MAXPACKET);
    

    return(pread_snoop);
}
#endif /* GROK_SNOOP */
