Commit 81bf475b authored by Pavel Šimerda's avatar Pavel Šimerda
Browse files

platform: master and slave devices

parent 649d2e4c
......@@ -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;
......
......@@ -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;
......
......@@ -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 *
......
......@@ -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);
......
......@@ -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))
......
......@@ -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 */