Logo Search packages:      
Sourcecode: lanmap version File versions  Download package

protocols.c

/* ex: set tabstop=4 noet: */
/**
 *
 */
/* TODO: many protocols do not properly validate their input; broken or maliciously crafted traffic could exploit
 * this program */

#include "lanmap.h"

#include <assert.h> /* assert() */
#include <ctype.h> /* isprint() */
#include <stdio.h>
#include <stdlib.h> /* exit() */
#include <stdarg.h> /* va_* */
#include <string.h> /* memcpy(), memset() */
#include <time.h> /* time() */
#include <limits.h> /* CHAR_BIT */
#ifdef WIN32
      #include "types.h"
      #include <sys/types.h> /* time_t */
#else
      #include <stdint.h> /* uint32_t and friends */
      #include <sys/types.h> /* u_char and friends */
#endif
#ifdef WIN32
      #include <winsock2.h> /* ntohl */
#else
      #include <netinet/in.h> /* ntohl */
#endif

#include <pcap.h>

#include "lanmap.h"
#include "facts.h"
#include "mac_vendor.h"
#include "debug.h"
#include "misc.h"

/* finer-grained debug */
#undef DEBUG_ETHER2

#define PROT_ASSERT(stmt, buf, len, currpos) \
      do { \
            if (!(stmt)) { \
                  ERRF("%s:%d: ASSERTION FAILED: %s\n", __FILE__, __LINE__, #stmt); \
                  bytes_dump_hex(buf, 0, len); \
                  ERRF("\ncurrpos: %u\n", currpos); \
                  ERRFLUSH(); \
            } \
      } while (0)

/**
 * if this is defined we print a msg every time we see something we can't fully process
 */
#undef PROT_DONT_KNOW /* shout when you see someting you don't understand, slightly annoying :/ */
#undef DEBUG_SUBNET_MATCH

extern int Verbose;
extern struct ip Ip_Listening;
extern uint32_t Dev_Mask[IFACE_MAX];
extern int Dump_Payload_Bytes;

static lhash_t Ip_Ping_Seq; /* struct ip * -> *int */

static const char *Conn_Descr[] = {
      "Unknown",
      "Ethernet",
      "Dialup Modem",
      "Radio Modem",
      "Cable Modem",
      "DSL"
};

/**
 * all supported protocol descriptions, accessible by PROT_*  offset
 * @note MUST be synced with PROT_ enum in .h
 */
const struct prot_descr Prot_Descr[] = {
      /*tla       full */
      { "???" ,   "Unknown" },
      { "Frame",  "Logical Frame" },
      { "PAD",    "Extra Padding" },
      { "SLL",    "Linux Cooked Capture" },
      { "Eth2",   "Ethernet II" } ,
      { "802.3",  "IEEE 802.3 Ethernet" },
      { "ARP",    "Address Resolution Protocol" },
      { "LLC",    "Logical Link Control" },
      { "CDP",    "Cisco Discovery Protocol" },
      { "STP",    "Spanning Tree Protocol" },
      { "AARP",   "Apple Address Resolution Protocol" },
      { "IP" ,    "Internet Protocol" },
      { "IPv6" ,  "Internet Protocol v6" },
      { "ICMP" ,  "Internet Control Message Protocol" },
      { "ICMPv6", "Internet Control Message Protocol v6" },
      { "UDP" ,   "User Datagram Protocol" },
      { "TCP" ,   "Transfer Control Protocol" },
      { "IGMP" ,  "Internet Group Management Protocol" },
      { "IPX" ,   "Internetwork Packet Exchange" },
      { "BOOTP" , "Bootstrap Protocol" },
      { "DNS" ,   "Domain Name Service" },
      { "NBDGM" , "NetBIOS Diagram" },
      { "NBNS" ,  "NetBIOS Name Service" },
      { "NBSSN" , "NetBIOS Session" },
      { "SMB" ,   "Server Message Block" },
      { "DDP" ,   "Data Delivery Protocol" },
      { "SAP" ,   "Service Advertisement Protocol" },
      { "HTTP" ,  "HyperText Transfer Protocol" },
      { "HTTPS" , "Secure HyperText Transfer Protocol" },
      { "IAPP",   "Inter-Access Point Protocol" },
      { "LOOP",   "Loopback" },
      { "GRE",    "General Route Encapsulation" },
      { "SMTP",   "Simple Mail Transfer Protocol" },
      { "AIM",    "AOL Instant Messenger" },
      { "IRC",    "Internet Relay Chat" },
      { "JBR",    "Jabber" },
      { "GNUT",   "Gnutella" },
      { "RTSP",   "Real-time Streaming Protocol" },
      { "PPP",    "Point-to-Point Protocol" },
      { "SNMP",   "Simple Network Management Protocol" },
      { "Teredo", "Teredo Tunneling (IPv6 over UDP)" },
      { "CStrike","Counter-Strike" },
      { "SSDP",   "Simple Service Discovery Protocol" },
      { "SYMW",   "Symbol AP Wifi" },
      { "CiscoWL","Cisco Wireless 2" },
      { "FTP",    "File Transfer Protocol" },
      { "SSH",    "Secure Shell" },
      { "Telnet", "Telnet" },
      { "TFTP",   "Trivial File Transfer Protocol" },
      { NULL,           NULL }
};

/**
 *
 * map port number to service type
 * @note must be in ascending order
 */
static const struct port Ports[] = {
      /*num                   prot_id */

      { PORT_FTP,             PROT_FTP },
      { PORT_SSH,             PROT_SSH },
      { PORT_TELNET,          PROT_TELNET },
      { PORT_SMTP,            PROT_SMTP },
      { PORT_DNS,             PROT_DNS },
      { PORT_BOOTPC,          PROT_BOOTP },
      { PORT_BOOTPS,          PROT_BOOTP },
      { PORT_TFTP,            PROT_TFTP },
      { PORT_HTTP,            PROT_HTTP },
#if 0
      { PORT_HTTP81,          PROT_HTTP },
#endif
      { PORT_HTTPS,           PROT_HTTPS },

      { PORT_SSDP,            PROT_SSDP },
      { PORT_NB_NS,           PROT_NB_NS },
      { PORT_NB_DGM,          PROT_NB_DGM },
      { PORT_NB_SSN,          PROT_NB_SSN },
      { PORT_SNMP,            PROT_SNMP },

      { PORT_SSDP,            PROT_SSDP },
      { PORT_TEREDO,          PROT_TEREDO },
      { PORT_AIM,             PROT_AIM },
      { PORT_GNUTELLA,  PROT_GNUTELLA },
      { PORT_IRC,             PROT_IRC },
      { PORT_CSTRIKE_CLIENT,  PROT_CSTRIKE },
      { PORT_CSTRIKE,         PROT_CSTRIKE },
      { PORT_HTTP8080,  PROT_HTTP },
      { PORT_HTTP8081,  PROT_HTTP },
      { 0,                    0 },
};

/**
 * reserved mac addresses by protocol
 * @note sorted ascending by addr
 * @note http://www.cavebear.com/CaveBear/Ethernet/Ethernet.txt
 */
static const struct mac_prot MAC_PROT[] = {
      /* NOTE: use PROT_UNKNOWN if the protocol is not supported by us; if it is we may use it as a hint */
      /*addr                                          len   prot_id                 descr */
      { "\x01\x00\x0C\xCC\xCC\xCC", 6,    PROT_LLC,         "CDP" },
      { "\x01\x00\x0C\xDD\xDD\xDD", 6,    PROT_UNKNOWN,     "CGMP" },
      { "\x01\x00\x5E",                   3,    PROT_UNKNOWN,     "Multicast" }, /* multicasting RFC 1112 */
      { "\x01\x80\xC2\x00\x00",           5,    PROT_STP,         "STP" },
      { "\x03\x00\x00\x00\x00\x01", 6,    PROT_UNKNOWN,     "NETBEUI Multicast" },
      { "\x03\x00\x00\x20\x00\x00", 6,    PROT_UNKNOWN,     "Multicast" }, /* multicasting RFC 1469 */
      { "\x33\x33",                             2,    PROT_IPV6,        "IPv6 Multicast" }, /* http://www.ciscopress.com/articles/article.asp?p=31948&seqNum=4&rl=1 */
      { "\x09\x00\x07\x00\x00",           5,    PROT_UNKNOWN,     "AppleTalk Multicast" },
      { "\x09\x00\x07\xFF\xFF\xFF", 6,    PROT_UNKNOWN,     "AppleTalk Broadcast" },
      { "\xFF\xFF\xFF\xFF\xFF\xFF", 6,    PROT_UNKNOWN,     "Broadcast" }, /* broadcast */
      /* end */
      { "",                                     0,    PROT_UNKNOWN,     "" },
      { "",                                     0,    PROT_UNKNOWN,     "" }
};

/**
 * organizational codes for LLC
 */
static const struct llc_org LLC_Orgs[] = {
      /*id        name  */
      { 0x000003, "Cisco" },
      { 0x080007, "Apple" },
      { 0, NULL }
};

/**
 * IP addresses that should not be counted as real hosts because they are reserved by a protocol...?
 */
/* FIXME: go back to numeric bit counts instead of actual masks... too inefficient and too ugly */
static const struct ip_prot Ip_Prot[] = {
      /*ip                                                                                off   len   prot_id                 descr */
      /* unable to grab an IP via DHCP address, use 169.x.x.x/8 */
      { { IPV4,   "\xFF\x00\x00\x00",     { "\xA9" } },                       0,    1,    PROT_UNKNOWN,     "DHCP Failure" },
      
      /* Multicast 224-239.x.x.x (ref: RFC3171) */
/*
   224.0.0.0   - 224.0.0.255     (224.0.0/24)  Local Network Control Block
   224.0.1.0   - 224.0.1.255     (224.0.1/24)  Internetwork Control Block
   224.0.2.0   - 224.0.255.0                   AD-HOC Block
   224.1.0.0   - 224.1.255.255   (224.1/16)    ST Multicast Groups
   224.2.0.0   - 224.2.255.255   (224.2/16)    SDP/SAP Block
   224.252.0.0 - 224.255.255.255               DIS Transient Block
   225.0.0.0   - 231.255.255.255               RESERVED
   232.0.0.0   - 232.255.255.255 (232/8)       Source Specific Multicast
                                               Block
   233.0.0.0   - 233.255.255.255 (233/8)       GLOP Block
   234.0.0.0   - 238.255.255.255               RESERVED
   239.0.0.0   - 239.255.255.255 (239/8)       Administratively Scoped
                                               Block
*/
      { { IPV4,   "\xFF\xFF\xFF\x00",     { "\xE0\x00\x00\x00" } },     0,    3,    PROT_UNKNOWN,     "Mcast Local Ctrl" },
      { { IPV4,   "\xFF\xFF\xFF\x00",     { "\xE0\x00\x01\x00" } },     0,    3,    PROT_UNKNOWN,     "Mcast Inet Ctrl" },
      /* TODO: cannot specify AD-HOC range :/ */
      { { IPV4,   "\xFF\xFF\xFF\x00",     { "\xE0\x00\x01\x00" } },     0,    3,    PROT_UNKNOWN,     "Mcast Inet Ctrl" },
      { { IPV4,   "\xFF\xFF\x00\x00",     { "\xE0\x01\x00\x00" } },     0,    2,    PROT_UNKNOWN,     "Mcast ST" },
      { { IPV4,   "\xFF\xFF\xFF\x00",     { "\xE0\x00\x00\x00" } },     0,    2,    PROT_UNKNOWN,     "Mcast SDP/SAP" },
      /* TODO: cannot specify DIS Transient */
      /* TODO: Reserved */
      { { IPV4,   "\xFF\x00\x00\x00",     { "\xE8\x00\x00\x00" } },     0,    1,    PROT_UNKNOWN,     "Mcast Src Spec" },
      { { IPV4,   "\xFF\x00\x00\x00",     { "\xE9\x00\x00\x00" } },     0,    1,    PROT_UNKNOWN,     "Mcast GLOP" },
      /* TODO: Reserved */
      { { IPV4,   "\xFF\xFF\xFF\x00",     { "\xEF\x00\x00\x00" } },     0,    1,    PROT_UNKNOWN,     "Mcast Admin Scoped" },

      /* IAPP Multicast is 224.0.1.178 (ref: 802.11F-2003) */
      { { IPV4,   "\xFF\xFF\xFF\xFF",     { "\xE0\x00\x01\xB2" } },     0,    4,    PROT_IAPP,        "Mcast IAPP" },

      /* NOTE: Mcast catch-all for anything I can't properly catch */
      { { IPV4,   "\xF0\x00\x00\x00",     { "\xE0\x00\x01\x00" } },     0,    1,    PROT_UNKNOWN,     "Mcast Other" },


      /* not sure what this address is exactly, but it's special... */
      /* fe80::5445:5245:444f and fe80::8000:5445:5245:444f */
      { { IPV6,   "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
                    { "\xFE\x80\x00\x00\x00\x00\x00\x00\x00\x00\x54\x45\x52\x45\x44\x4F" } },   0,    1,    PROT_UNKNOWN,     "Something to do with Teredo" },
      { { IPV6,   "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
                    { "\xFE\x80\x00\x00\x00\x00\x00\x00\x80\x00\x54\x45\x52\x45\x44\x4F" } },   0,    1,    PROT_UNKNOWN,     "Something to do with Teredo" },

      /* something to do with icmpv6 on windows... */
      /* fe80::8000:f227:bffb:e6af */
      { { IPV6,   "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00",
                    { "\xFE\x80\x00\x00\x00\x00\x00\x00\x00\x00\xF2\x27\xBF\xFB\xE6\x00" } },   0,    1,    PROT_UNKNOWN,     "Something to do with ICMPv6" },

      /* IPv6 6to4 */
      /* http://usipv6.com/2003arlington/presents/Jeff_Doyle.pdf */
      { { IPV6,   "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                    { "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } },   0,    1,    PROT_UNKNOWN,     "IPv6 6to4" },

      /* IPv6 SIIT */
      /* http://usipv6.com/2003arlington/presents/Jeff_Doyle.pdf */
      { { IPV6,   "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00",
                    { "\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } },   0,    1,    PROT_UNKNOWN,     "IPv6 SIIT (IPv6)" },
      { { IPV6,   "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00",
                    { "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00" } },   0,    1,    PROT_UNKNOWN,     "IPv6 SIIT (IPv4)" },

      /* fin */
      { { 0,            "\x00",                       { "" } },                           0,    0,    PROT_UNKNOWN,     "" }
};

/**
 * RFC 1918 Private IP Ranges
 */
static const struct ip Ip_Private[] = {
      { IPV4,     "\xFF\x00\x00\x00",     { "\x0A\x00\x00\x00" } }, /* 10.n.n.n */
      { IPV4,     "\xFF\x00\x00\x00",     { "\xEF\x00\x00\x00" } }, /* 127.n.n.n */
      { IPV4,     "\xFF\xF0\x00\x00",     { "\xAC\x1F\x00\x00" } }, /* 172.16-31 */
      { IPV4,     "\xFF\xFF\x00\x00",     { "\xC0\xA8\x00\x00" } }, /* 192.168.n.n */
      { IPV4,     "\xFF\xFF\x00\x00",     { "\xC0\xA8\x00\x00" } }, /* 192.168.n.n */
      { IPV6,     "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
            { "\xFE\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } }, /* IPv6 local */
      { IPV6,     "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
            { "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\x00\x01" } }, /* 127.0.0.1 embedded in IPv6 */
      { IPV6,     "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
            { "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" } }, /* IPv6 loopback */
      { 0,  "",                           { "" } }
};


/**
 * descriptions of ip 'prec' values
 */
const char *Ip_Prec_Descr[] = {
      "Routine",
      "Priority",
      "Immediate",
      "Flash",
      "Flash Override",
      "CRITIC/ECP",
      "Internetwork Control",
      "Network Control" /* 111b */
};

/**
 *
 */
static const char *Icmp_Type_Descr[] = {
      "Echo Response", /* 0x00 */
      "Unknown (0x1)", /* 0x01 */
      "Unknown (0x2)", /* 0x02 */
      "Unreachable", /* 0x03 */
      "Source Quench", /* 0x04 */
      "Redirect", /* 0x05 */
      "Unknown (0x6)", /* 0x06 */
      "Unknown (0x7)", /* 0x07 */
      "Echo Req", /* 0x08 */
      "Unknown (0x9)", /* 0x09 */
      "Unknown (0xA)", /* 0x0A */
      "Unknown (0xB)", /* 0x0B */
      "Parameter Problem", /* 0x0C */
      "Timestamp Req", /* 0x0D */
      "Timestamp Resp", /* 0x0E */
      "Info Req", /* 0x0F */
      "Info Resp", /* 0x10 */
      "(!) Invalid Icmp Code" /* 0x11 */
};

/**
 * NOTE: synced with TCP_OPT_ enum in netmap.h
 */
static const char *Tcp_Opts_Descr[] = {
      "END",
      "NOP",
      "MSS",
      "WS",
      "SACK",
      "SACKP",
      "ECHO_REQ",
      "ECHO_RESP",
      "TS",
      "POC",
      "POS",
      "CC",
      "CC_NEW",
      "CC_ECHO",
      "CHK_REQ",
      "CHK_DATA",
      "SKEETER",
      "BUBBA",
      "TRAILER",
      "MD5",
      "SCPS",
      "SNA",
      "RB",
      "CORR",
      "SNAP",
      "UNASSIGNED",
      "COMPFILT"
};

/* IAPP */
enum {
      IAPP_CMD_ADD_NOTIFY = 0,
      IAPP_CMD_MOVE_NOTIFY,
      IAPP_CMD_MOVE_RESP,
      IAPP_CMD_SEND_SEC_BLOCK,
      IAPP_CMD_ACK_SEC_BLOCK,
      IAPP_CMD_CACHE_NOTIFY,
      IAPP_CMD_CACHE_RESP,
      IAPP_CMD_TOOHIGH
};

enum {
      IAPP_MOVE_RESP_OK,
      IAPP_MOVE_RESP_DENIED,
      IAPP_MOVE_RESP_STALE,
      IAPP_MOVE_TOOHIGH
};

static const char *IAPP_CMD_STR[] = {
      "ADD-notify",
      "MOVE-notify",
      "MOVE-response",
      "Send-Security-Block",
      "ACK-Security-Block",
      "CACHE-notify",
      "CACHE-response"
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * initialize any data structures needed for protocols
 */
void protocol_init(void)
{
      lhash_init_ez(&Ip_Ping_Seq, 256);
            lhash_set_key_func_cmp(&Ip_Ping_Seq, ip_cmp);
            lhash_set_key_funcs(&Ip_Ping_Seq, ip_copy, free);
            lhash_set_val_funcs(&Ip_Ping_Seq, NULL, free);
}


/**
 *
 */
char * port_descr(uint16_t port, char *buf, size_t len)
{
      const struct port *search;
#ifdef _DEBUG
      assert(NULL != buf);
      assert(len >= PORT_DESCR_BUFLEN);
#endif

      for (search = Ports; search->num < port; search++)
            ; /* FIXME: linear search, we can do better */
      if (search->num == port) {
            strlcpy(buf, Prot_Descr[search->prot_id].tla, len);
      } else {
            buf[0] = '\0';
      }
      return buf;
}

/**
 * write a mac address to a human-readable string
 */
char * mac_addr_to_str(const struct mac *mac, char *buf, size_t len)
{
#ifdef _DEBUG
      assert(NULL != mac);
      assert(NULL != buf);
      assert(len >= MAC_ADDR_BUFLEN);
#endif

      snprintf(buf, len, "%02X:%02X:%02X:%02X:%02X:%02X",
            mac->addr[0], mac->addr[1], mac->addr[2],
            mac->addr[3], mac->addr[4], mac->addr[5]);

      return buf;
}

/***
 * @return 1 if address has a special purpose in a protocol or address scheme
 */
int mac_is_special(const struct mac *mac)
{
      const struct mac_prot *res;

#ifdef _DEBUG
      assert(NULL != mac);
#endif
      for (res = MAC_PROT; res != NULL && res->len > 0; res++) {
            if (mac->addr[0] == res->addr[0] && 0 == memcmp(mac->addr, res->addr, res->len))
                  return 1;
      }
      return 0;
}


/**
 * @return 1 if ip address has a special purpose in a protocol or address scheme
 */
int ip_is_special(const struct ip *ip)
{
      int unsigned i;

#ifdef _DEBUG
      assert(NULL != ip);
#endif

      for (i = 0; i < sizeof Ip_Prot / sizeof Ip_Prot[0]; i++)
            if (0 == ip_cmp(&Ip_Prot[i].ip, ip))
                  return 1;

      /* TODO: add to Ip_Prot? */

      switch (ip->version) {
      case IPV4:
            return (
                  0 == memcmp(ip->addr.v4, IPV4_ADDR_NONE, sizeof ip->addr.v4)
                  || 0 == memcmp(ip->addr.v4, IPV4_ADDR_BCAST, sizeof ip->addr.v4)
            );
            break;
      case IPV6:
            return (
                  0 == memcmp(ip->addr.v6, IPV6_ADDR_NONE, sizeof ip->addr.v6)
            );
            break;
      default:
            DEBUGF(__FILE__, __LINE__, "unexpected ip version 0x%02X(!)",
                  ip->version);
            return 0;
            break;
      }
      IMPOSSIBLE(__FILE__, __LINE__, "");
}

/**
 *
 */
int ip_is_private(const struct ip *ip)
{
      const struct ip *private;
#ifdef _DEBUG
      assert(NULL != ip);
#endif
      for (private = Ip_Private; '\0' != private->mask[0]; private++)
            if (private->version == ip->version &&
                  ip_subnet_match(ip, private, private->mask))
                  return 1;
      /* FIXME: get local IPv6 addresses... */
      if (IPV6 == ip->version)
            return 1;
      return 0;
}

/**
 * is an address a broadcast address?
 */
int ip_is_broadcast(const struct ip *ip)
{
#ifdef _DEBUG
      assert(NULL != ip);
#endif
      switch (ip->version) {
      case IPV4:
            return (255 == ip->addr.v4[3]); /* FIXME: is this right?! */
      case IPV6:
            DEBUGF(__FILE__, __LINE__, "don't know IPv6 broadcast?!");
            /* fallthrough... */
      default:
            return 0;
      }
}


/**
 * do two ips match for the first 'maskbits' bits?
 * NOTE: libpcap doesn't work with IPv6?
 */
int ip_subnet_match(const struct ip *a, const struct ip *b, const u_char *mask)
{
      u_char charmask, *acmp = NULL, *bcmp = NULL;
      size_t i, maxbytes = 0;
#ifdef _DEBUG
      assert(NULL != a);
      assert(NULL != b);
#endif
      if (a->version != b->version) {
            DEBUGF(__FILE__, __LINE__, "you're trying to compare ips of version %u and %u, wtf?",
                  a->version, b->version);
            return 0;
      } else if (IPV4 == a->version) {
            acmp = ((struct ip *)a)->addr.v4;
            bcmp = ((struct ip *)b)->addr.v4;
            maxbytes = sizeof a->addr.v4;
      } else if (IPV6 == a->version) {
            acmp = ((struct ip *)a)->addr.v6;
            bcmp = ((struct ip *)b)->addr.v6;
            maxbytes = sizeof a->addr.v6;
      }
      for (i = 0; i < maxbytes; i++, acmp++, bcmp++) {
            charmask = mask[i];
            if ((*acmp & charmask) != (*bcmp & charmask)) {
#ifdef DEBUG_SUBNET_MATCH
                  char abuf[IP_ADDR_BUFLEN], bbuf[IP_ADDR_BUFLEN];
                  DEBUGF(__FILE__, __LINE__, "%s != %s ((%02X & %02X) != (%02X & %02X)) mask %08X",
                        ip_addr_to_str(a, abuf, sizeof abuf),
                        ip_addr_to_str(b, bbuf, sizeof bbuf),
                        *acmp, charmask, *bcmp, charmask,
                        mask);
#endif
                  return 0;
            }
      }

#ifdef DEBUG_SUBNET_MATCH
      {
            char abuf[IP_ADDR_BUFLEN], bbuf[IP_ADDR_BUFLEN];
            DEBUGF(__FILE__, __LINE__, "%s == %s at mask %08X",
                  ip_addr_to_str(a, abuf, sizeof abuf),
                  ip_addr_to_str(b, bbuf, sizeof bbuf),
                  mask);
      }
#endif

      return 1;
}

/**
 * write an ip address to a human-readable string
 */
char * ip_addr_to_str(const struct ip *ip, char *buf, size_t len)
{
#ifdef _DEBUG
      assert(NULL != ip);
      assert(NULL != buf);
      assert(len >= IP_ADDR_BUFLEN);
#endif

      switch (ip->version) {
      case IPV4:
            snprintf(buf, len, "%u.%u.%u.%u",
                  ip->addr.v4[0], ip->addr.v4[1], ip->addr.v4[2], ip->addr.v4[3]);
            break;
      case IPV6:
      { /* canonical IPv6 representation... this could be its own function... */
            /* FIXME: take one byte at a time instead of 2 for REAL canonicalization */
            int unsigned i, in_zero = 0, seen_zero = 0, post_zero = 0;
            size_t off;
            for (i = 0, off = 0; i < sizeof ip->addr.v6 && off < len; i+=2) {
                  /* two bytes at a time, compress zeroes */
                  in_zero = (0 == ip->addr.v6[i] && (0 == seen_zero || (1 == in_zero && 1 == seen_zero)));
                  if (!seen_zero && in_zero)
                        seen_zero = 1;
                  /* if we have just exited a series of 0s, print an extra ":" */
                  if (!in_zero && !post_zero && seen_zero) {
                        strlcat(buf, ":", len);
                        off++;
                        post_zero = 1;
                  }
                  /* ":" separator, assuming we're not currently in a series of 0s */
                  if ((i > 0 && !in_zero) || (0 == i && in_zero)) {
                        strlcat(buf, ":", len);
                        off++;
                  }
                  /* ISATAP -- ::0:5efe:W.X.Y.Z -- IPv4 embedded in IPv6 */
                  if (12 == i && 0 == memcmp("\x00\x00\x5e\xfe", ip->addr.v6 + 8, 4)) {
                        snprintf(buf + off, len, "%u.%u.%u.%u",
                              ip->addr.v6[12], ip->addr.v6[13], ip->addr.v6[14], ip->addr.v6[15]);
                        break; /* we're done */
                  }
                  /* 2 bytes at once, lowercase, if not in a series of 0s */
                  if (!in_zero) {
                        if (0 == ip->addr.v6[i]) {
                              if (0 == ip->addr.v6[i + 1]) {
                                    strlcat(buf, "0", len);
                                    off++;
                              } else {
                                    snprintf(buf + off, len, "%x", ip->addr.v6[i + 1]);
                                    off += 2;
                              }
                        } else {
                              snprintf(buf + off, len, "%x", ((ip->addr.v6[i] << 8) | ip->addr.v6[i + 1]));
                              off += 4;
                        }

                  }
            }
      }
            break;
      default:
            strlcpy(buf, "???", len);
            break;
      }

      return buf;
}

/**
 *
 */
const char * icmp_type_to_str(u_char type)
{
      if (type >= ICMP_TYPE_WHOOPS_TOO_HIGH)
            return Icmp_Type_Descr[ICMP_TYPE_WHOOPS_TOO_HIGH];
      return Icmp_Type_Descr[type];
}

/**
 * 
 */
protonode_t * protonode_new(protonode_t *parent, int prot_id)
{
      protonode_t *node = malloc(sizeof *node);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__,
                  "couldn't allocate %u bytes for a protonode_t!", sizeof *node);
            return NULL;
      }
      memset(node, 0, sizeof *node);
      node->parent = parent;
      node->prot_id = prot_id;
      return node;
}

static uint32_t Frame_Cnt = 0;
/**
 * @param buf raw buffer
 * @param len  buflen
 * @param linktype type of datalink returned from pcap
 * @return NULL on error, or an allocated tree of 1+ protonode_t's
 */
protonode_t * node_new_parse(const u_char *buf, size_t len, int linktype)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
#endif

      /* set up logical frame */
      node = protonode_new(NULL, PROT_LOGIC);
      if (NULL == node)
            return NULL;
      node->data.logic.frame = Frame_Cnt++;
      node->data.logic.len = (uint32_t)len;
      node->data.logic.when = time(NULL);
      node->data.logic.type = linktype;

      switch (node->data.logic.type) {
      case DLT_EN10MB: /* ethernet */
            used = parse_ethernet2(buf, len, node);
            break;
      case DLT_LINUX_SLL:
            used = parse_linux_sll(buf, len, node);
            break;
      case DLT_RAW: /* raw ip */
            used = parse_ip(buf, len, node);
            break;
      case DLT_ATM_RFC1483: /*  RFC 1483 LLC/SNAP-encapsulated ATM; the packet begins with an IEEE 802.2 LLC header. */
            used = parse_llc(buf, len, node);
            break;
      case DLT_IEEE802_11: /* wireless, neat!... same as ethernet? check! */
      default:
#ifdef PROT_DONT_KNOW
            DEBUGF(__FILE__, __LINE__,
                  "don't know what to do with datalink type 0x%X (%s)",
                  node->data.logic.type, pcap_datalink_val_to_name(node->data.logic.type));
#endif
            break;
      }

      /* we didn't parse the whole thing... */
      if (used < len) {
            /* find last node */
            protonode_t *last = node, *tmp = node->child;
            while (NULL != tmp) {
                  last = tmp;
                  tmp = tmp->child;
            }
            used += parse_unknown(buf + used, len - used, last);
      }

      node->bytes = used;
      
      return node;
}

/**
 * take a fully parsed msg and see what we can learn about it
 * @param parent node of the received msg
 */
void node_report(protonode_t *node)
{
      struct mac *srcmac;
      struct ip *ipsrc = NULL, *ipdst = NULL;
      protonode_t *ether = NULL, *curr;
      size_t bytes[PROT_COUNT] = { 0 }, packets[PROT_COUNT] = { 0 };

#ifdef _DEBUG
      assert(NULL != node);
#endif

      /* seek to the end and process backwards */
      if (NULL == node->child) {
            DEBUGF(__FILE__, __LINE__, "");
            return;
      } else if (PROT_ETHER2 != node->child->prot_id && PROT_IEEE_802_3 != node->child->prot_id) {
            DEBUGF(__FILE__, __LINE__, "");
            return;
      }

      ether = node->child;
      srcmac = &ether->data.ether.src;
      
      /* seek forward to the highest level protocol */
      for (curr = node; NULL != curr->child; curr = curr->child);

      /* work backwards... */
      for (; NULL != curr; curr = curr->parent) {

            /* record stats for each protocol */
            if (PROT_LOGIC != curr->prot_id) {
                  /* don't report for logic frame */
                  packets[curr->prot_id]++;
                  bytes[curr->prot_id] += curr->bytes;
            }

            switch (curr->prot_id) {
            default:
                  break;
            case PROT_UNKNOWN:
                  bytes[curr->prot_id] += curr->data.unknown.len;
                  break;
            case PROT_LOGIC: /* Logical frame */
                  break;
            case PROT_LINUX_SLL:
                  break;
            case PROT_ETHER2: /* Ethernet 2 */
            case PROT_IEEE_802_3: /* IEEE 802.3 Ethernet */
                  break;
            case PROT_ARP: /* Address Resolution Protocol */
                  report_mac_ip(&curr->data.arp.src_mac, &curr->data.arp.src_ip);
                  break;
            case PROT_LLC: /* Link Layer Control */
                  break;
            case PROT_CDP: /* Cisco Discovery Protocol */
#ifdef WE_NEED_TO_ANALYZE_THIS_STRING_AND_REPORT_AN_OS
                  if ('\0' != node->data.cdp.version_id[0])
                        report_mac_os(srcmac, FACT_OS_IOS, SURE_POSITIVE, node->data.cdp.version_id);
#endif
                  if ('\0' != node->data.cdp.platform[0]) report_mac_fact(srcmac, FACT_HW_CISCO);
                  if (node->data.cdp.capab_router) report_mac_fact(srcmac, FACT_ROLE_ROUTER);
                  if (node->data.cdp.capab_trans_bridge || node->data.cdp.capab_srcrt_bridge)
                        report_mac_fact(srcmac, FACT_ROLE_BRIDGE);
                  if (node->data.cdp.capab_switch) report_mac_fact(srcmac, FACT_ROLE_SWITCH);
                  if (node->data.cdp.capab_host) report_mac_fact(srcmac, FACT_ROLE_HOST);
                  if (node->data.cdp.capab_repeater) report_mac_fact(srcmac, FACT_ROLE_REPEATER);
                  break;
            case PROT_STP: /* Spanning Tree Protocol */
                  report_mac_fact(&curr->data.stp.root_id.mac, FACT_ROLE_BRIDGE);
                  report_mac_fact(&curr->data.stp.bridge_id.mac, FACT_ROLE_BRIDGE);
                  break;
            case PROT_AARP: /* Apple Address Resolution Protocol */
                  report_mac_ip(&curr->data.aarp.src_mac, &curr->data.aarp.src_ip);
                  break;
            case PROT_IP:
#if 0
                  if (!ip_is_special(&curr->data.ip.src))
                        report_mac_ip(srcmac, &curr->data.ip.src);
#endif
                  ipsrc = &curr->data.ip.src;
                  ipdst = &curr->data.ip.dest;
                  break;
            case PROT_IPV6:
                  if (NULL != curr->parent && PROT_TEREDO == curr->parent->prot_id &&
                        NULL != curr->parent->parent && PROT_UDP == curr->parent->parent->prot_id &&
                        NULL != curr->parent->parent->parent && PROT_IP == curr->parent->parent->parent->prot_id &&
                        ip_is_private(&curr->parent->parent->parent->data.ip.src)
                  ) {
                        /* Teredo tunneling */
                        /* FIXME: this logic is fucked... need to match subnets */
                        report_mac_ip(srcmac, &curr->parent->parent->parent->data.ip.src);
                        if (!ip_is_special(&curr->data.ipv6.src)) {
                              report_mac_ip(srcmac, &curr->data.ipv6.src);
                              ipsrc = &curr->data.ipv6.src;
                              ipdst = &curr->data.ipv6.dest;
                        }
                  } else if (!ip_is_special(&curr->data.ipv6.src)) {
                        report_mac_ip(srcmac, &curr->data.ipv6.src);
                        ipsrc = &curr->data.ipv6.src;
                        ipdst = &curr->data.ipv6.dest;
                  }
                  break;
            case PROT_ICMP:
                  break;
            case PROT_ICMPV6:
                  break;
            case PROT_UDP:
                  break;
            case PROT_TCP:
                  bytes[curr->prot_id] += curr->data.tcp.payload_bytes;
                  report_tcp(curr);
                  break;
            case PROT_IGMP:
                  break;
            case PROT_IPX:
                  break;
            case PROT_BOOTP:
                  bootp_report(curr);
                  break;
            case PROT_DNS:
                  if (DNS_TYPE_RESP == curr->data.dns.type && curr->data.dns.q_cnt > 0) {
                        /* dns -> udp -> ip */
                        report_ip_fact(&curr->parent->parent->data.ip.src, FACT_ROLE_SERVER_DNS);
                  }
                  break;
            case PROT_NB_DGM:
                  break;
            case PROT_NB_NS:
                  break;
            case PROT_DDP:
                  break;
            case PROT_SAP: /* Service Advertisement Protocol */
                  /* type of service */
                  switch (curr->data.sap.server.type) {
                  case SAP_SERVER_TYPE_PRINTER:
                        /* this thing is an HP Laserjet advertising itself over IPX */
                        report_mac_fact(srcmac, FACT_ROLE_PRINTER);
                        break;
                  case SAP_SERVER_TYPE_FILESERVER:
                        /* this thing is serving files */
                        report_mac_fact(srcmac, FACT_ROLE_SERVER_FILES);
                        break;
                  default: /* nothing */
#ifdef PROT_DONT_KNOW
                        DEBUGF(__FILE__, __LINE__, "unknown SAP server type 0x%04X",
                              curr->data.sap.server.type);
#endif
                        break;
                  }
                  break;
            case PROT_HTTP:
                  break;
            case PROT_SSDP:
                  if (NULL != curr->parent && PROT_UDP == curr->parent->prot_id
                        && NULL != curr->parent->parent && PROT_IP == curr->parent->prot_id)
                        report_ip_fact(&curr->parent->parent->data.ip.src, FACT_ROLE_ROUTER);
                  break;
            }

      } /* for */

      /* FIXME: report_ip_traffic should probably have same sig as report_mac_traffic... currently it's
       * being used to relate IP<->MAC, that should be different */
#if 0
      if (NULL != ipsrc && NULL != ipdst) {
            /* some traffic, such as ICMP Destination Unreachable go to a broadcast mac address
             * but are targetted at the IP layer by specific IP. if we report the mac traffic
             * it'll be skipped on account of the broadcast addr, so we report ip traffic instead */
            report_ip_traffic(ipsrc, ipdst, packets, bytes);
      } else {
      }
#endif

      report_mac_traffic(&ether->data.ether.src, &ether->data.ether.dest, packets, bytes);

}


/**
 * free node and (recursively) any child nodes
 */
void node_free(protonode_t *node)
{
      protonode_t *curr, *tmp;
#ifdef _DEBUG
      assert(NULL != node);
#endif

      for (
            curr = tmp = node;
            NULL != curr && NULL != tmp;
            curr = tmp
      ) {
            tmp = curr->child;
            free(curr);
      }
}

/**
 * search reserved mac address prefixes
 * @return 
 * @todo binary search, you lazy bastard
 */
const struct mac_prot * mac_addr_prot(const struct mac *mac)
{
      const struct mac_prot *res;

#ifdef _DEBUG
      assert(NULL != mac);
#endif

      for (res = MAC_PROT; res != NULL && res->len > 0; res++) {
            if (0 == memcmp(mac->addr, res->addr, res->len))
                  return res;
      }

      return NULL;
}

#if 0
/**
 * turn a vendor id into a human-readable name
 */
const char * mac_addr_vend_to_str(int id)
{
      if (id < 0 || id >= VEND_OOPS_TOOHIGH) {
            DEBUGF(__FILE__, __LINE__,
                  "%d is not a valid vendor id, dude", id);
            id = 0;
      }
      return Vendor_Descr[id];
}
#endif

/**
 * return human-readable description of mac address, whether it be protocol
 * or vendor-related
 */
const char * mac_addr_descr(const struct mac *mac)
{
      const struct mac_vend *vend;
      const struct mac_prot *prot;

#ifdef _DEBUG
      assert(NULL != mac);
#endif

      vend = mac_addr_vend(mac);
      if (NULL != vend) {
            return vend->vendor;
      }

      prot = mac_addr_prot(mac);
      if (NULL != prot) {
            /* TODO: check offset bounds! */
            return prot->descr;
      }

      return Prot_Descr[PROT_UNKNOWN].tla; /* return unknown description... better than NULL :P */
}



/**
 * parse an unknown packet
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_unknown(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_UNKNOWN);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.unknown.len = len;
      node->data.unknown.data = buf;
      node->bytes = len;

      return len;
}

/**
 * data is known to be extraneous padding with no purpose
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_padding(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_EXTRA_JUNK);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.unknown.len = len;
      node->data.unknown.data = buf;
      node->bytes = len;

      return len;
}

/**
 * parse Linux Cooked Capture
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_linux_sll(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate that we're ethernet... TODO: improve this */
      if (len < LINUX_SLL_LEN_MIN) {
            return 0;
      }
      
      node = protonode_new(parent, PROT_LINUX_SLL);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.linux_sll.type = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.linux_sll.ll_type = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.linux_sll.ll_len = (buf[used] << 8) | buf[used + 1];
      used += 2;

      memcpy(node->data.linux_sll.ll_addr, buf + used, 8);
      used += 8;

      node->data.linux_sll.ethernet_type = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->bytes = used;

      switch (node->data.linux_sll.ethernet_type) {
      case ETHER2_TYPE_ARP:
            used += parse_arp(buf + used, len - used, node);
            break;
      case ETHER2_TYPE_IP:
            used += parse_ip(buf + used, len - used, node);
            break;
      case ETHER2_TYPE_802_2_LLC:
            used += parse_llc(buf + used, len - used, node);
            break;
      default:
            /* nothing... */
            /* default to ethernet2?! */
            used += parse_ip(buf + used, len - used, node);
            break;
      }

      return used;
}

/**
 * parse Ethernet
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_ethernet2(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate that we're ethernet... TODO: improve this */
      if (len < ETHER_LEN_MIN) {
            return 0;
      }
      
      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_ETHER2);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      memcpy(node->data.ether.dest.addr, buf + used, MAC_ADDR_BYTES);
      used += MAC_ADDR_BYTES;
      memcpy(node->data.ether.src.addr, buf + used, MAC_ADDR_BYTES);
      used += MAC_ADDR_BYTES;
      node->data.ether.data.type = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      if (len - used == node->data.ether.data.type) {
            /* if type matches the length, this is almost certainly an 802.3 packet */
            node_free(node);
            return parse_ethernet802_3(buf, len, parent);
      }

      node->bytes = used;

      switch (node->data.ether.data.type) {
      case ETHER2_TYPE_ARP:
            used += parse_arp(buf + used, len - used, node);
            break;
      case ETHER2_TYPE_LOOP:
            used += parse_loop(buf + used, len - used, node);
            break;
      case ETHER2_TYPE_IP:
            used += parse_ip(buf + used, len - used, node);
            break;

/* FROM: http://www.thetechfirm.com/packets/
Ethertype 8781 Packets
While Sniffing at a customer site I found some Ethernet II packets with an Ethertype of 8781.  Several of my protocol analyzers decoded up to the Type portion of the packet and then indicated that the rest of the payload was not interpreted or decoded.
After some poking around I found that Ethertype 8780 - 8785 belongs to Symbol, as evident from the source MAC address (also Symbol).
These packets were being transmitted from a Symbol access point.
*/
      case 0x8781:
            used += parse_symbol_wifi(buf + used, len - used, node);
            break;

      default:
            if (node->data.ether.data.type < ETHER_FRAME_MAX_BYTES) {
                  /* if field is too high to be a valid length we assume it's 802.3 */
#ifdef DEBUG_ETHER2
                  DEBUGF(__FILE__, __LINE__, "assuming 0x%04X(%u) is 802.3...",
                        node->data.ether.data.type, node->data.ether.data.type);
#endif
                  node_free(node);
                  return parse_ethernet802_3(buf, len, parent);
            } else { /* too low to be type, likely it's an unrecognized ethernet type */
                  DEBUGF(__FILE__, __LINE__, "don't know ether2 type 0x%04X...",
                        node->data.ether.data.type);
            }
            break;
      }

      if (ETHER_LEN_PADTO == len && used < len) { /* pad to min length */
            node->bytes += len - used;
            node->data.ether.pad_len = (uint16_t)(len - used);
            node->data.ether.pad_bytes = (const u_char *)(buf + used);
            used = len;
      }
      
      return used;
}

/**
 * parse LOOPBACK
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_loop(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      if (len < LOOP_LEN_MIN) {
            return 0;
      }
      
      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_LOOP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.loop.skip = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.loop.func = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.loop.receiptno = (buf[used] << 8) | buf[used + 1];
      used += 2;

      /* rest of packet is loop data */
      node->data.loop.datalen = (uint32_t)len;
      node->data.loop.data = buf + used;
      used = len;
      
      return used;
}


/**
 * parse Ethernet 802.3
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_ethernet802_3(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate that we're ethernet... TODO: improve this */
      if (len < ETHER_LEN_MIN) {
            return 0;
      }
      
      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_IEEE_802_3);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      memcpy(node->data.ether.dest.addr, buf + used, MAC_ADDR_BYTES);
      used += MAC_ADDR_BYTES;
      memcpy(node->data.ether.src.addr, buf + used, MAC_ADDR_BYTES);
      used += MAC_ADDR_BYTES;
      node->data.ether.data.len = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->bytes = used;

      /* check for special mac addresses */
      do {
            const struct mac_prot *res;
            res = mac_addr_prot(&node->data.ether.dest);
            if (NULL == res) {
                  char mac_buf[MAC_ADDR_BUFLEN];
                  DEBUGF(__FILE__, __LINE__, "802.3 no match for mac %s",
                        mac_addr_to_str(&node->data.ether.dest, mac_buf, sizeof mac_buf));
                  break;
            }
            /* TODO: replace switch with a table of function pointers and an offset */
            switch (res->prot_id)
            {
            case PROT_ARP:
                  used += parse_arp(buf + used, len - used, node);
                  break;
            case PROT_STP:
            case PROT_LLC:
            default: /* assume llc by default */
                  used += parse_llc(buf + used, len - used, node);
                  break;
            }
      } while (0); /* onetime */

      if (ETHER_LEN_PADTO == len && used < len) { /* pad to min length */
            node->bytes += len - used;
            node->data.ether.pad_len = (uint16_t)(len - used);
            node->data.ether.pad_bytes = (const u_char *)(buf + used);
            used = len;
      }

      return used;
}

/**
 * parse Address Resolution Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note ARP looks like:
 * 
 */
size_t parse_arp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < ARP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "arp len %u invalid (%u:%u)",
                  len, ARP_LEN_MIN, ARP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_ARP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }

      parent->child = node;

      node->data.arp.hw = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.arp.prot = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.arp.hw_size = buf[used++];
      node->data.arp.prot_size = buf[used++];

      node->data.arp.opcode = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      memcpy(node->data.arp.src_mac.addr, buf + used, node->data.arp.hw_size);
      used += node->data.arp.hw_size;

      node->data.arp.src_ip.version = IPV4;
      memcpy(node->data.arp.src_ip.addr.v4, buf + used, node->data.arp.prot_size);
      used += node->data.arp.prot_size;
      
      memcpy(node->data.arp.dest_mac.addr, buf + used, node->data.arp.hw_size);
      used += node->data.arp.hw_size;

      node->data.arp.dest_ip.version = IPV4;
      memcpy(node->data.arp.dest_ip.addr.v4, buf + used, node->data.arp.prot_size);
      used += node->data.arp.prot_size;

      node->bytes = used;

      if (used < len)
            return parse_padding(buf + used, len - used, node);

      return used;
}


/**
 * parse Logical Link Control
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note LLC looks like:
 * [DSAP(1)][SSAP(1)][flags(1)][orgcode(3)][pid(2)]
 */
size_t parse_llc(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < LLC_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "llc len %u invalid (%u:%u)",
                  len, LLC_LEN_MIN, LLC_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_LLC);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;


      /* yes, store same value in multiple fields */
      /* FIXME: are these bitmasks right? */
      node->data.llc.dsap.whole = buf[used];
      node->data.llc.dsap.ig = buf[used] & 0x01;
      node->data.llc.dsap.addr = buf[used] & 0xFE;
      used++;

      /* yes, store same value in multiple fields */
      node->data.llc.ssap.whole = buf[used];
      node->data.llc.ssap.cr = buf[used] & 0x01;
      node->data.llc.ssap.addr = buf[used] & 0xFE;
      used++;

      node->data.llc.cmd = buf[used] & 0xFC;
      node->data.llc.frame_type = buf[used] & 0x3;
      used++;

      if (LLC_DSAP_SNAP == node->data.llc.dsap.whole) {
            node->data.llc.data.snap.org_id = (buf[used] << 16) | (buf[used + 1] << 8) | buf[used + 2];
            used += 3;

            node->data.llc.data.snap.pid = (buf[used] << 8) | buf[used + 1];
            used += 2;
      }

      node->bytes = used;

      switch (node->data.llc.dsap.whole) {
      case LLC_DSAP_SNAP:
            switch (node->data.llc.data.snap.pid) {
            case LLC_PID_CDP:
                  used += parse_cdp(buf + used, len - used, node);
                  break;
            case LLC_PID_CISCOWL:
                  used += parse_ciscowl(buf + used, len - used, node);
                  break;
            case LLC_PID_APPLETALK:
                  break;
            case LLC_PID_AARP:
                  used += parse_aarp(buf + used, len - used, node);
                  break;
            default:
                  /* NOTE: when inside SLL this is exactly the same as how ether2(?) chooses next... */
                  switch (node->data.llc.data.snap.pid) {
                  case ETHER2_TYPE_IP:
                        used += parse_ip(buf + used, len - used, node);
                        break;
                  case ETHER2_TYPE_ARP:
                        used += parse_arp(buf + used, len - used, node);
                        break;
                  default:
                        break;
                  }
                  break;
            }
            break;
      case LLC_DSAP_ST_BDPU: /* spanning tree */
            used += parse_stp(buf + used, len - used, node);
            break;
      case LLC_DSAP_NETWARE: /* IPX */
            used += parse_ipx(buf + used, len - used, node);
            break;
      default:
            break;
      }
      
      return used;
}

/**
 * parse Cisco Discovery Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note CDP looks like:
 *
 */
size_t parse_cdp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < CDP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "cdp len %u invalid (%u:%u)",
                  len, CDP_LEN_MIN, CDP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_CDP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.cdp.version = buf[used++];
      node->data.cdp.ttl = buf[used++];

      node->data.cdp.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      {
            uint16_t sec_type, sec_len;
            size_t val_len;
            while (used + 4 < len) {
                  sec_type = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  sec_len = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  sec_len -= 4; /* 2 bytes type + 2 bytes len */
                  if (used + sec_len > len) {
                        DEBUGF(__FILE__, __LINE__, "");
                        break;
                  }
                  switch (sec_type) {
                  case CDP_TYPE_DEV:
                        val_len = MIN((size_t)sec_len, sizeof node->data.cdp.dev_id - 1);
                        memcpy(node->data.cdp.dev_id, buf + used, val_len);
                        node->data.cdp.dev_id[val_len] = '\0';
                        break;
                  case CDP_TYPE_ADDR:
                        break;
                  case CDP_TYPE_PORT:
                        val_len = MIN((size_t)sec_len, sizeof node->data.cdp.port - 1);
                        memcpy(node->data.cdp.port, buf + used, val_len);
                        node->data.cdp.port[val_len] = '\0';
                        break;
                  case CDP_TYPE_CAPAB:
                  {
                        uint32_t capab = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
                        node->data.cdp.capab_unused = (capab & CDP_CAPAB_UNUSED);
                        node->data.cdp.capab_router = (capab & CDP_CAPAB_ROUTER) != 0;
                        node->data.cdp.capab_trans_bridge = (capab & CDP_CAPAB_TRANS_BRIDGE) != 0;
                        node->data.cdp.capab_srcrt_bridge = (capab & CDP_CAPAB_SRCRT_BRIDGE) != 0;
                        node->data.cdp.capab_switch = (capab & CDP_CAPAB_SWITCH) != 0;
                        node->data.cdp.capab_host = (capab & CDP_CAPAB_HOST) != 0;
                        node->data.cdp.capab_igmp = (capab & CDP_CAPAB_IGMP) != 0;
                        node->data.cdp.capab_repeater = (capab & CDP_CAPAB_REPEATER) != 0;
                  }
                        break;
                  case CDP_TYPE_VERSION:
                        val_len = MIN((size_t)sec_len, sizeof node->data.cdp.version_id - 1);
                        memcpy(node->data.cdp.version_id, buf + used, val_len);
                        node->data.cdp.version_id[val_len] = '\0';
                        break;
                  case CDP_TYPE_PLATFORM:
                        val_len = MIN((size_t)sec_len, sizeof node->data.cdp.platform - 1);
                        memcpy(node->data.cdp.platform, buf + used, val_len);
                        node->data.cdp.platform[val_len] = '\0';
                        break;
                  case CDP_TYPE_IPPREFIX:
                        DEBUGF(__FILE__, __LINE__, "cdp ip prefix unimplemented");
                        break;
                  }

                  used += sec_len;
            }
      }

      node->bytes = used;


      return used;
}

/**
 * parse Spanning-Tree Protocol
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note STP looks like:
 *
 */
size_t parse_stp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < STP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "stp len %u invalid (%u:%u)",
                  len, STP_LEN_MIN, STP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_STP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;
      
      node->data.stp.prot_id = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.stp.prot_ver = buf[used++];
      node->data.stp.bdpu_type = buf[used++];
      node->data.stp.topo_chg_ack = buf[used] & 0x80;
      node->data.stp.flag_unused = buf[used] & 0x7E;
      node->data.stp.topo_chg = buf[used] & 0x01;
      used++;

      /* root_id */
      node->data.stp.root_id.num = ((buf[used] << 8) | buf[used + 1]);
      used += 2;
      memcpy(node->data.stp.root_id.mac.addr, buf + used, 6);
      used += 6;

      /* path_cost */
      node->data.stp.path_cost =
            ((buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3]);
      used += 4;

      /* bridge_id */
      node->data.stp.bridge_id.num = ((buf[used] << 8) | buf[used + 1]);
      used += 2;
      memcpy(node->data.stp.bridge_id.mac.addr, buf + used, 6);
      used += 6;

      node->data.stp.port_id = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.stp.msg_age = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.stp.max_age = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.stp.hello_time = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.stp.fwd_delay = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->bytes = used;

      
      return used;
}


/**
 * parse Apple Address Resolution Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note identical to ARP
 * @note ARP looks like:
 * 
 */
size_t parse_aarp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < AARP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "arp len %u invalid (%u:%u)",
                  len, AARP_LEN_MIN, AARP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_AARP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }

      parent->child = node;

      node->data.aarp.hw = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.aarp.prot = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.aarp.hw_size = buf[used++];
      node->data.aarp.prot_size = buf[used++];

      node->data.aarp.opcode = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      memcpy(node->data.aarp.src_mac.addr, buf + used, node->data.aarp.hw_size);
      used += node->data.aarp.hw_size;

      node->data.aarp.src_ip.version = IPV4;
      memcpy(node->data.aarp.src_ip.addr.v4, buf + used, node->data.aarp.prot_size);
      used += node->data.aarp.prot_size;
      
      memcpy(node->data.aarp.dest_mac.addr, buf + used, node->data.aarp.hw_size);
      used += node->data.aarp.hw_size;

      node->data.aarp.dest_ip.version = IPV4;
      memcpy(node->data.aarp.dest_ip.addr.v4, buf + used, node->data.aarp.prot_size);
      used += node->data.aarp.prot_size;

      node->bytes = used;

      return used;
}

/**
 * parse CiscoWL
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 * 
 */
size_t parse_ciscowl(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < CISCOWL_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "ciscowl len %u invalid (%u:%u)",
                  len, CISCOWL_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_CISCOWL);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }

      parent->child = node;

      node->data.ciscowl.len = (buf[used] << 8) | buf[used + 1];
      used += 2;
      node->data.ciscowl.type = (buf[used] << 8) | buf[used + 1];
      used += 2;
      memcpy(&node->data.ciscowl.dst.addr, buf + used, 6);
      used += 6;
      memcpy(&node->data.ciscowl.src.addr, buf + used, 6);
      used += 6;
      node->data.ciscowl.unknown1 = (buf[used] << 8) | buf[used + 1];
      used += 2;
      node->data.ciscowl.unknown2 = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 3] << 8) | buf[used + 4];
      used += 4;

      node->bytes = used;

      report_mac_fact(&node->data.ciscowl.src, FACT_ROLE_ACCESS_POINT);

      return used;
}


/**
 * parse Service Advertisement Protocol
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note SAP looks like:
 *
 */
size_t parse_sap(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < SAP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "sap len %u invalid (%u)",
                  len, IPX_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_SAP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.sap.code = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.sap.server.type = (buf[used] << 8) | buf[used + 1];
      used += 2;

      if (len > 4) {
            /* fixed-width name */
            memcpy(node->data.sap.server.name, buf + used, SAP_SERVER_NAME_MAXLEN);
            node->data.sap.server.name[SAP_SERVER_NAME_MAXLEN] = '\0';
            used += SAP_SERVER_NAME_BUFLEN;
      
            node->data.sap.server.network = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
            used += 4;
      
            memcpy(node->data.sap.server.node.addr, buf + used, 6);
            used += 6;
      
            node->data.sap.server.socket = (buf[used] << 8) | buf[used + 1];
            used += 2;
      
            node->data.sap.server.intermed_nets = (buf[used] << 8) | buf[used + 1];
            used += 2;
      }

      node->bytes = used;

      return used;
}


/**
 * parse Internetwork Packet eXchange
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note IPX looks like:
 *
 */
size_t parse_ipx(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < IPX_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "ipx len %u invalid (%u:%u)",
                  len, IPX_LEN_MIN, IPX_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_IPX);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.ipx.checksum = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.ipx.len = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.ipx.hops = buf[used++];
      node->data.ipx.type = buf[used++];

      /* dest */
      node->data.ipx.dest.net =
            (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;
      memcpy(node->data.ipx.dest.node.addr, buf + used, 6);
      used += 6;
      node->data.ipx.dest.socket = (buf[used] << 8) | buf[used + 1];
      used += 2;

      /* src */
      node->data.ipx.src.net =
            (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;
      memcpy(node->data.ipx.src.node.addr, buf + used, 6);
      used += 6;
      node->data.ipx.src.socket = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->bytes = used;

      switch (node->data.ipx.dest.socket) {
      case IPX_SOCK_SAP:
            used += parse_sap(buf + used, len - used, node);
            break;
      default:
            break;
      }
      
      return used;
}

/**
 * parse Data Delivery Protocol
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note DDP looks like:
 *
 */
size_t parse_ddp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < DDP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "ddp len %u invalid (%u)",
                  len, DDP_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_DDP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;
      
      node->data.ddp.hops = buf[used++];
      node->data.ddp.len = buf[used++];

      node->data.ddp.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.ddp.dest_net = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.ddp.src_net = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.ddp.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.ddp.src_node = buf[used++];
      node->data.ddp.dest_socket = buf[used++];
      node->data.ddp.src_socket = buf[used++];
      node->data.ddp.prot_type = buf[used++];

      node->bytes = used;
      
      return used;
}


/**
 * parse Internet Protocol
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note STP looks like:
 *
 */
size_t parse_ip(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < IP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "ip len %u invalid (%u:%u)",
                  len, IP_LEN_MIN, IP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_IP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.ip.version = (buf[used] & 0xF0) >> 4;
      node->data.ip.headlen = (buf[used] & 0x0F) << 2;
      used++;

      node->data.ip.prec = (buf[used] & 0xD0) >> 5;
      node->data.ip.lowdelay = (buf[used] & 0x10) >> 4;
      node->data.ip.throughput = (buf[used] & 0x8) >> 3;
      node->data.ip.reliable = (buf[used] & 0x4) >> 2;
      node->data.ip.ect = (buf[used] & 0x2) >> 1;
      node->data.ip.ece = buf[used] & 0x1;
      used++;

      node->data.ip.len = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.ip.id = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.ip.res = (buf[used] & 0x80) != 0;
      node->data.ip.dontfrag = (buf[used] & 0x40) != 0;
      node->data.ip.morefrag = (buf[used] & 0x20) != 0;
      node->data.ip.id = ((buf[used] & 0x1F) | buf[used + 1]);
      used += 2;

      node->data.ip.ttl = buf[used++];
      node->data.ip.prot = buf[used++];

      node->data.ip.checksum = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.ip.src.version = IPV4;
      memcpy(node->data.ip.src.addr.v4, buf + used, 4);
      used += 4;

      node->data.ip.dest.version = IPV4;
      memcpy(node->data.ip.dest.addr.v4, buf + used, 4);
      used += 4;

      node->data.ip.opt_len = (u_char)((size_t)node->data.ip.headlen - used);
      
      memcpy(node->data.ip.opt_bytes, buf + used, node->data.ip.opt_len);
      used += node->data.ip.opt_len;

      node->bytes = used;

      /* where to go next? */
      switch (node->data.ip.prot) {
      case IP_PROT_ICMP:
            used += parse_icmp(buf + used, len - used, node);
            break;
      case IP_PROT_IGMP:
            used += parse_igmp(buf + used, len - used, node);
            break;
      case IP_PROT_UDP:
            used += parse_udp(buf + used, len - used, node);
            break;
      case IP_PROT_IPV6:
            used += parse_ipv6(buf + used, len - used, node);
            break;
      case IP_PROT_TCP:
            used += parse_tcp(buf + used, len - used, node);
            break;
      default:
#ifdef PROT_DONT_KNOW
            DEBUGF(__FILE__, __LINE__,
                  "don't know how to handle ip protocol 0x%X", node->data.ip.prot);
#endif
            break;
      }

      if (NULL != parent && (PROT_ETHER2 == parent->prot_id || PROT_IEEE_802_3 == parent->prot_id)) {
            report_ip_traffic(&parent->data.ether.src, &node->data.ip.src, 
                  &parent->data.ether.dest, &node->data.ip.dest);
      }
      
      return used;
}

/**
 * parse Internet Protocol v6
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note IPv6 looks like (RFC 2460):
 *
 */
size_t parse_ipv6(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < IPV6_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "ipv6 len %u invalid (%u:%u)",
                  len, IPV6_LEN_MIN, IPV6_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_IPV6);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.ipv6.version = (buf[used] & 0xF0);
      node->data.ipv6.traffic_class = ((buf[used] & 0xF) << 4) | ((buf[used + 1] & 0xF0) >> 4);
      used++;
      /* 20-bit flow label */
      node->data.ipv6.flow_label = ((buf[used] & 0xF) << 16) | (buf[used + 1] << 8) | buf[used + 2];
      used += 3;

      node->data.ipv6.payload_len = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.ipv6.next = buf[used++];
      node->data.ipv6.hop_limit = buf[used++];

      node->data.ipv6.src.version = IPV6;
      memcpy(node->data.ipv6.src.addr.v6, buf + used, 16);
      used += 16;

      node->data.ipv6.dest.version = IPV6;
      memcpy(node->data.ipv6.dest.addr.v6, buf + used, 16);
      used += 16;

      node->bytes = used;

      /* where to go next? */
      switch (node->data.ipv6.next) {
      case IPV6_NEXT_ICMPV6:
            used += parse_icmpv6(buf + used, len - used, node);
            break;
      default:
#ifdef PROT_DONT_KNOW
            DEBUGF(__FILE__, __LINE__,
                  "don't know how to handle next 0x%X", node->data.ipv6.next);
#endif
            /* fallthrough... */
      case IPV6_NEXT_NONE: /* do nothing */
            break;
      }
      
      return used;
}

/**
 * parse Internet Control Message Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note ICMP looks like (RFC 792?):
 *
 */
#if 0
static lhash_t ICMP_Echo_Req_Seq_By_Ip;
lhash_init_ez(&ICMP_Echo_Req_Seq_By_Ip, 256);
      lhash_set_key_func_cmp(&ICMP_Echo_Req_Seq_By_Ip, ip_cmp);
      lhash_set_key_funcs(&ICMP_Echo_Req_Seq_By_Ip, NULL, free);
      lhash_set_val_funcs(&ICMP_Echo_Req_Seq_By_Ip, NULL, free);
#endif
size_t parse_icmp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif  
      
      /* validate */
      if (len < ICMP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "icmp len %u invalid (%u:%u)",
                  len, ICMP_LEN_MIN, ICMP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_ICMP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.icmp.type = buf[used++];
      node->data.icmp.code = buf[used++];

      node->data.icmp.checksum = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      /* FIXME: enforce minimum length constraints */
      switch (node->data.icmp.type)
      {
      case ICMP_TYPE_UNREACH:
      case ICMP_TYPE_TIMEOUT:
      case ICMP_TYPE_SRC_QUENCH:
            memcpy(node->data.icmp.data.unused, buf + used, 4);
            used += 4;
            used += parse_ip(buf + used, len - used, node);
            break;
      case ICMP_TYPE_PARAM:
            node->data.icmp.data.param.offset = buf[used++];
            memcpy(node->data.icmp.data.param.unused, buf + used, 3);
            used += 3;
            used += parse_ip(buf + used, len - used, node);
            break;
      case ICMP_TYPE_REDIRECT:
            node->data.icmp.data.gateway.version = IPV4;
            memcpy(node->data.icmp.data.gateway.addr.v4, buf + used, 4);
            used += 4;
            used += parse_ip(buf + used, len - used, node);
            break;
      case ICMP_TYPE_ECHO_REQ:
      case ICMP_TYPE_ECHO_RESP:
            node->data.icmp.data.echo.id = (buf[used] << 8) | buf[used + 1];
            used += 2;
            node->data.icmp.data.echo.seq = (buf[used] << 8) | buf[used + 1];
            used += 2;

            /* TODO: record sequence number... windows' bytes are reversed */

            /* rest is assumed to be echo data */
            node->data.icmp.data.echo.data_len = len - used;
            node->data.icmp.data.echo.data = buf + used;
            used = len;

            ping_report(node);

            break;
      case ICMP_TYPE_TIMESTAMP_REQ:
      case ICMP_TYPE_TIMESTAMP_RESP:
            node->data.icmp.data.timestamp.orig = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
            used += 4;
            node->data.icmp.data.timestamp.recv = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
            used += 4;
            node->data.icmp.data.timestamp.trans = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
            used += 4;
            break;
      case ICMP_TYPE_INFO_REQ:
      case ICMP_TYPE_INFO_RESP:
            node->data.icmp.data.info.id = ((buf[used] << 8) | buf[used + 1]);
            used += 2;
            node->data.icmp.data.info.seq = ((buf[used] << 8) | buf[used + 1]);
            used += 2;
            break;
      default:
            break;
      }

      node->bytes = used;

      return used;
}

/**
 * parse Internet Group Message Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 */
size_t parse_igmp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif  
      
      /* validate */
      if (len < IGMP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "igmp len %u invalid (%u:%u)",
                  len, IGMP_LEN_MIN, IGMP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_IGMP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.igmp.version = 2; /* FIXME: how do we know what version of IGMP it is? */
      node->data.igmp.type = buf[used++];
      node->data.igmp.max_resp = buf[used++];

      node->data.igmp.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      memcpy(&node->data.igmp.multicast, buf + used, 4);
      used += 4;

      node->bytes = used;

      return used;
}



/**
 * parse Internet Control Management Protocol v6
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note ICMPv6 looks like (RFC ???):
 *
 */
size_t parse_icmpv6(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < ICMPV6_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "icmpv6 len %u invalid (%u:%u)",
                  len, ICMPV6_LEN_MIN, ICMPV6_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_ICMPV6);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.icmpv6.type = buf[used++];
      node->data.icmpv6.code = buf[used++];
      node->data.icmpv6.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      /* TODO: the rest */

      return used;
}

/**
 * parse User Datagram Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note UDP looks like:
 *
 */
size_t parse_udp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < UDP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "udp len %u invalid (%u:%u)",
                  len, UDP_LEN_MIN, UDP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_UDP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.udp.src_port = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.udp.dest_port = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.udp.len = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->data.udp.checksum = ((buf[used] << 8) | buf[used + 1]);
      used += 2;

      node->bytes = used;

      /* based on ports */
      /* TODO: there's got to be a cleaner and more efficient way than this... */

      if (PORT_NB_NS == node->data.udp.dest_port || PORT_NB_NS == node->data.udp.src_port) {
            used += parse_netbios_ns(buf + used, len - used, node);
      } else if (PORT_NB_DGM == node->data.udp.dest_port || PORT_NB_DGM == node->data.udp.src_port) {
            used += parse_netbios_dgm(buf + used, len - used, node);
      } else if (PORT_DNS == node->data.udp.dest_port || PORT_DNS == node->data.udp.src_port) {
            used += parse_dns(buf + used, len - used, node);
      } else if (PORT_SNMP == node->data.udp.dest_port || PORT_SNMP == node->data.udp.src_port) {
            used += parse_snmp(buf + used, len - used, node);
      } else if (PORT_TEREDO == node->data.udp.dest_port || PORT_TEREDO == node->data.udp.src_port) {
            used += parse_teredo(buf + used, len - used, node);
      } else if (PORT_CSTRIKE == node->data.udp.dest_port || PORT_CSTRIKE == node->data.udp.src_port
            || PORT_CSTRIKE_CLIENT == node->data.udp.dest_port || PORT_CSTRIKE_CLIENT == node->data.udp.src_port) {
                  used += parse_cstrike(buf + used, len - used, node);
      } else if (PORT_SSDP == node->data.udp.dest_port) {
                  used += parse_ssdp(buf + used, len - used, node);
      } else {
            /* protocols that are always to/from the same ports */
            switch ((node->data.udp.src_port << 8) | node->data.udp.dest_port) {
            default:
#ifdef PROT_DONT_KNOW
                  DEBUGF(__FILE__, __LINE__,
                        "src_port: %d, dest_port: %d, what to do?",
                        node->data.udp.src_port, node->data.udp.dest_port);
#endif
                  break;
            case ((PORT_BOOTPC << 8) | PORT_BOOTPS):
            case ((PORT_BOOTPS << 8) | PORT_BOOTPC): /* FIXME: is this right? */
                  used += parse_bootp(buf + used, len - used, node);
                  break;
            }
      }

      return used;
}

/**
 * translate a raw hostname in dns to human-readable name
 * @param orig pointer to entire dns record, needed for PTR records
 * @param origlen
 * @param raw 
 * @param rawlen 
 * @param buf 
 * @param buflen 
 * @return number of bytes consumed
 */
static size_t parse_dns_hostname(const u_char *orig, size_t origlen, const u_char *raw,
      size_t rawlen, char *buf, size_t buflen)
{
      size_t rawcurr, bufcurr;
      int is_ptr = 0;
#ifdef _DEBUG
      assert(NULL != orig);
      assert(NULL != raw);
      assert(NULL != buf);
#endif
      if (0 == buflen || rawlen < 2) /* we skip the first raw byte */
            return 0;
      buf[0] = '\0';
      if (0xC0 == raw[0]) {
            /* PTR record */
            raw = orig + raw[1];
            is_ptr = 1;
      }
      for (
            bufcurr = 0, rawcurr = 1;
            rawcurr < rawlen && bufcurr < buflen && '\0' != raw[rawcurr];
            rawcurr++, bufcurr++
      ) {
            switch (raw[rawcurr]) {
            default:
            case '\0':
                  buf[bufcurr] = raw[rawcurr];
                  break;
            case '\1':
            case '\2':
            case '\3':
            case '\4':
            case '\5':
            case '\6':
            case '\7':
            case '\x8':
            case '\x9':
                  buf[bufcurr] = '.';
                  break;
            }
      }
      if (!is_ptr && '\0' == raw[rawcurr]) {
            buf[bufcurr] = '\0';
            rawcurr++;
      }
      /* return number of raw bytes actually consumed */
      return (is_ptr ? 2 : rawcurr);
}

/**
 * parse Domain Name Service
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note DNS looks like:
 *
 */
size_t parse_dns(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;
      const u_char *origbuf = buf; /* needed for PTR records */
      size_t origlen = len;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < DNS_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "dns len %u invalid (%u:%u)",
                  len, DNS_LEN_MIN, DNS_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_DNS);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.dns.trans_id = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.dns.type = (buf[used] & 0x80) >> 7;
      node->data.dns.opcode = ((buf[used] & 0x78) >> 3);
      node->data.dns.auth = (buf[used] & 0x4) >> 2;
      node->data.dns.trunc = (buf[used] & 0x2) >> 1;
      node->data.dns.rec_des = buf[used] & 0x1;
      used++;

      node->data.dns.rec_avail = (buf[used] & 0x80) >> 7;
      node->data.dns.Z = (buf[used] & 0x40) >> 6;
      node->data.dns.ans_auth = (buf[used] & 0x20) >> 5;
      node->data.dns.unused = (buf[used] & 0x10) >> 4;
      node->data.dns.rec_des = (buf[used] & 0xF);
      used++;

      node->data.dns.q_cnt = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.dns.ans_rr_cnt = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.dns.auth_rr_cnt = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.dns.add_rr_cnt = (buf[used] << 8) | buf[used + 1];
      used += 2;

      do {
            int unsigned i;
            
            /* questions */
            for (i = 0; i < node->data.dns.q_cnt && used < len; i++) {
                  if (i >= sizeof node->data.dns.q / sizeof node->data.dns.q[0]) {
                        used = len;
                        break;
                  }
                  used += parse_dns_hostname(origbuf, origlen, buf + used, len - used,
                        node->data.dns.q[i].host, sizeof node->data.dns.q[i].host);
                  node->data.dns.q[i].type = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  node->data.dns.q[i].class = (buf[used] << 8) | buf[used + 1];
                  used += 2;
            }
            
            if (used >= len)
                  break;

      } while (0);

      do {
            int unsigned i;
            
            /* answer rr */
            for (i = 0; i < node->data.dns.ans_rr_cnt && used < len; i++) {
                  if (i >= sizeof node->data.dns.ans_rr / sizeof node->data.dns.ans_rr[0]) {
                        used = len;
                        break;
                  }
                  used += parse_dns_hostname(origbuf, origlen, buf + used, len - used,
                        node->data.dns.ans_rr[i].host, sizeof node->data.dns.ans_rr[i].host);
                  node->data.dns.ans_rr[i].type = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  node->data.dns.ans_rr[i].cl = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  node->data.dns.ans_rr[i].ttl = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
                  used += 2;
                  node->data.dns.ans_rr[i].datalen = (buf[used] << 8) | buf[used + 1];
                  used += 2;

                  /* FIXME: can these ever be non-default values? */
                  /* NOTE: argh, getting a liiiittle too deep */
                  switch (node->data.dns.ans_rr[i].type) {
                  case DNS_REC_TYPE_A:
                        if (4 != node->data.dns.ans_rr[i].datalen) {
                              DEBUGF(__FILE__, __LINE__, "ans_rr[%d] datalen: %u bytes...", i,
                                    node->data.dns.ans_rr[i].type);
                        } else {
                              node->data.dns.ans_rr[i].data.a.ip.version = IPV4;
                              if (used + 4 <= len)
                                    memcpy(node->data.dns.ans_rr[i].data.a.ip.addr.v4, buf + used, 4);
                              used += 4;
                        }
                        break;
                  case DNS_REC_TYPE_PTR:
#if 0
                        DEBUGF(__FILE__, __LINE__, "ans_rr ptr %d, parse_dns_hostname", i);
#endif
                        used += parse_dns_hostname(origbuf, origlen, buf + used, len + used,
                              node->data.dns.ans_rr[i].data.ptr.domain,
                              sizeof node->data.dns.ans_rr[i].data.ptr.domain);
                        break;
                  case DNS_REC_TYPE_HINFO:
                  {
                        u_char len = buf[used++];
                        memcpy(node->data.dns.ans_rr[i].data.hinfo.cpu, buf + used, len);
                        node->data.dns.ans_rr[i].data.hinfo.cpu[len] = '\0';
                        used += len;
                        len = buf[used++];
                        memcpy(node->data.dns.ans_rr[i].data.hinfo.os, buf + used, len);
                        node->data.dns.ans_rr[i].data.hinfo.os[len] = '\0';
                        used += len;
                  }
                        break;
                  case DNS_REC_TYPE_AAAA:
                        PROT_ASSERT(16 == node->data.dns.ans_rr[i].datalen, buf, len, used);
                        node->data.dns.ans_rr[i].data.a.ip.version = IPV6;
                        if (used + 16 <= len)
                              memcpy(node->data.dns.ans_rr[i].data.a.ip.addr.v6, buf + used, 16);
                        used += 16;
                        break;
                  default:
                        break;
                  }
            }
            
            if (used >= len)
                  break;

      } while (0);


      /* TODO: argh, write the rest! */
      node->data.dns.ans_rr_cnt = 0;
      node->data.dns.auth_rr_cnt = 0;
      node->data.dns.add_rr_cnt = 0;

      node->bytes = used;

      return used;
}

/**
 * parse Bootstrap Protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note BOOTP looks like:
 *
 */
size_t parse_bootp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < BOOTP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "bootp len %u invalid (%u:%u)",
                  len, BOOTP_LEN_MIN, BOOTP_LEN_MAX);
            return 0;
      }
      
      node = protonode_new(parent, PROT_BOOTP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.bootp.type = buf[used++];
      node->data.bootp.hw_type = buf[used++];
      node->data.bootp.hw_len = buf[used++];
      node->data.bootp.hops = buf[used++];

      node->data.bootp.trans_id = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;

      node->data.bootp.secs = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.bootp.flag_bcast = (buf[used] & 0x80) >> 7;
      node->data.bootp.flag_res = ((buf[used] & 0x7F) << 8) | buf[used + 1];
      used += 2;

      node->data.bootp.client_ip.version = IPV4;
      memcpy(node->data.bootp.client_ip.addr.v4, buf + used, 4);
      used += 4;

      node->data.bootp.your_ip.version = IPV4;
      memcpy(node->data.bootp.your_ip.addr.v4, buf + used, 4);
      used += 4;

      node->data.bootp.next_ip.version = IPV4;
      memcpy(node->data.bootp.next_ip.addr.v4, buf + used, 4);
      used += 4;

      node->data.bootp.relay_ip.version = IPV4;
      memcpy(node->data.bootp.relay_ip.addr.v4, buf + used, 4);
      used += 4;

      memcpy(node->data.bootp.client_mac.addr, buf + used, 6);
      used += 6;
      used += 10; /* unused... TODO: capture */

      (void)strlcpy(node->data.bootp.server_host, (char *)buf + used, sizeof node->data.bootp.server_host);
      used += 64;
      (void)strlcpy(node->data.bootp.boot_file, (char *)buf + used, sizeof node->data.bootp.boot_file);
      used += 128;

      node->data.bootp.magic_cookie = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;

      /* parse bootp options */
      {
            register int i;
            u_char opt, opt_len;
            size_t tmplen;

            for (i = 0; BOOTP_OPTS_END_BYTE != buf[used] && used + 1 < len; i++) {
                  opt = buf[used++];
                  opt_len = buf[used++];
                  node->data.bootp.opt_count++;
                  node->data.bootp.opts[i].type = opt;
                  if (opt_len + used >= len) {
                        DEBUGF(__FILE__, __LINE__,
                              "opt:%u, opt_len:%u, used:%u, len:%u, bailing...",
                              opt, opt_len, used, len);
                        break;
                  }
                  switch (opt) {
                  case BOOTP_OPT_HOSTNAME:
                        tmplen = MIN((size_t)opt_len, sizeof node->data.bootp.opts[i].data.hostname - 1);
                        memcpy(node->data.bootp.opts[i].data.hostname, buf + used, tmplen);
                        node->data.bootp.opts[i].data.hostname[tmplen + 1] = '\0';
                        break;
                  case BOOTP_OPT_VENDOR_CLASS:
                        tmplen = MIN((size_t)opt_len, sizeof node->data.bootp.opts[i].data.vendor_class - 1);
                        memcpy(node->data.bootp.opts[i].data.vendor_class, buf + used, tmplen);
                        node->data.bootp.opts[i].data.vendor_class[tmplen + 1] = '\0';
#ifdef _DEBUG
                        {
                              char mac_buf[MAC_ADDR_BUFLEN];
                              DEBUGF(__FILE__, __LINE__, "mac %s vendor class: \"%s\"",
                                    mac_addr_to_str(&node->data.bootp.client_mac, mac_buf, sizeof mac_buf),
                                    node->data.bootp.opts[i].data.vendor_class);
                        }
#endif
                        break;
                  case BOOTP_OPT_IP_REQ:
                        /* opt_len should be 4... */
                        tmplen = MIN((size_t)opt_len, 4);
                        node->data.bootp.opts[i].data.ip_req.version = IPV4;
                        memcpy(node->data.bootp.opts[i].data.ip_req.addr.v4, buf + used, tmplen);
                        break;
                  case BOOTP_OPT_SERVER_ID:
                        /* opt_len should be 4... */
                        tmplen = MIN((size_t)opt_len, 4);
                        node->data.bootp.opts[i].data.server_id.version = IPV4;
                        memcpy(node->data.bootp.opts[i].data.server_id.addr.v4, buf + used, tmplen);
                        break;
                  case BOOTP_OPT_MSG_TYPE:
                        /* opt_len should be 1... */
                        node->data.bootp.opts[i].data.msg_type = buf[used];
                        break;
                  case BOOTP_OPT_SUBNET_MASK:
                        /* opt_len should be 4... */
                        tmplen = MIN((size_t)opt_len, 4);
                        node->data.bootp.opts[i].data.subnet_mask.version = IPV4;
                        memcpy(node->data.bootp.opts[i].data.subnet_mask.addr.v4, buf + used, tmplen);
                        break;
                  case BOOTP_OPT_ROUTER:
                        /* opt_len should be a multiple of 4... */
                  {
                        int j;
                        for (j = i; opt_len > 0 && j < BOOTP_OPTS_MAX && BOOTP_OPTS_END_BYTE != buf[used] && used + 1 < len; j++) {
                              node->data.bootp.opt_count++;
                              node->data.bootp.opts[j].type = opt;
                              tmplen = MIN((size_t)opt_len, 4);
                              opt_len -= 4;
                              node->data.bootp.opts[j].data.router.version = IPV4;
                              memcpy(node->data.bootp.opts[j].data.router.addr.v4, buf + used, tmplen);
                              used += tmplen;
                              /* report */
                              report_ip_fact(&node->data.bootp.opts[j].data.router, FACT_ROLE_ROUTER);
                        }
                  }
                        break;
#if 0
                  case BOOTP_OPT_DNS_SERVER:
                  {
                        int i;
                        for (tmplen = opt_len; tmplen > 0; tmplen -= 4) {
                              node->data.bootp.opts[i].data.server_id.version = IPV4;
                              memcpy(node->data.bootp.opts[i].data.server_id.addr.v4, buf + used, 4);
                        }
                  }
                        break;
#endif
                  case BOOTP_OPT_DOMAIN:
                        tmplen = MIN((size_t)opt_len, sizeof node->data.bootp.opts[i].data.domain - 1);
                        memcpy(node->data.bootp.opts[i].data.domain, buf + used, tmplen);
                        node->data.bootp.opts[i].data.domain[tmplen + 1] = '\0';
                        break;

                  case BOOTP_OPT_TIME_RENEW:
                  case BOOTP_OPT_TIME_REBIND:
                  case BOOTP_OPT_LEASE_TIME:
                        node->data.bootp.opts[i].data.secs =
                              (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
                        break;

                  case BOOTP_OPT_PARAM_REQ_LIST:
                        node->data.bootp.opts[i].data.param_req_list.size = opt_len;
                        memcpy(node->data.bootp.opts[i].data.param_req_list.req, buf + used, opt_len);
                        break;

                  default:
                        DEBUGF(__FILE__, __LINE__,
                              "dunno opt #%u", opt, opt_len);
                        break;
                  }

                  used += opt_len;
            }

            /* consume end byte */
            if (BOOTP_OPTS_END_BYTE == buf[used])
                  used++;

      }

      node->bytes = used;


      return used;
}

/**
 * parse NETBIOS Name Service protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 */
size_t parse_netbios_ns(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      node = protonode_new(parent, PROT_NB_NS);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      node->bytes = used;

      return used;
}

/**
 * parse NETBIOS Datagram protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note NETBIOS DGM looks like:
 *
 */
size_t parse_netbios_dgm(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < NB_SSN_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "netbios_ssn len %u invalid (%u)",
                  len, NB_DGM_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_NB_DGM);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.nb_dgm.type = buf[used++];

      switch (node->data.nb_dgm.type) {
      case 0x11:
      node->data.nb_dgm.node_type = buf[used] & 0xFC;
      node->data.nb_dgm.first_frag = (buf[used] & 0x2) >> 1;
      node->data.nb_dgm.more_frag = buf[used] & 0x1;
      used++;

      node->data.nb_dgm.id = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.nb_dgm.src_ip.version = IPV4;
      memcpy(node->data.nb_dgm.src_ip.addr.v4, buf + used, 4);
      used += 4;

      node->data.nb_dgm.src_port = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.nb_dgm.len = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.nb_dgm.offset = (buf[used] << 8) | buf[used + 1];
      used += 2;

      /* FIXME: we do not rigorously check lengths... a malformed/constructed
       * packet will cause us to segfault */

      node->data.nb_dgm.src_name_len = buf[used++];
      memcpy(node->data.nb_dgm.src_name, buf + used,
            MIN((size_t)(node->data.nb_dgm.src_name_len + 1), sizeof node->data.nb_dgm.src_name));
      used += node->data.nb_dgm.src_name_len + 1;

      {
            int rd, wr;
            for (
                  rd = wr = 0;
                  /* stay short, stop at \0 and ' ', there will be extra spaces */
                  rd < node->data.nb_dgm.src_name_len && node->data.nb_dgm.src_name[rd] != '\0';
                  wr++, rd += 2
            ) {
                  node->data.nb_dgm.src_name[wr] =
                        ((node->data.nb_dgm.src_name[rd] - 'A') << 4) | (node->data.nb_dgm.src_name[rd + 1] - 'A');
                  if (' ' == node->data.nb_dgm.src_name[wr])
                        break;
            }
            node->data.nb_dgm.src_name[wr] = '\0';

            /* report */
            report_ip_hostname(&node->parent->parent->data.ip.src, node->data.nb_dgm.src_name);
      }

      node->data.nb_dgm.dest_name_len = buf[used++];
      memcpy(node->data.nb_dgm.dest_name, buf + used,
            MIN((size_t)(node->data.nb_dgm.dest_name_len + 1), sizeof node->data.nb_dgm.dest_name));
      used += node->data.nb_dgm.dest_name_len + 1;

      {
            int rd, wr;
            for (
                  rd = wr = 0;
                  /* stay short, stop at \0 and ' ', there will be extra spaces */
                  rd < node->data.nb_dgm.dest_name_len && node->data.nb_dgm.dest_name[rd] != '\0';
                  wr++, rd += 2
            ) {
                  node->data.nb_dgm.dest_name[wr] =
                        ((node->data.nb_dgm.dest_name[rd] - 'A') << 4) | (node->data.nb_dgm.dest_name[rd + 1] - 'A');
                  if (' ' == node->data.nb_dgm.dest_name[wr])
                        break;
            }
            node->data.nb_dgm.dest_name[wr] = '\0';
      }

      node->bytes = used;

      PROT_ASSERT(isalpha((unsigned char)node->data.nb_dgm.src_name[0]) && isalpha((unsigned char)node->data.nb_dgm.dest_name[0]),
            buf - used, used, used);

      default:
            break;
      }

      if (used < len)
            used += parse_smb(buf + used, len - used, node);

      return used;
}

/**
 * parse SMB protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note SMB looks like:
 *
 */
size_t parse_smb(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      node = protonode_new(parent, PROT_SMB);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;
      node->bytes = used;

      return used;
}

/**
 * parse SNMP protocol
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note SNMP looks like:
 *
 */
size_t parse_snmp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      node = protonode_new(parent, PROT_SNMP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;
      node->bytes = used;

      return used;
}

/**
 * parse Teredo tunneling
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note Teredo looks like:
 *
 */
size_t parse_teredo(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      if (len < TEREDO_LEN_MIN) {
            ERRF("teredo too short (%u < %u), cannot continue");
            return 0;
      }

      node = protonode_new(parent, PROT_TEREDO);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.teredo.zero_one = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.teredo.id_len = buf[used++];
      node->data.teredo.au_len = buf[used++];

      do { 
            if (len - used < node->data.teredo.id_len) {
                  DEBUGF(__FILE__, __LINE__, "teredo: id_len too long...");
                  used = len;
                  break;
            }
            memcpy(node->data.teredo.id, buf + used, node->data.teredo.id_len);
            used += node->data.teredo.id_len;
      
            if (len - used < node->data.teredo.id_len) {
                  DEBUGF(__FILE__, __LINE__, "teredo: au_len too long...");
                  used = len;
                  break;
            }
            memcpy(node->data.teredo.au, buf + used, node->data.teredo.au_len);
            used += node->data.teredo.au_len;

            if (len - used < sizeof node->data.teredo.nonce) {
                  DEBUGF(__FILE__, __LINE__, "teredo: not enough room for nonce...");
                  used = len;
                  break;
            }
            memcpy(node->data.teredo.nonce, buf + used, sizeof node->data.teredo.nonce);
            used += sizeof node->data.teredo.nonce;

            if (len - used < 1) {
                  DEBUGF(__FILE__, __LINE__, "teredo: not enough room for conf...");
                  used = len;
                  break;
            }
            node->data.teredo.conf = buf[used++];

            /* process optional origin indication */
            /* see http://www.ietf.org/internet-drafts/draft-huitema-v6ops-teredo-05.txt Section 5.1.1 */

            if (len - used < 8)
                  break;

            /* two following zeroes, which cannot occur as the first 2 bytes of IPv6 indicate
             * an Origin Indicator */
            if (0 != buf[used] || 0 != buf[used + 1])
                  break;
            used += 2;

            node->data.teredo.has_origin = 1;

            /* port and ipv4 addr obfuscated by XORed with FFs... */

            node->data.teredo.origin.port = ((buf[used] << 8) | buf[used + 1]) ^ 0xFFFF;
            used += 2;

            node->data.teredo.origin.ip.version = IPV4;
            node->data.teredo.origin.ip.addr.v4[0] = buf[used++] ^ 0xFF;
            node->data.teredo.origin.ip.addr.v4[1] = buf[used++] ^ 0xFF;
            node->data.teredo.origin.ip.addr.v4[2] = buf[used++] ^ 0xFF;
            node->data.teredo.origin.ip.addr.v4[3] = buf[used++] ^ 0xFF;

      } while (0);

      node->bytes = used;

      /* the whole point of Teredo is to encapsulate IPv6 over IPv4 UDP... so IPv6 is next... */
      if (used < len)
            used += parse_ipv6(buf + used, len - used, node);

      return used;
}

/**
 * parse Counter-Strike
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note CStrike looks like:
 *
 */
size_t parse_cstrike(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      node = protonode_new(parent, PROT_CSTRIKE);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      node->bytes = used;

      return used;
}

/**
 * parse SSDP
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note SSDP looks like:
 *
 * @note http://www.upnp.org/download/draft_cai_ssdp_v1_03.txt
 */
size_t parse_ssdp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      node = protonode_new(parent, PROT_SSDP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      node->bytes = used;

      /* just so we can dump it later... if we add logic to really parse it, remove this */
      node->data.unknown.len = used;
      node->data.unknown.data = buf;

      return used;
}

/**
 * parse TCP
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note TCP looks like:
 *
 */
size_t parse_tcp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;
      u_char optbyte;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < TCP_LEN_MIN) {
#ifdef ICMP_REDKIRECT_SENDS_LOTS_OF_SHORT_TCP_HEADERS
            DEBUGF(__FILE__, __LINE__, "tcp len %u invalid (%u)",
                  len, TCP_LEN_MIN);
#endif
            return 0;
      }
      
      node = protonode_new(parent, PROT_TCP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;


      node->data.tcp.src_port = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.tcp.dest_port = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.tcp.seqno = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;

      node->data.tcp.ackno = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;

      node->data.tcp.headlen = (buf[used] & 0xF0) >> 2;
      node->data.tcp.reserved = buf[used] & 0x0F;
      used++;

      optbyte = buf[used]; /* save raw byte for later */
      node->data.tcp.cwr = !!(buf[used] & 0x80);
      node->data.tcp.ecn = !!(buf[used] & 0x40);
      node->data.tcp.urg = !!(buf[used] & 0x20);
      node->data.tcp.ack = !!(buf[used] & 0x10);
      node->data.tcp.psh = !!(buf[used] & 0x08);
      node->data.tcp.rst = !!(buf[used] & 0x04);
      node->data.tcp.syn = !!(buf[used] & 0x02);
      node->data.tcp.fin = !!(buf[used] & 0x01);
      used++;

      node->data.tcp.window = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.tcp.checksum = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.tcp.urg_ptr = (buf[used] << 8) | buf[used + 1];
      used += 2;

      /* parse options */
      while (used < node->data.tcp.headlen) {
            node->data.tcp.opt[node->data.tcp.opts].type = buf[used];
            switch (buf[used++]) {
            case TCP_OPT_END:
            case TCP_OPT_NOP:
                  /* no data */
                  break;
            case TCP_OPT_MSS:
                  used++; /* skip len byte, should be 4 */
                  node->data.tcp.opt[node->data.tcp.opts].data.mss = (buf[used] << 8) | buf[used + 1];
                  used += 2;
                  break;
            case TCP_OPT_WS:
                  used++; /* skip len byte, should be 2 */
                  node->data.tcp.opt[node->data.tcp.opts].data.ws = buf[used++];
                  break;
            case TCP_OPT_SACK:
                  used++; /* skip len byte, should be 2 */
                  break;
            case TCP_OPT_TS:
                  used++; /* skip len byte, should be 10 */
                  node->data.tcp.opt[node->data.tcp.opts].data.ts.val = ntohl(*(uint32_t *)(buf + used));
                  used += 4;
                  node->data.tcp.opt[node->data.tcp.opts].data.ts.ecr = ntohl(*(uint32_t *)(buf + used));
                  used += 4;
                  break;
            default:
                  break;
            }
            ++node->data.tcp.opts;
      }

      node->bytes = used;

      if (used < len) {
            /* dest port */
            switch (node->data.tcp.dest_port) {
            case PORT_SMTP:
                  used += parse_smtp(buf + used, len - used, node);
                  break;
            case PORT_HTTP:
            case PORT_HTTP8080:
                  used += parse_http(buf + used, len - used, node);
                  break;
            case PORT_HTTPS:
                  used += parse_https(buf + used, len - used, node);
                  break;
            case PORT_RTSP:
                  used += parse_rtsp(buf + used, len - used, node);
                  break;
            case PORT_AIM:
                  used += parse_aim(buf + used, len - used, node);
                  break;
            case PORT_IRC:
                  used += parse_irc(buf + used, len - used, node);
                  break;
#if 0
            case PORT_NB_SSN:
                  used += parse_netbios_ssn(buf + used, len - used, node);
                  break;
#endif
            case PORT_GNUTELLA:
                  used += parse_gnutella(buf + used, len - used, node);
                  break;
            default:
                  /* src port */
                  switch (node->data.tcp.src_port) {
                  case PORT_SMTP:
                        used += parse_smtp(buf + used, len - used, node);
                        break;
                  case PORT_HTTP:
                  case PORT_HTTP8080:
                        used += parse_http(buf + used, len - used, node);
                        break;
                  case PORT_HTTPS:
                        used += parse_https(buf + used, len - used, node);
                        break;
                  case PORT_RTSP:
                        used += parse_rtsp(buf + used, len - used, node);
                        break;
                  case PORT_AIM:
                        used += parse_aim(buf + used, len - used, node);
                        break;
                  case PORT_IRC:
                        used += parse_irc(buf + used, len - used, node);
                        break;
#if 0
                  case PORT_NB_SSN:
                        used += parse_netbios_ssn(buf + used, len - used, node);
                        break;
#endif
                  case PORT_GNUTELLA:
                        used += parse_gnutella(buf + used, len - used, node);
                        break;
                  default:
                        break;
                  }
                  break;
            }
      }

      if (used < len) {
            node->data.tcp.payload = buf + used;
            node->data.tcp.payload_bytes = len - used;
            used = len;
      }

      return used;
}


/**
 * parse HTTP
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note HTTP looks like:
 *
 */
size_t parse_http(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < HTTP_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "http len %u invalid (%u)",
                  len, HTTP_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_HTTP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* record sender as web server */

      /* FIXME: */
#if 0
      /* FIXME: just searching for user agent currently */
      {
            const char *curr = buf;
            char *nl = strstr(curr, "\r\n");
            while (NULL != nl && len > 2) {
                  curr = nl + 2;
                  if (0 == strncmp(curr, "User-Agent: ", strlen("User-Agent: "))) {
                        char ua[256];
                        char *end;
                        curr = curr + strlen("User-Agent: ");
                        end = strstr(curr, "\r\n");
                        if (NULL == end)
                              end = strchr(curr, '\0');
                        strlcpy(ua, curr, (size_t)(end - curr + 1));
                        report_http_useragent(node, ua);
                  }
                  nl = strstr(curr, "\r\n");
            }
      }
#endif

      node->bytes = used;

      return used;
}

/**
 * parse HTTPS
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note HTTPS looks like:
 *
 */
size_t parse_https(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif
      
      node = protonode_new(parent, PROT_HTTPS);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      node->bytes = used;

      return used;
}

/**
 * parse SMTP
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note SMTP looks like:
 *
 */
size_t parse_smtp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_SMTP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}

/**
 * parse AIM 
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note AIM looks like:
 *
 */
size_t parse_aim(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_AIM);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}

/**
 * parse IRC 
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note IRC looks like:
 *
 */
size_t parse_irc(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_IRC);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}

/**
 * parse Gnutella
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note Gnutella looks like:
 *
 */
size_t parse_gnutella(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_GNUTELLA);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}


/**
 * parse RTSP
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note RTSP looks like:
 *
 */
size_t parse_rtsp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_RTSP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}


/**
 * parse GRE
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note GRE looks like:
 *
 */
size_t parse_gre(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* validate */
      if (len < GRE_LEN_MIN) {
            DEBUGF(__FILE__, __LINE__, "gre len %u invalid (%u)",
                  len, GRE_LEN_MIN);
            return 0;
      }
      
      node = protonode_new(parent, PROT_GRE);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      node->data.gre.checksum = buf[used] & 0x80;
      node->data.gre.routing= buf[used] & 0x40;
      node->data.gre.key= buf[used] & 0x20;
      node->data.gre.seqno = buf[used] & 0x10;
      node->data.gre.str_src_rt = buf[used] & 0x08;
      node->data.gre.rec_ctrl = buf[used] & 0x07;
      used++;

      node->data.gre.has_ack_no = buf[used] & 0x80;
      node->data.gre.flags = buf[used] & 0x78;
      node->data.gre.version = buf[used] & 0x08;
      used++;

      node->data.gre.prot_type = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.gre.payload = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.gre.call_id = (buf[used] << 8) | buf[used + 1];
      used += 2;

      node->data.gre.ack_no = (buf[used] << 24) | (buf[used + 1] << 16) | (buf[used + 2] << 8) | buf[used + 3];
      used += 4;

      switch (node->data.gre.prot_type) {
      case GRE_PROT_PPP:
            used += parse_ppp(buf + used, len - used, node);
            break;
      default:
            printf("unknown GRE type: 0x%02X\n", node->data.gre.prot_type);
            break;
      }

      return used;
}

/**
 * parse PPP 
 *
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 *
 * @note PPP looks like:
 *
 */
size_t parse_ppp(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      /* FIXME: validate */
      
      node = protonode_new(parent, PROT_PPP);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used = len;

      /* TODO: write! */

      node->bytes = used;

      return used;
}

/**
 * parse Symbol Wifi
 * Symbol has eth2 types 8780-8785, i see 8781 at work, denoting an access point
Fields:
      0x00-0x0f   16    Unknown
      0x10-0x13   4     Originating IP
      0x14-0x26   19    Unknown
      0x27-0x4c   38    SSID
      0x4d-0x5c   16    Unknown label (frequency range maybe?)
      0x5d-0x7c   32    Long name
 * @param buf
 * @param len
 * @param parent 'current' node, logical parent
 */
size_t parse_symbol_wifi(const u_char *buf, size_t len, protonode_t *parent)
{
      protonode_t *node;
      size_t used = 0;

#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != parent);
#endif

      if (len < 0x7C) {
            return 0;
      }
      
      /* we're not sure which eth we'll be */
      node = protonode_new(parent, PROT_SYMBOL_WIFI);
      if (NULL == node) {
            DEBUGF(__FILE__, __LINE__, "");
            return 0;
      }
      parent->child = node;

      used += 16; /* unknown */
      memcpy(&node->data.symw.ip.addr.v4, buf + used, 4); /* ip */
      used += 4;
      used += 20; /* unknown */
      memcpy(&node->data.symw.ssid, buf + used, 38); /* ssid */
      used += 38;
      memcpy(&node->data.symw.freq_descr, buf + used, 17); /* frequency descr */
      used += 17;
      memcpy(&node->data.symw.ap_descr, buf + used, 31); /* long ap name */
      used += 31;

      assert(PROT_ETHER2 == parent->prot_id);
      
      switch (len) {
      case 223: /* APs transmit 223 bytes */
            report_mac_fact(&parent->data.ether.src, FACT_ROLE_ACCESS_POINT);
            break;
      case 126: /* clients transmit shorter msgs (126 bytes) */
            report_mac_fact(&parent->data.ether.src, FACT_ROLE_WIFI_CLIENT);
            break;
      default:
            DEBUGF(__FILE__, __LINE__, "symbol wifi len %u?!", len);
            break;
      }

      used = len;
      node->bytes = used;

      return used;
}


/**
 * dump a protonode of any type to stdout, recursively
 */
void prot_dump(protonode_t *node)
{
      protonode_t *curr;
      size_t i;
      const struct prot_descr *descr;
      char indent[80] = "";

      for (curr = node, i = 0; NULL != curr; curr = curr->child, i++) {
            descr = Prot_Descr + curr->prot_id;
#ifdef _DEBUG
            printf("%s<%s (%d) %u bytes>\n",
                  indent, descr->tla, curr->prot_id, curr->bytes);
#else
            printf("%s<%s %lu bytes>\n",
                  indent, descr->tla, (long unsigned)curr->bytes);
#endif
            switch (curr->prot_id) {
            default:
#ifdef _DEBUG
                  PROGRAMMER_OOPS(__FILE__, __LINE__,
                        "why are you sending me protocol 0x%02X(%u)? i can't handle that",
                        curr->prot_id, curr->prot_id);
                  abort();
#endif
                  return;

            /* protocols we semi-process but don't properly dump... */
            case PROT_SMB:
            case PROT_SMTP:
            case PROT_SSDP:
            case PROT_GNUTELLA:
            case PROT_ICMPV6:
            /* fall-through... */
            case PROT_UNKNOWN: /* dump payload, anything past what we parse */
            case PROT_EXTRA_JUNK:
            {
                  size_t offset = 0, len;
                  len = (size_t)(Dump_Payload_Bytes >= 0 ? MIN(Dump_Payload_Bytes, (int)curr->data.unknown.len) : (int)curr->data.unknown.len);
                  while (offset < len) {
                        printf("%s", indent);
                        offset = bytes_dump_hex_line(curr->data.unknown.data, offset, len);
                  }
                  if (Dump_Payload_Bytes != 0 && offset < curr->data.unknown.len) /* note that we've chopped */
                        printf("%s!chopped at %u bytes\n", indent, Dump_Payload_Bytes);
            }
                  break;

            case PROT_LOGIC:
                  printf(
                        "%sframe: %u\n"
                        "%s  len: %u\n"
                        "%s time: %lu\n"
                        "%s type: %d (%s)\n",
                        indent, curr->data.logic.frame,
                        indent, curr->data.logic.len,
                        indent, curr->data.logic.when,
                        indent, curr->data.logic.type, pcap_datalink_val_to_name(curr->data.logic.type));
                  break;

            case PROT_LINUX_SLL:
            {
                  printf(
                        "%s    type: 0x%04X\n"
                        "%sdev_type: 0x%02X\n"
                        "%s  ll_len: %u bytes\n"
                        "%s ll_type: 0x%04X\n"
                        "%s ll_addr: %02X %02X %02X %02X %02X %02X %02X %02X\n"
                        "%seth_type: 0x%04X\n",
                        indent, curr->data.linux_sll.type,
                        indent, curr->data.linux_sll.dev_type,
                        indent, curr->data.linux_sll.ll_len,
                        indent, curr->data.linux_sll.ll_type,
                        indent,
                              curr->data.linux_sll.ll_addr[0],
                              curr->data.linux_sll.ll_addr[1],
                              curr->data.linux_sll.ll_addr[2],
                              curr->data.linux_sll.ll_addr[3],
                              curr->data.linux_sll.ll_addr[4],
                              curr->data.linux_sll.ll_addr[5],
                              curr->data.linux_sll.ll_addr[6],
                              curr->data.linux_sll.ll_addr[7],
                        indent, curr->data.linux_sll.ethernet_type
                  );
            }
            break;

            /* some kind of ethernet, they use the same data structure and are almost
             * identical */
            case PROT_ETHER2:
            case PROT_IEEE_802_3:
            {
                  char src_buf[MAC_ADDR_BUFLEN];
                  char dest_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s src: %s\n"
                        "%sdest: %s\n",
                        indent, mac_addr_to_str(&curr->data.ether.src, src_buf, sizeof src_buf),
                        indent, mac_addr_to_str(&curr->data.ether.dest, dest_buf, sizeof dest_buf));
                  if (PROT_ETHER2 == curr->prot_id) {
                        printf(
                              "%stype: 0x%04X\n",
                              indent, curr->data.ether.data.type);
                  } else if (PROT_IEEE_802_3 == curr->prot_id) {
                        printf(
                              "%s len: %u bytes\n",
                              indent, curr->data.ether.data.type);
                  }

                  if (curr->data.ether.pad_len > 0) {
                        size_t offset = 0;
                        printf("%spad_len: %d\n",
                              indent, curr->data.ether.pad_len);
                        while (offset < curr->data.ether.pad_len) {
                              printf("%s", indent);
                              offset = bytes_dump_hex_line(curr->data.ether.pad_bytes, offset,
                                    curr->data.ether.pad_len);
                        }
                  }
            }
                  break;

            case PROT_ARP:
            {
                  char src_mac_buf[MAC_ADDR_BUFLEN];
                  char src_ip_buf[IP_ADDR_BUFLEN];
                  char dest_mac_buf[MAC_ADDR_BUFLEN];
                  char dest_ip_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%s    hw: 0x%04X\n"
                        "%s  prot: 0x%04X\n"
                        "%shwsize: %d\n"
                        "%sprotsz: %d\n"
                        "%sopcode: 0x%04X\n"
                        "%ssrcmac: %s (%s)\n"
                        "%s srcip: %s\n"
                        "%sdstmac: %s (%s)\n"
                        "%s dstip: %s\n",
                        indent, curr->data.arp.hw,
                        indent, curr->data.arp.prot,
                        indent, curr->data.arp.hw_size,
                        indent, curr->data.arp.prot_size,
                        indent, curr->data.arp.opcode,
                        indent, mac_addr_to_str(&curr->data.arp.src_mac, src_mac_buf, sizeof src_mac_buf),
                               mac_addr_vend_name(&curr->data.arp.src_mac),
                        indent, ip_addr_to_str(&curr->data.arp.src_ip, src_ip_buf, sizeof src_ip_buf),
                        indent, mac_addr_to_str(&curr->data.arp.dest_mac, dest_mac_buf, sizeof dest_mac_buf),
                               mac_addr_vend_name(&curr->data.arp.dest_mac),
                        indent, ip_addr_to_str(&curr->data.arp.dest_ip, dest_ip_buf, sizeof dest_ip_buf));
            }
                  break;

            case PROT_LLC:
                  printf(
                        "%s  dsap: 0x%02X (ig: %u, addr: 0x%02X)\n"
                        "%s  ssap: 0x%02X (cr: %u, addr: 0x%02X)\n"
                        "%s   cmd: 0x%06X\n"
                        "%sfrmtyp: 0x%02X\n",
                        indent, curr->data.llc.dsap.whole,
                              curr->data.llc.dsap.ig,
                              curr->data.llc.dsap.addr,
                        indent, curr->data.llc.ssap.whole,
                              curr->data.llc.ssap.cr,
                              curr->data.llc.ssap.addr,
                        indent, curr->data.llc.cmd,
                        indent, curr->data.llc.frame_type);
                  /* extra SNAP info */
                  if (LLC_DSAP_SNAP == curr->data.llc.dsap.whole) {
                        printf(
                              "%sorg_id: 0x%06X\n"
                              "%s   pid: 0x%04X\n",
                              indent, curr->data.llc.data.snap.org_id,
                              indent, curr->data.llc.data.snap.pid);
                  }
                  break;
            case PROT_CDP:
                  printf(
                        "%sprotver: %d\n"
                        "%s    ttl: %d\n"
                        "%s chksum: 0x%04X\n"
                        "%s dev_id: %s\n"
                        "%s   port: %s\n"
                        "%sversion: %s\n"
                        "%splatfrm: %s\n"
                        "%s router: %d, trans_br: %d, srcrt_br: %d, switch: %d, host: %d, igmp: %d, repeater: %d, unused: 0x%08X\n",
                        indent, curr->data.cdp.version,
                        indent, curr->data.cdp.ttl,
                        indent, curr->data.cdp.checksum,
                        indent, curr->data.cdp.dev_id,
                        indent, curr->data.cdp.port,
                        indent, curr->data.cdp.version_id,
                        indent, curr->data.cdp.platform,
                        indent,
                              curr->data.cdp.capab_router,
                              curr->data.cdp.capab_trans_bridge,
                              curr->data.cdp.capab_srcrt_bridge,
                              curr->data.cdp.capab_switch,
                              curr->data.cdp.capab_host,
                              curr->data.cdp.capab_igmp,
                              curr->data.cdp.capab_repeater,
                              curr->data.cdp.capab_unused);
                  break;
            case PROT_STP:
            {
                  char root_mac_buf[MAC_ADDR_BUFLEN];
                  char bridge_mac_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s   prot_id: 0x%04X\n"
                        "%s  prot_ver: 0x%X\n"
                        "%s bdpu_type: 0x%X\n"
                        "%s     flags: topo_chg_ack: %u, unused: 0x%02X, topo_chg: %u\n"
                        "%s  root_num: 0x%04X\n"
                        "%s  root_mac: %s (%s)\n"
                        "%s path_cost: %u\n"
                        "%sbridge_num: 0x%04X\n"
                        "%sbridge_mac: %s (%s)\n"
                        "%s   port_id: 0x%04X (%u)\n"
                        "%s   msg_age: 0x%04X (%u seconds)\n"
                        "%s   max_age: 0x%04X (%u seconds)\n"
                        "%shello_time: 0x%04X (%u seconds)\n"
                        "%s fwd_delay: 0x%04X (%u seconds)\n",
                        indent, curr->data.stp.prot_id,
                        indent, curr->data.stp.prot_ver,
                        indent, curr->data.stp.bdpu_type,
                        indent,
                              curr->data.stp.topo_chg_ack,
                              curr->data.stp.flag_unused,
                              curr->data.stp.topo_chg,
                        indent, curr->data.stp.root_id.num,
                        indent, mac_addr_to_str(&curr->data.stp.root_id.mac, root_mac_buf, sizeof root_mac_buf),
                               mac_addr_vend_name(&curr->data.stp.root_id.mac),
                        indent, curr->data.stp.path_cost,
                        indent, curr->data.stp.bridge_id.num,
                        indent, mac_addr_to_str(&curr->data.stp.bridge_id.mac, bridge_mac_buf, sizeof bridge_mac_buf),
                               mac_addr_vend_name(&curr->data.stp.bridge_id.mac),
                        indent, curr->data.stp.port_id, curr->data.stp.port_id,
                        indent, curr->data.stp.msg_age, curr->data.stp.msg_age,
                        indent, curr->data.stp.max_age, curr->data.stp.max_age,
                        indent, curr->data.stp.hello_time, curr->data.stp.hello_time,
                        indent, curr->data.stp.fwd_delay, curr->data.stp.fwd_delay);
            }
                  break;

            case PROT_AARP:
            {
                  char src_mac_buf[MAC_ADDR_BUFLEN];
                  char src_ip_buf[IP_ADDR_BUFLEN];
                  char dest_mac_buf[MAC_ADDR_BUFLEN];
                  char dest_ip_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%s     hw: 0x%04X\n"
                        "%s   prot: 0x%04X\n"
                        "%s  hw_sz: %d\n"
                        "%sprot_sz: %d\n"
                        "%s opcode: 0x%04X\n"
                        "%ssrc_mac: %s (%s)\n"
                        "%s src_ip: %s\n"
                        "%sdst_mac: %s (%s)\n"
                        "%s dst_ip: %s\n",
                        indent, curr->data.aarp.hw,
                        indent, curr->data.aarp.prot,
                        indent, curr->data.aarp.hw_size,
                        indent, curr->data.aarp.prot_size,
                        indent, curr->data.aarp.opcode,
                        indent, mac_addr_to_str(&curr->data.aarp.src_mac, src_mac_buf, sizeof src_mac_buf),
                               mac_addr_vend_name(&curr->data.aarp.src_mac),
                        indent, ip_addr_to_str(&curr->data.aarp.src_ip, src_ip_buf, sizeof src_ip_buf),
                        indent, mac_addr_to_str(&curr->data.aarp.dest_mac, dest_mac_buf, sizeof dest_mac_buf),
                               mac_addr_vend_name(&curr->data.aarp.dest_mac),
                        indent, ip_addr_to_str(&curr->data.aarp.dest_ip, dest_ip_buf, sizeof dest_ip_buf));
            }
                  break;

            case PROT_IPX:
            {
                  char dest_node_buf[MAC_ADDR_BUFLEN];
                  char src_node_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s chksum: 0x%04X\n"
                        "%s    len: %u bytes\n"
                        "%s   hops: %u\n"
                        "%s   type: %u\n"
                        "%sdst_net: 0x%08X\n"
                        "%sdstnode: %s\n"
                        "%sdstsock: 0x%04X\n"
                        "%s srcnet: 0x%08X\n"
                        "%ssrcnode: %s\n"
                        "%ssrcsock: 0x%04X\n",
                        indent, curr->data.ipx.checksum,
                        indent, curr->data.ipx.len,
                        indent, curr->data.ipx.hops,
                        indent, curr->data.ipx.type,
                        indent, curr->data.ipx.dest.net,
                        indent, mac_addr_to_str(&curr->data.ipx.dest.node, dest_node_buf, sizeof dest_node_buf),
                        indent, curr->data.ipx.dest.socket,
                        indent, curr->data.ipx.src.net,
                        indent, mac_addr_to_str(&curr->data.ipx.src.node, src_node_buf, sizeof src_node_buf),
                        indent, curr->data.ipx.src.socket);
            }
                  break;

            case PROT_IP:
            {
                  char src_buf[IP_ADDR_BUFLEN];
                  char dest_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%sversion: %d\n"
                        "%sheadlen: %d bytes\n"
                        "%s   prec: %d, lowdelay: %d, throughput: %d, reliable: %d ect: %d, ece: %d\n"
                        "%s    len: %d\n"
                        "%s     id: 0x%04X\n"
                        "%s    res: 0x%X, dontfrag: 0x%X, morefrag: 0x%X\n"
                        "%sfragoff: %d\n"
                        "%s    ttl: %d\n"
                        "%s   prot: 0x%02X\n"
                        "%s chksum: 0x%04X\n"
                        "%s    src: %s\n"
                        "%s   dest: %s\n",
                        indent, curr->data.ip.version,
                        indent, curr->data.ip.headlen,
                        indent,
                              curr->data.ip.prec,
                              curr->data.ip.lowdelay,
                              curr->data.ip.throughput,
                              curr->data.ip.reliable,
                              curr->data.ip.ect,
                              curr->data.ip.ece,
                        indent, curr->data.ip.len,
                        indent, curr->data.ip.id,
                        indent,
                              curr->data.ip.res,
                              curr->data.ip.dontfrag,
                              curr->data.ip.morefrag,
                        indent, curr->data.ip.fragoff,
                        indent, curr->data.ip.ttl,
                        indent, curr->data.ip.prot,
                        indent, curr->data.ip.checksum,
                        indent, ip_addr_to_str(&curr->data.ip.src, src_buf, sizeof src_buf),
                        indent, ip_addr_to_str(&curr->data.ip.dest, dest_buf, sizeof dest_buf));
            }
                  break;

            case PROT_IPV6:
            {
                  char src_buf[IP_ADDR_BUFLEN];
                  char dest_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%sversion: %d\n"
                        "%straf_cl: %d\n"
                        "%sflowlbl: 0x%5X\n"
                        "%spayload: %d bytes\n"
                        "%s   next: 0x%02X\n"
                        "%s    ttl: 0x%02X\n"
                        "%s    src: %s\n"
                        "%s   dest: %s\n",
                        indent, curr->data.ipv6.version,
                        indent, curr->data.ipv6.traffic_class,
                        indent, curr->data.ipv6.flow_label,
                        indent, curr->data.ipv6.payload_len,
                        indent, curr->data.ipv6.next,
                        indent, curr->data.ipv6.hop_limit,
                        indent, ip_addr_to_str(&curr->data.ipv6.src, src_buf, sizeof src_buf),
                        indent, ip_addr_to_str(&curr->data.ipv6.dest, dest_buf, sizeof dest_buf));
            }
                  break;
            case PROT_ICMP:
                  printf(
                        "%s  type: 0x%02X (%s)\n"
                        "%s  code: 0x%02X\n"
                        "%schksum: 0x%04X\n",
                        indent, curr->data.icmp.type, icmp_type_to_str(curr->data.icmp.type),
                        indent, curr->data.icmp.code,
                        indent, curr->data.icmp.checksum);

                  switch (curr->data.icmp.type) {
                  case ICMP_TYPE_UNREACH:
                  case ICMP_TYPE_TIMEOUT:
                  case ICMP_TYPE_SRC_QUENCH:
                        printf(
                              "%s unused: %02X %02X %02X %02X\n",
                              indent,
                                    curr->data.icmp.data.unused[0],
                                    curr->data.icmp.data.unused[1],
                                    curr->data.icmp.data.unused[2],
                                    curr->data.icmp.data.unused[3]);
                        break;
                  case ICMP_TYPE_PARAM:
                        printf(
                              "%s offset: %u\n"
                              "%s unused: %02X %02X %02X\n",
                              indent, curr->data.icmp.data.param.offset,
                              indent,
                                    curr->data.icmp.data.param.unused[0],
                                    curr->data.icmp.data.param.unused[1],
                                    curr->data.icmp.data.param.unused[2]);
                        break;
                  case ICMP_TYPE_REDIRECT:
                  {
                        char gateway_buf[IP_ADDR_BUFLEN];
                        printf(
                              "%sgateway: %s\n",
                              indent, ip_addr_to_str(&curr->data.icmp.data.gateway,
                                    gateway_buf, sizeof gateway_buf));
                  }
                        break;
                  case ICMP_TYPE_ECHO_REQ:
                  case ICMP_TYPE_ECHO_RESP:
                        printf(
                              "%s    id: 0x%04X\n"
                              "%s   seq: 0x%04X\n"
                              "%s  data: %u bytes\n",
                              indent, curr->data.icmp.data.echo.id,
                              indent, curr->data.icmp.data.echo.seq,
                              indent, (unsigned int) curr->data.icmp.data.echo.data_len);
                  {
                        size_t offset = 0;
                        while (offset < curr->data.icmp.data.echo.data_len) {
                              printf("%s", indent);
                              offset = bytes_dump_hex_line(curr->data.icmp.data.echo.data, offset,
                                    curr->data.icmp.data.echo.data_len);
                        }
                  }
                        break;
                  case ICMP_TYPE_TIMESTAMP_REQ:
                  case ICMP_TYPE_TIMESTAMP_RESP:
                        printf(
                              "%s   orig: %u (%s)\n"
                              "%s   recv: %u (%s)\n"
                              "%s  trans: %u (%s)\n",
                              indent, curr->data.icmp.data.timestamp.orig, "",
                              indent, curr->data.icmp.data.timestamp.recv, "",
                              indent, curr->data.icmp.data.timestamp.trans, "");
                        break;
                  case ICMP_TYPE_INFO_REQ:
                  case ICMP_TYPE_INFO_RESP:
                        printf(
                              "%s     id: 0x%04X\n"
                              "%s    seq: 0x%04X\n",
                              indent, curr->data.icmp.data.echo.id,
                              indent, curr->data.icmp.data.echo.seq);
                        break;
                  default:
                        break;
                  }

                  break;

            case PROT_IGMP:
            {
                  char ip_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%s  version: %d\n"
                        "%s     type: 0x%02X\n"
                        "%s max_resp: %d (FIXME: wrong!)\n" /* not calculated at all... */
                        "%s checksum: 0x%04X\n"
                        "%smulticast: %s\n",
                        indent, curr->data.igmp.version,
                        indent, curr->data.igmp.type,
                        indent, curr->data.igmp.max_resp,
                        indent, curr->data.igmp.checksum,
                        indent, ip_addr_to_str(&curr->data.igmp.multicast, ip_buf, sizeof ip_buf));
            }
                  break;

            case PROT_UDP:
            {
                  char src_port_buf[PORT_DESCR_BUFLEN];
                  char dest_port_buf[PORT_DESCR_BUFLEN];
                  printf(
                        "%ssrcport: %d (%s)\n"
                        "%sdstport: %d (%s)\n"
                        "%s    len: %d\n"
                        "%s chksum: 0x%04X\n",
                        indent,
                              curr->data.udp.src_port,
                              port_descr(curr->data.udp.src_port, src_port_buf, sizeof src_port_buf),
                        indent,
                              curr->data.udp.dest_port,
                              port_descr(curr->data.udp.dest_port, dest_port_buf, sizeof dest_port_buf),
                        indent, curr->data.udp.len,
                        indent, curr->data.udp.checksum);
            }
                  break;

            case PROT_BOOTP:
            {
                  char client_ip_buf[IP_ADDR_BUFLEN];
                  char your_ip_buf[IP_ADDR_BUFLEN];
                  char next_ip_buf[IP_ADDR_BUFLEN];
                  char relay_ip_buf[IP_ADDR_BUFLEN];
                  char client_mac_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s        type: 0x%02X\n"
                        "%s     hw_type: 0x%02X\n"
                        "%s      hw_len: %u\n"
                        "%s        hops: %u\n"
                        "%s    trans_id: 0x%08X\n"
                        "%s        secs: %u\n"
                        "%s  flag_bcast: 0x%X\n"
                        "%s    flag_res: 0x%04X\n"
                        "%s   client_ip: %s\n"
                        "%s     your_ip: %s\n"
                        "%s     next_ip: %s\n"
                        "%s    relay_ip: %s\n"
                        "%s  client_mac: %s (%s)\n"
                        "%s server_host: %s\n"
                        "%s   boot_file: %s\n"
                        "%smagic_cookie: 0x%04X\n"
                        "%s     options: %u\n",
                        indent, curr->data.bootp.type,
                        indent, curr->data.bootp.hw_type,
                        indent, curr->data.bootp.hw_len,
                        indent, curr->data.bootp.hops,
                        indent, curr->data.bootp.trans_id,
                        indent, curr->data.bootp.secs,
                        indent, curr->data.bootp.flag_bcast,
                        indent, curr->data.bootp.flag_res,
                        indent, ip_addr_to_str(&curr->data.bootp.client_ip, client_ip_buf, sizeof client_ip_buf),
                        indent, ip_addr_to_str(&curr->data.bootp.your_ip, your_ip_buf, sizeof your_ip_buf),
                        indent, ip_addr_to_str(&curr->data.bootp.next_ip, next_ip_buf, sizeof next_ip_buf),
                        indent, ip_addr_to_str(&curr->data.bootp.relay_ip, relay_ip_buf, sizeof relay_ip_buf),
                        indent, mac_addr_to_str(&curr->data.bootp.client_mac, client_mac_buf, sizeof client_mac_buf),
                               mac_addr_vend_name(&curr->data.bootp.client_mac),
                        indent, curr->data.bootp.server_host,
                        indent, curr->data.bootp.boot_file,
                        indent, curr->data.bootp.magic_cookie,
                        indent, curr->data.bootp.opt_count
                  );

                  /* FIXME: we need to sanitize the strings before we print them, this is a vulnerability! */
                  {
                        u_char i;
                        char ip_buf[IP_ADDR_BUFLEN];
                        for (i = 0; i < curr->data.bootp.opt_count; i++) {
                              switch (curr->data.bootp.opts[i].type) {
                              default:
                                    printf("%s    opt 0x%02X: ???\n",
                                          indent, curr->data.bootp.opts[i].type);
                                    break;
                              case BOOTP_OPT_HOSTNAME:
                                    printf("%s    hostname: %s\n",
                                          indent, curr->data.bootp.opts[i].data.hostname);
                                    break;
                              case BOOTP_OPT_VENDOR_CLASS:
                                    printf("%svendor_class: \"%s\"\n",
                                          indent, curr->data.bootp.opts[i].data.vendor_class);
                                    break;
                              case BOOTP_OPT_IP_REQ:
                                    printf("%s      ip_req: %s\n",
                                          indent, ip_addr_to_str(&curr->data.bootp.opts[i].data.ip_req, ip_buf, sizeof ip_buf));
                                    break;
                              case BOOTP_OPT_SERVER_ID:
                                    printf("%s   server_id: %s\n",
                                          indent, ip_addr_to_str(&curr->data.bootp.opts[i].data.server_id, ip_buf, sizeof ip_buf));
                                    break;
                              case BOOTP_OPT_MSG_TYPE:
                                    printf("%s    msg_type: 0x%02X\n",
                                          indent, curr->data.bootp.opts[i].data.msg_type);
                                    break;
                              case BOOTP_OPT_SUBNET_MASK:
                                    printf("%s subnet_mask: %s\n",
                                          indent, ip_addr_to_str(&curr->data.bootp.opts[i].data.subnet_mask, ip_buf, sizeof ip_buf));
                                    break;
                              case BOOTP_OPT_ROUTER:
                                    printf("%s      router: %s\n",
                                          indent, ip_addr_to_str(&curr->data.bootp.opts[i].data.router, ip_buf, sizeof ip_buf));
                                    break;
                              case BOOTP_OPT_DOMAIN:
                                    printf("%s      domain: %s\n",
                                          indent, curr->data.bootp.opts[i].data.domain);
                                    break;
                              case BOOTP_OPT_TIME_RENEW:
                                    printf("%srenewal time: %u\n",
                                          indent, curr->data.bootp.opts[i].data.secs);
                                    break;
                              case BOOTP_OPT_TIME_REBIND:
                                    printf("%s rebind time: %u\n",
                                          indent, curr->data.bootp.opts[i].data.secs);
                                    break;
                              case BOOTP_OPT_PARAM_REQ_LIST:
                                    printf("%s  param reqs: %d items\n",
                                          indent, curr->data.bootp.opts[i].data.param_req_list.size);
                              {
                                    u_char j;
                                    for (j = 0; j < curr->data.bootp.opts[i].data.param_req_list.size; j++)
                                          printf("%s   [%02d] 0x%02X:\n", indent, j,
                                                curr->data.bootp.opts[i].data.param_req_list.req[j]);
                              }
                                    break;
                              } /* switch */
                        } /* for */
                  } /* opt scope */
            } /* case scope */
                  break;

            case PROT_DNS:
                  printf(
                        "%s    id: 0x%04X\n"
                        "%s  type: 0x%02X\n"
                        "%sopcode: 0x%04X\n"
                        "%s  type:%u, opcode:0x%X, auth:%u, trunc:%u, rec_des:%u, rec_avail:%u, Z:%u, ans_auth:%u, reply:0x%X\n",
                        indent, curr->data.dns.trans_id,
                        indent, curr->data.dns.type,
                        indent, curr->data.dns.opcode,
                        indent,
                              curr->data.dns.type,
                              curr->data.dns.opcode,
                              curr->data.dns.auth,
                              curr->data.dns.trunc,
                              curr->data.dns.rec_des,
                              curr->data.dns.rec_avail,
                              curr->data.dns.Z,
                              curr->data.dns.ans_auth,
                              curr->data.dns.reply
                  );

                  {
                        int unsigned i;

                        printf("%squeries: %u\n", indent, curr->data.dns.q_cnt);
                        for (i = 0; i < curr->data.dns.q_cnt; i++) {
                              printf("%s[%u] name: \"%s\", type: 0x%04X, class: 0x%04X\n",
                                    indent, i,
                                    curr->data.dns.q[i].host,
                                    curr->data.dns.q[i].type,
                                    curr->data.dns.q[i].class);
                        }

                        printf("%sanswers: %u\n", indent, curr->data.dns.ans_rr_cnt);
                        for (i = 0; i < curr->data.dns.ans_rr_cnt; i++) {
                              printf("%s[%u] %s type:%04X, class:%04X, ttl:%d, datalen:%u\n", indent, i,
                                    curr->data.dns.ans_rr[i].host,
                                    curr->data.dns.ans_rr[i].type,
                                    curr->data.dns.ans_rr[i].cl,
                                    curr->data.dns.ans_rr[i].ttl,
                                    curr->data.dns.ans_rr[i].datalen);
                        }

                        printf("%sauthoritative nameservers: %u\n", indent, curr->data.dns.auth_rr_cnt);
                        for (i = 0; i < curr->data.dns.auth_rr_cnt; i++) {
                              printf("%s[%u]\n", indent, i);
                        }

                        printf("%sadditional: %u\n", indent, curr->data.dns.add_rr_cnt);
                        for (i = 0; i < curr->data.dns.add_rr_cnt; i++) {
                              printf("%s[%u]\n", indent, i);
                        }
                  }

                  break; /* end PROT_DNS */

            case PROT_NB_DGM:
            {
                  char ip_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%s      type: 0x%02X\n"
                        "%s node_type: 0x%02X\n"
                        "%sfirst_frag: 0x%X\n"
                        "%smore_frags: 0x%X\n"
                        "%s        id: 0x%04X\n"
                        "%s    src_ip: %s\n"
                        "%s  src_port: 0x%02X\n"
                        "%s       len: 0x%02X\n"
                        "%s    offset: 0x%02X\n"
                        "%s  src_name: \"%s\"\n"
                        "%s dest_name: \"%s\"\n",
                        indent, curr->data.nb_dgm.type,
                        indent, curr->data.nb_dgm.node_type,
                        indent, curr->data.nb_dgm.first_frag,
                        indent, curr->data.nb_dgm.more_frag,
                        indent, curr->data.nb_dgm.id,
                        indent, ip_addr_to_str(&curr->data.nb_dgm.src_ip, ip_buf, sizeof ip_buf),
                        indent, curr->data.nb_dgm.src_port,
                        indent, curr->data.nb_dgm.len,
                        indent, curr->data.nb_dgm.offset,
                        /* FIXME: escape unprintable chars */
                        indent, curr->data.nb_dgm.src_name,
                        indent, curr->data.nb_dgm.dest_name
                  );
            }
                  break;

            case PROT_DDP:
                  printf(
                        "%s    hops: %u\n"
                        "%s     len: %u bytes\n"
                        "%s  chksum: 0x%04X\n"
                        "%s  dstnet: %u\n"
                        "%s  srcnet: %u\n"
                        "%s dstnode: %u\n"
                        "%s srcnode: %u\n"
                        "%s dstsock: %u\n"
                        "%s srcsock: %u\n"
                        "%sprottype: 0x%02X\n",
                        indent, curr->data.ddp.hops,
                        indent, curr->data.ddp.len,
                        indent, curr->data.ddp.checksum,
                        indent, curr->data.ddp.dest_net,
                        indent, curr->data.ddp.src_net,
                        indent, curr->data.ddp.dest_node,
                        indent, curr->data.ddp.src_node,
                        indent, curr->data.ddp.dest_socket,
                        indent, curr->data.ddp.src_socket,
                        indent, curr->data.ddp.prot_type
                  );
                  break;

            case PROT_SAP:
            {
                  char mac_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s       code: 0x%04X\n"
                        "%s       type: 0x%04X\n"
                        "%s       name: %s\n"
                        "%s    network: 0x%08X\n"
                        "%s       node: %s\n"
                        "%s     socket: 0x%04X\n"
                        "%sintrmd_nets: 0x%04X\n",
                        indent, curr->data.sap.code,
                        indent, curr->data.sap.server.type,
                        indent, curr->data.sap.server.name,
                        indent, curr->data.sap.server.network,
                        indent, mac_addr_to_str(&curr->data.sap.server.node, mac_buf, sizeof mac_buf),
                        indent, curr->data.sap.server.socket,
                        indent, curr->data.sap.server.intermed_nets
                  );
            } /* SAP */
                  break;

            case PROT_TCP:
                  printf(
                        "%ssrc_port: %u\n"
                        "%sdst_port: %u\n"
                        "%s   seqno: %u\n"
                        "%s   ackno: %u\n"
                        "%s headlen: %u bytes\n"
                        "%sreserved: 0x%03X\n"
                        "%s     cwr: %u, ecn: %u, urg: %u, ack: %u, psh: %u, rst: %u, syn: %u, fin: %u\n"
                        "%s  window: %u\n"
                        "%s  chksum: %04X\n"
                        "%s urg_ptr: 0x%04X\n"
                        "%s    opts: %d\n",
                        indent, curr->data.tcp.src_port,
                        indent, curr->data.tcp.dest_port,
                        indent, curr->data.tcp.seqno,
                        indent, curr->data.tcp.ackno,
                        indent, curr->data.tcp.headlen,
                        indent, curr->data.tcp.reserved,
                        indent,
                              curr->data.tcp.cwr,
                              curr->data.tcp.ecn,
                              curr->data.tcp.urg,
                              curr->data.tcp.ack,
                              curr->data.tcp.psh,
                              curr->data.tcp.rst,
                              curr->data.tcp.syn,
                              curr->data.tcp.fin,
                        indent, curr->data.tcp.window,
                        indent, curr->data.tcp.checksum,
                        indent, curr->data.tcp.urg_ptr,
                        indent, curr->data.tcp.opts
                  );
                  /* print options */
                  if (curr->data.tcp.opts) {
                        int i;
                        for (i = 0; i < curr->data.tcp.opts; i++) {
                              printf("%s  opt[%d]: ", indent, i);
                              switch (curr->data.tcp.opt[i].type) {
                              case TCP_OPT_END:
                                    printf("END\n");
                                    break;
                              case TCP_OPT_NOP:
                                    printf("NOP\n");
                                    break;
                              case TCP_OPT_MSS:
                                    printf("MSS: %u\n", curr->data.tcp.opt[i].data.mss);
                                    break;
                              case TCP_OPT_WS:
                                    printf("WS: %u\n",
                                          node->data.tcp.opt[node->data.tcp.opts].data.ws);
                                    break;
                              case TCP_OPT_SACK:
                                    printf("SACK\n");
                                    break;
                              case TCP_OPT_TS:
                                    printf("TS: tsval: %u, tsecr: %u\n",
                                          curr->data.tcp.opt[i].data.ts.val,
                                          curr->data.tcp.opt[i].data.ts.ecr);
                                    break;
                              default:
                                    printf("0x%02X\n", curr->data.tcp.opt[i].type);
                                    break;
                              }
                        }
                  }
                  break;

            case PROT_GRE:
                  printf(
                        "%schecksum?: %u\n"
                        "%s routing?: %u\n"
                        "%s     key?: %u\n"
                        "%s   seqno?: %u\n"
                        "%sstrsrcrt?: %u\n"
                        "%s rec_ctrl: %X\n"
                        "%s  ack_no?: %u\n"
                        "%s    flags: 0x%02X\n"
                        "%s  version: %u\n"
                        "%sprot_type: 0x%02X\n"
                        "%s  payload: %u bytes\n"
                        "%s  call_id: 0x%02X\n"
                        "%s   ack_no: 0x%04X\n",
                        indent, curr->data.gre.checksum,
                        indent, curr->data.gre.routing,
                        indent, curr->data.gre.key,
                        indent, curr->data.gre.seqno,
                        indent, curr->data.gre.str_src_rt,
                        indent, curr->data.gre.rec_ctrl,
                        indent, curr->data.gre.has_ack_no,
                        indent, curr->data.gre.flags,
                        indent, curr->data.gre.version,
                        indent, curr->data.gre.prot_type,
                        indent, curr->data.gre.payload,
                        indent, curr->data.gre.call_id,
                        indent, curr->data.gre.ack_no
                  );

            case PROT_HTTP:
                  /* nothing, yet */
                  break;

            case PROT_LOOP:
                  printf(
                        "%s    skip: %u\n"
                        "%sfunction: %u\n"
                        "%s receipt: %u\n"
                        "%s    data: %lu bytes:\n",
                        indent, curr->data.loop.skip,
                        indent, curr->data.loop.func,
                        indent, curr->data.loop.receiptno,
                        indent, (long unsigned)curr->data.loop.datalen
                  );
                  break;

            case PROT_TEREDO:
                  printf(
                        "%szero_one: 0x%04X\n"
                        "%s  id_len: %u\n"
                        "%s  au_len: %u\n"
                        "%s      id: ",
                        indent, curr->data.teredo.zero_one,
                        indent, curr->data.teredo.id_len,
                        indent, curr->data.teredo.au_len,
                        indent
                  );
                  bytes_dump_escape(curr->data.teredo.id, curr->data.teredo.id_len);
                  printf(
                        "\n"
                        "%s      au: ",
                        indent
                  );
                  bytes_dump_escape(curr->data.teredo.au, curr->data.teredo.au_len);
                  printf(
                        "\n"
                        "%s   nonce: 0x%02X%02X%02X%02X%02X%02X%02X%02X\n"
                        "%s    conf: 0x%02X\n",
                        indent,
                              curr->data.teredo.nonce[0],
                              curr->data.teredo.nonce[1],
                              curr->data.teredo.nonce[2],
                              curr->data.teredo.nonce[3],
                              curr->data.teredo.nonce[4],
                              curr->data.teredo.nonce[5],
                              curr->data.teredo.nonce[6],
                              curr->data.teredo.nonce[7],
                        indent, curr->data.teredo.conf
                  );
                  if (curr->data.teredo.has_origin) {
                        char ip_buf[IP_ADDR_BUFLEN];
                        printf(
                              "%sorigport: %u\n"
                              "%s  origip: %s\n",
                              indent, curr->data.teredo.origin.port,
                              indent, ip_addr_to_str(&curr->data.teredo.origin.ip, ip_buf, sizeof ip_buf)
                        );
                  }
                  break; /* Teredo */

            case PROT_SYMBOL_WIFI:
            {
                  char ip_buf[IP_ADDR_BUFLEN];
                  printf(
                        "%s   ip: %s\n"
                        "%s ssid: %s\n"
                        "%sfreq?: %s\n"
                        "%s   ap: %s\n",
                        indent, ip_addr_to_str(&curr->data.symw.ip, ip_buf, sizeof ip_buf),
                        indent, curr->data.symw.ssid,
                        indent, curr->data.symw.freq_descr,
                        indent, curr->data.symw.ap_descr
                  );
            }
                  break;

            case PROT_CISCOWL:
            {
                  char dst_buf[MAC_ADDR_BUFLEN], src_buf[MAC_ADDR_BUFLEN];
                  printf(
                        "%s len: %02X\n"
                        "%stype: %04X\n"
                        "%s dst: %s\n"
                        "%s src: %s\n"
                        "%sunk1: %04X\n"
                        "%sunk2: %08X\n",
                        indent, curr->data.ciscowl.len,
                        indent, curr->data.ciscowl.type,
                        indent, mac_addr_to_str(&curr->data.ciscowl.dst, dst_buf, sizeof dst_buf),
                        indent, mac_addr_to_str(&curr->data.ciscowl.src, src_buf, sizeof src_buf),
                        indent, curr->data.ciscowl.unknown1,
                        indent, curr->data.ciscowl.unknown2
                  );
            }
                  break;
                  
            } /* big switch */

#if 0
#ifdef _DEBUG
            printf("%s    child: %p\n",
                  indent, (void *)curr->child);
#endif
#endif

#ifdef YEAH_USE_INDENT_BLAH
            if (i < sizeof indent - 1) indent[i] = ' ';
#endif
      }
      printf("\n");
}

/**
 * match pattern of length patlen against buf. does buf matches pattern of any length?
 * pattern "a", buf: "aa" -> match, pattern "aa", buf "a" -> match
 * @return 1 on match, else 0
 */
int bytes_match_repeat(const u_char *buf, size_t buflen, const u_char *pattern,
      size_t patlen)
{
      const u_char *bufcur;
      size_t bufchunk = buflen;


#ifdef _DEBUG
      assert(NULL != buf);
      assert(NULL != pattern);
#endif

      for (bufcur = buf; bufchunk > 0; bufcur += bufchunk) {
            bufchunk = (buflen - (bufcur - buf)) % (patlen + 1);
            if (0 != memcmp(bufcur, pattern, bufchunk))
                  return 0;
      }

      return 1;
}

/**
 * guess ttl by rounding up to the next highest power of 2...
 * @note this won't work in every case, some machines don't use powers of 2 for ttl
 */
int unsigned guess_ttl(int unsigned ttl)
{
      ttl &= 0xFF;
      ttl--;
      ttl |= ttl >> 1;
      ttl |= ttl >> 2;
      ttl |= ttl >> 4;
      ttl++;
      return (256 == ttl ? 255 : ttl);
}


/**
 * analyze an ICMP ECHO request
 */
extern const struct ping_sig PING_SIGS[];
void ping_report(protonode_t *node)
{
      struct ping_sig sig;
      const struct ping_sig *test;
      int match = 0;

#ifdef _DEBUG
      assert(NULL != node);
#endif

      sig.type = node->data.icmp.type;

      if (ICMP_TYPE_ECHO_REQ != sig.type && ICMP_TYPE_ECHO_RESP != sig.type) {
            return;
      } else if (NULL == node->parent || PROT_IP != node->parent->prot_id) {
            DEBUGF(__FILE__, __LINE__, "ICMP parent isn't IP?! skipping signature check...");
      }

      /* construct sig for current */
      sig.ttl = guess_ttl(node->parent->data.ip.ttl);
      { /* figure out if sequence is msbf */
            uint16_t *seq = lhash_get(&Ip_Ping_Seq, &node->parent->data.ip.src);
            sig.seq_msbf = -1; /* default: unknown */
            if (NULL == seq) {
                  seq = malloc(sizeof *seq);
                  if (NULL != seq) {
                        *seq = node->data.icmp.data.echo.seq;
                        lhash_set(&Ip_Ping_Seq, &node->parent->data.ip.src, seq);
                  }
            } else { /* check endianness */
                  uint16_t seqnow = node->data.icmp.data.echo.seq;
                  if (*seq == seqnow - 1) {
                        sig.seq_msbf = !machine_is_big_endian();
                  } else { /* NOTE: check both, !LE != ME, seq may not be in order */
                        uint16_t tmp = ((seqnow >> 8) | ((seqnow & 0xFF) << 8)) - 1;
                        tmp = ((tmp >> 8) | ((tmp& 0xFF) << 8));
                        if (*seq == tmp) {
                              sig.seq_msbf = machine_is_big_endian();
                        } else {
                              DEBUGF(__FILE__, __LINE__, "seq last:%04X, now (lsbf:%04X, lsbf+1:%04X, msbf:%04X)",
                                    *seq, seqnow, seqnow+1, tmp);
                        }
                  }
                  /* save current seq as last (native endianness) */
                  *seq = seqnow;
            }
      }
      sig.id = node->data.icmp.data.echo.id;
      sig.df = node->parent->data.ip.dontfrag;
      sig.plbytes = (int)node->data.icmp.data.echo.data_len;
      sig.payload = node->data.icmp.data.echo.data;

      /* try to match sig */
      for (test = PING_SIGS; !match && test->oslen != -1; test++) {
            match = (
                  (-1 == test->type || test->type == sig.type) &&
                  (-1 == test->ttl || test->ttl == sig.ttl) &&
                  (-1 == test->seq_msbf || test->seq_msbf == sig.seq_msbf) &&
                  (-1 == test->id || test->id == sig.id) &&
                  (-1 == test->df || test->df == sig.df)  &&
                  (-1 == test->plbytes || (test->plbytes == sig.plbytes && (NULL == test->payload ||
                        /* FIXME: why doesn't this work?! */
                        /* bytes_match_repeat(sig.payload + test->ploffset, sig.plbytes, test->payload, test->plpatlen)))) */
                        1)))
            );
            if (match) break;
      }

      if (match) {
            report_ip_ping_sig(&node->parent->data.ip.src, (int)(test - PING_SIGS));
      }

      /* NOTE: don't dump non-matching sigs where we don't know byte order of seq... we won't know it for all machines
       * until the second ping */
      if (-1 != sig.seq_msbf && (!match || Verbose > 2)) {
            char src_buf[IP_ADDR_BUFLEN], dest_buf[IP_ADDR_BUFLEN], payload_buf[BYTES_DUMP_ESCAPE_BUFLEN(MTU_ETHER)];
            printf("%s (%smatch) (%-15s -> %-15s) ttl:%3u seq_msbf:%1u(0x%04X) id:0x%04X df:%d plbytes:%u payload:%s\n",
                  (PING_REQ == sig.type ? "PING" : (PING_RESP == sig.type ? "PONG" : "???")),
                  (match ? "" : "no "),
                  ip_addr_to_str(&node->parent->data.ip.src, src_buf, sizeof src_buf),
                  ip_addr_to_str(&node->parent->data.ip.dest, dest_buf, sizeof dest_buf),
                  sig.ttl,
                  sig.seq_msbf,
                  node->data.icmp.data.echo.seq,
                  node->data.icmp.data.echo.id,
                  sig.df,
                  sig.plbytes,
                  bytes_dump_escape_buf(payload_buf, sizeof payload_buf, node->data.icmp.data.echo.data,
                              node->data.icmp.data.echo.data_len));
      }
}



/**
 * analyze a tcp packet for clues about who sent it
 * @note tcp headers can be included within ICMP error messages... these should not be associated with
 * the sender of ICMP
 */
extern const struct tcp_sig TCP_SYN_PRINTS[];
void report_tcp(protonode_t *node)
{
      struct tcp_sig print;
      const struct tcp_sig *test;
      int i, match = 0;
      int mss = -1; /* unknown mss */

#ifdef _DEBUG
      assert(NULL != node);
#endif

      if (!(node->data.tcp.syn && !node->data.tcp.ack)) {
            /* currently we test SYN only */
            return;
      } else if (!ip_is_private(&node->parent->data.ip.src)) {
            /* we're not interested in testing outside IPs */
            return; 
      }

      /* build fingerprint */
      print.window = node->data.tcp.window;
      print.ttl = guess_ttl(node->parent->data.ip.ttl);
      print.df = node->parent->data.ip.dontfrag;
      print.headlen = node->data.tcp.headlen;
      print.opts = node->data.tcp.opts;
      print.ts = -1;
      print.ws = -1;

      /* fill in options if there */
      for (i = 0; i < node->data.tcp.opts; i++) {
            switch (node->data.tcp.opt[i].type) {
            case TCP_OPT_MSS: /* record mss just for fun... try matching to MTU of physical connection later */
                  mss = node->data.tcp.opt[i].data.mss;
                  break;
            case TCP_OPT_TS:
                  print.ts = (0 != node->data.tcp.opt[i].data.ts.val);
                  break;
            case TCP_OPT_WS:
                  print.ws = node->data.tcp.opt[i].data.ws;
                  break;
            }
      }

      /* try to match fingerprint */
      /* FIXME: sort them by something, probably window size and use binary search you insensitive clod! */
      for (test = TCP_SYN_PRINTS; test->window != -1; test++) {
            if ((TCP_MULT_MSS & test->window ? (test->window ^ TCP_MULT_MSS) == print.window / mss :
                        (TCP_MULT_MTU & test->window ? (test->window ^ TCP_MULT_MTU) == TCP_MSS_TO_MTU(print.window) / mss :
                              (TCP_MULT_MOD & test->window ? print.window % (test->window ^ TCP_MULT_MOD) == 0 :
                                    test->window == print.window))) &&
                  (-1 == test->ttl || test->ttl == print.ttl) &&
                  (-1 == test->df || test->df == print.df) &&
                  test->headlen == print.headlen &&
                  test->opts == print.opts &&
                  test->ts == print.ts &&
                  test->ws == print.ws
            ) {
                  match = 1;
                  /* test options */
                  for (i = 0; i < print.opts && 1 == match; i++);
                        match = test->opt[i] == node->data.tcp.opt[i].type;
                  if (1 == match)
                        break;
            }
      }

      if (match)
            report_ip_tcp_syn_sig(&node->parent->data.ip.src, (int)(test - TCP_SYN_PRINTS));

      if (!match || Verbose > 2) {
            int i;
            char ip_buf[IP_ADDR_BUFLEN];
            printf("TCP_SYN (%smatch) %15s ttl:%u,df:%d,window:%u,hdl:%d,ts:%d,ws:%d,opts:%d(",
                  (match ? "" : "no "),
                  ip_addr_to_str(&node->parent->data.ip.src, ip_buf, sizeof ip_buf),
                  print.ttl, print.df, print.window, print.headlen, print.ts, print.ws, print.opts);
            for (i = 0; i < node->data.tcp.opts; i++) {
                  if (node->data.tcp.opt[i].type < TCP_OPT_WHOOPS_TOO_HIGH) {
                        printf("%s%s", (i > 0 ? "," : ""),
                              Tcp_Opts_Descr[node->data.tcp.opt[i].type]);
                  } else {
                        printf("%d,", node->data.tcp.opt[i].type);
                  }
            }
            printf(")(mss:%d)\n", mss); /* print mss just for fun */
      }
}

/**
 * parse a human-readable Operating System into a guess at the machine
 * txt could be:
 *    BOOTP Vendor Class Id
 */
/**
 *
 * Apple:
 *    OS 9 will ID itself in the BOOTP Vendor Class
 *          "Mac OS 9.2.2 Open Transport 2.7.9 Power Mac G4 (Graphite)"
 *    ...shit, we even know what color it is
 *    OSX BOOTP won't have a vendor class, but will include a hostname (maybe?)...
 *          but we can still ID them as OSX because they'll likely have an Apple mac address
 */
/* FIXME: cleanup vendor shit... sucks */
extern const struct bootp_sig BOOTP_SIGS[];
void bootp_report(protonode_t *node)
{
      char *vendor_class = NULL;
      int hw;
      int vendor;
      char os_extra[256];
      const struct mac *mac;


#ifdef _DEBUG
      assert(NULL != node);
#endif

      if (BOOTP_TYPE_RESP == node->data.bootp.type) {
            if (NULL != node->parent && PROT_UDP == node->parent->prot_id &&
                  NULL != node->parent->parent && PROT_IP == node->parent->parent->prot_id) {
                  report_ip_fact(&node->parent->parent->data.ip.src, FACT_ROLE_SERVER_DHCP);
            }
      }
      /* TODO: maybe repot mac<->ip here... but an IP given by dhcpd can still be turned down by the client... */

      mac = &node->data.bootp.client_mac;

      /* NOTE: let's try to figure out what BOOTP requests look like from different machines...
       * perhaps it's a candidate for fingerprinting */
      {
            char mac_buf[MAC_ADDR_BUFLEN], client_buf[IP_ADDR_BUFLEN], your_buf[IP_ADDR_BUFLEN];
            printf("BOOTP %u: mac:%s, client:%15s, your:%15s, trans:%u, rel:%u, df:%u\n",
                  node->data.bootp.type,
                  mac_addr_to_str(&node->data.bootp.client_mac, mac_buf, sizeof mac_buf),
                  ip_addr_to_str(&node->data.bootp.client_ip, client_buf, sizeof client_buf),
                  ip_addr_to_str(&node->data.bootp.your_ip, your_buf, sizeof your_buf),
                  node->data.bootp.trans_id,
                  node->parent->parent->data.ip.reliable,
                  node->parent->parent->data.ip.dontfrag);
      }

      { /* match discover fingerprint */
            const struct bootp_sig *sigmatch;
            struct bootp_sig sig;
            int i, j, match;

            sig.ttl = node->parent->parent->data.ip.ttl;
            sig.type = -1; /* unknown */
            sig.flags = node->data.bootp.opt_count;
            sig.flagreqs = 0;
            sig.oslen = 0;
      
            /* build signature */
            for (i = 0; i < node->data.bootp.opt_count; i++) {
                  sig.flag[i] = node->data.bootp.opts[i].type;
                  if (BOOTP_OPT_MSG_TYPE == node->data.bootp.opts[i].type) /* record type */
                        sig.type = node->data.bootp.opts[i].data.msg_type;
                  if (BOOTP_OPT_PARAM_REQ_LIST == node->data.bootp.opts[i].type) {
                        sig.flagreqs = node->data.bootp.opts[i].data.param_req_list.size;
                        for (j = 0; j < node->data.bootp.opts[i].data.param_req_list.size; j++)
                              sig.flagreq[j] = node->data.bootp.opts[i].data.param_req_list.req[j];
                  }
            }

            for (match = 0, sigmatch = BOOTP_SIGS; !match && sigmatch->ttl != -2; sigmatch++) {
                  match = (
                        (-1 == sigmatch->ttl || sigmatch->ttl == sig.ttl) &&
                        (-1 == sigmatch->type || sigmatch->type == sig.type) &&
                        sigmatch->flags == sig.flags &&
                        sigmatch->flagreqs == sig.flagreqs &&
                        0 == memcmp(sigmatch->flag, sig.flag, sizeof sig.flag[0] * sig.flags) &&
                        0 == memcmp(sigmatch->flagreq, sig.flagreq, sizeof sig.flagreq[0] * sig.flagreqs)
                  );
                  if (match) {
                        report_mac_bootp_sig(mac, (int)(sigmatch - BOOTP_SIGS));
                        break;
                  }
            }
            
            if (!match || Verbose > 2) {
                  /* print fingerprint */
                  printf("BOOTP SIG type:%u, ttl:%u, flags:%u(",
                        sig.type, sig.ttl, sig.flags);
                  for (i = 0; i < sig.flags; i++)
                        printf("%s%u", (i > 0 ? "," : ""), sig.flag[i]);
                  printf("), reqs:%u(",
                        sig.flagreqs);
                  for (i = 0; i < sig.flagreqs; i++)
                        printf("%s%u", (i > 0 ? "," : ""), sig.flagreq[i]);
                  printf(")(msgtype:%u)\n", sig.oslen);
            }

      }

      hw = vendor = FACT_UNKNOWN;
      os_extra[0] = '\0';
            
      { /* track down vendor class */
            u_char i;
            for (i = 0; i < node->data.bootp.opt_count; i++) {
                  if (BOOTP_OPT_VENDOR_CLASS == node->data.bootp.opts[i].type) {
                        vendor_class = node->data.bootp.opts[i].data.vendor_class;
                        break;
                  }
            }
      }

      if (BOOTP_TYPE_REQ != node->data.bootp.type)
            return; /* go nay further */

#if THIS_SUCKS
      if (NULL != vendor_class) {
            if (0 == strncmp(vendor_class, "Mac OS 9", strlen("Mac OS 9"))) {
                  vendor = FACT_VEND_APPLE;
                  do { /* pull exact version out */
                        char *nextspace = strchr(vendor_class + strlen("Mac OS 9"), ' ');
                        if (NULL == nextspace)
                              break;
                        strlcpy(os_extra, vendor_class,
                              MIN((size_t)((nextspace - vendor_class) + 1), sizeof os_extra));

                        if (0 == strcmp(os_extra, "Mac OS 9.2.2")) {
                              report_mac_hint(mac, HINT_BOOTP_VENDOR_MACOS_922);
                        } else if (0 == strcmp(os_extra, "Mac OS 9.2")) {
                              report_mac_hint(mac, HINT_BOOTP_VENDOR_MACOS_92);
                        } else {
                              report_mac_hint(mac, HINT_BOOTP_VENDOR_MACOS);
                        }

                  } while (0);

                  
                  if (NULL != (pos = strstr(vendor_class, "Power Mac"))) {
                        if (0 == strncmp(pos, "Power Mac G4", strlen("Power Mac G4"))) {
                              hw = FACT_HW_APPLE_POWER_MAC_G4;
                        } else {
                              hw = FACT_HW_APPLE_POWER_MAC;
                        }
                  }
            } else if (0 == strncmp(vendor_class, "MSFT", strlen("MSFT"))) {
                  if (0 == strcmp(vendor_class, "MSFT 5.0")) {
                        report_mac_hint(mac, HINT_BOOTP_VENDOR_MSFT_50);
                  } else if (0 == strcmp(vendor_class, "MSFT 5.1")) {
                        report_mac_hint(mac, HINT_BOOTP_VENDOR_MSFT_51);
                  }
            } else {
                  /* other shit... */
            }
      }
#endif /* THIS SUCKS */

      if (FACT_UNKNOWN != hw)
            report_mac_fact(mac, hw);

      if (FACT_UNKNOWN != vendor)
            report_mac_fact(mac, vendor);

      DEBUGF(__FILE__, __LINE__,
            "bootp_report: hw: %s, vendor: %s",
            fact_to_str(hw), fact_to_str(vendor));
}



Generated by  Doxygen 1.6.0   Back to index