From 81eb41fb12afe6ef56a6fd79cd4cb3cb5362c34b Mon Sep 17 00:00:00 2001 From: Quentin Armitage Date: Tue, 25 Jul 2023 23:05:25 +0100 Subject: [PATCH] vrrp: Stop link local VMAC address responging to neighbour solicit 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 --- doc/man/man5/keepalived.conf.5.in | 4 +- keepalived/core/global_data.c | 7 ++ keepalived/core/global_parser.c | 31 ++++-- keepalived/core/keepalived_netlink.c | 1 + keepalived/include/global_data.h | 3 + keepalived/include/vrrp_ipset.h | 1 + keepalived/vrrp/vrrp_ipset.c | 48 +++++++-- keepalived/vrrp/vrrp_iptables.c | 81 ++++++++++++--- keepalived/vrrp/vrrp_nftables.c | 145 +++++++++++++++++++++++++-- 9 files changed, 285 insertions(+), 36 deletions(-) diff --git a/doc/man/man5/keepalived.conf.5.in b/doc/man/man5/keepalived.conf.5.in index ce8f4e6a8f..c6e44123cf 100644 --- a/doc/man/man5/keepalived.conf.5.in +++ b/doc/man/man5/keepalived.conf.5.in @@ -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 diff --git a/keepalived/core/global_data.c b/keepalived/core/global_data.c index 8f67dcd1ae..7288651267 100644 --- a/keepalived/core/global_data.c +++ b/keepalived/core/global_data.c @@ -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_ @@ -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 } diff --git a/keepalived/core/global_parser.c b/keepalived/core/global_parser.c index a3f23290a2..04b45e19db 100644 --- a/keepalived/core/global_parser.c +++ b/keepalived/core/global_parser.c @@ -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; @@ -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); @@ -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_ diff --git a/keepalived/core/keepalived_netlink.c b/keepalived/core/keepalived_netlink.c index 9a42a2724f..2a855fa51c 100644 --- a/keepalived/core/keepalived_netlink.c +++ b/keepalived/core/keepalived_netlink.c @@ -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); diff --git a/keepalived/include/global_data.h b/keepalived/include/global_data.h index 6d15899ede..1e81586cb1 100644 --- a/keepalived/include/global_data.h +++ b/keepalived/include/global_data.h @@ -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_ diff --git a/keepalived/include/vrrp_ipset.h b/keepalived/include/vrrp_ipset.h index fd06885756..56f46c2dea 100644 --- a/keepalived/include/vrrp_ipset.h +++ b/keepalived/include/vrrp_ipset.h @@ -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); diff --git a/keepalived/vrrp/vrrp_ipset.c b/keepalived/vrrp/vrrp_ipset.c index e67b7bded1..ff86a7c601 100644 --- a/keepalived/vrrp/vrrp_ipset.c +++ b/keepalived/vrrp/vrrp_ipset.c @@ -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 @@ -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; } @@ -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; @@ -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) @@ -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) { @@ -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 @@ -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 } diff --git a/keepalived/vrrp/vrrp_iptables.c b/keepalived/vrrp/vrrp_iptables.c index 0eef2bc8da..842e5645fb 100644 --- a/keepalived/vrrp/vrrp_iptables.c +++ b/keepalived/vrrp/vrrp_iptables.c @@ -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; } } @@ -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); } @@ -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) { @@ -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); } diff --git a/keepalived/vrrp/vrrp_nftables.c b/keepalived/vrrp/vrrp_nftables.c index 822476aca5..696076b906 100644 --- a/keepalived/vrrp/vrrp_nftables.c +++ b/keepalived/vrrp/vrrp_nftables.c @@ -82,6 +82,7 @@ static const char vmac_set_name[] = "vmac_set"; static const char vmac_set_name[] = "vmac_set"; static const char *vmac_map_name = vmac_set_name; #endif +static const char parent_link_local_set_name[] = "parent_link_local"; #if HAVE_DECL_NFT_META_OIFKIND static const char macvlan[16] = "macvlan"; #endif @@ -558,6 +559,81 @@ static struct nftnl_rule *setup_rule_simple(uint8_t family, const char *table, } #endif +static void +setup_parent_link_local(struct mnl_nlmsg_batch *batch) +{ + struct nlmsghdr *nlh; + struct nftnl_set *s; + struct nftnl_rule *r; + int type_for_if; + uint8_t protocol = IPPROTO_ICMPV6; + struct icmp6_hdr icmp6; + + if (!ifname_type) + ifname_type = set_nf_ifname_type(); + + type_for_if = global_data->vrrp_nf_ifindex ? NFT_TYPE_IFINDEX : ifname_type; + + /* nft add set ip6 keepalived parent_link_local { type ipv6_addr . iface_index/name } */ + s = setup_set(NFPROTO_IPV6, global_data->vrrp_nf_table_name, parent_link_local_set_name, (NFT_TYPE_IP6ADDR << NFT_TYPE_BITS) | type_for_if, 0, 0); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWSET, NFPROTO_IPV6, + NLM_F_CREATE|NLM_F_ACK, seq++); + + nftnl_set_nlmsg_build_payload(nlh, s); + nftnl_set_free(s); + my_mnl_nlmsg_batch_next(batch); + + /* nft add rule ip6 keepalived out icmpv6 type nd-neighbor-advert [meta oifkind macvlan] ip6 saddr . oif @parent_link_local */ + r = nftnl_rule_alloc(); + if (r == NULL) { + log_message(LOG_INFO, "OOM error - %d", errno); + return; + } + + nftnl_rule_set_str(r, NFTNL_RULE_TABLE, global_data->vrrp_nf_table_name); + nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, "out"); + nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, NFPROTO_IPV6); + +#if HAVE_DECL_NFT_META_L4PROTO + add_meta(r, NFT_META_L4PROTO, NFT_REG_1); /* From Linux 3.14 */ +#else + add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, + offsetof(struct ip6_hdr, ip6_nxt), sizeof(((struct ip6_hdr *)NULL)->ip6_nxt)); +#endif + add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &protocol, sizeof(protocol)); + add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, + offsetof(struct icmp6_hdr, icmp6_type), sizeof(icmp6.icmp6_type)); + icmp6.icmp6_type = ND_NEIGHBOR_ADVERT; + add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &icmp6.icmp6_type, sizeof(icmp6.icmp6_type)); + +#if HAVE_DECL_NFT_META_OIFKIND + add_meta(r, NFT_META_OIFKIND, NFT_REG_2); + add_cmp(r, NFT_REG_2, NFT_CMP_EQ, &macvlan, sizeof(macvlan)); +#endif + + add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, + offsetof(struct ip6_hdr, ip6_src), sizeof(struct in6_addr)); + + add_meta(r, global_data->vrrp_nf_ifindex ? NFT_META_OIF : NFT_META_OIFNAME, NFT_REG_2); + + add_lookup(r, NFT_REG_1, NO_REG, parent_link_local_set_name, 0, false); + + add_counter(r); + + add_immediate_verdict(r, NF_DROP, NULL); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWRULE, + nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY), + NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, seq++); + + nftnl_rule_nlmsg_build_payload(nlh, r); + nftnl_rule_free(r); + my_mnl_nlmsg_batch_next(batch); +} + static void setup_link_local_checks(struct mnl_nlmsg_batch *batch, bool concat_ifname) { @@ -575,8 +651,8 @@ setup_link_local_checks(struct mnl_nlmsg_batch *batch, bool concat_ifname) s = setup_set(NFPROTO_IPV6, global_data->vrrp_nf_table_name, set_name, (NFT_TYPE_IP6ADDR << NFT_TYPE_BITS) | type_for_if, 0, 0); nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), - NFT_MSG_NEWSET, NFPROTO_IPV6, - NLM_F_CREATE|NLM_F_ACK, seq++); + NFT_MSG_NEWSET, NFPROTO_IPV6, + NLM_F_CREATE|NLM_F_ACK, seq++); nftnl_set_nlmsg_build_payload(nlh, s); nftnl_set_free(s); @@ -810,6 +886,8 @@ nft_setup_ipv6(struct mnl_nlmsg_batch *batch) nftnl_chain_free(t); my_mnl_nlmsg_batch_next(batch); + setup_parent_link_local(batch); + ipv6_table_setup = true; } @@ -903,7 +981,7 @@ nft_setup_ipv6_vips(struct mnl_nlmsg_batch *batch) nftnl_set_free(s); my_mnl_nlmsg_batch_next(batch); - /* nft add rule ip6 keepalived in icmpv6 @neighbor-discovery accept */ + /* nft add rule ip6 keepalived in type icmpv6 @neighbor-discovery accept */ r = setup_rule_icmpv6(NFPROTO_IPV6, global_data->vrrp_nf_table_name, "in", NULL, "neighbor-discovery", 0, NF_ACCEPT, false); nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), @@ -926,7 +1004,7 @@ nft_setup_ipv6_vips(struct mnl_nlmsg_batch *batch) nftnl_rule_free(r); my_mnl_nlmsg_batch_next(batch); - /* nft add rule ip6 keepalived in icmpv6 @neighbor-discovery accept */ + /* nft add rule ip6 keepalived in icmpv6 type @neighbor-discovery accept */ r = setup_rule_icmpv6(NFPROTO_IPV6, global_data->vrrp_nf_table_name, "out", NULL, "neighbor-discovery", 0, NF_ACCEPT, false); nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), @@ -1009,7 +1087,7 @@ nft_update_ipv6_address(struct mnl_nlmsg_batch *batch, ip_address_t *addr, bool struct nftnl_set **set_global, struct nftnl_set **set_ll, struct nftnl_set **set_ll_ifname) { struct nftnl_set_elem *e; - uint32_t data_buf[sizeof(struct in6_addr) + IFNAMSIZ]; + uint32_t data_buf[(sizeof(struct in6_addr) + IFNAMSIZ + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; struct nftnl_set **s; const char *set_name; bool use_link_name = false; @@ -1410,10 +1488,52 @@ nft_update_vmac_element(struct mnl_nlmsg_batch *batch, struct nftnl_set *s, ifin my_mnl_nlmsg_batch_next(batch); } +static void +nft_update_parent_link_local_element(struct mnl_nlmsg_batch *batch, struct nftnl_set *s, const interface_t *ifp, + int cmd, uint8_t nfproto) +{ + struct nlmsghdr *nlh; + struct nftnl_set_elem *e; + uint16_t type = cmd == NFT_MSG_NEWSETELEM ? NLM_F_CREATE | NLM_F_ACK : NLM_F_ACK; + uint32_t data_buf[(sizeof(struct in6_addr) + IFNAMSIZ + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; + uint32_t len; + + /* nft add element ip keepalived parent_link_local { fe80::b89a:21ff:fee1:f794, "eth0" } */ + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + cmd, nfproto, + type, seq++); + data_buf[0] = ifp->base_ifp->sin6_addr.s6_addr32[0]; + data_buf[1] = ifp->base_ifp->sin6_addr.s6_addr32[1]; + data_buf[2] = ifp->base_ifp->sin6_addr.s6_addr32[2]; + data_buf[3] = ifp->base_ifp->sin6_addr.s6_addr32[3]; + len = sizeof(struct in6_addr); + + if (!(e = nftnl_set_elem_alloc())) { + log_message(LOG_INFO, "OOM error - %d", errno); + return; + } + + if (global_data->vrrp_nf_ifindex) { + data_buf[4] = ifp->ifindex; + len += sizeof(data_buf[4]); + } else { + memset(&data_buf[4], 0, IFNAMSIZ); + memcpy(&data_buf[4], ifp->ifname, strlen(ifp->ifname)); + len += IFNAMSIZ; + } + + nftnl_set_elem_set(e, NFTNL_SET_ELEM_KEY, &data_buf, len); + nftnl_set_elem_add(s, e); + + nftnl_set_elems_nlmsg_build_payload(nlh, s); + my_mnl_nlmsg_batch_next(batch); +} + static void nft_update_vmac_family(struct mnl_nlmsg_batch *batch, const interface_t *ifp, uint8_t nfproto, int cmd) { struct nftnl_set *s, *s1 = NULL; + struct nftnl_set *s2; if ((nfproto == NFPROTO_IPV4 && !ipv4_igmp_setup) || (nfproto == NFPROTO_IPV6 && !ipv6_igmp_setup)) @@ -1443,10 +1563,23 @@ nft_update_vmac_family(struct mnl_nlmsg_batch *batch, const interface_t *ifp, ui } nft_update_vmac_element(batch, s, ifp->ifindex, ifp->base_ifp->ifindex, cmd, nfproto); + if (nfproto == NFPROTO_IPV6) { #if HAVE_DECL_NFTA_DUP_MAX - if (nfproto == NFPROTO_IPV6) nft_update_vmac_element(batch, s1, ifp->ifindex, 0, cmd, nfproto); #endif + s2 = nftnl_set_alloc(); + if (s2 == NULL) { + log_message(LOG_INFO, "OOM error - %d", errno); + return; + } + + nftnl_set_set_str(s2, NFTNL_SET_TABLE, global_data->vrrp_nf_table_name); + nftnl_set_set_str(s2, NFTNL_SET_NAME, parent_link_local_set_name); + + nft_update_parent_link_local_element(batch, s2, ifp, cmd, nfproto); + + nftnl_set_free(s2); + } nftnl_set_free(s); #if HAVE_DECL_NFTA_DUP_MAX