Skip to content

Commit

Permalink
vrrp: Stop link local VMAC address responging to neighbour solicit
Browse files Browse the repository at this point in the history
When an IPv6 VRRP instance using  VMAC is in backup state, the
link local address configured on the VMAC interface is the same as
the link local address on the parent interface of the VMAC. This
causes a problem with switches learning the MAC address of the
VMAC is now on the backup. This causes packets meant to be sent to
the master being sent to the backup.

This commit uses nftables/iptables to stop neighbour advertisements
for the link local address of the VMAC interface and its parent
interface being sent from the VMAC interface.

Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
  • Loading branch information
pqarmitage committed Jul 25, 2023
1 parent 6968794 commit 81eb41f
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 36 deletions.
4 changes: 2 additions & 2 deletions doc/man/man5/keepalived.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,8 @@ possibly following any cleanup actions needed.
# iptables. If so, then the ipset names can be specified, defaults
# as below. If no names are specified, ipsets will not be used,
# otherwise any omitted names will be constructed by adding "_if"
# and/or "6" and _igmp/_mld to previously specified names.
\fBvrrp_ipsets \fR[keepalived [keepalived6 [keepalived_if6 [keepalived_igmp [keepalived_mld]]]]]
# and/or "6" and _igmp/_mld/_nd to previously specified names.
\fBvrrp_ipsets \fR[keepalived [keepalived6 [keepalived_if6 [keepalived_igmp [keepalived_mld [keepalived_vmac_nd]]]]]]

# An alternative to moving IGMP messages from VMACs to their parent interfaces
# is to disable them altogether in the kernel by setting
Expand Down
7 changes: 7 additions & 0 deletions keepalived/core/global_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,9 @@ free_global_data(data_t * data)
FREE_CONST_PTR(data->vrrp_ipset_address_iface6);
FREE_CONST_PTR(data->vrrp_ipset_igmp);
FREE_CONST_PTR(data->vrrp_ipset_mld);
#ifdef _HAVE_VRRP_VMAC_
FREE_CONST_PTR(data->vrrp_ipset_vmac_nd);
#endif
#endif
#endif
#ifdef _WITH_NFTABLES_
Expand Down Expand Up @@ -691,6 +694,10 @@ dump_global_data(FILE *fp, data_t * data)
conf_write(fp," ipset IGMP set = %s", data->vrrp_ipset_igmp);
if (data->vrrp_ipset_mld)
conf_write(fp," ipset MLD set = %s", data->vrrp_ipset_mld);
#ifdef _HAVE_VRRP_VMAC_
if (data->vrrp_ipset_vmac_nd)
conf_write(fp," ipset ND set = %s", data->vrrp_ipset_vmac_nd);
#endif
}
#endif
}
Expand Down
31 changes: 23 additions & 8 deletions keepalived/core/global_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,7 @@ vrrp_ipsets_handler(const vector_t *strvec)
FREE_CONST_PTR(global_data->vrrp_ipset_address_iface6);
FREE_CONST_PTR(global_data->vrrp_ipset_igmp);
FREE_CONST_PTR(global_data->vrrp_ipset_mld);
FREE_CONST_PTR(global_data->vrrp_ipset_vmac_nd);

if (vector_size(strvec) < 2) {
global_data->using_ipsets = false;
Expand All @@ -1115,22 +1116,21 @@ vrrp_ipsets_handler(const vector_t *strvec)
return;
}
global_data->vrrp_ipset_address6 = STRDUP(strvec_slot(strvec,2));
}
else {
} else {
/* No second set specified, copy first name and add "6" */
strcpy_safe(set_name, global_data->vrrp_ipset_address);
set_name[IPSET_MAXNAMELEN - 2] = '\0';
strcat(set_name, "6");
global_data->vrrp_ipset_address6 = STRDUP(set_name);
}

if (vector_size(strvec) >= 4) {
if (strlen(strvec_slot(strvec,3)) >= IPSET_MAXNAMELEN - 1) {
report_config_error(CONFIG_GENERAL_ERROR, "VRRP Error : ipset IPv6 address_iface name too long - ignored");
return;
}
global_data->vrrp_ipset_address_iface6 = STRDUP(strvec_slot(strvec,3));
}
else {
} else {
/* No third set specified, copy second name and add "_if6" */
strcpy_safe(set_name, global_data->vrrp_ipset_address6);
len = strlen(set_name);
Expand All @@ -1147,28 +1147,43 @@ vrrp_ipsets_handler(const vector_t *strvec)
return;
}
global_data->vrrp_ipset_igmp = STRDUP(strvec_slot(strvec,4));
}
else {
} else {
/* No second set specified, copy first name and add "_igmp" */
strcpy_safe(set_name, global_data->vrrp_ipset_address);
set_name[sizeof(set_name) - 6] = '\0';
strcat(set_name, "_igmp");
global_data->vrrp_ipset_igmp = STRDUP(set_name);
}

if (vector_size(strvec) >= 6) {
if (strlen(strvec_slot(strvec,5)) >= IPSET_MAXNAMELEN - 1) {
report_config_error(CONFIG_GENERAL_ERROR, "VRRP Error : ipset MLD name too long - ignored");
return;
}
global_data->vrrp_ipset_mld = STRDUP(strvec_slot(strvec,5));
}
else {
} else {
/* No second set specified, copy first name and add "_mld" */
strcpy_safe(set_name, global_data->vrrp_ipset_address);
set_name[sizeof(set_name) - 5] = '\0';
strcat(set_name, "_mld");
global_data->vrrp_ipset_mld = STRDUP(set_name);
}

#ifdef _HAVE_VRRP_VMAC_
if (vector_size(strvec) >= 7) {
if (strlen(strvec_slot(strvec,6)) >= IPSET_MAXNAMELEN - 1) {
report_config_error(CONFIG_GENERAL_ERROR, "VRRP Error : ipset ND name too long - ignored");
return;
}
global_data->vrrp_ipset_vmac_nd = STRDUP(strvec_slot(strvec,6));
} else {
/* No second set specified, copy first name and add "_nd" */
strcpy_safe(set_name, global_data->vrrp_ipset_address);
set_name[sizeof(set_name) - 5] = '\0';
strcat(set_name, "_nd");
global_data->vrrp_ipset_vmac_nd = STRDUP(set_name);
}
#endif
}
#endif
#elif defined _WITH_NFTABLES_
Expand Down
1 change: 1 addition & 0 deletions keepalived/core/keepalived_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,7 @@ netlink_if_link_filter(__attribute__((unused)) struct sockaddr_nl *snl, struct n
if (tb[IFLA_IFNAME] == NULL)
return -1;
name = (char *)RTA_DATA(tb[IFLA_IFNAME]);
log_message(LOG_INFO, "Got netlink new message for %s", name);

/* Skip it if already exists */
ifp = if_get_by_ifname(name, IF_CREATE_NETLINK);
Expand Down
3 changes: 3 additions & 0 deletions keepalived/include/global_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ typedef struct _data {
const char *vrrp_ipset_address_iface6;
const char *vrrp_ipset_igmp;
const char *vrrp_ipset_mld;
#ifdef _HAVE_VRRP_VMAC_
const char *vrrp_ipset_vmac_nd;
#endif
#endif
#endif
#ifdef _WITH_NFTABLES_
Expand Down
1 change: 1 addition & 0 deletions keepalived/include/vrrp_ipset.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extern void* ipset_session_start(void);
extern void ipset_session_end(void *);
extern void ipset_entry(void *, int, const ip_address_t*);
extern void ipset_entry_igmp(void*, int, const char *, uint8_t);
extern void ipset_entry_nd(void*, int, const interface_t *);
extern void set_default_ipsets(void);
extern void disable_ipsets(void);

Expand Down
48 changes: 42 additions & 6 deletions keepalived/vrrp/vrrp_ipset.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ has_ipset_setname(struct ipset_session* session, const char *setname)

static bool
create_sets(struct ipset_session **session, const char* addr4, const char* addr6, const char* addr_if6,
const char *igmp, const char *mld, bool is_reload)
const char *igmp, const char *mld,
#ifndef _HAVE_VRRP_VMAC_
__attribute__((unused))
#endif
const char *vmac_nd, bool is_reload)
{
if (!*session)
#ifdef LIBIPSET_PRE_V7_COMPAT
Expand Down Expand Up @@ -231,6 +235,13 @@ create_sets(struct ipset_session **session, const char* addr4, const char* addr6
ipset_create(*session, mld, "hash:net,iface", NFPROTO_IPV6);
}

#ifdef _HAVE_VRRP_VMAC_
if (vmac_nd) {
if (!is_reload || !has_ipset_setname(*session, vmac_nd))
ipset_create(*session, vmac_nd, "hash:net,iface", NFPROTO_IPV6);
}
#endif

return true;
}

Expand Down Expand Up @@ -345,8 +356,12 @@ remove_ipsets(struct ipset_session **session, uint8_t family, bool vip_sets)
} else {
if (family == AF_INET)
ipset_destroy(*session, global_data->vrrp_ipset_igmp);
else
else {
ipset_destroy(*session, global_data->vrrp_ipset_mld);
#ifdef _HAVE_VRRP_VMAC_
ipset_destroy(*session, global_data->vrrp_ipset_vmac_nd);
#endif
}
}

return true;
Expand All @@ -367,17 +382,21 @@ remove_igmp_ipsets(struct ipset_session **session, uint8_t family)
bool add_vip_ipsets(struct ipset_session **session, uint8_t family, bool is_reload)
{
if (family == AF_INET)
return create_sets(session, global_data->vrrp_ipset_address, NULL, NULL, NULL, NULL, is_reload);
return create_sets(session, global_data->vrrp_ipset_address, NULL, NULL, NULL, NULL, NULL, is_reload);

return create_sets(session, NULL, global_data->vrrp_ipset_address6, global_data->vrrp_ipset_address_iface6, NULL, NULL, is_reload);
return create_sets(session, NULL, global_data->vrrp_ipset_address6, global_data->vrrp_ipset_address_iface6, NULL, NULL, NULL, is_reload);
}

bool add_igmp_ipsets(struct ipset_session **session, uint8_t family, bool is_reload)
{
if (family == AF_INET)
return create_sets(session, NULL, NULL, NULL, global_data->vrrp_ipset_igmp, NULL, is_reload);
return create_sets(session, NULL, NULL, NULL, global_data->vrrp_ipset_igmp, NULL, NULL, is_reload);

return create_sets(session, NULL, NULL, NULL, NULL, global_data->vrrp_ipset_mld, is_reload);
return create_sets(session, NULL, NULL, NULL, NULL, global_data->vrrp_ipset_mld, NULL, is_reload)
#ifdef _HAVE_VRRP_VMAC_
&& create_sets(session, NULL, NULL, NULL, NULL, NULL, global_data->vrrp_ipset_vmac_nd, is_reload)
#endif
;
}

void* ipset_session_start(void)
Expand Down Expand Up @@ -431,6 +450,17 @@ void ipset_entry_igmp(void* vsession, int cmd, const char* ifname, uint8_t famil
do_ipset_cmd(session, (cmd == IPADDRESS_DEL) ? IPSET_CMD_DEL : IPSET_CMD_ADD, set, &addr, 0, 0, ifname);
}

#ifdef _HAVE_VRRP_VMAC_
void ipset_entry_nd(void* vsession, int cmd, const interface_t* ifp)
{
struct ipset_session *session = vsession;
ip_address_t addr = { .ifa.ifa_family = AF_INET6, .u.sin6_addr = ifp->base_ifp->sin6_addr };


do_ipset_cmd(session, (cmd == IPADDRESS_DEL) ? IPSET_CMD_DEL : IPSET_CMD_ADD, global_data->vrrp_ipset_vmac_nd, &addr, -1, 0, ifp->ifname);
}
#endif

void
set_default_ipsets(void)
{
Expand All @@ -439,6 +469,9 @@ set_default_ipsets(void)
global_data->vrrp_ipset_address_iface6 = STRDUP(DEFAULT_IPSET_NAME "_if6");
global_data->vrrp_ipset_igmp = STRDUP(DEFAULT_IPSET_NAME "_igmp");
global_data->vrrp_ipset_mld = STRDUP(DEFAULT_IPSET_NAME "_mld");
#ifdef _HAVE_VRRP_VMAC_
global_data->vrrp_ipset_vmac_nd = STRDUP(DEFAULT_IPSET_NAME "_nd");
#endif
}

void
Expand All @@ -450,4 +483,7 @@ disable_ipsets(void)
FREE_CONST_PTR(global_data->vrrp_ipset_address_iface6);
FREE_CONST_PTR(global_data->vrrp_ipset_igmp);
FREE_CONST_PTR(global_data->vrrp_ipset_mld);
#ifdef _HAVE_VRRP_VMAC_
FREE_CONST_PTR(global_data->vrrp_ipset_vmac_nd);
#endif
}
81 changes: 67 additions & 14 deletions keepalived/vrrp/vrrp_iptables.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ add_del_igmp_rules(struct ipt_handle *h, int cmd, uint8_t family)

if (h->h6 || (h->h6 = ip6tables_open("filter"))) {
ip6tables_add_rules(h->h6, global_data->vrrp_iptables_outchain, APPEND_RULE, IPSET_DIM_TWO, 0, XTC_LABEL_DROP, NULL, NULL, global_data->vrrp_ipset_mld, IPPROTO_ICMPV6, ICMPV6_MLD2_REPORT, cmd, false);
#ifdef _HAVE_VRRP_VMAC_
ip6tables_add_rules(h->h6, global_data->vrrp_iptables_outchain, APPEND_RULE, IPSET_DIM_TWO, IPSET_DIM_ONE_SRC, XTC_LABEL_DROP, NULL, NULL, global_data->vrrp_ipset_vmac_nd, IPPROTO_ICMPV6, ND_NEIGHBOR_ADVERT, cmd, false);
#endif
h->updated_v6 = true;
}
}
Expand Down Expand Up @@ -341,33 +344,33 @@ handle_iptable_rule_to_vip(ip_address_t *ipaddress, int cmd, struct ipt_handle *
IN6_IS_ADDR_LINKLOCAL(&ipaddress->u.sin6_addr))
ifname = ipaddress->ifp->ifname;

iptables_entry(h, family, global_data->vrrp_iptables_inchain, 0,
XTC_LABEL_DROP, NULL, ipaddress, ifname, NULL,
IPPROTO_NONE, 0, cmd, 0, force);

if (global_data->vrrp_iptables_outchain)
iptables_entry(h, family, global_data->vrrp_iptables_outchain, 0,
XTC_LABEL_DROP, ipaddress, NULL, NULL, ifname,
IPPROTO_NONE, 0, cmd, 0, force);

if (family == AF_INET6 && global_data->vrrp_iptables_inchain) {
if (global_data->vrrp_iptables_outchain) {
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_outchain, 0,
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_outchain, APPEND_RULE,
XTC_LABEL_ACCEPT, ipaddress, NULL, NULL, ifname,
IPPROTO_ICMPV6, ND_NEIGHBOR_SOLICIT, cmd, 0, force);
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_outchain, 1,
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_outchain, APPEND_RULE,
XTC_LABEL_ACCEPT, ipaddress, NULL, NULL, ifname,
IPPROTO_ICMPV6, ND_NEIGHBOR_ADVERT, cmd, 0, force);
}

iptables_entry(h, AF_INET6, global_data->vrrp_iptables_inchain, 0,
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_inchain, APPEND_RULE,
XTC_LABEL_ACCEPT, NULL, ipaddress, ifname, NULL,
IPPROTO_ICMPV6, ND_NEIGHBOR_SOLICIT, cmd, 0, force);
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_inchain, 1,
iptables_entry(h, AF_INET6, global_data->vrrp_iptables_inchain, APPEND_RULE,
XTC_LABEL_ACCEPT, NULL, ipaddress, ifname, NULL,
IPPROTO_ICMPV6, ND_NEIGHBOR_ADVERT, cmd, 0, force);
}

if (global_data->vrrp_iptables_outchain)
iptables_entry(h, family, global_data->vrrp_iptables_outchain, APPEND_RULE,
XTC_LABEL_DROP, ipaddress, NULL, NULL, ifname,
IPPROTO_NONE, 0, cmd, 0, force);

iptables_entry(h, family, global_data->vrrp_iptables_inchain, APPEND_RULE,
XTC_LABEL_DROP, NULL, ipaddress, ifname, NULL,
IPPROTO_NONE, 0, cmd, 0, force);

ipaddress->iptable_rule_set = (cmd != IPADDRESS_DEL);
}

Expand Down Expand Up @@ -578,12 +581,58 @@ handle_iptable_rule_for_igmp(const char *ifname, int cmd, int family, struct ipt
}
#endif

iptables_entry(h, family, global_data->vrrp_iptables_outchain, APPEND_RULE,
iptables_entry(h, family, global_data->vrrp_iptables_outchain, 0,
XTC_LABEL_DROP, NULL, NULL, NULL, ifname,
family == AF_INET ? IPPROTO_IGMP : IPPROTO_ICMPV6, family == AF_INET ? 0 : ICMPV6_MLD2_REPORT,
cmd, 0, false);
}

static void
handle_iptable_rule_for_nd(const interface_t *ifp, int cmd, struct ipt_handle *h)
{
ip_address_t addr = { .ifa.ifa_family = AF_INET6, .u.sin6_addr = ifp->base_ifp->sin6_addr };

if (!global_data->vrrp_iptables_outchain ||
igmp_setup[1] == INIT_FAILED)
return;

if (igmp_setup[1] == NOT_INIT) {
if (setup[1] == NOT_INIT)
iptables_init(AF_INET6);

if (setup[1] == INIT_FAILED) {
igmp_setup[1] = INIT_FAILED;
return;
}

#ifdef _HAVE_LIBIPSET_
if (global_data->using_ipsets) {
add_del_igmp_sets(h, IPADDRESS_ADD, AF_INET6);
add_del_igmp_rules(h, IPADDRESS_ADD, AF_INET6);
}
#endif

igmp_setup[1] = INIT_SUCCESS;
}

#ifdef _HAVE_LIBIPSET_
if (global_data->using_ipsets)
{
if (!h->session)
h->session = ipset_session_start();

ipset_entry_nd(h->session, cmd, ifp);

return;
}
#endif

iptables_entry(h, AF_INET6, global_data->vrrp_iptables_outchain, 0,
XTC_LABEL_DROP, &addr, NULL, NULL, ifp->ifname,
IPPROTO_ICMPV6, ND_NEIGHBOR_ADVERT,
cmd, 0, false);
}

static void
iptables_update_vmac(const interface_t *ifp, int family, bool other_family, int cmd)
{
Expand All @@ -598,6 +647,10 @@ iptables_update_vmac(const interface_t *ifp, int family, bool other_family, int

if (other_family)
handle_iptable_rule_for_igmp(ifp->ifname, cmd, family == AF_INET ? AF_INET6 : AF_INET, h);

if (family == AF_INET6)
handle_iptable_rule_for_nd(ifp, cmd, h);

res = iptables_close(h);
} while (res == EAGAIN && ++tries < IPTABLES_MAX_TRIES);
}
Expand Down

0 comments on commit 81eb41f

Please sign in to comment.