/* to get sys_errlist declared */
#define _BSD_SOURCE

#include <sys/socket.h>
#include <net/if.h>
#include <net/if_ether.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <resolv.h>
#include <errno.h>
#include "uint16.h"
#include "fmt.h"
#include "buffer.h"
#include "ip4.h"
#include "ip6.h"

#ifndef __dietlibc__
#define __attribute__(x)
#endif



static int buffer1_puts(const char* s) __attribute__((regparm(1)));
static int buffer1_puts(const char* s) { return buffer_puts(buffer_1,s); }

static int buffer1_putulong(unsigned long l) __attribute__((regparm(1)));
static int buffer1_putulong(unsigned long l) { return buffer_putulong(buffer_1,l); }

static int buffer1_putspace() { return buffer_putspace(buffer_1); }

static void die(const char* message) {
  buffer_puts(buffer_2,message);
  buffer_puts(buffer_2,": ");
  buffer_puts(buffer_2,sys_errlist[errno]);
  buffer_putnlflush(buffer_2);
  exit(1);
}

int opte=0,optv=0,optn=0;

static void buffer1_put2dec(const unsigned char x) {
  char c[2];
  c[0]=(x/10)+'0';
  c[1]=(x%10)+'0';
  buffer_put(buffer_1,c,2);
}

static void dumpethaddr(const unsigned char* x) {
  int i;
  for (i=0; i<6; ++i) {
    buffer_putxlong(buffer_1,x[i]);
    if (i<5) buffer1_puts(":");
  }
}

static int dumpip4(const char* ip) {
  char tmp[IP4_FMT];
  buffer_put(buffer_1,tmp,fmt_ip4(tmp,ip));
}

static int dumpip6(const char* ip) {
  char tmp[IP6_FMT];
  buffer_put(buffer_1,tmp,fmt_ip6(tmp,ip));
}

static void dumpeth(const char* buf) {
  uint16 type=uint16_read_big(buf+12);
  dumpethaddr(buf+6);
  buffer1_putspace();
  dumpethaddr(buf);
  buffer1_putspace();
  switch (type) {
  case 0x800: buffer1_puts("ip"); break;
  default: buffer_putxlong(buffer_1,type);
  }
  buffer1_putspace();
}

static void dump_2layer(const unsigned char* const buf,const unsigned int proto);
/* this dumps the 1layer ieee802.11 specific part of the package */
static void dumpethwl_mgt(const unsigned char* const buf,const unsigned char subtype) {
  //buffer1_puts("802.11 management ");
  switch(subtype) {
  case 0x0: buffer1_puts("association request"); break;
  case 0x1: buffer1_puts("association response"); break;
  case 0x2: buffer1_puts("reassociation request"); break;
  case 0x3: buffer1_puts("reassociation response"); break;
  case 0x4: buffer1_puts("Probe request"); break;
  case 0x5: buffer1_puts("Probe request"); break;
  case 0x8: buffer1_puts("Beacon"); break;
  case 0x9: buffer1_puts("ATIM"); break;
  case 0xA: buffer1_puts("Disassociation"); break;
  case 0xB: buffer1_puts("Authentication"); break;
  case 0xC: buffer1_puts("Deauthentication"); break;
  default: buffer1_puts("???"); break;
  }
}
static void dumpethwl_ctl(const unsigned char* buf,unsigned char subtype) {
  buffer1_puts("802.11 control ");
  switch(subtype) {
  case 0xA:
    buffer1_puts("power-save poll");
    break;
  case 0xB:
    buffer1_puts("RTS ta:");
    dumpethaddr(buf+10); /* target address */
print_ra:
    buffer1_puts(" ra:");
    dumpethaddr(buf+4); /* remote address */
    break;
  case 0xC: buffer1_puts("CTS"); goto print_ra;
  case 0xD: buffer1_puts("ACK"); goto print_ra;
  case 0xE: buffer1_puts("contention-free period end"); break;
  case 0xF: buffer1_puts("contention-free period end/ack"); break;
  default: buffer1_puts("???"); break;
  }
}
static void dumpethwl_data(const unsigned char* buf,unsigned char subtype) {
  int proto;
  unsigned char data_type=buf[1]&3;
  if (opte) {
    uint16 seq_frag=uint16_read_big(buf+22);
    buffer1_puts("data ");
    dumpethaddr(buf+4); /* destination address */
    buffer1_putspace();
    dumpethaddr(buf+16); /* source address */
    buffer1_putspace();
    dumpethaddr(buf+10); /* BSS ID */
    buffer1_putspace();
    buffer1_puts("sequence ");
    buffer_putlong(buffer_1,seq_frag>>4); /* sequence number */
    buffer1_putspace();
  }
  buf+=(data_type==3)?30:24;
  dump_2layer(buf+8,uint16_read_big(buf+6));
}
static void dumpethwl(const unsigned char* buf) {
  unsigned char subtype=(buf[0]>>4)&15;
  unsigned char type=(buf[0]>>2)&3;
#if 0
  buffer1_puts("802.11 ");
#endif
  switch(type) {
  case 0x00: dumpethwl_mgt(buf,subtype); break;
  case 0x01: dumpethwl_ctl(buf,subtype); break;
  case 0x02: dumpethwl_data(buf,subtype); return;
  default: buffer1_puts("reserved ?!?"); break;
  }
  buffer1_putspace();
  return;
}

static void dumpinval(const unsigned char* message,unsigned long val) {
  buffer1_puts("[");
  buffer1_puts(message);
  buffer1_putspace();
  buffer1_putulong(val);
  buffer1_puts("]");
}

static void dumpicmp(const unsigned char* buf) { /* rfc792 */
  static const char* echos[6]={"net unreachable","host unreachable",
	"proto unreachable","port unreachable","fragmentation needed",
	"source route failed"};
  static const char* redirects[4]={"net","host","TOS+net","TOS+host"};
  buffer1_puts(": icmp: ");
  switch (buf[0]) {
  case 8: buffer1_puts("echo request"); break;
  case 0: buffer1_puts("echo reply"); break;
  case 3:
    if (buf[1]<6)
      buffer1_puts(echos[buf[1]]);
    else
      dumpinval("unreach",buf[1]);
    break;
  case 5:
    if (buf[1]<6) {
      buffer1_puts(redirects[buf[1]]);
      buffer1_puts(" redirect");
    } else
      dumpinval("redirect",buf[1]);
    break;
  case 11:
    switch (buf[1]) {
    case 0: buffer1_puts("TTL exceeded in transit"); break;
    case 1: buffer1_puts("fragment reassembly time exceeded"); break;
    }
    break;
  case 13: buffer1_puts("timestamp request"); break;
  case 14: buffer1_puts("timestamp reply"); break;
  case 15: buffer1_puts("info request"); break;
  case 16: buffer1_puts("info reply"); break;
  }
}

static void dumpdnstype(unsigned int type) {
  switch (type) {
  case 1: buffer1_puts("A"); break;
  case 2: buffer1_puts("NS"); break;
  case 3: buffer1_puts("MD"); break;
  case 4: buffer1_puts("MF"); break;
  case 5: buffer1_puts("CNAME"); break;
  case 6: buffer1_puts("SOA"); break;
  case 7: buffer1_puts("MB"); break;
  case 8: buffer1_puts("MG"); break;
  case 9: buffer1_puts("MR"); break;
  case 10: buffer1_puts("NULL"); break;
  case 11: buffer1_puts("WKS"); break;
  case 12: buffer1_puts("PTR"); break;
  case 13: buffer1_puts("HINFO"); break;
  case 14: buffer1_puts("MINFO"); break;
  case 15: buffer1_puts("MX"); break;
  case 16: buffer1_puts("TXT"); break;
  case 28: buffer1_puts("AAAA"); break;
  default: dumpinval("type",type);
  }
}

static void dumpdnsclass(unsigned int class) {
  switch (class) {
  case 1: break;
  case 2: buffer1_puts(" CS"); break;
  case 3: buffer1_puts(" CH"); break;
  case 4: buffer1_puts(" HS"); break;
  default: buffer1_putspace(); dumpinval("class",class);
  }
}

static void dumpdns(const unsigned char* buf,int len) { /* rfc1035 */
  int q;
  buffer1_putulong(uint16_read_big(buf));
  switch (q=((buf[2]>>3)&0xf)) {
  case 1: buffer1_puts("IQUERY"); break;
  case 2: buffer1_puts("STATUS");
  case 0: break;
  default: dumpinval("query",q);
  }
  if (buf[2]&0x80) {
    int q=uint16_read_big(buf+4);
    int j=12;
    /* answer */
    buffer1_putspace();
    if (buf[4]&0xf) {
      switch (buf[4]&0xf) {
      case 1: buffer1_puts("FormErr"); break;
      case 2: buffer1_puts("ServFail"); break;
      case 3: buffer1_puts("NXDomain"); break;
      case 4: buffer1_puts("NotImpl"); break;
      case 5: buffer1_puts("Refused"); break;
      case 6: buffer1_puts("YXDomain"); break;
      case 7: buffer1_puts("YXRRSet"); break;
      case 8: buffer1_puts("NXRRSet"); break;
      case 9: buffer1_puts("NotAuth"); break;
      case 10: buffer1_puts("NotZone"); break;
      case 11: buffer1_puts("Max"); break;
      case 16: buffer1_puts("BadSig"); break;
      case 17: buffer1_puts("BadKey"); break;
      case 18: buffer1_puts("BadTime"); break;
      default: dumpinval("error",buf[4]&0xf);
      }
    } else {
      buffer1_putulong(uint16_read_big(buf+6)); buffer1_puts("/");
      buffer1_putulong(uint16_read_big(buf+8)); buffer1_puts("/");
      buffer1_putulong(uint16_read_big(buf+10)); buffer1_putspace();
      while (q) {
	char name[1024];
	int l=dn_expand((char*)buf,(char*)buf+len,(char*)buf+j,name,sizeof name);
	if (optv) {
	  buffer1_puts("q: ");
	  dumpdnstype(uint16_read_big(buf+l+j));
	  dumpdnsclass(uint16_read_big(buf+l+j+2));
	  buffer1_putspace();
	  buffer1_puts(name);
	  buffer1_putspace();
	}
	--q; j+=l+4;
      }
      q=uint16_read_big(buf+6);
      {
	char name[1024];
	int l=dn_expand((char*)buf,(char*)buf+len,(char*)buf+j,name,sizeof name);
	dumpdnstype(uint16_read_big(buf+l+j));
	buffer1_putspace();
	switch (uint16_read_big(buf+l+j)) {
	case 1: dumpip4(buf+l+j+10); break;
	case 2: case 5:
		{
		  int k=dn_expand((char*)buf,(char*)buf+len,(char*)buf+j+l+10,name,sizeof name);
		  buffer1_puts(name);
		}
		break;
	case 28: dumpip4(buf+l+j+10); break;
	case 15:
		{
		  int k=dn_expand((char*)buf,(char*)buf+len,(char*)buf+j+l+12,name,sizeof name);
		  buffer1_putulong(uint16_read(buf+j+l+10));
		  buffer1_putspace();
		  buffer1_puts(name);
		}
		break;
	case 16: buffer_put(buffer_1,buf+l+j+10,uint16_read(buf+l+j+8)); break;
	}
      }
    }
    if (buf[2]&4) buffer1_puts("*");
  } else {
    /* query */
    if (buf[2]&1) buffer1_puts("+");
    buffer1_putspace();
    if (uint16_read_big(buf+4)>0) {
      char name[1024];
      int l=dn_expand((char*)buf,(char*)buf+len,(char*)buf+12,name,sizeof name);
      dumpdnstype(uint16_read_big(buf+l+12));
      dumpdnsclass(uint16_read_big(buf+l+14));
      buffer1_puts("? ");
      buffer1_puts(name);
    }
  }
}

static void dumpudp(const unsigned char* buf) { /* rfc768 */
  int len=uint16_read_big(buf+4)-8;
  buffer1_puts(": udp: ");
  if (uint16_read_big(buf)==53 || uint16_read_big(buf+2)==53) dumpdns(buf+8,len);
  buffer1_puts(" (");
  buffer1_putulong(len);
  buffer1_puts(")");
}

static void dumptcp(const unsigned char* buf,unsigned long pktlen) { /* rfc793 */
  int hlen=(buf[12]>>4)*4;
  buffer1_puts(": tcp: ");
  if (buf[13]&1) buffer1_puts("F");
  if (buf[13]&2) buffer1_puts("S");
  if (buf[13]&4) buffer1_puts("R");
  if (buf[13]&8) buffer1_puts("P");
//  if (buf[13]&16) buffer1_puts("A");
  if (buf[13]&32) buffer1_puts("U");
  if (!(buf[13]&63)) buffer1_puts(".");
  buffer1_putspace();
  buffer1_putulong(uint32_read_big(buf+4));
  buffer1_puts(":");
  buffer1_putulong(uint32_read_big(buf+4)+pktlen-hlen);
  buffer1_puts("(");
  buffer1_putulong(pktlen-hlen);
  buffer1_puts(")");
  if (buf[13]&16) {
    buffer1_puts(" ack ");
    buffer1_putulong(uint32_read_big(buf+8));
  }
  buffer1_puts(" win ");
  buffer1_putulong(uint16_read_big(buf+14));
  /* options */
  if (hlen>20) {
    const unsigned char* opts;
    int first=1;
    buffer1_puts(" <");
    for (opts=buf+20; (opts<buf+hlen) && *opts; ) {
      if (first) first=0; else buffer1_puts(",");
      switch (*opts) {
      case 1: buffer1_puts("nop"); ++opts; break;
      case 2: buffer1_puts("mss ");
	      buffer1_putulong(uint16_read_big(opts+2)); break;
      case 4: buffer1_puts("sackOK"); break;
      case 5: buffer1_puts("sack"); break;
      case 8: buffer1_puts("timestamp ");
	      buffer1_putulong(uint32_read_big(opts+2));
	      buffer1_putspace();
	      buffer1_putulong(uint32_read_big(opts+6));
	      break;
      case 10: buffer1_puts("nak ");
	      buffer1_putulong(uint32_read_big(opts+2));
	      buffer1_puts(":");
	      buffer1_putulong(uint32_read_big(opts+2)+opts[6]);
	      buffer1_puts("(");
	      buffer1_putulong(opts[6]);
	      buffer1_puts(")");
	      break;
      case 11: buffer1_puts("bigwin ");
	      buffer1_putulong(uint32_read_big(opts+2)&0x3fffffff);
	      break;
      case 19: buffer1_puts("md5-bgp-hack");
	       break;
      default: dumpinval("tcp option",*opts); break;
      }
      if (*opts<2) ++opts; else opts+=opts[1];
    }
    buffer1_puts(">");
  }
}

static void dumpip(const unsigned char* buf) {	/* rfc791 */
  char tmp[IP4_FMT];
  const char* next=buf+(buf[0]&0xf)*4;
  if ((buf[0]&0xf0) != 0x40) return;
  buffer_put(buffer_1,tmp,fmt_ip4(tmp,buf+12));
  if (buf[9]==6 || buf[9]==17) {
    buffer1_puts(".");
    buffer1_putulong(uint16_read_big(next));
  }
  buffer1_puts(" > ");
  buffer_put(buffer_1,tmp,fmt_ip4(tmp,buf+16));
  if (buf[9]==6 || buf[9]==17) {
    buffer1_puts(".");
    buffer1_putulong(uint16_read_big(next+2));
  }
  switch (buf[9]) { /* protocol */
  case 1: /* icmp */
    dumpicmp(next); break;
  case 6: /* tcp */
    dumptcp(next,uint16_read_big(buf+2)-(buf[0]&0xf)*4); break;
  case 17: /* udp */
    dumpudp(next); break;
  }
  if (buf[6]&0x40) buffer1_puts(" (DF)");
  if (optv) {
    buffer1_puts(" (ttl ");
    buffer1_putulong(buf[8]);
    buffer1_puts(", id ");
    buffer1_putulong(uint16_read_big(buf+4));
    buffer1_puts(", len ");
    buffer1_putulong(uint16_read_big(buf+2));
    buffer1_puts(")");
  }
}

static void dumparp(const unsigned char* pkt) {	/* rfc826 */
  int ar_hrd,ar_pro,hlen,plen,i;
  switch ((ar_hrd=uint16_read_big(pkt))) {
  case 1: /* Ethernet */ break;
  default: dumpinval("arp hw address type",ar_hrd); return;
  }
  switch ((ar_pro=uint16_read_big(pkt+2))) {
  case 0x0800: /* IP */ break;
  default: dumpinval("arp protocol address type",ar_pro); return;
  }
  hlen=pkt[4]; plen=pkt[5];
  switch (i=uint16_read_big(pkt+6)) {
  case 1:
    buffer1_puts("arp who-has ");
    dumpip4(pkt+8+hlen*2+plen);
    buffer1_puts(" tell ");
    dumpip4(pkt+8+hlen);
    break;
  case 2:
    buffer1_puts("arp reply ");
    dumpip4(pkt+8+hlen);
    buffer1_puts(" is-at ");
    dumpethaddr(pkt+8);
    break;
  default:
    dumpinval("arp opcode",i);
  }
}

static void dump_2layer(const unsigned char* const buf,const unsigned int proto) {
  switch (proto) {
  case 0x8864: /* pppoe */ break;
  case 0x0800: dumpip(buf); break;
  case 0x0806: dumparp(buf); break;
  case 0: break;
  default:
    dumpinval("layer 2 protocol",proto);
  }
}

main(int argc,char* argv[]) {
  int sock,len;
  char buf[16*1024];
  struct sockaddr_ll sl,from;
  socklen_t fromlen=sizeof from;
  const char* interface="eth0";
  int addrlen;
  struct ifreq ifr;

  for (;;) {
    int i;
    if ((i=getopt(argc,argv,"i:evn"))==-1) break;
    switch (i) {
    case 0:
      buffer_puts(buffer_2,"non-option ");
      buffer_puts(buffer_2,argv[optind]);
      buffer_putnlflush(buffer_2);
      break;
    case 'i': interface=optarg; break;
    case 'e': opte=1; break;
    case 'v': optv=1; break;
    case 'n': optn=1; break;
    default: buffer_putsflush(buffer_2,"usage: tcpdump [-nev] [-i ifname]"); return 0; break;
    }
  }
  buffer_puts(buffer_2,"using interface ");
  buffer_puts(buffer_2,interface);
  buffer_putnlflush(buffer_2);
  sock=socket(PF_PACKET,SOCK_RAW,ETH_P_ALL);
  if (sock==-1) die("socket");
  sl.sll_family=AF_PACKET;
  sl.sll_protocol=htons(ETH_P_ALL);
  sl.sll_ifindex=if_nametoindex(interface);
  if (bind(sock,(struct sockaddr*)&sl,sizeof sl)==-1) die("bind");
  {
    strncpy(ifr.ifr_name,interface,IFNAMSIZ);
    if (ioctl(sock,SIOCGIFHWADDR, &ifr)==-1) die("SIOCGIFINDEX");
    switch (ifr.ifr_hwaddr.sa_family) {
    case ARPHRD_ETHER:
    case ARPHRD_LOOPBACK:
    case ARPHRD_IEEE80211: // WLAN
    case ARPHRD_PPP:
      break;
    default:
      buffer_puts(buffer_2,"unknown layer 2: ");
      buffer_putulong(buffer_2,ifr.ifr_hwaddr.sa_family);
      buffer_putnlflush(buffer_2);
      exit(1);
    }
  }
  while ((len=recvfrom(sock,buf,sizeof buf,0,(struct sockaddr*)&from,&fromlen))>0) {
    struct timeval tv;
    struct tm *t;

    if (ifr.ifr_hwaddr.sa_family==ARPHRD_LOOPBACK && from.sll_pkttype!=PACKET_OUTGOING)
      /* Linux gives every packet twice on loopback.  Only look at
       * outgoing packets */
      continue;
    ioctl(sock,SIOCGSTAMP,&tv);
    t=localtime(&tv.tv_sec);

    buffer1_put2dec(t->tm_hour);
    buffer1_puts(":");
    buffer1_put2dec(t->tm_min);
    buffer1_puts(":");
    buffer1_put2dec(t->tm_sec);
    buffer1_puts(".");
    buffer1_put2dec(tv.tv_usec/10000);
    buffer1_put2dec((tv.tv_usec/1000)%100);
    buffer1_put2dec((tv.tv_usec/10)%100);
    buffer1_putspace();

    switch (ifr.ifr_hwaddr.sa_family) {
    case ARPHRD_IEEE80211:
      dumpethwl(buf);
      if (opte) { buffer1_putulong(len); buffer1_puts(": "); }
      break;
    case ARPHRD_ETHER:
    case ARPHRD_LOOPBACK:
      if (opte) { dumpeth(buf); buffer1_putulong(len); buffer1_puts(": "); }
      dump_2layer(buf+14,uint16_read_big(buf+12));
      break;
    default:
      dumpinval("layer 2",ifr.ifr_hwaddr.sa_family);
    }
    buffer_putnlflush(buffer_1);
  }
  die("recv");
}
