/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2011 - 2018 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-device-bond.h" #include #include #include #include #include "NetworkManagerUtils.h" #include "nm-device-private.h" #include "libnm-platform/nm-platform.h" #include "nm-device-factory.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-manager.h" #include "nm-setting-bond-port.h" #include "nm-bond-manager.h" #define _NMLOG_DEVICE_TYPE NMDeviceBond #include "nm-device-logging.h" /*****************************************************************************/ #define OPTIONS_APPLY_SUBSET \ NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, \ NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_ARP_INTERVAL, \ NM_SETTING_BOND_OPTION_ARP_VALIDATE, NM_SETTING_BOND_OPTION_PRIMARY, \ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, \ NM_SETTING_BOND_OPTION_AD_SELECT, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, \ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, \ NM_SETTING_BOND_OPTION_FAIL_OVER_MAC, NM_SETTING_BOND_OPTION_LACP_RATE, \ NM_SETTING_BOND_OPTION_LP_INTERVAL, NM_SETTING_BOND_OPTION_MIN_LINKS, \ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, \ NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, \ NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, \ NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, NM_SETTING_BOND_OPTION_LACP_ACTIVE #define OPTIONS_REAPPLY_SUBSET \ NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, \ NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_ARP_INTERVAL, \ NM_SETTING_BOND_OPTION_ARP_VALIDATE, NM_SETTING_BOND_OPTION_PRIMARY, \ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, \ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, \ NM_SETTING_BOND_OPTION_LP_INTERVAL, NM_SETTING_BOND_OPTION_MIN_LINKS, \ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER, \ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, \ NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, \ NM_SETTING_BOND_OPTION_LACP_ACTIVE #define OPTIONS_REAPPLY_FULL \ OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \ NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET /*****************************************************************************/ struct _NMDeviceBond { NMDevice parent; NMBondManager *bond_manager; }; struct _NMDeviceBondClass { NMDeviceClass parent; }; G_DEFINE_TYPE(NMDeviceBond, nm_device_bond, NM_TYPE_DEVICE) /*****************************************************************************/ static NMDeviceCapabilities get_generic_capabilities(NMDevice *dev) { return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; } static gboolean complete_connection(NMDevice *device, NMConnection *connection, const char *specific_object, NMConnection *const *existing_connections, GError **error) { nm_utils_complete_generic(nm_device_get_platform(device), connection, NM_SETTING_BOND_SETTING_NAME, existing_connections, NULL, _("Bond connection"), "bond", NULL); _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND); return TRUE; } /*****************************************************************************/ static gboolean _set_bond_attr(NMDevice *device, const char *attr, const char *value) { NMDeviceBond *self = NM_DEVICE_BOND(device); int ifindex = nm_device_get_ifindex(device); gboolean ret; nm_assert(attr && attr[0]); nm_assert(value); if (nm_streq(value, NM_BOND_AD_ACTOR_SYSTEM_DEFAULT) && nm_streq(attr, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) { gs_free char *cur_val = NULL; /* kernel does not allow setting ad_actor_system to "00:00:00:00:00:00". We would thus * log an EINVAL error. Avoid that... at least, if the value is already "00:00:00:00:00:00". */ cur_val = nm_platform_sysctl_controller_get_option(nm_device_get_platform(device), ifindex, attr); if (nm_streq0(cur_val, NM_BOND_AD_ACTOR_SYSTEM_DEFAULT)) return TRUE; /* OK, the current value is different, and we will proceed setting "00:00:00:00:00:00". * That will fail, and we will log a warning. There is nothing else to do. */ } ret = nm_platform_sysctl_controller_set_option(nm_device_get_platform(device), ifindex, attr, value); if (!ret) _LOGW(LOGD_PLATFORM, "failed to set bonding attribute '%s' to '%s'", attr, value); return ret; } #define _set_bond_attr_take(device, attr, value) \ G_STMT_START \ { \ gs_free char *_tmp = (value); \ \ _set_bond_attr(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, _tmp); \ } \ G_STMT_END #define _set_bond_attr_printf(device, attr, fmt, ...) \ _set_bond_attr_take((device), (attr), g_strdup_printf(fmt, __VA_ARGS__)) static gboolean ignore_option(NMSettingBond *s_bond, const char *option, const char *value) { const char *defvalue; if (nm_streq0(option, NM_SETTING_BOND_OPTION_MIIMON)) { /* The default value for miimon, when missing in the setting, is * 0 if arp_interval is != 0, and 100 otherwise. So, let's ignore * miimon=0 (which means that miimon is disabled) and accept any * other value. Adding miimon=100 does not cause any harm. */ defvalue = "0"; } else defvalue = nm_setting_bond_get_option_default(s_bond, option); return nm_streq0(value, defvalue); } static void update_connection(NMDevice *device, NMConnection *connection) { NMDeviceBond *self = NM_DEVICE_BOND(device); NMSettingBond *s_bond = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND); int ifindex = nm_device_get_ifindex(device); NMBondMode mode = NM_BOND_MODE_UNKNOWN; const char **options; /* Read bond options from sysfs and update the Bond setting to match */ options = nm_setting_bond_get_valid_options(NULL); for (; options[0]; options++) { const char *option = options[0]; gs_free char *value = NULL; char *p; if (NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, NM_SETTING_BOND_OPTION_BALANCE_SLB)) continue; value = nm_platform_sysctl_controller_get_option(nm_device_get_platform(device), ifindex, option); if (value && _nm_setting_bond_get_option_type(s_bond, option) == NM_BOND_OPTION_TYPE_BOTH) { p = strchr(value, ' '); if (p) *p = '\0'; } if (mode == NM_BOND_MODE_UNKNOWN) { if (value && nm_streq(option, NM_SETTING_BOND_OPTION_MODE)) mode = _nm_setting_bond_mode_from_string(value); if (mode == NM_BOND_MODE_UNKNOWN) continue; } if (!_nm_setting_bond_option_supported(option, mode)) continue; if (value && value[0] && !ignore_option(s_bond, option, value)) { /* Replace " " with "," for arp_ip_targets from the kernel */ if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) { for (p = value; *p; p++) { if (*p == ' ') *p = ','; } } if (!_nm_setting_bond_validate_option(option, value, NULL)) _LOGT(LOGD_BOND, "cannot set invalid bond option '%s' = '%s'", option, value); else nm_setting_bond_add_option(s_bond, option, value); } } } static gboolean controller_update_port_connection(NMDevice *self, NMDevice *port, NMConnection *connection, GError **error) { NMSettingBondPort *s_port; int ifindex_port = nm_device_get_ifindex(port); NMConnection *applied_connection = nm_device_get_applied_connection(self); const NMPlatformLink *pllink; g_return_val_if_fail(ifindex_port > 0, FALSE); s_port = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND_PORT); pllink = nm_platform_link_get(nm_device_get_platform(port), ifindex_port); if (pllink && pllink->port_kind == NM_PORT_KIND_BOND) g_object_set(s_port, NM_SETTING_BOND_PORT_QUEUE_ID, pllink->port_data.bond.queue_id, NM_SETTING_BOND_PORT_PRIO, pllink->port_data.bond.prio, NULL); g_object_set(nm_connection_get_setting_connection(connection), NM_SETTING_CONNECTION_CONTROLLER, nm_connection_get_uuid(applied_connection), NM_SETTING_CONNECTION_PORT_TYPE, NM_SETTING_BOND_SETTING_NAME, NULL); return TRUE; } static void set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new_arp_ip_target) { gs_unref_ptrarray GPtrArray *free_list = NULL; gs_free const char **cur_strv = NULL; gs_free const char **new_strv = NULL; gsize cur_len; gsize new_len; gsize i; gsize j; cur_strv = nm_strsplit_set_full(cur_arp_ip_target, NM_ASCII_SPACES, NM_STRSPLIT_SET_FLAGS_STRSTRIP); new_strv = nm_utils_bond_option_ip_split(new_arp_ip_target); cur_len = NM_PTRARRAY_LEN(cur_strv); new_len = NM_PTRARRAY_LEN(new_strv); if (new_len > 0) { for (j = 0, i = 0; i < new_len; i++) { const char *s; in_addr_t a4; s = new_strv[i]; if (nm_inet_parse_bin(AF_INET, s, NULL, &a4)) { char sbuf[INET_ADDRSTRLEN]; nm_inet4_ntop(a4, sbuf); if (!nm_streq(s, sbuf)) { if (!free_list) free_list = g_ptr_array_new_with_free_func(g_free); s = g_strdup(sbuf); g_ptr_array_add(free_list, (gpointer) s); } } if (!nm_strv_contains(new_strv, i, s)) new_strv[j++] = s; } new_strv[j] = NULL; new_len = j; } if (cur_len == 0 && new_len == 0) return; if (nm_strv_equal(cur_strv, new_strv)) return; for (i = 0; i < cur_len; i++) _set_bond_attr_printf(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "-%s", cur_strv[i]); for (i = 0; i < new_len; i++) _set_bond_attr_printf(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "+%s", new_strv[i]); } /* * Sets bond attribute stored in the option hashtable or * the default value if no value was set. */ static void set_bond_attr_or_default(NMDevice *device, NMSettingBond *s_bond, const char *opt) { NMDeviceBond *self = NM_DEVICE_BOND(device); const char *value; value = nm_setting_bond_get_option_normalized(s_bond, opt); if (!value) { if (_LOGT_ENABLED(LOGD_BOND) && nm_setting_bond_get_option_by_name(s_bond, opt)) _LOGT(LOGD_BOND, "bond option '%s' not set as it conflicts with other options", opt); return; } _set_bond_attr(device, opt, value); } static void set_bond_attrs_or_default(NMDevice *device, NMSettingBond *s_bond, const char *const *attr_v) { nm_assert(NM_IS_DEVICE(device)); nm_assert(s_bond); nm_assert(attr_v); for (; *attr_v; ++attr_v) set_bond_attr_or_default(device, s_bond, *attr_v); } static void set_bond_arp_ip_targets(NMDevice *device, NMSettingBond *s_bond) { int ifindex = nm_device_get_ifindex(device); gs_free char *cur_arp_ip_target = NULL; /* ARP targets: clear and initialize the list */ cur_arp_ip_target = nm_platform_sysctl_controller_get_option(nm_device_get_platform(device), ifindex, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); set_arp_targets( device, cur_arp_ip_target, nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)); } static guint8 _bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_MAX_ARP_TARGETS]) { gs_free const char **ip = NULL; in_addr_t in_a; int i; int added = 0; ip = nm_utils_bond_option_ip_split(value); if (!ip) return added; for (i = 0; ip[i]; i++) { if (added > NM_BOND_MAX_ARP_TARGETS - 1) break; if (!nm_inet_parse_bin(AF_INET, ip[i], NULL, &in_a)) nm_assert_not_reached(); /* verify() already validated the IP addresses */ out[added++] = in_a; } return added; } static guint8 _bond_ns_ip6_target_to_platform(const char *value, struct in6_addr out[static NM_BOND_MAX_ARP_TARGETS]) { gs_free const char **ip = NULL; struct in6_addr in6_a; int i; int added = 0; ip = nm_utils_bond_option_ip_split(value); if (!ip) return added; for (i = 0; ip[i]; i++) { if (added > NM_BOND_MAX_ARP_TARGETS - 1) break; if (!nm_inet_parse_bin(AF_INET6, ip[i], NULL, &in6_a)) nm_assert_not_reached(); /* verify() already validated the IP addresses */ out[added++] = in6_a; } return added; } static int _setting_bond_primary_opt_as_ifindex(NMSettingBond *s_bond) { const char *primary_str; int ifindex = 0; primary_str = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_PRIMARY); if (primary_str != NULL) ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, primary_str); return ifindex; } static void _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *props) { const char *opt_value; #define _v_fcn(fcn, s_bond, opt) (fcn(nm_setting_bond_get_option_normalized((s_bond), (opt)))) #define _v_u8(s_bond, opt) _nm_setting_bond_opt_value_as_u8((s_bond), (opt)) #define _v_u16(s_bond, opt) _nm_setting_bond_opt_value_as_u16((s_bond), (opt)) #define _v_u32(s_bond, opt) _nm_setting_bond_opt_value_as_u32((s_bond), (opt)) #define _v_intbool(s_bond, opt) _nm_setting_bond_opt_value_as_intbool((s_bond), (opt)) *props = (NMPlatformLnkBond) { .mode = _v_fcn(_nm_setting_bond_mode_from_string, s_bond, NM_SETTING_BOND_OPTION_MODE), .primary = _setting_bond_primary_opt_as_ifindex(s_bond), .miimon = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON), .updelay = _v_u32(s_bond, NM_SETTING_BOND_OPTION_UPDELAY), .downdelay = _v_u32(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY), .arp_interval = _v_u32(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL), .resend_igmp = _v_u32(s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP), .min_links = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS), .lp_interval = _v_u32(s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL), .packets_per_port = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE), .peer_notif_delay = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY), .arp_all_targets = _v_fcn(_nm_setting_bond_arp_all_targets_from_string, s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS), .arp_validate = _v_fcn(_nm_setting_bond_arp_validate_from_string, s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE), .ad_actor_sys_prio = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO), .ad_user_port_key = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY), .primary_reselect = _v_fcn(_nm_setting_bond_primary_reselect_from_string, s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT), .fail_over_mac = _v_fcn(_nm_setting_bond_fail_over_mac_from_string, s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC), .xmit_hash_policy = _v_fcn(_nm_setting_bond_xmit_hash_policy_from_string, s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY), .num_grat_arp = _v_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP), .all_ports_active = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE), .arp_missed_max = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX), .lacp_active = _v_fcn(_nm_setting_bond_lacp_active_from_string, s_bond, NM_SETTING_BOND_OPTION_LACP_ACTIVE), .lacp_rate = _v_fcn(_nm_setting_bond_lacp_rate_from_string, s_bond, NM_SETTING_BOND_OPTION_LACP_RATE), .ad_select = _v_fcn(_nm_setting_bond_ad_select_from_string, s_bond, NM_SETTING_BOND_OPTION_AD_SELECT), .use_carrier = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER), .tlb_dynamic_lb = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB), }; nm_ether_addr_from_string( &props->ad_actor_system, nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)); opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); if (opt_value != NULL) props->arp_ip_targets_num = _bond_arp_ip_target_to_platform(opt_value, props->arp_ip_target); opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_NS_IP6_TARGET); if (opt_value != NULL) props->ns_ip6_targets_num = _bond_ns_ip6_target_to_platform(opt_value, props->ns_ip6_target); props->miimon_has = !props->arp_interval && !props->arp_validate; props->updelay_has = props->miimon_has && props->miimon; props->downdelay_has = props->miimon_has && props->miimon; props->peer_notif_delay_has = (props->miimon || props->arp_interval) && props->peer_notif_delay; props->resend_igmp_has = props->resend_igmp != 1; props->lp_interval_has = props->lp_interval != 1; props->tlb_dynamic_lb_has = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB); props->lacp_active_has = NM_IN_SET(props->mode, NM_BOND_MODE_8023AD); } static void _balance_slb_cb(NMBondManager *bond_manager, NMBondManagerEventType event_type, gpointer user_data) { NMDevice *device = user_data; NMDeviceBond *self = NM_DEVICE_BOND(device); nm_assert(NM_IS_DEVICE_BOND(self)); nm_assert(self->bond_manager == bond_manager); switch (event_type) { case NM_BOND_MANAGER_EVENT_TYPE_STATE: switch (nm_bond_manager_get_state(bond_manager)) { case NM_OPTION_BOOL_FALSE: if (nm_device_get_state(device) <= NM_DEVICE_STATE_ACTIVATED) { _LOGD(LOGD_BOND, "balance-slb: failed"); nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED); } return; case NM_OPTION_BOOL_TRUE: if (nm_device_get_state(device) <= NM_DEVICE_STATE_ACTIVATED && nm_device_devip_get_state(device, AF_UNSPEC) <= NM_DEVICE_IP_STATE_PENDING) { nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_READY, NULL); } return; case NM_OPTION_BOOL_DEFAULT: if (nm_device_get_state(device) <= NM_DEVICE_STATE_ACTIVATED && nm_device_devip_get_state(device, AF_UNSPEC) == NM_DEVICE_IP_STATE_READY) { /* We are again busy. We can also go back to "pending" from "ready". * If ip-config state is not yet complete, this will further delay it. * Otherwise, it should have no effect. */ nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING, NULL); } return; } nm_assert_not_reached(); return; } nm_assert_not_reached(); } static void _balance_slb_setup(NMDeviceBond *self, NMConnection *connection) { int ifindex = nm_device_get_ifindex(NM_DEVICE(self)); gboolean balance_slb = FALSE; const char *uuid; NMSettingBond *s_bond; if (ifindex > 0 && connection && (s_bond = nm_connection_get_setting_bond(connection))) balance_slb = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_BALANCE_SLB); if (!balance_slb) { if (nm_clear_pointer(&self->bond_manager, nm_bond_manager_destroy)) { _LOGD(LOGD_BOND, "balance-slb: stopped"); nm_device_devip_set_state(NM_DEVICE(self), AF_UNSPEC, NM_DEVICE_IP_STATE_NONE, NULL); } return; } uuid = nm_connection_get_uuid(connection); if (self->bond_manager) { if (nm_bond_manager_get_ifindex(self->bond_manager) == ifindex && nm_streq0(nm_bond_manager_get_connection_uuid(self->bond_manager), uuid)) { _LOGD(LOGD_BOND, "balance-slb: reapply"); nm_bond_manager_reapply(self->bond_manager); return; } nm_clear_pointer(&self->bond_manager, nm_bond_manager_destroy); _LOGD(LOGD_BOND, "balance-slb: restart"); } _LOGD(LOGD_BOND, "balance-slb: start"); if (nm_device_devip_get_state(NM_DEVICE(self), AF_UNSPEC) < NM_DEVICE_IP_STATE_PENDING) nm_device_devip_set_state(NM_DEVICE(self), AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING, NULL); self->bond_manager = nm_bond_manager_new(nm_device_get_platform(NM_DEVICE(self)), ifindex, uuid, _balance_slb_cb, self); nm_assert(nm_bond_manager_get_state(self->bond_manager) == NM_OPTION_BOOL_DEFAULT); } static NMActStageReturn act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) { NMDeviceBond *self = NM_DEVICE_BOND(device); NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; NMConnection *connection; NMSettingBond *s_bond; NMPlatformLnkBond props; int r; int ifindex = nm_device_get_ifindex(device); connection = nm_device_get_applied_connection(device); g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); s_bond = nm_connection_get_setting_bond(connection); g_return_val_if_fail(s_bond, NM_ACT_STAGE_RETURN_FAILURE); if (nm_device_managed_type_is_external(device)) return NM_ACT_STAGE_RETURN_SUCCESS; _balance_slb_setup(self, connection); if (nm_device_managed_type_is_external_or_assume(device)) return NM_ACT_STAGE_RETURN_SUCCESS; _platform_lnk_bond_init_from_setting(s_bond, &props); /* Interface must be down to set bond options */ nm_device_take_down(device, TRUE); r = nm_platform_link_bond_change(nm_device_get_platform(device), ifindex, &props); if (r < 0) { ret = NM_ACT_STAGE_RETURN_FAILURE; NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); } else { if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) ret = NM_ACT_STAGE_RETURN_FAILURE; } /* This is a workaround because netlink do not support ifname as primary */ set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY); nm_device_bring_up(device); return ret; } static void commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_port) { NMBondMode mode = NM_BOND_MODE_UNKNOWN; const char *value; NMSettingBond *s_bond; gint32 prio; gboolean prio_has; s_bond = nm_device_get_applied_setting(bond_device, NM_TYPE_SETTING_BOND); if (s_bond) { value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_MODE); mode = _nm_setting_bond_mode_from_string(value); } prio = s_port ? nm_setting_bond_port_get_prio(s_port) : NM_BOND_PORT_PRIO_DEF; if (prio != 0) { /* The profile explicitly sets the priority. No matter what, we try to set it * in netlink. */ prio_has = TRUE; } else if (!NM_IN_SET(mode, NM_BOND_MODE_ACTIVEBACKUP, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB)) { /* The priority only is configurable with certain modes. If we don't have * one of those modes, don't try to set the priority explicitly to zero. */ prio_has = FALSE; } else if (nm_platform_kernel_support_get_full( NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BOND_PORT_PRIO, FALSE) == NM_OPTION_BOOL_TRUE) { /* We can only detect support if we have it. We cannot detect lack of support if * we don't have it. * * But we did explicitly detect support, so explicitly set the prio to zero. */ prio_has = TRUE; } else { /* We either have an unsuitable mode or didn't detect kernel support for the * priority. Don't explicitly set priority to zero. It is already the default, * so it shouldn't be necessary. */ prio_has = FALSE; } nm_platform_link_change(nm_device_get_platform(port), nm_device_get_ifindex(port), NULL, &((NMPlatformLinkBondPort) { .queue_id = s_port ? nm_setting_bond_port_get_queue_id(s_port) : NM_BOND_PORT_QUEUE_ID_DEF, .prio = prio_has ? prio : 0, .prio_has = prio_has, }), NULL, 0); } static NMTernary attach_port(NMDevice *device, NMDevice *port, NMConnection *connection, gboolean configure, GCancellable *cancellable, NMDeviceAttachPortCallback callback, gpointer user_data) { NMDeviceBond *self = NM_DEVICE_BOND(device); NMSettingBondPort *s_port; nm_device_controller_check_port_physical_port(device, port, LOGD_BOND); if (configure) { gboolean success; nm_device_take_down(port, TRUE); success = nm_platform_link_attach_port(nm_device_get_platform(device), nm_device_get_ip_ifindex(device), nm_device_get_ip_ifindex(port)); nm_device_bring_up(port); if (!success) { _LOGI(LOGD_BOND, "attaching bond port %s: failed", nm_device_get_ip_iface(port)); return FALSE; } s_port = _nm_connection_get_setting(connection, NM_TYPE_SETTING_BOND_PORT); commit_port_options(device, port, s_port); _LOGI(LOGD_BOND, "attached bond port %s", nm_device_get_ip_iface(port)); } else _LOGI(LOGD_BOND, "bond port %s was attached", nm_device_get_ip_iface(port)); return TRUE; } static NMTernary detach_port(NMDevice *device, NMDevice *port, gboolean configure, GCancellable *cancellable, NMDeviceAttachPortCallback callback, gpointer user_data) { NMDeviceBond *self = NM_DEVICE_BOND(device); gboolean success; gs_free char *address = NULL; int ifindex_port; int ifindex; if (configure) { ifindex = nm_device_get_ifindex(device); if (ifindex <= 0 || !nm_platform_link_get(nm_device_get_platform(device), ifindex)) configure = FALSE; } ifindex_port = nm_device_get_ip_ifindex(port); if (ifindex_port <= 0) _LOGD(LOGD_BOND, "bond port %s is already detached", nm_device_get_ip_iface(port)); if (configure) { NMConnection *applied; NMSettingWired *s_wired; const char *cloned_mac; address = g_strdup(nm_device_get_hw_address(device)); if (ifindex_port > 0) { success = nm_platform_link_release_port(nm_device_get_platform(device), nm_device_get_ip_ifindex(device), ifindex_port); if (success) { _LOGI(LOGD_BOND, "detached bond port %s", nm_device_get_ip_iface(port)); } else { _LOGW(LOGD_BOND, "failed to detach bond port %s", nm_device_get_ip_iface(port)); } } if ((applied = nm_device_get_applied_connection(device)) && ((s_wired = nm_connection_get_setting_wired(applied))) && ((cloned_mac = nm_setting_wired_get_cloned_mac_address(s_wired)))) { /* When the last port is released the bond MAC will be set to a random * value by kernel; if we have set a cloned-mac-address, we need to * restore it to the previous value. */ nm_platform_process_events(nm_device_get_platform(device)); if (nm_device_update_hw_address(device)) nm_device_hw_addr_set(device, address, "restore", FALSE); } /* Kernel bonding code "closes" the port when releasing it, (which clears * IFF_UP), so we must bring it back up here to ensure carrier changes and * other state is noticed by the now-released port. */ if (ifindex_port > 0) { if (!nm_device_bring_up(port)) _LOGW(LOGD_BOND, "detached bond port could not be brought up."); } } else { if (ifindex_port > 0) { _LOGI(LOGD_BOND, "bond port %s was detached", nm_device_get_ip_iface(port)); } } return TRUE; } static gboolean create_and_realize(NMDevice *device, NMConnection *connection, NMDevice *parent, const NMPlatformLink **out_plink, GError **error) { const char *iface = nm_device_get_iface(device); NMSettingBond *s_bond; NMPlatformLnkBond props; int r; g_assert(iface); s_bond = nm_connection_get_setting_bond(connection); nm_assert(s_bond); _platform_lnk_bond_init_from_setting(s_bond, &props); r = nm_platform_link_bond_add(nm_device_get_platform(device), iface, &props, out_plink); if (r < 0) { g_set_error(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED, "Failed to create bond interface '%s' for '%s': %s", iface, nm_connection_get_id(connection), nm_strerror(r)); return FALSE; } return TRUE; } static gboolean can_reapply_change(NMDevice *device, const char *setting_name, NMSetting *s_old, NMSetting *s_new, GHashTable *diffs, GError **error) { NMDeviceClass *device_class; /* Only handle bond setting here, delegate other settings to parent class */ if (nm_streq(setting_name, NM_SETTING_BOND_SETTING_NAME)) { NMSettingBond *s_a = NM_SETTING_BOND(s_old); NMSettingBond *s_b = NM_SETTING_BOND(s_new); const char **option_list; if (!nm_device_hash_check_invalid_keys(diffs, NM_SETTING_BOND_SETTING_NAME, error, NM_SETTING_BOND_OPTIONS)) return FALSE; option_list = nm_setting_bond_get_valid_options(NULL); for (; *option_list; ++option_list) { const char *name = *option_list; /* We support changes to these */ if (NM_IN_STRSET(name, OPTIONS_REAPPLY_FULL, NM_SETTING_BOND_OPTION_BALANCE_SLB)) continue; /* Reject any other changes */ if (!nm_streq0(nm_setting_bond_get_option_normalized(s_a, name), nm_setting_bond_get_option_normalized(s_b, name))) { g_set_error(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, "Can't reapply '%s' bond option", name); return FALSE; } } return TRUE; } device_class = NM_DEVICE_CLASS(nm_device_bond_parent_class); return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); } static void reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new) { NMDeviceBond *self = NM_DEVICE_BOND(device); NMSettingBond *s_bond; const char *value; NMBondMode mode; NM_DEVICE_CLASS(nm_device_bond_parent_class)->reapply_connection(device, con_old, con_new); _LOGD(LOGD_BOND, "reapplying bond settings"); s_bond = nm_connection_get_setting_bond(con_new); g_return_if_fail(s_bond); value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_MODE); mode = _nm_setting_bond_mode_from_string(value); g_return_if_fail(mode != NM_BOND_MODE_UNKNOWN); /* Below we set only the bond options that kernel allows one to modify * while keeping the bond interface up */ set_bond_arp_ip_targets(device, s_bond); set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_REAPPLY_SUBSET)); _balance_slb_setup(self, con_new); } static void deactivate(NMDevice *device) { NMDeviceBond *self = NM_DEVICE_BOND(device); _balance_slb_setup(self, NULL); } /*****************************************************************************/ 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) { nm_assert(nm_device_is_controller(NM_DEVICE(self))); } static const NMDBusInterfaceInfoExtended interface_info_device_bond = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( NM_DBUS_INTERFACE_DEVICE_BOND, .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "HwAddress", "s", NM_DEVICE_HW_ADDRESS, .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "Carrier", "b", NM_DEVICE_CARRIER, .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "Slaves", "ao", NM_DEVICE_SLAVES, .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ), }; static void nm_device_bond_class_init(NMDeviceBondClass *klass) { NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bond); device_class->connection_type_supported = NM_SETTING_BOND_SETTING_NAME; device_class->connection_type_check_compatible = NM_SETTING_BOND_SETTING_NAME; device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_BOND); device_class->is_controller = TRUE; device_class->get_generic_capabilities = get_generic_capabilities; device_class->complete_connection = complete_connection; device_class->update_connection = update_connection; device_class->controller_update_port_connection = controller_update_port_connection; device_class->create_and_realize = create_and_realize; device_class->act_stage1_prepare = act_stage1_prepare; device_class->act_stage1_prepare_also_for_external_or_assume = TRUE; device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired; device_class->attach_port = attach_port; device_class->detach_port = detach_port; device_class->can_reapply_change = can_reapply_change; device_class->reapply_connection = reapply_connection; device_class->deactivate = deactivate; } /*****************************************************************************/ #define NM_TYPE_BOND_DEVICE_FACTORY (nm_bond_device_factory_get_type()) #define NM_BOND_DEVICE_FACTORY(obj) \ (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BOND_DEVICE_FACTORY, NMBondDeviceFactory)) static NMDevice * create_device(NMDeviceFactory *factory, const char *iface, const NMPlatformLink *plink, NMConnection *connection, gboolean *out_ignore) { return g_object_new(NM_TYPE_DEVICE_BOND, NM_DEVICE_IFACE, iface, NM_DEVICE_DRIVER, "bonding", NM_DEVICE_TYPE_DESC, "Bond", NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BOND, NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_BOND, NULL); } NM_DEVICE_FACTORY_DEFINE_INTERNAL( BOND, Bond, bond, NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_BOND) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BOND_SETTING_NAME), factory_class->create_device = create_device;);