/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/tihdr.h>
#include <sys/policy.h>
#include <sys/tsol/tnet.h>

#include <inet/common.h>
#include <inet/kstatcom.h>
#include <inet/snmpcom.h>
#include <inet/mib2.h>
#include <inet/optcom.h>
#include <inet/snmpcom.h>
#include <inet/kstatcom.h>
#include <inet/udp_impl.h>

static int	udp_kstat_update(kstat_t *, int);
static int	udp_kstat2_update(kstat_t *, int);
static void	udp_sum_mib(udp_stack_t *, mib2_udp_t *);
static void	udp_clr_stats(udp_stat_t *);
static void	udp_add_stats(udp_stat_counter_t *, udp_stat_t *);
static void	udp_add_mib(mib2_udp_t *, mib2_udp_t *);
/*
 * return SNMP stuff in buffer in mpdata. We don't hold any lock and report
 * information that can be changing beneath us.
 */
mblk_t *
udp_snmp_get(queue_t *q, mblk_t *mpctl, boolean_t legacy_req)
{
	mblk_t			*mpdata;
	mblk_t			*mp_conn_ctl;
	mblk_t			*mp_attr_ctl;
	mblk_t			*mp6_conn_ctl;
	mblk_t			*mp6_attr_ctl;
	mblk_t			*mp_conn_tail;
	mblk_t			*mp_attr_tail;
	mblk_t			*mp6_conn_tail;
	mblk_t			*mp6_attr_tail;
	struct opthdr		*optp;
	mib2_udpEntry_t		ude;
	mib2_udp6Entry_t	ude6;
	mib2_transportMLPEntry_t mlp;
	int			state;
	zoneid_t		zoneid;
	int			i;
	connf_t			*connfp;
	conn_t			*connp = Q_TO_CONN(q);
	int			v4_conn_idx;
	int			v6_conn_idx;
	boolean_t		needattr;
	udp_t			*udp;
	ip_stack_t		*ipst = connp->conn_netstack->netstack_ip;
	udp_stack_t		*us = connp->conn_netstack->netstack_udp;
	mblk_t			*mp2ctl;
	mib2_udp_t		udp_mib;
	size_t			udp_mib_size, ude_size, ude6_size;


	/*
	 * make a copy of the original message
	 */
	mp2ctl = copymsg(mpctl);

	mp_conn_ctl = mp_attr_ctl = mp6_conn_ctl = NULL;
	if (mpctl == NULL ||
	    (mpdata = mpctl->b_cont) == NULL ||
	    (mp_conn_ctl = copymsg(mpctl)) == NULL ||
	    (mp_attr_ctl = copymsg(mpctl)) == NULL ||
	    (mp6_conn_ctl = copymsg(mpctl)) == NULL ||
	    (mp6_attr_ctl = copymsg(mpctl)) == NULL) {
		freemsg(mp_conn_ctl);
		freemsg(mp_attr_ctl);
		freemsg(mp6_conn_ctl);
		freemsg(mpctl);
		freemsg(mp2ctl);
		return (0);
	}

	zoneid = connp->conn_zoneid;

	if (legacy_req) {
		udp_mib_size = LEGACY_MIB_SIZE(&udp_mib, mib2_udp_t);
		ude_size = LEGACY_MIB_SIZE(&ude, mib2_udpEntry_t);
		ude6_size = LEGACY_MIB_SIZE(&ude6, mib2_udp6Entry_t);
	} else {
		udp_mib_size = sizeof (mib2_udp_t);
		ude_size = sizeof (mib2_udpEntry_t);
		ude6_size = sizeof (mib2_udp6Entry_t);
	}

	bzero(&udp_mib, sizeof (udp_mib));
	/* fixed length structure for IPv4 and IPv6 counters */
	SET_MIB(udp_mib.udpEntrySize, ude_size);
	SET_MIB(udp_mib.udp6EntrySize, ude6_size);

	udp_sum_mib(us, &udp_mib);

	/*
	 * Synchronize 32- and 64-bit counters.  Note that udpInDatagrams and
	 * udpOutDatagrams are not updated anywhere in UDP.  The new 64 bits
	 * counters are used.  Hence the old counters' values in us_sc_mib
	 * are always 0.
	 */
	SYNC32_MIB(&udp_mib, udpInDatagrams, udpHCInDatagrams);
	SYNC32_MIB(&udp_mib, udpOutDatagrams, udpHCOutDatagrams);

	optp = (struct opthdr *)&mpctl->b_rptr[sizeof (struct T_optmgmt_ack)];
	optp->level = MIB2_UDP;
	optp->name = 0;
	(void) snmp_append_data(mpdata, (char *)&udp_mib, udp_mib_size);
	optp->len = msgdsize(mpdata);
	qreply(q, mpctl);

	mp_conn_tail = mp_attr_tail = mp6_conn_tail = mp6_attr_tail = NULL;
	v4_conn_idx = v6_conn_idx = 0;

	for (i = 0; i < CONN_G_HASH_SIZE; i++) {
		connfp = &ipst->ips_ipcl_globalhash_fanout[i];
		connp = NULL;

		while ((connp = ipcl_get_next_conn(connfp, connp,
		    IPCL_UDPCONN))) {
			udp = connp->conn_udp;
			if (zoneid != connp->conn_zoneid)
				continue;

			/*
			 * Note that the port numbers are sent in
			 * host byte order
			 */

			if (udp->udp_state == TS_UNBND)
				state = MIB2_UDP_unbound;
			else if (udp->udp_state == TS_IDLE)
				state = MIB2_UDP_idle;
			else if (udp->udp_state == TS_DATA_XFER)
				state = MIB2_UDP_connected;
			else
				state = MIB2_UDP_unknown;

			needattr = B_FALSE;
			bzero(&mlp, sizeof (mlp));
			if (connp->conn_mlp_type != mlptSingle) {
				if (connp->conn_mlp_type == mlptShared ||
				    connp->conn_mlp_type == mlptBoth)
					mlp.tme_flags |= MIB2_TMEF_SHARED;
				if (connp->conn_mlp_type == mlptPrivate ||
				    connp->conn_mlp_type == mlptBoth)
					mlp.tme_flags |= MIB2_TMEF_PRIVATE;
				needattr = B_TRUE;
			}
			if (connp->conn_anon_mlp) {
				mlp.tme_flags |= MIB2_TMEF_ANONMLP;
				needattr = B_TRUE;
			}
			switch (connp->conn_mac_mode) {
			case CONN_MAC_DEFAULT:
				break;
			case CONN_MAC_AWARE:
				mlp.tme_flags |= MIB2_TMEF_MACEXEMPT;
				needattr = B_TRUE;
				break;
			case CONN_MAC_IMPLICIT:
				mlp.tme_flags |= MIB2_TMEF_MACIMPLICIT;
				needattr = B_TRUE;
				break;
			}
			mutex_enter(&connp->conn_lock);
			if (udp->udp_state == TS_DATA_XFER &&
			    connp->conn_ixa->ixa_tsl != NULL) {
				ts_label_t *tsl;

				tsl = connp->conn_ixa->ixa_tsl;
				mlp.tme_flags |= MIB2_TMEF_IS_LABELED;
				mlp.tme_doi = label2doi(tsl);
				mlp.tme_label = *label2bslabel(tsl);
				needattr = B_TRUE;
			}
			mutex_exit(&connp->conn_lock);

			/*
			 * Create an IPv4 table entry for IPv4 entries and also
			 * any IPv6 entries which are bound to in6addr_any
			 * (i.e. anything a IPv4 peer could connect/send to).
			 */
			if (connp->conn_ipversion == IPV4_VERSION ||
			    (udp->udp_state <= TS_IDLE &&
			    IN6_IS_ADDR_UNSPECIFIED(&connp->conn_laddr_v6))) {
				ude.udpEntryInfo.ue_state = state;
				/*
				 * If in6addr_any this will set it to
				 * INADDR_ANY
				 */
				ude.udpLocalAddress = connp->conn_laddr_v4;
				ude.udpLocalPort = ntohs(connp->conn_lport);
				if (udp->udp_state == TS_DATA_XFER) {
					/*
					 * Can potentially get here for
					 * v6 socket if another process
					 * (say, ping) has just done a
					 * sendto(), changing the state
					 * from the TS_IDLE above to
					 * TS_DATA_XFER by the time we hit
					 * this part of the code.
					 */
					ude.udpEntryInfo.ue_RemoteAddress =
					    connp->conn_faddr_v4;
					ude.udpEntryInfo.ue_RemotePort =
					    ntohs(connp->conn_fport);
				} else {
					ude.udpEntryInfo.ue_RemoteAddress = 0;
					ude.udpEntryInfo.ue_RemotePort = 0;
				}

				/*
				 * We make the assumption that all udp_t
				 * structs will be created within an address
				 * region no larger than 32-bits.
				 */
				ude.udpInstance = (uint32_t)(uintptr_t)udp;
				ude.udpCreationProcess =
				    (connp->conn_cpid < 0) ?
				    MIB2_UNKNOWN_PROCESS :
				    connp->conn_cpid;
				ude.udpCreationTime = connp->conn_open_time;

				(void) snmp_append_data2(mp_conn_ctl->b_cont,
				    &mp_conn_tail, (char *)&ude, ude_size);
				mlp.tme_connidx = v4_conn_idx++;
				if (needattr)
					(void) snmp_append_data2(
					    mp_attr_ctl->b_cont, &mp_attr_tail,
					    (char *)&mlp, sizeof (mlp));
			}
			if (connp->conn_ipversion == IPV6_VERSION) {
				ude6.udp6EntryInfo.ue_state  = state;
				ude6.udp6LocalAddress = connp->conn_laddr_v6;
				ude6.udp6LocalPort = ntohs(connp->conn_lport);
				mutex_enter(&connp->conn_lock);
				if (connp->conn_ixa->ixa_flags &
				    IXAF_SCOPEID_SET) {
					ude6.udp6IfIndex =
					    connp->conn_ixa->ixa_scopeid;
				} else {
					ude6.udp6IfIndex = connp->conn_bound_if;
				}
				mutex_exit(&connp->conn_lock);
				if (udp->udp_state == TS_DATA_XFER) {
					ude6.udp6EntryInfo.ue_RemoteAddress =
					    connp->conn_faddr_v6;
					ude6.udp6EntryInfo.ue_RemotePort =
					    ntohs(connp->conn_fport);
				} else {
					ude6.udp6EntryInfo.ue_RemoteAddress =
					    sin6_null.sin6_addr;
					ude6.udp6EntryInfo.ue_RemotePort = 0;
				}
				/*
				 * We make the assumption that all udp_t
				 * structs will be created within an address
				 * region no larger than 32-bits.
				 */
				ude6.udp6Instance = (uint32_t)(uintptr_t)udp;
				ude6.udp6CreationProcess =
				    (connp->conn_cpid < 0) ?
				    MIB2_UNKNOWN_PROCESS :
				    connp->conn_cpid;
				ude6.udp6CreationTime = connp->conn_open_time;

				(void) snmp_append_data2(mp6_conn_ctl->b_cont,
				    &mp6_conn_tail, (char *)&ude6, ude6_size);
				mlp.tme_connidx = v6_conn_idx++;
				if (needattr)
					(void) snmp_append_data2(
					    mp6_attr_ctl->b_cont,
					    &mp6_attr_tail, (char *)&mlp,
					    sizeof (mlp));
			}
		}
	}

	/* IPv4 UDP endpoints */
	optp = (struct opthdr *)&mp_conn_ctl->b_rptr[
	    sizeof (struct T_optmgmt_ack)];
	optp->level = MIB2_UDP;
	optp->name = MIB2_UDP_ENTRY;
	optp->len = msgdsize(mp_conn_ctl->b_cont);
	qreply(q, mp_conn_ctl);

	/* table of MLP attributes... */
	optp = (struct opthdr *)&mp_attr_ctl->b_rptr[
	    sizeof (struct T_optmgmt_ack)];
	optp->level = MIB2_UDP;
	optp->name = EXPER_XPORT_MLP;
	optp->len = msgdsize(mp_attr_ctl->b_cont);
	if (optp->len == 0)
		freemsg(mp_attr_ctl);
	else
		qreply(q, mp_attr_ctl);

	/* IPv6 UDP endpoints */
	optp = (struct opthdr *)&mp6_conn_ctl->b_rptr[
	    sizeof (struct T_optmgmt_ack)];
	optp->level = MIB2_UDP6;
	optp->name = MIB2_UDP6_ENTRY;
	optp->len = msgdsize(mp6_conn_ctl->b_cont);
	qreply(q, mp6_conn_ctl);

	/* table of MLP attributes... */
	optp = (struct opthdr *)&mp6_attr_ctl->b_rptr[
	    sizeof (struct T_optmgmt_ack)];
	optp->level = MIB2_UDP6;
	optp->name = EXPER_XPORT_MLP;
	optp->len = msgdsize(mp6_attr_ctl->b_cont);
	if (optp->len == 0)
		freemsg(mp6_attr_ctl);
	else
		qreply(q, mp6_attr_ctl);

	return (mp2ctl);
}

/*
 * Return 0 if invalid set request, 1 otherwise, including non-udp requests.
 * NOTE: Per MIB-II, UDP has no writable data.
 * TODO:  If this ever actually tries to set anything, it needs to be
 * to do the appropriate locking.
 */
/* ARGSUSED */
int
udp_snmp_set(queue_t *q, t_scalar_t level, t_scalar_t name,
    uchar_t *ptr, int len)
{
	switch (level) {
	case MIB2_UDP:
		return (0);
	default:
		return (1);
	}
}

void
udp_kstat_fini(netstackid_t stackid, kstat_t *ksp)
{
	if (ksp != NULL) {
		ASSERT(stackid == (netstackid_t)(uintptr_t)ksp->ks_private);
		kstat_delete_netstack(ksp, stackid);
	}
}

/*
 * To add stats from one mib2_udp_t to another.  Static fields are not added.
 * The caller should set them up propertly.
 */
static void
udp_add_mib(mib2_udp_t *from, mib2_udp_t *to)
{
	to->udpHCInDatagrams += from->udpHCInDatagrams;
	to->udpInErrors += from->udpInErrors;
	to->udpHCOutDatagrams += from->udpHCOutDatagrams;
	to->udpOutErrors += from->udpOutErrors;
}


void *
udp_kstat2_init(netstackid_t stackid)
{
	kstat_t *ksp;

	udp_stat_t template = {
		{ "udp_sock_fallback",		KSTAT_DATA_UINT64 },
		{ "udp_out_opt",		KSTAT_DATA_UINT64 },
		{ "udp_out_err_notconn",	KSTAT_DATA_UINT64 },
		{ "udp_out_err_output",		KSTAT_DATA_UINT64 },
		{ "udp_out_err_tudr",		KSTAT_DATA_UINT64 },
#ifdef DEBUG
		{ "udp_data_conn",		KSTAT_DATA_UINT64 },
		{ "udp_data_notconn",		KSTAT_DATA_UINT64 },
		{ "udp_out_lastdst",		KSTAT_DATA_UINT64 },
		{ "udp_out_diffdst",		KSTAT_DATA_UINT64 },
		{ "udp_out_ipv6",		KSTAT_DATA_UINT64 },
		{ "udp_out_mapped",		KSTAT_DATA_UINT64 },
		{ "udp_out_ipv4",		KSTAT_DATA_UINT64 },
#endif
	};

	ksp = kstat_create_netstack(UDP_MOD_NAME, 0, "udpstat", "net",
	    KSTAT_TYPE_NAMED, sizeof (template) / sizeof (kstat_named_t),
	    0, stackid);

	if (ksp == NULL)
		return (NULL);

	bcopy(&template, ksp->ks_data, sizeof (template));
	ksp->ks_update = udp_kstat2_update;
	ksp->ks_private = (void *)(uintptr_t)stackid;

	kstat_install(ksp);
	return (ksp);
}

void
udp_kstat2_fini(netstackid_t stackid, kstat_t *ksp)
{
	if (ksp != NULL) {
		ASSERT(stackid == (netstackid_t)(uintptr_t)ksp->ks_private);
		kstat_delete_netstack(ksp, stackid);
	}
}

/*
 * To copy counters from the per CPU udpp_stat_counter_t to the stack
 * udp_stat_t.
 */
static void
udp_add_stats(udp_stat_counter_t *from, udp_stat_t *to)
{
	to->udp_sock_fallback.value.ui64 += from->udp_sock_fallback;
	to->udp_out_opt.value.ui64 += from->udp_out_opt;
	to->udp_out_err_notconn.value.ui64 += from->udp_out_err_notconn;
	to->udp_out_err_output.value.ui64 += from->udp_out_err_output;
	to->udp_out_err_tudr.value.ui64 += from->udp_out_err_tudr;
#ifdef DEBUG
	to->udp_data_conn.value.ui64 += from->udp_data_conn;
	to->udp_data_notconn.value.ui64 += from->udp_data_notconn;
	to->udp_out_lastdst.value.ui64 += from->udp_out_lastdst;
	to->udp_out_diffdst.value.ui64 += from->udp_out_diffdst;
	to->udp_out_ipv6.value.ui64 += from->udp_out_ipv6;
	to->udp_out_mapped.value.ui64 += from->udp_out_mapped;
	to->udp_out_ipv4.value.ui64 += from->udp_out_ipv4;
#endif
}

/*
 * To set all udp_stat_t counters to 0.
 */
static void
udp_clr_stats(udp_stat_t *stats)
{
	stats->udp_sock_fallback.value.ui64 = 0;
	stats->udp_out_opt.value.ui64 = 0;
	stats->udp_out_err_notconn.value.ui64 = 0;
	stats->udp_out_err_output.value.ui64 = 0;
	stats->udp_out_err_tudr.value.ui64 = 0;
#ifdef DEBUG
	stats->udp_data_conn.value.ui64 = 0;
	stats->udp_data_notconn.value.ui64 = 0;
	stats->udp_out_lastdst.value.ui64 = 0;
	stats->udp_out_diffdst.value.ui64 = 0;
	stats->udp_out_ipv6.value.ui64 = 0;
	stats->udp_out_mapped.value.ui64 = 0;
	stats->udp_out_ipv4.value.ui64 = 0;
#endif
}

int
udp_kstat2_update(kstat_t *kp, int rw)
{
	udp_stat_t	*stats;
	netstackid_t	stackid = (netstackid_t)(uintptr_t)kp->ks_private;
	netstack_t	*ns;
	udp_stack_t	*us;
	int		i;
	int		cnt;

	if (rw == KSTAT_WRITE)
		return (EACCES);

	ns = netstack_find_by_stackid(stackid);
	if (ns == NULL)
		return (-1);
	us = ns->netstack_udp;
	if (us == NULL) {
		netstack_rele(ns);
		return (-1);
	}
	stats = (udp_stat_t *)kp->ks_data;
	udp_clr_stats(stats);

	cnt = us->us_sc_cnt;
	for (i = 0; i < cnt; i++)
		udp_add_stats(&us->us_sc[i]->udp_sc_stats, stats);

	netstack_rele(ns);
	return (0);
}

void *
udp_kstat_init(netstackid_t stackid)
{
	kstat_t	*ksp;

	udp_named_kstat_t template = {
		{ "inDatagrams",	KSTAT_DATA_UINT64, 0 },
		{ "inErrors",		KSTAT_DATA_UINT32, 0 },
		{ "outDatagrams",	KSTAT_DATA_UINT64, 0 },
		{ "entrySize",		KSTAT_DATA_INT32, 0 },
		{ "entry6Size",		KSTAT_DATA_INT32, 0 },
		{ "outErrors",		KSTAT_DATA_UINT32, 0 },
	};

	ksp = kstat_create_netstack(UDP_MOD_NAME, 0, UDP_MOD_NAME, "mib2",
	    KSTAT_TYPE_NAMED, NUM_OF_FIELDS(udp_named_kstat_t), 0, stackid);

	if (ksp == NULL)
		return (NULL);

	template.entrySize.value.ui32 = sizeof (mib2_udpEntry_t);
	template.entry6Size.value.ui32 = sizeof (mib2_udp6Entry_t);

	bcopy(&template, ksp->ks_data, sizeof (template));
	ksp->ks_update = udp_kstat_update;
	ksp->ks_private = (void *)(uintptr_t)stackid;

	kstat_install(ksp);
	return (ksp);
}

/*
 * To sum up all MIB2 stats for a udp_stack_t from all per CPU stats.  The
 * caller should initialize the target mib2_udp_t properly as this function
 * just adds up all the per CPU stats.
 */
static void
udp_sum_mib(udp_stack_t *us, mib2_udp_t *udp_mib)
{
	int i;
	int cnt;

	cnt = us->us_sc_cnt;
	for (i = 0; i < cnt; i++)
		udp_add_mib(&us->us_sc[i]->udp_sc_mib, udp_mib);
}

static int
udp_kstat_update(kstat_t *kp, int rw)
{
	udp_named_kstat_t *udpkp;
	netstackid_t	stackid = (netstackid_t)(uintptr_t)kp->ks_private;
	netstack_t	*ns;
	udp_stack_t	*us;
	mib2_udp_t	udp_mib;

	if (rw == KSTAT_WRITE)
		return (EACCES);

	ns = netstack_find_by_stackid(stackid);
	if (ns == NULL)
		return (-1);
	us = ns->netstack_udp;
	if (us == NULL) {
		netstack_rele(ns);
		return (-1);
	}
	udpkp = (udp_named_kstat_t *)kp->ks_data;

	bzero(&udp_mib, sizeof (udp_mib));
	udp_sum_mib(us, &udp_mib);

	udpkp->inDatagrams.value.ui64 =	udp_mib.udpHCInDatagrams;
	udpkp->inErrors.value.ui32 =	udp_mib.udpInErrors;
	udpkp->outDatagrams.value.ui64 = udp_mib.udpHCOutDatagrams;
	udpkp->outErrors.value.ui32 =	udp_mib.udpOutErrors;
	netstack_rele(ns);
	return (0);
}