#ifndef lint
static	char	*sccsid = "@(#)subnet.c	1.5	(Berkeley) 1/4/88";
#endif

#include "../common/conf.h"

#ifdef SUBNET

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifndef NETMASK
#include <net/if.h>
#endif
#include <sys/ioctl.h>

/*
 * The following routines provide a general interface for
 * subnet support.  Like the library function "inet_netof",
 * which returns the standard (i.e., non-subnet) network
 * portion of an internet address, "inet_snetof" returns
 * the subnetwork portion -- if there is one.  If there
 * isn't, it returns 0.
 *
 * Subnets, under 4.3, are specific to a given set of
 * machines -- right down to the network interfaces.
 * Because of this, the function "getifconf" must be
 * called first.  This routine builds a table listing
 * all the (internet) interfaces present on a machine,
 * along with their subnet masks.  Then when inet_snetof
 * is called, it can quickly scan this table.
 *
 * Unfortunately, there "ain't no graceful way" to handle
 * certain situations.  For example, the kernel permits
 * arbitrary subnet bits -- that is, you could have a
 * 22 bit network field and a 10 bit subnet field.
 * However, due to braindamage at the user level, in
 * such sterling routines as getnetbyaddr, you need to
 * have a subnet mask which is an even multiple of 8.
 * Unless you are running with class C subnets, in which
 * case it should be a multiple of 4.  Because of this rot,
 * if you have non-multiples of 4 bits of subnet, you should
 * define DAMAGED_NETMASK when you compile.  This will round
 * things off to a multiple of 8 bits.
 *
 * Finally, you may want subnet support even if your system doesn't
 * support the ioctls to get subnet mask information.  If you want
 * such a thing, you can define NETMASK to be a constant that is
 * the subnet mask for your network.
 *
 * And don't *even* get me started on how the definitions of the inet_foo()
 * routines changed between 4.2 and 4.3, making internet addresses
 * be unsigned long vs. struct in_addr.  Don't blame me if this
 * won't lint...
 */

/*
 * One structure for each interface, containing
 * the network number and subnet mask, stored in HBO.
 */
struct in_if {
	u_long	i_net;		/* Network number, shifted right */
	u_long	i_subnetmask;	/* Subnet mask for this if */
	int	i_bitshift;	/* How many bits right for outside */
};

/*
 * Table (eventually, once we malloc) of
 * internet interface subnet informaiton.
 */
static	struct in_if	*in_ifsni;

static	int		if_count;

/*
 * Get the network interface configuration,
 * and squirrel away the network numbers and
 * subnet masks of each interface.  Return
 * number of interfaces found, or -1 on error.
 * N.B.: don't call this more than once...
 */

getifconf()
{
#ifndef NETMASK
	register int	i, j;
	int		s;
	struct ifconf	ifc;
	char		buf[1024];
	register struct ifreq	*ifr;
	u_long		inet_netof();
	u_long		addr;

	/*
	 * Find out how many interfaces we have, and malloc
	 * room for information about each one.
	 */

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		return (-1);

	ifc.ifc_buf = buf;
	ifc.ifc_len = sizeof (buf);

	if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
		(void) close(s);
		return (-1);
	}

	/*
	 * if_count here is the count of possible
	 * interfaces we may be interested in... actual
	 * interfaces may be less (some may not be internet,
	 * not all are necessarily up, etc.)
	 */

	if_count = ifc.ifc_len / sizeof (struct ifreq);

	in_ifsni = (struct in_if *) malloc(if_count * sizeof (struct in_if));
	if (in_ifsni == 0) {
		(void) close(s);
		return (-1);
	}

	for (i = j = 0; i < if_count; ++i) {
		ifr = &ifc.ifc_req[i];
		if (ioctl(s, SIOCGIFFLAGS, ifr) < 0)
			continue;
		if ((ifr->ifr_flags & IFF_UP) == 0)
			continue;
		if (ioctl(s, SIOCGIFADDR, ifr) < 0)
			continue;
		if (ifr->ifr_addr.sa_family != AF_INET)
			continue;
		addr = (*(struct sockaddr_in *)&ifr->ifr_addr).sin_addr.s_addr;
		in_ifsni[j].i_net = inet_netof(addr);
		if (ioctl(s, SIOCGIFNETMASK, ifr) < 0)
			continue;
		in_ifsni[j].i_subnetmask =
		 ntohl((*(struct sockaddr_in *)&ifr->ifr_addr).sin_addr.s_addr);
		/*
		 * The following should "never happen".  But under SunOs
		 * 3.4, along with the rest of their broken networking code,
		 * SIOCGIFNETMASK can get a netmask which is 0.  There
		 * really isn't anything that "right" that we can do
		 * about it, so we'll set their subnet mask to be their
		 * *net*work mask.  Which may or may not be right.
		 */
		if (in_ifsni[j].i_subnetmask == 0) {
			addr = ntohl(addr);
			if (IN_CLASSA(addr))
				in_ifsni[j].i_subnetmask = IN_CLASSA_NET;
			else if (IN_CLASSB(addr))
				in_ifsni[j].i_subnetmask = IN_CLASSB_NET;
			else if (IN_CLASSC(addr))
				in_ifsni[j].i_subnetmask = IN_CLASSC_NET;
			else			/* what to do ... */
				in_ifsni[j].i_subnetmask = IN_CLASSC_NET;
		} else
			in_ifsni[j].i_bitshift = bsr(in_ifsni[j].i_subnetmask);
		j++;
	}

	if_count = j;

	(void) close(s);

	return (if_count);

#else	/* hard-coded subnets */

	if_count = 1;

	in_ifsni = (struct in_if *) malloc(if_count * sizeof (struct in_if));
	if (in_ifsni == 0) {
		return (-1);
	}
	in_ifsni[0].i_net = 0;
	in_ifsni[0].i_subnetmask = NETMASK;
	in_ifsni[0].i_bitshift = bsr(in_ifsni[0].i_subnetmask);
	return (if_count);
#endif
}


/*
 * Return the (sub)network number from an internet address.
 * "in" is in NBO, return value in host byte order.
 * If "in" is not a subnet, return 0.
 */

u_long
inet_snetof(in)
	u_long	in;
{
	register int	j;
	register u_long	i = ntohl(in);
	register u_long	net;
	u_long		inet_netof(), inet_lnaof();

	net = inet_netof(in);

	/*
	 * Check whether network is a subnet;
	 * if so, return subnet number.
	 */
	for (j = 0; j < if_count; ++j)
#ifdef NETMASK
		if (1) {
#else
		if (net == in_ifsni[j].i_net) {
#endif
			net = i & in_ifsni[j].i_subnetmask;
			if (inet_lnaof(htonl(net)) == 0)
				return (0);
			else
				return (net >> in_ifsni[j].i_bitshift);
		}

	return (0);
}


/*
 * Return the number of bits required to
 * shift right a mask into a getnetent-able entitity.
 */

bsr(mask)
	register int	mask;
{
	register int	count = 0;

	if (mask == 0)		/* "never happen", except with SunOs 3.4 */
		return (0);

	while ((mask & 1) == 0) {
		++count;
		mask >>= 1;
	}
#ifdef DAMAGED_NETMASK
	count /= 8;			/* XXX gag retch puke barf */
	count *= 8;
#endif
	return (count);
}

#endif
