/*
 * Sniff network traffic to guess network data
 *
 * Copyright (C) 2003  Enrico Zini <enrico@debian.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

#include "TrafficScanner.h"
#include "util/output.h"

#include <netinet/in.h> // ntohs, htons, ...

extern "C" {
#include <libnet.h>
}

using namespace std;
using namespace wibble::sys;

/*
static string fmt_ip(unsigned int ip) throw ()
{
	unsigned char ipfmt[4];
	memcpy(ipfmt, &ip, 4);
	return fmt("%d.%d.%d.%d",
			(int)ipfmt[0], (int)ipfmt[1], (int)ipfmt[2], (int)ipfmt[3]);
}

static void memdump(const string& prefix, unsigned char* mem, int size) throw ()
{
	warning("%.*s", PFSTR(prefix));
	for (int i = 0; i < size; i++)
	{
		warning(" %02x", (int)mem[i]);
	}
	warning("\n");
}

static unsigned int netaddr_to_netmask(unsigned int na) throw ()
{
	unsigned int res = 0;
	na = ntohl(na);
	
	// Count the trailing zeros
	int i = 0;
	for ( ; i < 32 && (na & (1 << i)) == 0; i++)
		;

	// Add 1s to the start of res
	for ( ; i < 32; i++)
		res |= (1 << i);

	return htonl(res);
}
*/

static unsigned int merge_netmasks(unsigned int nm1, unsigned int nm2) throw ()
{
	if (nm1 == nm2)
		return nm1;
	unsigned int dpat = nm1 ^ nm2;

	dpat = ntohl(dpat);

	// Count the leading zeros
	int i = 0;
	for ( ; i < 32 && (dpat & (0x80000000 >> i)) == 0; i++)
		;

	if (i < 8)
		return 0;

	int res = 0;
	for (int j = 0; j < i; j++)
	{
		res >>= 1;
		res |= 0x80000000;
	}
	return htonl(res) & (nm1 | nm2);
}

void TrafficScanner::handleEthernet(struct libnet_ethernet_hdr* eth_header) throw ()
{
	//warning("packet ");
	if (ntohs (eth_header->ether_type) == ETHERTYPE_ARP)
	{
		//warning("arp ");
		// Parse and check the arp header
		struct libnet_arp_hdr* arp_header = (struct libnet_arp_hdr *)((char*)eth_header + LIBNET_ETH_H);

		if (ntohs (arp_header->ar_op) == ARPOP_REPLY)
		{
			ip_key ipkey = 0;
			memcpy(&ipkey, arp_get_sip(arp_header), 4);

			mac_key mkey = 0;
			memcpy(&mkey, arp_get_sha(arp_header), 6);

			scanData[mkey].addr = ipkey;

			//string fmtip = fmt_ip(ipkey);
			//warning("got address: %.*s\n", PFSTR(fmtip));
		}// else
		//	warning("no arp reply\n");
	}
	else if (ntohs (eth_header->ether_type) == ETHERTYPE_IP)
	{
		//warning("ip ");
		// Parse and check the ip header
		struct libnet_ipv4_hdr* ip_header = (struct libnet_ipv4_hdr *)((char*)eth_header + LIBNET_ETH_H);

		mac_key msrc = 0;
		mac_key mdst = 0;
		ip_key isrc = 0;
		ip_key idst = 0;

		memcpy(&msrc, &(eth_header->ether_shost), 6);
		memcpy(&mdst, &(eth_header->ether_dhost), 6);
		memcpy(&isrc, &(ip_header->ip_src), 4);
		memcpy(&idst, &(ip_header->ip_dst), 4);

		/*
		// Bad: could give a bad addr to the gw
		map<mac_key, HostData>::iterator iter_src = scanData.find(msrc);
		if (iter_src != scanData.end() && iter_src->second.addr == 0)
			iter_src->second.addr = isrc;
			
		// Bad: could give a bad addr to the gw
		map<mac_key, HostData>::iterator iter_dst = scanData.find(mdst);
		if (iter_dst != scanData.end() && iter_dst->second.addr == 0)
			iter_dst->second.addr = idst;
		*/

		scanData[msrc].addr_sent.insert(isrc);
		scanData[mdst].addr_recv.insert(idst);

		if (local_addrs.find(isrc) == local_addrs.end())
		{
			requestArp(isrc);
			local_addrs[isrc] = 0;
		}
		if (local_addrs.find(idst) == local_addrs.end())
		{
			requestArp(idst);
			local_addrs[idst] = 0;
		}

		//string fmtsrc = fmt_ip(isrc);
		//string fmtdst = fmt_ip(idst);
		//warning(" %.*s -> %.*s\n", PFSTR(fmtsrc), PFSTR(fmtdst));

		//memdump("pkt data:", (unsigned char*)ip_header, 64);
	}
	//else
		//warning("other\n");

	// Get the network mask
	unsigned int netaddr = 0xffffffff;
	bool first = true;
	for (map<mac_key, HostData>::const_iterator h = scanData.begin();
			h != scanData.end(); h++)
		if (h->second.addr != 0)
			if (first)
			{
				netaddr = h->second.addr;
				first = false;
			}
			else
				netaddr = merge_netmasks(netaddr, h->second.addr);

	if (guessed_netaddr != netaddr)
	{
		guessed_netaddr = netaddr;
		/*
		string fmt_na = fmt_ip(guessed_netaddr);
		warning("Network address: %.*s\n", PFSTR(fmt_na));

		string fmt_nm = fmt_ip(netaddr_to_netmask(guessed_netaddr));
		warning("Network mask: %.*s\n", PFSTR(fmt_nm));
		*/
	}

	// Find out the gateway
	for (map<mac_key, HostData>::const_iterator h = scanData.begin();
			h != scanData.end(); h++)
	{
		if (h->second.addr != 0)
		{
			//string fmt_rec = fmt_ip(h->second.addr);

			unsigned int gwaddr = netaddr;

			//warning("Names for %.*s:", PFSTR(fmt_rec));
			for (set<ip_key>::const_iterator i = h->second.addr_sent.begin();
					i != h->second.addr_sent.end(); i++)
			{
				gwaddr = merge_netmasks(gwaddr, *i);
				//string f = fmt_ip(*i);
				//warning(" %.*s", PFSTR(f));
			}
			//warning("\n");

			for (set<ip_key>::const_iterator i = h->second.addr_recv.begin();
					i != h->second.addr_recv.end(); i++)
				gwaddr = merge_netmasks(gwaddr, *i);

			if (gwaddr == 0)
			{
				//int ogws = guessed_gateways.size();
				guessed_gateways.insert(h->first);
				/*
				if (ogws != guessed_gateways.size())
				{
					for (set<mac_key>::const_iterator i = guessed_gateways.begin();
							i != guessed_gateways.end(); i++)
					{
						string fmt_gw = fmt_ip(scanData[*i].addr);
						if (i == guessed_gateways.begin())
							warning("Gateway: %.*s", PFSTR(fmt_gw));
						else
							warning(", %.*s", PFSTR(fmt_gw));

					}
					warning("\n");
				}
				*/
			}
		}
	}
}

void TrafficScanner::requestArp(ip_key addr)
	throw ()
{
	// Build and send the arp probe

	// Using ip_no_addr as the source ip address seems to be a good idea: see
	// -D switch to arping, which points to Duplicate Address Detection mode
	// and RFC2131, 4.4.1.

	unsigned char ether_broadcast_addr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	unsigned char ether_no_addr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char ip_no_addr[4] = {0x00, 0x00, 0x00, 0x00};
	struct libnet_ether_addr* localmac = sender.getMACAddress();
	libnet_t *ln_context = sender.getLibnetContext();

	libnet_build_arp (
			ARPHRD_ETHER,
			ETHERTYPE_IP,
			ETHER_ADDR_LEN,
			4,
			ARPOP_REQUEST,
			localmac->ether_addr_octet,
			(u_char *)ip_no_addr,
			ether_no_addr,
			(u_char *)&addr,
			NULL,
			0,
			ln_context, 0);

	libnet_build_ethernet (
			ether_broadcast_addr,
			localmac->ether_addr_octet,
			ETHERTYPE_ARP,
			NULL,
			0,
			ln_context, 0);

	u_char* buf;
	u_int32_t len;
	libnet_adv_cull_packet(ln_context, &buf, &len);
	Buffer pkt(buf, len, false);

	// FIXME: How is a sane way of deallocating buf?
	if (ln_context->aligner > 0)
		buf -= ln_context->aligner;
	free(buf);

	libnet_clear_packet(ln_context);

	// Enqueue the packet for sending
	sender.post(pkt, 1000, 10000);
}

// vim:set ts=4 sw=4:
