/* Public domain ARP based passive "ping" to collect a list of  */
/* active hosts on your subnet. In the example MY_FILTER, your  */
/* router is 10.0.0.1 and your PC is 10.0.0.99. Conceived 2001. */
#define MY_FILTER "arp host 10.0.0.1 or arp host 10.0.0.99"
/* Compile with  gcc -Wall THIS.c /usr/lib/libpcap.a -o THIS */
/* -Lsomedir -lpcap seems not to work :( */
 
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>

#include <string.h> /* memcpy */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <pcap.h>

#define ESRC(ep) ((ep)->ether_shost)
#define EDST(ep) ((ep)->ether_dhost)
#define SHA(ap) ((ap)->arp_sha)
#define THA(ap) ((ap)->arp_tha)
#define SPA(ap) ((ap)->arp_spa)
#define TPA(ap) ((ap)->arp_tpa)

#define DEB(msg) (void)fprintf(stderr,"Debug: %s\n",msg)
#undef DETAIL
#define EXPERIMENT 42


int     main(int, char **);
void    process_ether(u_char *, const struct pcap_pkthdr *, const u_char *);
char    *e2str(u_char *);

static u_int32_t net;
static u_int32_t netmask;
int bits[256] = { 0, };

int
main(int argc, char **argv)
{
//        register char *cp;
        register int snaplen, timeout, linktype, status;
        register pcap_t *pd;
        register char *interface;
        struct bpf_program code;
        char errbuf[PCAP_ERRBUF_SIZE];

net = 0;
netmask = 0;

interface = pcap_lookupdev(errbuf);
if (interface==NULL) { pcap_perror(pd,"pcap_lookupdev"); exit(1); };

if (pcap_lookupnet(interface, &net, &netmask, errbuf) < 0)
   { pcap_perror(pd,"pcap_lookupnet"); exit(1); };

snaplen = sizeof(struct ether_header) + sizeof(struct ether_arp);
timeout = 1000;

pd = pcap_open_live(interface, snaplen, 0, timeout, errbuf);
/* int is promisc, timeout in ms */
if (pd==NULL) { pcap_perror(pd,"pcap_open_live"); exit(1); };

linktype = pcap_datalink(pd);
/* get link layer type */
if (linktype != DLT_EN10MB) { exit(1); }; /* only handle eth */

if (pcap_compile(pd, &code, MY_FILTER /* "arp or rarp" */, 1, netmask) < 0)
/* string format as on command line for tcpdump !? */
   { pcap_perror(pd,"pcap_compile"); exit(1); };

if (pcap_setfilter(pd, &code) < 0) 
/* activate filter */
   { pcap_perror(pd,"pcap_compile"); exit(1); };

status = pcap_loop(pd, 0, process_ether, NULL);
/* loop listening until N packets or error, neg for forever, */
/* 0 for till timeout, EOF or error */
if (status < 0) { pcap_perror(pd,"pcap_loop"); exit(1); };

pcap_close(pd);
exit(0);
} /* END OF MAIN */

void
process_ether(register u_char *u, register const struct pcap_pkthdr *h,
    register const u_char *p)
{
        register struct ether_header *eh;
        register struct ether_arp *ea;
        register u_char *sea, *sha, *dea;
//      register u_char *dha;
//        register time_t t;
//        u_int32_t sia;
	struct in_addr mysia;
	int len, i, j;

        eh = (struct ether_header *)p;
        ea = (struct ether_arp *)(eh + 1);
	len = h->caplen;

	if (len < sizeof(*eh) + sizeof(*ea))
           { DEB("short"); return; /* short packet */ };
	if ((ntohs(ea->arp_hrd) != ARPHRD_ETHER) || 
	    (ntohs(ea->arp_pro) != ETHERTYPE_IP))
           { DEB("no ip/eth"); 
             fprintf(stderr,"arp_hrd: %d arp_pro: %d\n",
                     ntohs(ea->arp_hrd),ntohs(ea->arp_pro));
             return; /* wrong proto, strange */ };
        if ((ea->arp_hln != 6) || (ea->arp_pln != 4)) /* bytes */
           { DEB("bad hln/pln"); return; /* bad proto/hardware addr len */ };
	
	if (ntohs(eh->ether_type) != ETHERTYPE_ARP)
           { DEB("not arp"); 
             printf("ether_type: %d\n",ntohs(eh->ether_type));
             return; /* not interested in others */ };

        /* or: REVARP... REVARP_REQUEST... */
	if ( (ntohs(ea->arp_op) != ARPOP_REQUEST) &&
	     (ntohs(ea->arp_op) != ARPOP_REPLY) )
	/* REQ=1 REPL=2 RREQ=3 RREPL=4 */
           { DEB("not normal ARP - maybe RARP");
             fprintf(stderr,"arp_op: %d\n",ntohs(ea->arp_op));
             return; /* only requests for now */ };

        /* Source hardware ethernet address */
        sea = (u_char *)ESRC(eh);
        /* Source ip address */
        sha = (u_char *)SHA(ea);
        /* Destination ethernet address */
        dea = (u_char *)EDST(eh);
//        /* Destination host ip address */
//        dha = (u_char *)THA(ea);

	/* ** sea should be same as sha ** */
	/* 000000000000 or ffffffffffff means broadcast */

	if ( ntohs(ea->arp_op) == ARPOP_REPLY )
	{ memcpy(&mysia,&TPA(ea),4);
#ifdef EXPERIMENT
	  printf("[arp-ping: %d has asked for me]\n",
	         ((unsigned char *)&mysia)[3] );
#endif
	  fprintf(stderr,"reply to ip=%s eth=%s -> ",
	          inet_ntoa(mysia),e2str(dea));
	};

	memcpy(&mysia,&SPA(ea),4);
	/* so show intoa(sia) and e2str(sea) */
	fprintf(stderr,"arp: ip=%s eth=%s\n", 
	        inet_ntoa(mysia),e2str(sea));

	if ( ntohs(ea->arp_op) == ARPOP_REPLY )
	{ return; /* no statistics on replies */ };

	/* And now for the fun part: statistics! */
	i = ((unsigned char *)&mysia)[3];
	bits[i]++;

	len=0;
	for (i=0; i<256; i++)
	    {
#ifdef DETAIL
	      if (bits[i]) printf("%3.3d ",i);
#endif
	      if (bits[i]>len) len=bits[i];
	    };
#ifdef DETAIL
	printf("\n");
#endif

	if (len>3)
	   {
#ifdef DETAIL	    
	     printf("Max count reached - decrementing counts.");
#endif
	     printf("\nHosts: ");
	     for (j=i=0; i<256; i++)
	         { if (bits[i]) { printf("%3.3d ",i); j++; }
	           if (bits[i]>0) bits[i]--;
	         };
	     printf("=> %d\n",j);
	   };

#ifdef DETAIL
	if (len==1)
	   { printf("Counting has started - Active:\n");
	     for (i=0; i<256; i++) { if (bits[i]) printf("%3.3d ",i); };
	     printf("\n");
	   };
#endif

return;
};

char *
e2str(register u_char *e)
{
        static char str[32];

        (void)sprintf(str, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
            e[0], e[1], e[2], e[3], e[4], e[5]);
        return (str);
}
