diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c index 2d9b5598a..49ca156d5 100644 --- a/src/core/devices/nm-device-bond.c +++ b/src/core/devices/nm-device-bond.c @@ -923,6 +923,91 @@ deactivate(NMDevice *device) /*****************************************************************************/ +gboolean +nm_device_bond_is_slb(NMDevice *device) +{ + NMConnection *connection; + NMSettingBond *s_bond; + + connection = nm_device_get_applied_connection(device); + if (!connection) + return FALSE; + + s_bond = nm_connection_get_setting_bond(connection); + if (!s_bond) + return FALSE; + + if (!_nm_setting_bond_opt_value_as_intbool(s_bond, NM_SETTING_BOND_OPTION_BALANCE_SLB)) + return FALSE; + + return TRUE; +} + +gboolean +nm_device_bond_announce_ports_on_slb(NMDevice *controller, NMDevice *port) +{ + NMDeviceBond *self = NM_DEVICE_BOND(controller); + int port_ifindex = nm_device_get_ifindex(port); + int controller_ifindex = nm_device_get_ifindex(controller); + NML3Cfg *l3cfg = nm_device_get_l3cfg(controller); + NMDevice *bond_controller = nm_device_get_controller(controller); + NML3Cfg *bridge_l3cfg; + gs_free in_addr_t *addrs_array = NULL; + gsize addrs_len; + + addrs_array = nm_l3cfg_get_configured_ip4_addresses(l3cfg, &addrs_len); + + if (addrs_len > 0) { + /* the bond has IPs configured, it is not attached to a + * bridge then. */ + if (!nm_bond_manager_send_arp(controller_ifindex, + -1, + nm_device_get_platform(port), + addrs_array, + addrs_len)) { + _LOGT(LOGD_BOND, + "failed to send gARP on port %s (ifindex %d)", + nm_device_get_iface(port), + port_ifindex); + return FALSE; + } + } else if (bond_controller + && nm_device_get_device_type(bond_controller) == NM_DEVICE_TYPE_BRIDGE) { + /* the bond is attached to a bridge, firts let's check if the bridge has IP + * configuration. */ + bridge_l3cfg = nm_device_get_l3cfg(bond_controller); + addrs_array = nm_l3cfg_get_configured_ip4_addresses(bridge_l3cfg, &addrs_len); + if (addrs_len > 0) { + /* the bridge has IPs configured, announcing them on the bond */ + if (!nm_bond_manager_send_arp(controller_ifindex, + -1, + nm_device_get_platform(port), + addrs_array, + addrs_len)) { + _LOGT(LOGD_BOND, + "failed to send gARP on port %s (ifindex %d) on behalf of bridge", + nm_device_get_iface(port), + port_ifindex); + return FALSE; + } + } + + /* we are going to ARP probe the content of the FDB table */ + if (!nm_bond_manager_send_arp(controller_ifindex, + nm_device_get_ifindex(bond_controller), + nm_device_get_platform(port), + NULL, + 0)) { + _LOGT(LOGD_BOND, "failed to send ARP probing with content of FDB table"); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + static void nm_device_bond_init(NMDeviceBond *self) { diff --git a/src/core/devices/nm-device-bond.h b/src/core/devices/nm-device-bond.h index 083189bb7..2a415843d 100644 --- a/src/core/devices/nm-device-bond.h +++ b/src/core/devices/nm-device-bond.h @@ -23,4 +23,7 @@ typedef struct _NMDeviceBondClass NMDeviceBondClass; GType nm_device_bond_get_type(void); +gboolean nm_device_bond_is_slb(NMDevice *device); +gboolean nm_device_bond_announce_ports_on_slb(NMDevice *controller, NMDevice *port); + #endif /* NM_DEVICE_BOND_H */ diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 27c784059..ba3e07c9a 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -78,6 +78,7 @@ #include "nm-hostname-manager.h" #include "nm-device-generic.h" +#include "nm-device-bond.h" #include "nm-device-bridge.h" #include "nm-device-loopback.h" #include "nm-device-vlan.h" @@ -7542,10 +7543,12 @@ device_link_changed(gpointer user_data) NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); gboolean ip_ifname_changed = FALSE; nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL; + NMDevice *controller; const NMPlatformLink *pllink; const char *str; int ifindex; gboolean was_up; + gboolean carrier_was_up; gboolean update_unmanaged_specs = FALSE; gboolean got_hw_addr = FALSE, had_hw_addr; gboolean seen_down = priv->device_link_changed_down; @@ -7628,6 +7631,8 @@ device_link_changed(gpointer user_data) _LOGD(LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface); } + carrier_was_up = priv->carrier; + /* Update carrier from link event if applicable. */ if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT) && !nm_device_has_capability(self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) @@ -7644,6 +7649,35 @@ device_link_changed(gpointer user_data) was_up = priv->up; priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP); + if ((was_up && !priv->up) || (carrier_was_up && !priv->carrier)) { + /* the link was up and now is down, or the carrier was up and now is down. We must + * check if this is a port of a bond and if that bond is in balance-slb mode to perform + * gARP on the controller's port. + */ + controller = nm_device_get_controller(self); + if (controller && nm_device_get_device_type(controller) == NM_DEVICE_TYPE_BOND + && nm_device_bond_is_slb(controller)) { + NMDevicePrivate *controller_priv = NM_DEVICE_GET_PRIVATE(controller); + PortInfo *info; + + _LOGT( + LOGD_CORE, + "controller %s is a bond in bonding-slb mode, redirecting traffic to another port", + nm_device_get_iface(controller)); + + c_list_for_each_entry (info, &controller_priv->ports, lst_port) { + if (info->port != self && NM_DEVICE_GET_PRIVATE(info->port)->carrier) { + _LOGT(LOGD_CORE, + "sending gARP on port %s (ifindex %d)", + nm_device_get_iface(info->port), + nm_device_get_ifindex(info->port)); + if (nm_device_bond_announce_ports_on_slb(controller, info->port)) + break; + } + } + } + } + if (pllink->initialized && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) { nm_device_set_unmanaged_by_user_udev(self); nm_device_set_unmanaged_by_user_conf(self); diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c index c33e043d1..2f7fe36c1 100644 --- a/src/core/nm-bond-manager.c +++ b/src/core/nm-bond-manager.c @@ -6,8 +6,13 @@ #include +#include +#include +#include + #include "NetworkManagerUtils.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" +#include "libnm-platform/nm-linux-platform.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-object.h" @@ -94,6 +99,32 @@ struct _NMBondManager { /*****************************************************************************/ +#define IP_ADDR_LEN 4 + +#define ARP_OP_GARP 0x0001 +#define ARP_OP_RARP 0x0003 + +#define ARP_HW_TYPE_ETH 0x0001 + +#define ARP_PROTOCOL_IPV4 0x0800 + +typedef struct _nm_packed { + char s_addr[ETH_ALEN]; + char d_addr[ETH_ALEN]; + guint16 eth_type; + guint16 hw_type; + guint16 protocol; + guint8 addr_len; + guint8 ip_len; + guint16 op; + char s_hw_addr[ETH_ALEN]; + char s_ip_addr[IP_ADDR_LEN]; + char d_hw_addr[ETH_ALEN]; + char d_ip_addr[IP_ADDR_LEN]; +} ARPPacket; + +/*****************************************************************************/ + static void _nft_call(NMBondManager *self, gboolean up, const char *bond_ifname, @@ -839,6 +870,89 @@ nm_bond_manager_reapply(NMBondManager *self) _reconfigure_check(self, TRUE); } +gboolean +nm_bond_manager_send_arp(int bond_ifindex, + int bridge_ifindex, + struct _NMPlatform *platform, + in_addr_t *addrs_array, + gsize addrs_len) +{ + struct sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ARP), + .sll_ifindex = bond_ifindex, + }; + ARPPacket data; + const guint8 *hwaddr; + gsize hwaddrlen = 0; + nm_auto_close int sockfd = -1; + bool announce_fdb = FALSE; + + nm_assert(NM_IS_PLATFORM(platform)); + nm_assert(bond_ifindex); + + /* if the bridge_ifindex is specified is because we want to + * announce the FDB table content from the bridge */ + if (bridge_ifindex > 0) + announce_fdb = TRUE; + + sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); + if (sockfd < 0) + return FALSE; + + hwaddr = nm_platform_link_get_address(platform, bond_ifindex, &hwaddrlen); + /* infiniband interfaces not supported */ + if (hwaddrlen > ETH_ALEN) + return FALSE; + + /* common ARP options to be configured */ + memset(data.d_addr, 0xff, ETH_ALEN); + data.eth_type = htons(ETH_P_ARP); + data.hw_type = htons(ARP_HW_TYPE_ETH); + data.protocol = htons(ARP_PROTOCOL_IPV4); + data.addr_len = ETH_ALEN; + data.ip_len = IP_ADDR_LEN; + + if (announce_fdb) { + /* if we are announcing the FDB we do a RARP, we don't set the + * source/dest IPv4 address */ + int ifindexes[] = {bridge_ifindex, bond_ifindex}; + int i; + gs_free NMEtherAddr **fdb_addrs = NULL; + + fdb_addrs = nm_linux_platform_get_link_fdb_table(platform, ifindexes, 2); + /* we want to send a Reverse ARP (RARP) packet */ + data.op = htons(ARP_OP_RARP); + + i = 0; + while (fdb_addrs[i] != NULL) { + NMEtherAddr *tmp_hwaddr = fdb_addrs[i]; + memcpy(data.s_hw_addr, tmp_hwaddr, ETH_ALEN); + memcpy(data.d_hw_addr, tmp_hwaddr, ETH_ALEN); + memcpy(data.s_addr, tmp_hwaddr, ETH_ALEN); + g_free(tmp_hwaddr); + if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) + return FALSE; + i++; + } + } else { + /* we want to send a Gratuitous ARP (GARP) packet */ + data.op = htons(ARP_OP_GARP); + memcpy(data.s_addr, hwaddr, hwaddrlen); + memcpy(data.s_hw_addr, hwaddr, hwaddrlen); + for (int i = 0; i < addrs_len; i++) { + const in_addr_t tmp_addr = addrs_array[i]; + + unaligned_write_ne32(data.s_ip_addr, tmp_addr); + unaligned_write_ne32(data.d_ip_addr, tmp_addr); + if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) + return FALSE; + } + } + + return TRUE; +} + /*****************************************************************************/ int diff --git a/src/core/nm-bond-manager.h b/src/core/nm-bond-manager.h index 92a89f0b9..78ada15ba 100644 --- a/src/core/nm-bond-manager.h +++ b/src/core/nm-bond-manager.h @@ -23,6 +23,12 @@ NMBondManager *nm_bond_manager_new(struct _NMPlatform *platform, void nm_bond_manager_reapply(NMBondManager *self); +gboolean nm_bond_manager_send_arp(int bond_ifindex, + int bridge_ifindex, + struct _NMPlatform *platform, + in_addr_t *addrs_array, + gsize addrs_len); + void nm_bond_manager_destroy(NMBondManager *self); int nm_bond_manager_get_ifindex(NMBondManager *self); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 94d79da86..48a50a599 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -5552,6 +5552,30 @@ nm_l3cfg_get_best_default_route(NML3Cfg *self, int addr_family, gboolean get_com return nm_l3_config_data_get_best_default_route(l3cd, addr_family); } +in_addr_t * +nm_l3cfg_get_configured_ip4_addresses(NML3Cfg *self, gsize *out_len) +{ + GArray *array = NULL; + NMDedupMultiIter iter; + const NMPObject *obj; + const NML3ConfigData *l3cd; + + l3cd = nm_l3cfg_get_combined_l3cd(self, FALSE); + + if (!l3cd) + return NULL; + + array = g_array_new(FALSE, FALSE, sizeof(in_addr_t)); + + nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) { + in_addr_t tmp = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; + nm_g_array_append_simple(array, tmp); + } + + *out_len = array->len; + return NM_CAST_ALIGN(in_addr_t, g_array_free(array, FALSE)); +} + /*****************************************************************************/ gboolean diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index f5ebc69ea..a352860d4 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -437,6 +437,8 @@ const NML3ConfigData *nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_com const NMPObject * nm_l3cfg_get_best_default_route(NML3Cfg *self, int addr_family, gboolean get_commited); +in_addr_t *nm_l3cfg_get_configured_ip4_addresses(NML3Cfg *self, gsize *out_len); + /*****************************************************************************/ gboolean nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self); diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index 1ca3e304e..5f0e515a4 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -58,6 +58,17 @@ nm_ether_addr_from_string(NMEtherAddr *addr, const char *str) return addr; } +guint +nm_ether_addr_hash(const NMEtherAddr *a) +{ + NMHashState h; + + nm_hash_init(&h, 1947951703u); + nm_hash_update(&h, a, sizeof(NMEtherAddr)); + + return nm_hash_complete(&h); +} + /*****************************************************************************/ /** diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index ff855784e..754c21aec 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -241,6 +241,8 @@ nm_ether_addr_is_zero(const NMEtherAddr *a) return nm_memeq(a, &nm_ether_addr_zero, sizeof(NMEtherAddr)); } +guint nm_ether_addr_hash(const NMEtherAddr *a); + /*****************************************************************************/ struct ether_addr; diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index f12032aff..3510e68a8 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -323,6 +323,10 @@ G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1)); #define IFLA_VF_VLAN_INFO_UNSPEC 0 #define IFLA_VF_VLAN_INFO 1 +/*****************************************************************************/ + +#define NDA_CONTROLLER NDA_MASTER + /* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */ struct _ifla_vf_setting { guint32 vf; @@ -10411,6 +10415,125 @@ link_get_driver_info(NMPlatform *platform, /*****************************************************************************/ +typedef struct { + int ifindexes_len; + int *ifindexes; + GHashTable *out_fdb_addrs; +} FdbData; + +static int +parse_fdb_cb(const struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct ndmsg *ndmsg = NLMSG_DATA(nlh); + int from_ifindex = ndmsg->ndm_ifindex; + bool match = FALSE; + + static const struct nla_policy policy[] = { + [NDA_LLADDR] = {.minlen = ETH_ALEN, .maxlen = ETH_ALEN}, + [NDA_CONTROLLER] = {.type = NLA_U32}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + FdbData *data = arg; + int fdb_controller = -1; + + if (nlmsg_parse_arr(nlh, sizeof(*ndmsg), tb, policy) < 0) + return NL_SKIP; + + if (tb[NDA_CONTROLLER]) + fdb_controller = nla_get_u32(tb[NDA_CONTROLLER]); + + for (int i = 0; i < data->ifindexes_len; i++) { + int current_ifindex = data->ifindexes[i]; + + if (NM_IN_SET(current_ifindex, from_ifindex, fdb_controller)) { + match = TRUE; + break; + } + } + + if (!match) + return NL_SKIP; + + if (tb[NDA_LLADDR]) { + NMEtherAddr *hwaddr = g_new(NMEtherAddr, 1); + memcpy(hwaddr, nla_data(tb[NDA_LLADDR]), ETH_ALEN); + g_hash_table_add(data->out_fdb_addrs, hwaddr); + } + + return NL_OK; +} + +NMEtherAddr ** +nm_linux_platform_get_link_fdb_table(NMPlatform *platform, int *ifindexes, guint ifindexes_len) +{ + int nle; + struct nl_sock *sk = NULL; + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_unref_hashtable GHashTable *fdb_addrs = NULL; + FdbData data; + const struct ndmsg ndm = { + .ndm_family = AF_BRIDGE, + }; + gpointer *ret = NULL; + + nm_assert(ifindexes); + nm_assert(ifindexes_len >= 1); + + fdb_addrs = g_hash_table_new_full((GHashFunc) nm_ether_addr_hash, + (GEqualFunc) nm_ether_addr_equal, + g_free, + NULL); + + msg = nlmsg_alloc_new(0, RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP); + + if (nlmsg_append_struct(msg, &ndm) < 0) + goto err; + + nle = nl_socket_new(&sk, NETLINK_ROUTE, NL_SOCKET_FLAGS_DISABLE_MSG_PEEK, 0, 0); + if (nle < 0) { + _LOGD("get-link-fdb: error opening socket: %s (%d)", nm_strerror(nle), nle); + goto err; + } + + nle = nl_send_auto(sk, msg); + if (nle < 0) { + _LOGD("get-link-fdb: failed sending request: %s (%d)", nm_strerror(nle), nle); + goto err; + } + + data = ((FdbData) { + .ifindexes_len = ifindexes_len, + .ifindexes = ifindexes, + .out_fdb_addrs = fdb_addrs, + }); + + do { + nle = nl_recvmsgs(sk, + &((const struct nl_cb) { + .valid_cb = parse_fdb_cb, + .valid_arg = &data, + })); + } while (nle == -EAGAIN); + + if (nle < 0) { + _LOGD("get-link-fdb: recv failed: %s (%d)", nm_strerror(nle), nle); + goto err; + } + + ret = g_hash_table_get_keys_as_array(fdb_addrs, NULL); + g_hash_table_steal_all(fdb_addrs); + nl_socket_free(sk); + return NM_CAST_ALIGN(NMEtherAddr *, ret); + +err: + if (sk) + nl_socket_free(sk); + return NULL; +} + +/*****************************************************************************/ + static gboolean ip4_address_add(NMPlatform *platform, int ifindex, diff --git a/src/libnm-platform/nm-linux-platform.h b/src/libnm-platform/nm-linux-platform.h index 08135a4ac..3f591b7f1 100644 --- a/src/libnm-platform/nm-linux-platform.h +++ b/src/libnm-platform/nm-linux-platform.h @@ -25,6 +25,9 @@ GType nm_linux_platform_get_type(void); struct _NMDedupMultiIndex; +NMEtherAddr ** +nm_linux_platform_get_link_fdb_table(NMPlatform *platform, int *ifindexes, guint ifindexes_len); + NMPlatform *nm_linux_platform_new(struct _NMDedupMultiIndex *multi_idx, gboolean log_with_ptr, gboolean netns_support,