// $Id: net_util.cc,v 1.11 2003/06/16 03:55:04 vern Exp $
//
// Copyright (c) 1999, 2000, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#ifdef BROv6
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>
#endif

#include "net_util.h"

// - adapted from tcpdump
// Returns the ones-complement checksum of a chunk of b short-aligned bytes.
int ones_complement_checksum(const void* p, int b, uint32 sum)
	{
	const u_short* sp = (u_short*) p;	// better be aligned!

	b /= 2;	// convert to count of short's

	/* No need for endian conversions. */
	while ( --b >= 0 )
		sum += *sp++;

	while ( sum > 0xffff )
		sum = (sum & 0xffff) + (sum >> 16);

	return sum;
	}

int tcp_checksum(const struct ip* ip, const struct tcphdr* tp, int len)
	{
	int tcp_len = tp->th_off * 4 + len;
	uint32 sum;

	if ( len % 2 == 1 )
		// Add in pad byte.
		sum = htons(((const u_char*) tp)[tcp_len - 1] << 8);
	else
		sum = 0;

	sum = ones_complement_checksum((void*) &ip->ip_src.s_addr, 4, sum);
	sum = ones_complement_checksum((void*) &ip->ip_dst.s_addr, 4, sum);

	uint32 addl_pseudo =
		(htons(IPPROTO_TCP) << 16) | htons((unsigned short) tcp_len);

	sum = ones_complement_checksum((void*) &addl_pseudo, 4, sum);
	sum = ones_complement_checksum((void*) tp, tcp_len, sum);

	return sum;
	}

int udp_checksum(const struct ip* ip, const struct udphdr* up, int len)
	{
	uint32 sum;

	if ( len % 2 == 1 )
		// Add in pad byte.
		sum = htons(((const u_char*) up)[len - 1] << 8);
	else
		sum = 0;

	sum = ones_complement_checksum((void*) &ip->ip_src.s_addr, 4, sum);
	sum = ones_complement_checksum((void*) &ip->ip_dst.s_addr, 4, sum);

	uint32 addl_pseudo =
		(htons(IPPROTO_UDP) << 16) | htons((unsigned short) len);

	sum = ones_complement_checksum((void*) &addl_pseudo, 4, sum);
	sum = ones_complement_checksum((void*) up, len, sum);

	return sum;
	}

int icmp_checksum(const struct icmp* icmpp, int len)
	{
	uint32 sum;

	if ( len % 2 == 1 )
		// Add in pad byte.
		sum = htons(((const u_char*) icmpp)[len - 1] << 8);
	else
		sum = 0;

	sum = ones_complement_checksum((void*) icmpp, len, sum);

	return sum;
	}


#define CLASS_A 0x00000000
#define CLASS_B 0x80000000
#define CLASS_C 0xc0000000
#define CLASS_D 0xe0000000
#define CLASS_E 0xf0000000

#define CHECK_CLASS(addr,class) (((addr) & (class)) == (class))
char addr_to_class(uint32 addr)
	{
	if ( CHECK_CLASS(addr, CLASS_E) )
		return 'E';
	else if ( CHECK_CLASS(addr, CLASS_D) )
		return 'D';
	else if ( CHECK_CLASS(addr, CLASS_C) )
		return 'C';
	else if ( CHECK_CLASS(addr, CLASS_B) )
		return 'B';
	else
		return 'A';
	}

uint32 addr_to_net(uint32 addr)
	{
	if ( CHECK_CLASS(addr, CLASS_D) )
		; // class D's are left alone ###
	else if ( CHECK_CLASS(addr, CLASS_C) )
		addr = addr & 0xffffff00;
	else if ( CHECK_CLASS(addr, CLASS_B) )
		addr = addr & 0xffff0000;
	else
		addr = addr & 0xff000000;

	return addr;
	}

const char* dotted_addr(uint32 addr, int alternative)
	{
	addr = ntohl(addr);
	const char* fmt = alternative ? "%d,%d.%d.%d" : "%d.%d.%d.%d";

	static char buf[32];
	sprintf(buf, fmt,
		addr >> 24, (addr >> 16) & 0xff,
		(addr >> 8) & 0xff, addr & 0xff);

	return buf;
	}

const char* dotted_addr(const uint32* addr, int alternative)
	{
#ifdef BROv6
	if ( is_v4_addr(addr) )
		return dotted_addr(addr[3], alternative);

	static char buf[256];

	if ( inet_ntop(AF_INET6, addr, buf, sizeof buf) == NULL )
		return "<bad IPv6 address conversion>";

	return buf;

#else
	return dotted_addr(to_v4_addr(addr), alternative);
#endif
	}

const char* dotted_net(uint32 addr)
	{
	addr = ntohl(addr);

	static char buf[32];

	if ( CHECK_CLASS(addr, CLASS_D) )
		sprintf(buf, "%d.%d.%d.%d",
			addr >> 24, (addr >> 16) & 0xff,
			(addr >> 8) & 0xff, addr & 0xff);

	else if ( CHECK_CLASS(addr, CLASS_C) )
		sprintf(buf, "%d.%d.%d",
			addr >> 24, (addr >> 16) & 0xff, (addr >> 8) & 0xff);

	else
		// Same for class A's and B's.
		sprintf(buf, "%d.%d", addr >> 24, (addr >> 16) & 0xff);

	return buf;
	}

const char* dotted_net6(const uint32* addr)
	{
	// ### this isn't right, but net's should go away eventually ...
	return dotted_addr(addr);
	}

uint32 dotted_to_addr(const char* addr_text)
	{
	int addr[4];

	if ( sscanf(addr_text,
		    "%d.%d.%d.%d", addr+0, addr+1, addr+2, addr+3) != 4 )
		{
		error("bad dotted address:", addr_text );
		return 0;
		}

	if ( addr[0] < 0 || addr[1] < 0 || addr[2] < 0 || addr[3] < 0 ||
	     addr[0] > 255 || addr[1] > 255 || addr[2] > 255 || addr[3] > 255 )
		{
		error("bad dotted address:", addr_text);
		return 0;
		}

	uint32 a = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];

	// ### perhaps do gethostbyaddr here?

	return uint32(htonl(a));
	}

#ifdef BROv6
uint32* dotted_to_addr6(const char* addr_text)
	{
	uint32* addr = new uint32[4];
	if ( inet_pton(AF_INET6, addr_text, addr) <= 0 )
		{
		error("bad IPv6 address:", addr_text );
		return 0;
		}

	return addr;
	}

#endif

#ifdef BROv6
int is_v4_addr(const uint32 addr[4])
	{
	return addr[0] == 0 && addr[1] == 0 && addr[2] == 0;
	}
#endif

uint32 to_v4_addr(const uint32* addr)
	{
#ifdef BROv6
	if ( ! is_v4_addr(addr) )
		internal_error("conversion of non-IPv4 address to IPv4 address");
	return addr[3];
#else
	return addr[0];
#endif
	}

uint32 mask_addr(uint32 a, uint32 top_bits_to_keep)
	{
	if ( top_bits_to_keep == 0 || top_bits_to_keep > 32 )
		{
		error("bad address mask value", top_bits_to_keep);
		return a;
		}

	uint32 addr = ntohl(a);
	int shift = 32 - top_bits_to_keep;
	addr >>= shift;
	addr <<= shift;

	return htonl(addr);
	}

const uint32* mask_addr(const uint32* a, uint32 top_bits_to_keep)
	{
#ifdef BROv6
	static uint32 addr[4];

	addr[0] = a[0];
	addr[1] = a[1];
	addr[2] = a[2];
	addr[3] = a[3];

	// This is a bit dicey: if it's a v4 address, then we interpret
	// the mask as being with respect to 32 bits total, even though
	// strictly speaking, the v4 address comprises the least-significant
	// bits out of 128, rather than the most significant.
	uint32 max_bits = is_v4_addr(a) ? 32 : 128;

	if ( top_bits_to_keep == 0 || top_bits_to_keep > max_bits )
		{
		error("bad address mask value", top_bits_to_keep);
		return addr;
		}

	int word = 3;	// start zeroing out with word #3
	int bits_to_chop = max_bits - top_bits_to_keep;	// bits to discard
	while ( bits_to_chop >= 32 )
		{ // there's an entire word to discard
		addr[word] = 0;
		--word;	// move on to next, more significant word
		bits_to_chop -= 32;	// we just go rid of 32 bits
		}

	// All that's left to work with now is the word pointed to by "word".
	uint32 addr32 = ntohl(addr[word]);
	addr32 >>= bits_to_chop;
	addr32 <<= bits_to_chop;
	addr[word] = htonl(addr32);

	return addr;
#else
	return a;
#endif
	}
