/*
 * Copyright (c) 2001-2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 *	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Begemot: bsnmp/snmp_mibII/mibII_ip.c,v 1.11 2005/05/23 09:03:40 brandt_h Exp $
 *
 * ip group scalars.
 */
#include "mibII.h"
#include "mibII_oid.h"
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp_var.h>

static struct ipstat ipstat;
static u_int	ip_idrop;
static struct icmpstat icmpstat;

static int	ip_forwarding;
static int	ip_defttl;
static uint64_t ip_tick;

static uint64_t ipstat_tick;

static int
fetch_ipstat(void)
{
	size_t len;

	len = sizeof(ipstat);
	if (sysctlbyname("net.inet.ip.stats", &ipstat, &len, NULL, 0) == -1) {
		syslog(LOG_ERR, "net.inet.ip.stats: %m");
		return (-1);
	}
	if (len != sizeof(ipstat)) {
		syslog(LOG_ERR, "net.inet.ip.stats: wrong size");
		return (-1);
	}
	len = sizeof(ip_idrop);
	if (sysctlbyname("net.inet.ip.intr_queue_drops", &ip_idrop, &len, NULL, 0) == -1)
		syslog(LOG_WARNING, "net.inet.ip.intr_queue_drops: %m");
	if (len != sizeof(ip_idrop)) {
		syslog(LOG_WARNING, "net.inet.ip.intr_queue_drops: wrong size");
		ip_idrop = 0;
	}
	len = sizeof(icmpstat);
	if (sysctlbyname("net.inet.icmp.stats", &icmpstat, &len, NULL, 0) == -1) {
		syslog(LOG_ERR, "net.inet.icmp.stats: %m");
		return (-1);
	}
	if (len != sizeof(icmpstat)) {
		syslog(LOG_ERR, "net.inet.icmp.stats: wrong size");
		return (-1);
	}

	ipstat_tick = get_ticks();
	return (0);
}

static int
fetch_ip(void)
{
	size_t len;

	len = sizeof(ip_forwarding);
	if (sysctlbyname("net.inet.ip.forwarding", &ip_forwarding, &len,
	    NULL, 0) == -1) {
		syslog(LOG_ERR, "net.inet.ip.forwarding: %m");
		return (-1);
	}
	if (len != sizeof(ip_forwarding)) {
		syslog(LOG_ERR, "net.inet.ip.forwarding: wrong size");
		return (-1);
	}

	len = sizeof(ip_defttl);
	if (sysctlbyname("net.inet.ip.ttl", &ip_defttl, &len,
	    NULL, 0) == -1) {
		syslog(LOG_ERR, "net.inet.ip.ttl: %m");
		return (-1);
	}
	if (len != sizeof(ip_defttl)) {
		syslog(LOG_ERR, "net.inet.ip.ttl: wrong size");
		return (-1);
	}

	ip_tick = get_ticks();
	return (0);
}

static int
ip_forward(int forw, int *old)
{
	size_t olen;

	olen = sizeof(*old);
	if (sysctlbyname("net.inet.ip.forwarding", old, old ? &olen : NULL,
	    &forw, sizeof(forw)) == -1) {
		syslog(LOG_ERR, "set net.inet.ip.forwarding: %m");
		return (-1);
	}
	ip_forwarding = forw;
	return (0);
}

static int
ip_setttl(int ttl, int *old)
{
	size_t olen;

	olen = sizeof(*old);
	if (sysctlbyname("net.inet.ip.ttl", old, old ? &olen : NULL,
	    &ttl, sizeof(ttl)) == -1) {
		syslog(LOG_ERR, "set net.inet.ip.ttl: %m");
		return (-1);
	}
	ip_defttl = ttl;
	return (0);
}

/*
 * READ/WRITE ip group.
 */
int
op_ip(struct snmp_context *ctx, struct snmp_value *value,
    u_int sub, u_int idx __unused, enum snmp_op op)
{
	int old;

	switch (op) {

	  case SNMP_OP_GETNEXT:
		abort();

	  case SNMP_OP_GET:
		break;

	  case SNMP_OP_SET:
		if (ip_tick < this_tick)
			if (fetch_ip() == -1)
				return (SNMP_ERR_GENERR);

		switch (value->var.subs[sub - 1]) {

		  case LEAF_ipForwarding:
			ctx->scratch->int1 = ip_forwarding ? 1 : 2;
			ctx->scratch->int2 = value->v.integer;
			if (value->v.integer == 1) {
				if (!ip_forwarding && ip_forward(1, &old))
					return (SNMP_ERR_GENERR);
				ctx->scratch->int1 = old ? 1 : 2;
			} else if (value->v.integer == 2) {
				if (ip_forwarding && ip_forward(0, &old))
					return (SNMP_ERR_GENERR);
				ctx->scratch->int1 = old;
			} else
				return (SNMP_ERR_WRONG_VALUE);
			break;

		  case LEAF_ipDefaultTTL:
			ctx->scratch->int1 = ip_defttl;
			ctx->scratch->int2 = value->v.integer;
			if (value->v.integer < 1 || value->v.integer > 255)
				return (SNMP_ERR_WRONG_VALUE);
			if (ip_defttl != value->v.integer &&
			    ip_setttl(value->v.integer, &old))
				return (SNMP_ERR_GENERR);
			ctx->scratch->int1 = old;
			break;
		}
		return (SNMP_ERR_NOERROR);

	  case SNMP_OP_ROLLBACK:
		switch (value->var.subs[sub - 1]) {

		  case LEAF_ipForwarding:
			if (ctx->scratch->int1 == 1) {
				if (ctx->scratch->int2 == 2)
					(void)ip_forward(1, NULL);
			} else {
				if (ctx->scratch->int2 == 1)
					(void)ip_forward(0, NULL);
			}
			break;

		  case LEAF_ipDefaultTTL:
			if (ctx->scratch->int1 != ctx->scratch->int2)
				(void)ip_setttl(ctx->scratch->int1, NULL);
			break;
		}
		return (SNMP_ERR_NOERROR);

	  case SNMP_OP_COMMIT:
		return (SNMP_ERR_NOERROR);
	}

	if (ip_tick < this_tick)
		if (fetch_ip() == -1)
			return (SNMP_ERR_GENERR);

	switch (value->var.subs[sub - 1]) {

	  case LEAF_ipForwarding:
		value->v.integer = ip_forwarding ? 1 : 2;
		break;

	  case LEAF_ipDefaultTTL:
		value->v.integer = ip_defttl;
		break;
	}
	return (SNMP_ERR_NOERROR);
}

/*
 * READ-ONLY statistics ip group.
 */
int
op_ipstat(struct snmp_context *ctx __unused, struct snmp_value *value,
    u_int sub, u_int idx __unused, enum snmp_op op)
{
	switch (op) {

	  case SNMP_OP_GETNEXT:
		abort();

	  case SNMP_OP_GET:
		break;

	  case SNMP_OP_SET:
		return (SNMP_ERR_NOT_WRITEABLE);

	  case SNMP_OP_ROLLBACK:
	  case SNMP_OP_COMMIT:
		abort();
	}

	if (ipstat_tick < this_tick)
		fetch_ipstat();

	switch (value->var.subs[sub - 1]) {

	  case LEAF_ipInReceives:
		value->v.uint32 = ipstat.ips_total;
		break;

	  case LEAF_ipInHdrErrors:
		value->v.uint32 = ipstat.ips_badsum + ipstat.ips_tooshort
		    + ipstat.ips_toosmall + ipstat.ips_badhlen
		    + ipstat.ips_badlen + ipstat.ips_badvers +
		    + ipstat.ips_toolong;
		break;

	  case LEAF_ipInAddrErrors:
		value->v.uint32 = ipstat.ips_cantforward;
		break;

	  case LEAF_ipForwDatagrams:
		value->v.uint32 = ipstat.ips_forward;
		break;

	  case LEAF_ipInUnknownProtos:
		value->v.uint32 = ipstat.ips_noproto;
		break;

	  case LEAF_ipInDiscards:
		value->v.uint32 = ip_idrop;
		break;

	  case LEAF_ipInDelivers:
		value->v.uint32 = ipstat.ips_delivered;
		break;

	  case LEAF_ipOutRequests:
		value->v.uint32 = ipstat.ips_localout;
		break;

	  case LEAF_ipOutDiscards:
		value->v.uint32 = ipstat.ips_odropped;
		break;

	  case LEAF_ipOutNoRoutes:
		value->v.uint32 = ipstat.ips_noroute;
		break;

	  case LEAF_ipReasmTimeout:
		value->v.integer = IPFRAGTTL;
		break;

	  case LEAF_ipReasmReqds:
		value->v.uint32 = ipstat.ips_fragments;
		break;

	  case LEAF_ipReasmOKs:
		value->v.uint32 = ipstat.ips_reassembled;
		break;

	  case LEAF_ipReasmFails:
		value->v.uint32 = ipstat.ips_fragdropped
		    + ipstat.ips_fragtimeout;
		break;

	  case LEAF_ipFragOKs:
		value->v.uint32 = ipstat.ips_fragmented;
		break;

	  case LEAF_ipFragFails:
		value->v.uint32 = ipstat.ips_cantfrag;
		break;

	  case LEAF_ipFragCreates:
		value->v.uint32 = ipstat.ips_ofragments;
		break;
	}
	return (SNMP_ERR_NOERROR);
}

/*
 * READ-ONLY statistics icmp group.
 */
int
op_icmpstat(struct snmp_context *ctx __unused, struct snmp_value *value,
    u_int sub, u_int idx __unused, enum snmp_op op)
{
	u_int i;

	switch (op) {

	  case SNMP_OP_GETNEXT:
		abort();

	  case SNMP_OP_GET:
		break;

	  case SNMP_OP_SET:
		return (SNMP_ERR_NOT_WRITEABLE);

	  case SNMP_OP_ROLLBACK:
	  case SNMP_OP_COMMIT:
		abort();
	}

	if (ipstat_tick < this_tick)
		fetch_ipstat();

	switch (value->var.subs[sub - 1]) {

	  case LEAF_icmpInMsgs:
		value->v.integer = 0;
		for (i = 0; i <= ICMP_MAXTYPE; i++)
			value->v.integer += icmpstat.icps_inhist[i];
		value->v.integer += icmpstat.icps_tooshort +
		    icmpstat.icps_checksum;
		/* missing: bad type and packets on faith */
		break;

	  case LEAF_icmpInErrors:
		value->v.integer = icmpstat.icps_tooshort +
		    icmpstat.icps_checksum +
		    icmpstat.icps_badlen +
		    icmpstat.icps_badcode +
		    icmpstat.icps_bmcastecho +
		    icmpstat.icps_bmcasttstamp;
		break;

	  case LEAF_icmpInDestUnreachs:
		value->v.integer = icmpstat.icps_inhist[ICMP_UNREACH];
		break;

	  case LEAF_icmpInTimeExcds:
		value->v.integer = icmpstat.icps_inhist[ICMP_TIMXCEED];
		break;

	  case LEAF_icmpInParmProbs:
		value->v.integer = icmpstat.icps_inhist[ICMP_PARAMPROB];
		break;

	  case LEAF_icmpInSrcQuenchs:
		value->v.integer = icmpstat.icps_inhist[ICMP_SOURCEQUENCH];
		break;

	  case LEAF_icmpInRedirects:
		value->v.integer = icmpstat.icps_inhist[ICMP_REDIRECT];
		break;

	  case LEAF_icmpInEchos:
		value->v.integer = icmpstat.icps_inhist[ICMP_ECHO];
		break;

	  case LEAF_icmpInEchoReps:
		value->v.integer = icmpstat.icps_inhist[ICMP_ECHOREPLY];
		break;

	  case LEAF_icmpInTimestamps:
		value->v.integer = icmpstat.icps_inhist[ICMP_TSTAMP];
		break;

	  case LEAF_icmpInTimestampReps:
		value->v.integer = icmpstat.icps_inhist[ICMP_TSTAMPREPLY];
		break;

	  case LEAF_icmpInAddrMasks:
		value->v.integer = icmpstat.icps_inhist[ICMP_MASKREQ];
		break;

	  case LEAF_icmpInAddrMaskReps:
		value->v.integer = icmpstat.icps_inhist[ICMP_MASKREPLY];
		break;

	  case LEAF_icmpOutMsgs:
		value->v.integer = 0;
		for (i = 0; i <= ICMP_MAXTYPE; i++)
			value->v.integer += icmpstat.icps_outhist[i];
		value->v.integer += icmpstat.icps_badaddr +
		    icmpstat.icps_noroute;
		break;

	  case LEAF_icmpOutErrors:
		value->v.integer = icmpstat.icps_badaddr +
		    icmpstat.icps_noroute;
		break;

	  case LEAF_icmpOutDestUnreachs:
		value->v.integer = icmpstat.icps_outhist[ICMP_UNREACH];
		break;

	  case LEAF_icmpOutTimeExcds:
		value->v.integer = icmpstat.icps_outhist[ICMP_TIMXCEED];
		break;

	  case LEAF_icmpOutParmProbs:
		value->v.integer = icmpstat.icps_outhist[ICMP_PARAMPROB];
		break;

	  case LEAF_icmpOutSrcQuenchs:
		value->v.integer = icmpstat.icps_outhist[ICMP_SOURCEQUENCH];
		break;

	  case LEAF_icmpOutRedirects:
		value->v.integer = icmpstat.icps_outhist[ICMP_REDIRECT];
		break;

	  case LEAF_icmpOutEchos:
		value->v.integer = icmpstat.icps_outhist[ICMP_ECHO];
		break;

	  case LEAF_icmpOutEchoReps:
		value->v.integer = icmpstat.icps_outhist[ICMP_ECHOREPLY];
		break;

	  case LEAF_icmpOutTimestamps:
		value->v.integer = icmpstat.icps_outhist[ICMP_TSTAMP];
		break;

	  case LEAF_icmpOutTimestampReps:
		value->v.integer = icmpstat.icps_outhist[ICMP_TSTAMPREPLY];
		break;

	  case LEAF_icmpOutAddrMasks:
		value->v.integer = icmpstat.icps_outhist[ICMP_MASKREQ];
		break;

	  case LEAF_icmpOutAddrMaskReps:
		value->v.integer = icmpstat.icps_outhist[ICMP_MASKREPLY];
		break;
	}
	return (SNMP_ERR_NOERROR);
}
