bonding: send ARP announcement on bonding-slb link/carrier down

When a bond in balance-slb is created, the ports are enabled or disabled
based on carrier and link state. If the link/carrier goes down, the port
becomes disabled and we must make sure the MAC tables of the switches
are updated properly so the traffic is redirected.

In order to solve this, we send a GARP or RARP broadcast packet on the
bond. This fix cover 3 different balance-slb scenarios.

Scenario 1: The bond in balance-slb mode has IPv4 address configured and
some ports connected. Here the bond is acting like active-backup as the
packets will always have as source MAC the address of the bond
interface. When a port goes down, NetworkManager will send a GARP
broadcast announcing the address configured on the bond with the MAC
address configured on the port.

Scenario 2: The bond in balance-slb mode is connected to a bridge and has
some ports connected. The bridge has IPv4 configured. When a port goes
down, NetworkManager will send a GARP broadcast announcing the address
configured on the bridge with the MAC address configured on the port.

Scenario 3: The bond in balance-slb mode is connected to a bridge and
has some ports connected. The bridge does not have IP configuration and
therefore everything is L2. When a port goes down, NetworkManager will
query the FDB table and filter the entries by the ones belonging to the
bridge and the bond ifindexes. Then, it will send a RARP broadcast
announcing every learned MAC address from FDB.

Fixes: e9268e3924 ('firewall: add mlag firewall utils for multi chassis link aggregation (MLAG) for bonding-slb')
This commit is contained in:
Fernando Fernandez Mancera
2024-11-07 09:10:20 +01:00
parent 00f47efcb2
commit 3f2f922dd9
5 changed files with 242 additions and 0 deletions

View File

@@ -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)
{

View File

@@ -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 */

View File

@@ -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);

View File

@@ -6,8 +6,13 @@
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/socket.h>
#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

View File

@@ -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);