Blob Blame History Raw
/*
 * Soft:        Keepalived is a failover program for the LVS project
 *              <www.linuxvirtualserver.org>. It monitor & manipulate
 *              a loadbalanced server pool using multi-layer checks.
 *
 * Part:        SNMP framework
 *
 * Authors:     Vincent Bernat <bernat@luffy.cx>
 *
 *              This program 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 General Public License for more details.
 *
 *              This program is free software; you can redistribute it and/or
 *              modify it under the terms of the GNU General Public License
 *              as published by the Free Software Foundation; either version
 *              2 of the License, or (at your option) any later version.
 *
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
 */

#include "config.h"

#include "scheduler.h"
#include "snmp.h"
#include "logger.h"
#include "global_data.h"
#include "main.h"
#include "utils.h"

#include <net-snmp/agent/agent_sysORTable.h>

static int
snmp_keepalived_log(__attribute__((unused)) int major, __attribute__((unused)) int minor, void *serverarg, __attribute__((unused)) void *clientarg)
{
	struct snmp_log_message *slm = (struct snmp_log_message*)serverarg;
	int slm_len = strlen(slm->msg);

	if (slm_len && slm->msg[slm_len-1] == '\n')
		slm_len--;
	log_message(slm->priority, "%.*s", slm_len, slm->msg);
	return 0;
}

/* Convert linux scope to InetScopeType */
unsigned long
snmp_scope(int scope)
{
	switch (scope) {
	case 0: return 14;  /* global */
	case 255: return 0; /* nowhere */
	case 254: return 1; /* host */
	case 253: return 2; /* link */
	case 200: return 5; /* site */
	default: return 0;
	}
	return 0;
}

void*
snmp_header_list_table(struct variable *vp, oid *name, size_t *length,
		  int exact, size_t *var_len, WriteMethod **write_method, list dlist)
{
	element e;
	void *scr;
	oid target, current;

	if (header_simple_table(vp, name, length, exact, var_len, write_method, -1))
		return NULL;

	if (LIST_ISEMPTY(dlist))
		return NULL;

	target = name[*length - 1];
	current = 0;

	for (e = LIST_HEAD(dlist); e; ELEMENT_NEXT(e)) {
		scr = ELEMENT_DATA(e);
		current++;
		if (current == target)
			/* Exact match */
			return scr;
		if (current < target)
			/* No match found yet */
			continue;
		if (exact)
			/* No exact match found */
			return NULL;
		/* current is the best match */
		name[*length - 1] = current;
		return scr;
	}
	/* No match found at end */
	return NULL;
}

enum snmp_global_magic {
	SNMP_KEEPALIVEDVERSION,
	SNMP_ROUTERID,
	SNMP_MAIL_SMTPSERVERADDRESSTYPE,
	SNMP_MAIL_SMTPSERVERADDRESS,
	SNMP_MAIL_SMTPSERVERTIMEOUT,
	SNMP_MAIL_EMAILFROM,
	SNMP_MAIL_EMAILADDRESS,
	SNMP_MAIL_EMAILFAULTS,
	SNMP_MAIL_SMTPSERVERPORT,
	SNMP_TRAPS,
	SNMP_LINKBEAT,
	SNMP_LVSFLUSH,
	SNMP_IPVS_64BIT_STATS,
	SNMP_NET_NAMESPACE,
	SNMP_DBUS,
	SNMP_DYNAMIC_INTERFACES,
	SNMP_SMTP_ALERT,
	SNMP_SMTP_ALERT_VRRP,
	SNMP_SMTP_ALERT_CHECKER,
};

static u_char*
snmp_scalar(struct variable *vp, oid *name, size_t *length,
		 int exact, size_t *var_len, WriteMethod **write_method)
{
	static unsigned long long_ret;

	if (header_generic(vp, name, length, exact, var_len, write_method))
		return NULL;

	switch (vp->magic) {
	case SNMP_KEEPALIVEDVERSION:
		*var_len = strlen(version_string);
		return (u_char *)version_string;
	case SNMP_ROUTERID:
		if (!global_data->router_id) return NULL;
		*var_len = strlen(global_data->router_id);
		return (u_char *)global_data->router_id;
	case SNMP_MAIL_SMTPSERVERADDRESSTYPE:
		long_ret = (global_data->smtp_server.ss_family == AF_INET6)?2:1;
		return (u_char *)&long_ret;
	case SNMP_MAIL_SMTPSERVERADDRESS:
		if (global_data->smtp_server.ss_family == AF_INET6) {
			struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&global_data->smtp_server;
			*var_len = 16;
			return (u_char *)&addr6->sin6_addr;
		} else {
			struct sockaddr_in *addr4 = (struct sockaddr_in *)&global_data->smtp_server;
			*var_len = 4;
			return (u_char *)&addr4->sin_addr;
		}
		return NULL;
	case SNMP_MAIL_SMTPSERVERPORT:
		long_ret = ntohs(inet_sockaddrport(&global_data->smtp_server));
		return (u_char *)&long_ret;
	case SNMP_MAIL_SMTPSERVERTIMEOUT:
		long_ret = global_data->smtp_connection_to / TIMER_HZ;
		return (u_char *)&long_ret;
	case SNMP_MAIL_EMAILFROM:
		if (!global_data->email_from) return NULL;
		*var_len = strlen(global_data->email_from);
		return (u_char *)global_data->email_from;
#ifdef _WITH_VRRP_
	case SNMP_MAIL_EMAILFAULTS:
		long_ret = global_data->no_email_faults?2:1;
		return (u_char *)&long_ret;
#endif
	case SNMP_TRAPS:
		long_ret = global_data->enable_traps?1:2;
		return (u_char *)&long_ret;
	case SNMP_LINKBEAT:
		long_ret = global_data->linkbeat_use_polling?2:1;
		return (u_char *)&long_ret;
#ifdef _WITH_LVS_
	case SNMP_LVSFLUSH:
		long_ret = global_data->lvs_flush?1:2;
		return (u_char *)&long_ret;
#endif
	case SNMP_IPVS_64BIT_STATS:
#ifdef _WITH_LVS_64BIT_STATS_
		long_ret = 1;
#else
		long_ret = 2;
#endif
		return (u_char *)&long_ret;
	case SNMP_NET_NAMESPACE:
#if HAVE_DECL_CLONE_NEWNET
		if (global_data->network_namespace) {
			*var_len = strlen(global_data->network_namespace);
			return (u_char *)global_data->network_namespace;
		}
#endif
		*var_len = 0;
		return (u_char *)"";
	case SNMP_DBUS:
#ifdef _WITH_DBUS_
		if (global_data->enable_dbus)
			long_ret = 1;
		else
#endif
			long_ret = 2;
		return (u_char *)&long_ret;
#ifdef _WITH_VRRP_
	case SNMP_DYNAMIC_INTERFACES:
		long_ret = global_data->dynamic_interfaces ? 1 : 2;
		return (u_char *)&long_ret;
#endif
	case SNMP_SMTP_ALERT:
		long_ret = global_data->smtp_alert == -1 ? 3 : global_data->smtp_alert ? 1 : 2;
		return (u_char *)&long_ret;
#ifdef _WITH_VRRP_
	case SNMP_SMTP_ALERT_VRRP:
		long_ret = global_data->smtp_alert_vrrp == -1 ? 3 : global_data->smtp_alert_vrrp ? 1 : 2;
		return (u_char *)&long_ret;
#endif
#ifdef _WITH_LVS_
	case SNMP_SMTP_ALERT_CHECKER:
		long_ret = global_data->smtp_alert_checker == -1 ? 3 : global_data->smtp_alert_checker ? 1 : 2;
		return (u_char *)&long_ret;
#endif
	default:
		break;
	}
	return NULL;
}

static u_char*
snmp_mail(struct variable *vp, oid *name, size_t *length,
		 int exact, size_t *var_len, WriteMethod **write_method)
{
	char *m;
	if ((m = (char *)snmp_header_list_table(vp, name, length, exact,
						 var_len, write_method,
						 global_data->email)) == NULL)
		return NULL;

	switch (vp->magic) {
	case SNMP_MAIL_EMAILADDRESS:
		*var_len = strlen(m);
		return (u_char *)m;
	default:
		break;
	}
	return NULL;
}

static const char global_name[] = "Keepalived";
static oid global_oid[] = GLOBAL_OID;
static struct variable8 global_vars[] = {
	/* version */
	{SNMP_KEEPALIVEDVERSION, ASN_OCTET_STR, RONLY, snmp_scalar, 1, {1}},
	/* routerId */
	{SNMP_ROUTERID, ASN_OCTET_STR, RONLY, snmp_scalar, 1, {2}},
	/* mail */
	{SNMP_MAIL_SMTPSERVERADDRESSTYPE, ASN_INTEGER, RONLY, snmp_scalar, 2, {3, 1}},
	{SNMP_MAIL_SMTPSERVERADDRESS, ASN_OCTET_STR, RONLY, snmp_scalar, 2, {3, 2}},
	{SNMP_MAIL_SMTPSERVERTIMEOUT, ASN_UNSIGNED, RONLY, snmp_scalar, 2, {3, 3}},
	{SNMP_MAIL_EMAILFROM, ASN_OCTET_STR, RONLY, snmp_scalar, 2, {3, 4}},
	/* emailTable */
	{SNMP_MAIL_EMAILADDRESS, ASN_OCTET_STR, RONLY, snmp_mail, 4, {3, 5, 1, 2}},
	/* SMTP server port */
	{SNMP_MAIL_SMTPSERVERPORT, ASN_UNSIGNED, RONLY, snmp_scalar, 2, {3, 6}},
	/* are vrrp fault state transitions emailed */
	{SNMP_MAIL_EMAILFAULTS, ASN_INTEGER, RONLY, snmp_scalar, 2, {3, 7}},
	{SNMP_SMTP_ALERT, ASN_INTEGER, RONLY, snmp_scalar, 2, {3, 8}},
#ifdef _WITH_VRRP_
	{SNMP_SMTP_ALERT_VRRP, ASN_INTEGER, RONLY, snmp_scalar, 2, {3, 9}},
#endif
#ifdef _WITH_LVS_
	{SNMP_SMTP_ALERT_CHECKER, ASN_INTEGER, RONLY, snmp_scalar, 2, {3, 10}},
#endif
	/* trapEnable */
	{SNMP_TRAPS, ASN_INTEGER, RONLY, snmp_scalar, 1, {4}},
	/* linkBeat */
	{SNMP_LINKBEAT, ASN_INTEGER, RONLY, snmp_scalar, 1, {5}},
	/* lvsFlush */
	{SNMP_LVSFLUSH, ASN_INTEGER, RONLY, snmp_scalar, 1, {6}},
#ifdef _WITH_LVS_64BIT_STATS_
	/* LVS 64-bit stats */
	{SNMP_IPVS_64BIT_STATS, ASN_INTEGER, RONLY, snmp_scalar, 1, {7}},
#endif
	{SNMP_NET_NAMESPACE, ASN_OCTET_STR, RONLY, snmp_scalar, 1, {8}},
#ifdef _WITH_DBUS_
	{SNMP_DBUS, ASN_INTEGER, RONLY, snmp_scalar, 1, {9}},
#endif
#ifdef _WITH_VRRP_
	{SNMP_DYNAMIC_INTERFACES, ASN_INTEGER, RONLY, snmp_scalar, 1, {10}},
#endif
};

static int
snmp_setup_session_cb(__attribute__((unused)) int majorID, __attribute__((unused)) int minorID,
		      void *serverarg, __attribute__((unused)) void *clientarg)
{
	netsnmp_session *sess = serverarg;
	if (serverarg == NULL)
		return 0;
	/*
	 * Because ping are done synchronously, we do everything to
	 * avoid to block too long. Better disconnect from the master
	 * agent than waiting for him...
	 */
	sess->timeout = ONE_SEC / 3;
	sess->retries = 0;
	return 0;
}

void snmp_register_mib(oid *myoid, size_t len, const char *name,
		       struct variable *variables, size_t varsize, size_t varlen)
{
	char name_buf[80];

	if (register_mib(name, (struct variable *) variables, varsize,
			 varlen, myoid, len) != MIB_REGISTERED_OK)
		log_message(LOG_WARNING, "Unable to register %s MIB", name);

	snprintf(name_buf, sizeof(name_buf), "The MIB module for %s", name);
	register_sysORTable(myoid, len, name_buf);
}

void
snmp_unregister_mib(oid *myoid, size_t len)
{
	unregister_sysORTable(myoid, len);
}

void
snmp_agent_init(const char *snmp_socket, bool base_mib)
{
	log_message(LOG_INFO, "Starting SNMP subagent");
	netsnmp_enable_subagent();
	snmp_disable_log();
	snmp_enable_calllog();
	snmp_register_callback(SNMP_CALLBACK_LIBRARY,
			       SNMP_CALLBACK_LOGGING,
			       snmp_keepalived_log,
			       NULL);

	/* Do not handle persistent states */
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
	    NETSNMP_DS_LIB_DONT_PERSIST_STATE, TRUE);
	/* Do not load any MIB */
	setenv("MIBS", "", 1);
	/*
	 * We also register a callback to modify default timeout and
	 * retries value.
	 */
	snmp_register_callback(SNMP_CALLBACK_LIBRARY,
			       SNMP_CALLBACK_SESSION_INIT,
			       snmp_setup_session_cb, NULL);
	/* Specify the socket to master agent, if provided */
	if (snmp_socket != NULL) {
		netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
				      NETSNMP_DS_AGENT_X_SOCKET,
				      snmp_socket);
	}
	/*
	 * Ping AgentX less often than every 15 seconds: pinging can
	 * block keepalived. We check every 2 minutes.
	 */
	netsnmp_ds_set_int(NETSNMP_DS_APPLICATION_ID,
			   NETSNMP_DS_AGENT_AGENTX_PING_INTERVAL, 120);

	/* Tell library not to raise SIGALRM */
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_ALARM_DONT_USE_SIG, 1);

	init_agent(global_name);
	if (base_mib)
		snmp_register_mib(global_oid, OID_LENGTH(global_oid), global_name,
				  (struct variable *)global_vars,
				  sizeof(struct variable8),
				  sizeof(global_vars)/sizeof(struct variable8));
	init_snmp(global_name);

	master->snmp_timer_thread = thread_add_timer(master, snmp_timeout_thread, 0, TIMER_NEVER);

	snmp_running = true;
}

void
snmp_agent_close(bool base_mib)
{
	if (base_mib)
		snmp_unregister_mib(global_oid, OID_LENGTH(global_oid));
	snmp_shutdown(global_name);

	snmp_running = false;
}

#ifdef THREAD_DUMP
void
register_snmp_addresses(void)
{
}
#endif