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:        NETLINK IPv4 address manipulation.
 *
 * 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"

/* Global include */
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>

/* local include */
#include "vrrp_ipaddress.h"
#include "vrrp.h"
#include "keepalived_netlink.h"
#include "vrrp_data.h"
#include "logger.h"
#include "utils.h"
#include "bitops.h"
#include "global_data.h"
#include "rttables.h"
#include "memory.h"
#include "parser.h"
#ifdef _WITH_FIREWALL_
#include "vrrp_firewall.h"
#endif


#define INFINITY_LIFE_TIME      0xFFFFFFFF

const char *
ipaddresstos(char *buf, const ip_address_t *ip_addr)
{
	static char addr_str[IPADDRESSTOS_BUF_LEN];
	char *end;

	if (!buf)
		buf = addr_str;

	if (IP_IS6(ip_addr))
		inet_ntop(AF_INET6, &ip_addr->u.sin6_addr, buf, INET6_ADDRSTRLEN);
	else
		inet_ntop(AF_INET, &ip_addr->u.sin.sin_addr, buf, INET_ADDRSTRLEN);
	if ((ip_addr->ifa.ifa_family == AF_INET && ip_addr->ifa.ifa_prefixlen != 32 ) ||
	    (ip_addr->ifa.ifa_family == AF_INET6 && ip_addr->ifa.ifa_prefixlen != 128 )) {
		end = buf + strlen(buf);
		snprintf(end, buf + IPADDRESSTOS_BUF_LEN - end, "/%u", ip_addr->ifa.ifa_prefixlen);
	}

	return buf;
}

/* Add/Delete IP address to a specific interface_t */
int
netlink_ipaddress(ip_address_t *ip_addr, int cmd)
{
	struct ifa_cacheinfo cinfo;
	int status = 1;
	struct {
		struct nlmsghdr n;
		struct ifaddrmsg ifa;
		char buf[256];
	} req;
#if HAVE_DECL_IFA_FLAGS
	uint32_t ifa_flags = 0;
#else
	uint8_t ifa_flags = 0;
#endif

	if (cmd == IPADDRESS_ADD) {
		/* We can't add the address if the interface doesn't exist */
		if (!ip_addr->ifp->ifindex) {
			log_message(LOG_INFO, "Not adding address %s to %s since interface doesn't exist"
					    , ipaddresstos(NULL, ip_addr), ip_addr->ifp->ifname);
			return -1;
		}

		/* Make sure the ifindex for the address is current */
		ip_addr->ifa.ifa_index = ip_addr->ifp->ifindex;
	}
	else if (!ip_addr->ifp->ifindex) {
		/* The interface has been deleted, so there is no point deleting the address */
		return 0;
	}
	else if (!ip_addr->ifa.ifa_index)
		ip_addr->ifa.ifa_index = ip_addr->ifp->ifindex;

	memset(&req, 0, sizeof (req));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof (struct ifaddrmsg));
	req.n.nlmsg_flags = NLM_F_REQUEST;
	req.n.nlmsg_type = (cmd == IPADDRESS_DEL) ? RTM_DELADDR : RTM_NEWADDR;
	req.ifa = ip_addr->ifa;

	if (cmd == IPADDRESS_ADD)
		ifa_flags = ip_addr->flags;

	if (IP_IS6(ip_addr)) {
		if (cmd == IPADDRESS_ADD) {
			/* A preferred_lft of 0 marks an IPv6 address as deprecated (rfc3484)
			 * in order to prevent using VRRP VIP as source address in
			 * healthchecking use cases. */
			if (ip_addr->preferred_lft != INFINITY_LIFE_TIME) {
				memset(&cinfo, 0, sizeof(cinfo));
				cinfo.ifa_prefered = ip_addr->preferred_lft;
				cinfo.ifa_valid = INFINITY_LIFE_TIME;

				addattr_l(&req.n, sizeof(req), IFA_CACHEINFO, &cinfo, sizeof(cinfo));
			}

			/* Disable, per VIP, Duplicate Address Detection algorithm (DAD).
			 * Using the nodad flag has the following benefits:
			 *
			 * (1) The address becomes immediately usable after they're
			 *     configured.
			 * (2) In the case of a temporary layer-2 / split-brain problem
			 *     we can avoid that the active VIP transitions into the
			 *     dadfailed phase and stays there forever - leaving us
			 *     without service. HA/VRRP setups have their own "DAD"-like
			 *     functionality, so it's not really needed from the IPv6 stack.
			 */
#ifdef IFA_F_NODAD	/* Since Linux 2.6.19 */
			if (!(ip_addr->flagmask & IFA_F_NODAD))
				ifa_flags |= IFA_F_NODAD;
#endif
		}

		addattr_l(&req.n, sizeof(req), IFA_LOCAL,
			  &ip_addr->u.sin6_addr, sizeof(ip_addr->u.sin6_addr));
	} else {
		addattr_l(&req.n, sizeof(req), IFA_LOCAL,
			  &ip_addr->u.sin.sin_addr, sizeof(ip_addr->u.sin.sin_addr));

		if (cmd == IPADDRESS_ADD) {
			if (ip_addr->u.sin.sin_brd.s_addr)
				addattr_l(&req.n, sizeof(req), IFA_BROADCAST,
					  &ip_addr->u.sin.sin_brd, sizeof(ip_addr->u.sin.sin_brd));
		}
		else {
			/* IPADDRESS_DEL */
			addattr_l(&req.n, sizeof(req), IFA_ADDRESS,
				  &ip_addr->u.sin.sin_addr, sizeof(ip_addr->u.sin.sin_addr));
		}
	}

	if (cmd == IPADDRESS_ADD) {
#if HAVE_DECL_IFA_FLAGS
		if (ifa_flags)
			addattr32(&req.n, sizeof(req), IFA_FLAGS, ifa_flags);
#else
		req.ifa.ifa_flags = ifa_flags;
#endif
		if (ip_addr->label)
			addattr_l(&req.n, sizeof (req), IFA_LABEL,
				  ip_addr->label, strlen(ip_addr->label) + 1);

		if (ip_addr->have_peer)
			addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &ip_addr->peer, req.ifa.ifa_family == AF_INET6 ? 16 : 4);
	}

	/* If the state of the interface or its parent is down, it might be because the interface
	 * has been deleted, but we get the link status change message before the RTM_DELLINK message */
	if (cmd == IPADDRESS_DEL &&
	    (((ip_addr->ifp->ifi_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING))
#ifdef _HAVE_VRRP_VMAC_
	     || ((IF_BASE_IFP(ip_addr->ifp)->ifi_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING))
#endif
													     ))
		netlink_error_ignore = ENODEV;
	if (netlink_talk(&nl_cmd, &req.n) < 0)
		status = -1;
	netlink_error_ignore = 0;

	return status;
}

/* Add/Delete a list of IP addresses */
bool
netlink_iplist(list_head_t *ip_list, int cmd, bool force)
{
	ip_address_t *ip_addr;
	bool changed_entries = false;

	/*
	 * If "--dont-release-vrrp" is set then try to release addresses
	 * that may be there, even if we didn't set them.
	 */
	list_for_each_entry(ip_addr, ip_list, e_list) {
		if ((cmd == IPADDRESS_ADD && !ip_addr->set) ||
		    (cmd == IPADDRESS_DEL &&
		     (force || ip_addr->set || __test_bit(DONT_RELEASE_VRRP_BIT, &debug)))) {
			/* If we are removing addresses left over from previous run
			 * and they don't exist, don't report an error */
			if (force)
				netlink_error_ignore = ENODEV;

			if (netlink_ipaddress(ip_addr, cmd) > 0) {
				ip_addr->set = (cmd == IPADDRESS_ADD);
				changed_entries = true;
			}
			else
				ip_addr->set = false;
		}
	}

	return changed_entries;
}

/* IP address dump/allocation */
void
free_ipaddress(ip_address_t *ip_addr)
{
	FREE_PTR(ip_addr->label);
	list_del_init(&ip_addr->e_list);
	FREE(ip_addr);
}

void
free_ipaddress_list(list_head_t *l)
{
	ip_address_t *ip_addr, *ip_addr_tmp;

	list_for_each_entry_safe(ip_addr, ip_addr_tmp, l, e_list)
		free_ipaddress(ip_addr);
}

void
format_ipaddress(const ip_address_t *ip_addr, char *buf, size_t buf_len)
{
	char peer[INET6_ADDRSTRLEN + 4];	/* allow for subnet */
	char *buf_p = buf;
	char *buf_end = buf + buf_len;

	buf_p += snprintf(buf_p, buf_end - buf_p, "%s", ipaddresstos(NULL, ip_addr));
	if (!IP_IS6(ip_addr) && ip_addr->u.sin.sin_brd.s_addr) {
		buf_p += snprintf(buf_p, buf_end - buf_p, " brd %s",
			 inet_ntop2(ip_addr->u.sin.sin_brd.s_addr));
	}
	buf_p += snprintf(buf_p, buf_end - buf_p, " dev %s scope %s"
			       , IF_NAME(ip_addr->ifp)
			       , get_rttables_scope(ip_addr->ifa.ifa_scope));
	if (ip_addr->label)
		buf_p += snprintf(buf_p, buf_end - buf_p, " label %s", ip_addr->label);
	if (ip_addr->have_peer) {
		inet_ntop(ip_addr->ifa.ifa_family, &ip_addr->peer, peer, sizeof(peer));
		buf_p += snprintf(buf_p, buf_end - buf_p, " peer %s/%d"
				       , peer, ip_addr->ifa.ifa_prefixlen);
	}
#ifdef IFA_F_HOMEADDRESS		/* Linux 2.6.19 */
	if (ip_addr->flags & IFA_F_HOMEADDRESS)
		buf_p += snprintf(buf_p, buf_end - buf_p, " home");
#endif
#ifdef IFA_F_NODAD			/* Linux 2.6.19 */
	if (ip_addr->flagmask & IFA_F_NODAD)
		buf_p += snprintf(buf_p, buf_end - buf_p, " -nodad");
#endif
#ifdef IFA_F_MANAGETEMPADDR		/* Linux 3.14 */
	if (ip_addr->flags & IFA_F_MANAGETEMPADDR)
		buf_p += snprintf(buf_p, buf_end - buf_p, " mngtmpaddr");
#endif
#ifdef IFA_F_NOPREFIXROUTE		/* Linux 3.14 */
	if (ip_addr->flags & IFA_F_NOPREFIXROUTE)
		buf_p += snprintf(buf_p, buf_end - buf_p, " noprefixroute");
#endif
#ifdef IFA_F_MCAUTOJOIN			/* Linux 4.1 */
	if (ip_addr->flags & IFA_F_MCAUTOJOIN)
		buf_p += snprintf(buf_p, buf_end - buf_p, " autojoin");
#endif
	if (ip_addr->dont_track)
		buf_p += snprintf(buf_p, buf_end - buf_p, "%s", " no_track");

	if (ip_addr->track_group)
		buf_p += snprintf(buf_p, buf_end - buf_p, " track_group %s", ip_addr->track_group->gname);

	if (IP_IS6(ip_addr)) {
		if (ip_addr->preferred_lft == 0)
			buf_p += snprintf(buf_p, buf_end - buf_p, " deprecated");
		else if (ip_addr->preferred_lft == INFINITY_LIFE_TIME)
			buf_p += snprintf(buf_p, buf_end - buf_p, " preferred_lft forever");
		else
			buf_p += snprintf(buf_p, buf_end - buf_p, " preferred_lft %" PRIu32, ip_addr->preferred_lft);
	}

	if (ip_addr->set)
		buf_p += snprintf(buf_p, buf_end - buf_p, " set");
#ifdef _WITH_IPTABLES_
	if (ip_addr->iptable_rule_set)
		buf_p += snprintf(buf_p, buf_end - buf_p, " iptable_set");
#endif
#ifdef _WITH_NFTABLES_
	if (ip_addr->nftable_rule_set)
		buf_p += snprintf(buf_p, buf_end - buf_p, " nftable_set");
#endif
}

void
dump_ipaddress(FILE *fp, const ip_address_t *ip_addr)
{
	char buf[256];

	format_ipaddress(ip_addr, buf, sizeof(buf));

	conf_write(fp, "     %s", buf);
}

void
dump_ipaddress_list(FILE *fp, const list_head_t *l)
{
	ip_address_t *ip_addr;

	list_for_each_entry(ip_addr, l, e_list)
		dump_ipaddress(fp, ip_addr);
}

ip_address_t *
parse_ipaddress(ip_address_t *ip_addr, const char *str, bool allow_subnet_mask)
{
	ip_address_t *new = ip_addr;
	void *addr;
	const char *p;
	unsigned prefixlen;
	const char *str_dup = NULL;

	/* No ip address, allocate a brand new one */
	if (!new)
		new = (ip_address_t *) MALLOC(sizeof(ip_address_t));

	/* Parse ip address */
	new->ifa.ifa_family = (strchr(str, ':')) ? AF_INET6 : AF_INET;
	new->ifa.ifa_prefixlen = (IP_IS6(new)) ? 128 : 32;

	if (allow_subnet_mask)
		p = strchr(str, '/');
	else
		p = NULL;

	if (p) {
		if (!read_unsigned(p + 1, &prefixlen, 0, new->ifa.ifa_prefixlen, true))
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid address prefix len %s for address %s - using %d", p + 1, str, new->ifa.ifa_prefixlen);
		else
			new->ifa.ifa_prefixlen = prefixlen;

		str_dup = STRNDUP(str, p - str);
	}

	addr = (IP_IS6(new)) ? (void *) &new->u.sin6_addr :
			       (void *) &new->u.sin.sin_addr;
	if (!inet_pton(IP_FAMILY(new), str_dup ? str_dup : str, addr)) {
		report_config_error(CONFIG_GENERAL_ERROR, "VRRP parsed invalid IP %s. skipping IP...", str);
		if (!ip_addr)
			FREE(new);
		new = NULL;
	}

	/* Release dup'd string */
	if (str_dup)
		FREE_CONST(str_dup);

	return new;
}

ip_address_t *
parse_route(const char *str)
{
	ip_address_t *new = (ip_address_t *)MALLOC(sizeof(ip_address_t));

	/* Handle the specials */
	if (!strcmp(str, "default") || !strcmp(str, "any") || !strcmp(str, "all")) {
		new->ifa.ifa_family = AF_UNSPEC;
		return new;
	}

	/* Maintained for backward compatibility v2.0.7 and earlier */
	if (!strcmp(str, "default6")) {
		log_message(LOG_INFO, "'default6' is deprecated - please replace with 'inet6 default'");
		new->ifa.ifa_family = AF_INET6;
		return new;
	}

	if (!parse_ipaddress(new, str, true)) {
		FREE(new);
		return NULL;
	}

	return new;
}

void
alloc_ipaddress(list_head_t *ip_list, const vector_t *strvec, bool static_addr)
{
/* The way this works is slightly strange.
 *
 * We don't set the interface for the address unless dev DEVNAME is specified,
 * in case a VMAC is added later. When the complete configuration is checked,
 * if the ifindex is 0, then it will be set to the interface of the
 * vrrp_instance (VMAC or physical interface).
 */
	ip_address_t *new;
	interface_t *ifp_local;
	const char *str;
	unsigned int i = 0, addr_idx = 0;
	uint8_t scope;
	bool param_avail;
	bool param_missing = false;
	const char *param;
	ip_address_t peer = { .ifa.ifa_family = AF_UNSPEC };
	int brd_len = 0;
	uint32_t mask;
	bool have_broadcast = false;
	unsigned preferred_lft;
	bool preferred_lft_set = false;

	new = (ip_address_t *) MALLOC(sizeof(ip_address_t));
	if (!new) {
		log_message(LOG_INFO, "Unable to allocate new ip_address");
		return;
	}
	INIT_LIST_HEAD(&new->e_list);

	/* We expect the address first */
	if (!parse_ipaddress(new, strvec_slot(strvec, 0), true)) {
		FREE(new);
		return;
	}

	addr_idx = i++;

	/* FMT parse */
	while (i < vector_size(strvec)) {
		str = strvec_slot(strvec, i);

		/* cmd parsing */
		param_avail = (vector_size(strvec) >= i+2);

		if (!strcmp(str, "dev")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			if (new->ifp) {
				report_config_error(CONFIG_GENERAL_ERROR, "Cannot specify static ipaddress device more than once for %s", strvec_slot(strvec, addr_idx));
				FREE(new);
				return;
			}
			if (!(ifp_local = if_get_by_ifname(strvec_slot(strvec, ++i), IF_CREATE_IF_DYNAMIC))) {
				report_config_error(CONFIG_GENERAL_ERROR, "WARNING - interface %s for ip address %s doesn't exist",
						strvec_slot(strvec, i), strvec_slot(strvec, addr_idx));
				FREE(new);
				return;
			}
			new->ifp = ifp_local;
		} else if (!strcmp(str, "scope")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			if (!find_rttables_scope(strvec_slot(strvec, ++i), &scope))
				report_config_error(CONFIG_GENERAL_ERROR, "Invalid scope '%s' specified for %s - ignoring", strvec_slot(strvec,i), strvec_slot(strvec, addr_idx));
			else
				new->ifa.ifa_scope = scope;
		} else if (!strcmp(str, "broadcast") || !strcmp(str, "brd")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			if (IP_IS6(new)) {
				report_config_error(CONFIG_GENERAL_ERROR, "VRRP is trying to assign a broadcast %s to the IPv6 address %s !!?? "
						      "WTF... skipping VIP..."
						    , strvec_slot(strvec, i), strvec_slot(strvec, addr_idx));
				FREE(new);
				return;
			}

			have_broadcast = true;

			param = strvec_slot(strvec, ++i);
			if (!strcmp(param, "-"))
				brd_len = -2;
			else if (!strcmp(param, "+"))
				brd_len = -1;
			else if (!inet_pton(AF_INET, param, &new->u.sin.sin_brd)) {
				report_config_error(CONFIG_GENERAL_ERROR, "VRRP is trying to assign invalid broadcast %s. "
						      "skipping VIP...", strvec_slot(strvec, i));
				FREE(new);
				return;
			}
		} else if (!strcmp(str, "label")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			new->label = MALLOC(IFNAMSIZ);
			strncpy(new->label, strvec_slot(strvec, ++i), IFNAMSIZ);
		} else if (!strcmp(str, "peer")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			i++;
			if (new->have_peer) {
				report_config_error(CONFIG_GENERAL_ERROR, "Peer %s - another peer has already been specified", strvec_slot(strvec, i));
				continue;
			}

			if (!parse_ipaddress(&peer, strvec_slot(strvec,i), false))
				report_config_error(CONFIG_GENERAL_ERROR, "Invalid peer address %s", strvec_slot(strvec, i));
			else if (peer.ifa.ifa_family != new->ifa.ifa_family)
				report_config_error(CONFIG_GENERAL_ERROR, "Peer address %s does not match address family", strvec_slot(strvec, i));
			else {
				if ((new->ifa.ifa_family == AF_INET6 && new->ifa.ifa_prefixlen != 128) ||
				    (new->ifa.ifa_family == AF_INET && new->ifa.ifa_prefixlen != 32))
					report_config_error(CONFIG_GENERAL_ERROR, "Cannot specify address prefix when specifying peer address - ignoring");
				new->have_peer = true;
				new->ifa.ifa_prefixlen = peer.ifa.ifa_prefixlen;
				if (new->ifa.ifa_family == AF_INET6)
					new->peer.sin6_addr = peer.u.sin6_addr;
				else
					new->peer.sin_addr = peer.u.sin.sin_addr;
			}
#ifdef IFA_F_HOMEADDRESS		/* Linux 2.6.19 */
		} else if (!strcmp(str, "home")) {
			new->flags |= IFA_F_HOMEADDRESS;
			new->flagmask |= IFA_F_HOMEADDRESS;
#endif
#ifdef IFA_F_NODAD			/* Linux 2.6.19 */
		} else if (!strcmp(str, "-nodad")) {
			new->flagmask |= IFA_F_NODAD;
#endif
#ifdef IFA_F_MANAGETEMPADDR		/* Linux 3.14 */
		} else if (!strcmp(str, "mngtmpaddr")) {
			new->flags |= IFA_F_MANAGETEMPADDR;
			new->flagmask |= IFA_F_MANAGETEMPADDR;
#endif
#ifdef IFA_F_NOPREFIXROUTE		/* Linux 3.14 */
		} else if (!strcmp(str, "noprefixroute")) {
			new->flags |= IFA_F_NOPREFIXROUTE;
			new->flagmask |= IFA_F_NOPREFIXROUTE;
#endif
#ifdef IFA_F_MCAUTOJOIN			/* Linux 4.1 */
		} else if (!strcmp(str, "autojoin")) {
			new->flags |= IFA_F_MCAUTOJOIN;
			new->flagmask |= IFA_F_MCAUTOJOIN;
#endif
		} else if (!strcmp(str, "no_track")) {
			new->dont_track = true;
		} else if (!strcmp(str, "preferred_lft")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}

			i++;
			if (!strcmp(strvec_slot(strvec, i), "forever")) {
				new->preferred_lft = INFINITY_LIFE_TIME;
				preferred_lft_set = true;
			} else if (read_unsigned_strvec(strvec, i, &preferred_lft, 0, UINT32_MAX, true)) {
				new->preferred_lft = (uint32_t)preferred_lft;
				preferred_lft_set = true;
			} else
				report_config_error(CONFIG_GENERAL_ERROR, "preferred_lft %s is invalid", strvec_slot(strvec, i));
		} else if (static_addr && !strcmp(str, "track_group")) {
			if (!param_avail) {
				param_missing = true;
				break;
			}
			i++;
			if (new->track_group) {
				report_config_error(CONFIG_GENERAL_ERROR, "track_group %s is a duplicate", strvec_slot(strvec, i));
				break;
			}
			if (!(new->track_group = static_track_group_find(strvec_slot(strvec, i))))
				report_config_error(CONFIG_GENERAL_ERROR, "track_group %s not found", strvec_slot(strvec, i));
		} else
			report_config_error(CONFIG_GENERAL_ERROR, "Unknown configuration entry '%s' for ip address - ignoring", str);
		i++;
	}

	/* Check if there was a missing parameter for a keyword */
	if (param_missing) {
		report_config_error(CONFIG_GENERAL_ERROR, "No %s parameter specified for %s", str, strvec_slot(strvec, addr_idx));
		FREE(new);
		return;
	}

	/* Set the broadcast address if necessary */
	if (have_broadcast && new->have_peer) {
		report_config_error(CONFIG_GENERAL_ERROR, "Cannot specify broadcast and peer addresses - ignoring broadcast address");
		new->u.sin.sin_brd.s_addr = 0;
	}
	else if (brd_len < 0 && new->ifa.ifa_prefixlen <= 30) {
		new->u.sin.sin_brd = (new->have_peer) ? new->peer.sin_addr : new->u.sin.sin_addr;
		mask = 0xffffffffU >> new->ifa.ifa_prefixlen;
		mask = htonl(mask);
		if (brd_len == -1)	/* '+' */
			new->u.sin.sin_brd.s_addr |= mask;
		else
			new->u.sin.sin_brd.s_addr &= ~mask;
	}
	else if (brd_len < 0)
		report_config_error(CONFIG_GENERAL_ERROR, "Address prefix length %d too long for broadcast", new->ifa.ifa_prefixlen);

	if (static_addr && !new->ifp) {
		new->ifp = get_default_if();
		if (!new->ifp) {
			report_config_error(CONFIG_FATAL, "Static address %s requires either an interface"
							  " or default interface must exist"
							, strvec_slot(strvec, addr_idx));
			FREE(new);
			return;
		}
	}

	if (new->ifa.ifa_family == AF_INET6) {
		if (new->ifa.ifa_scope) {
			report_config_error(CONFIG_GENERAL_ERROR, "Cannot specify scope for IPv6 addresses (%s) - ignoring scope", strvec_slot(strvec, addr_idx));
			new->ifa.ifa_scope = 0;
		}
		if (new->label) {
			report_config_error(CONFIG_GENERAL_ERROR, "Cannot specify label for IPv6 addresses (%s) - ignoring label", strvec_slot(strvec, addr_idx));
			FREE(new->label);
			new->label = NULL;
		}

		if (!preferred_lft_set) {
			/* Set the old defaults if preferred_lft not set */
			if (new->ifa.ifa_prefixlen == 128)
				new->preferred_lft = 0;
			else
				new->preferred_lft = INFINITY_LIFE_TIME;
		}
	}

	if (new->track_group && !new->ifp) {
		report_config_error(CONFIG_GENERAL_ERROR, "Static route cannot have track_group if interface not specified");
		new->track_group = NULL;
	}

	list_add_tail(&new->e_list, ip_list);
}

/* Find an address in a list */
static bool
address_exist(vrrp_t *vrrp, ip_address_t *ip_addr)
{
	ip_address_t *ipaddr;
	char addr_str[INET6_ADDRSTRLEN];
	void *addr;

	/* If the following check isn't made, we get lots of compiler warnings */
	if (!ip_addr)
		return true;


	list_for_each_entry(ipaddr, &vrrp->vip, e_list) {
		if (IP_ISEQ(ipaddr, ip_addr)) {
			ipaddr->set = ip_addr->set;
#ifdef _WITH_IPTABLES_
			ipaddr->iptable_rule_set = ip_addr->iptable_rule_set;
#endif
#ifdef _WITH_NFTABLES_
			ipaddr->nftable_rule_set = ip_addr->nftable_rule_set;
#endif
			ipaddr->ifa.ifa_index = ip_addr->ifa.ifa_index;
			return true;
		}
	}

	list_for_each_entry(ipaddr, &vrrp->evip, e_list) {
		if (IP_ISEQ(ipaddr, ip_addr)) {
			ipaddr->set = ip_addr->set;
#ifdef _WITH_IPTABLES_
			ipaddr->iptable_rule_set = ip_addr->iptable_rule_set;
#endif
#ifdef _WITH_NFTABLES_
			ipaddr->nftable_rule_set = ip_addr->nftable_rule_set;
#endif
			ipaddr->ifa.ifa_index = ip_addr->ifa.ifa_index;
			return true;
		}
	}

	addr = (IP_IS6(ip_addr)) ? (void *) &ip_addr->u.sin6_addr :
				  (void *) &ip_addr->u.sin.sin_addr;
	inet_ntop(IP_FAMILY(ip_addr), addr, addr_str, INET6_ADDRSTRLEN);

	log_message(LOG_INFO, "(%s) ip address %s/%d dev %s, no longer exist"
			    , vrrp->iname
			    , addr_str
			    , ip_addr->ifa.ifa_prefixlen
			    , ip_addr->ifp->ifname);

	return false;
}

/* Clear diff addresses */
void
get_diff_address(vrrp_t *old, vrrp_t *new, list_head_t *old_addr)
{
	ip_address_t *ip_addr, *ip_addr_tmp;

	/* No addresses in previous conf */
	if (list_empty(&old->vip) && list_empty(&old->evip))
		return;

	list_for_each_entry_safe(ip_addr, ip_addr_tmp, &old->vip, e_list) {
		if (ip_addr->set && !address_exist(new, ip_addr)) {
			list_del_init(&ip_addr->e_list);
			list_add_tail(&ip_addr->e_list, old_addr);
		}
	}

	list_for_each_entry_safe(ip_addr, ip_addr_tmp, &old->evip, e_list) {
		if (ip_addr->set && !address_exist(new, ip_addr)) {
			list_del_init(&ip_addr->e_list);
			list_add_tail(&ip_addr->e_list, old_addr);
		}
	}
}

/* Clear diff addresses */
void
clear_address_list(list_head_t *delete_addr,
#ifndef _WITH_FIREWALL_
				     __attribute__((unused))
#endif
							     bool remove_from_firewall)
{
	/* No addresses to delete */
	if (list_empty(delete_addr))
		return;

	/* All addresses removed */
	netlink_iplist(delete_addr, IPADDRESS_DEL, false);
#ifdef _WITH_FIREWALL_
	if (remove_from_firewall)
		firewall_remove_rule_to_iplist(delete_addr);
#endif
}

/* Clear static ip address */
void
clear_diff_static_addresses(void)
{
	LIST_HEAD_INITIALIZE(remove_addr);
	vrrp_t old = {};
	vrrp_t new = {};

	list_copy(&old.vip, &old_vrrp_data->static_addresses);
	list_copy(&new.vip, &vrrp_data->static_addresses);
	INIT_LIST_HEAD(&old.evip);
	INIT_LIST_HEAD(&new.evip);

	get_diff_address(&old, &new, &remove_addr);

	list_copy(&old_vrrp_data->static_addresses, &old.vip);
	list_copy(&vrrp_data->static_addresses, &new.vip);

	clear_address_list(&remove_addr, false);
	free_ipaddress_list(&remove_addr);
}

void reinstate_static_address(ip_address_t *ip_addr)
{
	char buf[256];

	ip_addr->set = (netlink_ipaddress(ip_addr, IPADDRESS_ADD) > 0);
	format_ipaddress(ip_addr, buf, sizeof(buf));
	log_message(LOG_INFO, "Restoring deleted static address %s", buf);
}