diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c index 231886c0bb..ac5fd3b956 100644 --- a/src/platform/nm-fake-platform.c +++ b/src/platform/nm-fake-platform.c @@ -171,7 +171,26 @@ link_get_type (NMPlatform *platform, int ifindex) static void link_changed (NMPlatform *platform, NMPlatformLink *device) { + NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform); + int i; + g_signal_emit_by_name (platform, "link-changed", device); + + if (device->master) { + NMPlatformLink *master = link_get (platform, device->master); + + g_return_if_fail (master != device); + + master->connected = FALSE; + for (i = 0; i < priv->links->len; i++) { + NMPlatformLink *slave = &g_array_index (priv->links, NMPlatformLink, i); + + if (slave && slave->master == master->ifindex && slave->connected) + master->connected = TRUE; + } + + link_changed (platform, master); + } } static gboolean @@ -188,6 +207,11 @@ link_set_up (NMPlatform *platform, int ifindex) case NM_LINK_TYPE_DUMMY: device->connected = TRUE; break; + case NM_LINK_TYPE_BRIDGE: + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + device->connected = FALSE; + break; default: device->connected = FALSE; g_error ("Unexpected device type: %d", device->type); @@ -300,6 +324,53 @@ link_supports_vlans (NMPlatform *platform, int ifindex) } } +static gboolean +link_enslave (NMPlatform *platform, int master, int slave) +{ + NMPlatformLink *device = link_get (platform, slave); + + g_return_val_if_fail (device, FALSE); + + device->master = master; + + link_changed (platform, device); + + return TRUE; +} + +static gboolean +link_release (NMPlatform *platform, int master_idx, int slave_idx) +{ + NMPlatformLink *master = link_get (platform, master_idx); + NMPlatformLink *slave = link_get (platform, slave_idx); + + g_return_val_if_fail (master, FALSE); + g_return_val_if_fail (slave, FALSE); + + if (slave->master != master->ifindex) { + platform->error = NM_PLATFORM_ERROR_NOT_SLAVE; + return FALSE; + } + + slave->master = 0; + + link_changed (platform, slave); + link_changed (platform, master); + + return TRUE; +} + +static int +link_get_master (NMPlatform *platform, int slave) +{ + NMPlatformLink *device = link_get (platform, slave); + + g_return_val_if_fail (device, FALSE); + + return device->master; +} + + /******************************************************************/ static GArray * @@ -733,6 +804,10 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass) platform_class->link_supports_carrier_detect = link_supports_carrier_detect; platform_class->link_supports_vlans = link_supports_vlans; + platform_class->link_enslave = link_enslave; + platform_class->link_release = link_release; + platform_class->link_get_master = link_get_master; + platform_class->ip4_address_get_all = ip4_address_get_all; platform_class->ip6_address_get_all = ip6_address_get_all; platform_class->ip4_address_add = ip4_address_add; diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 7b15e69465..00baa6fb73 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -266,6 +266,12 @@ type_to_string (NMLinkType type) switch (type) { case NM_LINK_TYPE_DUMMY: return "dummy"; + case NM_LINK_TYPE_BRIDGE: + return "bridge"; + case NM_LINK_TYPE_BOND: + return "bond"; + case NM_LINK_TYPE_TEAM: + return "team"; default: g_warning ("Wrong type: %d", type); return NULL; @@ -293,6 +299,12 @@ link_extract_type (struct rtnl_link *rtnllink) } else if (!g_strcmp0 (type, "dummy")) return NM_LINK_TYPE_DUMMY; + else if (!g_strcmp0 (type, "bridge")) + return NM_LINK_TYPE_BRIDGE; + else if (!g_strcmp0 (type, "bond")) + return NM_LINK_TYPE_BOND; + else if (!g_strcmp0 (type, "team")) + return NM_LINK_TYPE_TEAM; else return NM_LINK_TYPE_UNKNOWN; } @@ -310,6 +322,53 @@ link_init (NMPlatformLink *info, struct rtnl_link *rtnllink) info->up = !!(rtnl_link_get_flags (rtnllink) & IFF_UP); info->connected = !!(rtnl_link_get_flags (rtnllink) & IFF_LOWER_UP); info->arp = !(rtnl_link_get_flags (rtnllink) & IFF_NOARP); + info->master = rtnl_link_get_master (rtnllink); +} + +/* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break + * the carrier detection. This hack makes nm-platform think they don't have the + * IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all + * slaves down. + * + * Note: This is still a bit racy but when NetworkManager asks for enslaving a slave, + * nm-platform will do that synchronously and will immediately ask for both master + * and slave information after the enslaving request. After the synchronous call, the + * master carrier is already updated with the slave carrier in mind. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=910348 + */ +static void +hack_empty_master_iff_lower_up (NMPlatform *platform, struct nl_object *object) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + struct rtnl_link *rtnllink; + int ifindex; + struct nl_object *slave; + + if (!object) + return; + if (strcmp (nl_object_get_type (object), "route/link")) + return; + + rtnllink = (struct rtnl_link *) object; + + ifindex = rtnl_link_get_ifindex (rtnllink); + + switch (link_extract_type (rtnllink)) { + case NM_LINK_TYPE_BRIDGE: + case NM_LINK_TYPE_BOND: + for (slave = nl_cache_get_first (priv->link_cache); slave; slave = nl_cache_get_next (slave)) { + struct rtnl_link *rtnlslave = (struct rtnl_link *) slave; + if (rtnl_link_get_master (rtnlslave) == ifindex + && rtnl_link_get_flags (rtnlslave) & IFF_LOWER_UP) + return; + } + break; + default: + return; + } + + rtnl_link_unset_flags (rtnllink, IFF_LOWER_UP); } static void @@ -480,6 +539,8 @@ process_nl_error (NMPlatform *platform, int nle) } } +static struct nl_object * build_rtnl_link (int ifindex, const char *name, NMLinkType type); + static gboolean refresh_object (NMPlatform *platform, struct nl_object *object, int nle) { @@ -497,6 +558,8 @@ refresh_object (NMPlatform *platform, struct nl_object *object, int nle) g_return_val_if_fail (kernel_object, FALSE); + hack_empty_master_iff_lower_up (platform, kernel_object); + if (cached_object) { nl_cache_remove (cached_object); nle = nl_cache_add (cache, kernel_object); @@ -508,6 +571,24 @@ refresh_object (NMPlatform *platform, struct nl_object *object, int nle) announce_object (platform, kernel_object, cached_object ? CHANGED : ADDED); + /* Refresh the master device (even on enslave/release) */ + if (object_type_from_nl_object (kernel_object) == LINK) { + int kernel_master = rtnl_link_get_master ((struct rtnl_link *) kernel_object); + int cached_master = cached_object ? rtnl_link_get_master ((struct rtnl_link *) cached_object) : 0; + struct nl_object *master_object; + + if (kernel_master) { + master_object = build_rtnl_link (kernel_master, NULL, NM_LINK_TYPE_NONE); + refresh_object (platform, master_object, 0); + nl_object_put (master_object); + } + if (cached_master && cached_master != kernel_master) { + master_object = build_rtnl_link (cached_master, NULL, NM_LINK_TYPE_NONE); + refresh_object (platform, master_object, 0); + nl_object_put (master_object); + } + } + return TRUE; } @@ -578,6 +659,8 @@ event_notification (struct nl_msg *msg, gpointer user_data) debug ("netlink event (type %d)", event); + hack_empty_master_iff_lower_up (platform, kernel_object); + /* Removed object */ switch (event) { case RTM_DELLINK: @@ -884,6 +967,46 @@ link_supports_vlans (NMPlatform *platform, int ifindex) return !(edata.features.features[0].active & NETIF_F_VLAN_CHALLENGED); } +static gboolean +link_refresh (NMPlatform *platform, int ifindex, int nle) +{ + auto_nl_object struct nl_object *object = build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_NONE); + + return refresh_object (platform, object, nle); +} + +static gboolean +link_enslave (NMPlatform *platform, int master, int slave) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + return link_refresh (platform, slave, rtnl_link_enslave_ifindex (priv->nlh, master, slave)); +} + +static gboolean +link_release (NMPlatform *platform, int master, int slave) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + return link_refresh (platform, slave, rtnl_link_release_ifindex (priv->nlh, slave)); +} + +static int +link_get_master (NMPlatform *platform, int slave) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + auto_nl_object struct rtnl_link *rtnllink; + int result; + + rtnllink = rtnl_link_get (priv->link_cache, slave); + g_assert (rtnllink); + + result = rtnl_link_get_master (rtnllink); + g_assert (result >= 0); + + return result; +} + /******************************************************************/ static int @@ -1345,6 +1468,10 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->link_supports_carrier_detect = link_supports_carrier_detect; platform_class->link_supports_vlans = link_supports_vlans; + platform_class->link_enslave = link_enslave; + platform_class->link_release = link_release; + platform_class->link_get_master = link_get_master; + platform_class->ip4_address_get_all = ip4_address_get_all; platform_class->ip6_address_get_all = ip6_address_get_all; platform_class->ip4_address_add = ip4_address_add; diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 9d7174b53f..e5ac9264df 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -502,6 +502,118 @@ nm_platform_link_set_noarp (int ifindex) return klass->link_set_noarp (platform, ifindex); } +/** + * nm_platform_link_enslave: + * @master: Interface index of the master + * @slave: Interface index of the slave + * + * Enslave @slave to @master. + */ +gboolean +nm_platform_link_enslave (int master, int slave) +{ + reset_error (); + + g_assert (platform); + g_return_val_if_fail (master > 0, FALSE); + g_return_val_if_fail (slave> 0, FALSE); + g_return_val_if_fail (klass->link_enslave, FALSE); + + debug ("link: enslaving '%s' (%d) to master '%s' (%d)", + nm_platform_link_get_name (slave), slave, + nm_platform_link_get_name (master), master); + return klass->link_enslave (platform, master, slave); +} + +/** + * nm_platform_link_release: + * @master: Interface index of the master + * @slave: Interface index of the slave + * + * Release @slave from @master. + */ +gboolean +nm_platform_link_release (int master, int slave) +{ + reset_error (); + + g_assert (platform); + g_return_val_if_fail (master > 0, FALSE); + g_return_val_if_fail (slave > 0, FALSE); + g_return_val_if_fail (klass->link_release, FALSE); + + if (nm_platform_link_get_master (slave) != master) { + platform->error = NM_PLATFORM_ERROR_NOT_SLAVE; + return FALSE; + } + + debug ("link: releasing '%s' (%d) from master '%s' (%d)", + nm_platform_link_get_name (slave), slave, + nm_platform_link_get_name (master), master); + return klass->link_release (platform, master, slave); +} + +/** + * nm_platform_link_get_master: + * @slave: Interface index of the slave. + * + * Returns: Interfase index of the slave's master. + */ +int +nm_platform_link_get_master (int slave) +{ + reset_error (); + + g_assert (platform); + g_return_val_if_fail (slave >= 0, FALSE); + g_return_val_if_fail (klass->link_get_master, FALSE); + + if (!nm_platform_link_get_name (slave)) { + platform->error = NM_PLATFORM_ERROR_NOT_FOUND; + return 0; + } + return klass->link_get_master (platform, slave); +} + +/** + * nm_platform_bridge_add: + * @name: New interface name + * + * Create a virtual bridge. + */ +gboolean +nm_platform_bridge_add (const char *name) +{ + debug ("link: adding bridge '%s'", name); + return nm_platform_link_add (name, NM_LINK_TYPE_BRIDGE); +} + +/** + * nm_platform_bond_add: + * @name: New interface name + * + * Create a virtual bonding device. + */ +gboolean +nm_platform_bond_add (const char *name) +{ + debug ("link: adding bond '%s'", name); + return nm_platform_link_add (name, NM_LINK_TYPE_BOND); +} + +/** + * nm_platform_team_add: + * @name: New interface name + * + * Create a virtual teaming device. + */ +gboolean +nm_platform_team_add (const char *name) +{ + debug ("link: adding team '%s'", name); + return nm_platform_link_add (name, NM_LINK_TYPE_TEAM); +} + /******************************************************************/ GArray * diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index 32461e4a5f..ff7c1c224b 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -41,12 +41,16 @@ typedef enum { NM_LINK_TYPE_LOOPBACK, NM_LINK_TYPE_ETHERNET, NM_LINK_TYPE_DUMMY, + NM_LINK_TYPE_BRIDGE, + NM_LINK_TYPE_BOND, + NM_LINK_TYPE_TEAM, } NMLinkType; typedef struct { int ifindex; char name[IFNAMSIZ]; NMLinkType type; + int master; gboolean up; gboolean connected; gboolean arp; @@ -139,6 +143,10 @@ typedef struct { gboolean (*link_supports_carrier_detect) (NMPlatform *, int ifindex); gboolean (*link_supports_vlans) (NMPlatform *, int ifindex); + gboolean (*link_enslave) (NMPlatform *, int master, int slave); + gboolean (*link_release) (NMPlatform *, int master, int slave); + gboolean (*link_get_master) (NMPlatform *, int slave); + GArray * (*ip4_address_get_all) (NMPlatform *, int ifindex); GArray * (*ip6_address_get_all) (NMPlatform *, int ifindex); gboolean (*ip4_address_add) (NMPlatform *, int ifindex, in_addr_t address, int plen); @@ -199,6 +207,8 @@ enum { NM_PLATFORM_ERROR_NOT_FOUND, /* object already exists */ NM_PLATFORM_ERROR_EXISTS, + /* object is not a slave */ + NM_PLATFORM_ERROR_NOT_SLAVE }; /******************************************************************/ @@ -216,6 +226,9 @@ const char *nm_platform_get_error_msg (void); GArray *nm_platform_link_get_all (void); gboolean nm_platform_dummy_add (const char *name); +gboolean nm_platform_bridge_add (const char *name); +gboolean nm_platform_bond_add (const char *name); +gboolean nm_platform_team_add (const char *name); gboolean nm_platform_link_exists (const char *name); gboolean nm_platform_link_delete (int ifindex); gboolean nm_platform_link_delete_by_name (const char *ifindex); @@ -234,6 +247,10 @@ gboolean nm_platform_link_uses_arp (int ifindex); gboolean nm_platform_link_supports_carrier_detect (int ifindex); gboolean nm_platform_link_supports_vlans (int ifindex); +gboolean nm_platform_link_enslave (int master, int slave); +gboolean nm_platform_link_release (int master, int slave); +int nm_platform_link_get_master (int slave); + GArray *nm_platform_ip4_address_get_all (int ifindex); GArray *nm_platform_ip6_address_get_all (int ifindex); gboolean nm_platform_ip4_address_add (int ifindex, in_addr_t address, int plen); diff --git a/src/platform/tests/dump.c b/src/platform/tests/dump.c index 4f4aa127eb..80f2aa0139 100644 --- a/src/platform/tests/dump.c +++ b/src/platform/tests/dump.c @@ -16,6 +16,12 @@ type_to_string (NMLinkType type) return "ethernet"; case NM_LINK_TYPE_DUMMY: return "dummy"; + case NM_LINK_TYPE_BRIDGE: + return "bridge"; + case NM_LINK_TYPE_BOND: + return "bond"; + case NM_LINK_TYPE_TEAM: + return "team"; default: return "unknown-type"; } @@ -46,6 +52,8 @@ dump_interface (NMPlatformLink *link) printf (" DOWN"); if (!link->arp) printf (" noarp"); + if (link->master) + printf (" master %d", link->master); printf ("\n"); if (nm_platform_link_supports_carrier_detect (link->ifindex)) diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index be1e7cc6ed..9c46547189 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -6,6 +6,7 @@ #define DEVICE_NAME "nm-test-device" #define BOGUS_NAME "nm-bogus-device" #define BOGUS_IFINDEX INT_MAX +#define SLAVE_NAME "nm-test-slave" static void link_callback (NMPlatform *platform, NMPlatformLink *received, SignalData *data) @@ -97,6 +98,185 @@ test_loopback (void) g_assert (!nm_platform_link_supports_vlans (LO_INDEX)); } +static int +virtual_add (NMLinkType link_type, const char *name, SignalData *link_added, SignalData *link_changed) +{ + switch (link_type) { + case NM_LINK_TYPE_DUMMY: + return nm_platform_dummy_add (name); + case NM_LINK_TYPE_BRIDGE: + return nm_platform_bridge_add (name); + case NM_LINK_TYPE_BOND: + return nm_platform_bond_add (name); + case NM_LINK_TYPE_TEAM: + return nm_platform_team_add (name); + default: + g_error ("Link type %d unhandled.", link_type); + } +} + +static void +test_slave (int master, int type, SignalData *link_added, SignalData *master_changed, SignalData *link_removed) +{ + int ifindex; + SignalData *link_changed = add_signal ("link-changed", link_callback); + + g_assert (virtual_add (type, SLAVE_NAME, link_added, link_changed)); + ifindex = nm_platform_link_get_ifindex (SLAVE_NAME); + g_assert (ifindex > 0); + accept_signal (link_added); + + /* Set the slave up to see whether master's IFF_LOWER_UP is set correctly. + * + * See https://bugzilla.redhat.com/show_bug.cgi?id=910348 + */ + g_assert (nm_platform_link_set_down (ifindex)); + g_assert (!nm_platform_link_is_up (ifindex)); + accept_signal (link_changed); + + /* Enslave */ + link_changed->ifindex = ifindex; + g_assert (nm_platform_link_enslave (master, ifindex)); no_error (); + g_assert (nm_platform_link_get_master (ifindex) == master); no_error (); + accept_signal (link_changed); + accept_signal (master_changed); + + /* Set master up */ + g_assert (nm_platform_link_set_up (master)); + accept_signal (master_changed); + + /* Master with a disconnected slave is disconnected + * + * For some reason, bonding and teaming slaves are automatically set up. We + * need to set them back down for this test. + */ + switch (nm_platform_link_get_type (master)) { + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + g_assert (nm_platform_link_set_down (ifindex)); + accept_signal (link_changed); + accept_signal (master_changed); + break; + default: + break; + } + g_assert (!nm_platform_link_is_up (ifindex)); + g_assert (!nm_platform_link_is_connected (ifindex)); + g_assert (!nm_platform_link_is_connected (master)); + + /* Set slave up and see if master gets up too */ + g_assert (nm_platform_link_set_up (ifindex)); no_error (); + g_assert (nm_platform_link_is_connected (ifindex)); + g_assert (nm_platform_link_is_connected (master)); + accept_signal (link_changed); + accept_signal (master_changed); + + /* Enslave again + * + * Gracefully succeed if already enslaved. + */ + g_assert (nm_platform_link_enslave (master, ifindex)); no_error (); + accept_signal (link_changed); + accept_signal (master_changed); + + /* Release */ + g_assert (nm_platform_link_release (master, ifindex)); + g_assert (nm_platform_link_get_master (ifindex) == 0); no_error (); + accept_signal (link_changed); + accept_signal (master_changed); + + /* Release again */ + g_assert (!nm_platform_link_release (master, ifindex)); + error (NM_PLATFORM_ERROR_NOT_SLAVE); + + /* Remove */ + g_assert (nm_platform_link_delete (ifindex)); + no_error (); + accept_signal (link_removed); + + free_signal (link_changed); +} + +static void +test_virtual (NMLinkType link_type) +{ + int ifindex; + + SignalData *link_added = add_signal ("link-added", link_callback); + SignalData *link_changed = add_signal ("link-changed", link_callback); + SignalData *link_removed = add_signal ("link-removed", link_callback); + + /* Add */ + g_assert (virtual_add (link_type, DEVICE_NAME, link_added, link_changed)); + no_error (); + g_assert (nm_platform_link_exists (DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex (DEVICE_NAME); + g_assert (ifindex >= 0); + g_assert (nm_platform_link_get_type (ifindex) == link_type); + accept_signal (link_added); + + /* Add again */ + g_assert (!virtual_add (link_type, DEVICE_NAME, link_added, link_changed)); + error (NM_PLATFORM_ERROR_EXISTS); + + /* Set ARP/NOARP */ + g_assert (nm_platform_link_uses_arp (ifindex)); + g_assert (nm_platform_link_set_noarp (ifindex)); + g_assert (!nm_platform_link_uses_arp (ifindex)); + accept_signal (link_changed); + g_assert (nm_platform_link_set_arp (ifindex)); + g_assert (nm_platform_link_uses_arp (ifindex)); + accept_signal (link_changed); + + /* Enslave and release */ + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + link_changed->ifindex = ifindex; + test_slave (ifindex, NM_LINK_TYPE_DUMMY, link_added, link_changed, link_removed); + link_changed->ifindex = 0; + break; + default: + break; + } + + /* Delete */ + g_assert (nm_platform_link_delete_by_name (DEVICE_NAME)); + no_error (); + g_assert (!nm_platform_link_exists (DEVICE_NAME)); no_error (); + g_assert (nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_NONE); + error (NM_PLATFORM_ERROR_NOT_FOUND); + accept_signal (link_removed); + + /* Delete again */ + g_assert (!nm_platform_link_delete_by_name (DEVICE_NAME)); + error (NM_PLATFORM_ERROR_NOT_FOUND); + + /* No pending signal */ + free_signal (link_added); + free_signal (link_changed); + free_signal (link_removed); +} + +static void +test_bridge (void) +{ + test_virtual (NM_LINK_TYPE_BRIDGE); +} + +static void +test_bond (void) +{ + test_virtual (NM_LINK_TYPE_BOND); +} + +static void +test_team (void) +{ + test_virtual (NM_LINK_TYPE_TEAM); +} + static void test_internal (void) { @@ -249,11 +429,16 @@ main (int argc, char **argv) /* Clean up */ nm_platform_link_delete_by_name (DEVICE_NAME); + nm_platform_link_delete_by_name (SLAVE_NAME); g_assert (!nm_platform_link_exists (DEVICE_NAME)); + g_assert (!nm_platform_link_exists (SLAVE_NAME)); g_test_add_func ("/link/bogus", test_bogus); g_test_add_func ("/link/loopback", test_loopback); g_test_add_func ("/link/internal", test_internal); + g_test_add_func ("/link/virtual/bridge", test_bridge); + g_test_add_func ("/link/virtual/bond", test_bond); + g_test_add_func ("/link/virtual/team", test_team); if (strcmp (g_type_name (G_TYPE_FROM_INSTANCE (nm_platform_get ())), "NMFakePlatform")) g_test_add_func ("/link/external", test_external);