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:        Checkers registration.
 *
 * Author:      Alexandre Cassen, <acassen@linux-vs.org>
 *
 *              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 <dlfcn.h>
#include <stdint.h>
#include <stdio.h>
#include <arpa/inet.h>

#include "check_api.h"
#include "main.h"
#include "parser.h"
#include "utils.h"
#include "logger.h"
#include "bitops.h"
#include "global_data.h"
#include "keepalived_netlink.h"
#include "check_misc.h"
#include "check_smtp.h"
#include "check_tcp.h"
#include "check_http.h"
#include "check_ssl.h"
#include "check_dns.h"
#include "ipwrapper.h"
#include "check_daemon.h"
#ifdef _WITH_BFD_
#include "check_bfd.h"
#include "bfd_event.h"
#include "bfd_daemon.h"
#endif

/* Global vars */
list checkers_queue;

/* free checker data */
static void
free_checker(void *data)
{
	checker_t *checker = data;
	(*checker->free_func) (checker);
}

/* dump checker data */
static void
dump_checker(FILE *fp, void *data)
{
	checker_t *checker = data;
	char *vs_ret;
	char *vs_sav;

	vs_ret = FMT_VS(checker->vs);
	vs_sav = MALLOC(strlen(vs_ret) + 1);
	strcpy(vs_sav, vs_ret);

	conf_write(fp, " %s -> %s", vs_sav, FMT_CHK(checker));
	FREE(vs_sav);

	(*checker->dump_func) (fp, checker);
}

void
dump_connection_opts(FILE *fp, void *data)
{
	conn_opts_t *conn = data;

	conf_write(fp, "     Dest = %s", inet_sockaddrtopair(&conn->dst));
	if (conn->bindto.ss_family)
		conf_write(fp, "     Bind to = %s", inet_sockaddrtopair(&conn->bindto));
	if (conn->bind_if[0])
		conf_write(fp, "     Bind i/f = %s", conn->bind_if);
#ifdef _WITH_SO_MARK_
	if (conn->fwmark != 0)
		conf_write(fp, "     Mark = %u", conn->fwmark);
#endif
	conf_write(fp, "     Timeout = %d", conn->connection_to/TIMER_HZ);
}

void
dump_checker_opts(FILE *fp, void *data)
{
	checker_t *checker = data;
	conn_opts_t *conn = checker->co;

	if (conn) {
		conf_write(fp, "   Connection");
		dump_connection_opts(fp, conn);
	}

	conf_write(fp, "   Alpha is %s", checker->alpha ? "ON" : "OFF");
	conf_write(fp, "   Delay loop = %lu" , checker->delay_loop / TIMER_HZ);
	if (checker->retry) {
		conf_write(fp, "   Retry count = %u" , checker->retry);
		conf_write(fp, "   Retry delay = %lu" , checker->delay_before_retry / TIMER_HZ);
	}
	conf_write(fp, "   Warmup = %lu", checker->warmup / TIMER_HZ);
}

/* Queue a checker into the checkers_queue */
checker_t *
queue_checker(void (*free_func) (void *), void (*dump_func) (FILE *, void *)
	      , int (*launch) (thread_t *)
	      , bool (*compare) (void *, void *)
	      , void *data
	      , conn_opts_t *co)
{
	virtual_server_t *vs = LIST_TAIL_DATA(check_data->vs);
	real_server_t *rs = LIST_TAIL_DATA(vs->rs);
	checker_t *checker = (checker_t *) MALLOC(sizeof (checker_t));

	/* Set default dst = RS, timeout = 5 */
	if (co) {
		co->dst = rs->addr;
		co->connection_to = 5 * TIMER_HZ;
	}

	checker->free_func = free_func;
	checker->dump_func = dump_func;
	checker->launch = launch;
	checker->compare = compare;
	checker->vs = vs;
	checker->rs = rs;
	checker->data = data;
	checker->co = co;
	checker->enabled = true;
	checker->alpha = -1;
	checker->delay_loop = ULONG_MAX;
	checker->warmup = ULONG_MAX;
	checker->retry = UINT_MAX;
	checker->delay_before_retry = ULONG_MAX;
	checker->retry_it = 0;
	checker->is_up = true;
	checker->default_delay_before_retry = 1 * TIMER_HZ;
	checker->default_retry = 1 ;

	/* queue the checker */
	list_add(checkers_queue, checker);

	return checker;
}

void
dequeue_new_checker(void)
{
	checker_t *checker = ELEMENT_DATA(checkers_queue->tail);

	if (!checker->is_up)
		set_checker_state(checker, true);

	free_list_element(checkers_queue, checkers_queue->tail);
}

bool
check_conn_opts(conn_opts_t *co)
{
	if (co->dst.ss_family == AF_INET6 &&
	    IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6*)&co->dst)->sin6_addr) &&
	    !co->bind_if[0]) {
		report_config_error(CONFIG_GENERAL_ERROR, "Checker link local address %s requires a bind_if", inet_sockaddrtos(&co->dst));
		return false;
	}

	return true;
}

bool
compare_conn_opts(conn_opts_t *a, conn_opts_t *b)
{
	if (a == b)
		return true;

	if (!a || !b)
		return false;
	if (!sockstorage_equal(&a->dst, &b->dst))
		return false;
	if (!sockstorage_equal(&a->bindto, &b->bindto))
		return false;
	if (strcmp(a->bind_if, b->bind_if))
		return false;
	if (a->connection_to != b->connection_to)
		return false;
#ifdef _WITH_SO_MARK_
	if (a->fwmark != b->fwmark)
		return false;
#endif

	return true;
}

void
checker_set_dst_port(struct sockaddr_storage *dst, uint16_t port)
{
	/* NOTE: we are relying on the offset of sin_port and sin6_port being
	 * the same if an IPv6 address is specified after the port */
	if (dst->ss_family == AF_INET6) {
		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) dst;
		addr6->sin6_port = port;
	} else {
		struct sockaddr_in *addr4 = (struct sockaddr_in *) dst;
		addr4->sin_port = port;
	}
}

/* "connect_ip" keyword */
static void
co_ip_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();

	if (inet_stosockaddr(strvec_slot(strvec, 1), NULL, &co->dst))
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid connect_ip address %s - ignoring", FMT_STR_VSLOT(strvec, 1));
	else if (co->bindto.ss_family != AF_UNSPEC &&
		 co->bindto.ss_family != co->dst.ss_family) {
		report_config_error(CONFIG_GENERAL_ERROR, "connect_ip address %s does not match address family of bindto - skipping", FMT_STR_VSLOT(strvec, 1));
		co->dst.ss_family = AF_UNSPEC;
	}
}

/* "connect_port" keyword */
static void
co_port_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();
	unsigned port;

	if (!read_unsigned_strvec(strvec, 1, &port, 1, 65535, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid checker connect_port '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}

	checker_set_dst_port(&co->dst, htons(port));
}

/* "bindto" keyword */
static void
co_srcip_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();
	if (inet_stosockaddr(strvec_slot(strvec, 1), NULL, &co->bindto))
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid bindto address %s - ignoring", FMT_STR_VSLOT(strvec, 1));
	else if (co->dst.ss_family != AF_UNSPEC &&
		 co->dst.ss_family != co->bindto.ss_family) {
		report_config_error(CONFIG_GENERAL_ERROR, "bindto address %s does not match address family of connect_ip - skipping", FMT_STR_VSLOT(strvec, 1));
		co->bindto.ss_family = AF_UNSPEC;
	}
}

/* "bind_port" keyword */
static void
co_srcport_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();
	unsigned port;

	if (!read_unsigned_strvec(strvec, 1, &port, 1, 65535, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid checker bind_port '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}

	checker_set_dst_port(&co->bindto, htons(port));
}

/* "bind_if" keyword */
static void
co_srcif_handler(vector_t *strvec)
{
	// This is needed for link local IPv6 bindto address
	conn_opts_t *co = CHECKER_GET_CO();

	if (strlen(strvec_slot(strvec, 1)) > sizeof(co->bind_if) - 1) {
		report_config_error(CONFIG_GENERAL_ERROR, "Interface name %s is too long - ignoring", FMT_STR_VSLOT(strvec, 1));
		return;
	}
	strcpy(co->bind_if, strvec_slot(strvec, 1));
}

/* "connect_timeout" keyword */
static void
co_timeout_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();
	unsigned long timer;

	if (!read_timer(strvec, 1, &timer, 1, UINT_MAX / TIMER_HZ, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "connect_timeout %s invalid - ignoring", FMT_STR_VSLOT(strvec, 1));
		return;
	}
	co->connection_to = timer;
}

#ifdef _WITH_SO_MARK_
/* "fwmark" keyword */
static void
co_fwmark_handler(vector_t *strvec)
{
	conn_opts_t *co = CHECKER_GET_CO();
	unsigned fwmark;

	if (!read_unsigned_strvec(strvec, 1, &fwmark, 0, UINT_MAX, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid fwmark connection value '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}
	co->fwmark = fwmark;
}
#endif

static void
retry_handler(vector_t *strvec)
{
	checker_t *checker = CHECKER_GET_CURRENT();
	unsigned retry;

	if (!read_unsigned_strvec(strvec, 1, &retry, 0, UINT_MAX, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid retry connection value '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}

	checker->retry = retry;
}

static void
delay_before_retry_handler(vector_t *strvec)
{
	checker_t *checker = CHECKER_GET_CURRENT();
	unsigned delay;

	if (!read_unsigned_strvec(strvec, 1, &delay, 0, UINT_MAX / TIMER_HZ, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid delay_before_retry connection value '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}

	checker->delay_before_retry = delay * TIMER_HZ;
}

/* "warmup" keyword */
static void
warmup_handler(vector_t *strvec)
{
	checker_t *checker = CHECKER_GET_CURRENT();
	unsigned warmup;

	if (!read_unsigned_strvec(strvec, 1, &warmup, 0, UINT_MAX / TIMER_HZ, true)) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid warmup connection value '%s'", FMT_STR_VSLOT(strvec, 1));
		return;
	}

	checker->warmup = warmup * TIMER_HZ;
}

static void
delay_handler(vector_t *strvec)
{
	unsigned long delay_loop;
	checker_t *checker = CHECKER_GET_CURRENT();

	if (read_timer(strvec, 1, &delay_loop, 1, 0, true))
		checker->delay_loop = delay_loop;
	else
		report_config_error(CONFIG_GENERAL_ERROR, "delay_loop '%s' is invalid - ignoring", FMT_STR_VSLOT(strvec, 1));
}

static void
alpha_handler(vector_t *strvec)
{
	checker_t *checker = CHECKER_GET_CURRENT();
	int res = true;

	if (vector_size(strvec) >= 2) {
		res = check_true_false(strvec_slot(strvec, 1));
		if (res == -1) {
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid alpha parameter %s", FMT_STR_VSLOT(strvec, 1));
			return;
		}
	}
	checker->alpha = res;
}
void
install_checker_common_keywords(bool connection_keywords)
{
	if (connection_keywords) {
		install_keyword("connect_ip", &co_ip_handler);
		install_keyword("connect_port", &co_port_handler);
		install_keyword("bindto", &co_srcip_handler);
		install_keyword("bind_port", &co_srcport_handler);
		install_keyword("bind_if", &co_srcif_handler);
		install_keyword("connect_timeout", &co_timeout_handler);
#ifdef _WITH_SO_MARK_
		install_keyword("fwmark", &co_fwmark_handler);
#endif
	}
	install_keyword("retry", &retry_handler);
	install_keyword("delay_before_retry", &delay_before_retry_handler);
	install_keyword("warmup", &warmup_handler);
	install_keyword("delay_loop", &delay_handler);
	install_keyword("alpha", &alpha_handler);
}

/* dump the checkers_queue */
void
dump_checkers_queue(FILE *fp)
{
	if (!LIST_ISEMPTY(checkers_queue)) {
		conf_write(fp, "------< Health checkers >------");
		dump_list(fp, checkers_queue);
	}
}

/* init the global checkers queue */
void
init_checkers_queue(void)
{
	checkers_queue = alloc_list(free_checker, dump_checker);
}

/* release the checkers for a virtual server */
void
free_vs_checkers(virtual_server_t *vs)
{
	element e;
	element next;
	checker_t *checker;

	if (LIST_ISEMPTY(checkers_queue))
		return;

	for (e = LIST_HEAD(checkers_queue); e; e = next) {
		next = e->next;

		checker = ELEMENT_DATA(e);
		if (checker->vs != vs)
			continue;

		free_list_element(checkers_queue, e);
	}
}

/* release the checkers_queue */
void
free_checkers_queue(void)
{
	if (!checkers_queue)
		return;

	free_list(&checkers_queue);
}

/* register checkers to the global I/O scheduler */
void
register_checkers_thread(void)
{
	checker_t *checker;
	element e;
	unsigned long warmup;

	LIST_FOREACH(checkers_queue, checker, e) {
		if (checker->launch)
		{
			if (checker->vs->ha_suspend && !checker->vs->ha_suspend_addr_count)
				checker->enabled = false;

			log_message(LOG_INFO, "%sctivating healthchecker for service %s for VS %s"
					    , checker->enabled ? "A" : "Dea", FMT_RS(checker->rs, checker->vs), FMT_VS(checker->vs));

			/* wait for a random timeout to begin checker thread.
			   It helps avoiding multiple simultaneous checks to
			   the same RS.
			*/
			warmup = checker->warmup;
			if (warmup)
				warmup = warmup * (unsigned)rand() / RAND_MAX;
			thread_add_timer(master, checker->launch, checker,
					 BOOTSTRAP_DELAY + warmup);
		}
	}

#ifdef _WITH_BFD_
	log_message(LOG_INFO, "Activating BFD healthchecker");

	/* We need to always enable this, since the bfd process may write to the pipe, and we
	 * need to ensure that messages are stripped out. */
	start_bfd_monitoring(master);
#endif
}

/* Sync checkers activity with netlink kernel reflection */
static bool
addr_matches(const virtual_server_t *vs, void *address)
{
	void *addr;
	virtual_server_group_entry_t *vsg_entry;

	if (vs->addr.ss_family != AF_UNSPEC) {
		if (vs->addr.ss_family == AF_INET6)
			addr = (void *) &((struct sockaddr_in6 *)&vs->addr)->sin6_addr;
		else
			addr = (void *) &((struct sockaddr_in *)&vs->addr)->sin_addr;

		return inaddr_equal(vs->addr.ss_family, addr, address);
	}

	if (!vs->vsg)
		return false;

	if (vs->vsg->addr_range) {
		element e;
		struct in_addr mask_addr = {0};
		struct in6_addr mask_addr6 = {{{0}}};
		unsigned addr_base;

		if (vs->af == AF_INET) {
			mask_addr = *(struct in_addr*)address;
			addr_base = ntohl(mask_addr.s_addr) & 0xFF;
			mask_addr.s_addr &= htonl(0xFFFFFF00);
		}
		else {
			mask_addr6 = *(struct in6_addr*)address;
			addr_base = ntohs(mask_addr6.s6_addr16[7]);
			mask_addr6.s6_addr16[7] = 0;
		}

		for (e = LIST_HEAD(vs->vsg->addr_range); e; ELEMENT_NEXT(e)) {
			vsg_entry = ELEMENT_DATA(e);
			struct sockaddr_storage range_addr = vsg_entry->addr;
			uint32_t ra_base;

			if (!vsg_entry->range) {
				if (vsg_entry->addr.ss_family == AF_INET6)
					addr = (void *) &((struct sockaddr_in6 *)&vsg_entry->addr)->sin6_addr;
				else
					addr = (void *) &((struct sockaddr_in *)&vsg_entry->addr)->sin_addr;

				if (inaddr_equal(vsg_entry->addr.ss_family, addr, address))
					return true;
			}
			else {
				if (range_addr.ss_family == AF_INET) {
					struct in_addr ra;

					ra = ((struct sockaddr_in *)&range_addr)->sin_addr;
					ra_base = ntohl(ra.s_addr) & 0xFF;

					if (addr_base < ra_base || addr_base > ra_base + vsg_entry->range)
						continue;

					ra.s_addr &= htonl(0xFFFFFF00);
					if (ra.s_addr != mask_addr.s_addr)
						continue;
				}
				else
				{
					struct in6_addr ra = ((struct sockaddr_in6 *)&range_addr)->sin6_addr;
					ra_base = ntohs(ra.s6_addr16[7]);

					if (addr_base < ra_base || addr_base > ra_base + vsg_entry->range)
						continue;

					ra.s6_addr16[7] = 0;
					if (!inaddr_equal(AF_INET6, &ra, &mask_addr6))
						continue;
				}

				return true;
			}
		}
	}

	return false;
}

void
update_checker_activity(sa_family_t family, void *address, bool enable)
{
	checker_t *checker;
	virtual_server_t *vs;
	element e, e1;
	char addr_str[INET6_ADDRSTRLEN];
	bool address_logged = false;

	if (__test_bit(LOG_ADDRESS_CHANGES, &debug)) {
		inet_ntop(family, address, addr_str, sizeof(addr_str));
		log_message(LOG_INFO, "Netlink reflector reports IP %s %s"
				    , addr_str, (enable) ? "added" : "removed");
		address_logged = true;
	}

	if (!using_ha_suspend)
		return;

	if (LIST_ISEMPTY(checkers_queue))
		return;

	/* Check if any of the virtual servers are using this address, and have ha_suspend */
	LIST_FOREACH(check_data->vs, vs, e) {
		if (!vs->ha_suspend)
			continue;

		/* If there is no address configured, the family will be AF_UNSPEC */
		if (vs->af != family)
			continue;

		if (!addr_matches(vs, address))
			continue;

		if (!address_logged &&
		    __test_bit(LOG_DETAIL_BIT, &debug)) {
			inet_ntop(family, address, addr_str, sizeof(addr_str));
			log_message(LOG_INFO, "Netlink reflector reports IP %s %s"
					    , addr_str, (enable) ? "added" : "removed");
		}
		address_logged = true;

		/* If we have that same address (IPv6 link local) on multiple interfaces,
		 * we want to count them multiple times so that we only suspend the checkers
		 * if they are all deleted */
		if (enable)
			vs->ha_suspend_addr_count++;
		else
			vs->ha_suspend_addr_count--;

		/* Processing Healthcheckers queue for this vs */
		LIST_FOREACH(checkers_queue, checker, e1) {
			if (checker->vs != vs)
				continue;

			if (enable != checker->enabled &&
			    (enable || vs->ha_suspend_addr_count == 0)) {
				log_message(LOG_INFO, "%sing healthchecker for service %s for VS %s",
							!checker->enabled ? "Activat" : "Suspend",
							FMT_RS(checker->rs, checker->vs), FMT_VS(checker->vs));
				checker->enabled = enable;
			}
		}
	}
}

/* Install checkers keywords */
void
install_checkers_keyword(void)
{
	install_misc_check_keyword();
	install_smtp_check_keyword();
	install_tcp_check_keyword();
	install_http_check_keyword();
	install_ssl_check_keyword();
	install_dns_check_keyword();
#ifdef _WITH_BFD_
	install_bfd_check_keyword();
#endif
}