diff --git a/src/nm-device.c b/src/nm-device.c index 0e553f507..8f243320d 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -537,6 +537,16 @@ nm_device_get_act_request (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->act_request; } +NMConnection * +nm_device_get_connection (NMDevice *self) +{ + NMActRequest *req; + + req = nm_device_get_act_request (self); + g_assert (req); + + return nm_act_request_get_connection (req); +} gboolean nm_device_is_available (NMDevice *self) @@ -759,6 +769,52 @@ real_act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason) return NM_ACT_STAGE_RETURN_SUCCESS; } +static gboolean +handle_slave_activation (NMDevice *slave, NMDevice *master) +{ + NMConnection *connection; + NMSettingConnection *s_con; + + connection = nm_device_get_connection (slave); + g_assert (connection); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (nm_setting_connection_is_slave_type (s_con, NM_SETTING_BOND_SETTING_NAME)) { + /* + * Bonding + * + * Kernel expects slaves to be down while the enslaving is + * taking place. + */ + nm_device_hw_take_down (slave, TRUE); + + if (!nm_system_iface_enslave (slave, master)) + return FALSE; + + nm_device_hw_bring_up (slave, TRUE, NULL); + } + + return TRUE; +} + +static void +handle_slave_deactivation (NMDevice *slave, NMDevice *master) +{ + NMConnection *connection; + NMSettingConnection *s_con; + + connection = nm_device_get_connection (slave); + g_assert (connection); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (nm_setting_connection_is_slave_type (s_con, NM_SETTING_BOND_SETTING_NAME)) + nm_system_iface_release (slave, master); +} + /* * nm_device_activate_stage1_device_prepare * @@ -773,6 +829,7 @@ nm_device_activate_stage1_device_prepare (gpointer user_data) const char *iface; NMActStageReturn ret; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + NMDevice *master; /* Clear the activation source ID now that this stage has run */ activation_source_clear (self, FALSE, 0); @@ -783,6 +840,13 @@ nm_device_activate_stage1_device_prepare (gpointer user_data) nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface); nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE); + if ((master = nm_device_get_master (self))) { + if (!handle_slave_activation (self, master)) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + } + ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason); if (ret == NM_ACT_STAGE_RETURN_POSTPONE) { goto out; @@ -2771,6 +2835,7 @@ nm_device_deactivate (NMDeviceInterface *device, NMDeviceStateReason reason) NMDevice *self = NM_DEVICE (device); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; + NMDevice *master; gboolean tried_ipv6 = FALSE; int ifindex, family; @@ -2814,6 +2879,9 @@ nm_device_deactivate (NMDeviceInterface *device, NMDeviceStateReason reason) if (NM_DEVICE_GET_CLASS (self)->deactivate) NM_DEVICE_GET_CLASS (self)->deactivate (self); + if ((master = nm_device_get_master (self))) + handle_slave_deactivation (self, master); + /* Tear down an existing activation request */ clear_act_request (self); diff --git a/src/nm-device.h b/src/nm-device.h index d27f64f26..0d51bc513 100644 --- a/src/nm-device.h +++ b/src/nm-device.h @@ -155,6 +155,7 @@ NMDevice * nm_device_get_master (NMDevice *self); void nm_device_set_master (NMDevice *self, NMDevice *master); NMActRequest * nm_device_get_act_request (NMDevice *dev); +NMConnection * nm_device_get_connection (NMDevice *dev); gboolean nm_device_is_available (NMDevice *dev); diff --git a/src/nm-netlink-compat.h b/src/nm-netlink-compat.h index ba15caf98..ecc21c3d4 100644 --- a/src/nm-netlink-compat.h +++ b/src/nm-netlink-compat.h @@ -16,6 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2011 Caixa Magica Software. + * Copyright (C) 2011 Red Hat, Inc. */ #ifndef NM_NETLINK_COMPAT_H @@ -241,6 +242,19 @@ rtnl_link_get_type (struct rtnl_link *rtnl_link) return NULL; } +static inline int +rtnl_link_bond_enslave_ifindex (struct nl_sock *h, int master_ifidx, int slave_ifidx) +{ + /* Bonding only in libnl3 */ + return -NLE_OPNOTSUPP; +} + +static inline int +rtnl_link_bond_release_ifindex (struct nl_sock *h, int slave_ifidx) +{ + /* Bonding only in libnl3 */ + return -NLE_OPNOTSUPP; +} #endif /* HAVE_LIBNL1 || HAVE_LIBNL2 */ #endif /* NM_NETLINK_COMPAT_H */ diff --git a/src/nm-system.c b/src/nm-system.c index 9e3b78c93..709a08b7a 100644 --- a/src/nm-system.c +++ b/src/nm-system.c @@ -21,6 +21,7 @@ * Copyright (C) January, 1998 Sergei Viznyuk */ +#include #include #include #include @@ -41,6 +42,8 @@ #include #include #include +#include +#include #include "nm-system.h" #include "nm-device.h" @@ -57,6 +60,10 @@ #include #include +#ifdef HAVE_LIBNL3 +#include +#endif + static void nm_system_device_set_priority (int ifindex, NMIP4Config *config, int priority); @@ -657,15 +664,8 @@ nm_system_iface_set_up (int ifindex, return success; } -/** - * nm_system_iface_is_up: - * @ifindex: interface index - * - * Returns: %TRUE if the interface is up, %FALSE if it was down or the check - * failed. - **/ -gboolean -nm_system_iface_is_up (int ifindex) +guint32 +nm_system_iface_get_flags (int ifindex) { struct rtnl_link *l; guint32 flags; @@ -686,7 +686,20 @@ nm_system_iface_is_up (int ifindex) flags = rtnl_link_get_flags (l); rtnl_link_put (l); - return flags & IFF_UP; + return flags; +} + +/** + * nm_system_iface_is_up: + * @ifindex: interface index + * + * Returns: %TRUE if the interface is up, %FALSE if it was down or the check + * failed. + **/ +gboolean +nm_system_iface_is_up (int ifindex) +{ + return nm_system_iface_get_flags (ifindex) & IFF_UP; } /** @@ -1212,7 +1225,7 @@ nm_system_device_set_priority (int ifindex, * Returns: %TRUE on success, %FALSE on failure */ gboolean -nm_system_add_bonding_master(NMSettingBond *setting) +nm_system_add_bonding_master (NMSettingBond *setting) { struct nl_sock *sock; const char *name; @@ -1234,6 +1247,181 @@ nm_system_add_bonding_master(NMSettingBond *setting) return TRUE; } +static gboolean +nm_system_iface_compat_enslave (NMDevice *slave, const char *master_name) +{ + struct ifreq ifr; + int fd; + gboolean ret = FALSE; + + memset (&ifr, 0, sizeof (ifr)); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_DEVICE, "couldn't open control socket."); + return FALSE; + } + + strncpy (ifr.ifr_name, master_name, IFNAMSIZ); + strncpy (ifr.ifr_slave, nm_device_get_iface (slave), IFNAMSIZ); + + if (ioctl (fd, SIOCBONDENSLAVE, &ifr) < 0 && + ioctl (fd, BOND_ENSLAVE_OLD, &ifr) < 0) { + nm_log_err (LOGD_DEVICE, "(%s): error enslaving %s: %d (%s)", + master_name, nm_device_get_iface (slave), + errno, strerror (errno)); + goto errout; + } + + ret = TRUE; + +errout: + close (fd); + + return ret; +} + +/** + * nm_system_iface_enslave: + * @slave: Slave device + * @master: Master device + * + * Enslaves the 'slave' to 'master. This function targets implementing a + * generic interface to attaching all kinds of slaves to masters. Currently + * only bonding is properly supported due to the backwards compatibility + * function being bonding specific. + * + * The slave device needs to be down as a prerequirement. + * + * Returns: %TRUE on success, or %FALSE + */ +gboolean +nm_system_iface_enslave (NMDevice *slave, NMDevice *master) +{ + struct nl_sock *sock; + const char *master_name; + int err, master_ifindex, slave_ifindex; + + master_name = nm_device_get_iface (master); + if (!master_name) + return FALSE; + + sock = nm_netlink_get_default_handle (); + + master_ifindex = nm_netlink_iface_to_index (master_name); + g_assert (master_ifindex > 0); + + if (!(nm_system_iface_get_flags (master_ifindex) & IFF_MASTER)) { + nm_log_err (LOGD_DEVICE, "(%s): interface is not a master", master_name); + return FALSE; + } + + slave_ifindex = nm_device_get_ifindex (slave); + g_assert (slave_ifindex > 0); + + g_assert (!nm_system_iface_is_up (slave_ifindex)); + + if (nm_system_iface_get_flags (slave_ifindex) & IFF_SLAVE) { + nm_log_err (LOGD_DEVICE, "(%s): %s is already a slave", + master_name, nm_device_get_iface (slave)); + return FALSE; + } + + err = rtnl_link_bond_enslave_ifindex (sock, master_ifindex, slave_ifindex); + if (err == -NLE_OPNOTSUPP) + return nm_system_iface_compat_enslave (slave, master_name); + + if (err < 0) { + nm_log_err (LOGD_DEVICE, "(%s): error enslaving %s: %d (%s)", + master_name, nm_device_get_iface (slave), + err, nl_geterror (err)); + return FALSE; + } + + return TRUE; +} + +static gboolean +nm_system_iface_compat_release (NMDevice *device, const char *master_name) +{ + struct ifreq ifr; + int fd; + gboolean ret = FALSE; + + memset (&ifr, 0, sizeof (ifr)); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_DEVICE, "couldn't open control socket."); + return FALSE; + } + + strncpy (ifr.ifr_name, master_name, IFNAMSIZ); + strncpy (ifr.ifr_slave, nm_device_get_iface (device), IFNAMSIZ); + + if (ioctl (fd, SIOCBONDRELEASE, &ifr) < 0 && + ioctl (fd, BOND_RELEASE_OLD, &ifr) < 0) { + nm_log_err (LOGD_DEVICE, "(%s): error releasing slave %s: %d (%s)", + master_name, nm_device_get_iface (device), + errno, strerror (errno)); + goto errout; + } + + ret = TRUE; + +errout: + close (fd); + + return ret; +} + +/** + * nm_system_iface_release: + * @slave: Slave device + * @maser: Master device + * + * Releases the 'slave' which is attached to 'master. This function targets + * implementing a generic interface to releasing all kinds of slaves. Currently + * only bonding is properly supported due to the backwards compatibility + * function being bonding specific. + * + * Returns: %TRUE on success, or %FALSE + */ +gboolean +nm_system_iface_release (NMDevice *slave, NMDevice *master) +{ + struct nl_sock *sock; + const char *master_name; + int err, slave_ifindex; + + master_name = nm_device_get_iface (master); + if (!master_name) + return TRUE; + + sock = nm_netlink_get_default_handle (); + + slave_ifindex = nm_device_get_ifindex (slave); + g_assert (slave_ifindex > 0); + + /* Only release if this is actually a slave */ + if (!(nm_system_iface_get_flags (slave_ifindex) & IFF_SLAVE)) + goto out; + + err = rtnl_link_bond_release_ifindex (sock, slave_ifindex); + if (err == -NLE_OPNOTSUPP) + return nm_system_iface_compat_release (slave, master_name); + + if (err < 0) { + nm_log_err (LOGD_DEVICE, "(%s): error releasing slave %s: %d (%s)", + master_name, nm_device_get_iface (slave), + err, nl_geterror (err)); + return FALSE; + } + +out: + return TRUE; +} + /** * nm_system_get_link_type: * @name: name of link diff --git a/src/nm-system.h b/src/nm-system.h index 1fe91f66f..339dfa743 100644 --- a/src/nm-system.h +++ b/src/nm-system.h @@ -84,6 +84,7 @@ gboolean nm_system_iface_set_up (int ifindex, gboolean up, gboolean *no_firmware); +guint32 nm_system_iface_get_flags (int ifindex); gboolean nm_system_iface_is_up (int ifindex); gboolean nm_system_iface_set_mtu (int ifindex, guint32 mtu); @@ -91,6 +92,8 @@ gboolean nm_system_iface_set_mtu (int ifindex, guint32 mtu); gboolean nm_system_iface_set_mac (int ifindex, const struct ether_addr *mac); gboolean nm_system_add_bonding_master (NMSettingBond *setting); +gboolean nm_system_iface_enslave (NMDevice *slave, NMDevice *master); +gboolean nm_system_iface_release (NMDevice *slave, NMDevice *master); char * nm_system_get_link_type (const char *name);