From e91c81af796fbc7e12ec9994344bfd79320da197 Mon Sep 17 00:00:00 2001
From: Marius Tomaschewski <mt@suse.com>
Date: Fri, 1 Mar 2024 19:04:29 +0100
Subject: [PATCH] interface dependencies fix preview 1 (bsc#1205604)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Remove port arrays from bond,team,bridge,ovs-bridge and consistently
  use config and state info attached to the port interface as in rtnetlink(7).
- Cleanup ifcfg parsing, schema configuration and service properties
- Migrate ports in xml config and policies already applied in nanny
- Remove "missed config" generation from finite state machine, which is
  is completed while parsing the config or while xml config migration.
- Issue a warning when "lower" interface (e.g. eth0) config is missed
  while parsing config depending on it (e.g. eth0.42 vlan).
- Resolve ovs master to the effective bridge in config and wickedd
- Implement netif-check-state require checks using system relations
  from wickedd/kernel instead of config relations for ifdown and add
  linkDown and deleteDevice checks to all master and lower references.
- Add a `wicked <ifup|ifdown|ifreload> --dry-run …` option to show the
  system/config interface hierarchies as notice with +/- marked interfaces
  to setup and/or shutdown.

Signed-off-by: Marius Tomaschewski <mt@suse.com>

diff --git a/client/compat.c b/client/compat.c
index 37f92e767..f1134e61a 100644
--- a/client/compat.c
+++ b/client/compat.c
@@ -125,6 +125,8 @@ ni_compat_netdev_new(const char *ifname)
 	compat = xcalloc(1, sizeof(*compat));
 	compat->dev = ni_netdev_new(ifname, 0);
 
+	ni_netdev_port_config_init(&compat->port, NI_IFTYPE_UNKNOWN);
+
 	/* Apply defaults */
 	compat->dhcp4.update = ni_config_addrconf_update(ifname, NI_ADDRCONF_DHCP, AF_INET);
 	compat->dhcp4.recover_lease = TRUE;
@@ -184,6 +186,9 @@ ni_compat_netdev_free(ni_compat_netdev_t *compat)
 	if (compat) {
 		if (compat->dev)
 			ni_netdev_put(compat->dev);
+
+		ni_netdev_port_config_destroy(&compat->port);
+
 		ni_ifworker_control_free(compat->control);
 		ni_var_array_destroy(&compat->scripts);
 		ni_string_free(&compat->firewall.zone);
@@ -200,16 +205,6 @@ ni_compat_netdev_free(ni_compat_netdev_t *compat)
 		ni_string_free(&compat->dhcp6.client_id);
 		ni_dhcp6_prefix_req_list_destroy(&compat->dhcp6.prefix_reqs);
 		ni_string_array_destroy(&compat->dhcp6.request_options);
-		switch(compat->port.type) {
-		case NI_IFTYPE_TEAM:
-			ni_team_port_config_destroy(&compat->port.conf.team);
-			break;
-		case NI_IFTYPE_OVS_BRIDGE:
-			ni_ovs_bridge_port_config_destroy(&compat->port.conf.ovsbr);
-			break;
-		default:
-			break;
-		}
 
 		free(compat);
 	}
@@ -492,11 +487,23 @@ __ni_compat_generate_infiniband(xml_node_t *ifnode, const ni_compat_netdev_t *co
 	return TRUE;
 }
 
+static ni_bool_t
+ni_compat_generate_bonding_port_config(xml_node_t *port, const ni_bonding_port_config_t *config)
+{
+	if (!port || !config)
+		return FALSE;
+
+	if (config->queue_id != -1U)
+		xml_node_new_element("queue-id", port, ni_sprint_uint(config->queue_id));
+
+	return TRUE;
+}
+
 static ni_bool_t
 __ni_compat_generate_bonding(xml_node_t *ifnode, const ni_compat_netdev_t *compat)
 {
 	ni_bonding_t *bond;
-	xml_node_t *child, *snodes, *snode;
+	xml_node_t *child;
 	unsigned int i;
 	int verbose = 0; /* do not suppress defaults */
 
@@ -543,29 +550,14 @@ __ni_compat_generate_bonding(xml_node_t *ifnode, const ni_compat_netdev_t *compa
 			ni_bonding_mii_carrier_detect_name(bond->miimon.carrier_detect));
 	}
 
-	snodes = bond->slaves.count ? xml_node_create(child, "slaves") : NULL;
-	for (i = 0; i < bond->slaves.count; ++i) {
-		ni_bonding_slave_t *slave = bond->slaves.data[i];
-
-		if (!slave || ni_string_empty(slave->device.name))
-			continue;
-
-		snode = xml_node_new("slave", snodes);
-		xml_node_new_element("device", snode, slave->device.name);
-
-		switch (bond->mode) {
-		case NI_BOND_MODE_ACTIVE_BACKUP:
-		case NI_BOND_MODE_BALANCE_TLB:
-		case NI_BOND_MODE_BALANCE_ALB:
-			if (ni_string_eq(bond->primary_slave.name, slave->device.name)) {
-				xml_node_new_element("primary", snode, "true");
-			}
-			if (ni_string_eq(bond->active_slave.name, slave->device.name)) {
-				xml_node_new_element("active", snode, "true");
-			}
-		default:
-			break;
-		}
+	switch (bond->mode) {
+	case NI_BOND_MODE_ACTIVE_BACKUP:
+	case NI_BOND_MODE_BALANCE_TLB:
+	case NI_BOND_MODE_BALANCE_ALB:
+		if (!ni_string_empty(bond->primary_slave.name))
+			xml_node_new_element("primary", child, bond->primary_slave.name);
+	default:
+		break;
 	}
 
 	switch (bond->mode) {
@@ -870,8 +862,10 @@ __ni_compat_generate_team_link_watch(xml_node_t *tnode, const ni_team_link_watch
 }
 
 static ni_bool_t
-__ni_compat_generate_team_port_config(xml_node_t *port, const ni_team_port_config_t *config)
+ni_compat_generate_team_port_config(xml_node_t *port, const ni_team_port_config_t *config)
 {
+	if (!port || !config)
+		return FALSE;
 
 	if (config->queue_id != -1U)
 	     xml_node_new_element("queue_id", port, ni_sprint_uint(config->queue_id));
@@ -891,34 +885,6 @@ __ni_compat_generate_team_port_config(xml_node_t *port, const ni_team_port_confi
 	return TRUE;
 }
 
-static ni_bool_t
-__ni_compat_generate_team_ports(xml_node_t *tnode, const ni_team_port_array_t *array)
-{
-	xml_node_t *ports;
-	unsigned int i;
-
-	if (!array || !tnode)
-		return FALSE;
-
-	if (!array->count)
-		return TRUE;
-
-	ports = xml_node_new("ports", tnode);
-	for (i = 0; i < array->count; i++) {
-		ni_team_port_t *p = array->data[i];
-		xml_node_t *port;
-
-		if (ni_string_empty(p->device.name))
-			continue;
-
-		port = xml_node_new("port", ports);
-		xml_node_new_element("device", port, p->device.name);
-		__ni_compat_generate_team_port_config(port, &p->config);
-	}
-
-	return TRUE;
-}
-
 static ni_bool_t
 ni_compat_generate_team_notify_peers(xml_node_t *parent, const ni_team_t *team)
 {
@@ -994,9 +960,6 @@ __ni_compat_generate_team(xml_node_t *ifnode, const ni_compat_netdev_t *compat)
 	if (!__ni_compat_generate_team_link_watch(tnode, &team->link_watch))
 		return FALSE;
 
-	if (!__ni_compat_generate_team_ports(tnode, &team->ports))
-		return FALSE;
-
 	return TRUE;
 }
 
@@ -1143,28 +1106,11 @@ __ni_compat_generate_ppp(xml_node_t *ifnode, const ni_compat_netdev_t *compat)
 }
 
 static ni_bool_t
-__ni_compat_generate_ovs_bridge_ports(xml_node_t *bnode, const ni_ovs_bridge_port_array_t *array)
+ni_compat_generate_ovs_bridge_port_config(xml_node_t *port, const ni_ovs_bridge_port_config_t *config)
 {
-	xml_node_t *ports;
-	unsigned int i;
-
-	if (!array || !bnode)
+	if (!port || !config)
 		return FALSE;
 
-	if (!array->count)
-		return TRUE;
-
-	ports = xml_node_new("ports", bnode);
-	for (i = 0; i < array->count; i++) {
-		ni_ovs_bridge_port_t *p = array->data[i];
-		xml_node_t *port;
-
-		if (ni_string_empty(p->device.name))
-			continue;
-
-		port = xml_node_new("port", ports);
-		xml_node_new_element("device", port, p->device.name);
-	}
 	return TRUE;
 }
 
@@ -1181,10 +1127,23 @@ __ni_compat_generate_ovs_bridge(xml_node_t *ifnode, const ni_compat_netdev_t *co
 		xml_node_t *vnode = xml_node_new("vlan", bnode);
 		xml_node_new_element("parent", vnode, ovsbr->config.vlan.parent.name);
 		xml_node_new_element_uint("tag", vnode, ovsbr->config.vlan.tag);
-	} /* else? */
-	if (!__ni_compat_generate_ovs_bridge_ports(bnode, &ovsbr->ports))
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_compat_generate_bridge_port_config(xml_node_t *port, const ni_bridge_port_config_t *config)
+{
+	if (!port || !config)
 		return FALSE;
 
+	if (config->priority != NI_BRIDGE_VALUE_NOT_SET)
+		xml_node_new_element_uint("priority", port, config->priority);
+
+	if (config->path_cost != NI_BRIDGE_VALUE_NOT_SET)
+		xml_node_new_element_uint("path-cost", port, config->path_cost);
+
 	return TRUE;
 }
 
@@ -1194,8 +1153,6 @@ __ni_compat_generate_bridge(xml_node_t *ifnode, const ni_compat_netdev_t *compat
 	const ni_netdev_t *dev = compat->dev;
 	ni_bridge_t *bridge;
 	xml_node_t *child;
-	xml_node_t *ports;
-	unsigned int i;
 	char *tmp = NULL;
 
 	bridge = ni_netdev_get_bridge(compat->dev);
@@ -1230,24 +1187,6 @@ __ni_compat_generate_bridge(xml_node_t *ifnode, const ni_compat_netdev_t *compat
 		ni_string_free(&tmp);
 	}
 
-	ports = xml_node_new("ports", child);
-	for (i = 0; i < bridge->ports.count; ++i) {
-		const ni_bridge_port_t *port = bridge->ports.data[i];
-		xml_node_t *portnode = xml_node_new("port", ports);
-
-		xml_node_new_element("device", portnode, port->ifname);
-		if (port->priority != NI_BRIDGE_VALUE_NOT_SET &&
-		    ni_string_printf(&tmp, "%u", port->priority)) {
-			xml_node_new_element("priority", portnode, tmp);
-			ni_string_free(&tmp);
-		}
-		if (port->path_cost != NI_BRIDGE_VALUE_NOT_SET &&
-		    ni_string_printf(&tmp, "%u", port->path_cost)) {
-			xml_node_new_element("path-cost", portnode, tmp);
-			ni_string_free(&tmp);
-		}
-	}
-
 	if (dev->link.hwaddr.len) {
 		xml_node_new_element("address", child,
 			ni_link_address_print(&dev->link.hwaddr));
@@ -3003,11 +2942,17 @@ ni_compat_generate_ifnode_content(xml_node_t *ifnode, const ni_compat_netdev_t *
 			xml_node_add_attr(port, "type", ni_linktype_type_to_name(compat->port.type));
 
 			switch(compat->port.type) {
-			case NI_IFTYPE_OVS_BRIDGE:
-				xml_node_new_element("bridge", port, compat->port.conf.ovsbr.bridge.name);
+			case NI_IFTYPE_BOND:
+				ni_compat_generate_bonding_port_config(port, compat->port.bond);
 				break;
 			case NI_IFTYPE_TEAM:
-				__ni_compat_generate_team_port_config(port, &compat->port.conf.team);
+				ni_compat_generate_team_port_config(port, compat->port.team);
+				break;
+			case NI_IFTYPE_BRIDGE:
+				ni_compat_generate_bridge_port_config(port, compat->port.bridge);
+				break;
+			case NI_IFTYPE_OVS_BRIDGE:
+				ni_compat_generate_ovs_bridge_port_config(port, compat->port.ovsbr);
 				break;
 			default:
 				break;
@@ -3075,25 +3020,93 @@ ni_compat_generate_interface(xml_document_t *doc, const ni_compat_netdev_t *comp
 	return NULL;
 }
 
+static ni_bool_t
+ni_compat_generate_policy_match_device(xml_node_t *match,
+		const ni_compat_netdev_t *compat)
+{
+	const char *name = compat->dev->name;
+
+	/*
+	 * Generate <device>${name}</device> match (self reference) node.
+	 */
+	if (!match || ni_string_empty(name))
+		return FALSE;
+
+	if (xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, match, name))
+		return TRUE;
+
+	return FALSE;
+}
+
+static ni_bool_t
+ni_compat_generate_policy_match_device_ref(xml_node_t *match,
+		const char *name)
+{
+	xml_node_t *ref;
+
+	/*
+	 * Generate <reference><device>${name}</device></reference>
+	 * match node to a related (lower, master, …) interface.
+	 */
+	if (!match || ni_string_empty(name))
+		return FALSE;
+
+	if (!(ref = xml_node_new(NI_NANNY_IFPOLICY_MATCH_REF, match)))
+		return FALSE;
+
+	if (xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, ref, name))
+		return TRUE;
+
+	xml_node_detach(ref);
+	xml_node_free(ref);
+	return FALSE;
+}
+
+static ni_bool_t
+ni_compat_generate_policy_match_lower_ref(xml_node_t *match,
+		const ni_compat_netdev_t *compat)
+{
+	const char *name = compat->dev->link.lowerdev.name;
+
+	/* only some interfaces use lower */
+	if (ni_string_empty(name))
+		return TRUE;
+
+	return ni_compat_generate_policy_match_device_ref(match, name);
+}
+
+static ni_bool_t
+ni_compat_generate_policy_match_master_ref(xml_node_t *match,
+		const ni_compat_netdev_t *compat)
+{
+	const char *name = compat->dev->link.masterdev.name;
+
+	/* not being a port is not an error */
+	if (ni_string_empty(name))
+		return TRUE;
+
+	return ni_compat_generate_policy_match_device_ref(match, name);
+}
+
 static ni_bool_t
 ni_compat_generate_policy_match(xml_node_t *match, const ni_compat_netdev_t *compat)
 {
 	ni_netdev_t *dev = compat->dev;
 
-	ni_trace(" - generate policy match for %s: type=%s, master=%s, lower=%s",
+	ni_debug_application("  - generate policy match for %s: type=%s, master=%s, lower=%s",
 			dev->name, ni_linktype_type_to_name(dev->link.type),
 			dev->link.masterdev.name, dev->link.lowerdev.name);
 
-	/* properties we want to match */
+	if (!ni_compat_generate_policy_match_device(match, compat))
+		return FALSE;
 
-#if 0	/* TODO: */
-	if (compat->identify.hwaddr.len &&
-	    compat->identify.hwaddr.type == ARPHRD_ETHER) {
-		/* TODO */
-	} /* else */
-#endif
-	if (!ni_string_empty(compat->dev->name))
-		xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, match, compat->dev->name);
+	/* link's reference to lower device */
+	if (!ni_compat_generate_policy_match_lower_ref(match, compat))
+		return FALSE;
+
+	/* port's reference to a master device */
+	if (!ni_compat_generate_policy_match_master_ref(match, compat))
+		return FALSE;
 
 	return TRUE;
 }
@@ -3103,7 +3116,7 @@ ni_compat_generate_policy_action(xml_node_t *action, const ni_compat_netdev_t *c
 {
 	ni_netdev_t *dev = compat->dev;
 
-	ni_trace(" - generate policy action %s for %s: type=%s, master=%s, lower=%s",
+	ni_debug_application("  - generate policy action %s for %s: type=%s, master=%s, lower=%s",
 			action->name,
 			dev->name, ni_linktype_type_to_name(dev->link.type),
 			dev->link.masterdev.name, dev->link.lowerdev.name);
@@ -3135,7 +3148,8 @@ ni_compat_generate_policy(xml_document_t *doc, const ni_compat_netdev_t *compat,
 	else
 		pname = ni_ifpolicy_name_from_ifname(compat->dev->name);
 
-	ni_trace("* generate policy[%u] for %s: name=%s", nr, compat->dev->name, pname);
+	ni_debug_application("* generate policy[%u] for %s: name=%s",
+			nr, compat->dev->name, pname);
 	xml_node_add_attr(ifpolicy, NI_NANNY_IFPOLICY_NAME, pname);
 	ni_string_free(&pname);
 
@@ -3193,23 +3207,25 @@ ni_compat_generate_interfaces(xml_document_array_t *array, ni_compat_ifconfig_t
 		if (ni_string_empty(conf->origin))
 			ni_string_dup(&conf->origin, ifcfg->schema);
 
-		xml_node_location_relocate(root, conf->origin);
-		if (!xml_node_location_filename(root))
+		if (ni_string_empty(conf->origin))
 			goto cleanup;
 
 		if (!ni_compat_generate_interface(doc, compat))
 			goto cleanup;
 
+		xml_node_location_relocate(root, conf->origin);
 		if (!raw)
 			ni_ifconfig_metadata_add_to_node(root, conf);
 
 		if (ni_ifconfig_validate_adding_doc(doc, check_prio)) {
 			ni_debug_ifconfig("%s: %s", __func__, xml_node_location(root));
-			xml_document_array_append(array, doc);
-		} else {
-	cleanup:
-			xml_document_free(doc);
+
+			if (xml_document_expand(array, doc))
+				continue;
 		}
+
+	cleanup:
+		xml_document_free(doc);
 	}
 
 	return count - array->count;
@@ -3243,23 +3259,25 @@ ni_compat_generate_policies(xml_document_array_t *array, ni_compat_ifconfig_t *i
 		if (ni_string_empty(conf->origin))
 			ni_string_dup(&conf->origin, ifcfg->schema);
 
-		xml_node_location_relocate(root, conf->origin);
-		if (!xml_node_location_filename(root))
+		if (ni_string_empty(conf->origin))
 			goto cleanup;
 
 		if (!ni_compat_generate_policy(doc, compat, array->count + 1))
 			goto cleanup;
 
+		xml_node_location_relocate(root, conf->origin);
 		if (!raw)
 			ni_ifconfig_metadata_add_to_node(root, conf);
 
 		if (ni_ifconfig_validate_adding_doc(doc, check_prio)) {
 			ni_debug_ifconfig("%s: %s", __func__, xml_node_location(root));
-			xml_document_array_append(array, doc);
-		} else {
-	cleanup:
-			xml_document_free(doc);
+
+			if (xml_document_expand(array, doc))
+				continue;
 		}
+
+	cleanup:
+		xml_document_free(doc);
 	}
 
 	return count - array->count;
diff --git a/client/convert.c b/client/convert.c
index eb16c926f..026f54faf 100644
--- a/client/convert.c
+++ b/client/convert.c
@@ -161,7 +161,11 @@ ni_wicked_convert_dump(xml_document_array_t *docs, ni_string_array_t *filter, FI
 		if (xml_node_is_empty(root))
 			continue;
 
-		for (node = root->children; node; node = node->next) {
+		if (ni_string_empty(root->name))
+			node = root->children;
+		else
+			node = root;
+		for (; node; node = node->next) {
 			if (!ni_wicked_convert_match(node, filter))
 				continue;
 
@@ -204,7 +208,11 @@ ni_wicked_convert_to_dir(xml_document_array_t *docs, ni_string_array_t *filter,
 		if (xml_node_is_empty(root))
 			continue;
 
-		for (node = root->children; node; node = node->next) {
+		if (ni_string_empty(root->name))
+			node = root->children;
+		else
+			node = root;
+		for (; node; node = node->next) {
 			if (!ni_wicked_convert_match(node, filter))
 				continue;
 
@@ -429,6 +437,9 @@ ni_wicked_convert(const char *caller, int argc, char **argv)
 		}
 	}
 
+	if (ni_ifxml_migrate_docs(&docs))
+		ni_debug_readwrite("Migrated configuration to current schema");
+
 	if (opt_output == NULL || ni_string_eq(opt_output, "-")) {
 		ni_wicked_convert_dump(&docs, &filter, stdout);
 	} else
diff --git a/client/dracut/cmdline.c b/client/dracut/cmdline.c
index faa34fa85..5557d1378 100644
--- a/client/dracut/cmdline.c
+++ b/client/dracut/cmdline.c
@@ -1133,8 +1133,6 @@ ni_ifconfig_read_dracut_cmdline(xml_document_array_t *array,
 	xml_document_t *doc;
 	xml_node_t *options;
 
-	doc = xml_document_new();
-
 	/*
 	 * expose config parser specific "meta options"
 	 * to the caller in order to:
@@ -1144,9 +1142,19 @@ ni_ifconfig_read_dracut_cmdline(xml_document_array_t *array,
 	 *   do not result in any ifcomfig/ifpolicy
 	 *   but may be needed in e.g. in bootstrap
 	 */
-	options = xml_node_new("options", xml_document_root(doc));
+	if (!(options = xml_node_new("options", NULL)))
+		return FALSE;
+
 	xml_node_add_attr(options, "origin", "dracut:cmdline:");
-	xml_document_array_append(array, doc);
+	if (!(doc = xml_document_create(NULL, options))) {
+		xml_node_free(options);
+		return FALSE;
+	}
+	xml_node_location_relocate(doc->root, "<dracut:cmdline>");
+	if (!xml_document_array_append(array, doc)) {
+		xml_document_free(doc);
+		return FALSE;
+	}
 
 	if (ni_dracut_cmdline_file_parse(&params, path)) {
 		/*
diff --git a/client/ifcheck.c b/client/ifcheck.c
index e06c4ba13..627b6cedf 100644
--- a/client/ifcheck.c
+++ b/client/ifcheck.c
@@ -90,8 +90,6 @@ ni_ifcheck_device_link_required(ni_netdev_t *dev)
 
 	if (cs && ni_tristate_is_set(cs->control.require_link))
 		link_required = cs->control.require_link;
-	else if (dev)
-		link_required = ni_netdev_guess_link_required(dev);
 
 	return !ni_tristate_is_disabled(link_required);
 }
@@ -121,8 +119,6 @@ ni_ifcheck_worker_device_link_required(ni_ifworker_t *w)
 
 	if (w && ni_tristate_is_set(w->control.link_required))
 		link_required = w->control.link_required;
-	else if (w && w->device)
-		link_required = ni_netdev_guess_link_required(w->device);
 
 	return !ni_tristate_is_disabled(link_required);
 }
diff --git a/client/ifdown.c b/client/ifdown.c
index b80a3a0d2..5e2d3f03b 100644
--- a/client/ifdown.c
+++ b/client/ifdown.c
@@ -107,7 +107,8 @@ int
 ni_do_ifdown(int argc, char **argv)
 {
 	enum  { OPT_HELP, OPT_FORCE, OPT_DELETE, OPT_NO_DELETE, OPT_TIMEOUT,
-		OPT_RELEASE, OPT_NO_RELEASE	};
+		OPT_RELEASE, OPT_NO_RELEASE, OPT_DRY_RUN,
+	};
 	static struct option ifdown_options[] = {
 		{ "help",	no_argument, NULL,		OPT_HELP },
 		{ "force",	required_argument, NULL,	OPT_FORCE },
@@ -116,6 +117,8 @@ ni_do_ifdown(int argc, char **argv)
 		{ "release",	no_argument, NULL,		OPT_RELEASE },
 		{ "no-release",	no_argument, NULL,		OPT_NO_RELEASE },
 		{ "timeout",	required_argument, NULL,	OPT_TIMEOUT },
+		{ "dry-run",	no_argument, NULL,		OPT_DRY_RUN },
+
 		{ NULL }
 	};
 	ni_ifmatcher_t ifmatch;
@@ -126,6 +129,7 @@ ni_do_ifdown(int argc, char **argv)
 	unsigned int seconds = NI_IFWORKER_DEFAULT_TIMEOUT;
 	ni_stringbuf_t sb = NI_STRINGBUF_INIT_DYNAMIC;
 	ni_tristate_t opt_release = NI_TRISTATE_DEFAULT;
+	ni_log_fn_t *dry_run = NULL;
 	ni_fsm_t *fsm;
 	int c, status = NI_WICKED_RC_USAGE;
 
@@ -188,6 +192,10 @@ ni_do_ifdown(int argc, char **argv)
 			}
 			break;
 
+		case OPT_DRY_RUN:
+			dry_run = ni_note;
+			break;
+
 		default:
 		case OPT_HELP:
 usage:
@@ -207,8 +215,10 @@ usage:
 				"  --[no-]release\n"
 				"      Override active config to (not) release leases\n"
 				"  --timeout <sec>\n"
-				"      Timeout after <sec> seconds\n",
-				sb.string
+				"      Timeout after <sec> seconds\n"
+				"  --dry-run\n"
+				"      Show system interface hierarchy as notice with (-) markers and exit.\n"
+				, sb.string
 				);
 			ni_stringbuf_destroy(&sb);
 			return status;
@@ -242,11 +252,19 @@ usage:
 		return NI_WICKED_RC_ERROR;
 	}
 
+	if (ni_fsm_build_hierarchy(fsm, FALSE) < 0) {
+		ni_error("ifdown: unable to build interface hierarchy");
+		status = NI_WICKED_RC_ERROR;
+		goto cleanup;
+	}
+
 	/* Get workers that match given criteria */
+	status = NI_WICKED_RC_SUCCESS;
 	nmarked = 0;
 	while (optind < argc) {
 		ifmatch.name = argv[optind++];
 		ifmatch.ifdown = TRUE;
+
 		ni_fsm_get_matching_workers(fsm, &ifmatch, &ifmarked);
 
 		if (ni_string_eq(ifmatch.name, "all") ||
@@ -259,6 +277,10 @@ usage:
 			ni_string_array_append(&ifnames, ifmatch.name);
 	}
 
+	ni_fsm_print_system_hierarchy(fsm, &ifmarked, dry_run);
+	if (dry_run)
+		goto cleanup;
+
 	/* Mark and start selected workers */
 	if (ifmarked.count) {
 		/* Disable devices and delete all related policies from nanny */
@@ -289,6 +311,7 @@ usage:
 		status = ni_ifstatus_shutdown_result(fsm, &ifnames, &ifmarked);
 	}
 
+cleanup:
 	ni_string_array_destroy(&ifnames);
 	ni_ifworker_array_destroy(&ifmarked);
 	ni_fsm_free(fsm);
diff --git a/client/ifreload.c b/client/ifreload.c
index 8786cb3b8..77059906c 100644
--- a/client/ifreload.c
+++ b/client/ifreload.c
@@ -47,288 +47,114 @@
 #include "ifreload.h"
 #include "ifstatus.h"
 
-static ni_bool_t
-ifreload_mark_add(ni_ifworker_array_t *marked, ni_ifworker_t *w)
+static void
+ifreload_match_down(ni_fsm_t *fsm, ni_ifworker_array_t *ifmarked, const char *ifname)
 {
-	if (ni_ifworker_array_index(marked, w) != -1)
-		return FALSE;
-
-	ni_ifworker_array_append(marked, w);
-	return TRUE;
+	ni_ifmatcher_t ifmatch;
+
+	memset(&ifmatch, 0, sizeof(ifmatch));
+	ifmatch.allow_persistent = FALSE;
+	ifmatch.require_configured = TRUE;
+	ifmatch.require_config = FALSE;
+	ifmatch.ifreload = TRUE;
+	ifmatch.ifdown = TRUE;
+	ifmatch.name = ifname;
+
+	/* to shutdown if config changed + dependencies */
+	ni_fsm_get_matching_workers(fsm, &ifmatch, ifmarked);
 }
 
-static inline void
-ifreload_mark_down_lower_deps(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *lower,
-				void (*logit)(const char *, ...) __fmtattr)
-{
-	ni_ifworker_t *w;
-	unsigned int i;
-
-	/* we need to shutdown also all depending worker devices c
-	 * that can't exist without w device (c->lowerdev == w) */
-	for (i = 0; i < fsm->workers.count; ++i) {
-		w = fsm->workers.data[i];
-
-		if (!w || w->lowerdev != lower)
-			continue;
-
-		if (ni_ifcheck_device_is_persistent(w->device)) {
-			logit("skipping %s shutdown: persistent mode is on", w->name);
-			continue;
-		}
-
-		w->target_range.min = NI_FSM_STATE_NONE;
-		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
-
-		if (!ifreload_mark_add(marked, w))
-			continue;
-
-		ni_debug_ifconfig("marked %s for shutdown (config: %s, device: %s, target state %s) as dependency",
-				w->name,
-				ni_ifcheck_worker_config_exists(w) ? "exists" :
-				ni_ifcheck_device_configured(w->device) ? "deleted" : "-",
-				ni_ifcheck_worker_device_exists(w) ? "exists" : "-",
-				ni_ifworker_state_name(w->target_range.max));
-	}
-}
-
-static ni_bool_t
-ifreload_mark_down(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *w,
-				void (*logit)(const char *, ...) __fmtattr,
-				unsigned int depth)
+static void
+ifreload_match_up(ni_fsm_t *fsm, ni_ifworker_array_t *ifmarked, const char *ifname)
 {
-	/* ifdown is disabled when persistent mode is on (todo: add --force?) */
-	if (ni_ifcheck_device_is_persistent(w->device)) {
-		logit("skipping %s shutdown: persistent mode is on", w->name);
-		return FALSE;
-	}
-
-	/* initialize as if the config would have been modified */
-	if (ni_ifcheck_worker_config_exists(w)) {
-		w->target_range.min = NI_FSM_STATE_NONE;
-		w->target_range.max = NI_FSM_STATE_DEVICE_READY;
-
-		/* the config has been modified, but some changes require deletion */
-		if (w->iftype == NI_IFTYPE_TEAM || w->iftype == NI_IFTYPE_VLAN) {
-			/* examples:
-			 *   - the team runner (mode) changes require teamd restart
-			 *   - VlanID changed, the interface need to be recreated */
-			w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
-		} else
-		if (w->iftype != NI_IFTYPE_UNKNOWN) {
-			/* if type changed, e.g. device is bond with bridge config */
-			if (w->device && w->device->link.type != w->iftype)
-				w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
-		}
-	} else
-	if (ni_ifcheck_device_configured(w->device)) {
-		/* config has been removed, so just delete it if applicable (=virtual) */
-		w->target_range.min = NI_FSM_STATE_NONE;
-		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
-		ni_client_state_config_reset(&w->config.meta);
-	} else
-	if (!ni_ifcheck_worker_device_exists(w)) {
-		/* when the device does not exists, delete policy in nanny (if any) */
-		w->target_range.min = NI_FSM_STATE_NONE;
-		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
-	} else {
-		/* do not shut down devices we don't handle */
-		logit("skipping %s shutdown: device was not configured by wicked", w->name);
-		return FALSE;
-	}
-
-	if (depth && ni_ifcheck_worker_device_exists(w)) {
-		ni_ifworker_t *c;
-		unsigned int i;
-
-		for (i = 0; i < fsm->workers.count; ++i) {
-			c = fsm->workers.data[i];
-
-			if (!ni_ifcheck_worker_device_exists(c))
-				continue;
-
-			if (!ni_string_eq(c->device->link.masterdev.name, w->name))
-				continue;
-
-			if (!ni_ifcheck_worker_config_matches(c)) {
-				ni_trace("exec ifreload_mark_workers down: %s", c->name);
-				ifreload_mark_down(fsm, marked, c, logit, depth - 1);
-			}
-		}
-	}
-
-	/* shut down depending devices because this one is deleted */
-	if (w->target_range.max == NI_FSM_STATE_DEVICE_DOWN)
-		ifreload_mark_down_lower_deps(fsm, marked, w, logit);
-
-	if (!ifreload_mark_add(marked, w))
-		return FALSE;
-
-	ni_debug_ifconfig("marked %s for shutdown (config: %s, device: %s, target state %s)",
-			w->name,
-			ni_ifcheck_worker_config_exists(w) ? "exists" :
-			ni_ifcheck_device_configured(w->device) ? "deleted" : "-",
-			ni_ifcheck_worker_device_exists(w) ? "exists" : "-",
-			ni_ifworker_state_name(w->target_range.max));
-
-	return TRUE;
+	ni_ifmatcher_t ifmatch;
+
+	memset(&ifmatch, 0, sizeof(ifmatch));
+	ifmatch.allow_persistent = TRUE;
+	ifmatch.require_configured = FALSE;
+	ifmatch.require_config = TRUE;
+	ifmatch.ifreload = TRUE;
+	ifmatch.ifdown = FALSE;
+	ifmatch.name = ifname;
+
+	/* to set-up if config changed + dependencies */
+	ni_fsm_get_matching_workers(fsm, &ifmatch, ifmarked);
 }
 
 static void
-ifreload_mark_up_slave_deps(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *master,
-				void (*logit)(const char *, ...) __fmtattr)
+ifreload_match_workers(ni_fsm_t *fsm, ni_ifworker_array_t *down_marked,
+		ni_ifworker_array_t *up_marked, const char *ifname)
 {
-	ni_ifworker_t *w;
-	unsigned int i;
-
-	for (i = 0; i < fsm->workers.count; ++i) {
-		w = fsm->workers.data[i];
-
-		if (!w || w->masterdev != master)
-			continue;
-
-		if (!ni_ifcheck_worker_config_exists(w)) {
-			logit("skipping %s set-up: no configuration available", w->name);
-			continue;
-		}
-
-		if (!ifreload_mark_add(marked, w))
-			continue;
-
-		ni_debug_ifconfig("marked %s for set-up (config: %s, device: %s, target state %s) as dependency",
-				w->name,
-				(ni_ifcheck_worker_config_exists(w) ?
-				 (ni_ifcheck_worker_config_matches(w) ? "unchanged" : "modified") :
-				 (ni_ifcheck_device_configured(w->device) ? "deleted" : "-")),
-				(ni_ifcheck_worker_device_exists(w) ? "exists" : "-"),
-				ni_ifworker_state_name(w->target_range.max));
-	}
+	ifreload_match_down(fsm, down_marked, ifname);
+	ifreload_match_up(fsm, up_marked, ifname);
 }
 
 static void
-ifreload_mark_up_master(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *w,
-				void (*logit)(const char *, ...) __fmtattr)
+ifreload_mark_worker_down(ni_fsm_t *fsm, ni_ifworker_array_t *down_marked, ni_ifworker_t *w)
 {
+	/* set default: try to go down without to delete it */
+	w->target_range.min = NI_FSM_STATE_NONE;
+	w->target_range.max = NI_FSM_STATE_DEVICE_READY;
+
+	/* when there is no config to apply, try to delete */
 	if (!ni_ifcheck_worker_config_exists(w)) {
-		logit("skipping %s set-up: no configuration available", w->name);
+		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
 		return;
 	}
 
-	if (!ifreload_mark_add(marked, w))
+	/*
+	 * config and device type differs (e.g. bridge vs bond)
+	 */
+	if (w->iftype != NI_IFTYPE_UNKNOWN && w->device &&
+			w->device->link.type != w->iftype) {
+		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
 		return;
-
-	ni_debug_ifconfig("marked %s for set-up (config %s, device %s, target state %s) as dependency",
-			w->name,
-			(ni_ifcheck_worker_config_exists(w) ?
-			 (ni_ifcheck_worker_config_matches(w) ? "unchanged" : "modified") :
-			 (ni_ifcheck_device_configured(w->device) ? "deleted" : "-")),
-			(ni_ifcheck_worker_device_exists(w) ? "exists" : "-"),
-			ni_ifworker_state_name(w->target_range.max));
-}
-
-static void
-ifreload_mark_up_lower_deps(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *lower,
-				void (*logit)(const char *, ...) __fmtattr)
-{
-	ni_ifworker_t *w;
-	unsigned int i;
-
-	for (i = 0; i < fsm->workers.count; ++i) {
-		w = fsm->workers.data[i];
-
-		if (!w || w->lowerdev != lower)
-			continue;
-
-		if (!ni_ifcheck_worker_config_exists(w)) {
-			logit("skipping %s set-up: no configuration available", w->name);
-			continue;
-		}
-
-		/* e.g. a vlan [w] can't exist without it's lowerdev [l]
-		 * and is deleted as dependency to deletion of lower [l].
-		 * for now, we need to trigger set-up of it's master [m]
-		 * (team0 [l] <-lower- team0.42 [w] -master-> [m] br42)
-		 * to ensure it gets (re-)enslaved into it.
-		 */
-		if (w->masterdev)
-			ifreload_mark_up_master(fsm, marked, w->masterdev, logit);
-
-		if (!ifreload_mark_add(marked, w))
-			continue;
-
-		ni_debug_ifconfig("marked %s for set-up (config %s, device %s, target state %s) as dependency",
-				w->name,
-				(ni_ifcheck_worker_config_exists(w) ?
-				 (ni_ifcheck_worker_config_matches(w) ? "unchanged" : "modified") :
-				 (ni_ifcheck_device_configured(w->device) ? "deleted" : "-")),
-				(ni_ifcheck_worker_device_exists(w) ? "exists" : "-"),
-				ni_ifworker_state_name(w->target_range.max));
 	}
-}
 
-static ni_bool_t
-ifreload_mark_up(const ni_fsm_t *fsm, ni_ifworker_array_t *marked, ni_ifworker_t *w,
-				void (*logit)(const char *, ...) __fmtattr)
-{
-	if (!ni_ifcheck_worker_config_exists(w)) {
-		logit("skipping %s set-up: no configuration available", w->name);
-		return FALSE;
+	/*
+	 * link config may have changed what may require deletion
+	 */
+	switch (w->iftype) {
+	case NI_IFTYPE_PPP:
+	case NI_IFTYPE_TEAM:
+	case NI_IFTYPE_VLAN:
+		/*
+		 * - ppp,team config file changes require service restart
+		 * - vlan id changed
+		 */
+		w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
+		return;
+	default:
+		break;
 	}
-
-	/* trigger set-up for slaves to (re-)enslave them */
-	ifreload_mark_up_slave_deps(fsm, marked, w, logit);
-
-	/* trigger set-up for devices we're base / lower
-	 * for as they can't exists without their base */
-	ifreload_mark_up_lower_deps(fsm, marked, w, logit);
-
-	if (!ifreload_mark_add(marked, w))
-		return FALSE;
-
-	ni_debug_ifconfig("marked %s for set-up (config %s, device %s, target state %s)",
-			w->name,
-			(ni_ifcheck_worker_config_exists(w) ?
-			 (ni_ifcheck_worker_config_matches(w) ? "unchanged" : "modified") :
-			 (ni_ifcheck_device_configured(w->device) ? "deleted" : "-")),
-			(ni_ifcheck_worker_device_exists(w) ? "exists" : "-"),
-			ni_ifworker_state_name(w->target_range.max));
-
-	return TRUE;
 }
 
 static void
-ifreload_mark_workers(const ni_fsm_t *fsm, ni_ifworker_array_t *down_marked, ni_ifworker_array_t *up_marked, const char *ifname)
+ifreload_mark_workers_down(ni_fsm_t *fsm, ni_ifworker_array_t *ifmarked)
 {
-	void (*logit)(const char *, ...) __fmtattr = ifname ? ni_note : ni_info;
 	ni_ifworker_t *w;
 	unsigned int i;
 
-	/* shutdown if config changed + dependencies */
-	for (i = 0; i < fsm->workers.count; ++i) {
-		w = fsm->workers.data[i];
-
+	/* init the shutdown / deletion targets */
+	for (i = 0; i < ifmarked->count; ++i) {
+		w = ifmarked->data[i];
 		if (w->type != NI_IFWORKER_TYPE_NETDEV)
 			continue;
 
-		if (ifname && !ni_string_eq(w->name, ifname))
-			continue;
-
-		if (!ni_ifcheck_worker_config_matches(w))
-			ifreload_mark_down(fsm, down_marked, w, logit, 1);
+		ifreload_mark_worker_down(fsm, ifmarked, w);
 	}
-
-	/* set-up if config changed + dependencies */
-	for (i = 0; i < fsm->workers.count; ++i) {
-		w = fsm->workers.data[i];
+	/* second pass: check lower changes */
+	for (i = 0; i < ifmarked->count; ++i) {
+		w = ifmarked->data[i];
 
 		if (w->type != NI_IFWORKER_TYPE_NETDEV)
 			continue;
 
-		if (ifname && !ni_string_eq(w->name, ifname))
+		if (!w->lowerdev)
 			continue;
 
-		if (!ni_ifcheck_worker_config_matches(w))
-			ifreload_mark_up(fsm, up_marked, w, logit);
+		if (w->lowerdev->target_range.max == NI_FSM_STATE_DEVICE_DOWN)
+			w->target_range.max = NI_FSM_STATE_DEVICE_DOWN;
 	}
 }
 
@@ -343,6 +169,7 @@ ni_do_ifreload(const char *caller, int argc, char **argv)
 		OPT_TRANSIENT	= 'T',
 		OPT_RELEASE,
 		OPT_NO_RELEASE,
+		OPT_DRY_RUN,
 	};
 	static struct option ifreload_options[] = {
 		{ "help",		no_argument,		NULL,	OPT_HELP },
@@ -352,6 +179,7 @@ ni_do_ifreload(const char *caller, int argc, char **argv)
 		{ "persistent",		no_argument,		NULL,	OPT_PERSISTENT },
 		{ "release",		no_argument,		NULL,	OPT_RELEASE },
 		{ "no-release",		no_argument,		NULL,	OPT_NO_RELEASE },
+		{ "dry-run",		no_argument,		NULL,	OPT_DRY_RUN },
 
 		{ NULL,			no_argument,		NULL,	0 }
 	};
@@ -363,6 +191,7 @@ ni_do_ifreload(const char *caller, int argc, char **argv)
 	ni_bool_t opt_persistent = FALSE;
 	ni_bool_t opt_transient = FALSE;
 	ni_tristate_t opt_release = NI_TRISTATE_DEFAULT;
+	ni_log_fn_t *dry_run = NULL;
 	unsigned int seconds = 0;
 	int c, status = NI_WICKED_RC_USAGE;
 	char *saved_argv0, *program = NULL;
@@ -408,6 +237,10 @@ ni_do_ifreload(const char *caller, int argc, char **argv)
 			opt_release = NI_TRISTATE_DISABLE;
 			break;
 
+		case OPT_DRY_RUN:
+			dry_run = ni_note;
+			break;
+
 		default:
 		case OPT_HELP:
 usage:
@@ -426,6 +259,8 @@ usage:
 				"      Set interface into persistent mode (no regular ifdown allowed)\n"
 				"  --[no-]release\n"
 				"      Override active config to (not) release leases in ifdown\n"
+				"  --dry-run\n"
+				"      Show interface hierarchies as notice with (+/-) markers and exit.\n"
 				, program);
 			goto cleanup;
 		}
@@ -491,23 +326,28 @@ usage:
 
 	/* Build the up a config relations/hierarchy tree */
 	if (ni_fsm_build_hierarchy(fsm, FALSE) < 0) {
-		ni_error("ifreload: unable to build device config hierarchy");
+		ni_error("ifreload: unable to build interface hierarchy");
 		/* Severe error we always explicitly return */
 		status = NI_WICKED_RC_ERROR;
 		goto cleanup;
 	}
 
+	/* Get workers that match given criteria */
 	status = NI_WICKED_RC_SUCCESS;
 	for (c = optind; c < argc; ++c) {
 		const char *ifname = argv[c];
 
 		if (ni_string_eq(ifname, "all") || ni_string_empty(ifname)) {
-			ifreload_mark_workers(fsm, &down_marked, &up_marked, NULL);
+			ifreload_match_workers(fsm, &down_marked, &up_marked, NULL);
 			break;
 		} else {
-			ifreload_mark_workers(fsm, &down_marked, &up_marked, ifname);
+			ifreload_match_workers(fsm, &down_marked, &up_marked, ifname);
 		}
 	}
+	ni_fsm_print_system_hierarchy(fsm, &down_marked, dry_run);
+	ni_fsm_print_config_hierarchy(fsm, &up_marked, dry_run);
+	if (dry_run)
+		goto cleanup;
 
 	if (opt_persistent) {
 		for (i = 0; i < up_marked.count; ++i) {
@@ -534,6 +374,8 @@ usage:
 		/* Run ifdown part of the reload */
 		ni_debug_application("Shutting down unneeded devices");
 
+		ifreload_mark_workers_down(fsm, &down_marked);
+
 		/* delete policies */
 		ni_ifdown_fire_nanny(&down_marked);
 
@@ -571,8 +413,6 @@ usage:
 		goto cleanup;
 	}
 
-	ni_fsm_pull_in_children(&up_marked, fsm);
-
 	/* anything to ifup? */
 	if (up_marked.count) {
 		if (!(monitor = ni_nanny_fsm_monitor_new(fsm))) {
diff --git a/client/ifup.c b/client/ifup.c
index 45dcc37e2..641e31f5d 100644
--- a/client/ifup.c
+++ b/client/ifup.c
@@ -53,110 +53,120 @@ struct ni_nanny_fsm_monitor {
 	ni_ifworker_array_t *	marked;
 };
 
-static xml_node_t *
-__ni_ifup_generate_match_dev(xml_node_t *node, ni_ifworker_t *w)
-{
-	if (!node || !w || ni_string_empty(w->name))
-		return NULL;
-	return xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, node, w->name);
-}
-
 static ni_bool_t
-__ni_ifup_generate_match_link_port_ref(xml_node_t *match, xml_node_t *port)
+ni_ifup_generate_match_device(xml_node_t *node, ni_ifworker_t *w)
 {
-	const char *type = xml_node_get_attr(port, NI_CLIENT_IFCONFIG_PORT_TYPE);
-	ni_iftype_t ptype = ni_linktype_name_to_type(type);
-	xml_node_t *ref, *ovsbr;
-
-	switch (ptype) {
-	case NI_IFTYPE_OVS_BRIDGE:
-		ovsbr = xml_node_get_child(port, NI_CLIENT_IFCONFIG_BRIDGE);
-		if (!ovsbr || ni_string_empty(ovsbr->cdata))
-			return FALSE;
-
-		if (!(ref = xml_node_new(NI_NANNY_IFPOLICY_MATCH_REF, match)))
-			return FALSE;
-
-		if (!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, ref, ovsbr->cdata)) {
-			xml_node_free(ref);
-			return FALSE;
-		}
-		break;
+	/*
+	 * Generate <device>${w->name}</device> match (self reference) node.
+	 */
+	if (!node || !w || ni_string_empty(w->name))
+		return FALSE;
 
-	default:
-		/* other port types need master only */
-		break;
-	}
-	return TRUE;
+	return !!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, node, w->name);
 }
 
 static ni_bool_t
-__ni_ifup_generate_match_link_ref(xml_node_t *match, xml_node_t *link)
+ni_ifup_generate_match_device_ref(xml_node_t *match, const char *name)
 {
-	xml_node_t *ref, *master, *port;
-
-	if (!(master = xml_node_get_child(link, NI_CLIENT_IFCONFIG_MASTER)))
-		return TRUE; /* <link> does not contain a <master> node */
-
-	if (ni_string_empty(master->cdata))
-		return FALSE;
-
-	if (ni_string_eq(master->cdata, ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM))) {
-		if ((port = xml_node_get_child(link, NI_CLIENT_IFCONFIG_LINK_PORT)))
-			return __ni_ifup_generate_match_link_port_ref(match, port);
+	xml_node_t *ref;
 
+	/*
+	 * Generate <reference><device>${name}</device></reference>
+	 * match node to a related (lower, master, …) interface name.
+	 */
+	if (!match || ni_string_empty(name))
 		return FALSE;
-	}
 
 	if (!(ref = xml_node_new(NI_NANNY_IFPOLICY_MATCH_REF, match)))
 		return FALSE;
 
-	if (!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, ref, master->cdata)) {
-		xml_node_free(ref);
-		return FALSE;
-	}
+	if (xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, ref, name))
+		return TRUE;
 
-	if ((port = xml_node_get_child(link, NI_CLIENT_IFCONFIG_LINK_PORT)))
-		return __ni_ifup_generate_match_link_port_ref(match, port);
-
-	return TRUE; /* master ref at least */
+	xml_node_detach(ref);
+	xml_node_free(ref);
+	return FALSE;
 }
 
 static ni_bool_t
-__ni_ifup_generate_match_master_ref(xml_node_t *match, ni_ifworker_t *master)
+ni_ifup_generate_match_lower_ref(xml_node_t *match, ni_ifworker_t *w)
 {
-	xml_node_t *ref;
-
-	if (!master || ni_string_empty(master->name))
-		return FALSE;
+	/*
+	 * If we have a lower/underlying interface (e.g. lower=bond0)
+	 * reference and our (e.g. vlan bond0.42) interface can't
+	 * be setup or even can't be created or exist without it,
+	 * e.g.:
+	 *     ip link add dev bond0.42 link bond0 type vlan id 42
+	 *
+	 * In the config, the lower reference does not have a fix
+	 * node name, but is provided by type specific nodes, e.g.
+	 * for vlan|macvlan:
+	 *     <vlan|macvlan|…>
+	 *       <device>${w->lowerdev}</device>
+	 *     </vlan|macvlan|…>
+	 * for ovs vlan (fake) bridge's parent bridge:
+	 *     <ovs-bridge>
+	 *       <vlan>
+	 *         <parent>${w->lowerdev}</device>
+	 *       </vlan>
+	 *     </ovs-bridge>
+	 *
+	 * The w->lowerdev points to the worker resolved from the
+	 * config by fsm while (config) hierarchy build, so we
+	 * don't need to "find it manually".
+	 */
+	if (!w->lowerdev)
+		return TRUE;
 
-	if (!(ref = xml_node_new(NI_NANNY_IFPOLICY_MATCH_REF, match)))
-		return FALSE;
+	return ni_ifup_generate_match_device_ref(match, w->lowerdev->name);
+}
 
-	if (!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, ref, master->name)) {
-		xml_node_free(ref);
-		return FALSE;
-	}
+static ni_bool_t
+ni_ifup_generate_match_master_ref(xml_node_t *match, ni_ifworker_t *w)
+{
+	/*
+	 * If we are a port (e.g. bond0.42 vlan) bound to a master
+	 * (e.g. br42 bridge) required for the link setup, e.g.:
+	 *     ip link set dev bond0.42 master br42
+	 *
+	 * The <link> node provides a typed port union:
+	 *   <link>
+	 *     <master>${w->masterdev}</master>
+	 *     <port type="${type-name}">${type-data}</port>
+	 *   </link>
+	 *
+	 * The port type is the type of the effective master and
+	 * its content is type specific port configuration node.
+	 *
+	 * The w->masterdev points to the worker resolved from the
+	 * config by fsm while (config) hierarchy build, so we
+	 * don't need to "find it manually".
+	 */
+	if (!w->masterdev)
+		return TRUE;
 
-	return TRUE;
+	return ni_ifup_generate_match_device_ref(match, w->masterdev->name);
 }
 
 static ni_bool_t
-__ni_ifup_generate_match_refs(xml_node_t *match, ni_ifworker_t *w)
+ni_ifup_generate_match_refs(xml_node_t *match, ni_ifworker_t *w)
 {
-	xml_node_t *link;
-
-	if (w->masterdev)
-		return __ni_ifup_generate_match_master_ref(match, w->masterdev);
+	/*
+	 * Some interface types may have a lower (underlying)
+	 * interface and (most) can be also bound as port to
+	 * an another master reference.
+	 */
+	if (!ni_ifup_generate_match_lower_ref(match, w))
+		return FALSE;
 
-	if ((link = xml_node_get_child(w->config.node, NI_CLIENT_IFCONFIG_LINK)))
-		return __ni_ifup_generate_match_link_ref(match, link);
+	if (!ni_ifup_generate_match_master_ref(match, w))
+		return FALSE;
 
-	return TRUE; /* no refs is not an error */
+	return TRUE; /* no refa is not an error */
 }
 
 static xml_node_t *
-__ni_ifup_generate_match(const char *name, ni_ifworker_t *w)
+ni_ifup_generate_match(const char *name, ni_ifworker_t *w)
 {
 	xml_node_t *match;
 
@@ -167,47 +177,27 @@ __ni_ifup_generate_match(const char *name, ni_ifworker_t *w)
 		"generate policy match for %s (type %s)", w->name,
 		ni_linktype_type_to_name(w->iftype));
 
-	if (!__ni_ifup_generate_match_dev(match, w))
+	if (!ni_ifup_generate_match_device(match, w)) {
+		ni_error("%s: unable to generate policy device match",
+				w->name);
 		goto error;
+	}
 
-	/* Ignore child dependency for following device types:
-	 *  - ovs-system: otherwise ovs-system would require all ports
-	 *    in all ovs-bridges and want to get at least one up ...
-	 *    this is not what we want :-)
+	/*
+	 * The ovs-system datapath does not have any
+	 * (should not have any) references to other
+	 * interfaces...
 	 */
-	switch (w->iftype) {
-	case NI_IFTYPE_OVS_SYSTEM:
+	if (w->iftype == NI_IFTYPE_OVS_SYSTEM ||
+	    ni_string_eq(w->name, ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM)))
 		goto done;
-		break;
-	default:
-		if (ni_string_eq(w->name, ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM)))
-			goto done;
-		break;
-	}
 
-	if (!__ni_ifup_generate_match_refs(match, w)) {
-		ni_debug_application("%s: unable to generate policy match device references",
+	if (!ni_ifup_generate_match_refs(match, w)) {
+		ni_error("%s: unable to generate policy device reference match",
 				w->name);
 		goto error;
 	}
 
-	if (w->children.count) {
-		xml_node_t *or;
-		unsigned int i;
-
-		if (!(or = xml_node_new(NI_NANNY_IFPOLICY_MATCH_COND_OR, match)))
-			goto error;
-
-		for (i = 0; i < w->children.count; i++) {
-			ni_ifworker_t *child = w->children.data[i];
-			xml_node_t *cnode;
-
-			cnode = xml_node_new(NI_NANNY_IFPOLICY_MATCH_COND_CHILD, or);
-			if (!cnode || !__ni_ifup_generate_match_dev(cnode, child))
-				goto error;
-		}
-	}
-
 done:
 	return match;
 error:
@@ -227,7 +217,7 @@ ni_ifup_start_policy(ni_ifworker_t *w)
 
 	ni_debug_application("%s: hiring nanny", w->name);
 
-	match = __ni_ifup_generate_match(NI_NANNY_IFPOLICY_MATCH, w);
+	match = ni_ifup_generate_match(NI_NANNY_IFPOLICY_MATCH, w);
 	if (!match)
 		goto error;
 
@@ -455,6 +445,7 @@ ni_do_ifup(int argc, char **argv)
 {
 	enum  { OPT_HELP, OPT_IFCONFIG, OPT_CONTROL_MODE, OPT_STAGE, OPT_TIMEOUT,
 		OPT_SKIP_ACTIVE, OPT_SKIP_ORIGIN, OPT_PERSISTENT, OPT_TRANSIENT,
+		OPT_DRY_RUN,
 #ifdef NI_TEST_HACKS
 		OPT_IGNORE_PRIO, OPT_IGNORE_STARTMODE,
 #endif
@@ -474,6 +465,7 @@ ni_do_ifup(int argc, char **argv)
 		{ "ignore-startmode",no_argument, NULL,	OPT_IGNORE_STARTMODE },
 #endif
 		{ "persistent",	no_argument, NULL,	OPT_PERSISTENT },
+		{ "dry-run",	no_argument, NULL,	OPT_DRY_RUN },
 		{ NULL }
 	};
 
@@ -484,6 +476,7 @@ ni_do_ifup(int argc, char **argv)
 	ni_string_array_t ifnames = NI_STRING_ARRAY_INIT;
 	ni_bool_t check_prio = TRUE, set_persistent = FALSE;
 	ni_bool_t opt_transient = FALSE;
+	ni_log_fn_t *dry_run = NULL;
 	int c, status = NI_WICKED_RC_USAGE;
 	unsigned int seconds = 0;
 	ni_fsm_t *fsm;
@@ -538,12 +531,16 @@ ni_do_ifup(int argc, char **argv)
 			break;
 #endif
 
+		case OPT_TRANSIENT:
+			opt_transient = TRUE;
+			break;
+
 		case OPT_PERSISTENT:
 			set_persistent = TRUE;
 			break;
 
-		case OPT_TRANSIENT:
-			opt_transient = TRUE;
+		case OPT_DRY_RUN:
+			dry_run = ni_note;
 			break;
 
 		default:
@@ -578,6 +575,8 @@ usage:
 #endif
 				"  --persistent\n"
 				"      Set interface into persistent mode (no regular ifdown allowed)\n"
+				"  --dry-run\n"
+				"      Show interface hierarchies as notice with (+) markers and exit.\n"
 				);
 			goto cleanup;
 		}
@@ -633,15 +632,14 @@ usage:
 	ni_nanny_fsm_monitor_arm(monitor, fsm->worker_timeout);
 
 	if (ni_fsm_build_hierarchy(fsm, TRUE) < 0) {
-		ni_error("ifup: unable to build device hierarchy");
+		ni_error("ifup: unable to build interface hierarchy");
 		/* Severe error we always explicitly return */
 		status = NI_WICKED_RC_ERROR;
 		goto cleanup;
 	}
 
-	status = NI_WICKED_RC_SUCCESS;
-
 	/* Get workers that match given criteria */
+	status = NI_WICKED_RC_SUCCESS;
 	while (optind < argc) {
 		ifmatch.name = argv[optind++];
 
@@ -662,7 +660,10 @@ usage:
 			ni_string_array_append(&ifnames, ifmatch.name);
 	}
 
-	ni_fsm_pull_in_children(&ifmarked, fsm);
+	ni_fsm_print_system_hierarchy(fsm, NULL, dry_run);
+	ni_fsm_print_config_hierarchy(fsm, &ifmarked, dry_run);
+	if (dry_run)
+		goto cleanup;
 
 	if (!ni_ifup_hire_nanny(&ifmarked, set_persistent))
 		status = NI_WICKED_RC_NOT_CONFIGURED;
diff --git a/client/nanny.c b/client/nanny.c
index 17a7f2142..4e3306c33 100644
--- a/client/nanny.c
+++ b/client/nanny.c
@@ -305,7 +305,7 @@ ni_nanny_addpolicy_node(const xml_node_t *pnode, const char *origin)
 int
 ni_nanny_addpolicy(xml_document_t *doc)
 {
-	xml_node_t *root, *pnode;
+	xml_node_t *root;
 	const char *origin;
 	int count = 0;
 
@@ -320,16 +320,7 @@ ni_nanny_addpolicy(xml_document_t *doc)
 			NI_CLIENT_IFCONFIG, origin, NI_NANNY_IFPOLICY);
 		return -1;
 	}
-
-	for (pnode = root->children; pnode; pnode = pnode->next) {
-		int rv = ni_nanny_addpolicy_node(pnode, origin);
-		if (rv < 0)
-			return rv;
-
-		count += rv;
-	}
-
-	return count;
+	return ni_nanny_addpolicy_node(xml_document_root(doc), origin);
 }
 
 /*
diff --git a/client/read-config.c b/client/read-config.c
index 097a46802..9c7ee2858 100644
--- a/client/read-config.c
+++ b/client/read-config.c
@@ -279,17 +279,17 @@ ni_ifconfig_load(ni_fsm_t *fsm, const char *root, ni_string_array_t *opt_ifconfi
 			return FALSE;
 		}
 	}
+	if (ni_ifxml_migrate_docs(&docs))
+		ni_debug_readwrite("Migrated configuration to current schema");
 
 	for (i = 0; i < docs.count; i++) {
-		xml_node_t *root, *ifnode;
+		xml_node_t *root;
 		const char *origin;
 
 		root = xml_document_root(docs.data[i]);
 		origin = xml_node_location_filename(root);
-		for (ifnode = root->children; ifnode; ifnode = ifnode->next) {
-			/* We do not fail when unable to generate ifworker */
-			ni_fsm_workers_from_xml(fsm, ifnode, origin);
-		}
+		/* We do not fail when unable to generate ifworker */
+		ni_fsm_workers_from_xml(fsm, root, origin);
 	}
 
 	xml_document_array_destroy(&docs);
@@ -415,8 +415,11 @@ ni_ifconfig_validate_adding_doc(xml_document_t *config_doc, ni_bool_t check_prio
 	src_root = xml_document_root(config_doc);
 	src_prio = ni_ifconfig_origin_get_prio(xml_node_location_filename(src_root));
 
-	/* Go through all config_doc's <interfaces> */
-	for (src_child = src_root->children; src_child; src_child = src_child->next) {
+	if (ni_string_empty(src_root->name))
+		src_child = src_root->children;
+	else
+		src_child = src_root;
+	for ( ; src_child; src_child = src_child->next) {
 		int rv;
 
 		if (ni_ifconfig_is_policy(src_child)) {
@@ -489,8 +492,6 @@ ni_ifconfig_read_wicked_xml_file(xml_document_array_t *docs,
 
 		xml_node_location_set(node, xml_location_clone(cnode->location));
 		xml_node_reparent(node, cnode);
-		if (ni_ifconfig_migrate(node))
-			ni_debug_readwrite("Migrated \"%s\" config to current schema", conf.origin);
 
 		ni_ifconfig_metadata_clear(node);
 		if (!raw)
@@ -498,10 +499,11 @@ ni_ifconfig_read_wicked_xml_file(xml_document_array_t *docs,
 
 		if (ni_ifconfig_validate_adding_doc(doc, check_prio)) {
 			ni_debug_ifconfig("%s: %s", __func__, xml_node_location(node));
-			xml_document_array_append(docs, doc);
-		} else {
-			xml_document_free(doc);
+
+			if (xml_document_expand(docs, doc))
+				continue;
 		}
+		xml_document_free(doc);
 	}
 
 	ni_client_state_config_reset(&conf);
@@ -699,10 +701,10 @@ ni_ifconfig_read_firmware(xml_document_array_t *array,
 
 			if (ni_ifconfig_validate_adding_doc(config, check_prio)) {
 				ni_debug_ifconfig("%s: %s", __func__, xml_node_location(node));
-				xml_document_array_append(array, config);
-			} else {
-				xml_document_free(config);
+				if (xml_document_expand(array, config))
+					continue;
 			}
+			xml_document_free(config);
 		}
 	}
 
@@ -732,7 +734,11 @@ ni_ifconfig_metadata_add_to_node(xml_node_t *root, ni_client_state_config_t *con
 	if (!root || !root->children || !conf || ni_string_empty(conf->origin))
 		return;
 
-	while ((ifnode = xml_node_get_next_child(root, root->children->name, ifnode))) {
+	if (ni_string_empty(root->name))
+		ifnode = root->children;
+	else
+		ifnode = root;
+	for ( ; ifnode; ifnode = ifnode->next) {
 		xml_node_add_attr(ifnode,
 				NI_CLIENT_STATE_XML_CONFIG_ORIGIN_NODE,
 					conf->origin);
@@ -759,7 +765,11 @@ ni_ifconfig_metadata_get_from_node(ni_client_state_config_t *conf, xml_node_t *r
 		return FALSE;
 
 	ni_client_state_config_reset(conf);
-	while ((ifnode = xml_node_get_next_child(root, root->children->name, ifnode))) {
+	if (ni_string_empty(root->name))
+		ifnode = root->children;
+	else
+		ifnode = root;
+	for ( ; ifnode; ifnode = ifnode->next) {
 		/* only first   node with proper meta data attributes is processed */
 		const char *origin;
 		const char *str;
@@ -790,7 +800,11 @@ ni_ifconfig_metadata_clear(xml_node_t *root)
 	if (!root || !root->children)
 		return;
 
-	while ((ifnode = xml_node_get_next_child(root, root->children->name, ifnode))) {
+	if (ni_string_empty(root->name))
+		ifnode = root->children;
+	else
+		ifnode = root;
+	for ( ; ifnode; ifnode = ifnode->next) {
 		xml_node_del_attr(ifnode, NI_CLIENT_STATE_XML_CONFIG_ORIGIN_NODE);
 		xml_node_del_attr(ifnode, NI_CLIENT_STATE_XML_CONFIG_UUID_NODE);
 		xml_node_del_attr(ifnode, NI_CLIENT_STATE_XML_CONFIG_OWNER_NODE);
diff --git a/client/redhat/compat-redhat.c b/client/redhat/compat-redhat.c
index 63fd7a35b..4208a3748 100644
--- a/client/redhat/compat-redhat.c
+++ b/client/redhat/compat-redhat.c
@@ -195,12 +195,11 @@ __ni_redhat_read_interface(const char *filename, const char *ifname, ni_compat_n
 		goto error;
 
 done:
-	ni_sysconfig_destroy(sc);
+	ni_sysconfig_free(sc);
 	return compat;
 
 error:
-	if (sc)
-		ni_sysconfig_destroy(sc);
+	ni_sysconfig_free(sc);
 	return NULL;
 }
 
@@ -287,7 +286,6 @@ try_bridge_port(ni_sysconfig_t *sc, ni_compat_netdev_t *compat, ni_compat_netdev
 {
 	ni_netdev_t *dev = compat->dev;
 	ni_compat_netdev_t *master;
-	ni_bridge_t *bridge;
 	const char *bridge_name;
 
 	if (!(bridge_name = ni_sysconfig_get_value(sc, "BRIDGE")))
@@ -298,16 +296,21 @@ try_bridge_port(ni_sysconfig_t *sc, ni_compat_netdev_t *compat, ni_compat_netdev
 		master = ni_compat_netdev_new(bridge_name);
 	if (master->dev->link.type == NI_IFTYPE_UNKNOWN) {
 		master->dev->link.type = NI_IFTYPE_BRIDGE;
+		ni_netdev_get_bridge(master->dev);
 	} else if (master->dev->link.type != NI_IFTYPE_BRIDGE) {
-		ni_error("%s: specifies BRIDGE=%s which is not a bonding device",
+		ni_error("%s: specifies BRIDGE=%s which is not a bridge device",
 				dev->name, bridge_name);
 		return FALSE;
 	}
 
-	bridge = ni_netdev_get_bridge(master->dev);
-	ni_bridge_port_new(bridge, dev->name, 0);
+	if (ni_string_empty(dev->link.masterdev.name))
+		return ni_netdev_ref_set_ifname(&dev->link.masterdev, bridge_name);
+	if (ni_string_eq(dev->link.masterdev.name, bridge_name))
+		return TRUE;
 
-	return TRUE;
+	ni_error("%s: specifies BRIDGE=%s which is already member of %s",
+			dev->name, bridge_name, dev->link.masterdev.name);
+	return FALSE;
 }
 
 /*
@@ -319,7 +322,6 @@ try_bonding_slave(ni_sysconfig_t *sc, ni_compat_netdev_t *compat, ni_compat_netd
 	ni_netdev_t *dev = compat->dev;
 	ni_compat_netdev_t *master;
 	ni_bool_t is_slave = 0;
-	ni_bonding_t *bonding;
 	const char *master_name;
 
 	if (!ni_sysconfig_get_boolean(sc, "SLAVE", &is_slave) || !is_slave)
@@ -331,18 +333,24 @@ try_bonding_slave(ni_sysconfig_t *sc, ni_compat_netdev_t *compat, ni_compat_netd
 	}
 
 	master = ni_compat_netdev_by_name(known_devices, master_name);
-	if (master == NULL) {
+	if (master == NULL)
 		master = ni_compat_netdev_new(master_name);
+	if (master->dev->link.type == NI_IFTYPE_UNKNOWN) {
 		master->dev->link.type = NI_IFTYPE_BOND;
+		ni_netdev_get_bonding(master->dev);
 	} else if (master->dev->link.type != NI_IFTYPE_BOND) {
 		ni_error("%s: specifies MASTER=%s which is not a bonding device", dev->name, master_name);
 		return FALSE;
 	}
 
-	bonding = ni_netdev_get_bonding(master->dev);
-	if (ni_bonding_has_slave(bonding, dev->name))
+	if (ni_string_empty(dev->link.masterdev.name))
+		return ni_netdev_ref_set_ifname(&dev->link.masterdev, master_name);
+	if (ni_string_eq(dev->link.masterdev.name, master_name))
 		return TRUE;
-	return ni_bonding_add_slave(bonding, dev->name) != NULL;
+
+	ni_error("%s: specifies MASTER=%s which is already member of %s",
+			dev->name, master_name, dev->link.masterdev.name);
+	return FALSE;
 }
 
 
@@ -420,7 +428,7 @@ try_vlan(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 /*
  * Given a suffix like "" or "_1", try to get the IP address and prefix length.
  * If successful, create a new ni_address, attach it to the interface object
- * and return it. 
+ * and return it.
  */
 static ni_bool_t
 __ni_redhat_addrconf_static(ni_sysconfig_t *sc, ni_compat_netdev_t *compat, const char *label)
diff --git a/client/suse/compat-suse.c b/client/suse/compat-suse.c
index 85b01df12..d2fdd9a49 100644
--- a/client/suse/compat-suse.c
+++ b/client/suse/compat-suse.c
@@ -28,20 +28,6 @@
 #include "config.h"
 #endif
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <ctype.h>
-#include <errno.h>
-#include <net/if_arp.h>
-#include <net/ethernet.h>
-#include <netlink/netlink.h>
-#include <sys/types.h>
-#include <sys/utsname.h>
-#include <pwd.h>
-#include <grp.h>
-
 #include <wicked/address.h>
 #include <wicked/util.h>
 #include <wicked/logging.h>
@@ -67,11 +53,13 @@
 #include <wicked/tuntap.h>
 #include <wicked/tunneling.h>
 #include <wicked/ethtool.h>
-
 #include <wicked/objectmodel.h>
 #include <wicked/dbus.h>
+#include <wicked/array.h>
+
 #include "appconfig.h"
 #include "util_priv.h"
+#include "array_priv.h"
 #include "duid.h"
 #include "dhcp.h"
 #include "dhcp6/options.h"
@@ -79,15 +67,63 @@
 #include "client/suse/ifsysctl.h"
 #include "client/wicked-client.h"
 
+#include <ctype.h>
+#include <errno.h>
+#include <linux/if_arp.h>
+#include <linux/if_addr.h>
+#include <netlink/netlink.h>
+#include <sys/utsname.h>
+#include <pwd.h>
+#include <grp.h>
+
+
+typedef struct ni_suse_ifcfg {
+	ni_sysconfig_t *		config;
+	ni_compat_netdev_t *		compat;
+	ni_bool_t			broken;
+	ni_bool_t			l2only;
+} ni_suse_ifcfg_t;
+
+#define NI_SUSE_IFCFG_ARRAY_INIT	NI_ARRAY_INIT
+#define NI_SUSE_IFCFG_ARRAY_CHUNK	8
+ni_declare_ptr_array_type(ni_suse_ifcfg);
+
+static ni_suse_ifcfg_t *		ni_suse_ifcfg_new(void);
+static void				ni_suse_ifcfg_free(ni_suse_ifcfg_t *);
+static ni_suse_ifcfg_t *		ni_suse_ifcfg_new_missing(ni_suse_ifcfg_t *,
+							const char *);
+static void				ni_suse_ifcfg_set_hotplug(ni_suse_ifcfg_t *);
+static void				ni_suse_ifcfg_set_l2_only(ni_suse_ifcfg_t *, ni_bool_t);
+static ni_suse_ifcfg_t *		ni_suse_ifcfg_add_l2_port(ni_suse_ifcfg_array_t *,
+							ni_suse_ifcfg_t *, const char *,
+							const char *, const char *,
+							ni_bool_t);
+static ni_suse_ifcfg_t *		ni_suse_ifcfg_add_lower(ni_suse_ifcfg_array_t *,
+							ni_suse_ifcfg_t *, const char *,
+							const char *);
+
+static ni_suse_ifcfg_t *		ni_suse_ifcfg_find_by_ifname(ni_suse_ifcfg_array_t *,
+							const char *);
+
+typedef ni_bool_t (*ifcfg_process_fn_t)(ni_suse_ifcfg_array_t *, ni_suse_ifcfg_t *,
+					const char *, const char *, const char *);
+
+static unsigned int			ni_suse_ifcfg_process_indexed(ni_suse_ifcfg_array_t *,
+							ni_suse_ifcfg_t *, const char *,
+							ifcfg_process_fn_t);
+
+static ni_bool_t			ni_suse_ifcfg_parse(ni_suse_ifcfg_array_t *);
+
+static					ni_declare_ptr_array_init(ni_suse_ifcfg);
+static					ni_declare_ptr_array_destroy(ni_suse_ifcfg);
+static					ni_declare_ptr_array_append(ni_suse_ifcfg);
+
+
 typedef ni_bool_t (*try_function_t)(const ni_sysconfig_t *, ni_netdev_t *, const char *);
 
-static ni_compat_netdev_t *	__ni_suse_read_interface(const char *, const char *);
 static ni_bool_t		__ni_suse_read_globals(const char *, const char *, const char *);
 static void			__ni_suse_free_globals(void);
 static void			__ni_suse_show_unapplied_routes(void);
-static void			__ni_suse_adjust_slaves(ni_compat_netdev_array_t *);
-static void			__ni_suse_adjust_ovs_system(ni_compat_netdev_t *);
-static ni_bool_t		__ni_suse_sysconfig_read(ni_sysconfig_t *, ni_compat_netdev_t *);
 static int			__process_indexed_variables(const ni_sysconfig_t *, ni_netdev_t *,
 							const char *, try_function_t);
 static ni_var_t *		__find_indexed_variable(const ni_sysconfig_t *, const char *, const char *);
@@ -147,7 +183,7 @@ static ni_bool_t		__ni_ipv6_disbled;
 #define __NI_WIRELESS_WPA_PSK_MIN_LEN	8
 
 static ni_bool_t
-__ni_suse_ifcfg_valid_suffix(const char *name, size_t pfxlen)
+ni_suse_ifcfg_valid_suffix(const char *name, size_t pfxlen)
 {
 	const char *blacklist[] = {
 		"~", ".old", ".bak", ".orig", ".scpmbackup",
@@ -173,51 +209,118 @@ __ni_suse_ifcfg_valid_suffix(const char *name, size_t pfxlen)
 }
 
 static ni_bool_t
-__ni_suse_ifcfg_valid_prefix(const char *basename, const char *prefix)
+ni_suse_ifcfg_valid_prefix(const char *basename, const char *prefix)
 {
-	size_t pfxlen;
-
 	if (!basename || !prefix)
 		return FALSE;
 
-	pfxlen = strlen(prefix);
-	if (strncmp(basename, prefix, pfxlen))
-		return FALSE;
+	return ni_string_startswith(basename, prefix);
+}
 
-	return TRUE;
+/*
+ * Read a single ifcfg file and init compat netdev
+ */
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_read_file(const char *filename)
+{
+	const char *basename = ni_basename(filename);
+	size_t pfxlen = sizeof(__NI_SUSE_CONFIG_IFPREFIX)-1;
+	ni_suse_ifcfg_t *ifcfg = NULL;
+	const char *ifname;
+
+	/* Check in case ni_suse_ifcfg_read_file is used directly */
+	if (!ni_suse_ifcfg_valid_prefix(basename, __NI_SUSE_CONFIG_IFPREFIX)) {
+		ni_error("Rejecting file without '%s' prefix: %s",
+				__NI_SUSE_CONFIG_IFPREFIX, filename);
+		return NULL;
+	}
+	if (!ni_suse_ifcfg_valid_suffix(basename, pfxlen)) {
+		ni_error("Rejecting blacklisted %sfile suffix: %s",
+				__NI_SUSE_CONFIG_IFPREFIX, filename);
+		return NULL;
+	}
+
+	ifname = basename + pfxlen;
+	if (!ni_netdev_name_is_valid(ifname)) {
+		ni_error("Rejecting suspect interface name: %s", ifname);
+		return NULL;
+	}
+
+	if (!(ifcfg = ni_suse_ifcfg_new())) {
+		ni_error("Unable to allocate ifcfg structure for %sfile: %s",
+				__NI_SUSE_CONFIG_IFPREFIX, filename);
+		return NULL;
+	}
+
+	if (!(ifcfg->compat = ni_compat_netdev_new(ifname))) {
+		ni_error("Unable to allocate netdev structure for %sfile: %s",
+				__NI_SUSE_CONFIG_IFPREFIX, filename);
+		ni_suse_ifcfg_free(ifcfg);
+		return NULL;
+	}
+
+	if (!(ifcfg->config = ni_sysconfig_read(filename))) {
+		/* errors reported */
+		ni_suse_ifcfg_free(ifcfg);
+		return NULL;
+	}
+
+	return ifcfg;
 }
 
+/*
+ * Read ifcfg files in directory and init compat netdevs
+ */
 static int
-__ni_suse_ifcfg_scan_files(const char *dirname, ni_string_array_t *res)
+ni_suse_ifcfg_read_files(ni_suse_ifcfg_array_t *ifcfgs, const char *dirname)
 {
 	ni_string_array_t files = NI_STRING_ARRAY_INIT;
 	const char *pattern = __NI_SUSE_CONFIG_IFPREFIX"*";
 	size_t pfxlen = sizeof(__NI_SUSE_CONFIG_IFPREFIX)-1;
-	unsigned int i, count = res->count;
+	unsigned int i, count = ifcfgs->count;
+	ni_suse_ifcfg_t *ifcfg;
+	char *filename = NULL;
+	const char *file;
 
-	if( !ni_scandir(dirname, pattern, &files))
+	if (!ni_scandir(dirname, pattern, &files))
 		return 0;
 
-	for(i = 0; i < files.count; ++i) {
-		const char *file = files.data[i];
+	for (i = 0; i < files.count; ++i) {
+		if (!(file = files.data[i]))
+			continue;
+
+		if (!ni_suse_ifcfg_valid_suffix(file, pfxlen)) {
+			/* e.g. .bak suffix created by firewalld */
+			ni_debug_readwrite("Ignoring blacklisted %sfile suffix: %s",
+					__NI_SUSE_CONFIG_IFPREFIX, file);
+			continue;
+		}
 
-		if (!__ni_suse_ifcfg_valid_suffix(file, pfxlen)) {
-			ni_debug_readwrite("Ignoring blacklisted %sfile: %s",
+		if (!ni_string_printf(&filename, "%s/%s", dirname, file)) {
+			ni_error("Failed to construct full path for %sfile %s: %m",
 					__NI_SUSE_CONFIG_IFPREFIX, file);
 			continue;
 		}
 
-		ni_string_array_append(res, file);
+		if (!(ifcfg = ni_suse_ifcfg_read_file(filename)))
+			continue;	/* errors reported */
+
+		if (!ni_suse_ifcfg_array_append(ifcfgs, ifcfg)) {
+			ni_error("Failed to add '%s' structure to array: %m", file);
+			ni_suse_ifcfg_free(ifcfg);
+		}
 	}
+
+	ni_string_free(&filename);
 	ni_string_array_destroy(&files);
 
-	return res->count - count;
+	return ifcfgs->count - count;
 }
 
 ni_bool_t
 __ni_suse_get_ifconfig(const char *root, const char *path, ni_compat_ifconfig_t *result)
 {
-	ni_string_array_t files = NI_STRING_ARRAY_INIT;
+	ni_suse_ifcfg_array_t ifcfgs = NI_SUSE_IFCFG_ARRAY_INIT;
 	ni_bool_t success = FALSE;
 	char pathbuf[PATH_MAX];
 	char *pathname = NULL;
@@ -242,28 +345,16 @@ __ni_suse_get_ifconfig(const char *root, const char *path, ni_compat_ifconfig_t
 		}
 	} else
 	if (ni_isdir(pathname)) {
+
 		if (!__ni_suse_read_globals(root, _path, pathname))
 			goto done;
 
-		if (!__ni_suse_ifcfg_scan_files(pathname, &files)) {
+		if (!ni_suse_ifcfg_read_files(&ifcfgs, pathname)) {
 			ni_debug_readwrite("No ifcfg files found in %s", pathname);
 			success = TRUE;
 			goto done;
 		}
 
-		for (i = 0; i < files.count; ++i) {
-			const char *filename = files.data[i];
-			const char *ifname = filename + (sizeof(__NI_SUSE_CONFIG_IFPREFIX)-1);
-			ni_compat_netdev_t *compat;
-
-			snprintf(pathbuf, sizeof(pathbuf), "%s/%s", pathname, filename);
-			if (!(compat = __ni_suse_read_interface(pathbuf, ifname)))
-				continue;
-
-			ni_compat_netdev_set_origin(compat, result->schema, pathbuf);
-			ni_compat_netdev_array_append(&result->netdevs, compat);
-		}
-
 		if (__ni_suse_config_defaults) {
 			extern unsigned int ni_wait_for_interfaces;
 
@@ -271,13 +362,28 @@ __ni_suse_get_ifconfig(const char *root, const char *path, ni_compat_ifconfig_t
 						"WAIT_FOR_INTERFACES",
 						&ni_wait_for_interfaces);
 		}
+
+		ni_suse_ifcfg_parse(&ifcfgs);
 	} else {
 		ni_error("Cannot use '%s' to read suse ifcfg files -- not a directory",
 				pathname);
 		goto done;
 	}
 
-	__ni_suse_adjust_slaves(&result->netdevs);
+	for (i = 0; i < ifcfgs.count; ++i) {
+		ni_suse_ifcfg_t *ifcfg = ifcfgs.data[i];
+
+		if (ifcfg->broken)
+			continue;
+
+		ni_compat_netdev_set_origin(ifcfg->compat, result->schema,
+						ifcfg->config->pathname);
+		ni_compat_netdev_array_append(&result->netdevs, ifcfg->compat);
+		ifcfg->compat = NULL;
+		ni_sysconfig_free(ifcfg->config);
+		ifcfg->config = NULL;
+	}
+
 	__ni_suse_show_unapplied_routes();
 
 	success = TRUE;
@@ -285,7 +391,7 @@ __ni_suse_get_ifconfig(const char *root, const char *path, ni_compat_ifconfig_t
 done:
 	ni_string_free(&pathname);
 	__ni_suse_free_globals();
-	ni_string_array_destroy(&files);
+	ni_suse_ifcfg_array_destroy(&ifcfgs);
 	return success;
 }
 
@@ -495,12 +601,12 @@ __ni_suse_free_globals(void)
 	ni_string_free(&__ni_suse_default_hostname);
 
 	if (__ni_suse_config_defaults) {
-		ni_sysconfig_destroy(__ni_suse_config_defaults);
+		ni_sysconfig_free(__ni_suse_config_defaults);
 		__ni_suse_config_defaults = NULL;
 	}
 
 	if (__ni_suse_dhcp_defaults) {
-		ni_sysconfig_destroy(__ni_suse_dhcp_defaults);
+		ni_sysconfig_free(__ni_suse_dhcp_defaults);
 		__ni_suse_dhcp_defaults = NULL;
 	}
 
@@ -1559,65 +1665,16 @@ error:
 	return FALSE;
 }
 
-
-/*
- * Read the configuration of a single interface from a sysconfig file
- */
-static ni_compat_netdev_t *
-__ni_suse_read_interface(const char *filename, const char *ifname)
-{
-	const char *basename = ni_basename(filename);
-	size_t pfxlen = sizeof(__NI_SUSE_CONFIG_IFPREFIX)-1;
-	ni_compat_netdev_t *compat = NULL;
-	ni_sysconfig_t *sc;
-
-	if (ni_string_len(ifname) == 0) {
-		if (!__ni_suse_ifcfg_valid_prefix(basename, __NI_SUSE_CONFIG_IFPREFIX)) {
-			ni_error("Rejecting file without '%s' prefix: %s",
-				__NI_SUSE_CONFIG_IFPREFIX, filename);
-			return NULL;
-		}
-		if (!__ni_suse_ifcfg_valid_suffix(basename, pfxlen)) {
-			ni_error("Rejecting blacklisted %sfile: %s",
-				__NI_SUSE_CONFIG_IFPREFIX, filename);
-			return NULL;
-		}
-		ifname = basename + pfxlen;
-	}
-
-	if (!ni_netdev_name_is_valid(ifname)) {
-		ni_error("Rejecting suspect interface name: %s", ifname);
-		return NULL;
-	}
-
-	if (!(sc = ni_sysconfig_read(filename)))
-		goto error;
-
-	compat = ni_compat_netdev_new(ifname);
-	if (!compat || !__ni_suse_sysconfig_read(sc, compat))
-		goto error;
-
-	ni_sysconfig_destroy(sc);
-	return compat;
-
-error:
-	if (sc)
-		ni_sysconfig_destroy(sc);
-	if (compat)
-		ni_compat_netdev_free(compat);
-	return NULL;
-}
-
 /*
  * Translate the SUSE startmodes to <control> element
  */
 static ni_ifworker_control_t *
-__ni_suse_startmode(const ni_sysconfig_t *sc)
+ni_suse_ifcfg_parse_startmode(const ni_sysconfig_t *sc)
 {
-	static const struct __ni_control_params {
+	static const struct ni_suse_control_params {
 		const char *		name;
 		ni_ifworker_control_t	control;
-	} __ni_suse_control_params[] = {
+	} control_params[] = {
 		/* manual is the default in ifcfg */
 		{ "manual",	{ "manual",	NULL,		FALSE,	FALSE,	NI_TRISTATE_DEFAULT,	0, 0 } },
 
@@ -1635,18 +1692,22 @@ __ni_suse_startmode(const ni_sysconfig_t *sc)
 
 		{ NULL }
 	};
-	const struct __ni_control_params *p, *params = NULL;
+	const struct ni_suse_control_params *p, *params = NULL;
 	ni_ifworker_control_t *control;
 	const char *mode, *value;
 
-	params = &__ni_suse_control_params[0];
-	if (sc && (mode = ni_sysconfig_get_value(sc, "STARTMODE"))) {
-		for (p = __ni_suse_control_params; p->name; ++p) {
+	params = &control_params[0];
+	if ((mode = ni_sysconfig_get_value(sc, "STARTMODE"))) {
+		for (p = control_params; p->name; ++p) {
 			if (ni_string_eq(p->name, mode)) {
 				params = p;
 				break;
 			}
 		}
+		if (!p->name) {
+			ni_warn("%s: ifcfg file with invalid STARTMODE='%s', assuming '%s'",
+					ni_basename(sc->pathname), mode, params->name);
+		}
 	}
 
 	if ((control = ni_ifworker_control_clone(&params->control))) {
@@ -1683,9 +1744,9 @@ __ni_suse_startmode(const ni_sysconfig_t *sc)
  * Try loopback interface
  */
 static int
-try_loopback(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_loopback(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 
 	/* Consider "lo" as a reserved name for loopback. */
 	if (strcmp(dev->name, "lo"))
@@ -1715,9 +1776,10 @@ __maybe_infiniband(const char *ifname)
 }
 
 static int
-try_infiniband(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_infiniband(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *umcast;
 	const char *mode;
 	const char *pkey;
@@ -2210,25 +2272,27 @@ try_add_ethtool_vars(const ni_sysconfig_t *sc, ni_netdev_t *dev, const char *suf
 	return TRUE;
 }
 
-static int
-ni_suse_ifcfg_get_ethtool(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+static ni_bool_t
+ni_suse_ifcfg_parse_ethtool(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_compat_netdev_t *compat = ifcfg->compat;
 	ni_netdev_t *dev = compat->dev;
 
 	/* process ETHTOOL_OPTIONS[SUFFIX] array */
 	if (__process_indexed_variables(sc, dev, "ETHTOOL_OPTIONS",
 					try_add_ethtool_vars) < 0) {
-		ni_error("ifcfg-%s: Cannot parse ETHTOOL_OPTIONS variables",
+		ni_warn("ifcfg-%s: Cannot parse ETHTOOL_OPTIONS variables",
 				dev->name);
-		return -1;
 	}
-	return 0;
+	return TRUE;
 }
 
 static int
-try_ethernet(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_ethernet(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *lladdr = NULL;
 
 	if (dev->link.type != NI_IFTYPE_UNKNOWN)
@@ -2257,26 +2321,27 @@ try_ethernet(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
  * Global bonding configuration is contained in BONDING_MODULE_OPTS
  */
 static ni_bool_t
-try_add_bonding_slave(const ni_sysconfig_t *sc, ni_netdev_t *dev, const char *suffix)
+try_add_bonding_port(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *port, const char *variable, const char *suffix)
 {
-	ni_bonding_t *bond;
-	ni_var_t *var;
+	ni_suse_ifcfg_t *pcfg;
+	ni_netdev_t *mdev;
+	const char *type;
 
-	var = __find_indexed_variable(sc, "BONDING_SLAVE", suffix);
-	if (!var || ni_string_empty(var->value))
+	mdev = ifcfg->compat->dev;
+	type = ni_linktype_type_to_name(mdev->link.type);
+	if (!(pcfg = ni_suse_ifcfg_add_l2_port(ifcfgs,
+			ifcfg, mdev->name, type, port, FALSE))) {
+		ni_error("ifcfg-%s: failed to add %s port %s='%s'",
+				mdev->name, type, variable, port);
 		return FALSE;
+	}
 
-	dev->link.type = NI_IFTYPE_BOND;
-
-	if ((bond = ni_netdev_get_bonding(dev)) == NULL)
-		return FALSE;
+	ni_netdev_port_config_destroy(&pcfg->compat->port);
+	ni_netdev_port_config_init(&pcfg->compat->port, mdev->link.type);
+	/* no bond port specific config variables implemented yet */
 
-	if (ni_bonding_has_slave(bond, var->value)) {
-		ni_warn("ifcfg-%s: Duplicate slave in BONDING_SLAVE%s=''%s'",
-				dev->name, suffix, var->value);
-		return TRUE; /* warn without to fail */
-	}
-	return ni_bonding_add_slave(bond, var->value) != NULL;
+	return TRUE;
 }
 
 static ni_bool_t
@@ -2318,9 +2383,10 @@ try_set_bonding_options(ni_netdev_t *dev, const char *options)
 }
 
 static int
-try_bonding(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_bonding(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *module_opts, *err;
 	const char *lladdr;
 	ni_bool_t enabled;
@@ -2337,10 +2403,6 @@ try_bonding(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	dev->link.type = NI_IFTYPE_BOND;
 	(void)ni_netdev_get_bonding(dev);
 
-	if (__process_indexed_variables(sc, dev, "BONDING_SLAVE",
-					try_add_bonding_slave) != 0)
-		return -1;
-
 	if ((module_opts = ni_sysconfig_get_value(sc, "BONDING_MODULE_OPTS")) != NULL) {
 		if (!try_set_bonding_options(dev, module_opts))
 			return -1;
@@ -2361,6 +2423,13 @@ try_bonding(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		}
 	}
 
+	if (!ni_suse_ifcfg_process_indexed(ifcfgs, ifcfg, "BONDING_SLAVE",
+			try_add_bonding_port)) {
+		ni_error("ifcfg-%s: invalid bonding without BONDING_SLAVEs",
+				dev->name);
+		return -1;
+	}
+
 	return 0;
 }
 
@@ -2558,76 +2627,98 @@ failure:
 }
 
 static ni_bool_t
-try_add_team_port(const ni_sysconfig_t *sc, ni_netdev_t *dev, const char *suffix)
+try_add_team_port_config(ni_team_port_config_t *pconf, const ni_sysconfig_t *sc,
+		const char *suffix, const char *file)
 {
-	ni_team_port_t *port;
-	ni_team_t *team;
+	ni_bool_t ret = TRUE;
 	ni_var_t *var;
 
-	if (!(team = ni_netdev_get_team(dev)))
-		return FALSE;
-
-	var = __find_indexed_variable(sc, "TEAM_PORT_DEVICE", suffix);
-	if (!var || ni_string_empty(var->value)) {
-		ni_error("ifcfg-%s: TEAM_PORT_DEVICE%s cannot be empty",
-			dev->name, suffix);
-		return FALSE;
-	}
-
-	port = ni_team_port_new();
-	ni_netdev_ref_set_ifname(&port->device, var->value);
-
 	if ((var = __find_indexed_variable(sc, "TEAM_PORT_QUEUE_ID", suffix))) {
-		if (ni_parse_uint(var->value, &port->config.queue_id, 10) < 0) {
-			ni_error("ifcfg-%s: Cannot parse TEAM_PORT_QUEUE_ID%s='%s'",
-				dev->name, suffix, var->value);
-			ni_team_port_free(port);
-			return FALSE;
+		if (ni_parse_uint(var->value, &pconf->queue_id, 10) < 0) {
+			ni_warn("ifcfg-%s: Cannot parse TEAM_PORT_QUEUE_ID%s='%s'",
+				file, suffix, var->value);
+			ret = FALSE;
 		}
 	}
 
 	if ((var = __find_indexed_variable(sc, "TEAM_PORT_PRIO", suffix))) {
-		if (ni_parse_uint(var->value, &port->config.ab.prio, 10) < 0) {
-			ni_error("ifcfg-%s: Cannot parse TEAM_PORT_PRIO%s='%s'",
-				dev->name, suffix, var->value);
-			ni_team_port_free(port);
-			return FALSE;
+		if (ni_parse_uint(var->value, &pconf->ab.prio, 10) < 0) {
+			ni_warn("ifcfg-%s: Cannot parse TEAM_PORT_PRIO%s='%s'",
+				file, suffix, var->value);
+			ret = FALSE;
 		}
 	}
+
 	if ((var = __find_indexed_variable(sc, "TEAM_PORT_STICKY", suffix))) {
-		if (ni_parse_boolean(var->value, &port->config.ab.sticky) < 0) {
-			ni_error("ifcfg-%s: Cannot parse TEAM_PORT_STICKY%s='%s'",
-				dev->name, suffix, var->value);
-			ni_team_port_free(port);
-			return FALSE;
+		if (ni_parse_boolean(var->value, &pconf->ab.sticky) < 0) {
+			ni_warn("ifcfg-%s: Cannot parse TEAM_PORT_STICKY%s='%s'",
+				file, suffix, var->value);
+			ret = FALSE;
 		}
 	}
 
 	if ((var = __find_indexed_variable(sc, "TEAM_PORT_LACP_PRIO", suffix))) {
-		if (ni_parse_uint(var->value, &port->config.lacp.prio, 10) < 0) {
-			ni_error("ifcfg-%s: Cannot parse TEAM_PORT_LACP_PRIO%s='%s'",
-				dev->name, suffix, var->value);
-			ni_team_port_free(port);
-			return FALSE;
+		if (ni_parse_uint(var->value, &pconf->lacp.prio, 10) < 0) {
+			ni_warn("ifcfg-%s: Cannot parse TEAM_PORT_LACP_PRIO%s='%s'",
+				file, suffix, var->value);
+			ret = FALSE;
 		}
 	}
 
 	if ((var = __find_indexed_variable(sc, "TEAM_PORT_LACP_KEY", suffix))) {
-		if (ni_parse_uint(var->value, &port->config.lacp.key, 10) < 0) {
-			ni_error("ifcfg-%s: Cannot parse TEAM_PORT_LACP_KEY%s='%s'",
-				dev->name, suffix, var->value);
-			ni_team_port_free(port);
-			return FALSE;
+		if (ni_parse_uint(var->value, &pconf->lacp.key, 10) < 0) {
+			ni_warn("ifcfg-%s: Cannot parse TEAM_PORT_LACP_KEY%s='%s'",
+				file, suffix, var->value);
+			ret = FALSE;
+		}
+	}
+
+	return ret;
+}
+
+static ni_bool_t
+try_add_team_port(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *port, const char *variable, const char *suffix)
+{
+	ni_suse_ifcfg_t *pcfg;
+	ni_netdev_t *mdev;
+	ni_team_t *team;
+	ni_bool_t l2v6 = FALSE;
+	const char *type;
+
+	mdev = ifcfg->compat->dev;
+	if ((team = ni_netdev_get_team(mdev))) {
+		ni_team_link_watch_t *lw;
+		unsigned int i;
+
+		for (i = 0; i < team->link_watch.count; i++) {
+			lw = team->link_watch.data[i];
+			if (lw->type == NI_TEAM_LINK_WATCH_NSNA_PING)
+				l2v6 = TRUE;
 		}
 	}
+	type = ni_linktype_type_to_name(mdev->link.type);
+	if (!(pcfg = ni_suse_ifcfg_add_l2_port(ifcfgs,
+			ifcfg, mdev->name, type, port, l2v6))) {
+		ni_error("ifcfg-%s: failed to add %s port %s='%s'",
+				mdev->name, type, variable, port);
+		return FALSE;
+	}
 
-	return ni_team_port_array_append(&team->ports, port);
+	ni_netdev_port_config_destroy(&pcfg->compat->port);
+	if (ni_netdev_port_config_init(&pcfg->compat->port, NI_IFTYPE_TEAM)) {
+		try_add_team_port_config(pcfg->compat->port.team,
+				ifcfg->config, suffix, mdev->name);
+	}
+
+	return TRUE;
 }
 
 static int
-try_team(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_team(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *value;
 	ni_team_runner_type_t type;
 	ni_team_t *team;
@@ -2866,10 +2957,6 @@ try_team(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 					try_add_team_link_watch) < 0)
 		return -1;
 
-	if (__process_indexed_variables(sc, dev, "TEAM_PORT_DEVICE",
-					try_add_team_port) < 0)
-		return -1;
-
 #if 0
 	if ((err = ni_team_validate(ni_netdev_get_team(dev))) != NULL) {
 		ni_error("ifcfg-%s: team validation: %s",
@@ -2878,41 +2965,55 @@ try_team(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	}
 #endif
 
+	if (!ni_suse_ifcfg_process_indexed(ifcfgs, ifcfg, "TEAM_PORT_DEVICE",
+			try_add_team_port)) {
+		ni_error("ifcfg-%s: invalid bonding without TEAM_PORT_DEVICEs",
+				dev->name);
+		return -1;
+	}
+
 	return 0;
 }
 
 static ni_bool_t
-try_add_ovs_bridge_port(const ni_sysconfig_t *sc, ni_netdev_t *dev, const char *suffix)
+try_add_ovs_bridge_port(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *port, const char *variable, const char *suffix)
 {
-	const ni_var_t *var;
+	static const char *ovs_system = NULL;
+	ni_suse_ifcfg_t *pcfg;
+	ni_netdev_t *bdev;
+	const char *type;
 
-	if (!dev->ovsbr)
-		return FALSE;
+	if (!ovs_system)
+		ovs_system = ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM);
 
-	var = __find_indexed_variable(sc, "OVS_BRIDGE_PORT_DEVICE", suffix);
-	if (!var || !ni_netdev_name_is_valid(var->value)) {
-		size_t len;
-		if (var && (len = ni_string_len(var->value))) {
-			ni_error("ifcfg-%s: Suspect device in OVS_BRIDGE_PORT_DEVICE%s='%s'",
-					dev->name, suffix, ni_print_suspect(var->value, len));
-		} else {
-			ni_error("ifcfg-%s: OVS_BRIDGE_PORT_DEVICE%s cannot be empty",
-					dev->name, suffix);
-		}
+	bdev = ifcfg->compat->dev;
+	if (ni_string_eq(ovs_system, port)) {
+		ni_error("ifcfg-%s: reserved ovs datapath in %s='%s'",
+				bdev->name, variable, port);
 		return FALSE;
 	}
 
-	if (!ni_ovs_bridge_port_array_add_new(&dev->ovsbr->ports, var->value)) {
-		ni_warn("ifcfg-%s: Cannot add OVS_BRIDGE_PORT_DEVICE%s='%s' or not unique, skipped",
-				dev->name, suffix, var->value);
+	type = ni_linktype_type_to_name(bdev->link.type);
+	if (!(pcfg = ni_suse_ifcfg_add_l2_port(ifcfgs,
+			ifcfg, bdev->name, type, port, FALSE))) {
+		ni_error("ifcfg-%s: failed to add %s port %s='%s'",
+				bdev->name, type, variable, port);
+		return FALSE;
 	}
+
+	ni_netdev_port_config_destroy(&pcfg->compat->port);
+	ni_netdev_port_config_init(&pcfg->compat->port, bdev->link.type);
+	/* no ovs bridge port specific config variables (any more) */
+
 	return TRUE;
 }
 
 static int
-try_ovs_bridge(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_ovs_bridge(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_ovs_bridge_t *ovsbr;
 	ni_bool_t enabled;
 	const char *parent;
@@ -2929,14 +3030,25 @@ try_ovs_bridge(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	}
 
 	dev->link.type = NI_IFTYPE_OVS_BRIDGE;
-	ovsbr = ni_netdev_get_ovs_bridge(dev);
+	if (!(ovsbr = ni_netdev_get_ovs_bridge(dev))) {
+		ni_error("ifcfg-%s: unable to allocate ovs bridge structure",
+				dev->name);
+		return -1;
+	}
 
 	if ((parent = ni_sysconfig_get_value(sc, "OVS_BRIDGE_VLAN_PARENT"))) {
 		if (!ni_netdev_name_is_valid(parent)) {
 			ni_error("ifcfg-%s: Suspect device in OVS_BRIDGE_VLAN_PARENT='%s'",
-					dev->name, ni_print_suspect(parent, ni_string_len(parent)));
+					dev->name,
+					ni_print_suspect(parent, ni_string_len(parent)));
+			return -1;
+		}
+		if (!ni_suse_ifcfg_find_by_ifname(ifcfgs, parent)) {
+			ni_error("ifcfg-%s: Unable to find OVS vlan bridge parent config '%s'",
+					dev->name, parent);
 			return -1;
 		}
+
 		if (!(vlan = ni_sysconfig_get_value(sc, "OVS_BRIDGE_VLAN_TAG"))) {
 			ni_error("ifcfg-%s: OVS_BRIDGE_VLAN_TAG=... missed", dev->name);
 			return -1;
@@ -2951,39 +3063,47 @@ try_ovs_bridge(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 					dev->name, ovsbr->config.vlan.tag, __NI_VLAN_TAG_MAX);
 			return -1;
 		}
+
 		ni_netdev_ref_set_ifname(&ovsbr->config.vlan.parent, parent);
 		ovsbr->config.vlan.tag = tag;
 	}
 
-	if (__process_indexed_variables(sc, dev, "OVS_BRIDGE_PORT_DEVICE",
-					try_add_ovs_bridge_port) < 0)
-		return -1;
+	ni_suse_ifcfg_process_indexed(ifcfgs, ifcfg, "OVS_BRIDGE_PORT_DEVICE",
+			try_add_ovs_bridge_port);
 
 	return 0;
 }
 
 static int
-try_ovs_system(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_ovs_system(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	static const char *ovs_system = NULL;
-	ni_netdev_t *dev = compat->dev;
 
-	/* Consider ovs-system as a fixed/reserved master device for openvswitch */
+	/* Consider ovs-system as a fixed/reserved datapath name for openvswitch */
 	if (ovs_system == NULL)
 		ovs_system = ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM);
 
-	if (strcmp(dev->name, ovs_system))
+	if (!ni_string_eq(dev->name, ovs_system))
 		return 1;
 
-	if (dev->link.type != NI_IFTYPE_UNKNOWN) {
+	switch (dev->link.type) {
+	case NI_IFTYPE_UNKNOWN:
+		dev->link.type = NI_IFTYPE_OVS_SYSTEM;
+		/*
+		 * This datapath device does not need any setup (not even link up),
+		 * but as it is actively used as master device for all ovs ports,
+		 * we have to consider it in order to resolve dependencies...
+		 */
+		ni_suse_ifcfg_set_l2_only(ifcfg, FALSE);
+		return 0;
+	case NI_IFTYPE_OVS_SYSTEM:
+		return 0;
+	default:
 		ni_error("ifcfg-%s: %s config is using reserved %s device name",
 			dev->name, ni_linktype_type_to_name(dev->link.type), ovs_system);
 		return -1;
 	}
-
-	dev->link.type = NI_IFTYPE_OVS_SYSTEM;
-	__ni_suse_adjust_ovs_system(compat);
-
 	return 0;
 }
 
@@ -2991,10 +3111,101 @@ try_ovs_system(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 /*
  * Bridge devices are recognized by BRIDGE=yes
  */
+static ni_bool_t
+try_add_bridge_port_config(ni_bridge_port_config_t *pconf, const char *file,
+		const char *port, const char *prio, const char *cost)
+{
+	unsigned int tmp;
+	const char *err;
+
+	if (ni_string_empty(prio) || ni_string_eq(prio, "-")) {
+		pconf->priority = NI_BRIDGE_VALUE_NOT_SET;
+	} else if (ni_parse_uint(prio, &tmp, 0) < 0) {
+		ni_warn("ifcfg-%s: unable to parse priority '%s' for port '%s'",
+				file, prio, port);
+	} else if ((err = ni_bridge_port_priority_validate(tmp))) {
+		ni_warn("ifcfg-%s: %s '%s' for port '%s'",
+				file, err, prio, port);
+	} else {
+		pconf->priority = tmp;
+	}
+
+	if (ni_string_empty(cost) || ni_string_eq(cost, "-")) {
+		pconf->path_cost = NI_BRIDGE_VALUE_NOT_SET;
+	} else if (ni_parse_uint(cost, &tmp, 0) < 0) {
+		ni_warn("ifcfg-%s: unable to parse path-cost '%s' for port '%s'",
+				file, cost, port);
+	} else if ((err = ni_bridge_port_path_cost_validate(tmp))) {
+		ni_warn("ifcfg-%s: %s '%s' for port '%s'",
+				file, err, cost, port);
+	} else {
+		pconf->path_cost = tmp;
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+try_add_bridge_port(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *port, const char *prio, const char *cost)
+{
+	ni_suse_ifcfg_t *pcfg;
+	ni_netdev_t *mdev;
+	const char *type;
+
+	mdev = ifcfg->compat->dev;
+	type = ni_linktype_type_to_name(mdev->link.type);
+	if (!(pcfg = ni_suse_ifcfg_add_l2_port(ifcfgs,
+			ifcfg, mdev->name, type, port, FALSE))) {
+		ni_error("ifcfg-%s: failed to add %s port %s",
+				mdev->name, type, port);
+		return FALSE;
+	}
+
+	ni_netdev_port_config_destroy(&pcfg->compat->port);
+	if (ni_netdev_port_config_init(&pcfg->compat->port, mdev->link.type)) {
+		try_add_bridge_port_config(pcfg->compat->port.bridge,
+				mdev->name, port, prio, cost);
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+try_add_bridge_ports(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
+{
+	ni_string_array_t ports = NI_STRING_ARRAY_INIT;
+	ni_string_array_t prios = NI_STRING_ARRAY_INIT;
+	ni_string_array_t costs = NI_STRING_ARRAY_INIT;
+	ni_bool_t ret = TRUE;
+	unsigned int i;
+
+	ni_string_split(&ports, ni_sysconfig_get_value(ifcfg->config,
+				"BRIDGE_PORTS"), " \t", 0);
+	ni_string_split(&prios, ni_sysconfig_get_value(ifcfg->config,
+				"BRIDGE_PORTPRIORITIES"), " \t", 0);
+	ni_string_split(&costs, ni_sysconfig_get_value(ifcfg->config,
+				"BRIDGE_PATHCOSTS"), " \t", 0);
+
+	for (i = 0; i < ports.count; ++i) {
+		if (!try_add_bridge_port(ifcfgs, ifcfg,
+				ni_string_array_at(&ports, i),
+				ni_string_array_at(&prios, i),
+				ni_string_array_at(&costs, i)))
+			ret = FALSE;
+	}
+
+	ni_string_array_destroy(&ports);
+	ni_string_array_destroy(&prios);
+	ni_string_array_destroy(&costs);
+	return ret;
+}
+
 static int
-try_bridge(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_bridge(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_bridge_t *bridge;
 	ni_bool_t enabled;
 	const char *value;
@@ -3063,77 +3274,6 @@ try_bridge(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		}
 	}
 
-	if ((value = ni_sysconfig_get_value(sc, "BRIDGE_PORTS")) != NULL) {
-		char *portnames = NULL, *name_pos = NULL, *name = NULL;
-
-		ni_string_dup(&portnames, value);
-		for (name = strtok_r(portnames, " \t", &name_pos);
-		     name != NULL;
-		     name = strtok_r(NULL, " \t", &name_pos)) {
-
-			if (!ni_netdev_name_is_valid(name)) {
-				ni_error("ifcfg-%s: BRIDGE_PORTS='%s' "
-					 "rejecting suspect port name '%s'",
-					 dev->name, value, name);
-				free(portnames);
-				return -1;
-			}
-
-			ni_bridge_port_new(bridge, name, 0);
-		}
-		ni_string_free(&portnames);
-	}
-
-	if ((value = ni_sysconfig_get_value(sc, "BRIDGE_PORTPRIORITIES")) != NULL) {
-		char *portprios = NULL, *prio_pos = NULL, *prio = NULL;
-		unsigned int tmp, i = 0;
-
-		ni_string_dup(&portprios, value);
-		for (prio = strtok_r(portprios, " \t", &prio_pos);
-		     prio != NULL && i < bridge->ports.count;
-		     prio = strtok_r(NULL, " \t", &prio_pos), ++i) {
-			ni_bridge_port_t *port = bridge->ports.data[i];
-
-			if (!strcmp("-", prio))
-				continue;
-
-			if (ni_parse_uint(prio, &tmp, 0) < 0) {
-				ni_error("ifcfg-%s: BRIDGE_PORTPRIORITIES='%s' "
-					 "unable to parse port '%s' priority '%s'",
-					 dev->name, value, port->ifname, prio);
-				free(portprios);
-				return -1;
-			}
-			port->priority = tmp;
-		}
-		ni_string_free(&portprios);
-	}
-
-	if ((value = ni_sysconfig_get_value(sc, "BRIDGE_PATHCOSTS")) != NULL) {
-		char *portcosts = NULL, *cost_pos = NULL, *cost = NULL;
-		unsigned int tmp, i = 0;
-
-		ni_string_dup(&portcosts, value);
-		for (cost = strtok_r(portcosts, " \t", &cost_pos);
-		     cost != NULL && i < bridge->ports.count;
-		     cost = strtok_r(NULL, " \t", &cost_pos), ++i) {
-			ni_bridge_port_t *port = bridge->ports.data[i++];
-
-			if (!strcmp("-", cost))
-				continue;
-
-			if (ni_parse_uint(cost, &tmp, 0) < 0) {
-				ni_error("ifcfg-%s: BRIDGE_PATHCOSTS='%s' "
-					 "unable to parse port '%s' costs '%s'",
-					 dev->name, value, port->ifname, cost);
-				free(portcosts);
-				return -1;
-			}
-			port->path_cost = tmp;
-		}
-		ni_string_free(&portcosts);
-	}
-
 	if ((value = ni_bridge_validate(bridge)) != NULL) {
 		ni_error("ifcfg-%s: bridge validation: %s", dev->name, value);
 		return -1;
@@ -3147,6 +3287,8 @@ try_bridge(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		}
 	}
 
+	try_add_bridge_ports(ifcfgs, ifcfg);
+
 	return 0;
 }
 
@@ -3154,9 +3296,10 @@ try_bridge(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
  * VLAN interfaces are recognized by their name (vlan<N>)
  */
 static int
-try_vlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_vlan(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_vlan_t *vlan;
 	const char *etherdev = NULL;
 	const char *vlanprot = NULL;
@@ -3165,7 +3308,8 @@ try_vlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	unsigned int tag = 0;
 	size_t len;
 
-	if ((etherdev = ni_sysconfig_get_value(sc, "ETHERDEVICE")) == NULL)
+	etherdev = ni_sysconfig_get_value(sc, "ETHERDEVICE");
+	if (ni_string_empty(etherdev))
 		return 1;
 
 	if (dev->link.type != NI_IFTYPE_UNKNOWN) {
@@ -3175,11 +3319,9 @@ try_vlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	}
 
 	dev->link.type = NI_IFTYPE_VLAN;
-	vlan = ni_netdev_get_vlan(dev);
-
-	if (!strcmp(dev->name, etherdev)) {
-		ni_error("ifcfg-%s: ETHERDEVICE=\"%s\" self-reference",
-			dev->name, etherdev);
+	if (!(vlan = ni_netdev_get_vlan(dev))) {
+		ni_error("ifcfg-%s: unable to allocate ethernet vlan structure",
+				dev->name);
 		return -1;
 	}
 
@@ -3227,6 +3369,9 @@ try_vlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		vlan->protocol = protocol;
 	}
 
+	if (!ni_suse_ifcfg_add_lower(ifcfgs, ifcfg, etherdev, "vlan ETHERDEVICE"))
+		return -1;
+
 	ni_string_dup(&dev->link.lowerdev.name, etherdev);
 	vlan->tag = tag;
 
@@ -3242,9 +3387,10 @@ try_vlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 }
 
 static int
-try_vxlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_vxlan(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_vxlan_t *vxlan;
 	ni_bool_t enabled;
 	unsigned int val;
@@ -3271,11 +3417,9 @@ try_vxlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		}
 	}
 	if ((str = ni_sysconfig_get_value(sc, "VXLAN_DEVICE"))) {
-		if (!ni_netdev_name_is_valid(str) || ni_string_eq(str, dev->name)) {
-			ni_error("ifcfg-%s: Invalid name in VXLAN_DEVICE=\"%s\"",
-					dev->name, ni_print_suspect(str, 15));
+		if (!ni_suse_ifcfg_add_lower(ifcfgs, ifcfg, str, "VXLAN_DEVICE"))
 			return -1;
-		}
+
 		ni_string_dup(&dev->link.lowerdev.name, str);
 	}
 
@@ -3469,9 +3613,10 @@ try_vxlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
  * differ from the macvlan/tap device being created.
  */
 static int
-try_macvlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_macvlan(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_macvlan_t *macvlan = NULL;
 	const char *macvlan_dev = NULL;
 	unsigned int macvlan_iftype = NI_IFTYPE_UNKNOWN;
@@ -3512,11 +3657,8 @@ try_macvlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		return -1;
 	}
 
-	if (!strcmp(dev->name, macvlan_dev)) {
-		ni_error("ifcfg-%s: %s=\"%s\" self-reference",
-			dev->name, syscfg_dev_key, macvlan_dev);
+	if (!ni_suse_ifcfg_add_lower(ifcfgs, ifcfg, macvlan_dev, syscfg_dev_key))
 		return -1;
-	}
 
 	macvlan->mode = NI_MACVLAN_MODE_VEPA;
         if ((macvlan_mode = ni_sysconfig_get_value(sc, syscfg_mode_key)) != NULL) {
@@ -3569,9 +3711,10 @@ try_macvlan(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
  * itself.
  */
 static int
-try_dummy(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_dummy(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *iftype = NULL;
 	const char *lladdr = NULL;
 	const char *bootproto = NULL;
@@ -4361,9 +4504,10 @@ eap_failure:
 }
 
 static int
-try_wireless(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_wireless(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_string_array_t vnames = NI_STRING_ARRAY_INIT;
 	ni_bool_t enabled = FALSE;
 	const char *value;
@@ -4415,20 +4559,17 @@ __ni_suse_read_provider(const char *sibling, const char *provider)
 }
 
 static int
-try_pppoe(const ni_sysconfig_t *sc, const ni_sysconfig_t *psc, const char *name, ni_ppp_mode_pppoe_t *pppoe)
+try_pppoe(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		ni_ppp_mode_pppoe_t *pppoe)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
 	const char *value;
 
-	if (!sc || !psc || !pppoe || ni_string_empty(name))
-		return -1;
-
 	value = ni_sysconfig_get_value(sc, "DEVICE");
-	if (ni_string_empty(value) || !ni_netdev_name_is_valid(value) ||
-	    !ni_netdev_ref_set_ifname(&pppoe->device, value)) {
-		ni_error("ifcfg-%s: PPPoE config without valid ethernet device name: '%s'", name, value ? value : "");
+	if (!ni_suse_ifcfg_add_lower(ifcfgs, ifcfg, value, "PPPoE ethernet device"))
 		return -1;
-	}
 
+	ni_netdev_ref_set_ifname(&pppoe->device, value);
 	return 0;
 }
 
@@ -4436,9 +4577,10 @@ try_pppoe(const ni_sysconfig_t *sc, const ni_sysconfig_t *psc, const char *name,
  * Handle all sorts of PPP
  */
 static int
-try_ppp(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_ppp(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_sysconfig_t *psc = NULL;
 	const char *value;
 	ni_bool_t bval;
@@ -4586,7 +4728,7 @@ try_ppp(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	ret = 0;
 	switch (ppp->mode.type) {
 	case NI_PPP_MODE_PPPOE:
-		ret = try_pppoe(sc, psc, dev->name, &ppp->mode.pppoe);
+		ret = try_pppoe(ifcfgs, ifcfg, &ppp->mode.pppoe);
 		break;
 	case NI_PPP_MODE_PPPOATM:
 		/* PPPoATM support is not implemented */
@@ -4607,8 +4749,7 @@ try_ppp(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	}
 
 done:
-	if (psc)
-		ni_sysconfig_destroy(psc);
+	ni_sysconfig_free(psc);
 	return ret;
 }
 
@@ -4616,9 +4757,10 @@ done:
  * Handle Tunnel interfaces
  */
 static int
-__try_tunnel_tuntap(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel_tuntap(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *value;
 	ni_tuntap_t *tuntap;
 
@@ -4662,24 +4804,18 @@ __try_tunnel_tuntap(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 }
 
 static int
-__try_tunnel_generic(const char *ifname, unsigned short arp_type,
-		ni_linkinfo_t *link, ni_tunnel_t *tunnel,
-		const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel_generic(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *ifname, unsigned short arp_type,
+		ni_linkinfo_t *link, ni_tunnel_t *tunnel)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
 	const char *value = NULL;
 	unsigned int ui_value;
 
 	if ((value = ni_sysconfig_get_value(sc, "TUNNEL_DEVICE"))) {
-		if (!ni_netdev_name_is_valid(value)) {
-			ni_error("ifcfg-%s: TUNNEL_DEVICE=\"%s\" suspect interface name",
-					ifname, value);
+		if (!ni_suse_ifcfg_add_lower(ifcfgs, ifcfg, value, "TUNNEL_DEVICE"))
 			return -1;
-		}
-		if (ni_string_eq(value, ifname)) {
-			ni_error("ifcfg-%s: TUNNEL_DEVICE=\"%s\" invalid self-reference",
-					ifname, value);
-			return -1;
-		}
+
 		ni_string_dup(&link->lowerdev.name, value);
 	}
 
@@ -4737,9 +4873,9 @@ __try_tunnel_generic(const char *ifname, unsigned short arp_type,
 }
 
 static int
-__try_tunnel_ipip(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel_ipip(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_ipip_t *ipip = NULL;
 	int rv = 0;
 
@@ -4747,17 +4883,18 @@ __try_tunnel_ipip(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		return -1;
 
 	/* Populate generic tunneling data from config. */
-	rv = __try_tunnel_generic(dev->name, ARPHRD_TUNNEL, &dev->link,
-				&ipip->tunnel, sc, compat);
+	rv = try_tunnel_generic(ifcfgs, ifcfg, dev->name, ARPHRD_TUNNEL,
+				&dev->link, &ipip->tunnel);
 
 	return rv;
 }
 
 static int
-__try_tunnel_gre(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel_gre(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
 	ni_string_array_t flags = NI_STRING_ARRAY_INIT;
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_gre_t *gre = NULL;
 	ni_sockaddr_t addr;
 	const char *value;
@@ -4768,8 +4905,8 @@ __try_tunnel_gre(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		return -1;
 
 	/* Populate generic tunneling data from config. */
-	rv = __try_tunnel_generic(dev->name, ARPHRD_IPGRE, &dev->link,
-				&gre->tunnel, sc, compat);
+	rv = try_tunnel_generic(ifcfgs, ifcfg, dev->name, ARPHRD_IPGRE,
+				&dev->link, &gre->tunnel);
 
 	if ((value = ni_sysconfig_get_value(sc, "TUNNEL_GRE_FLAGS"))) {
 		ni_string_split(&flags, value, " \t", 0);
@@ -4860,9 +4997,10 @@ __try_tunnel_gre(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 }
 
 static int
-__try_tunnel_sit(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel_sit(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_sit_t *sit = NULL;
 	const char *value = NULL;
 	int rv = 0;
@@ -4871,8 +5009,8 @@ __try_tunnel_sit(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		return -1;
 
 	/* Populate generic tunneling data from config. */
-	rv = __try_tunnel_generic(dev->name, ARPHRD_SIT, &dev->link,
-				&sit->tunnel, sc, compat);
+	rv = try_tunnel_generic(ifcfgs, ifcfg, dev->name, ARPHRD_SIT,
+				&dev->link, &sit->tunnel);
 
 	if ((value = ni_sysconfig_get_value(sc, "SIT_ISATAP"))) {
 		if (ni_parse_boolean(value, &sit->isatap) < 0) {
@@ -4886,11 +5024,12 @@ __try_tunnel_sit(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 }
 
 static int
-try_tunnel(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+try_tunnel(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_netdev_t *dev = compat->dev;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	const char *value;
-	static const ni_intmap_t __tunnel_types[] = {
+	static const ni_intmap_t tunnel_types[] = {
 		{ "tun",	NI_IFTYPE_TUN		},
 		{ "tap",	NI_IFTYPE_TAP		},
 		{ "sit",	NI_IFTYPE_SIT		},
@@ -4904,7 +5043,7 @@ try_tunnel(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	if ((value = ni_sysconfig_get_value(sc, "TUNNEL")) == NULL)
 		return 1;
 
-	for (map = __tunnel_types; map->name; ++map) {
+	for (map = tunnel_types; map->name; ++map) {
 		if (!strcmp(map->name, value))
 			break;
 	}
@@ -4924,16 +5063,16 @@ try_tunnel(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	switch (dev->link.type) {
 	case NI_IFTYPE_TUN:
 	case NI_IFTYPE_TAP:
-		return __try_tunnel_tuntap(sc, compat);
+		return try_tunnel_tuntap(ifcfgs, ifcfg);
 
 	case NI_IFTYPE_IPIP:
-		return __try_tunnel_ipip(sc, compat);
+		return try_tunnel_ipip(ifcfgs, ifcfg);
 
 	case NI_IFTYPE_GRE:
-		return __try_tunnel_gre(sc, compat);
+		return try_tunnel_gre(ifcfgs, ifcfg);
 
 	case NI_IFTYPE_SIT:
-		return __try_tunnel_sit(sc, compat);
+		return try_tunnel_sit(ifcfgs, ifcfg);
 
 	default:
 		ni_warn("ifcfg-%s: conversion of %s tunnels not yet supported",
@@ -5760,7 +5899,7 @@ __ni_suse_addrconf_dhcp4(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat, n
 	config = ni_config_dhcp4_find_device(dev->name);
 	if ((merged = ni_sysconfig_merge_defaults(sc, __ni_suse_dhcp_defaults))) {
 		__ni_suse_addrconf_dhcp4_options(merged, compat, config);
-		ni_sysconfig_destroy(merged);
+		ni_sysconfig_free(merged);
 	}
 
 	compat->dhcp4.enabled = TRUE;
@@ -5789,7 +5928,7 @@ __ni_suse_addrconf_dhcp6(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat, n
 	config = ni_config_dhcp6_find_device(dev->name);
 	if ((merged = ni_sysconfig_merge_defaults(sc, __ni_suse_dhcp_defaults))) {
 		__ni_suse_addrconf_dhcp6_options(merged, compat, config);
-		ni_sysconfig_destroy(merged);
+		ni_sysconfig_free(merged);
 	}
 
 	compat->dhcp6.enabled = TRUE;
@@ -5874,7 +6013,7 @@ __ni_suse_addrconf_auto6(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 					dev->name, ni_print_suspect(value, ni_string_len(value)));
 			}
 		}
-		ni_sysconfig_destroy(merged);
+		ni_sysconfig_free(merged);
 	}
 	return TRUE;
 
@@ -5885,8 +6024,10 @@ ignored:
 
 
 static ni_bool_t
-__ni_suse_bootproto(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+ni_suse_ifcfg_parse_bootproto(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_compat_netdev_t *compat = ifcfg->compat;
 	ni_netdev_t *dev = compat->dev;
 	ni_bool_t primary;
 	const char *bootproto;
@@ -5895,7 +6036,21 @@ __ni_suse_bootproto(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	ni_ipv4_devinfo_t *ipv4;
 	ni_ipv6_devinfo_t *ipv6;
 
+	/*
+	 * The L3 setup & boot protocols are disabled on L2 ports already.
+	 */
+	if (ifcfg->l2only)
+		return TRUE;
+
+	/*
+	 * The BOOTPROTO=none disables the L3 (IP) setup & boot protocols.
+	 */
 	bootproto = ni_sysconfig_get_value(sc, "BOOTPROTO");
+	if (ni_string_eq_nocase(bootproto, "none")) {
+		ni_suse_ifcfg_set_l2_only(ifcfg, FALSE);
+		return TRUE;
+	}
+
 	if (ni_string_empty(bootproto) || ni_string_eq(dev->name, "lo")) {
 		if (dev->link.type == NI_IFTYPE_PPP)
 			bootproto = "ppp";
@@ -5904,16 +6059,6 @@ __ni_suse_bootproto(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	}
 
 	ipv4 = ni_netdev_get_ipv4(dev);
-	ipv6 = ni_netdev_get_ipv6(dev);
-
-	if (dev->link.masterdev.name || ni_string_eq_nocase(bootproto, "none")) {
-		if (ipv4)
-			ni_tristate_set(&ipv4->conf.enabled, FALSE);
-		if (ipv6)
-			ni_tristate_set(&ipv6->conf.enabled, FALSE);
-		return TRUE;
-	}
-
 	if (ipv4 && !ni_tristate_is_disabled(ipv4->conf.enabled)) {
 		if (__ni_suse_config_defaults) {
 			if ((value = ni_sysconfig_get_value(__ni_suse_config_defaults,
@@ -5935,17 +6080,13 @@ __ni_suse_bootproto(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 		}
 	}
 
-	if (dev->link.type == NI_IFTYPE_PPP && dev->ppp && !ipv6->conf.enabled)
+	ipv6 = ni_netdev_get_ipv6(dev);
+	if (ipv6 && dev->link.type == NI_IFTYPE_PPP && dev->ppp && !ipv6->conf.enabled)
 		dev->ppp->config.ipv6.enabled = FALSE;
 
 	if (ni_string_eq_nocase(bootproto, "ppp"))
 		return TRUE;
 
-	/* Hmm... ignore this config completely -> ibft firmware */
-	if (ni_string_eq_nocase(bootproto, "ibft")) {
-		return TRUE;
-	}
-
 	if (ni_string_eq_nocase(bootproto, "6to4")) {
 		__ni_suse_addrconf_static(sc, compat);
 		return TRUE;
@@ -6192,9 +6333,11 @@ __ni_suse_qualify_scripts(ni_compat_netdev_t *compat, const char *set, const cha
 	ni_string_array_destroy(&qualified);
 }
 
-static void
-__ni_suse_get_scripts(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+static ni_bool_t
+ni_suse_ifcfg_parse_scripts(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_compat_netdev_t *compat = ifcfg->compat;
 	const char *value;
 
 	value = ni_sysconfig_get_value(sc, "PRE_UP_SCRIPT");
@@ -6216,11 +6359,15 @@ __ni_suse_get_scripts(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 	if (!value && __ni_suse_config_defaults)
 		value = ni_sysconfig_get_value(__ni_suse_config_defaults, "POST_DOWN_SCRIPT");
 	__ni_suse_qualify_scripts(compat, "post-down", value);
+
+	return TRUE;
 }
 
-static void
-ni_suse_ifcfg_get_firewall(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+static ni_bool_t
+ni_suse_ifcfg_parse_firewall(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_compat_netdev_t *compat = ifcfg->compat;
 	ni_sysconfig_t *merged;
 	const char *value;
 
@@ -6232,11 +6379,11 @@ ni_suse_ifcfg_get_firewall(const ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
 			if (!ni_string_empty(value))
 				ni_string_dup(&compat->firewall.zone, value);
 		}
-		ni_sysconfig_destroy(merged);
+		ni_sysconfig_free(merged);
 	}
+	return TRUE;
 }
 
-
 /*
  * Read ifsysctl file
  */
@@ -6316,457 +6463,480 @@ __ifsysctl_get_tristate(ni_var_array_t *vars, const char *path, const char *ifna
 }
 
 static ni_bool_t
-__ni_suse_read_ifsysctl(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+ni_suse_ifcfg_parse_ifsysctl_ipv4(ni_suse_ifcfg_t *ifcfg, ni_var_array_t *ifsysctl)
 {
-	ni_var_array_t ifsysctl = NI_VAR_ARRAY_INIT;
-	ni_netdev_t *dev = compat->dev;
-	char pathbuf[PATH_MAX];
-	const char *dirname;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 	ni_ipv4_devinfo_t *ipv4;
-	ni_ipv6_devinfo_t *ipv6;
+
+	/*
+	 * On L2 ports, we've disabled L3 / IPv4 already.
+	 */
+	if (ifcfg->l2only)
+		return TRUE;
+
+	if (!(ipv4 = ni_netdev_get_ipv4(dev)))
+		return FALSE;
+
+	/* Note: no conf.enable and conf.arp-verify in sysctl */
+
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv4/conf", dev->name,
+			"forwarding", &ipv4->conf.forwarding);
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv4/conf", dev->name,
+			"arp_notify", &ipv4->conf.arp_notify);
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv4/conf", dev->name,
+			"accept_redirects", &ipv4->conf.accept_redirects);
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_suse_ifcfg_parse_ifsysctl_ipv6(ni_suse_ifcfg_t *ifcfg, ni_var_array_t *ifsysctl)
+{
 	ni_tristate_t disable_ipv6 = NI_TRISTATE_DEFAULT;
+	ni_netdev_t *dev = ifcfg->compat->dev;
+	ni_ipv6_devinfo_t *ipv6;
 
-	dirname = ni_dirname(sc->pathname);
-	if (ni_string_empty(dirname))
+	if (!(ipv6 = ni_netdev_get_ipv6(dev)))
 		return FALSE;
 
-	ni_var_array_copy(&ifsysctl, &__ni_suse_global_ifsysctl);
-	snprintf(pathbuf, sizeof(pathbuf), "%s/%s-%s", dirname,
-			__NI_SUSE_IFSYSCTL_FILE, dev->name);
-	if (ni_isreg(pathbuf)) {
-		ni_ifsysctl_file_load(&ifsysctl, pathbuf);
-	}
+	/*
+	 * First, get sysctl's affecting also "IPv6 L2" aka
+	 * stable secret for the fe80 link-address assignment.
+	 */
+	__ifsysctl_get_int(ifsysctl, "net/ipv6/conf", dev->name,
+			"addr_gen_mode", &ipv6->conf.addr_gen_mode, 10);
 
-	ipv4 = ni_netdev_get_ipv4(dev);
-	ni_tristate_set(&ipv4->conf.enabled, TRUE);
-	/* no conf.enable and conf.arp-verify in sysctl */
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv4/conf", dev->name,
-				"forwarding", &ipv4->conf.forwarding);
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv4/conf", dev->name,
-				"arp_notify", &ipv4->conf.arp_notify);
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv4/conf", dev->name,
-				"accept_redirects", &ipv4->conf.accept_redirects);
+	__ifsysctl_get_ipv6(ifsysctl, "net/ipv6/conf", dev->name,
+			"stable_secret", &ipv6->conf.stable_secret);
 
-	ipv6 = ni_netdev_get_ipv6(dev);
-	ni_tristate_set(&ipv6->conf.enabled, !__ni_ipv6_disbled);
-	if (__ni_ipv6_disbled) {
-		ni_var_array_destroy(&ifsysctl);
+	/*
+	 * On L2 ports, we may need the ipv6 link e.g. for nsna
+	 * ping monitoring, but already disabled e.g accept_ra
+	 * to avoid RA processing affecting the IPv6 L3 autoconf
+	 * aka auto6 (SLAAC) & dhcp6 and don't parse static IPs.
+	 */
+	if (ifcfg->l2only)
 		return TRUE;
-	}
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv6/conf", dev->name,
-				"disable_ipv6", &disable_ipv6);
+
+	/*
+	 * When IPv6 is disabled via sysctl, we're done
+	 */
+	if (__ni_ipv6_disbled)
+		ni_tristate_set(&ipv6->conf.enabled, FALSE);
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv6/conf", dev->name,
+			"disable_ipv6", &disable_ipv6);
 	if (ni_tristate_is_set(disable_ipv6))
 		ni_tristate_set(&ipv6->conf.enabled, !disable_ipv6);
+	if (ni_tristate_is_disabled(ipv6->conf.enabled))
+		return TRUE;
 
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv6/conf", dev->name,
-				"forwarding", &ipv6->conf.forwarding);
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv6/conf", dev->name,
+			"forwarding", &ipv6->conf.forwarding);
 
-	__ifsysctl_get_int(&ifsysctl, "net/ipv6/conf", dev->name,
-				"accept_ra", &ipv6->conf.accept_ra, 10);
+	__ifsysctl_get_int(ifsysctl, "net/ipv6/conf", dev->name,
+			"accept_ra", &ipv6->conf.accept_ra, 10);
 	if (ipv6->conf.accept_ra > NI_IPV6_ACCEPT_RA_ROUTER)
 		ipv6->conf.accept_ra = NI_IPV6_ACCEPT_RA_ROUTER;
 	else
 	if (ipv6->conf.accept_ra < NI_IPV6_ACCEPT_RA_DEFAULT)
 		ipv6->conf.accept_ra = NI_IPV6_ACCEPT_RA_DEFAULT;
 
-	__ifsysctl_get_int(&ifsysctl, "net/ipv6/conf", dev->name,
-				"accept_dad", &ipv6->conf.accept_dad, 10);
+	__ifsysctl_get_int(ifsysctl, "net/ipv6/conf", dev->name,
+			"accept_dad", &ipv6->conf.accept_dad, 10);
 	if (ipv6->conf.accept_dad > NI_IPV6_ACCEPT_DAD_FAIL_PROTOCOL)
 		ipv6->conf.accept_dad = NI_IPV6_ACCEPT_DAD_FAIL_PROTOCOL;
 	else
 	if (ipv6->conf.accept_dad < NI_IPV6_ACCEPT_DAD_DEFAULT)
 		ipv6->conf.accept_dad = NI_IPV6_ACCEPT_DAD_DEFAULT;
 
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv6/conf", dev->name,
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv6/conf", dev->name,
 				"autoconf", &ipv6->conf.autoconf);
 
-	__ifsysctl_get_int(&ifsysctl, "net/ipv6/conf", dev->name,
+	__ifsysctl_get_int(ifsysctl, "net/ipv6/conf", dev->name,
 				"use_tempaddr", &ipv6->conf.privacy, 10);
 	if (ipv6->conf.privacy > NI_IPV6_PRIVACY_PREFER_TEMPORARY)
 		ipv6->conf.privacy = NI_IPV6_PRIVACY_PREFER_TEMPORARY;
 	else if (ipv6->conf.privacy < NI_IPV6_PRIVACY_DEFAULT)
 		ipv6->conf.privacy = NI_IPV6_PRIVACY_DISABLED;
 
-	__ifsysctl_get_tristate(&ifsysctl, "net/ipv6/conf", dev->name,
-				"accept_redirects", &ipv6->conf.accept_redirects);
+	__ifsysctl_get_tristate(ifsysctl, "net/ipv6/conf", dev->name,
+			"accept_redirects", &ipv6->conf.accept_redirects);
 
-	__ifsysctl_get_int(&ifsysctl, "net/ipv6/conf", dev->name,
-				"addr_gen_mode", &ipv6->conf.addr_gen_mode, 10);
+	return TRUE;
+}
+
+static ni_bool_t
+ni_suse_ifcfg_parse_ifsysctl(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
+{
+	ni_var_array_t ifsysctl = NI_VAR_ARRAY_INIT;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
+	char pathbuf[PATH_MAX];
+	const char *dirname;
 
-	__ifsysctl_get_ipv6(&ifsysctl, "net/ipv6/conf", dev->name,
-				"stable_secret", &ipv6->conf.stable_secret);
+	dirname = ni_dirname(sc->pathname);
+	if (ni_string_empty(dirname))
+		return FALSE;
 
+	ni_var_array_copy(&ifsysctl, &__ni_suse_global_ifsysctl);
+	snprintf(pathbuf, sizeof(pathbuf), "%s/%s-%s", dirname,
+			__NI_SUSE_IFSYSCTL_FILE, dev->name);
+	if (ni_isreg(pathbuf)) {
+		ni_ifsysctl_file_load(&ifsysctl, pathbuf);
+	}
+
+	ni_suse_ifcfg_parse_ifsysctl_ipv4(ifcfg, &ifsysctl);
+	ni_suse_ifcfg_parse_ifsysctl_ipv6(ifcfg, &ifsysctl);
 	ni_var_array_destroy(&ifsysctl);
 	return TRUE;
 }
 
-static ni_compat_netdev_t *
-__ni_suse_find_compat(ni_compat_netdev_array_t *netdevs, const char *name)
+static ni_bool_t
+ni_suse_ifcfg_parse_linkinfo(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_compat_netdev_t *compat;
-	unsigned int i;
+	const ni_sysconfig_t *sc = ifcfg->config;
+	ni_netdev_t *dev = ifcfg->compat->dev;
 
-	for (i = 0; i < netdevs->count; ++i) {
-		compat = netdevs->data[i];
-		if (ni_string_eq(compat->dev->name, name))
-			return compat;
-	}
-	return NULL;
+	ni_sysconfig_get_integer(sc, "MTU", &dev->link.mtu);
+
+	return TRUE;
 }
 
-static ni_netdev_t *
-__ni_suse_find_compat_device(ni_compat_netdev_array_t *netdevs, const char *name)
+static inline ni_bool_t
+ni_suse_ifcfg_parse_control(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_compat_netdev_t *compat = __ni_suse_find_compat(netdevs, name);
-	return compat ? compat->dev : NULL;
+	if (!(ifcfg->compat->control = ni_suse_ifcfg_parse_startmode(ifcfg->config)))
+		return FALSE;
+
+	return TRUE;
 }
 
-static ni_bool_t
-__ni_suse_set_link_master(ni_netdev_t *dev, const char *master, const char *ifcfg)
+static inline ni_bool_t
+ni_suse_ifcfg_parse_iftypes(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg)
 {
-	ni_ipv4_devinfo_t *ipv4;
-	ni_ipv6_devinfo_t *ipv6;
-
-	if (ni_string_empty(dev->link.masterdev.name))
-		ni_netdev_ref_set_ifname(&dev->link.masterdev, master);
-	else
-	if (!ni_string_eq(master, dev->link.masterdev.name)) {
-		/*
-		 * The ifcfg device hierarchy _is_ broken. But it is quite hard
-		 * to fix it: the only possibilities are to remove the port/slave
-		 * from the 2nd master to keep the already assigned master intact
-		 * or to override the master.
-		 * But as we cannot judge which master config is correct and which
-		 * not, we let the upper layers decide how to handle this.
-		 */
-		ni_warn("ifcfg-%s: cannot enslave %s to %s, already enslaved by %s",
-				ifcfg, dev->name, master, dev->link.masterdev.name);
+	if (try_loopback(ifcfgs, ifcfg)   < 0 ||
+	    try_ovs_system(ifcfgs, ifcfg) < 0 ||
+	    try_ovs_bridge(ifcfgs, ifcfg) < 0 ||
+	    try_bonding(ifcfgs, ifcfg)    < 0 ||
+	    try_team(ifcfgs, ifcfg)       < 0 ||
+	    try_bridge(ifcfgs, ifcfg)     < 0 ||
+	    try_vlan(ifcfgs, ifcfg)       < 0 ||
+	    try_vxlan(ifcfgs, ifcfg)      < 0 ||
+	    try_macvlan(ifcfgs, ifcfg)    < 0 ||
+	    try_dummy(ifcfgs, ifcfg)      < 0 ||
+	    try_tunnel(ifcfgs, ifcfg)     < 0 ||
+	    try_ppp(ifcfgs, ifcfg)        < 0 ||
+	    try_wireless(ifcfgs, ifcfg)   < 0 ||
+	    try_infiniband(ifcfgs, ifcfg) < 0 ||
+	    /* keep ethernet the last one */
+	    try_ethernet(ifcfgs, ifcfg)   < 0)
 		return FALSE;
-	}
-
-	if ((ipv4 = ni_netdev_get_ipv4(dev)))
-		ni_tristate_set(&ipv4->conf.enabled, FALSE);
-	if ((ipv6 = ni_netdev_get_ipv6(dev)))
-		ni_tristate_set(&ipv6->conf.enabled, FALSE);
 
 	return TRUE;
 }
 
-static ni_compat_netdev_t *
-__ni_suse_create_compat_slave(ni_compat_netdev_array_t *netdevs, ni_compat_netdev_t *master, const char *master_name, const char *slave)
+static ni_bool_t
+ni_suse_ifcfg_parse_foreach(ni_suse_ifcfg_array_t *ifcfgs,
+		ni_bool_t (*parse)(ni_suse_ifcfg_array_t *, ni_suse_ifcfg_t *))
 {
-	ni_ifworker_control_t control = { "hotplug", NULL, FALSE, FALSE, NI_TRISTATE_DEFAULT, 0, 0 };
-	ni_compat_netdev_t *compat;
-	ni_client_state_t *m_cs;
-	ni_client_state_t *s_cs;
-
-	compat = ni_compat_netdev_new(slave);
-	if (!compat)
-		return NULL;
+	ni_suse_ifcfg_t *ifcfg;
+	unsigned int i;
 
-	if (master_name)
-		__ni_suse_set_link_master(compat->dev, master_name, master->dev->name);
-	else
-		__ni_suse_set_link_master(compat->dev, master->dev->name, master->dev->name);
+	if (!ifcfgs || !parse)
+		return FALSE;
 
-	/* apply control defaults  */
-	compat->control = ni_ifworker_control_clone(&control);
+	for (i = 0; i < ifcfgs->count; ++i) {
+		ifcfg = ifcfgs->data[i];
+		if (ifcfg->broken)
+			continue;
 
-	/* copy origin from master */
-	m_cs = ni_netdev_get_client_state(master->dev);
-	s_cs = ni_netdev_get_client_state(compat->dev);
-	ni_string_dup(&s_cs->config.origin, m_cs->config.origin);
+		if (!parse(ifcfgs, ifcfg))
+			ifcfg->broken = TRUE;
+	}
+	return TRUE;
+}
 
-	ni_compat_netdev_array_append(netdevs, compat);
+static ni_bool_t
+ni_suse_ifcfg_parse(ni_suse_ifcfg_array_t *ifcfgs)
+{
+	if (!ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_control)   ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_iftypes)   ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_linkinfo)  ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_ifsysctl)  ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_ethtool)	||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_firewall)  ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_scripts)   ||
+	    !ni_suse_ifcfg_parse_foreach(ifcfgs, ni_suse_ifcfg_parse_bootproto))
+		return FALSE;
 
-	return compat;
+	return TRUE;
 }
 
-static void
-__ni_suse_adjust_bond_slaves(ni_compat_netdev_array_t *netdevs, ni_compat_netdev_t *master)
+/*
+ * Given a basename like "IPADDR", try to find all variables with this
+ * prefix (eg "IPADDR", "IPADDR_0", "IPADDR_1", ...) and invoke the provided function
+ * for each. Note, this passes the variable suffix ("", "_0", "_1") rather than
+ * the full variable name into the called function.
+ */
+static int
+__process_indexed_variables(const ni_sysconfig_t *sc, ni_netdev_t *dev,
+				const char *basename,
+				ni_bool_t (*func)(const ni_sysconfig_t *, ni_netdev_t *, const char *))
 {
-	ni_bonding_t *bond = ni_netdev_get_bonding(master->dev);
-	ni_bonding_slave_t *slave;
-	const char *slave_name;
-	ni_netdev_t *dev;
-	unsigned int i;
+	ni_string_array_t names = NI_STRING_ARRAY_INIT;
+	unsigned int i, pfxlen;
 
-	for (i = 0; i < bond->slaves.count; ++i) {
-		slave = bond->slaves.data[i];
-		if (!slave || ni_string_empty(slave->device.name))
-			continue;
-		slave_name = slave->device.name;
-		dev = __ni_suse_find_compat_device(netdevs, slave_name);
-		if (dev) {
-			__ni_suse_set_link_master(dev, master->dev->name, master->dev->name);
-		} else {
-			__ni_suse_create_compat_slave(netdevs, master, master->dev->name, slave_name);
+	if (!ni_sysconfig_find_matching(sc, basename, &names))
+		return 1;
+
+	pfxlen = strlen(basename);
+	for (i = 0; i < names.count; ++i) {
+		if (!func(sc, dev, names.data[i] + pfxlen)) {
+			ni_string_array_destroy(&names);
+			return -1;
 		}
 	}
+	ni_string_array_destroy(&names);
+	return 0;
 }
 
-static void
-__ni_suse_adjust_team_ports(ni_compat_netdev_array_t *netdevs, ni_compat_netdev_t *master)
+/*
+ * Given a base name and a suffix (eg "IPADDR" and "_1"), build a variable name
+ * and look it up.
+ */
+static ni_var_t *
+__find_indexed_variable(const ni_sysconfig_t *sc, const char *basename, const char *suffix)
 {
-	ni_team_t *team = ni_netdev_get_team(master->dev);
-	const char *port;
-	ni_netdev_t *dev;
-	ni_compat_netdev_t *compat;
-	unsigned int i;
-	ni_bool_t nsna_enabled = FALSE;
-	ni_team_link_watch_t *lw;
-	ni_ipv6_devinfo_t *ipv6;
+	ni_var_t *res;
+	char namebuf[64];
 
-	for (i = 0; i < team->link_watch.count; i++) {
-		lw = team->link_watch.data[i];
-		if (lw && lw->type == NI_TEAM_LINK_WATCH_NSNA_PING)
-			nsna_enabled = TRUE;
-	}
+	snprintf(namebuf, sizeof(namebuf), "%s%s", basename, suffix);
+	res = ni_sysconfig_get(sc, namebuf);
+	if (res && (res->value == NULL || res->value[0] == '\0'))
+		res = NULL;
+	return res;
+}
 
-	for (i = 0; i < team->ports.count; i++) {
-		if (!team->ports.data[i])
-			continue;
-		port = team->ports.data[i]->device.name;
-		compat = __ni_suse_find_compat(netdevs, port);
-		if (!compat)
-			compat = __ni_suse_create_compat_slave(netdevs, master, master->dev->name, port);
 
-		if (compat && (dev = compat->dev)) {
-			if (!__ni_suse_set_link_master(dev, master->dev->name, master->dev->name))
-				continue;
-			if (nsna_enabled && (ipv6 = ni_netdev_get_ipv6(dev)))
-				ni_tristate_set(&ipv6->conf.enabled, TRUE);
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_new(void)
+{
+	ni_suse_ifcfg_t *ifcfg;
 
-			compat->port.type = NI_IFTYPE_TEAM;
-			compat->port.conf.team = team->ports.data[i]->config;
-		}
-	}
+	ifcfg = calloc(1, sizeof(*ifcfg));
+	return ifcfg;
 }
 
 static void
-__ni_suse_adjust_bridge_ports(ni_compat_netdev_array_t *netdevs, ni_compat_netdev_t *master)
+ni_suse_ifcfg_free(ni_suse_ifcfg_t *ifcfg)
 {
-	ni_bridge_t *bridge = ni_netdev_get_bridge(master->dev);
-	const char *port;
-	ni_netdev_t *dev;
-	unsigned int i;
-
-	for (i = 0; i < bridge->ports.count; ++i) {
-		port = bridge->ports.data[i]->ifname;
-		dev = __ni_suse_find_compat_device(netdevs, port);
-		if (dev) {
-			__ni_suse_set_link_master(dev, master->dev->name, master->dev->name);
-		} else {
-			__ni_suse_create_compat_slave(netdevs, master, master->dev->name, port);
+	if (ifcfg) {
+		if (ifcfg->config) {
+			ni_sysconfig_free(ifcfg->config);
+			ifcfg->config = NULL;
+		}
+		if (ifcfg->compat) {
+			ni_compat_netdev_free(ifcfg->compat);
+			ifcfg->compat = NULL;
 		}
+		free(ifcfg);
 	}
 }
 
-static void
-__ni_suse_adjust_ovs_system(ni_compat_netdev_t *compat)
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_new_missing(ni_suse_ifcfg_t *from, const char *ifname)
 {
-	static const ni_ifworker_control_t control = {
-		"hotplug", NULL, FALSE, FALSE, NI_TRISTATE_DISABLE, 0, 0
-	};
-	ni_ipv4_devinfo_t *ipv4;
-	ni_ipv6_devinfo_t *ipv6;
+	ni_suse_ifcfg_t *ifcfg;
 
-	/*
-	 * This datapath device does not need any setup (not even link up),
-	 * but as it is actively used as master device for all bridge ports
-	 * so we have to consider it... adjust it as good as we can.
-	 * We don't have any "do not set UP the link up" flag until now...
-	 */
-	compat->control = ni_ifworker_control_clone(&control);
-	if ((ipv4 = ni_netdev_get_ipv4(compat->dev)))
-		ni_tristate_set(&ipv4->conf.enabled, FALSE);
-	if ((ipv6 = ni_netdev_get_ipv6(compat->dev)))
-		ni_tristate_set(&ipv6->conf.enabled, FALSE);
-}
+	if (!from || !from->config || !ifname)
+		return NULL;
 
-static void
-__ni_suse_create_ovs_system(ni_compat_netdev_array_t *netdevs, const char *ovs_system, const char *origin)
-{
-	ni_compat_netdev_t *compat;
-	ni_client_state_t *cs;
-	const char *sibling;
+	if (!(ifcfg = ni_suse_ifcfg_new()))
+		goto error;
 
-	if ((compat = __ni_suse_find_compat(netdevs, ovs_system)))
-		return;
+	if (!(ifcfg->config = ni_sysconfig_new(from->config->pathname)))
+		goto error;
 
-	compat = ni_compat_netdev_new(ovs_system);
+	if (!(ifcfg->compat = ni_compat_netdev_new(ifname)))
+		goto error;
 
-	__ni_suse_adjust_ovs_system(compat);
-	cs = ni_netdev_get_client_state(compat->dev);
-	/* fake it, otherwise it would depend on the trigger device names  */
-	sibling = ni_sibling_path_printf(origin, __NI_SUSE_CONFIG_IFPREFIX"%s", ovs_system);
-	ni_string_dup(&cs->config.origin, sibling);
+	return ifcfg;
 
-	ni_compat_netdev_array_append(netdevs, compat);
+error:
+	ni_suse_ifcfg_free(ifcfg);
+	return NULL;
 }
 
 static void
-__ni_suse_adjust_ovs_bridge_ports(ni_compat_netdev_array_t *netdevs, ni_compat_netdev_t *master)
+ni_suse_ifcfg_set_hotplug(ni_suse_ifcfg_t *ifcfg)
 {
-	ni_ovs_bridge_t *ovsbr = ni_netdev_get_ovs_bridge(master->dev);
-	static const char *ovs_system = NULL;
-	ni_compat_netdev_t *compat;
-	ni_ovs_bridge_port_t *p;
-	ni_client_state_t *cs;
-	const char *port;
-	unsigned int i;
+	static const ni_ifworker_control_t control = {
+		"hotplug", NULL, FALSE, FALSE, NI_TRISTATE_DEFAULT, 0, 0
+	};
 
-	if (ovs_system == NULL)
-		ovs_system = ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM);
+	if (ifcfg->compat->control)
+		ni_ifworker_control_free(ifcfg->compat->control);
 
-	cs = ni_netdev_get_client_state(master->dev);
-	__ni_suse_create_ovs_system(netdevs, ovs_system, cs->config.origin);
-	for (i = 0; i < ovsbr->ports.count; ++i) {
-		p = ovsbr->ports.data[i];
-		port = p->device.name;
-		compat = __ni_suse_find_compat(netdevs, port);
-		if (compat) {
-			if (__ni_suse_set_link_master(compat->dev, ovs_system, master->dev->name)) {
-				compat->port.type = NI_IFTYPE_OVS_BRIDGE;
-				ni_netdev_ref_set_ifname(&compat->port.conf.ovsbr.bridge, master->dev->name);
-			}
-		} else
-		if ((compat = __ni_suse_create_compat_slave(netdevs, master, ovs_system, port))) {
-			compat->port.type = NI_IFTYPE_OVS_BRIDGE;
-			ni_netdev_ref_set_ifname(&compat->port.conf.ovsbr.bridge, master->dev->name);
-		}
-	}
+	/* apply STARTMODE=hotplug (missing Ok) control defaults */
+	ifcfg->compat->control = ni_ifworker_control_clone(&control);
 }
 
 static void
-__ni_suse_adjust_slaves(ni_compat_netdev_array_t *netdevs)
+ni_suse_ifcfg_set_l2_only(ni_suse_ifcfg_t *ifcfg, ni_bool_t l2v6)
 {
-	ni_compat_netdev_t *compat;
-	ni_netdev_t *dev;
-	unsigned int i;
-
-	for (i = 0; i < netdevs->count; ++i) {
-		compat = netdevs->data[i];
-		dev = compat->dev;
+	ni_ipv4_devinfo_t *ipv4;
+	ni_ipv6_devinfo_t *ipv6;
 
-		switch (dev->link.type) {
-		case NI_IFTYPE_BOND:
-			__ni_suse_adjust_bond_slaves(netdevs, compat);
-			break;
-		case NI_IFTYPE_BRIDGE:
-			__ni_suse_adjust_bridge_ports(netdevs, compat);
-			break;
-		case NI_IFTYPE_OVS_BRIDGE:
-			__ni_suse_adjust_ovs_bridge_ports(netdevs, compat);
-			break;
-		case NI_IFTYPE_TEAM:
-			__ni_suse_adjust_team_ports(netdevs, compat);
-		default:
-			break;
-		}
+	/* apply BOOTPROTO=none (no L3) defaults in L2 enslave;
+	 * link monitoring may need ipv6 (link layer address) */
+	if ((ipv4 = ni_netdev_get_ipv4(ifcfg->compat->dev)))
+		ni_tristate_set(&ipv4->conf.enabled, FALSE);
+	if ((ipv6 = ni_netdev_get_ipv6(ifcfg->compat->dev))) {
+		/* ipv6 link only without RA for L3 setup */
+		ni_tristate_set(&ipv6->conf.enabled, l2v6);
+		ni_tristate_set(&ipv6->conf.accept_ra, 0);
 	}
+	ifcfg->l2only = TRUE;
 }
 
 static ni_bool_t
-__ni_suse_read_linkinfo(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
+ni_suse_ifcfg_netdev_set_master(ni_netdev_t *pdev, ni_netdev_t *mdev,
+		const char *type, const char *file)
 {
-	ni_netdev_t *dev = compat->dev;
-	const char *master;
+	if (ni_string_empty(pdev->link.masterdev.name))
+		return ni_netdev_ref_set_ifname(&pdev->link.masterdev, mdev->name);
 
-	ni_sysconfig_get_integer(sc, "MTU", &dev->link.mtu);
-
-	if (!ni_string_empty(master = ni_sysconfig_get_value(sc, "MASTER_DEVICE"))) {
-		if (!__ni_suse_set_link_master(dev, master, dev->name))
-			return FALSE;
+	if (ni_string_eq(pdev->link.masterdev.name, mdev->name)) {
+		ni_warn("ifcfg-%s: ignoring duplicate '%s' %s port '%s'",
+				file, mdev->name, type, pdev->name);
+		return TRUE;
 	}
 
-	return TRUE;
+	/*
+	 * The ifcfg device hierarchy _is_ broken.
+	 *
+	 * As we cannot judge which config is correct and which not,
+	 * we keep the already assigned interface intact and let
+	 * further layers decide how to handle this condition.
+	 */
+	ni_error("ifcfg-%s: cannot add %s port '%s' to '%s', already in '%s'",
+			file, type, pdev->name, mdev->name,
+			pdev->link.masterdev.name);
+	return FALSE;
 }
 
-/*
- * Read an ifcfg file
- */
-static ni_bool_t
-__ni_suse_sysconfig_read(ni_sysconfig_t *sc, ni_compat_netdev_t *compat)
-{
-	compat->control = __ni_suse_startmode(sc);
-
-	if (try_loopback(sc, compat)   < 0 ||
-	    try_ovs_system(sc, compat) < 0 ||
-	    try_ovs_bridge(sc, compat) < 0 ||
-	    try_bonding(sc, compat)    < 0 ||
-	    try_team(sc, compat)       < 0 ||
-	    try_bridge(sc, compat)     < 0 ||
-	    try_vlan(sc, compat)       < 0 ||
-	    try_vxlan(sc, compat)      < 0 ||
-	    try_macvlan(sc, compat)    < 0 ||
-	    try_dummy(sc, compat)      < 0 ||
-	    try_tunnel(sc, compat)     < 0 ||
-	    try_ppp(sc, compat)        < 0 ||
-	    try_wireless(sc, compat)   < 0 ||
-	    try_infiniband(sc, compat) < 0 ||
-	    /* keep ethernet the last one */
-	    try_ethernet(sc, compat)   < 0)
-		return FALSE;
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_add_l2_port(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *mcfg,
+		const char *file, const char *type, const char *port, ni_bool_t l2v6)
+{
+	ni_suse_ifcfg_t *pcfg;
+	ni_netdev_t *mdev;
 
-	if (compat->dev->link.type == NI_IFTYPE_OVS_SYSTEM)
-		return TRUE;
+	mdev = mcfg->compat->dev;
+	if (!ni_netdev_name_is_valid(port) || ni_string_eq(mdev->name, port)) {
+		ni_error("ifcfg-%s: rejecting invalid '%s' %s port name '%s'",
+				mdev->name, file, type, port);
+		return NULL;
+	}
 
-	__ni_suse_read_linkinfo(sc, compat);
-	__ni_suse_read_ifsysctl(sc, compat);
-	__ni_suse_bootproto(sc, compat);
-	__ni_suse_get_scripts(sc, compat);
-	ni_suse_ifcfg_get_firewall(sc, compat);
-	ni_suse_ifcfg_get_ethtool(sc, compat);
+	if (!(pcfg = ni_suse_ifcfg_find_by_ifname(ifcfgs, port))) {
+		pcfg = ni_suse_ifcfg_new_missing(mcfg, port);
+		if (!ni_suse_ifcfg_array_append(ifcfgs, pcfg)) {
+			ni_suse_ifcfg_free(pcfg);
+			ni_error("ifcfg-%s: failed to add %s port '%s' to '%s'",
+					file, type, port, mdev->name);
+			return NULL;
+		}
+		ni_suse_ifcfg_set_hotplug(pcfg);
+	}
+	ni_suse_ifcfg_set_l2_only(pcfg, l2v6);
 
-	/* FIXME: What to do with these:
-		NAME
-	 */
+	if (ni_suse_ifcfg_netdev_set_master(pcfg->compat->dev, mdev, type, file))
+		return pcfg;
 
-	return TRUE;
+	pcfg->broken = TRUE;
+	return NULL;
 }
 
-/*
- * Given a basename like "IPADDR", try to find all variables with this
- * prefix (eg "IPADDR", "IPADDR_0", "IPADDR_1", ...) and invoke the provided function
- * for each. Note, this passes the variable suffix ("", "_0", "_1") rather than
- * the full variable name into the called function.
- */
-static int
-__process_indexed_variables(const ni_sysconfig_t *sc, ni_netdev_t *dev,
-				const char *basename,
-				ni_bool_t (*func)(const ni_sysconfig_t *, ni_netdev_t *, const char *))
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_add_lower(ni_suse_ifcfg_array_t *ifcfgs, ni_suse_ifcfg_t *ifcfg,
+		const char *lower, const char *info)
 {
-	ni_string_array_t names = NI_STRING_ARRAY_INIT;
-	unsigned int i, pfxlen;
+	ni_netdev_t *dev = ifcfg->compat->dev;
+	ni_suse_ifcfg_t *cfg;
 
-	if (!ni_sysconfig_find_matching(sc, basename, &names))
-		return 1;
+	if (!ni_netdev_name_is_valid(lower) || ni_string_eq(dev->name, lower)) {
+		ni_error("ifcfg-%s: rejecting invalid %s '%s' of '%s'",
+				dev->name, info ?: "lower interface",
+				lower, dev->name);
+		return NULL;
+	}
 
-	pfxlen = strlen(basename);
-	for (i = 0; i < names.count; ++i) {
-		if (!func(sc, dev, names.data[i] + pfxlen)) {
-			ni_string_array_destroy(&names);
-			return -1;
+	if (!(cfg = ni_suse_ifcfg_find_by_ifname(ifcfgs, lower))) {
+		cfg = ni_suse_ifcfg_new_missing(ifcfg, lower);
+		if (!ni_suse_ifcfg_array_append(ifcfgs, cfg)) {
+			ni_suse_ifcfg_free(cfg);
+			ni_error("ifcfg-%s: failed to add missing %s '%s' for '%s",
+					dev->name, info ?: "lower interface",
+					lower, dev->name);
+			return NULL;
 		}
+		ni_warn("ifcfg-%s: generated missing %s '%s' config for '%s'",
+			dev->name, info ?: "lower interface",
+			lower, dev->name);
+
+		/* Inherit from depending somehow? */
+		ni_suse_ifcfg_set_hotplug(cfg);
 	}
-	ni_string_array_destroy(&names);
-	return 0;
+	return cfg;
 }
 
-/*
- * Given a base name and a suffix (eg "IPADDR" and "_1"), build a variable name
- * and look it up.
- */
-static ni_var_t *
-__find_indexed_variable(const ni_sysconfig_t *sc, const char *basename, const char *suffix)
+static ni_suse_ifcfg_t *
+ni_suse_ifcfg_find_by_ifname(ni_suse_ifcfg_array_t *ifcfgs, const char *ifname)
 {
-	ni_var_t *res;
-	char namebuf[64];
+	ni_suse_ifcfg_t *ifcfg;
+	ni_netdev_t *dev;
+	unsigned int i;
 
-	snprintf(namebuf, sizeof(namebuf), "%s%s", basename, suffix);
-	res = ni_sysconfig_get(sc, namebuf);
-	if (res && (res->value == NULL || res->value[0] == '\0'))
-		res = NULL;
-	return res;
+	if (!ifcfgs || !ifname)
+		return NULL;
+
+	for (i = 0; i < ifcfgs->count; ++i) {
+		ifcfg = ifcfgs->data[i];
+		dev = ifcfg->compat->dev;
+
+		if (ni_string_eq(dev->name, ifname))
+			return ifcfg;
+	}
+	return NULL;
+}
+
+static unsigned int
+ni_suse_ifcfg_process_indexed(ni_suse_ifcfg_array_t *ifcfgs,
+		ni_suse_ifcfg_t *ifcfg, const char *prefix,
+		ifcfg_process_fn_t process)
+{
+	const ni_var_t *var;
+	unsigned int cnt, i;
+	size_t pfxlen;
+
+	if (!ifcfgs || !ifcfg || !ifcfg->config || !process)
+		return 0;
+
+	pfxlen = ni_string_len(prefix);
+	for (cnt = 0, i = 0; i < ifcfg->config->vars.count; ++i) {
+		var = &ifcfg->config->vars.data[i];
+
+		if (!var->value || strncmp(var->name, prefix, pfxlen))
+			continue;
+
+		if (process(ifcfgs, ifcfg, var->value, var->name, var->name + pfxlen))
+			cnt++;
+	}
+	return cnt;
 }
+
+static ni_define_ptr_array_init(ni_suse_ifcfg);
+static ni_define_ptr_array_destroy(ni_suse_ifcfg);
+static ni_define_ptr_array_realloc(ni_suse_ifcfg, NI_SUSE_IFCFG_ARRAY_CHUNK);
+static ni_define_ptr_array_append(ni_suse_ifcfg);
diff --git a/client/wicked-client.h b/client/wicked-client.h
index 88c766c1c..d2bd987f1 100644
--- a/client/wicked-client.h
+++ b/client/wicked-client.h
@@ -32,8 +32,6 @@
 #include <wicked/addrconf.h>
 #include <wicked/route.h>
 #include <wicked/fsm.h>
-#include <wicked/ovs.h>
-#include <wicked/team.h>
 
 extern int			opt_global_dryrun;
 extern char *			opt_global_rootdir;
@@ -56,13 +54,7 @@ typedef struct ni_compat_netdev {
 		ni_hwaddr_t	hwaddr;
 	} identify;
 
-	struct {
-		ni_iftype_t				type;
-		union {
-			ni_ovs_bridge_port_config_t	ovsbr;
-			ni_team_port_config_t		team;
-		} conf;
-	} port;
+	ni_netdev_port_config_t	port;
 
 	ni_rule_array_t		rules;
 
diff --git a/include/wicked/bonding.h b/include/wicked/bonding.h
index a17434719..23b6a7709 100644
--- a/include/wicked/bonding.h
+++ b/include/wicked/bonding.h
@@ -1,11 +1,24 @@
 /*
- * Bonding support for netinfo
+ *	Bonding support for netinfo
  *
- * Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2023 SUSE LLC
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-
-#ifndef __WICKED_BONDING_H__
-#define __WICKED_BONDING_H__
+#ifndef NI_WICKED_BONDING_H
+#define NI_WICKED_BONDING_H
 
 enum {
 	NI_BOND_MONITOR_MII = 1,
@@ -66,22 +79,27 @@ enum {
 };
 
 enum {
-	NI_BOND_SLAVE_STATE_ACTIVE = 0,
-	NI_BOND_SLAVE_STATE_BACKUP = 1,
+	NI_BOND_PORT_STATE_ACTIVE = 0,
+	NI_BOND_PORT_STATE_BACKUP = 1,
 };
 enum {
-	NI_BOND_SLAVE_LINK_UP = 0,
-	NI_BOND_SLAVE_LINK_FAIL = 1,
-	NI_BOND_SLAVE_LINK_DOWN = 2,
-	NI_BOND_SLAVE_LINK_BACK = 3,
+	NI_BOND_PORT_LINK_UP = 0,
+	NI_BOND_PORT_LINK_FAIL = 1,
+	NI_BOND_PORT_LINK_DOWN = 2,
+	NI_BOND_PORT_LINK_BACK = 3,
 };
 
-typedef struct ni_bonding_slave_array	ni_bonding_slave_array_t;
-typedef struct ni_bonding_slave		ni_bonding_slave_t;
-
-struct ni_bonding_slave_info {
-	unsigned int			refcount;
+/*
+ * Bonding port (link-request) configuration
+ */
+struct ni_bonding_port_config {
+	unsigned int			queue_id;
+};
 
+/*
+ * Bonding port interface info properties
+ */
+struct ni_bonding_port_info {
 	unsigned int			state;
 	unsigned int			mii_status;
 	ni_hwaddr_t			perm_hwaddr;
@@ -90,16 +108,6 @@ struct ni_bonding_slave_info {
 	unsigned int			link_failure_count;
 };
 
-struct ni_bonding_slave {
-	ni_netdev_ref_t			device;
-	ni_bonding_slave_info_t *	info;
-};
-
-struct ni_bonding_slave_array {
-	unsigned int			count;
-	ni_bonding_slave_t **		data;
-};
-
 struct ni_bonding {
 	unsigned int		mode;
 
@@ -143,82 +151,53 @@ struct ni_bonding {
 
 	ni_netdev_ref_t		primary_slave;
 	ni_netdev_ref_t		active_slave;
-
-	ni_bonding_slave_array_t slaves;
 };
 
-extern int		ni_bonding_load(const char *options);
-
-extern ni_bonding_t *	ni_bonding_new(void);
-extern ni_bonding_t *	ni_bonding_clone(const ni_bonding_t *);
-extern void		ni_bonding_free(ni_bonding_t *);
-
-extern void		ni_bonding_get_slave_names(const ni_bonding_t *, ni_string_array_t *);
-extern ni_bool_t	ni_bonding_has_slave(ni_bonding_t *, const char *);
-extern ni_bonding_slave_t * ni_bonding_add_slave(ni_bonding_t *, const char *);
-
-extern ni_bool_t	ni_bonding_set_option(ni_bonding_t *, const char *, const char *);
-extern int		ni_bonding_parse_sysfs_attrs(const char *, ni_bonding_t *);
-extern int		ni_bonding_write_sysfs_attrs(const char *ifname,
-						const ni_bonding_t *cfg_bond,
-						ni_bonding_t       *cur_bond,
-						ni_bool_t is_up, ni_bool_t has_slaves);
-
-extern ni_bool_t	ni_bonding_is_valid_arp_ip_target(const char *);
-
-extern const char *	ni_bonding_validate(const ni_bonding_t *bonding);
-
-extern const char *	ni_bonding_mode_type_to_name(unsigned int);
-extern int		ni_bonding_mode_name_to_type(const char *);
-
-extern const char *	ni_bonding_arp_validate_type_to_name(unsigned int);
-extern int		ni_bonding_arp_validate_name_to_type(const char *);
-
-extern const char *	ni_bonding_arp_validate_targets_to_name(unsigned int);
-extern int		ni_bonding_arp_validate_targets_to_type(const char *);
-
-extern const char *	ni_bonding_mii_carrier_detect_name(unsigned int);
-extern int		ni_bonding_mii_carrier_detect_type(const char *);
-
-extern const char *	ni_bonding_xmit_hash_policy_to_name(unsigned int);
-extern int		ni_bonding_xmit_hash_name_to_policy(const char *);
-
-extern const char *	ni_bonding_lacp_rate_name(unsigned int);
-extern int		ni_bonding_lacp_rate_mode(const char *);
-
-extern const char *	ni_bonding_ad_select_name(unsigned int);
-extern int		ni_bonding_ad_select_mode(const char *);
-
-extern const char *	ni_bonding_primary_reselect_name(unsigned int);
-extern int		ni_bonding_primary_reselect_mode(const char *);
-
-extern const char *	ni_bonding_fail_over_mac_name(unsigned int);
-extern int		ni_bonding_fail_over_mac_mode(const char *);
-
-extern const char *	ni_bonding_slave_state_name(unsigned int);
-extern const char *	ni_bonding_slave_mii_status_name(unsigned int);
-
-extern ni_bonding_slave_t *	ni_bonding_bind_slave(ni_bonding_t *, const ni_netdev_ref_t *, const char *);
-extern ni_bool_t		ni_bonding_unbind_slave(ni_bonding_t *, const ni_netdev_ref_t *, const char *);
-
-extern ni_bonding_slave_info_t *ni_bonding_slave_info_new(void);
-extern ni_bonding_slave_info_t *ni_bonding_slave_info_ref(ni_bonding_slave_info_t *);
-extern void			ni_bonding_slave_info_free(ni_bonding_slave_info_t *);
-extern void			ni_bonding_slave_info_reset(ni_bonding_slave_info_t *);
-
-extern ni_bonding_slave_t *	ni_bonding_slave_new(void);
-extern void			ni_bonding_slave_free(ni_bonding_slave_t *);
-extern void			ni_bonding_slave_set_info(ni_bonding_slave_t *, ni_bonding_slave_info_t *);
-extern ni_bonding_slave_info_t *ni_bonding_slave_get_info(ni_bonding_slave_t *);
-
-extern void			ni_bonding_slave_array_destroy(ni_bonding_slave_array_t *);
-extern ni_bool_t		ni_bonding_slave_array_append(ni_bonding_slave_array_t *, ni_bonding_slave_t *);
-extern ni_bool_t		ni_bonding_slave_array_delete(ni_bonding_slave_array_t *, unsigned int);
-extern ni_bonding_slave_t *	ni_bonding_slave_array_remove(ni_bonding_slave_array_t *, unsigned int);
-extern unsigned int		ni_bonding_slave_array_index_by_ifname(ni_bonding_slave_array_t *, const char *);
-extern unsigned int		ni_bonding_slave_array_index_by_ifindex(ni_bonding_slave_array_t *, unsigned int);
-extern ni_bonding_slave_t *	ni_bonding_slave_array_get(ni_bonding_slave_array_t *, unsigned int);
-extern ni_bonding_slave_t *	ni_bonding_slave_array_get_by_ifname(ni_bonding_slave_array_t *, const char *);
-extern ni_bonding_slave_t *	ni_bonding_slave_array_get_by_ifindex(ni_bonding_slave_array_t *, unsigned int);
-
-#endif /* __WICKED_BONDING_H__ */
+extern int				ni_bonding_load(const char *options);
+
+extern ni_bonding_t *			ni_bonding_new(void);
+extern ni_bonding_t *			ni_bonding_clone(const ni_bonding_t *);
+extern void				ni_bonding_free(ni_bonding_t *);
+
+extern const char *			ni_bonding_validate(const ni_bonding_t *bonding);
+extern ni_bool_t			ni_bonding_is_valid_arp_ip_target(const char *);
+
+extern ni_bool_t			ni_bonding_set_option(ni_bonding_t *,
+							const char *, const char *);
+extern int				ni_bonding_parse_sysfs_attrs(const char *,
+							ni_bonding_t *);
+extern int				ni_bonding_write_sysfs_attrs(const char *,
+							const ni_bonding_t *cfg_bond,
+							ni_bonding_t       *cur_bond,
+							ni_bool_t up, ni_bool_t ports);
+
+extern const char *			ni_bonding_mode_type_to_name(unsigned int);
+extern int				ni_bonding_mode_name_to_type(const char *);
+extern const char *			ni_bonding_arp_validate_type_to_name(unsigned int);
+extern int				ni_bonding_arp_validate_name_to_type(const char *);
+extern const char *			ni_bonding_arp_validate_targets_to_name(unsigned int);
+extern int				ni_bonding_arp_validate_targets_to_type(const char *);
+extern const char *			ni_bonding_mii_carrier_detect_name(unsigned int);
+extern int				ni_bonding_mii_carrier_detect_type(const char *);
+extern const char *			ni_bonding_xmit_hash_policy_to_name(unsigned int);
+extern int				ni_bonding_xmit_hash_name_to_policy(const char *);
+extern const char *			ni_bonding_lacp_rate_name(unsigned int);
+extern int				ni_bonding_lacp_rate_mode(const char *);
+extern const char *			ni_bonding_ad_select_name(unsigned int);
+extern int				ni_bonding_ad_select_mode(const char *);
+extern const char *			ni_bonding_primary_reselect_name(unsigned int);
+extern int				ni_bonding_primary_reselect_mode(const char *);
+extern const char *			ni_bonding_fail_over_mac_name(unsigned int);
+extern int				ni_bonding_fail_over_mac_mode(const char *);
+
+extern ni_bonding_port_config_t *	ni_bonding_port_config_new(void);
+extern void				ni_bonding_port_config_free(ni_bonding_port_config_t *);
+
+extern ni_bonding_port_info_t *		ni_bonding_port_info_new(void);
+extern void				ni_bonding_port_info_free(ni_bonding_port_info_t *);
+extern void				ni_bonding_port_info_reset(ni_bonding_port_info_t *);
+
+extern const char *			ni_bonding_port_state_name(unsigned int);
+extern const char *			ni_bonding_port_mii_status_name(unsigned int);
+
+#endif /* NI_WICKED_BONDING_H */
diff --git a/include/wicked/bridge.h b/include/wicked/bridge.h
index 9332edfa7..86ff0c927 100644
--- a/include/wicked/bridge.h
+++ b/include/wicked/bridge.h
@@ -1,28 +1,47 @@
 /*
- * bridge definitions for netinfo
+ *	bridge definitions for netinfo
  *
- * Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2012-2023 SUSE LLC
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
+#ifndef NI_WICKED_BRIDGE_H
+#define NI_WICKED_BRIDGE_H
 
-#ifndef __WICKED_BRIDGE_H__
-#define __WICKED_BRIDGE_H__
 
 #define NI_BRIDGE_VALUE_NOT_SET			-1U
 
-typedef enum ni_bridge_stp {
-	NI_BRIDGE_NO_STP = 0,			/* no spanning tree    */
-	NI_BRIDGE_KERN_STP,			/* STP in the kernel   */
-	NI_BRIDGE_USER_STP,			/* (R)STP in userspace */
-} ni_bridge_stp_t;
-
-typedef struct ni_bridge_port_status {
+/*
+ * Bridge port (link-request) configuration
+ */
+struct ni_bridge_port_config {
 	unsigned int		priority;
 	unsigned int		path_cost;
+};
 
-	int			state;
+/*
+ * Bridge port interface info properties
+ */
+struct ni_bridge_port_info {
+	unsigned int		state;
 	unsigned int		port_id;
 	unsigned int		port_no;
 
+	unsigned int		priority;
+	unsigned int		path_cost;
+
 	char *			designated_root;
 	char *			designated_bridge;
 
@@ -36,22 +55,13 @@ typedef struct ni_bridge_port_status {
 	unsigned long		hold_timer;
 	unsigned long		message_age_timer;
 	unsigned long		forward_delay_timer;
-} ni_bridge_port_status_t;
-
-struct ni_bridge_port {
-	char *			ifname;
-	unsigned int		ifindex;
-
-	unsigned int		priority;
-	unsigned int		path_cost;
-
-	ni_bridge_port_status_t	status;
 };
 
-typedef struct ni_bridge_port_array {
-	unsigned int		count;
-	ni_bridge_port_t **	data;
-} ni_bridge_port_array_t;
+typedef enum ni_bridge_stp {
+	NI_BRIDGE_NO_STP = 0,			/* no spanning tree    */
+	NI_BRIDGE_KERN_STP,			/* STP in the kernel   */
+	NI_BRIDGE_USER_STP,			/* (R)STP in userspace */
+} ni_bridge_stp_t;
 
 typedef struct ni_bridge_status {
 	unsigned int		stp_state;
@@ -79,29 +89,24 @@ struct ni_bridge {
 	double			max_age;
 
 	ni_bridge_status_t	status;
-	ni_bridge_port_array_t	ports;
 };
 
-extern ni_bridge_t *	ni_bridge_new(void);
-extern void		ni_bridge_free(ni_bridge_t *);
-extern void		ni_bridge_ports_destroy(ni_bridge_t *);
-extern void		ni_bridge_status_destroy(ni_bridge_status_t *);
-extern void		ni_bridge_port_status_destroy(ni_bridge_port_status_t *);
-extern int		ni_bridge_add_port(ni_bridge_t *, ni_bridge_port_t *);
-extern int		ni_bridge_del_port(ni_bridge_t *, unsigned int);
-extern int		ni_bridge_del_port_ifname(ni_bridge_t *, const char *);
-extern int		ni_bridge_del_port_ifindex(ni_bridge_t *, unsigned int);
-extern void		ni_bridge_get_port_names(const ni_bridge_t *, ni_string_array_t *);
-
-extern ni_bridge_port_t *ni_bridge_port_new(ni_bridge_t *br, const char *ifname, unsigned int ifindex);
-extern ni_bridge_port_t *ni_bridge_port_by_index(const ni_bridge_t *br, unsigned int ifindex);
-extern ni_bridge_port_t *ni_bridge_port_by_name(const ni_bridge_t *br, const char *ifname);
-extern ni_bridge_port_t *ni_bridge_port_clone(const ni_bridge_port_t *port);
-extern void		ni_bridge_port_free(ni_bridge_port_t *port);
-
-
-extern const char *	ni_bridge_port_validate(const ni_bridge_port_t *);
-extern const char *	ni_bridge_validate(const ni_bridge_t *);
-extern unsigned int	ni_bridge_waittime_from_xml(const xml_node_t *brnode);
-
-#endif /* __WICKED_BRIDGE_H__ */
+extern ni_bridge_t *			ni_bridge_new(void);
+extern void				ni_bridge_free(ni_bridge_t *);
+extern void				ni_bridge_status_destroy(ni_bridge_status_t *);
+
+extern unsigned int			ni_bridge_waittime_from_xml(const xml_node_t *brnode);
+
+extern const char *			ni_bridge_port_priority_validate(unsigned int);
+extern const char *			ni_bridge_port_path_cost_validate(unsigned int);
+extern const char *			ni_bridge_validate(const ni_bridge_t *);
+
+extern ni_bridge_port_config_t *	ni_bridge_port_config_new(void);
+extern void				ni_bridge_port_config_free(ni_bridge_port_config_t *);
+
+extern ni_bridge_port_info_t *		ni_bridge_port_info_new(void);
+extern void				ni_bridge_port_info_free(ni_bridge_port_info_t *);
+
+extern const char *			ni_bridge_port_state_name(unsigned int);
+
+#endif /* NI_WICKED_BRIDGE_H */
diff --git a/include/wicked/dbus.h b/include/wicked/dbus.h
index 6c18bec1b..887c69998 100644
--- a/include/wicked/dbus.h
+++ b/include/wicked/dbus.h
@@ -424,6 +424,23 @@ typedef struct ni_dbus_xml_validate_context {
 	void *			user_data;
 } ni_dbus_xml_validate_context_t;
 
+typedef struct ni_dbus_xml_traverse_path	ni_dbus_xml_traverse_path_t;
+typedef struct ni_dbus_xml_traverse_context	ni_dbus_xml_traverse_context_t;
+typedef dbus_bool_t				ni_dbus_xml_traverse_process_fn_t(
+								const ni_xs_type_t *, const char *,
+								const ni_dbus_xml_traverse_path_t *,
+								ni_dbus_xml_traverse_context_t *);
+
+struct ni_dbus_xml_traverse_path {
+	const ni_dbus_xml_traverse_path_t *	prev;
+	const ni_xs_type_t *			type;
+	const char *				name;
+};
+
+struct ni_dbus_xml_traverse_context {
+	ni_dbus_xml_traverse_process_fn_t *	process;
+};
+
 extern ni_xs_scope_t *		ni_dbus_xml_init(void);
 extern int			ni_dbus_xml_register_services(ni_xs_scope_t *);
 extern unsigned int		ni_dbus_xml_method_num_args(const ni_dbus_method_t *);
@@ -434,6 +451,9 @@ extern int			ni_dbus_xml_map_method_argument(const ni_dbus_method_t *method, uns
 						xml_node_t *doc_node, xml_node_t **ret_node, ni_bool_t *skip_call);
 extern dbus_bool_t		ni_dbus_xml_validate_argument(const ni_dbus_method_t *, unsigned int,
 						xml_node_t *, const ni_dbus_xml_validate_context_t *);
+extern dbus_bool_t		ni_dbus_xml_traverse_service_properties(const ni_dbus_service_t *,
+						ni_dbus_xml_traverse_context_t *);
+extern const char *		ni_dbus_xml_traverse_path_print(ni_stringbuf_t *, const ni_dbus_xml_traverse_path_t *);
 extern dbus_bool_t		ni_dbus_xml_serialize_arg(const ni_dbus_method_t *, unsigned int,
 						ni_dbus_variant_t *, xml_node_t *);
 extern dbus_bool_t		ni_dbus_xml_method_has_return(const ni_dbus_method_t *);
diff --git a/include/wicked/fsm.h b/include/wicked/fsm.h
index 4bf53c80e..c047c1c64 100644
--- a/include/wicked/fsm.h
+++ b/include/wicked/fsm.h
@@ -285,6 +285,7 @@ typedef struct ni_ifmatcher {
 				allow_persistent   : 1,
 				ignore_startmode   : 1,
 				skip_active        : 1,
+				ifreload           : 1,
 				ifdown             : 1;
 } ni_ifmatcher_t;
 
@@ -342,8 +343,10 @@ extern unsigned int		ni_fsm_get_matching_workers(ni_fsm_t *, ni_ifmatcher_t *, n
 extern unsigned int		ni_fsm_mark_matching_workers(ni_fsm_t *, ni_ifworker_array_t *, const ni_ifmarker_t *);
 extern unsigned int		ni_fsm_start_matching_workers(ni_fsm_t *, ni_ifworker_array_t *);
 extern void			ni_fsm_reset_matching_workers(ni_fsm_t *, ni_ifworker_array_t *, const ni_uint_range_t *, ni_bool_t);
-extern void			ni_fsm_print_config_hierarchy(const ni_fsm_t *);
-extern void			ni_fsm_print_system_hierarchy(const ni_fsm_t *);
+extern void			ni_fsm_print_config_hierarchy(const ni_fsm_t *,
+						const ni_ifworker_array_t *, ni_log_fn_t *);
+extern void			ni_fsm_print_system_hierarchy(const ni_fsm_t *,
+						const ni_ifworker_array_t *, ni_log_fn_t *);
 extern int			ni_fsm_build_hierarchy(ni_fsm_t *, ni_bool_t);
 extern ni_ifworker_t *		ni_fsm_worker_identify(ni_fsm_t *, const xml_node_t *, const char *,
 							ni_ifworker_type_t *, const char **);
diff --git a/include/wicked/logging.h b/include/wicked/logging.h
index 7d2fbd896..f9a65fdaf 100644
--- a/include/wicked/logging.h
+++ b/include/wicked/logging.h
@@ -17,6 +17,8 @@
 # define __noreturn	/* */
 #endif
 
+typedef void		ni_log_fn_t(const char *, ...);
+
 extern void		ni_info(const char *, ...) __fmtattr;
 extern void		ni_note(const char *, ...) __fmtattr;
 extern void		ni_warn(const char *, ...) __fmtattr;
diff --git a/include/wicked/netinfo.h b/include/wicked/netinfo.h
index 9f7e91539..ce0170587 100644
--- a/include/wicked/netinfo.h
+++ b/include/wicked/netinfo.h
@@ -14,19 +14,22 @@
 
 #include "client/client_state.h"
 
-typedef struct ni_link_stats	ni_link_stats_t;
+typedef struct ni_netdev_port_info	ni_netdev_port_info_t;
 
-typedef struct ni_slaveinfo	ni_slaveinfo_t;
-struct ni_slaveinfo {
-	ni_iftype_t		type;
-	char *			kind;
+struct ni_netdev_port_info {
+	ni_iftype_t			type;
+	char *				kind;
 
 	union {
-	    ni_bonding_slave_info_t *	bond;
+		ni_bonding_port_info_t *	bond;
+		ni_team_port_info_t *		team;
+		ni_bridge_port_info_t *		bridge;
+		ni_ovs_bridge_port_info_t *	ovsbr;
 	};
 };
 
 typedef struct ni_linkinfo ni_linkinfo_t;
+
 struct ni_linkinfo {
 	ni_iftype_t		type;
 	unsigned int		ifindex;
@@ -40,14 +43,12 @@ struct ni_linkinfo {
 	unsigned int		txqlen;
 	ni_netdev_ref_t		lowerdev;
 	ni_netdev_ref_t		masterdev;
-	ni_slaveinfo_t		slave;
+	ni_netdev_port_info_t	port;
 	unsigned int		oper_state;
 	char *			qdisc;
 	char *			kind;
 
 	unsigned int		saved_mtu;
-
-	ni_link_stats_t *	stats;
 };
 
 struct ni_netdev {
@@ -101,7 +102,25 @@ struct ni_netdev {
 	ni_event_filter_t *	event_filter;
 };
 
-typedef struct ni_netdev_port_req	ni_netdev_port_req_t;
+/*
+ * netdev link-request port config
+ */
+typedef struct ni_netdev_port_config	ni_netdev_port_config_t;
+
+struct ni_netdev_port_config {
+	ni_iftype_t			type;
+
+	union {
+		ni_bonding_port_config_t *	bond;
+		ni_team_port_config_t *		team;
+		ni_bridge_port_config_t *	bridge;
+		ni_ovs_bridge_port_config_t *	ovsbr;
+	};
+};
+
+/*
+ * netdev link-request configuration
+ */
 struct ni_netdev_req {
 	unsigned int		ifflags;
 
@@ -112,7 +131,7 @@ struct ni_netdev_req {
 	char *			alias;
 
 	ni_netdev_ref_t		master;
-	ni_netdev_port_req_t *	port;
+	ni_netdev_port_config_t port;
 };
 
 extern ni_bool_t	ni_set_global_config_path(const char *);
@@ -171,6 +190,7 @@ extern ni_netconfig_t *	ni_global_state_handle(int);
 extern ni_netdev_t *	ni_netdev_by_name(ni_netconfig_t *nic, const char *name);
 extern ni_netdev_t *	ni_netdev_by_index(ni_netconfig_t *nic, unsigned int index);
 extern ni_netdev_t *	ni_netdev_by_hwaddr(ni_netconfig_t *nic, const ni_hwaddr_t *lla);
+extern ni_netdev_t *	ni_netdev_by_iftype(ni_netconfig_t *nic, ni_iftype_t);
 extern ni_netdev_t *	ni_netdev_by_vlan_name_and_tag(ni_netconfig_t *nc,
 				const char *physdev, uint16_t tag);
 extern unsigned int	ni_netdev_name_to_index(const char *);
@@ -219,7 +239,6 @@ extern void		ni_netdev_set_ovs_bridge(ni_netdev_t *, ni_ovs_bridge_t *);
 extern void		ni_netdev_set_ethtool(ni_netdev_t *, ni_ethtool_t *);
 extern void		ni_netdev_set_ethernet(ni_netdev_t *, ni_ethernet_t *);
 extern void		ni_netdev_set_infiniband(ni_netdev_t *, ni_infiniband_t *);
-extern void		ni_netdev_set_link_stats(ni_netdev_t *, ni_link_stats_t *);
 extern void		ni_netdev_set_wireless(ni_netdev_t *, ni_wireless_t *);
 extern void		ni_netdev_set_openvpn(ni_netdev_t *, ni_openvpn_t *);
 extern void		ni_netdev_set_tuntap(ni_netdev_t *, ni_tuntap_t *);
@@ -240,7 +259,6 @@ extern ni_bool_t	ni_netdev_supports_arp(ni_netdev_t *);
 extern void             ni_netdev_clear_addresses(ni_netdev_t *);
 extern void             ni_netdev_clear_routes(ni_netdev_t *);
 extern void		ni_netdev_clear_event_filters(ni_netdev_t *);
-extern void		ni_netdev_slaveinfo_destroy(ni_slaveinfo_t *);
 
 extern const ni_uuid_t *ni_netdev_add_event_filter(ni_netdev_t *, unsigned int mask);
 extern const ni_uuid_t *ni_netdev_get_event_uuid(ni_netdev_t *, ni_event_t);
@@ -302,8 +320,6 @@ extern ni_bool_t	ni_netdev_device_is_ready(ni_netdev_t *);
 extern ni_bool_t	ni_netdev_device_always_ready(ni_netdev_t *);
 extern ni_bool_t	ni_netdev_link_always_ready(ni_linkinfo_t *);
 
-extern ni_tristate_t	ni_netdev_guess_link_required(const ni_netdev_t *);
-
 static inline int
 ni_netdev_device_is_up(const ni_netdev_t *dev)
 {
@@ -322,4 +338,18 @@ ni_netdev_network_is_up(const ni_netdev_t *dev)
 	return dev ? dev->link.ifflags & NI_IFF_NETWORK_UP : 0;
 }
 
+extern ni_bool_t		ni_netdev_port_info_data_init(ni_netdev_port_info_t *, ni_iftype_t);
+extern void			ni_netdev_port_info_data_destroy(ni_netdev_port_info_t *);
+extern ni_bool_t		ni_netdev_port_info_init(ni_netdev_port_info_t *, const char *);
+extern void			ni_netdev_port_info_destroy(ni_netdev_port_info_t *);
+
+extern ni_bool_t		ni_netdev_port_config_init(ni_netdev_port_config_t *, ni_iftype_t);
+extern void			ni_netdev_port_config_destroy(ni_netdev_port_config_t *);
+
+extern unsigned int		ni_netdev_get_ports(const ni_netdev_t *, ni_netdev_ref_array_t *,
+						ni_netconfig_t *);
+
+extern const ni_netdev_ref_t *	ni_netdev_get_master_ref(const ni_netdev_t *);
+extern ni_netdev_t *		ni_netdev_resolve_master(const ni_netdev_t *, ni_netconfig_t *);
+
 #endif /* NI_WICKED_NETINFO_H */
diff --git a/include/wicked/objectmodel.h b/include/wicked/objectmodel.h
index 908d7ac48..569eacfb1 100644
--- a/include/wicked/objectmodel.h
+++ b/include/wicked/objectmodel.h
@@ -35,6 +35,8 @@ extern void			ni_objectmodel_register_service(const ni_dbus_service_t *);
 extern void			ni_objectmodel_register_class(const ni_dbus_class_t *);
 extern const ni_dbus_class_t *	ni_objectmodel_get_class(const char *);
 extern ni_dbus_class_t *	ni_objectmodel_class_new(const char *, const ni_dbus_class_t *);
+extern unsigned int		ni_objectmodel_class_registry_count(void);
+extern unsigned int		ni_objectmodel_service_registry_count(void);
 
 #define NI_OBJECTMODEL_NETIF_CLASS		"netif"
 #define NI_OBJECTMODEL_NETIF_LIST_CLASS		"netif-list"
diff --git a/include/wicked/ovs.h b/include/wicked/ovs.h
index 6329ad97d..a4a8ca829 100644
--- a/include/wicked/ovs.h
+++ b/include/wicked/ovs.h
@@ -1,7 +1,7 @@
 /*
  *	OVS (bridge) device support
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,13 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifndef NI_WICKED_OVS_H
 #define NI_WICKED_OVS_H
@@ -33,19 +28,16 @@ typedef struct ni_ovs_bridge_port	ni_ovs_bridge_port_t;
  * ovs bridge port config
  */
 struct ni_ovs_bridge_port_config {
-	ni_netdev_ref_t			bridge;
+	ni_var_array_t			args;
 };
 
 /*
- * ovs bridge port
+ * OVS Bridge port interface info properties
  */
-struct ni_ovs_bridge_port {
-	ni_netdev_ref_t			device;
+struct ni_ovs_bridge_port_info {
+	ni_netdev_ref_t			bridge;
 };
 
-ni_declare_ptr_array_type(ni_ovs_bridge_port);
-
-
 /*
  * ovs bridge config
  */
@@ -61,26 +53,20 @@ struct ni_ovs_bridge_config {
  */
 struct ni_ovs_bridge {
 	ni_ovs_bridge_config_t		config;
-	ni_ovs_bridge_port_array_t	ports;
 };
 
 
-extern ni_ovs_bridge_t *	ni_ovs_bridge_new(void);
-extern void			ni_ovs_bridge_free(ni_ovs_bridge_t *);
-extern void			ni_ovs_bridge_config_init(ni_ovs_bridge_config_t *);
-extern void			ni_ovs_bridge_config_destroy(ni_ovs_bridge_config_t *);
-
-extern ni_ovs_bridge_port_t *	ni_ovs_bridge_port_new(void);
-extern void			ni_ovs_bridge_port_free(ni_ovs_bridge_port_t *);
+extern ni_ovs_bridge_t *		ni_ovs_bridge_new(void);
+extern void				ni_ovs_bridge_free(ni_ovs_bridge_t *);
+extern void				ni_ovs_bridge_config_init(ni_ovs_bridge_config_t *);
+extern void				ni_ovs_bridge_config_destroy(ni_ovs_bridge_config_t *);
 
-extern				ni_declare_ptr_array_init(ni_ovs_bridge_port);
-extern				ni_declare_ptr_array_destroy(ni_ovs_bridge_port);
-extern				ni_declare_ptr_array_append(ni_ovs_bridge_port);
-extern				ni_declare_ptr_array_delete_at(ni_ovs_bridge_port);
-extern ni_ovs_bridge_port_t *	ni_ovs_bridge_port_array_add_new(ni_ovs_bridge_port_array_t *, const char *);
-extern ni_ovs_bridge_port_t *	ni_ovs_bridge_port_array_find_by_name(ni_ovs_bridge_port_array_t *, const char *);
+extern ni_ovs_bridge_port_config_t *	ni_ovs_bridge_port_config_new(void);
+extern ni_bool_t			ni_ovs_bridge_port_config_init(ni_ovs_bridge_port_config_t *);
+extern void				ni_ovs_bridge_port_config_destroy(ni_ovs_bridge_port_config_t *);
+extern void				ni_ovs_bridge_port_config_free(ni_ovs_bridge_port_config_t *);
 
-extern void			ni_ovs_bridge_port_config_init(ni_ovs_bridge_port_config_t *);
-extern void			ni_ovs_bridge_port_config_destroy(ni_ovs_bridge_port_config_t *);
+extern ni_ovs_bridge_port_info_t *	ni_ovs_bridge_port_info_new(void);
+extern void				ni_ovs_bridge_port_info_free(ni_ovs_bridge_port_info_t *);
 
 #endif /* NI_WICKED_OVS_H */
diff --git a/include/wicked/sysconfig.h b/include/wicked/sysconfig.h
index 5e865a14d..ed059576a 100644
--- a/include/wicked/sysconfig.h
+++ b/include/wicked/sysconfig.h
@@ -18,7 +18,7 @@ struct ni_sysconfig {
 extern int		ni_sysconfig_scandir(const char *, const char *,
 				struct ni_string_array *nsa);
 extern ni_sysconfig_t *	ni_sysconfig_new(const char *pathname);
-extern void		ni_sysconfig_destroy(ni_sysconfig_t *);
+extern void		ni_sysconfig_free(ni_sysconfig_t *);
 extern ni_sysconfig_t *	ni_sysconfig_read(const char *);
 extern ni_sysconfig_t *	ni_sysconfig_read_matching(const char *filename, const char **varnames);
 extern ni_sysconfig_t *	ni_sysconfig_merge_defaults(const ni_sysconfig_t *, const ni_sysconfig_t *);
@@ -26,9 +26,9 @@ extern ni_sysconfig_t *	ni_sysconfig_merge_defaults(const ni_sysconfig_t *, cons
 extern int		ni_sysconfig_overwrite(ni_sysconfig_t *);
 extern int		ni_sysconfig_rewrite(ni_sysconfig_t *);
 
-extern void		ni_sysconfig_set(ni_sysconfig_t *, const char *name, const char *variable);
-extern void		ni_sysconfig_set_integer(ni_sysconfig_t *, const char *name, unsigned int);
-extern void		ni_sysconfig_set_boolean(ni_sysconfig_t *, const char *name, int);
+extern ni_bool_t	ni_sysconfig_set(ni_sysconfig_t *, const char *name, const char *variable);
+extern ni_bool_t	ni_sysconfig_set_integer(ni_sysconfig_t *, const char *name, unsigned int);
+extern ni_bool_t	ni_sysconfig_set_boolean(ni_sysconfig_t *, const char *name, int);
 
 extern ni_var_t *	ni_sysconfig_get(const ni_sysconfig_t *, const char *name);
 extern const char *	ni_sysconfig_get_value(const ni_sysconfig_t *, const char *);
@@ -40,7 +40,7 @@ extern ni_bool_t	ni_sysconfig_get_integer_optional(const ni_sysconfig_t *, const
 extern ni_bool_t	ni_sysconfig_get_boolean_optional(const ni_sysconfig_t *, const char *, ni_bool_t *);
 extern ni_bool_t	ni_sysconfig_test_boolean(const ni_sysconfig_t *, const char *);
 
-extern int		ni_sysconfig_find_matching(const ni_sysconfig_t *, const char *,
+extern unsigned int	ni_sysconfig_find_matching(const ni_sysconfig_t *, const char *,
 				struct ni_string_array *);
 
 
diff --git a/include/wicked/system.h b/include/wicked/system.h
index 55c2bee12..c0a3a40c7 100644
--- a/include/wicked/system.h
+++ b/include/wicked/system.h
@@ -55,9 +55,6 @@ extern int		ni_system_bridge_create(ni_netconfig_t *, const char *,
 				const ni_bridge_t *, ni_netdev_t **);
 extern int		ni_system_bridge_setup(ni_netconfig_t *, ni_netdev_t *,
 				const ni_bridge_t *);
-extern int		ni_system_bridge_add_port(ni_netconfig_t *, ni_netdev_t *,
-				const ni_bridge_port_t *);
-extern int		ni_system_bridge_remove_port(ni_netdev_t *, unsigned int);
 extern int		ni_system_bridge_shutdown(ni_netdev_t *);
 extern int		ni_system_bridge_delete(ni_netconfig_t *, ni_netdev_t *);
 extern int		ni_system_ovs_bridge_create(ni_netconfig_t *nc,
diff --git a/include/wicked/team.h b/include/wicked/team.h
index 94e93dced..8423c07b6 100644
--- a/include/wicked/team.h
+++ b/include/wicked/team.h
@@ -1,7 +1,7 @@
 /*
- *	Team device support
+ *	Team network interface support
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,14 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *      along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifndef NI_WICKED_TEAM_H
 #define NI_WICKED_TEAM_H
@@ -133,9 +127,8 @@ typedef struct ni_team_runner {
 	};
 } ni_team_runner_t;
 
-
 /*
- * link watch
+ * link watch type
  */
 typedef enum {
 	NI_TEAM_LINK_WATCH_ETHTOOL = 0,
@@ -144,7 +137,6 @@ typedef enum {
 	NI_TEAM_LINK_WATCH_TIPC,
 } ni_team_link_watch_type_t;
 
-
 /*
  * link watch type structs
  */
@@ -178,7 +170,6 @@ typedef struct ni_team_link_watch_ethtool {
 	unsigned int				delay_down;
 } ni_team_link_watch_ethtool_t;
 
-
 /*
  * link watch type union
  */
@@ -208,6 +199,9 @@ typedef struct ni_team_port_lacp {
 	unsigned int				key;
 } ni_team_port_lacp_t;
 
+/*
+ * team port (link-request) configuration
+ */
 struct ni_team_port_config {
 	unsigned int				queue_id;
 
@@ -216,18 +210,37 @@ struct ni_team_port_config {
 };
 
 /*
- * team port and array
+ * team port interface info properties
  */
-typedef struct ni_team_port {
-	ni_netdev_ref_t				device;
-	ni_team_port_config_t			config;
-} ni_team_port_t;
+typedef struct ni_team_port_runner_lacp_info	ni_team_port_runner_lacp_info_t;
+typedef struct ni_team_port_runner_info		ni_team_port_runner_info_t;
+typedef struct ni_team_port_link_watches_info	ni_team_port_link_watches_info_t;
+
+struct ni_team_port_runner_lacp_info {
+	struct {
+		unsigned int			id;
+	} aggregator;
+	ni_bool_t				selected;
+	char *					state;
+};
+struct ni_team_port_runner_info {
+	ni_team_runner_type_t			type;
+	union {
+		ni_team_port_runner_lacp_info_t	lacp;
+	};
+};
+struct ni_team_port_link_watches_info {
+	ni_bool_t				up;
+};
 
-typedef struct ni_team_port_array {
-	unsigned int				count;
-	ni_team_port_t **			data;
-} ni_team_port_array_t;
+struct ni_team_port_info {
+	ni_team_port_runner_info_t		runner;
+	ni_team_port_link_watches_info_t	watches;
+};
 
+/*
+ * team interface
+ */
 typedef enum {
 	NI_TEAM_LINK_WATCH_POLICY_ANY = 0U,
 	NI_TEAM_LINK_WATCH_POLICY_ALL = 1,
@@ -242,9 +255,7 @@ typedef struct ni_team_mcast_rejoin {
 	unsigned int				count;
 	unsigned int				interval;
 } ni_team_mcast_rejoin_t;
-/*
- * team device
- */
+
 struct ni_team {
 	unsigned int				debug_level;
 	ni_team_notify_peers_t			notify_peers;
@@ -252,10 +263,8 @@ struct ni_team {
 	ni_team_runner_t			runner;
 	ni_team_link_watch_policy_t		link_watch_policy;
 	ni_team_link_watch_array_t		link_watch;
-	ni_team_port_array_t			ports;
 };
 
-
 extern ni_team_t *				ni_team_new();
 extern void					ni_team_free(ni_team_t *);
 
@@ -267,36 +276,39 @@ extern ni_bool_t				ni_team_runner_name_to_type(const char *, ni_team_runner_typ
 
 extern const char *				ni_team_tx_hash_bit_to_name(ni_team_tx_hash_bit_t);
 extern ni_bool_t				ni_team_tx_hash_name_to_bit(const char *, ni_team_tx_hash_bit_t *);
-extern unsigned int				ni_team_tx_hash_get_bit_names(ni_team_tx_hash_bit_t, ni_string_array_t *);
+extern unsigned int				ni_team_tx_hash_get_bit_names(ni_team_tx_hash_bit_t,
+								ni_string_array_t *);
 extern const char *				ni_team_tx_balancer_type_to_name(ni_team_tx_balancer_type_t);
-extern ni_bool_t				ni_team_tx_balancer_name_to_type(const char *, ni_team_tx_balancer_type_t *);
+extern ni_bool_t				ni_team_tx_balancer_name_to_type(const char *,
+								ni_team_tx_balancer_type_t *);
 
 extern const char *				ni_team_lacp_select_policy_type_to_name(ni_team_lacp_select_policy_t);
-extern ni_bool_t				ni_team_lacp_select_policy_name_to_type(const char *, ni_team_lacp_select_policy_t *);
+extern ni_bool_t				ni_team_lacp_select_policy_name_to_type(const char *,
+								ni_team_lacp_select_policy_t *);
 
 extern const char *				ni_team_ab_hwaddr_policy_type_to_name(ni_team_ab_hwaddr_policy_t);
-extern ni_bool_t				ni_team_ab_hwaddr_policy_name_to_type(const char *, ni_team_ab_hwaddr_policy_t *);
+extern ni_bool_t				ni_team_ab_hwaddr_policy_name_to_type(const char *,
+								ni_team_ab_hwaddr_policy_t *);
 
 extern const char *				ni_team_link_watch_type_to_name(ni_team_link_watch_type_t);
-extern ni_bool_t				ni_team_link_watch_name_to_type(const char *, ni_team_link_watch_type_t *);
+extern ni_bool_t				ni_team_link_watch_name_to_type(const char *,
+								ni_team_link_watch_type_t *);
 
 extern const char *				ni_team_link_watch_policy_type_to_name(ni_team_link_watch_policy_t);
 extern ni_bool_t				ni_team_link_watch_policy_name_to_type(const char *,
-											ni_team_link_watch_policy_t *);
+								ni_team_link_watch_policy_t *);
 
 extern ni_team_link_watch_t *			ni_team_link_watch_new(ni_team_link_watch_type_t);
 extern void					ni_team_link_watch_free(ni_team_link_watch_t *);
 extern						ni_declare_ptr_array_append(ni_team_link_watch);
 extern						ni_declare_ptr_array_delete_at(ni_team_link_watch);
 
-extern ni_team_port_t *				ni_team_port_new(void);
-extern void					ni_team_port_free(ni_team_port_t *);
-extern						ni_declare_ptr_array_destroy(ni_team_port);
-extern						ni_declare_ptr_array_append(ni_team_port);
-extern						ni_declare_ptr_array_delete_at(ni_team_port);
-extern ni_team_port_t *				ni_team_port_array_find_by_name(ni_team_port_array_t *, const char *);
-
-extern void					ni_team_port_config_init(ni_team_port_config_t *);
+extern ni_team_port_config_t *			ni_team_port_config_new(void);
+extern ni_bool_t				ni_team_port_config_init(ni_team_port_config_t *);
 extern void					ni_team_port_config_destroy(ni_team_port_config_t *);
+extern void					ni_team_port_config_free(ni_team_port_config_t *);
+
+extern ni_team_port_info_t *			ni_team_port_info_new(void);
+extern void					ni_team_port_info_free(ni_team_port_info_t *);
 
 #endif /* NI_WICKED_TEAM_H */
diff --git a/include/wicked/types.h b/include/wicked/types.h
index 986012a0f..506bc1827 100644
--- a/include/wicked/types.h
+++ b/include/wicked/types.h
@@ -11,7 +11,7 @@
 #include <sys/types.h>
 #include <stdint.h>
 
-typedef unsigned char		ni_bool_t;
+typedef unsigned char				ni_bool_t;
 #ifndef TRUE
 # define TRUE 1
 #endif
@@ -20,126 +20,131 @@ typedef unsigned char		ni_bool_t;
 #endif
 
 typedef enum {
-	NI_TRISTATE_DEFAULT	= -1,
-	NI_TRISTATE_DISABLE	= 0,
-	NI_TRISTATE_ENABLE	= 1
+	NI_TRISTATE_DEFAULT			= -1,
+	NI_TRISTATE_DISABLE			= 0,
+	NI_TRISTATE_ENABLE			= 1
 } ni_tristate_t;
 
-typedef unsigned long long	ni_timeout_t;
-
-typedef union  ni_sockaddr	ni_sockaddr_t;
-typedef struct ni_netconfig	ni_netconfig_t;
-typedef struct ni_netdev	ni_netdev_t;
-typedef struct ni_route		ni_route_t;
-typedef struct ni_route_table	ni_route_table_t;
-typedef struct ni_rule		ni_rule_t;
-typedef struct ni_rule_array	ni_rule_array_t;
-typedef struct ni_vlan		ni_vlan_t;
-typedef struct ni_vxlan		ni_vxlan_t;
-typedef struct ni_macvlan	ni_macvlan_t;
-typedef struct ni_bridge	ni_bridge_t;
-typedef struct ni_bridge_port	ni_bridge_port_t;
-typedef struct ni_ovs_bridge	ni_ovs_bridge_t;
-typedef struct ni_ovs_bridge_port_config ni_ovs_bridge_port_config_t;
-typedef struct ni_bonding	ni_bonding_t;
-typedef struct ni_bonding_slave_info	ni_bonding_slave_info_t;
-typedef struct ni_team		ni_team_t;
-typedef struct ni_team_port_config ni_team_port_config_t;
-typedef struct ni_wireless	ni_wireless_t;
-typedef struct ni_ethtool	ni_ethtool_t;
-typedef struct ni_ethernet	ni_ethernet_t;
-typedef struct ni_infiniband	ni_infiniband_t;
-typedef struct ni_openvpn	ni_openvpn_t;
-typedef struct ni_tuntap	ni_tuntap_t;
-typedef struct ni_tunnel	ni_tunnel_t;
-typedef struct ni_sit		ni_sit_t;
-typedef struct ni_ipip		ni_ipip_t;
-typedef struct ni_gre		ni_gre_t;
-typedef struct ni_ppp		ni_ppp_t;
-typedef struct ni_dcb		ni_dcb_t;
-typedef struct ni_lldp		ni_lldp_t;
-typedef struct ni_nis_info	ni_nis_info_t;
-typedef struct ni_resolver_info	ni_resolver_info_t;
-typedef struct ni_addrconf_lease  ni_addrconf_lease_t;
-typedef struct ni_auto4_request	ni_auto4_request_t;
-typedef struct ni_netdev_req	ni_netdev_req_t;
-typedef struct ni_ipv4_devinfo	ni_ipv4_devinfo_t;
-typedef struct ni_ipv4_devconf	ni_ipv4_devconf_t;
-typedef struct ni_ipv6_devinfo	ni_ipv6_devinfo_t;
-typedef struct ni_ipv6_devconf	ni_ipv6_devconf_t;
-typedef struct ni_auto6		ni_auto6_t;
-typedef struct ni_event_filter	ni_event_filter_t;
-typedef struct ni_modem		ni_modem_t;
-typedef struct ni_pci_dev	ni_pci_dev_t;
+typedef unsigned long long			ni_timeout_t;
+
+typedef union  ni_sockaddr			ni_sockaddr_t;
+typedef struct ni_netconfig			ni_netconfig_t;
+typedef struct ni_netdev			ni_netdev_t;
+typedef struct ni_route				ni_route_t;
+typedef struct ni_route_table			ni_route_table_t;
+typedef struct ni_rule				ni_rule_t;
+typedef struct ni_rule_array			ni_rule_array_t;
+typedef struct ni_vlan				ni_vlan_t;
+typedef struct ni_vxlan				ni_vxlan_t;
+typedef struct ni_macvlan			ni_macvlan_t;
+typedef struct ni_bridge			ni_bridge_t;
+typedef struct ni_bridge_port			ni_bridge_port_t;
+typedef struct ni_bridge_port_info		ni_bridge_port_info_t;
+typedef struct ni_bridge_port_config		ni_bridge_port_config_t;
+typedef struct ni_ovs_bridge			ni_ovs_bridge_t;
+typedef struct ni_ovs_bridge_port_info		ni_ovs_bridge_port_info_t;
+typedef struct ni_ovs_bridge_port_config	ni_ovs_bridge_port_config_t;
+typedef struct ni_bonding			ni_bonding_t;
+typedef struct ni_bonding_port_info		ni_bonding_port_info_t;
+typedef struct ni_bonding_port_config		ni_bonding_port_config_t;
+typedef struct ni_team				ni_team_t;
+typedef struct ni_team_port_info		ni_team_port_info_t;
+typedef struct ni_team_port_config		ni_team_port_config_t;
+typedef struct ni_wireless			ni_wireless_t;
+typedef struct ni_ethtool			ni_ethtool_t;
+typedef struct ni_ethernet			ni_ethernet_t;
+typedef struct ni_infiniband			ni_infiniband_t;
+typedef struct ni_openvpn			ni_openvpn_t;
+typedef struct ni_tuntap			ni_tuntap_t;
+typedef struct ni_tunnel			ni_tunnel_t;
+typedef struct ni_sit				ni_sit_t;
+typedef struct ni_ipip				ni_ipip_t;
+typedef struct ni_gre				ni_gre_t;
+typedef struct ni_ppp				ni_ppp_t;
+typedef struct ni_dcb				ni_dcb_t;
+typedef struct ni_lldp				ni_lldp_t;
+typedef struct ni_nis_info			ni_nis_info_t;
+typedef struct ni_resolver_info			ni_resolver_info_t;
+typedef struct ni_addrconf_lease		ni_addrconf_lease_t;
+typedef struct ni_auto4_request			ni_auto4_request_t;
+typedef struct ni_netdev_req			ni_netdev_req_t;
+typedef struct ni_ipv4_devinfo			ni_ipv4_devinfo_t;
+typedef struct ni_ipv4_devconf			ni_ipv4_devconf_t;
+typedef struct ni_ipv6_devinfo			ni_ipv6_devinfo_t;
+typedef struct ni_ipv6_devconf			ni_ipv6_devconf_t;
+typedef struct ni_auto6				ni_auto6_t;
+typedef struct ni_event_filter			ni_event_filter_t;
+typedef struct ni_modem				ni_modem_t;
+typedef struct ni_pci_dev			ni_pci_dev_t;
 
 typedef struct ni_netdev_ref {
-	unsigned int		index;	/* by ifindex */
-	char *			name;	/* by ifname  */
+	unsigned int				index;	/* by ifindex */
+	char *					name;	/* by ifname  */
 } ni_netdev_ref_t;
 
-#define NI_NETDEV_REF_INIT		{ .index = 0, .name = NULL }
+#define NI_NETDEV_REF_INIT			{ .index = 0, .name = NULL }
 
 typedef struct ni_netdev_ref_array {
-	unsigned int		count;
-	ni_netdev_ref_t *	data;
+	unsigned int				count;
+	ni_netdev_ref_t *			data;
 } ni_netdev_ref_array_t;
 
-#define NI_NETDEV_REF_ARRAY_INIT	{ .count = 0, .data = NULL }
+#define NI_NETDEV_REF_ARRAY_INIT		{ .count = 0, .data = NULL }
 
-typedef struct ni_dbus_server	ni_dbus_server_t;
-typedef struct ni_dbus_client	ni_dbus_client_t;
+typedef struct ni_dbus_server			ni_dbus_server_t;
+typedef struct ni_dbus_client			ni_dbus_client_t;
 
-typedef struct ni_socket	ni_socket_t;
-typedef struct ni_socket_array	ni_socket_array_t;
-typedef struct ni_buffer	ni_buffer_t;
-typedef struct ni_extension	ni_extension_t;
-typedef struct ni_script_action	ni_script_action_t;
+typedef struct ni_socket			ni_socket_t;
+typedef struct ni_socket_array			ni_socket_array_t;
+typedef struct ni_buffer			ni_buffer_t;
+typedef struct ni_extension			ni_extension_t;
+typedef struct ni_script_action			ni_script_action_t;
 
-typedef struct ni_shellcmd	ni_shellcmd_t;
-typedef struct ni_process	ni_process_t;
+typedef struct ni_shellcmd			ni_shellcmd_t;
+typedef struct ni_process			ni_process_t;
 
 /*
  * These are used by the XML and XPATH code.
  */
-typedef struct xpath_format		xpath_format_t;
-typedef struct xpath_enode		xpath_enode_t;
-typedef struct xml_document		xml_document_t;
-typedef struct xml_document_array	xml_document_array_t;
-typedef struct xml_node			xml_node_t;
-typedef struct xml_location		xml_location_t;
-
-typedef struct ni_xs_type		ni_xs_type_t;
-typedef struct ni_xs_scope		ni_xs_scope_t;
-typedef struct ni_xs_method		ni_xs_method_t;
-typedef struct ni_xs_service		ni_xs_service_t;
+typedef struct xpath_format			xpath_format_t;
+typedef struct xpath_enode			xpath_enode_t;
+typedef struct xml_document			xml_document_t;
+typedef struct xml_document_array		xml_document_array_t;
+typedef struct xml_node				xml_node_t;
+typedef struct xml_location			xml_location_t;
+
+typedef struct ni_xs_type			ni_xs_type_t;
+typedef struct ni_xs_scope			ni_xs_scope_t;
+typedef struct ni_xs_method			ni_xs_method_t;
+typedef struct ni_xs_service			ni_xs_service_t;
 
 typedef struct xpath_format_array {
-	unsigned int		count;
-	xpath_format_t **	data;
+	unsigned int				count;
+	xpath_format_t **			data;
 } xpath_format_array_t;
 
 typedef union ni_uuid {
-	unsigned char		octets[16];
-	uint16_t		shorts[8];
-	uint32_t		words[4];
+	unsigned char				octets[16];
+	uint16_t				shorts[8];
+	uint32_t				words[4];
 } ni_uuid_t;
-#define NI_UUID_INIT		{ .words = { 0, 0, 0, 0 } }
+#define NI_UUID_INIT				{ .words = { 0, 0, 0, 0 } }
 
 /*
  * Link layer address
  */
-#define NI_MAXHWADDRLEN		64
+#define NI_MAXHWADDRLEN				64
 typedef struct ni_hwaddr {
-	unsigned short		type;
-	unsigned short		len;
-	unsigned char		data[NI_MAXHWADDRLEN];
+	unsigned short				type;
+	unsigned short				len;
+	unsigned char				data[NI_MAXHWADDRLEN];
 } ni_hwaddr_t;
 
 /*
  * Range of unsigned values
  */
 typedef struct ni_uint_range {
-	unsigned int		min, max;
+	unsigned int				min, max;
 } ni_uint_range_t;
 
 static inline void
@@ -160,26 +165,26 @@ ni_uint_range_update_max(ni_uint_range_t *r, unsigned int max)
  * Range of signed values
  */
 typedef struct ni_int_range {
-	int			min, max;
+	int					min, max;
 } ni_int_range_t;
 
 /*
  * Prototypes for ipv6 devinfo RA details
  */
-typedef struct ni_ipv6_ra_info	ni_ipv6_ra_info_t;
-typedef struct ni_ipv6_ra_pinfo	ni_ipv6_ra_pinfo_t;
+typedef struct ni_ipv6_ra_info			ni_ipv6_ra_info_t;
+typedef struct ni_ipv6_ra_pinfo			ni_ipv6_ra_pinfo_t;
 
 /*
  * Custom dhcp option declaration
  */
-typedef struct ni_dhcp_option_decl ni_dhcp_option_decl_t;
+typedef struct ni_dhcp_option_decl		ni_dhcp_option_decl_t;
 
 /*
  * Common DHCPv6 declarations
  */
-typedef struct ni_dhcp6_status		ni_dhcp6_status_t;
-typedef struct ni_dhcp6_ia		ni_dhcp6_ia_t;
-typedef struct ni_dhcp6_ia_addr		ni_dhcp6_ia_addr_t;
-typedef struct ni_dhcp6_prefix_req	ni_dhcp6_prefix_req_t;
+typedef struct ni_dhcp6_status			ni_dhcp6_status_t;
+typedef struct ni_dhcp6_ia			ni_dhcp6_ia_t;
+typedef struct ni_dhcp6_ia_addr			ni_dhcp6_ia_addr_t;
+typedef struct ni_dhcp6_prefix_req		ni_dhcp6_prefix_req_t;
 
 #endif /* NI_WICKED_TYPES_H */
diff --git a/include/wicked/util.h b/include/wicked/util.h
index 28871c938..54b8c888c 100644
--- a/include/wicked/util.h
+++ b/include/wicked/util.h
@@ -113,6 +113,7 @@ extern void		ni_string_free(char **);
 extern void		ni_string_clear(char **);
 extern ni_bool_t	ni_string_dup(char **, const char *);
 extern ni_bool_t	ni_string_set(char **, const char *, size_t len);
+extern ni_bool_t	ni_string_move(char **, char **);
 extern const char *	ni_string_printf(char **, const char *, ...);
 
 extern void		ni_string_array_init(ni_string_array_t *);
diff --git a/include/wicked/xml.h b/include/wicked/xml.h
index dbf3a80e4..b90750589 100644
--- a/include/wicked/xml.h
+++ b/include/wicked/xml.h
@@ -3,7 +3,8 @@
  *	This basically parses tags, attributes and CDATA, and that's
  *	just about it.
  *
- *	Copyright (C) 2009-2012  Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -15,30 +16,25 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write 
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 
- *	Boston, MA 02110-1301 USA.
- *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-
-#ifndef __WICKED_XML_H__
-#define __WICKED_XML_H__
+#ifndef NI_WICKED_XML_H
+#define NI_WICKED_XML_H
 
 #include <stdio.h>
 #include <wicked/util.h>
 #include <wicked/types.h>
+#include <wicked/array.h>
 
 struct xml_document {
 	char *			dtd;
 	struct xml_node *	root;
 };
 
-struct xml_document_array {
-	unsigned int		count;
-	xml_document_t **	data;
-};
-#define XML_DOCUMENT_ARRAY_INIT	{ 0, NULL }
+ni_declare_ptr_array_struct(xml_document);
+
+#define XML_DOCUMENT_ARRAY_INIT	NI_ARRAY_INIT
 
 struct xml_location_shared {
 	unsigned int		refcount;
@@ -67,12 +63,9 @@ struct xml_node {
 	xml_location_t *	location;
 };
 
-typedef struct xml_node_array	xml_node_array_t;
-struct xml_node_array {
-	unsigned int		count;
-	xml_node_t **		data;
-};
-#define XML_NODE_ARRAY_INIT	{ 0, NULL }
+ni_declare_ptr_array_type(xml_node);
+
+#define XML_NODE_ARRAY_INIT	NI_ARRAY_INIT
 
 extern xml_document_t *	xml_document_read(const char *);
 extern xml_document_t *	xml_document_scan(FILE *, const char *location);
@@ -90,6 +83,8 @@ extern xml_node_t *	xml_document_root(xml_document_t *);
 extern void		xml_document_set_root(xml_document_t *, xml_node_t *);
 extern xml_node_t *	xml_document_take_root(xml_document_t *);
 extern void		xml_document_free(xml_document_t *);
+extern xml_document_t *	xml_document_create(const char *, xml_node_t *);
+extern ni_bool_t	xml_document_expand(xml_document_array_t *, xml_document_t *);
 
 extern xml_node_t *	xml_node_new(const char *ident, xml_node_t *);
 extern xml_node_t *	xml_node_new_element(const char *ident, xml_node_t *, const char *cdata);
@@ -110,16 +105,16 @@ extern int		xml_node_content_uuid(const xml_node_t *, unsigned int, const ni_uui
 extern int		xml_node_print_fn(const xml_node_t *, void (*)(const char *, void *), void *);
 extern int		xml_node_print_debug(const xml_node_t *, unsigned int facility);
 extern xml_node_t *	xml_node_scan(FILE *fp, const char *location);
-extern void		xml_node_set_cdata(xml_node_t *, const char *);
-extern void		xml_node_set_int(xml_node_t *, int);
-extern void		xml_node_set_int64(xml_node_t *, int64_t);
-extern void		xml_node_set_uint(xml_node_t *, unsigned int);
-extern void		xml_node_set_uint64(xml_node_t *, uint64_t);
-extern void		xml_node_set_uint_hex(xml_node_t *, unsigned int);
-extern void		xml_node_add_attr(xml_node_t *, const char *, const char *);
-extern void		xml_node_add_attr_uint(xml_node_t *, const char *, unsigned int);
-extern void		xml_node_add_attr_ulong(xml_node_t *, const char *, unsigned long);
-extern void		xml_node_add_attr_double(xml_node_t *, const char *, double);
+extern ni_bool_t	xml_node_set_cdata(xml_node_t *, const char *);
+extern ni_bool_t	xml_node_set_int(xml_node_t *, int);
+extern ni_bool_t	xml_node_set_int64(xml_node_t *, int64_t);
+extern ni_bool_t	xml_node_set_uint(xml_node_t *, unsigned int);
+extern ni_bool_t	xml_node_set_uint64(xml_node_t *, uint64_t);
+extern ni_bool_t	xml_node_set_uint_hex(xml_node_t *, unsigned int);
+extern ni_bool_t	xml_node_add_attr(xml_node_t *, const char *, const char *);
+extern ni_bool_t	xml_node_add_attr_uint(xml_node_t *, const char *, unsigned int);
+extern ni_bool_t	xml_node_add_attr_ulong(xml_node_t *, const char *, unsigned long);
+extern ni_bool_t	xml_node_add_attr_double(xml_node_t *, const char *, double);
 
 extern ni_bool_t	xml_node_has_attr(const xml_node_t *, const char *);
 extern ni_bool_t	xml_node_del_attr(xml_node_t *, const char *);
@@ -130,6 +125,7 @@ extern ni_bool_t	xml_node_get_attr_ulong(const xml_node_t *, const char *, unsig
 extern ni_bool_t	xml_node_get_attr_double(const xml_node_t *, const char *, double *);
 extern xml_node_t *	xml_node_get_child(const xml_node_t *, const char *);
 extern xml_node_t *	xml_node_get_next_child(const xml_node_t *, const char *, const xml_node_t *);
+extern const char *	xml_node_get_child_cdata(const xml_node_t *, const char *);
 extern xml_node_t *	xml_node_get_child_with_attrs(const xml_node_t *, const char *,
 					const ni_var_array_t *);
 extern ni_bool_t	xml_node_replace_child(xml_node_t *, xml_node_t *);
@@ -155,18 +151,6 @@ extern void		xml_node_location_set(xml_node_t *, xml_location_t *);
 extern void		xml_node_location_modify(xml_node_t *, const char *);
 extern void		xml_node_location_relocate(xml_node_t *, const char *);
 
-extern void		xml_document_array_init(xml_document_array_t *);
-extern void		xml_document_array_destroy(xml_document_array_t *);
-extern xml_document_array_t *		xml_document_array_new(void);
-extern void		xml_document_array_free(xml_document_array_t *);
-extern void		xml_document_array_append(xml_document_array_t *, xml_document_t *);
-
-extern void		xml_node_array_init(xml_node_array_t *);
-extern void		xml_node_array_destroy(xml_node_array_t *);
-extern void		xml_node_array_append(xml_node_array_t *, xml_node_t *);
-extern xml_node_array_t *xml_node_array_new(void);
-extern void		xml_node_array_free(xml_node_array_t *);
-
 extern xml_node_t*	xml_node_create(xml_node_t *, const char *);
 extern void		xml_node_dict_set(xml_node_t *, const char *, const char *);
 
@@ -185,4 +169,16 @@ xml_document_is_empty(const xml_document_t *doc)
 	return (!doc || xml_node_is_empty(doc->root));
 }
 
-#endif /* __WICKED_XML_H__ */
+extern			ni_declare_ptr_array_init(xml_document);
+extern			ni_declare_ptr_array_destroy(xml_document);
+extern			ni_declare_ptr_array_append(xml_document);
+extern			ni_declare_ptr_array_insert(xml_document);
+extern			ni_declare_ptr_array_index(xml_document);
+
+extern			ni_declare_ptr_array_init(xml_node);
+extern			ni_declare_ptr_array_destroy(xml_node);
+extern			ni_declare_ptr_array_append(xml_node);
+extern xml_node_array_t *xml_node_array_new(void);
+extern void		xml_node_array_free(xml_node_array_t *);
+
+#endif /* NI_WICKED_XML_H */
diff --git a/nanny/main.c b/nanny/main.c
index 6090a63c3..a7fa17937 100644
--- a/nanny/main.c
+++ b/nanny/main.c
@@ -211,21 +211,22 @@ ni_nanny_statedir(void)
 }
 
 static ni_bool_t
-ni_nanny_policy_load(ni_nanny_t *mgr)
+ni_nanny_load_policies(ni_nanny_t *mgr)
 {
+	xml_document_array_t docs = XML_DOCUMENT_ARRAY_INIT;
 	ni_string_array_t files = NI_STRING_ARRAY_INIT;
 	char nanny_dir[PATH_MAX] = { '\0' };
+	xml_document_t *doc;
+	char *path = NULL;
 
 	ni_assert(mgr);
-	ni_debug_application("Loading previously saved policies:");
+	ni_debug_application("Loading previously saved policies");
 
 	snprintf(nanny_dir, sizeof(nanny_dir), "%s", ni_nanny_statedir());
 	if (ni_scandir(nanny_dir, "policy*.xml", &files) != 0) {
 		unsigned int i;
 
 		for (i = 0; i < files.count; ++i) {
-			char *path = NULL;
-			xml_document_t *doc;
 
 			if (!ni_string_printf(&path, "%s/%s", nanny_dir, files.data[i]))
 				continue;
@@ -235,20 +236,56 @@ ni_nanny_policy_load(ni_nanny_t *mgr)
 				ni_string_free(&path);
 				continue;
 			}
+			ni_string_free(&path);
 
-			if (!ni_nanny_create_policy(NULL, mgr, doc, NULL, TRUE)) {
-				ni_error("Unable to create policy from file '%s'", path);
+			if (!xml_document_expand(&docs, doc)) {
+				ni_error("Unable to allocate policy doc array memory: %m");
+				xml_document_free(doc);
+				continue;
 			}
+		}
 
-			ni_string_free(&path);
-			xml_document_free(doc);
+		if (ni_ifxml_migrate_docs(&docs))
+			ni_debug_readwrite("Migrated nanny policies to current schema");
+
+		for (i = 0; i < docs.count; ++i) {
+			doc = docs.data[i];
+			ni_bool_t migrated;
+			const char *name;
+
+			migrated = ni_ifxml_node_is_migrated(doc->root);
+			if (migrated) {
+				name = ni_ifpolicy_get_name(doc->root);
+
+				ni_ifxml_node_set_migrated(doc->root, FALSE);
+				if (ni_nanny_policy_file_path(&path, name) &&
+				    ni_nanny_policy_save(doc->root, path, NI_NANNY_POLICY_BAK)) {
+					ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_READWRITE,
+							"- saved migrated policy to file '%s'",
+							path);
+				}
+				ni_string_free(&path);
+			}
+		}
+
+		for (i = 0; i < docs.count; ++i) {
+			doc = docs.data[i];
+			const char *from;
+
+			if (!ni_nanny_create_policy(NULL, mgr, doc, NULL, TRUE)) {
+				from = xml_node_location_filename(doc->root);
+				ni_error("Unable to create policy from file '%s'",
+						from ? from : "<migrated>");
+			}
 		}
 	}
+	ni_string_array_destroy(&files);
 
-	if (files.count)
+	if (docs.count)
 		ni_nanny_recheck_policies(mgr, NULL);
 
-	ni_string_array_destroy(&files);
+	xml_document_array_destroy(&docs);
+
 	return TRUE;
 }
 
@@ -280,7 +317,7 @@ babysit(void)
 
 	ni_rfkill_open(handle_rfkill_event, mgr);
 	ni_nanny_discover_state(mgr);
-	ni_nanny_policy_load(mgr);
+	ni_nanny_load_policies(mgr);
 
 #ifdef HAVE_SYSTEMD_SD_DAEMON_H
 	if (opt_systemd) {
diff --git a/nanny/nanny.c b/nanny/nanny.c
index ed024684a..9a4fb2f3a 100644
--- a/nanny/nanny.c
+++ b/nanny/nanny.c
@@ -321,7 +321,7 @@ ni_nanny_create_managed_policy(ni_dbus_object_t **policy_object, ni_nanny_t *mgr
 /*
  * Creates nanny policy and register managed policy interface.
  * Return:
- *     -1 - policy node does not exist or is errornous
+ *     -1 - policy node does not exist or is erroneous
  *      0 - policy already exists
  *      1 - policy created and registered
  */
@@ -331,29 +331,33 @@ ni_nanny_create_policy(ni_dbus_object_t **policy_object, ni_nanny_t *mgr, xml_do
 {
 	xml_node_t *root, *pnode = NULL;
 	ni_fsm_policy_t *policy = NULL;
+	char *location = NULL;
 	const char *pname;
 	int rv = -1;
 
 	if (!mgr || !mgr->fsm)
 		return -1;
 
-	if (!doc || xml_document_is_empty(doc)) {
+	if (!doc || !(root = xml_document_root(doc))) {
 		ni_error("Invalid policy document");
 		goto error;
 	}
 
-	root = xml_document_root(doc);
 	if (xml_node_is_empty(root->children)) {
 		ni_error("Policy document is empty");
 		goto error;
 	}
 
-	if (!xml_node_is_empty(root->children->next)) {
-		ni_error("Policy document contains more then one node");
-		goto error;
+	if (ni_string_empty(root->name)) {
+		if (!xml_node_is_empty(root->children->next)) {
+			ni_error("Policy document contains more then one node");
+			goto error;
+		}
+		pnode = root->children;
+	} else {
+		pnode = root;
 	}
 
-	pnode = root->children;
 	if (!ni_ifconfig_is_policy(pnode)) {
 		pname = pnode->name;
 		ni_error("Not a valid policy document node \"%s\"",
@@ -375,12 +379,14 @@ ni_nanny_create_policy(ni_dbus_object_t **policy_object, ni_nanny_t *mgr, xml_do
 		goto error;
 	}
 
-	if (ni_ifconfig_migrate(pnode))
-		ni_debug_nanny("Migrated policy \"%s\" to current schema", pname);
-
 	if (caller_uid)
 		ni_ifpolicy_set_owner_uid(pnode, *caller_uid);
 
+	if (ni_nanny_policy_location(&location, pname)) {
+		xml_node_location_relocate(pnode, location);
+		ni_string_free(&location);
+	}
+
 	if (!(policy = ni_fsm_policy_new(mgr->fsm, pname, pnode))) {
 		ni_error("Unable to create policy object for %s", pname);
 		goto error;
@@ -801,8 +807,10 @@ ni_nanny_process_rename_event(ni_nanny_t *mgr, ni_ifworker_t *w)
 		rebuild = TRUE;
 	}
 
-	if (rebuild)
+	if (rebuild) {
 		ni_fsm_build_hierarchy(mgr->fsm, FALSE);
+		ni_fsm_print_config_hierarchy(mgr->fsm, NULL, NULL);
+	}
 }
 
 static void
@@ -950,23 +958,6 @@ ni_objectmodel_nanny_create_policy(ni_dbus_object_t *object, const ni_dbus_metho
 	return ni_dbus_message_append_object_path(reply, ni_dbus_object_get_path(policy_object));
 }
 
-ni_bool_t
-ni_nanny_policy_drop(const char *pname)
-{
-	char path[PATH_MAX] = {'\0'};
-
-	ni_managed_policy_filename(pname, path, sizeof(path));
-
-	if (unlink(path) < 0) {
-		if (errno == ENOENT)
-			return TRUE;
-
-		ni_error("Cannot remove policy file '%s': %m", path);
-		return FALSE;
-	}
-	return TRUE;
-}
-
 /*
  * Nanny.deletePolicy()
  */
@@ -1150,8 +1141,10 @@ ni_nanny_recheck_policies(ni_nanny_t *mgr, const ni_string_array_t *ifnames)
 		}
 	}
 
-	if (count)
+	if (count) {
 		ni_fsm_build_hierarchy(mgr->fsm, FALSE);
+		ni_fsm_print_config_hierarchy(mgr->fsm, NULL, NULL);
+	}
 }
 
 static dbus_bool_t
diff --git a/nanny/nanny.h b/nanny/nanny.h
index 8f735c9c7..c368784eb 100644
--- a/nanny/nanny.h
+++ b/nanny/nanny.h
@@ -15,6 +15,13 @@
 #include <wicked/secret.h>
 #include "appconfig.h"
 
+#ifndef NI_NANNY_POLICY_XML
+#define NI_NANNY_POLICY_XML	".xml"
+#endif
+#ifndef NI_NANNY_POLICY_BAK
+#define NI_NANNY_POLICY_BAK	".bak"
+#endif
+
 typedef struct ni_nanny		ni_nanny_t;
 typedef struct ni_managed_device ni_managed_device_t;
 typedef struct ni_managed_policy ni_managed_policy_t;
@@ -117,6 +124,11 @@ extern ni_nanny_t *		ni_nanny_new(void);
 extern void			ni_nanny_start(ni_nanny_t *);
 extern void			ni_nanny_free(ni_nanny_t *);
 extern const char *		ni_nanny_statedir(void);
+extern const char *		ni_nanny_policy_location(char **, const char *);
+extern const char *		ni_nanny_policy_file_name(char **, const char *);
+extern const char *		ni_nanny_policy_file_path(char **, const char *);
+extern ni_bool_t		ni_nanny_policy_drop(const char *);
+extern ni_bool_t		ni_nanny_policy_save(const xml_node_t *, const char *, const char *);
 extern void			ni_nanny_recheck_policies(ni_nanny_t *, const ni_string_array_t *);
 extern void			ni_nanny_schedule_recheck(ni_ifworker_array_t *, ni_ifworker_t *);
 extern void			ni_nanny_unschedule(ni_ifworker_array_t *, ni_ifworker_t *);
diff --git a/nanny/policy.c b/nanny/policy.c
index 8c0d2eed5..207fdce0d 100644
--- a/nanny/policy.c
+++ b/nanny/policy.c
@@ -7,17 +7,6 @@
 #include "config.h"
 #endif
 
-#include <sys/poll.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <signal.h>
-#include <getopt.h>
-#include <limits.h>
-#include <errno.h>
-
 #include <wicked/netinfo.h>
 #include <wicked/addrconf.h>
 #include <wicked/logging.h>
@@ -29,83 +18,145 @@
 #include <wicked/dbus-errors.h>
 #include <wicked/fsm.h>
 
-#include "util_priv.h"
 #include "nanny.h"
+#include "util_priv.h"
 #include "client/ifconfig.h"
 
-ni_bool_t
-ni_managed_policy_filename(const char *name, char *path, size_t size)
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+const char *
+ni_nanny_policy_location(char **location, const char *name)
 {
-	if (path && !ni_string_empty(name)) {
-		snprintf(path, size, "%s/%s.xml", ni_nanny_statedir(), name);
-		return TRUE;
-	}
+	if (!location || ni_string_empty(name))
+		return NULL;
 
-	return FALSE;
+	return ni_string_printf(location, "<%s>", name);
 }
 
-static ni_bool_t
-ni_managed_policy_save_node(const xml_node_t *pnode)
+const char *
+ni_nanny_policy_file_name(char **file, const char *name)
+{
+	if (!file || ni_string_empty(name))
+		return NULL;
+
+	return ni_string_printf(file, "%s" NI_NANNY_POLICY_XML, name);
+}
+
+const char *
+ni_nanny_policy_file_path(char **path, const char *name)
+{
+	const char *dir = ni_nanny_statedir();
+
+	if (!path || ni_string_empty(name) || ni_string_empty(dir))
+		return NULL;
+
+	return ni_string_printf(path, "%s/%s" NI_NANNY_POLICY_XML, dir, name);
+}
+
+ni_bool_t
+ni_nanny_policy_drop(const char *name)
 {
-	char path[PATH_MAX - sizeof(".XXXXXX")] = {'\0'};
-	char temp[PATH_MAX] = {'\0'};
-	const char *pname;
-	FILE *fp;
-	int fd;
+	char *path = NULL;
 
-	if (xml_node_is_empty(pnode))
+	if (!ni_nanny_policy_file_path(&path, name)) {
+		ni_error("Cannot construct path to remove policy '%s'", name);
 		return FALSE;
+	}
 
-	pname = ni_ifpolicy_get_name(pnode);
-	if (ni_string_empty(pname))
+	if (unlink(path) < 0 && errno != ENOENT) {
+		ni_error("Cannot remove policy file '%s': %m", path);
+		ni_string_free(&path);
 		return FALSE;
+	} else {
+		ni_string_free(&path);
+		return TRUE;
+	}
+}
 
-	ni_managed_policy_filename(pname, path, sizeof(path));
-	snprintf(temp, sizeof(temp), "%s.XXXXXX", path);
+ni_bool_t
+ni_nanny_policy_save(const xml_node_t *policy, const char *path, const char *bak)
+{
+	char *tmp = NULL;
+	char *old = NULL;
+	FILE *fp = NULL;
+	int fd = -1;
 
-	if ((fd = mkstemp(temp)) < 0) {
-		ni_error("Cannot create %s policy temp file", path);
+	if (xml_node_is_empty(policy) || ni_string_empty(path))
 		return FALSE;
+
+	if (!ni_string_empty(bak)) {
+		if (!ni_string_printf(&old, "%s%s", path, bak)) {
+			ni_error("Cannot construct policy backup file name '%s%s'",
+					path, bak);
+			goto failure;
+		}
+	}
+
+	if (!ni_string_printf(&tmp, "%s.XXXXXX", path))
+		goto failure;
+
+	if ((fd = mkstemp(tmp)) < 0) {
+		ni_error("Cannot create temporary policy file '%s': %m", path);
+		goto failure;
 	}
 
 	if (!(fp = fdopen(fd, "we"))) {
 		close(fd);
-		ni_error("Cannot create %s policy temp file", path);
+		ni_error("Cannot open temporary policy file '%s': %m", path);
 		goto failure;
 	}
 
-	if (xml_node_print(pnode, fp) < 0) {
-		ni_error("Cannot write into %s policy temp file", path);
+	if (xml_node_print(policy, fp) < 0) {
+		ni_error("Cannot write into temporary policy file '%s'", path);
 		goto failure;
+	} else {
+		fclose(fp);
+		fp = NULL;
 	}
 
-	if (rename(temp, path) < 0) {
-		ni_error("Cannot move temp file to policy file %s", path);
+	if (old && rename(path, old) < 0 && errno != ENOENT)
+		ni_warn("Cannot create policy backup file '%s': %m", old);
+
+	if (rename(tmp, path) < 0) {
+		ni_error("Cannot move temporary file to policy file '%s'", path);
 		goto failure;
 	}
 
-	fclose(fp);
-
 	return TRUE;
 
 failure:
-	if (fp) {
+	if (fp)
 		fclose(fp);
-	}
-	unlink(temp);
+	if (tmp)
+		unlink(tmp);
+	ni_string_free(&tmp);
+	ni_string_free(&old);
 	return FALSE;
 }
 
 static ni_bool_t
 ni_managed_policy_save(const ni_managed_policy_t *mpolicy)
 {
-	const xml_node_t *node;
+	const xml_node_t *policy;
+	const char *name;
+	char *path = NULL;
+	ni_bool_t ret;
 
 	if (!mpolicy)
 		return FALSE;
 
-	node = ni_fsm_policy_node(mpolicy->fsm_policy);
-	return ni_managed_policy_save_node(node);
+	if (!(policy = ni_fsm_policy_node(mpolicy->fsm_policy)))
+		return FALSE;
+
+	name = ni_ifpolicy_get_name(policy);
+	if (!(ni_nanny_policy_file_path(&path, name)))
+		return FALSE;
+
+	ret = ni_nanny_policy_save(policy, path, NULL);
+	ni_string_free(&path);
+	return ret;
 }
 
 void
diff --git a/schema/Makefile.am b/schema/Makefile.am
index 462dc0449..258708044 100644
--- a/schema/Makefile.am
+++ b/schema/Makefile.am
@@ -8,7 +8,7 @@ wicked_schema_DATA		= \
 
 wicked_schema_files		= \
 	addrconf.xml		\
-	bonding.xml		\
+	bond.xml		\
 	bridge.xml		\
 	ethtool.xml		\
 	ethernet.xml		\
diff --git a/schema/bond.xml b/schema/bond.xml
new file mode 100644
index 000000000..08bd9f43c
--- /dev/null
+++ b/schema/bond.xml
@@ -0,0 +1,130 @@
+<service name="bond" interface="org.opensuse.Network.Bond" object-class="netif-bond">
+  <description>
+    This link-layer service is provided by all bond network interfaces.
+
+    A bond can be created through the org.opensuse.Network.Bond.Factory
+    factory service interface.
+  </description>
+
+  <define name="port-config" class="dict"
+          description="Bond port (link-request) configuration">
+
+    <queue-id type="uint16"/>
+
+  </define>
+
+  <define name="port-info" class="dict"
+          description="Bond port specific interface info properties">
+
+    <state type="builtin-bonding-port-state"/>
+    <mii-status type="builtin-bonding-port-mii-status"/>
+    <link-failures type="uint32"/>
+    <perm-hwaddr type="ethernet-address"/>
+    <queue-id type="uint16"/>
+    <ad-aggregator-id type="uint16"/>
+
+  </define>
+
+  <define name="bond-config" class="dict"
+          description="Bond specific interface configuration">
+
+    <mode type="builtin-bonding-mode"/>
+
+    <!-- Link monitoring options.
+         There must be exactly one of either arpmon or miimon present,
+         which selects either mii or arp monitoring. Lack of both is an
+         error. If both are present, the behavior is undefined.
+     -->
+    <arpmon class="dict">
+      <interval type="uint32" constraint="required" description="ARP link monitoring frequency in milliseconds." />
+      <validate type="builtin-bonding-arp-validate"/>
+      <validate-targets type="uint32" constraint="enum">
+        <any value="0"/>
+        <all value="1"/>
+      </validate-targets>
+      <!-- targets class="array" element-type="ipv4-address" minlen="1"/ -->
+      <targets class="array" constraint="required" minlen="1" element-type="string" element-name="address" />
+    </arpmon>
+
+    <miimon class="dict">
+      <frequency type="uint32" constraint="required" description="Specifies the MII link monitoring frequency in milliseconds.
+             This determines how often the link state of each port is inspected for link failures."/>
+      <updelay type="uint32" description="Specifies the time, in milliseconds, to wait before enabling a
+             port after a link recovery has been detected."/>
+      <downdelay type="uint32" description="Specifies the time, in milliseconds, to wait before disabling
+	     a port after a link failure has been detected."/>
+      <carrier-detect type="builtin-bonding-mii-carrier-detect"/>
+    </miimon>
+
+    <xmit-hash-policy type="builtin-bonding-xmit-hash-policy"/>
+    <lacp-rate type="builtin-bonding-lacp-rate"/>
+    <ad-select type="builtin-bonding-ad-select"/>
+    <ad-user-port-key  type="uint16"/>
+    <ad-actor-sys-prio type="uint16"/>
+    <ad-actor-system   type="ethernet-address"/>
+    <fail-over-mac type="builtin-bonding-fail-over-mac"/>
+    <primary type="string"/>
+    <primary-reselect type="builtin-bonding-primary-reselect"/>
+
+    <all-slaves-active type="boolean" description="Enable to deliver duplicate frames received on inactive ports when enabled instead of dropping them." />
+    <min-links type="uint32" description="Specifies the minimum number of links that must be active before asserting carrier."/>
+    <num-grat-arp type="uint32" constraint="range" description="Specify the number of peer notifications using gratuitous ARPs to be issued after a failover event. Default is 1">
+      <min value="0"/>
+      <max value="255"/>
+    </num-grat-arp>
+    <num-unsol-na type="uint32" constraint="range" description="Specify the number of peer notifications usingunsolicited IPv6 Neighbor Advertisements to be issued after a failover event. Default is 1">
+      <min value="0"/>
+      <max value="255"/>
+    </num-unsol-na>
+    <resend-igmp type="uint32" constraint="range" description="Specify the number of IGMP membership reports to be issued after a failover event.">
+      <min value="0"/>
+      <max value="255"/>
+    </resend-igmp>
+    <packets-per-slave type="uint32" constraint="range">
+      <min value="0"/>
+      <max value="65535"/>
+    </packets-per-slave>
+    <tlb-dynamic-lb type="boolean"/>
+    <lp-interval type="uint32" constraint="range">
+      <min value="1"/>
+      <max value="2147483647"/>
+    </lp-interval>
+
+  </define>
+
+  <define name="configuration" class="dict" extends="bond:bond-config"
+          description="Bond interface (changeDevice) configuration">
+
+    <address class="array" element-type="byte" minlen="6" maxlen="20" notation="hwaddr"/>
+  </define>
+
+  <!-- the bond properties are currently same as configuration -->
+  <define name="properties" class="dict" extends="bond:bond-config"
+          description="Bond interface (service) state info properties">
+
+    <address class="array" element-type="byte" minlen="6" maxlen="20" notation="hwaddr"/>
+  </define>
+
+  <method name="changeDevice">
+    <arguments>
+      <config type="bond:configuration">
+        <meta:mapping document-node="/bond"/>
+      </config>
+    </arguments>
+  </method>
+
+</service>
+
+<service name="bond-factory" interface="org.opensuse.Network.Bond.Factory" object-class="netif-list">
+  <method name="newDevice">
+    <arguments>
+      <name type="string"/>
+      <config type="bond:configuration">
+        <meta:mapping document-node="/bond"/>
+      </config>
+    </arguments>
+    <return>
+      <string/>
+    </return>
+  </method>
+</service>
diff --git a/schema/bonding.xml b/schema/bonding.xml
deleted file mode 100644
index dc141f9ef..000000000
--- a/schema/bonding.xml
+++ /dev/null
@@ -1,156 +0,0 @@
-<service name="bonding" interface="org.opensuse.Network.Bond" object-class="netif-bond">
- <description>
-   This link-layer service is provided by all bonding devices.
-
-   Bond devices can be created through the org.opensuse.Network.Bond.Factory
-   factory interface.
- </description>
-
- <!-- This is the device-info returned in interface reports -->
- <define name="device-info" class="dict">
-   <mode type="builtin-bonding-mode"/>
-   <!-- At least one slave element has to occur.
-        Bonding requires at least one available slave at start time,
-        other slaves can be added later (hotplugged).
-
-        At most one of them may be marked as primary or active in
-        active-backup, balance-tlb or balance-alb modes. This forces
-        the use as primary or active and overwries kernels choice.
-     -->
-   <define name="slave" class="dict">
-     <device type="string">
-       <meta:netif-reference shared="false" />
-       <meta:require check="netif-check-state" op="linkUp" min-state="device-up" />
-     </device>
-
-     <active type="boolean"/>
-     <primary type="boolean"/>
-
-     <state type="builtin-bonding-slave-state"/>
-     <mii-status type="builtin-bonding-slave-mii-status"/>
-     <perm-hwaddr type="ethernet-address"/>
-     <link-failures type="uint32"/>
-     <queue-id type="uint16"/>
-     <ad-aggregator-id type="uint16"/>
-   </define>
-   <slaves class="array" element-type="slave" constraint="required" minlen="1" />
-
-   <!-- Link monitoring options.
-        There must be exactly one of either arpmon or miimon present,
-        which selects either mii or arp monitoring. Lack of both is an
-        error. If both are present, the behavior is undefined.
-     -->
-   <arpmon class="dict">
-     <interval type="uint32" constraint="required" description="ARP link monitoring frequency in milliseconds." />
-     <validate type="builtin-bonding-arp-validate"/>
-     <validate-targets type="uint32" constraint="enum">
-       <any value="0"/>
-       <all value="1"/>
-     </validate-targets>
-     <!-- targets class="array" element-type="ipv4-address" minlen="1"/ -->
-     <targets class="array" constraint="required" minlen="1" element-type="string" element-name="address" />
-   </arpmon>
-
-   <miimon class="dict">
-     <frequency type="uint32" constraint="required" description="Specifies the MII link monitoring frequency in milliseconds.
-             This determines how often the link state of each slave is inspected for link failures."/>
-     <updelay type="uint32" description="Specifies the time, in milliseconds, to wait before enabling a
-             slave after a link recovery has been detected."/>
-     <downdelay type="uint32" description="Specifies the time, in milliseconds, to wait before disabling
-	     a slave after a link failure has been detected."/>
-     <carrier-detect type="builtin-bonding-mii-carrier-detect"/>
-   </miimon>
-
-   <xmit-hash-policy type="builtin-bonding-xmit-hash-policy"/>
-   <lacp-rate type="builtin-bonding-lacp-rate"/>
-   <ad-select type="builtin-bonding-ad-select"/>
-   <ad-user-port-key  type="uint16"/>
-   <ad-actor-sys-prio type="uint16"/>
-   <ad-actor-system   type="ethernet-address"/>
-   <fail-over-mac type="builtin-bonding-fail-over-mac"/>
-   <primary-reselect type="builtin-bonding-primary-reselect"/>
-
-   <all-slaves-active type="boolean" description="Enable to deliver duplicate frames received on inactive slaves when enabled instead of dropping them." />
-   <min-links type="uint32" description="Specifies the minimum number of links that must be active before asserting carrier."/>
-   <num-grat-arp type="uint32" constraint="range" description="Specify the number of peer notifications using gratuitous ARPs to be issued after a failover event. Default is 1">
-    <min value="0"/>
-    <max value="255"/>
-   </num-grat-arp>
-   <num-unsol-na type="uint32" constraint="range" description="Specify the number of peer notifications usingunsolicited IPv6 Neighbor Advertisements to be issued after a failover event. Default is 1">
-     <min value="0"/>
-     <max value="255"/>
-   </num-unsol-na>
-   <resend-igmp type="uint32" constraint="range" description="Specify the number of IGMP membership reports to be issued after a failover event.">
-     <min value="0"/>
-     <max value="255"/>
-   </resend-igmp>
-   <packets-per-slave type="uint32" constraint="range">
-     <min value="0"/>
-     <max value="65535"/>
-   </packets-per-slave>
-   <tlb-dynamic-lb type="boolean"/>
-   <lp-interval type="uint32" constraint="range">
-     <min value="1"/>
-     <max value="2147483647"/>
-   </lp-interval>
-
-   <address class="array" element-type="byte" minlen="6" maxlen="20" notation="hwaddr"/>
- </define>
-
- <define name="properties" type="device-info"/>
-
- <method name="changeDevice">
-  <arguments>
-    <config type="bonding:device-info">
-      <meta:mapping document-node="/bond"/>
-    </config>
-  </arguments>
- </method>
-
-<!-- Later, not implemented yet
- <method name="addSlave">
-  <arguments>
-   <name type="string" />
-  </arguments>
- </method>
--->
-
-<!-- Later, not implemented yet
- <method name="removeSlave">
-  <arguments>
-   <name type="string" />
-  </arguments>
- </method>
--->
-
-<!-- Later, not implemented yet
- <method name="setActive">
-  <arguments>
-   <name type="string" />
-  </arguments>
- </method>
--->
-
-<!-- Later, not implemented yet:
- <method name="setPrimary">
-  <arguments>
-   <name type="string" />
-  </arguments>
- </method>
--->
-
-</service>
-
-<service name="bonding-factory" interface="org.opensuse.Network.Bond.Factory" object-class="netif-list">
-  <method name="newDevice">
-    <arguments>
-     <name type="string"/>
-     <config type="bonding:device-info">
-      <meta:mapping document-node="/bond"/>
-     </config>
-    </arguments>
-    <return>
-     <string/>
-    </return>
-  </method>
-</service>
diff --git a/schema/bridge.xml b/schema/bridge.xml
index f2385e528..050fe5915 100644
--- a/schema/bridge.xml
+++ b/schema/bridge.xml
@@ -1,51 +1,36 @@
-
 <service name="bridge" interface="org.opensuse.Network.Bridge" object-class="netif-bridge">
 
-  <!-- This is the port-config containing the port configuration -->
   <define name="port-config" class="dict"
-	  description="Bridge port configuration properties">
-    <!-- The port interface.
-         It's marked as a netif-reference, meaning that the device name is
-         optionally resolved by wicked at run time. You can use "classic"
-         device names such as eth0, but you can also use and <identify>
-         element to use any of the supported netif naming services
-      -->
-    <device type="string">
-      <meta:netif-reference shared="false"/>
-      <meta:require check="netif-check-state" op="linkUp" min-state="device-up" />
-    </device>
+	        description="Bridge port (link-request) configuration">
 
     <priority type="uint32" constraint="range">
       <min value="0"/>
       <max value="63"/>
     </priority>
     <path-cost type="uint32" />
+
   </define>
 
-  <!-- This is the port-status returned in bridge interface reports -->
-  <define name="port-status" class="dict" extends="bridge:port-config"
-          description="Bridge port configuration and status properties">
-     <!-- The following fields contain status information only,
-          and should not appear in configuration files.
-          These values are ignored when creating a bridge port.
-     -->
-
-    <!-- FIXME: state is really asking for an enum -->
-    <state type="uint32" />
-    <port-no type="uint32" />
-    <port-id type="uint32" />
+  <define name="port-info" class="dict"
+          description="Bridge port specific interface info properties">
+
+    <state      type="builtin-bridge-port-state" />
+    <port-no    type="uint32" />
+    <port-id    type="uint32" />
+    <priority   type="uint32" />
+    <path-cost  type="uint32" />
+
   </define>
 
-  <!-- This is the bridge config without ports used to configure the bridge -->
   <define name="bridge-config" class="dict"
-          description="Bridge interface configuration properties">
+          description="Bridge specific interface configuration properties">
 
     <!-- STP protocol related settings -->
     <stp type="boolean"
-     description="Enables Spanning Tree Protocol to handle multiple hops and avoid cyclic routes"/>
+         description="Enables Spanning Tree Protocol to handle multiple hops and avoid cyclic routes"/>
 
     <priority type="uint32" constraint="range"
-      description="The bridge with the lowest priority will be elected as the 'central' root bridge in the spanning tree.">
+              description="The bridge with the lowest priority will be elected as the 'central' root bridge in the spanning tree.">
       <min value="0"/>
       <max value="65535"/>
     </priority>
@@ -58,17 +43,18 @@
     <gc-interval type="double"   description="Garbage collection interval for the bridge" />
   </define>
 
-  <!-- This are bridge configuration properties inclusive ports -->
   <define name="configuration" class="dict" extends="bridge:bridge-config"
-          description="Bridge and port configuration properties">
-    <ports class="array" element-type="bridge:port-config" element-name="port" />
+          description="Bridge interface (changeDevice) configuration">
+
+    <!-- additional configurables supported by a bridge changeDevice() -->
+
     <address  type="ethernet-address"/>
   </define>
 
-  <!-- This are all bridge properties returned in service interface reports -->
   <define name="properties" class="dict" extends="bridge:bridge-config"
-          description="Bridge configuration and status properties">
+          description="Bridge interface (service) state info properties">
 
+    <!-- actual stp state reported by the kernel driver -->
     <stp-state type="uint32" constraint="enum">
       <none          value="0" description="STP is disabled"/>
       <kernel-stp    value="1" description="Kernel built-in STP implementation"/>
@@ -77,7 +63,6 @@
 
     <!-- we may implement more status information properties here -->
 
-    <ports class="array" element-type="bridge:port-status" element-name="port" />
     <address  type="ethernet-address"/>
   </define>
 
@@ -90,7 +75,7 @@
            call to changeDevice() if that element isn't there.
         -->
       <config type="bridge:configuration">
-       <meta:mapping document-node="/bridge" skip-unless-present="true" />
+        <meta:mapping document-node="/bridge" skip-unless-present="true" />
       </config>
     </arguments>
   </method>
@@ -99,29 +84,18 @@
     <!-- no arguments, no return code -->
   </method>
 
-  <method name="addPort">
-    <arguments>
-      <port type="bridge:port-config"/>
-    </arguments>
-  </method>
-
-  <method name="removePort">
-    <arguments>
-      <port type="string"/>
-    </arguments>
-  </method>
 </service>
 
 <service name="bridge-factory" interface="org.opensuse.Network.Bridge.Factory" object-class="netif-list">
   <method name="newDevice">
     <arguments>
-     <name type="string"/>
-     <config type="bridge:configuration">
-      <meta:mapping document-node="/bridge"/>
-     </config>
+      <name type="string"/>
+      <config type="bridge:configuration">
+        <meta:mapping document-node="/bridge"/>
+      </config>
     </arguments>
     <return>
-     <string/>
+      <string/>
     </return>
   </method>
 </service>
diff --git a/schema/constants.xml.in b/schema/constants.xml.in
index 8f3f9e3b7..705565528 100644
--- a/schema/constants.xml.in
+++ b/schema/constants.xml.in
@@ -92,16 +92,16 @@
 <define name="builtin-bonding-mii-carrier-detect" type="uint32" constraint="enum">
   <description>
     Specifies whether the MII monitor will use the netif network interface carrier
-    or the deprecated MII / ETHTOOL ioctls to determine the status of the slaves.
+    or the deprecated MII / ETHTOOL ioctls to determine the status of the ports.
     Default is to query netif carrier, but when the MII monitor fails to detect
-    carrier loss (e.g. disconnected cable) of a slave or it appears that the MII
+    carrier loss (e.g. disconnected cable) of a port or it appears that the MII
     monitor is very slow in detecting link failures, try specifying ioctl.
   </description>
   <@@BONDING_MII_CARRIER_DETECT_NAME@@ value="@@BONDING_MII_CARRIER_DETECT_VALUE@@"/>
 </define>
 <define name="builtin-bonding-xmit-hash-policy" type="uint32" constraint="enum">
   <description>
-    Selects the transmit hash policy to use for slave selection in balance-xor
+    Selects the transmit hash policy to use for port selection in balance-xor
     and 802.3ad modes. The policies layer2 and layer23 are ieee802-3ad compliant.
   </description>
   <@@BONDING_XMIT_HASH_NAME@@ value="@@BONDING_XMIT_HASH_VALUE@@"/>
@@ -121,27 +121,32 @@
 </define>
 <define name="builtin-bonding-fail-over-mac" type="uint32" constraint="enum">
   <description>
-    Specifies whether active-backup mode should set all slaves to the same
-    MAC address at enslavement, or perform special handling of the bond's
+    Specifies whether active-backup mode should set all ports to the same
+    MAC address at linking time, or perform special handling of the bond's
     MAC address in accordance with the selected policy.
   </description>
   <@@BONDING_FAIL_OVER_MAC_NAME@@ value="@@BONDING_FAIL_OVER_MAC_VALUE@@"/>
 </define>
 <define name="builtin-bonding-primary-reselect" type="uint32" constraint="enum">
   <description>
-    Specifies the reselection policy for the primary slave. This affects how
-    the primary slave is chosen to become the active slave when failure of
-    the active slave or recovery of the primary slave occurs. This option
-    is designed to prevent flip-flopping between the primary slave and other
-    slaves.
+    Specifies the reselection policy for the primary port. This affects how
+    the primary port is chosen to become the active port when failure of
+    the active port or recovery of the primary port occurs. This option
+    is designed to prevent flip-flopping between the primary port and other
+    ports.
   </description>
   <@@BONDING_PRIMARY_RESELECT_NAME@@ value="@@BONDING_PRIMARY_RESELECT_VALUE@@"/>
 </define>
-<define name="builtin-bonding-slave-state" type="uint32" constraint="enum">
-  <@@BONDING_SLAVE_STATE_NAME@@ value="@@BONDING_SLAVE_STATE_VALUE@@"/>
+<define name="builtin-bonding-port-state" type="uint32" constraint="enum">
+  <@@BONDING_PORT_STATE_NAME@@ value="@@BONDING_PORT_STATE_VALUE@@"/>
 </define>
-<define name="builtin-bonding-slave-mii-status" type="uint32" constraint="enum">
-  <@@BONDING_SLAVE_MII_STATUS_NAME@@ value="@@BONDING_SLAVE_MII_STATUS_VALUE@@"/>
+<define name="builtin-bonding-port-mii-status" type="uint32" constraint="enum">
+  <@@BONDING_PORT_MII_STATUS_NAME@@ value="@@BONDING_PORT_MII_STATUS_VALUE@@"/>
+</define>
+
+<!-- Built-in bridge & port constants -->
+<define name="builtin-bridge-port-state" type="uint32" constraint="enum">
+  <@@BRIDGE_PORT_STATE_NAME@@ value="@@BRIDGE_PORT_STATE_VALUE@@" />
 </define>
 
 <!-- Built-in route type/scope/table/protocol -->
diff --git a/schema/gre.xml b/schema/gre.xml
index f7b9f25b6..facb2f9c0 100644
--- a/schema/gre.xml
+++ b/schema/gre.xml
@@ -14,6 +14,8 @@
     <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
     <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
     <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+    <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+    <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
   </device>
   <local-address   type="ipv4-address"/>
   <remote-address  type="ipv4-address"/>
diff --git a/schema/infiniband.xml b/schema/infiniband.xml
index 2b99cb645..e21951133 100644
--- a/schema/infiniband.xml
+++ b/schema/infiniband.xml
@@ -2,7 +2,7 @@
      Infiniband Service definition
   -->
 <service name="infiniband" interface="org.opensuse.Network.Infiniband" object-class="netif-infiniband">
-      
+
   <define name="configuration" class="dict" description="Infiniband interface configuration">
 
     <mode      type="builtin-infiniband-mode"
@@ -37,6 +37,8 @@
       <meta:netif-reference shared="true"/>
       <meta:require check="netif-check-state" op="newDevice" min-state="device-setup" />
       <meta:require check="netif-check-state" op="linkUp" min-state="device-up" />
+      <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+      <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
     </device>
 
     <pkey type="uint16" description="Infiniband partition key"/>
diff --git a/schema/interface.xml b/schema/interface.xml
index b4e0f72fb..ce67f0a28 100644
--- a/schema/interface.xml
+++ b/schema/interface.xml
@@ -147,7 +147,17 @@
  <txqlen type="uint32"/>
  <mtu type="uint32"/>
  <alias type="string"/>
- <master type="string"/>
+ <master type="string">
+   <meta:netif-reference subordinate="true" />
+   <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+   <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
+ </master>
+ <port class="union" switch="type">
+   <bond       type="bond:port-info" />
+   <team       type="team:port-info" />
+   <bridge     type="bridge:port-info" />
+   <ovs-bridge type="ovs-bridge:port-info" />
+ </port>
 
  <!-- General ipv4 and ipv6 specific settings -->
  <ipv4 type="af-info"/>
@@ -227,7 +237,9 @@
      <meta:require check="netif-check-state" op="linkUp" min-state="firewall-up" />
    </master>
    <port class="union" switch="type">
+     <bond       type="bond:port-config" />
      <team       type="team:port-config" />
+     <bridge     type="bridge:port-config" />
      <ovs-bridge type="ovs-bridge:port-config" />
    </port>
 
diff --git a/schema/ipip.xml b/schema/ipip.xml
index ae5df53c6..cfbcaa354 100644
--- a/schema/ipip.xml
+++ b/schema/ipip.xml
@@ -14,6 +14,8 @@
     <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
     <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
     <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+    <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+    <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
   </device>
   <local-address   type="ipv4-address"/>
   <remote-address  type="ipv4-address"/>
diff --git a/schema/macvlan.xml b/schema/macvlan.xml
index 70857c2b5..8f4f53b22 100644
--- a/schema/macvlan.xml
+++ b/schema/macvlan.xml
@@ -19,6 +19,8 @@
     <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
     <meta:require check="netif-check-state" op="linkUp"    min-state="link-up" />
     <meta:require check="netif-check-state" op="login"     min-state="link-authenticated" />
+    <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+    <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
   </device>
   <address  type="ethernet-address"/>
   <mode     type="builtin-macvlan-mode"/>
diff --git a/schema/ovs-bridge.xml b/schema/ovs-bridge.xml
index d4eb8e3bf..d0355a322 100644
--- a/schema/ovs-bridge.xml
+++ b/schema/ovs-bridge.xml
@@ -4,39 +4,42 @@
 
 <service name="ovs-bridge" interface="org.opensuse.Network.OVSBridge" object-class="netif-ovs-bridge">
 
-  <define name="port-config" class="dict">
-    <bridge type="string" >
-      <meta:netif-reference supplemental="true" subordinate="true" />
-      <meta:require check="netif-check-state" op="linkUp" min-state="firewall-up" />
-    </bridge>
+  <define name="port-config" class="dict"
+          description="OVS Bridge port specific configuration properties">
   </define>
 
-  <define name="port-device" class="dict">
-    <device type="string">
-      <meta:netif-reference supplemental="true" shared="false"/>
-      <meta:require check="netif-check-state" op="linkUp" min-state="device-up" />
-    </device>
+  <define name="port-info" class="dict"
+          description="OVS Bridge port specific interface info properties">
   </define>
 
-  <define name="bridge-config" class="dict">
+  <define name="bridge-config" class="dict"
+          description="OVS Bridge specific interface configuration properties">
+
     <vlan class="dict">
       <parent type="string">
-        <meta:netif-reference supplemental="true" shared="true" />
+        <meta:netif-reference shared="true" />
         <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
         <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+        <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+        <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
       </parent>
       <tag type="uint16" />
     </vlan>
-    <ports class="array" element-type="ovs-bridge:port-device" element-name="port" />
+
   </define>
 
-  <define name="configuration" class="dict" extends="ovs-bridge:bridge-config"/>
-  <define name="properties" class="dict" extends="ovs-bridge:bridge-config"/>
+  <define name="configuration" class="dict" extends="ovs-bridge:bridge-config"
+          description="OVS Bridge interface (changeDevice) configuration">
+  </define>
+
+  <define name="properties" class="dict" extends="ovs-bridge:bridge-config"
+          description="OVS Bridge interface (service) state info properties">
+  </define>
 
   <method name="changeDevice">
     <arguments>
       <config type="ovs-bridge:configuration">
-       <meta:mapping document-node="/ovs-bridge" skip-unless-present="true" />
+        <meta:mapping document-node="/ovs-bridge" skip-unless-present="true" />
       </config>
     </arguments>
   </method>
diff --git a/schema/ppp.xml b/schema/ppp.xml
index 11c6220a1..76200dc23 100644
--- a/schema/ppp.xml
+++ b/schema/ppp.xml
@@ -12,6 +12,8 @@
       <meta:require check="netif-check-state" op="newDevice" min-state="link-up" />
       <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
       <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+      <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+      <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
     </device>
   </define>
 
@@ -64,7 +66,7 @@
     <dns          type="dns-config"/>
     <defaultroute type="boolean"/>
 
-    <auth         type="auth-config"/>  
+    <auth         type="auth-config"/>
     <ipv4         type="ipv4-config"/>
     <ipv6         type="ipv6-config"/>
   </define>
diff --git a/schema/sit.xml b/schema/sit.xml
index 5639a9a89..c471e8a08 100644
--- a/schema/sit.xml
+++ b/schema/sit.xml
@@ -22,6 +22,8 @@
     <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
     <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
     <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+    <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+    <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
   </device>
   <local-address   type="ipv4-address"/>
   <remote-address  type="ipv4-address"/>
diff --git a/schema/team.xml b/schema/team.xml
index dcfa46d68..d5cd1acef 100644
--- a/schema/team.xml
+++ b/schema/team.xml
@@ -1,10 +1,9 @@
 <service name="team" interface="org.opensuse.Network.Team" object-class="netif-team">
- <description>
-   This link-layer service is provided by all team devices.
+  <description>
+    This link-layer service is provided by all team network interfaces.
 
-   Team devices can be created through the org.opensuse.Network.Team.Factory
-   factory interface.
- </description>
+    Team interfaces can be created through the org.opensuse.Network.Team.Factory.
+  </description>
 
   <define name="tx-balancer-type" class="dict">
     <name               type="builtin-team-tx-balancer-type" />
@@ -77,14 +76,30 @@
     <lacp_prio   type="uint32" />
   </define>
 
-  <define name="port-device" class="dict" extends="team:port-config">
-    <device type="string">
-      <meta:netif-reference shared="false" />
-      <meta:require check="netif-check-state" op="linkUp" min-state="device-up" />
-    </device>
+  <define name="port-info" class="dict"
+          description="Team port specific interface info properties">
+
+    <runner       class="union" switch="name">
+      <roundrobin    class="dict"/>
+      <activebackup  class="dict"/>
+      <loadbalance   class="dict"/>
+      <broadcast     class="dict"/>
+      <random        class="dict"/>
+      <lacp          class="dict">
+        <aggregator-id type="uint16"/>
+        <selected      type="boolean"/>
+        <state         type="string"/>
+      </lacp>
+    </runner>
+    <link_watches    class="dict">
+      <up              type="boolean" />
+    </link_watches>
+
   </define>
 
-  <define name="device-info" class="dict">
+  <define name="team-config" class="dict"
+          description="Team specific interface configuration properties">
+
     <debug_level  type="uint32"/>
     <notify_peers class="dict">
       <count        type="uint32"/>
@@ -112,16 +127,28 @@
       </watch>
     </link_watch>
 
-    <ports class="array" element-type="team:port-device" element-name="port" />
+  </define>
+
+  <define name="configuration" class="dict" extends="team:team-config"
+          description="Team interface (changeDevice) configuration">
+
+    <!-- additional non-team specific changeDevice() configuratio -->
+
+    <address type="ethernet-address"/>
+  </define>
+
+  <define name="properties" class="dict" extends="team:team-config"
+          description="Team interface (service) state info properties">
+
+    <!-- additional non-team specific team service properties -->
 
     <address type="ethernet-address"/>
   </define>
 
-  <define name="properties" type="device-info"/>
 
   <method name="changeDevice">
     <arguments>
-      <config type="team:device-info">
+      <config type="team:configuration">
         <meta:mapping document-node="/team"/>
       </config>
     </arguments>
@@ -130,15 +157,17 @@
 </service>
 
 <service name="team-factory" interface="org.opensuse.Network.Team.Factory" object-class="netif-list">
+
   <method name="newDevice">
     <arguments>
-     <name type="string"/>
-     <config type="team:device-info">
-      <meta:mapping document-node="/team"/>
-     </config>
+      <name type="string"/>
+      <config type="team:configuration">
+        <meta:mapping document-node="/team"/>
+      </config>
     </arguments>
     <return>
-     <string/>
+      <string/>
     </return>
   </method>
+
 </service>
diff --git a/schema/vlan.xml b/schema/vlan.xml
index 29fe6ab94..094a63419 100644
--- a/schema/vlan.xml
+++ b/schema/vlan.xml
@@ -14,6 +14,8 @@
     <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
     <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
     <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+    <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+    <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
   </device>
   <address  type="ethernet-address"/>
   <protocol type="builtin-vlan-protocol"/>
diff --git a/schema/vxlan.xml b/schema/vxlan.xml
index e7cac1ecd..7d6a9da84 100644
--- a/schema/vxlan.xml
+++ b/schema/vxlan.xml
@@ -17,6 +17,8 @@
      <meta:require check="netif-check-state" op="newDevice" min-state="device-up" />
      <meta:require check="netif-check-state" op="login" min-state="link-authenticated" />
      <meta:require check="netif-check-state" op="linkUp" min-state="link-up" />
+     <meta:require check="netif-check-state" op="linkDown" max-state="firewall-up" />
+     <meta:require check="netif-check-state" op="deleteDevice" max-state="device-down" />
    </device>
 
    <id                type="uint32"/>
diff --git a/schema/wicked.xml b/schema/wicked.xml
index ab42e5260..b44de0aae 100644
--- a/schema/wicked.xml
+++ b/schema/wicked.xml
@@ -15,7 +15,7 @@
 <include name="macvlan.xml"/>
 <include name="dummy.xml"/>
 <include name="team.xml"/>
-<include name="bonding.xml"/>
+<include name="bond.xml"/>
 <include name="bridge.xml"/>
 <include name="infiniband.xml"/>
 <include name="ovs-bridge.xml"/>
diff --git a/src/bonding.c b/src/bonding.c
index 05882f993..ec51507dc 100644
--- a/src/bonding.c
+++ b/src/bonding.c
@@ -1,7 +1,21 @@
 /*
- * Routines for handling bonding devices
+ *	Routines for bonding interface handling
  *
- * Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2023 SUSE LLC
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -19,11 +33,6 @@
 #include "sysfs.h"
 #include "modprobe.h"
 
-/*
- * Slave array (re)allocation chunk
- */
-#define NI_BONDING_SLAVE_ARRAY_CHUNK		4
-
 
 /*
  * Kernel module and default parameter.
@@ -135,19 +144,19 @@ static const ni_intmap_t	__map_user_carrier_detect[] = {
 };
 
 /*
- * Bonding slave state and mii_status maps
+ * Bonding port state and mii_status maps
  */
-static const ni_intmap_t	ni_bonding_slave_state_map[] = {
-	{ "active",		NI_BOND_SLAVE_STATE_ACTIVE	},
-	{ "backup",		NI_BOND_SLAVE_STATE_BACKUP	},
+static const ni_intmap_t	ni_bonding_port_state_map[] = {
+	{ "active",		NI_BOND_PORT_STATE_ACTIVE	},
+	{ "backup",		NI_BOND_PORT_STATE_BACKUP	},
 
 	{ NULL,			-1U				}
 };
-static const ni_intmap_t	ni_bonding_slave_mii_status_map[] = {
-	{ "up",			NI_BOND_SLAVE_LINK_UP		},
-	{ "fail",		NI_BOND_SLAVE_LINK_FAIL		},
-	{ "down",		NI_BOND_SLAVE_LINK_DOWN		},
-	{ "back",		NI_BOND_SLAVE_LINK_BACK		},
+static const ni_intmap_t	ni_bonding_port_mii_status_map[] = {
+	{ "up",			NI_BOND_PORT_LINK_UP		},
+	{ "fail",		NI_BOND_PORT_LINK_FAIL		},
+	{ "down",		NI_BOND_PORT_LINK_DOWN		},
+	{ "back",		NI_BOND_PORT_LINK_BACK		},
 
 	{ NULL,			-1U				}
 };
@@ -195,7 +204,6 @@ __ni_bonding_clear(ni_bonding_t *bonding)
 {
 	ni_netdev_ref_destroy(&bonding->primary_slave);
 	ni_netdev_ref_destroy(&bonding->active_slave);
-	ni_bonding_slave_array_destroy(&bonding->slaves);
 	ni_string_array_destroy(&bonding->arpmon.targets);
 	memset(bonding, 0, sizeof(*bonding));
 
@@ -220,7 +228,6 @@ ni_bonding_t *
 ni_bonding_clone(const ni_bonding_t *orig)
 {
 	ni_bonding_t *bond;
-	unsigned int i;
 
 	if (!orig || !(bond = ni_bonding_new()))
 		return NULL;
@@ -269,13 +276,6 @@ ni_bonding_clone(const ni_bonding_t *orig)
 	ni_netdev_ref_set(&bond->active_slave,  orig->active_slave.name,
 						orig->active_slave.index);
 
-	for (i = 0; i < orig->slaves.count; ++i) {
-		const ni_bonding_slave_t *o = orig->slaves.data[i];
-		ni_bonding_slave_t *s = ni_bonding_slave_new();
-		ni_netdev_ref_set(&s->device, o->device.name, o->device.index);
-		s->info = ni_bonding_slave_info_ref(o->info);
-		ni_bonding_slave_array_append(&bond->slaves, s);
-	}
 #undef C
 
 	return bond;
@@ -571,140 +571,6 @@ ni_bonding_is_valid_arp_ip_target(const char *target)
 	return TRUE;
 }
 
-/*
- * Copy the slave names into the name array
- */
-void
-ni_bonding_get_slave_names(const ni_bonding_t *bonding, ni_string_array_t *array)
-{
-	unsigned int i;
-
-	if (!bonding || !array)
-		return;
-
-	ni_string_array_destroy(array);
-	for (i = 0; i < bonding->slaves.count; ++i) {
-		ni_bonding_slave_t *slave = bonding->slaves.data[i];
-
-		if (!slave || ni_string_empty(slave->device.name))
-			continue;
-
-		ni_string_array_append(array, slave->device.name);
-	}
-}
-
-/*
- * Report if the bond contains a slave device
- */
-ni_bool_t
-ni_bonding_has_slave(ni_bonding_t *bonding, const char *ifname)
-{
-	if (!bonding || ni_string_empty(ifname))
-		return FALSE;
-
-	return ni_bonding_slave_array_index_by_ifname(&bonding->slaves, ifname) != -1U;
-}
-
-/*
- * Add a slave device to the bond
- */
-ni_bonding_slave_t *
-ni_bonding_add_slave(ni_bonding_t *bonding, const char *ifname)
-{
-	ni_bonding_slave_t *slave;
-
-	if (!bonding || ni_string_empty(ifname))
-		return NULL;
-
-	if ((slave = ni_bonding_slave_new())) {
-
-		ni_netdev_ref_set_ifname(&slave->device, ifname);
-		if (ni_bonding_slave_array_append(&bonding->slaves, slave))
-			return slave;
-
-		ni_bonding_slave_free(slave);
-	}
-	return NULL;
-}
-
-ni_bonding_slave_t *
-ni_bonding_bind_slave(ni_bonding_t *bonding, const ni_netdev_ref_t *ref, const char *ifname)
-{
-	ni_bonding_slave_t *slave;
-
-	if (!bonding || !ref || !ref->index || ni_string_empty(ref->name)) {
-		ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
-				"%s: bind of bonding slave %s[%u] skipped -- invalid args",
-				ifname, ref ? ref->name : NULL, ref ? ref->index : 0);
-		return NULL;
-	}
-
-	slave = ni_bonding_slave_array_get_by_ifindex(&bonding->slaves, ref->index);
-	if (slave) {
-		if (ni_string_eq(slave->device.name, ref->name)) {
-			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
-					"%s: bonding slave %s[%u] is up to date",
-					ifname, slave->device.name, slave->device.index);
-		} else {
-			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
-					"%s: rebind of bonding slave %s[%u] ifname to %s",
-					ifname, slave->device.name, slave->device.index, ref->name);
-
-			ni_netdev_ref_set_ifname(&slave->device, ref->name);
-		}
-		return slave;
-	}
-
-	slave = ni_bonding_slave_new();
-	if (slave) {
-		ni_netdev_ref_set(&slave->device, ref->name, ref->index);
-		if (ni_bonding_slave_array_append(&bonding->slaves, slave)) {
-			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
-					"%s: bound new bonding slave %s[%u]",
-					ifname, slave->device.name, slave->device.index);
-			return slave;
-		}
-
-		ni_bonding_slave_free(slave);
-	}
-
-	ni_error("%s: unable to bind new slave %s[%u]", ifname, ref->name, ref->index);
-	return NULL;
-}
-
-ni_bool_t
-ni_bonding_unbind_slave(ni_bonding_t *bonding, const ni_netdev_ref_t *ref, const char *ifname)
-{
-	const ni_bonding_slave_t *slave;
-	unsigned int pos;
-
-	if (!bonding || !ref || !ref->index) {
-		ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
-				"%s: unbind of bonding slave %s[%u] skipped -- invalid args",
-				ifname, ref ? ref->name : NULL, ref ? ref->index : 0);
-		return FALSE;
-	}
-
-	pos = ni_bonding_slave_array_index_by_ifindex(&bonding->slaves, ref->index);
-	if (pos != -1U) {
-		slave = ni_bonding_slave_array_get(&bonding->slaves, pos);
-
-		if (slave)
-			ref = &slave->device;
-
-		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
-				"%s: unbind of bonding slave %s[%u] by ifindex",
-				ifname, ref->name, ref->index);
-
-		return ni_bonding_slave_array_delete(&bonding->slaves, pos);
-	}
-
-	ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
-			"%s: unbind of bonding slave %s[%u] skipped -- slave not found",
-			ifname, ref->name, ref->index);
-	return FALSE;
-}
-
 /*
  * Set the bonding mode, using the strings supported by the
  * module options
@@ -951,15 +817,15 @@ ni_bonding_arp_validate_targets_to_type(const char *name)
 }
 
 const char *
-ni_bonding_slave_state_name(unsigned int mode)
+ni_bonding_port_state_name(unsigned int mode)
 {
-	return ni_format_uint_mapped(mode, ni_bonding_slave_state_map);
+	return ni_format_uint_mapped(mode, ni_bonding_port_state_map);
 }
 
 const char *
-ni_bonding_slave_mii_status_name(unsigned int mode)
+ni_bonding_port_mii_status_name(unsigned int mode)
 {
-	return ni_format_uint_mapped(mode, ni_bonding_slave_mii_status_map);
+	return ni_format_uint_mapped(mode, ni_bonding_port_mii_status_map);
 }
 
 /*
@@ -1193,16 +1059,10 @@ ni_bonding_parse_sysfs_attrs(const char *ifname, ni_bonding_t *bonding)
 		{ "lp_interval",	TRUE  },
 		{ NULL,			FALSE },
 	};
-	ni_string_array_t slave_names = NI_STRING_ARRAY_INIT;
 	char *attrval = NULL;
 	unsigned int i;
 
 	__ni_bonding_clear(bonding);
-	ni_sysfs_bonding_get_slaves(ifname, &slave_names);
-	for (i = 0; i < slave_names.count; ++i)
-		ni_bonding_add_slave(bonding, slave_names.data[i]);
-	ni_string_array_destroy(&slave_names);
-
 	for (i = 0; attrs[i].name; ++i) {
 		const char *attrname = attrs[i].name;
 		int rv;
@@ -1282,7 +1142,8 @@ ni_bonding_write_one_sysfs_attr(const char *ifname, const ni_bonding_t *bonding,
  * as well as in dependency of the bonding up/down state and slave count.
  */
 int
-ni_bonding_write_sysfs_attrs(const char *ifname, const ni_bonding_t *bonding, ni_bonding_t *current, ni_bool_t is_up, ni_bool_t has_slaves)
+ni_bonding_write_sysfs_attrs(const char *ifname, const ni_bonding_t *bonding,
+	ni_bonding_t *current, ni_bool_t is_up, ni_bool_t has_ports)
 {
 	/*
 	 * option		up/down	slaves		modes
@@ -1352,9 +1213,9 @@ ni_bonding_write_sysfs_attrs(const char *ifname, const ni_bonding_t *bonding, ni
 			continue;
 		if (attrs[i].bstate == 2 && !is_up)
 			continue;
-		if (attrs[i].slaves == 1 && has_slaves)
+		if (attrs[i].slaves == 1 && has_ports)
 			continue;
-		if (attrs[i].slaves == 2 && !has_slaves)
+		if (attrs[i].slaves == 2 && !has_ports)
 			continue;
 
 		if (attrs[i].islist) {
@@ -1627,221 +1488,51 @@ ni_bonding_set_option(ni_bonding_t *bond, const char *option, const char *value)
 	return FALSE;
 }
 
-void
-ni_bonding_slave_info_reset(ni_bonding_slave_info_t *info)
-{
-	info->state = -1U;
-	info->mii_status = -1U;
-	info->queue_id = -1U;
-	info->ad_aggregator_id = -1U;
-	ni_link_address_init(&info->perm_hwaddr);
-}
-
-ni_bonding_slave_info_t *
-ni_bonding_slave_info_new(void)
-{
-	ni_bonding_slave_info_t *info;
-
-	info = xcalloc(1, sizeof(*info));
-	info->refcount = 1;
-	ni_bonding_slave_info_reset(info);
-	return info;
-}
-
-ni_bonding_slave_info_t *
-ni_bonding_slave_info_ref(ni_bonding_slave_info_t *info)
-{
-	if (info) {
-		ni_assert(info->refcount);
-		info->refcount++;
-	}
-	return info;
-}
-
-void
-ni_bonding_slave_info_free(ni_bonding_slave_info_t *info)
-{
-	if (info) {
-		ni_assert(info->refcount);
-		info->refcount--;
-
-		if (info->refcount == 0)
-			free(info);
-	}
-}
-
-ni_bonding_slave_t *
-ni_bonding_slave_new(void)
+/*
+ * Bonding port (link-request) configuration
+ */
+ni_bonding_port_config_t *
+ni_bonding_port_config_new(void)
 {
-	ni_bonding_slave_t *slave;
+	ni_bonding_port_config_t *conf;
 
-	slave = xcalloc(1, sizeof(*slave));
-	return slave;
+	if ((conf = malloc(sizeof(*conf))))
+		conf->queue_id = -1U;
+	return conf;
 }
 
 void
-ni_bonding_slave_free(ni_bonding_slave_t *slave)
+ni_bonding_port_config_free(ni_bonding_port_config_t *conf)
 {
-	if (slave) {
-		ni_netdev_ref_destroy(&slave->device);
-		ni_bonding_slave_info_free(slave->info);
-		free(slave);
-	}
-}
-
-ni_bonding_slave_info_t *
-ni_bonding_slave_get_info(ni_bonding_slave_t *slave)
-{
-	if (slave) {
-		if (!slave->info)
-			slave->info = ni_bonding_slave_info_new();
-		return slave->info;
-	}
-	return NULL;
+	free(conf);
 }
 
+/*
+ * Bonding port interface info properties
+ */
 void
-ni_bonding_slave_set_info(ni_bonding_slave_t *slave, ni_bonding_slave_info_t *info)
-{
-	if (slave) {
-		ni_bonding_slave_info_t *temp;
-
-		temp = ni_bonding_slave_info_ref(info);
-		ni_bonding_slave_info_free(slave->info);
-		slave->info = temp;
-	}
-}
-
-static inline void
-ni_bonding_slave_array_init(ni_bonding_slave_array_t *array)
-{
-	memset(array, 0, sizeof(*array));
-}
-
-static void
-ni_bonding_slave_array_realloc(ni_bonding_slave_array_t *array, unsigned int newsize)
-{
-	ni_bonding_slave_t **newdata;
-	unsigned int i;
-
-	newsize += NI_BONDING_SLAVE_ARRAY_CHUNK;
-	newdata = xrealloc(array->data, newsize * sizeof(ni_bonding_slave_t *));
-	array->data = newdata;
-	for (i = array->count; i < newsize; ++i)
-		array->data[i] = NULL;
-}
-
-ni_bool_t
-ni_bonding_slave_array_append(ni_bonding_slave_array_t *array, ni_bonding_slave_t *slave)
+ni_bonding_port_info_reset(ni_bonding_port_info_t *info)
 {
-	if (!array || !slave)
-		return FALSE;
-
-	if ((array->count % NI_BONDING_SLAVE_ARRAY_CHUNK) == 0)
-		ni_bonding_slave_array_realloc(array, array->count);
-
-	array->data[array->count++] = slave;
-	return TRUE;
-}
-
-ni_bonding_slave_t *
-ni_bonding_slave_array_remove(ni_bonding_slave_array_t *array, unsigned int index)
-{
-	ni_bonding_slave_t *slave;
-
-	if (!array || index >= array->count)
-		return NULL;
-
-	slave = array->data[index];
-	array->count--;
-	if (index < array->count) {
-		memmove(&array->data[index], &array->data[index + 1],
-			(array->count - index) * sizeof(slave));
-	}
-	array->data[array->count] = NULL;
-	return slave;
+	info->state = -1U;
+	info->mii_status = -1U;
+	info->queue_id = -1U;
+	info->ad_aggregator_id = -1U;
+	ni_link_address_init(&info->perm_hwaddr);
 }
 
-ni_bool_t
-ni_bonding_slave_array_delete(ni_bonding_slave_array_t *array, unsigned int index)
+ni_bonding_port_info_t *
+ni_bonding_port_info_new(void)
 {
-	ni_bonding_slave_t *slave;
+	ni_bonding_port_info_t *info;
 
-	if (!(slave = ni_bonding_slave_array_remove(array, index)))
-		return FALSE;
-
-	ni_bonding_slave_free(slave);
-	return TRUE;
+	if ((info = malloc(sizeof(*info))))
+		ni_bonding_port_info_reset(info);
+	return info;
 }
 
 void
-ni_bonding_slave_array_destroy(ni_bonding_slave_array_t *array)
-{
-	if (array) {
-		while (array->count > 0)
-			ni_bonding_slave_free(array->data[--array->count]);
-		free(array->data);
-
-		ni_bonding_slave_array_init(array);
-	}
-}
-
-unsigned int
-ni_bonding_slave_array_index_by_ifname(ni_bonding_slave_array_t *array, const char *ifname)
+ni_bonding_port_info_free(ni_bonding_port_info_t *info)
 {
-	unsigned int i;
-
-	if (!array || !ifname)
-		return -1U;
-
-	for (i = 0; i < array->count; ++i) {
-		ni_bonding_slave_t *slave = array->data[i];
-
-		if (slave && ni_string_eq(ifname, slave->device.name))
-			return i;
-	}
-	return -1U;
-}
-
-unsigned int
-ni_bonding_slave_array_index_by_ifindex(ni_bonding_slave_array_t *array, unsigned int ifindex)
-{
-	unsigned int i;
-
-	if (!array || !ifindex)
-		return -1U;
-
-	for (i = 0; i < array->count; ++i) {
-		ni_bonding_slave_t *slave = array->data[i];
-
-		if (slave && ifindex == slave->device.index)
-			return i;
-	}
-	return -1U;
-}
-
-ni_bonding_slave_t *
-ni_bonding_slave_array_get(ni_bonding_slave_array_t *array, unsigned int index)
-{
-	if (!array || index >= array->count)
-		return NULL;
-
-	return array->data[index];
-}
-
-ni_bonding_slave_t *
-ni_bonding_slave_array_get_by_ifname(ni_bonding_slave_array_t *array, const char *ifname)
-{
-	unsigned int index = ni_bonding_slave_array_index_by_ifname(array, ifname);
-
-	return ni_bonding_slave_array_get(array, index);
-}
-
-ni_bonding_slave_t *
-ni_bonding_slave_array_get_by_ifindex(ni_bonding_slave_array_t *array, unsigned int ifindex)
-{
-	unsigned int index = ni_bonding_slave_array_index_by_ifindex(array, ifindex);
-
-	return ni_bonding_slave_array_get(array, index);
+	free(info);
 }
 
diff --git a/src/bridge.c b/src/bridge.c
index 507b108dc..d4fef3c5d 100644
--- a/src/bridge.c
+++ b/src/bridge.c
@@ -1,7 +1,21 @@
 /*
- * Handing bridge interfaces.
+ *	bridge interface handling
  *
- * Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2012-2023 SUSE LLC
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -14,215 +28,75 @@
 #include "util_priv.h"
 #include "limits.h"
 
-#define NI_BRIDGE_PORT_ARRAY_CHUNK	16
+#include <linux/if_bridge.h>
 
-static void			ni_bridge_port_array_init(ni_bridge_port_array_t *);
-static void			ni_bridge_port_array_destroy(ni_bridge_port_array_t *);
-static void			__ni_bridge_port_array_realloc(ni_bridge_port_array_t *,
-					unsigned int);
-static int			__ni_bridge_port_array_append(ni_bridge_port_array_t *,
-					ni_bridge_port_t *);
 
-
-ni_bridge_port_t *
-ni_bridge_port_new(ni_bridge_t *bridge, const char *ifname, unsigned int ifindex)
+static ni_bool_t
+ni_bridge_port_config_init(ni_bridge_port_config_t *conf)
 {
-	ni_bridge_port_t *port;
-
-	port = xcalloc(1, sizeof(ni_bridge_port_t));
-	ni_string_dup(&port->ifname, ifname);
-	port->ifindex = ifindex;
-	/* apply "not set" defaults */
-	port->priority = NI_BRIDGE_VALUE_NOT_SET;
-	port->path_cost = NI_BRIDGE_VALUE_NOT_SET;
-
-	if (bridge)
-		__ni_bridge_port_array_append(&bridge->ports, port);
-	return port;
-}
-
-ni_bridge_port_t *
-ni_bridge_port_clone(const ni_bridge_port_t *src)
-{
-	ni_bridge_port_t *dst;
-
-	if (src) {
-		dst = ni_bridge_port_new(NULL, src->ifname, src->ifindex);
-		dst->priority = src->priority;
-		dst->path_cost = src->path_cost;
-		return dst;
+	if (conf) {
+		/* apply "not set" defaults */
+		conf->priority = NI_BRIDGE_VALUE_NOT_SET;
+		conf->path_cost = NI_BRIDGE_VALUE_NOT_SET;
+		return TRUE;
 	}
-	return NULL;
-}
-
-void
-ni_bridge_port_free(ni_bridge_port_t *port)
-{
-	ni_string_free(&port->ifname);
-	ni_bridge_port_status_destroy(&port->status);
-	free(port);
+	return FALSE;
 }
 
-static void
-ni_bridge_port_array_init(ni_bridge_port_array_t *array)
+ni_bridge_port_config_t *
+ni_bridge_port_config_new(void)
 {
-	memset(array, 0, sizeof(*array));
-}
+	ni_bridge_port_config_t *conf;
 
-static void
-ni_bridge_port_array_destroy(ni_bridge_port_array_t *array)
-{
-	while (array->count > 0)
-		ni_bridge_port_free(array->data[--array->count]);
-	free(array->data);
-	ni_bridge_port_array_init(array);
-}
+	conf = malloc(sizeof(*conf));
+	if (ni_bridge_port_config_init(conf))
+		return conf;
 
-static void
-__ni_bridge_port_array_realloc(ni_bridge_port_array_t *array, unsigned int newsize)
-{
-	ni_bridge_port_t **newdata;
-	unsigned int i;
-
-	newsize = (newsize + NI_BRIDGE_PORT_ARRAY_CHUNK);
-	newdata = xrealloc(array->data, newsize * sizeof(ni_bridge_port_t));
-	array->data = newdata;
-	for (i = array->count; i < newsize; ++i)
-		array->data[i] = NULL;
-}
-
-static int
-__ni_bridge_port_array_append(ni_bridge_port_array_t *array, ni_bridge_port_t *port)
-{
-	if ((array->count % NI_BRIDGE_PORT_ARRAY_CHUNK) == 0)
-		__ni_bridge_port_array_realloc(array, array->count);
-
-	array->data[array->count++] = port;
-	return 0;
+	free(conf);
+	return NULL;
 }
 
-static int
-ni_bridge_port_array_remove_index(ni_bridge_port_array_t *array, unsigned int pos)
+void
+ni_bridge_port_config_free(ni_bridge_port_config_t *conf)
 {
-	unsigned int i;
-
-	if (pos >= array->count)
-		return -1;
-
-	ni_bridge_port_free(array->data[pos]);
-	/* make it less cumbersome... */
-	array->data[pos] = NULL;
-	for (i = pos + 1; i < array->count; ++i) {
-		array->data[i - 1] = array->data[i];
-		array->data[i] = NULL;
-	}
-	array->count--;
-	return 0;
+	ni_bridge_port_config_init(conf);
+	free(conf);
 }
 
-/*
- * Locate a port given its name or index
- */
-ni_bridge_port_t *
-ni_bridge_port_by_index(const ni_bridge_t *bridge, unsigned int ifindex)
+static ni_bool_t
+ni_bridge_port_info_init(ni_bridge_port_info_t *info)
 {
-	ni_bridge_port_t **pp, *port;
-	unsigned int i;
-
-	for (i = 0, pp = bridge->ports.data; i < bridge->ports.count; ++i) {
-		port = *pp++;
-		if (port->ifindex == ifindex)
-			return port;
+	if (info) {
+		memset(info, 0, sizeof(*info));
+		/* apply "not set" defaults */
+		info->priority = NI_BRIDGE_VALUE_NOT_SET;
+		info->path_cost = NI_BRIDGE_VALUE_NOT_SET;
+		return TRUE;
 	}
-	return NULL;
+	return FALSE;
 }
 
-ni_bridge_port_t *
-ni_bridge_port_by_name(const ni_bridge_t *bridge, const char *ifname)
+ni_bridge_port_info_t *
+ni_bridge_port_info_new(void)
 {
-	ni_bridge_port_t **pp, *port;
-	unsigned int i;
+	ni_bridge_port_info_t *info;
 
-	if (ifname == NULL)
-		return NULL;
+	info = malloc(sizeof(*info));
+	if (ni_bridge_port_info_init(info))
+		return info;
 
-	for (i = 0, pp = bridge->ports.data; i < bridge->ports.count; ++i) {
-		port = *pp++;
-		if (ni_string_eq(port->ifname, ifname))
-			return port;
-	}
+	free(info);
 	return NULL;
 }
 
-/*
- * Add a port to the bridge configuration
- * Note, in case of success, the bridge will have taken ownership of the port object.
- */
-int
-ni_bridge_add_port(ni_bridge_t *bridge, ni_bridge_port_t *port)
-{
-	if (!port)
-		return -1;
-
-	if (port->ifindex && ni_bridge_port_by_index(bridge, port->ifindex))
-		return -1;
-	if (port->ifname && ni_bridge_port_by_name(bridge, port->ifname))
-		return -1;
-
-	__ni_bridge_port_array_append(&bridge->ports, port);
-	return 0;
-}
-
-
-int
-ni_bridge_del_port(ni_bridge_t *bridge, unsigned int pos)
-{
-	return ni_bridge_port_array_remove_index(&bridge->ports, pos);
-}
-
-int
-ni_bridge_del_port_ifname(ni_bridge_t *bridge, const char *ifname)
-{
-	ni_bridge_port_t **pp, *port;
-	unsigned int i;
-
-	for (i = 0, pp = bridge->ports.data; i < bridge->ports.count; ++i) {
-		port = *pp++;
-		if (ni_string_eq(port->ifname, ifname)) {
-			ni_bridge_port_array_remove_index(&bridge->ports, i);
-			return 0;
-		}
-	}
-	return -1;
-}
-
-int
-ni_bridge_del_port_ifindex(ni_bridge_t *bridge, unsigned int ifindex)
-{
-	ni_bridge_port_t **pp, *port;
-	unsigned int i;
-
-	for (i = 0, pp = bridge->ports.data; i < bridge->ports.count; ++i) {
-		port = *pp++;
-		if (port->ifindex == ifindex) {
-			ni_bridge_port_array_remove_index(&bridge->ports, i);
-			return 0;
-		}
-	}
-	return -1;
-}
-
 void
-ni_bridge_get_port_names(const ni_bridge_t *bridge, ni_string_array_t *names)
+ni_bridge_port_info_free(ni_bridge_port_info_t *info)
 {
-	unsigned int i;
-
-	if (!bridge || !names)
-		return;
-	for (i = 0; i < bridge->ports.count; ++i) {
-		ni_bridge_port_t *port = bridge->ports.data[i];
-		if (port && port->ifname && *port->ifname)
-			ni_string_array_append(names, port->ifname);
+	if (info) {
+		ni_string_free(&info->designated_root);
+		ni_string_free(&info->designated_bridge);
+		ni_bridge_port_info_init(info);
+		free(info);
 	}
 }
 
@@ -257,7 +131,6 @@ ni_bridge_new(void)
 static void
 __ni_bridge_destroy(ni_bridge_t *bridge)
 {
-	ni_bridge_port_array_destroy(&bridge->ports);
 	ni_bridge_status_destroy(&bridge->status);
 }
 
@@ -277,19 +150,6 @@ ni_bridge_status_destroy(ni_bridge_status_t *bs)
 	memset(bs, 0, sizeof(*bs));
 }
 
-void
-ni_bridge_port_status_destroy(ni_bridge_port_status_t *ps)
-{
-	ni_string_free(&ps->designated_root);
-	ni_string_free(&ps->designated_bridge);
-}
-
-void
-ni_bridge_ports_destroy(ni_bridge_t *bridge)
-{
-	ni_bridge_port_array_destroy(&bridge->ports);
-}
-
 /*
  * From IEEE 802.1D-1998, 8.10.2 (STP):
  * 				Recommended	Fixed value	Range
@@ -356,19 +216,22 @@ ni_bridge_ports_destroy(ni_bridge_t *bridge)
 #define NI_BRIDGE_PORT_MAX_COUNT	1024
 
 const char *
-ni_bridge_port_validate(const ni_bridge_port_t *port)
+ni_bridge_port_priority_validate(unsigned int priority)
 {
-	if (!port || !port->ifname)
-		return "uninitialized port configuration";
-
-	if (port->priority != NI_BRIDGE_VALUE_NOT_SET &&
-	    port->priority > NI_BRIDGE_PORT_PRIORITY_MAX)
+	if (priority != NI_BRIDGE_VALUE_NOT_SET &&
+	    priority > NI_BRIDGE_PORT_PRIORITY_MAX)
 		return "bridge port priority is out of supported range (0-63)";
 
-	if (port->path_cost != NI_BRIDGE_VALUE_NOT_SET &&
-	   (port->path_cost < NI_BRIDGE_PORT_PATH_COST_MIN ||
-	    port->path_cost > NI_BRIDGE_PORT_PATH_COST_MAX))
-		return "bridge port priority is out of supported range (0-65535)";
+	return NULL;
+}
+
+const char *
+ni_bridge_port_path_cost_validate(unsigned int path_cost)
+{
+	if (path_cost != NI_BRIDGE_VALUE_NOT_SET &&
+	    (path_cost < NI_BRIDGE_PORT_PATH_COST_MIN ||
+	     path_cost > NI_BRIDGE_PORT_PATH_COST_MAX))
+		return "bridge port path-cost is out of supported range (0-65535)";
 
 	return NULL;
 }
@@ -376,8 +239,6 @@ ni_bridge_port_validate(const ni_bridge_port_t *port)
 const char *
 ni_bridge_validate(const ni_bridge_t *bridge)
 {
-	unsigned int i;
-
 	if (!bridge)
 		return "uninitialized bridge configuration";
 
@@ -422,15 +283,6 @@ ni_bridge_validate(const ni_bridge_t *bridge)
 			return "bridge max-age is out of supported range (0.0-60.0)";
 	}
 
-	if (bridge->ports.count > NI_BRIDGE_PORT_MAX_COUNT)
-		return "bridge port count is higher than supported (0-1024)";
-
-	for (i = 0; i < bridge->ports.count; ++i) {
-		const char *err = ni_bridge_port_validate(bridge->ports.data[i]);
-		if (err != NULL)
-			return err;
-	}
-
 	return NULL;
 }
 
@@ -494,3 +346,23 @@ ni_bridge_waittime_from_xml(const xml_node_t *brnode)
 	waittime = ni_bridge_waittime(&bridge);
 	return waittime;
 }
+
+static const ni_intmap_t	ni_bridge_port_state_map[] = {
+	/*
+	 * just use BR_STATE constants from linux/if_bridge.h;
+	 * br_port_state_names from linux/net/bridge/br_stp.c.
+	 */
+	{ "disabled",		BR_STATE_DISABLED		},
+	{ "listening",		BR_STATE_LISTENING		},
+	{ "learning",		BR_STATE_LEARNING		},
+	{ "forwarding",		BR_STATE_FORWARDING		},
+	{ "blocking",		BR_STATE_BLOCKING		},
+
+	{ NULL,			-1U				}
+};
+
+const char *
+ni_bridge_port_state_name(unsigned int state)
+{
+	return ni_format_uint_mapped(state, ni_bridge_port_state_map);
+}
diff --git a/src/client/ifconfig.h b/src/client/ifconfig.h
index 39639b0ac..ff6ff479c 100644
--- a/src/client/ifconfig.h
+++ b/src/client/ifconfig.h
@@ -2,6 +2,7 @@
  *	wicked client ifconfig structures and objects
  *
  *	Copyright (C) 2010-2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2014-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,24 +14,20 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef   __WICKED_CLIENT_IFCONFIG_H__
-#define   __WICKED_CLIENT_IFCONFIG_H__
+#ifndef NI_WICKED_CLIENT_IFCONFIG_H
+#define NI_WICKED_CLIENT_IFCONFIG_H
 
 #define NI_CONFIG_ORIGIN			"origin"
+#define NI_CONFIG_OWNER				"owner"
 #define NI_CONFIG_UUID				"uuid"
 
 #define NI_CLIENT_IFCONFIG			"interface"
-#define NI_CLIENT_IFCONFIG_UUID			NI_CONFIG_UUID
 #define NI_CLIENT_IFCONFIG_ORIGIN		NI_CONFIG_ORIGIN
+#define NI_CLIENT_IFCONFIG_OWNER		NI_CONFIG_OWNER
+#define NI_CLIENT_IFCONFIG_UUID			NI_CONFIG_UUID
 #define NI_CLIENT_IFCONFIG_MATCH_NAME		"name"
 #define NI_CLIENT_IFCONFIG_CONTROL		"control"
 #define NI_CLIENT_IFCONFIG_MODE			"mode"
@@ -60,11 +57,11 @@
 #define NI_NANNY_IFPOLICY_REPLACE		"replace"
 #define NI_NANNY_IFPOLICY_CREATE		"create"
 
-#define NI_NANNY_IFPOLICY_UUID			NI_CONFIG_UUID
 #define NI_NANNY_IFPOLICY_ORIGIN		NI_CONFIG_ORIGIN
-#define NI_NANNY_IFPOLICY_NAME			"name"
-#define NI_NANNY_IFPOLICY_OWNER			"owner"
+#define NI_NANNY_IFPOLICY_OWNER			NI_CONFIG_OWNER
+#define NI_NANNY_IFPOLICY_UUID			NI_CONFIG_UUID
 #define NI_NANNY_IFPOLICY_WEIGHT		"weight"
+#define NI_NANNY_IFPOLICY_NAME			"name"
 
 extern ni_bool_t		ni_ifpolicy_match_add_min_state(xml_node_t *, unsigned int);
 extern ni_bool_t		ni_ifpolicy_match_add_link_type(xml_node_t *, unsigned int);
@@ -93,10 +90,12 @@ extern ni_bool_t		ni_nanny_call_add_secret(const ni_security_id_t *, const char
 extern ni_bool_t		ni_nanny_call_recheck(const ni_string_array_t *);
 
 extern ni_bool_t		ni_ifconfig_generate_uuid(const xml_node_t *, ni_uuid_t *);
-extern ni_bool_t		ni_ifconfig_migrate(xml_node_t *);
+extern ni_bool_t		ni_ifxml_node_is_migrated(const xml_node_t *);
+extern ni_bool_t		ni_ifxml_node_set_migrated(xml_node_t *, ni_bool_t);
+extern ni_bool_t		ni_ifxml_migrate_docs(xml_document_array_t *);
 
 static inline ni_bool_t
-ni_ifconfig_is_config(xml_node_t *ifnode)
+ni_ifconfig_is_config(const xml_node_t *ifnode)
 {
 	return !xml_node_is_empty(ifnode) && ni_string_eq(ifnode->name, NI_CLIENT_IFCONFIG);
 }
@@ -166,4 +165,4 @@ ni_ifpolicy_is_valid(const xml_node_t *pnode)
 	return ni_ifpolicy_name_is_valid(ni_ifpolicy_get_name(pnode));
 }
 
-#endif /* __WICKED_CLIENT_IFCONFIG_H__ */
+#endif /* NI_WICKED_CLIENT_IFCONFIG_H */
diff --git a/src/client/policy.c b/src/client/policy.c
index 0d5d5adab..5f17132af 100644
--- a/src/client/policy.c
+++ b/src/client/policy.c
@@ -2,6 +2,7 @@
  *	wicked client related policy functions
  *
  *	Copyright (C) 2010-2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2014-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,16 +14,9 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-
 #include <unistd.h>
 #include <sys/types.h>
 #include <ctype.h>
@@ -301,48 +295,94 @@ ni_convert_cfg_into_policy_node(const xml_node_t *ifcfg, xml_node_t *match, cons
 }
 
 xml_document_t *
-ni_convert_cfg_into_policy_doc(xml_document_t *ifconfig)
+ni_convert_cfg_into_policy_doc(xml_document_t *doc)
 {
-	xml_node_t *root, *ifnode, *ifname, *match;
+	xml_node_t *root, *match, *policy;
 	const char *origin;
+	const char *ifname;
+
+	if (xml_document_is_empty(doc))
+		return NULL;
 
-	if (xml_document_is_empty(ifconfig))
+	root = xml_document_root(doc);
+	if (ni_string_empty(root->name))
 		return NULL;
 
-	root = xml_document_root(ifconfig);
 	origin = xml_node_location_filename(root);
+	if (ni_string_empty(origin))
+		return NULL;
 
-	for (ifnode = root->children; ifnode; ifnode = ifnode->next) {
-		if (ni_ifpolicy_is_valid(ifnode)) {
-			const char *name = ni_ifpolicy_get_name(ifnode);
-			ni_debug_ifconfig("Ignoring already existing %s named %s from %s",
+	if (ni_ifpolicy_is_valid(root)) {
+		const char *name = ni_ifpolicy_get_name(root);
+
+		ni_debug_ifconfig("Ignoring already existing %s named %s from %s",
 				NI_NANNY_IFPOLICY, name, origin);
-			continue;
-		}
-		else if (ni_ifconfig_is_policy(ifnode)) {
-			ni_debug_ifconfig("Ignoring already existing, noname %s from %s",
+		return doc;
+	} else if (ni_ifconfig_is_policy(root)) {
+		ni_debug_ifconfig("Ignoring already existing, noname %s from %s",
 				NI_NANNY_IFPOLICY, origin);
-			continue;
-		}
+		return doc;
+	}
 
-		if (!ni_ifconfig_is_config(ifnode)) {
-			ni_error("Invalid object found in file %s: neither an %s nor %s",
-				origin, NI_CLIENT_IFCONFIG, NI_NANNY_IFPOLICY);
-			return NULL;
-		}
+	if (!ni_ifconfig_is_config(root)) {
+		ni_error("Invalid object found in file %s: neither an %s nor %s",
+			origin, NI_CLIENT_IFCONFIG, NI_NANNY_IFPOLICY);
+		return NULL;
+	}
+
+	ifname = xml_node_get_child_cdata(root, NI_CLIENT_IFCONFIG_MATCH_NAME);
+	if (ni_string_empty(ifname))
+		return NULL;
 
-		ifname = xml_node_get_child(ifnode, NI_CLIENT_IFCONFIG_MATCH_NAME);
-		if (!ifname || ni_string_empty(ifname->cdata))
-			return NULL;
+	if (!(match = xml_node_new(NI_NANNY_IFPOLICY_MATCH, NULL)))
+		return NULL;
 
-		if (!(match = ni_ifpolicy_generate_match(NULL, NULL)))
-			return NULL;
+	if (!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, match, ifname)) {
+		xml_node_free(match);
+		return NULL;
+	}
 
-		xml_node_add_child(root,
-			ni_convert_cfg_into_policy_node(ifnode, match, ifname->cdata, origin));
+	policy = ni_convert_cfg_into_policy_node(root, match, ifname, origin);
+	if (policy) {
+		xml_node_location_relocate(policy, origin);
+		xml_document_set_root(doc, policy);
+		xml_node_free(match);
+		return doc;
 	}
+	xml_node_free(match);
+	return NULL;
+}
+
+ni_bool_t
+ni_ifxml_node_is_migrated(const xml_node_t *node)
+{
+	ni_bool_t migrated = FALSE;
+	const char *value;
+
+	while (node && node->parent && !ni_string_empty(node->parent->name))
+		node = node->parent;
+
+	value = xml_node_get_attr(node, "migrated");
+	if (ni_parse_boolean(value, &migrated) != 0)
+		return FALSE;
+
+	return migrated;
+}
+
+ni_bool_t
+ni_ifxml_node_set_migrated(xml_node_t *node, ni_bool_t migrated)
+{
+	const char *value;
+
+	while (node && node->parent && !ni_string_empty(node->parent->name))
+		node = node->parent;
+
+	xml_node_del_attr(node, "migrated");
+	if (!migrated)
+		return TRUE;
 
-	return ifconfig;
+	value = ni_format_boolean(TRUE);
+	return xml_node_add_attr(node, "migrated", value);
 }
 
 static ni_bool_t
@@ -466,7 +506,8 @@ ni_ifconfig_migrate_ethtool(xml_node_t *ethernet, xml_node_t *ethtool)
 }
 
 static ni_bool_t
-ni_ifconfig_migrate_ethernet_node(xml_node_t *config, xml_node_t *ethernet)
+ni_ifconfig_migrate_ethernet_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *ethernet)
 {
 	ni_bool_t modified = FALSE;
 	xml_node_t *ethtool;
@@ -498,7 +539,8 @@ ni_ifconfig_migrate_wireless_network(xml_node_t *network)
 }
 
 static ni_bool_t
-ni_ifconfig_migrate_wireless_node(xml_node_t *config, xml_node_t *wireless)
+ni_ifconfig_migrate_wireless_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *wireless)
 {
 	ni_bool_t modified = FALSE;
 	xml_node_t *networks;
@@ -519,80 +561,1136 @@ ni_ifconfig_migrate_wireless_node(xml_node_t *config, xml_node_t *wireless)
 }
 
 static ni_bool_t
-ni_ifconfig_migrate_config_node(xml_node_t *config)
+ni_ifconfig_migrate_link_ovsbr_port(xml_node_t *link, xml_node_t *port)
 {
-	struct migrate_node {
-		const char *name;
-		ni_bool_t (*func)(xml_node_t *, xml_node_t *);
-	} *migrate, migrate_map[] = {
-		{ "ethernet",	ni_ifconfig_migrate_ethernet_node	},
-		{ "wireless",	ni_ifconfig_migrate_wireless_node	},
+	xml_node_t *bnode;
+	xml_node_t *mnode;
+	const char *mtype;
 
-		{ NULL }
-	};
+	/*
+	 * Rewrite link node containing port with type=ovs-bridge from:
+	 *   <link>
+	 *     <master>ovs-system</master>
+	 *     <port type="ovs-bridge">
+	 *       <bridge>ovsbrX</bridge>
+	 *     </port>
+	 *   </link>
+	 * into resolved link node with ovs-bridge in master reference:
+	 *   <link>
+	 *     <master>ovsbrX</master>
+	 *     <port type="ovs-bridge"/>
+	 *   </link>
+	 * by moving the bridge name from port into master node cdata.
+	 */
+	bnode = xml_node_get_child(port, NI_CLIENT_IFCONFIG_BRIDGE);
+	if (!bnode || ni_string_empty(bnode->cdata))
+		return FALSE;
+
+	if ((mnode = xml_node_get_child(link, NI_CLIENT_IFCONFIG_MASTER))) {
+		mtype = ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM);
+
+		/* if master is empty, ovs-system or same bridge */
+		if (!ni_string_empty(mnode->cdata) &&
+		    !ni_string_eq(mnode->cdata, mtype) &&
+		    !ni_string_eq(mnode->cdata, bnode->cdata))
+			return FALSE;
+
+	} else if (!(mnode = xml_node_new(NI_CLIENT_IFCONFIG_MASTER, link)))
+		return FALSE;
+
+	xml_node_set_cdata(mnode, bnode->cdata);
+	/* we've set the bridge in master and can remove the
+	 * obsolete bridge node from bridge-port config now. */
+	xml_node_detach(bnode);
+	xml_node_free(bnode);
+
+	/* mark the port migrated, may be triggered by master */
+	ni_ifxml_node_set_migrated(link, TRUE);
+	return TRUE;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_link_config(xml_node_t *link)
+{
+	xml_node_t *port;
+	const char *type;
+
+	if (!(port = xml_node_get_child(link, NI_CLIENT_IFCONFIG_LINK_PORT)))
+		return FALSE;
+
+	type = xml_node_get_attr(port, NI_CLIENT_IFCONFIG_PORT_TYPE);
+	switch ((ni_iftype_t)ni_linktype_name_to_type(type)) {
+	case NI_IFTYPE_OVS_BRIDGE:
+		return ni_ifconfig_migrate_link_ovsbr_port(link, port);
+	default:
+		return FALSE;
+	}
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_link_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *migrate)
+{
+	return ni_ifconfig_migrate_link_config(migrate);
+}
+
+static ni_bool_t
+ni_ifconfig_control_set_mode(xml_node_t *config, const char *mode)
+{
+	xml_node_t *ctrl, *node;
+
+	if (!(ctrl = xml_node_create(config, NI_CLIENT_IFCONFIG_CONTROL)))
+		return FALSE;
+
+	if (!(node = xml_node_create(ctrl, NI_CLIENT_IFCONFIG_MODE)))
+		return FALSE;
+
+	return xml_node_set_cdata(node, mode);
+}
+
+static const char *
+ni_ifconfig_link_get_master(xml_node_t *config, const xml_node_t **node)
+{
+	xml_node_t *link, *master;
+
+	if (!(link = xml_node_get_child(config, NI_CLIENT_IFCONFIG_LINK)))
+		return NULL;
+
+	if (!(master = xml_node_get_child(link, NI_CLIENT_IFCONFIG_MASTER)))
+		return NULL;
+
+	if (ni_string_empty(master->cdata))
+		return NULL;
+
+	if (node)
+		*node = master;
+	return master->cdata;
+}
+
+static ni_bool_t
+ni_ifconfig_link_set_master(xml_node_t *config, const char *master)
+{
+	xml_node_t *link, *node;
+
+	if (!(link = xml_node_create(config, NI_CLIENT_IFCONFIG_LINK)))
+		return FALSE;
+
+	if (!(node = xml_node_create(link, NI_CLIENT_IFCONFIG_MASTER)))
+		return FALSE;
+
+	return xml_node_set_cdata(node, master);
+}
+
+static const char *
+ni_ifconfig_link_get_port_config(xml_node_t *config, const xml_node_t **node)
+{
+	xml_node_t *link, *pconf;
+	const char *ptype;
+
+	if (!(link = xml_node_get_child(config, NI_CLIENT_IFCONFIG_LINK)))
+		return NULL;
+
+	if (!(pconf = xml_node_get_child(link, NI_CLIENT_IFCONFIG_LINK_PORT)))
+		return NULL;
+
+	ptype = xml_node_get_attr(pconf, NI_CLIENT_IFCONFIG_PORT_TYPE);
+	if (ni_string_empty(ptype))
+		return NULL;	/* invalid union node without a type */
+
+	if (node)
+		*node = pconf;
+	return ptype;
+}
+
+static ni_bool_t
+ni_ifconfig_link_set_port_config(xml_node_t *config, xml_node_t *pconf)
+{
+	xml_node_t *link, *oconf;
+
+	if (!pconf || !(link = xml_node_create(config, NI_CLIENT_IFCONFIG_LINK)))
+		return FALSE;
+
+	if ((oconf = xml_node_get_child(link, NI_CLIENT_IFCONFIG_LINK_PORT))) {
+		xml_node_detach(oconf);
+		xml_node_free(oconf);
+	}
+
+	xml_node_reparent(link, pconf);
+	return TRUE;
+}
+
+static xml_node_t *
+ni_ifconfig_create_port_config_node(const char *ptype)
+{
+	xml_node_t *pconf;
+
+	if (!(pconf = xml_node_new(NI_CLIENT_IFCONFIG_LINK_PORT, NULL)))
+		return NULL;
+
+	if (xml_node_add_attr(pconf, NI_CLIENT_IFCONFIG_PORT_TYPE, ptype))
+		return pconf;
+
+	xml_node_free(pconf);
+	return NULL;
+}
+
+static xml_node_t *
+ni_ifconfig_create(const char *ifname, const char *origin, const char *owner)
+{
+	xml_node_t *config;
+
+	if (ni_string_empty(ifname))
+		return NULL;
+
+	if (!(config = xml_node_new(NI_CLIENT_IFCONFIG, NULL)))
+		return NULL;
+
+	if (origin && !xml_node_add_attr(config, NI_CLIENT_IFCONFIG_ORIGIN, origin))
+		goto failure;
+
+	if (owner && !xml_node_add_attr(config, NI_CLIENT_IFCONFIG_OWNER, owner))
+		goto failure;
+
+	if (!xml_node_new_element(NI_CLIENT_IFCONFIG_MATCH_NAME, config, ifname))
+		goto failure;
+
+	return config;
+
+failure:
+	xml_node_free(config);
+	return NULL;
+}
+
+static xml_node_t *
+ni_ifconfig_get_name(xml_node_t *config)
+{
+	return xml_node_get_child(config, NI_CLIENT_IFCONFIG_MATCH_NAME);
+}
+
+static const char *
+ni_ifconfig_get_ifname(xml_node_t *config)
+{
+	xml_node_t *name;
+
+	if (!(name = ni_ifconfig_get_name(config)))
+		return NULL;
+
+	if (xml_node_get_attr(name, "namespace"))
+		return NULL;
+
+	return name->cdata;
+}
+
+static xml_node_t *
+ni_ifpolicy_create(const char *ifname, const char *origin, const char *owner,
+		xml_node_t **config)
+{
+	xml_node_t *policy, *match, *action;
+	char *name = NULL;
+
+	if (ni_string_empty(ifname) || (config && *config))
+		return NULL;
+
+	if (!(policy = xml_node_new(NI_NANNY_IFPOLICY, NULL)))
+		return NULL;
+
+	if (!(name = ni_ifpolicy_name_from_ifname(ifname)))
+		goto failure;
+
+	if (!xml_node_add_attr(policy, NI_NANNY_IFPOLICY_NAME, name))
+		goto failure;
+
+	ni_string_free(&name);
+
+	if (origin && !xml_node_add_attr(policy, NI_NANNY_IFPOLICY_ORIGIN, origin))
+		goto failure;
+
+	if (owner && !xml_node_add_attr(policy, NI_NANNY_IFPOLICY_OWNER, owner))
+		goto failure;
+
+	if (!(match = xml_node_new(NI_NANNY_IFPOLICY_MATCH, policy)))
+		goto failure;
+
+	if (!xml_node_new_element(NI_NANNY_IFPOLICY_MATCH_DEV, match, ifname))
+		goto failure;
+
+	if (!(action = xml_node_new(NI_NANNY_IFPOLICY_MERGE, policy)))
+		goto failure;
+
+	if (!xml_node_new_element(NI_CLIENT_IFCONFIG_MATCH_NAME, action, ifname))
+		goto failure;
+
+	/* action contains the actual config */
+	if (config)
+		*config = action;
+
+	return policy;
+
+failure:
+	ni_string_free(&name);
+	xml_node_detach(policy);
+	xml_node_free(policy);
+	return NULL;
+}
+
+static xml_node_t *
+ni_ifxml_get_ifconfig_node(xml_document_t *doc)
+{
+	xml_node_t *root, *node;
+
+	if (!(root = xml_document_root(doc)))
+		return NULL;
+
+	if (ni_ifconfig_is_config(root))
+		return root;
+
+	if (ni_ifconfig_is_policy(root)) {
+		if ((node = xml_node_get_child(root, NI_NANNY_IFPOLICY_MERGE)))
+			return node;
+		if ((node = xml_node_get_child(root, NI_NANNY_IFPOLICY_REPLACE)))
+			return node;
+		if ((node = xml_node_get_child(root, NI_NANNY_IFPOLICY_CREATE)))
+			return node;
+	}
+
+	return NULL;
+}
+
+static xml_node_t *
+ni_ifxml_find_config_by_ifname(xml_document_array_t *docs, const char *ifname)
+{
+	xml_document_t *doc;
+	xml_node_t *config;
+	const char *name;
+	unsigned int i;
+
+	for (i = 0; i < docs->count; ++i) {
+		doc = docs->data[i];
+		if (!(config = ni_ifxml_get_ifconfig_node(doc)))
+			continue;
+
+		if (!(name = ni_ifconfig_get_ifname(config)))
+			continue;
+
+		if (ni_string_eq(ifname, name))
+			return config;
+	}
+	return NULL;
+}
+
+static int
+ni_ifconfig_migrate_port_master(xml_node_t *migrate,
+		const char *port, const char *ptype,
+		const char *master)
+{
+	const xml_node_t *node = NULL;
+	const char *current;
+
+	if (!(current = ni_ifconfig_link_get_master(migrate, &node))) {
+		if (!ni_ifconfig_link_set_master(migrate, master)) {
+			ni_error("%s: failed to add %s port '%s' to '%s'",
+					xml_node_location(migrate), ptype,
+					port, master);
+			return -1;
+		}
+		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_READWRITE,
+				"%s: added %s port '%s' to '%s'",
+				xml_node_location(migrate), ptype,
+				port, master);
+		return 0;
+	}
+
+	if (!ni_string_eq(current, master)) {
+		ni_error("%s: cannot add %s port '%s' to '%s', already in '%s'",
+			xml_node_location(node), ptype, port, master, current);
+		return -1;
+	}
+	ni_debug_verbose(NI_LOG_DEBUG3, NI_TRACE_READWRITE,
+			"%s: %s port '%s' is already set to '%s'",
+			xml_node_location(node), ptype, port, current);
+	return 1;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_l2_port_ipv4(xml_node_t *migrate)
+{
+	xml_node_t *ip, *en;
+
+	if (!(ip = xml_node_create(migrate, NI_CLIENT_IFCONFIG_IPV4)))
+		return FALSE;
+
+	if (!(en = xml_node_create(ip, NI_CLIENT_IFCONFIG_IP_ENABLED)))
+		return FALSE;
+
+	if (ni_string_eq(en->cdata, "false"))
+		return FALSE; /* nothing to migrate */
+
+	return xml_node_set_cdata(en, "false");
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_l2_port_ipv6(xml_node_t *migrate, ni_bool_t l2v6)
+{
 	ni_bool_t modified = FALSE;
-	xml_node_t *found;
+	xml_node_t *ip, *en, *ra;
+
+	if (!(ip = xml_node_create(migrate, NI_CLIENT_IFCONFIG_IPV6)))
+		return FALSE;
 
-	for (migrate = migrate_map; migrate->name && migrate->func; ++migrate) {
-		found = xml_node_get_child(config, migrate->name);
-		if (found && migrate->func(config, found))
+	if (!(en = xml_node_create(ip, NI_CLIENT_IFCONFIG_IP_ENABLED)))
+		return FALSE;
+
+	if (l2v6) {
+		if (!(ra = xml_node_create(ip, "accept-ra")))
+			return FALSE;
+
+		if (!ni_string_eq(ra->cdata, "disable")) {
+			if (xml_node_set_cdata(ra, "disable"))
+				modified = TRUE;
+		}
+	}
+	if (!ni_string_eq(en->cdata, ni_format_boolean(l2v6))) {
+		if (xml_node_set_cdata(en, ni_format_boolean(l2v6)))
 			modified = TRUE;
 	}
 	return modified;
 }
 
 static ni_bool_t
-ni_ifconfig_migrate_node(xml_node_t *node, ni_bool_t *modified)
+ni_ifconfig_migrate_bond_port(xml_node_t *migrate,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		ni_bool_t l2v6)
 {
-	if (!modified || xml_node_is_empty(node))
+	const char *ptype = ni_linktype_type_to_name(type);
+	const xml_node_t *oconf = NULL;
+	const char *otype = NULL;
+	xml_node_t *nconf;
+	int ret;
+
+	if ((ret = ni_ifconfig_migrate_port_master(migrate, port, ptype, upper)) < 0)
 		return FALSE;
 
-	if (ni_ifconfig_is_config(node)) {
-		/* ifconfig with the effective config data */
-		if (ni_ifconfig_migrate_config_node(node))
-			*modified = TRUE;
-		return TRUE;
-	} else
-	if (ni_ifconfig_is_policy(node)) {
-		xml_node_t *action = NULL;
-
-		/* policy action contains the config data  */
-		if ((action = xml_node_get_child(node, NI_NANNY_IFPOLICY_MERGE))) {
-			if (ni_ifconfig_migrate_config_node(action))
-				*modified = TRUE;
-			return TRUE;
-		}
-		if ((action = xml_node_get_child(node, NI_NANNY_IFPOLICY_CREATE))) {
-			if (ni_ifconfig_migrate_config_node(action))
-				*modified = TRUE;
-			return TRUE;
-		}
-		if ((action = xml_node_get_child(node, NI_NANNY_IFPOLICY_REPLACE))) {
-			if (ni_ifconfig_migrate_config_node(action))
-				*modified = TRUE;
-			return TRUE;
-		}
+	if ((otype = ni_ifconfig_link_get_port_config(migrate, &oconf))) {
+		if (ni_string_eq(otype, ptype))
+			return ret == 0; /* no bond config to _migrate_ */
+
+		ni_warn("%s: removing mismatching %s port config from %s port '%s'",
+				xml_node_location(oconf), otype, ptype, port);
 	}
-	/* not a ifconfig or ifpolicy node */
-	return FALSE;
+	if ((nconf = ni_ifconfig_create_port_config_node(ptype))) {
+		/*
+		 * no port specific properties to migrate
+		 * from bonding:slave array, queue-id is new
+		 */
+		(void)data;
+
+		if (!ni_ifconfig_link_set_port_config(migrate, nconf))
+			xml_node_free(nconf);
+		else
+			ret = 0;
+	}
+
+	if (ni_ifconfig_migrate_l2_port_ipv4(migrate))
+		ret = 0;
+
+	if (ni_ifconfig_migrate_l2_port_ipv6(migrate, l2v6))
+		ret = 0;
+
+	return ret == 0;
 }
 
-ni_bool_t
-ni_ifconfig_migrate(xml_node_t *node)
+static ni_bool_t
+ni_ifconfig_migrate_team_port(xml_node_t *migrate,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		ni_bool_t l2v6)
 {
-	ni_bool_t modified = FALSE;
-	xml_node_t *child;
+	const char *ptype = ni_linktype_type_to_name(type);
+	const xml_node_t *oconf = NULL;
+	const char *otype = NULL;
+	xml_node_t *nconf;
+	int ret;
 
-	if (!node)
+	if ((ret = ni_ifconfig_migrate_port_master(migrate, port, ptype, upper)) < 0)
 		return FALSE;
 
-	/* node itself is a config or policy node  */
-	if (ni_ifconfig_migrate_node(node, &modified))
-		return modified;
+	if ((otype = ni_ifconfig_link_get_port_config(migrate, &oconf))) {
+		if (ni_string_eq(otype, ptype))
+			return ret == 0; /* no port config to _migrate_ */
+
+		ni_warn("%s: removing mismatching %s port config from %s port '%s'",
+				xml_node_location(oconf), otype, ptype, port);
+	}
+	if ((nconf = ni_ifconfig_create_port_config_node(ptype))) {
+		xml_node_t *node;
+
+		if ((node = xml_node_get_child(data, "queue_id")))
+			xml_node_reparent(nconf, node);
+		if ((node = xml_node_get_child(data, "prio")))
+			xml_node_reparent(nconf, node);
+		if ((node = xml_node_get_child(data, "sticky")))
+			xml_node_reparent(nconf, node);
+		if ((node = xml_node_get_child(data, "lacp_key")))
+			xml_node_reparent(nconf, node);
+		if ((node = xml_node_get_child(data, "lacp_prio")))
+			xml_node_reparent(nconf, node);
+
+		if (!ni_ifconfig_link_set_port_config(migrate, nconf))
+			xml_node_free(nconf);
+		else
+			ret = 0;
+	}
+
+	if (ni_ifconfig_migrate_l2_port_ipv4(migrate))
+		ret = 0;
+
+	if (ni_ifconfig_migrate_l2_port_ipv6(migrate, l2v6))
+		ret = 0;
+
+	return ret == 0;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_bridge_port(xml_node_t *migrate,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		ni_bool_t l2v6)
+{
+	const char *ptype = ni_linktype_type_to_name(type);
+	const xml_node_t *oconf = NULL;
+	const char *otype = NULL;
+	xml_node_t *nconf;
+	int ret;
+
+	if ((ret = ni_ifconfig_migrate_port_master(migrate, port, ptype, upper)) < 0)
+		return FALSE;
+
+	if ((otype = ni_ifconfig_link_get_port_config(migrate, &oconf))) {
+		if (ni_string_eq(otype, ptype))
+			return ret == 0; /* no port config to _migrate_ */
+
+		ni_warn("%s: removing mismatching %s port config from %s port '%s'",
+				xml_node_location(oconf), otype, ptype, port);
+	}
+	if ((nconf = ni_ifconfig_create_port_config_node(ptype))) {
+		xml_node_t *node;
+
+		if ((node = xml_node_get_child(data, "priority")))
+			xml_node_reparent(nconf, node);
+		if ((node = xml_node_get_child(data, "path-cost")))
+			xml_node_reparent(nconf, node);
+
+		if (!ni_ifconfig_link_set_port_config(migrate, nconf))
+			xml_node_free(nconf);
+		else
+			ret = 0;
+	}
+
+	if (ni_ifconfig_migrate_l2_port_ipv4(migrate))
+		ret = 0;
+
+	if (ni_ifconfig_migrate_l2_port_ipv6(migrate, l2v6))
+		ret = 0;
+
+	return ret == 0;
+}
 
-	/* node is a document root with children   */
-	for (child = node->children; child; child = child->next)
-		ni_ifconfig_migrate_node(child, &modified);
+static ni_bool_t
+ni_ifconfig_migrate_ovsbr_port(xml_node_t *migrate,
+		const char *ovsbr, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		ni_bool_t l2v6)
+{
+	const char *ptype = ni_linktype_type_to_name(type);
+	const xml_node_t *oconf = NULL;
+	const char *otype = NULL;
+	xml_node_t *nconf, *link;
+	int ret = 1;
+
+	/*
+	 * Hmm... just migrate the port's <link/> node as such -- for ovs,
+	 * we replace the ovs-system master with bridge from port config.
+	 */
+	link = xml_node_get_child(migrate, NI_CLIENT_IFCONFIG_LINK);
+	if (ni_ifconfig_migrate_link_config(link))
+		ret = 0;
+
+	/*
+	 * Now, we set the current ovsbr as master of the port and check
+	 * for conflict .. same as with any other (bond,team,...) master.
+	 */
+	if ((ret = ni_ifconfig_migrate_port_master(migrate, port, ptype, ovsbr)) < 0)
+		return FALSE;
+
+	if ((otype = ni_ifconfig_link_get_port_config(migrate, &oconf))) {
+		if (ni_string_eq(otype, ptype))
+			return ret == 0;
+
+		ni_warn("%s: removing mismatching %s port config from %s port '%s'",
+				xml_node_location(oconf), otype, ptype, port);
+	}
+	if ((nconf = ni_ifconfig_create_port_config_node(ptype))) {
+		/*
+		 * no port specific properties to migrate
+		 * from (old) ovs-bridge:port-device node
+		 */
+		(void)data;
+
+		/*
+		 * The ovs-bridge:port-config was always
+		 * in the port node of the port interface
+		 * and referring to the ovs bridge.
+		 */
+		if (!ni_ifconfig_link_set_port_config(migrate, nconf))
+			xml_node_free(nconf);
+		else
+			ret = 0;
+	}
+
+	if (ni_ifconfig_migrate_l2_port_ipv4(migrate))
+		ret = 0;
+
+	if (ni_ifconfig_migrate_l2_port_ipv6(migrate, l2v6))
+		ret = 0;
+
+	return ret == 0;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_l2_port(xml_node_t *migrate,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		ni_bool_t l2v6)
+{
+	ni_bool_t modified;
+
+	switch (type) {
+	case NI_IFTYPE_BOND:
+		modified = ni_ifconfig_migrate_bond_port(migrate,
+				upper, port, type, data, l2v6);
+		break;
+	case NI_IFTYPE_TEAM:
+		modified = ni_ifconfig_migrate_team_port(migrate,
+				upper, port, type, data, l2v6);
+		break;
+	case NI_IFTYPE_BRIDGE:
+		modified = ni_ifconfig_migrate_bridge_port(migrate,
+				upper, port, type, data, l2v6);
+		break;
+	case NI_IFTYPE_OVS_BRIDGE:
+		modified = ni_ifconfig_migrate_ovsbr_port(migrate,
+				upper, port, type, data, l2v6);
+		break;
+	default:
+		modified = FALSE;
+		break;
+	}
+	if (modified)
+		ni_ifxml_node_set_migrated(migrate, TRUE);
+	return modified;
+}
+
+static xml_node_t *
+ni_ifconfig_create_l2_port(xml_document_array_t *docs,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		const char *origin, const char *owner,
+		ni_bool_t l2v6)
+{
+	xml_document_t *doc;
+	xml_node_t *config;
+
+	if (!(config = ni_ifconfig_create(port, origin, owner)))
+		goto failure;
+
+	if (!ni_ifconfig_control_set_mode(config, "hotplug"))
+		goto failure;
+
+	if (!ni_ifconfig_migrate_l2_port(config, upper, port, type, data, l2v6))
+		goto failure;
+
+	if (!ni_ifxml_node_set_migrated(config, TRUE))
+		goto failure;
+
+	if (!(doc = xml_document_create(NULL, config)))
+		goto failure;
+
+	if (xml_document_array_append(docs, doc))
+		return config;
+
+	xml_document_free(doc);
+	return NULL;
+
+failure:
+	xml_node_free(config);
+	return NULL;
+}
+
+static ni_bool_t
+ni_ifpolicy_create_l2_port(xml_document_array_t *docs,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		const char *origin, const char *owner,
+		ni_bool_t l2v6)
+{
+	xml_node_t *policy = NULL;
+	xml_node_t *config = NULL;
+	xml_document_t *doc;
+
+	if (!(policy = ni_ifpolicy_create(port, origin, owner, &config)))
+		goto failure;
+
+	if (!ni_ifconfig_control_set_mode(config, "hotplug"))
+		goto failure;
+
+	if (!ni_ifconfig_migrate_l2_port(config, upper, port, type, data, l2v6))
+		goto failure;
+
+	if (!ni_ifxml_node_set_migrated(policy, TRUE))
+		goto failure;
+
+	if (!(doc = xml_document_create(NULL, policy)))
+		goto failure;
+
+	if (xml_document_array_append(docs, doc))
+		return TRUE;
+
+	xml_document_free(doc);
+	return FALSE;
+
+failure:
+	xml_node_free(policy);
+	return FALSE;
+}
+
+static ni_bool_t
+ni_ifxml_migrate_l2_port(xml_document_array_t *docs,
+		const char *upper, const char *port,
+		ni_iftype_t type, const xml_node_t *data,
+		const char *origin, const char *owner,
+		ni_bool_t l2v6, ni_bool_t policy)
+{
+	ni_bool_t modified = FALSE;
+	xml_node_t *config;
+
+	if ((config = ni_ifxml_find_config_by_ifname(docs, port))) {
+		if (ni_ifconfig_migrate_l2_port(config, upper, port, type, data, l2v6))
+			modified = TRUE;
+	} else if (policy) {
+		if (ni_ifpolicy_create_l2_port(docs, upper, port, type, data, origin, owner, l2v6))
+			modified = TRUE;
+	} else {
+		if (ni_ifconfig_create_l2_port(docs, upper, port, type, data, origin, owner, l2v6))
+			modified = TRUE;
+	}
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifpolicy_match_remove_child_ref(xml_node_t *policy, const char *name)
+{
+	xml_node_t *match, *or, *child, *next, *device;
+	ni_bool_t modified = FALSE;
+	const char *ns;
+
+	/*
+	 * A bond,team,[ovs-]bridge were referencing their ports via:
+	 * <or>
+	 *   <child><device>${portname}</device></or>
+	 *   […]
+	 * </or>
+	 * We remove the obsolete reference to the port inclusive of
+	 * the <or> and <child> nodes once they're empty.
+	 */
+	if (!policy || ni_string_empty(name))
+		return modified;
+
+	if (!(match = xml_node_get_child(policy, NI_NANNY_IFPOLICY_MATCH)))
+		return modified;
+
+	if (!(or = xml_node_get_child(match, NI_NANNY_IFPOLICY_MATCH_COND_OR)))
+		return modified;
+
+	for (child = or->children; child; child = next) {
+		next = or->next;
+
+		if (!(device = xml_node_get_child(child, NI_NANNY_IFPOLICY_MATCH_DEV)))
+			continue;
+
+		ns = xml_node_get_attr(device, "namespace");
+		if (!ni_string_empty(ns))
+			continue;
+
+		if (!ni_string_eq(device->cdata, name))
+			continue;
+
+		if (xml_node_delete_child_node(child, device))
+			modified = TRUE;
+
+		if (xml_node_is_empty(child) && xml_node_delete_child_node(or, child))
+			modified = TRUE;
+
+		break; /* We don't expect to find it again */
+	}
+	if (xml_node_is_empty(or) && xml_node_delete_child_node(match, or))
+		modified = TRUE;
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_bond_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *migrate)
+{
+	ni_bool_t   modified = FALSE;
+	xml_node_t *ports, *port;
+	xml_node_t *policy;
+	xml_node_t *device;
+	const char *primary = NULL;
+	const char *origin;
+	const char *owner;
+	const char *bond;
+
+	if (!(ports = xml_node_get_child(migrate, "slaves")))
+		return modified;
+
+	if (!(bond = ni_ifconfig_get_ifname(config)))
+		return modified;
+
+	policy = ni_ifconfig_is_config(config) ? NULL : config->parent;
+	origin = ni_ifpolicy_get_origin(policy ?: config);
+	owner = ni_ifpolicy_get_owner(policy ?: config);
+
+	for (port = ports->children; port; port = port->next) {
+		if (!ni_string_eq("slave", port->name))
+			continue;
+
+		if (!(device = xml_node_get_child(port, "device")))
+			continue;
+
+		if (xml_node_get_attr(device, "namespace") ||
+		    ni_string_empty(device->cdata))
+			continue;
+
+		if (!primary) {
+			ni_bool_t enabled;
+			const char *ptr;
+
+			ptr = xml_node_get_child_cdata(port, "primary");
+			if (ni_parse_boolean(ptr, &enabled) == 0 && enabled)
+				primary = device->cdata;
+		}
+
+		if (ni_ifxml_migrate_l2_port(docs,
+				bond, device->cdata,
+				NI_IFTYPE_BOND, NULL,
+				origin, owner, FALSE, !!policy))
+			modified = TRUE;
+
+		if (ni_ifpolicy_match_remove_child_ref(policy, device->cdata))
+			modified = TRUE;
+	}
+
+	if (primary && !xml_node_get_child_cdata(migrate, "primary")) {
+		if (xml_node_new_element("primary", migrate, primary))
+			modified = TRUE;
+	}
+
+	if (ports && xml_node_delete_child_node(migrate, ports))
+		modified = TRUE;
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifconfig_team_l2v6(const xml_node_t *team)
+{
+	const xml_node_t *link_watch, *watch;
+
+	if (!team || !(link_watch = xml_node_get_child(team, "link_watch")))
+		return FALSE;
+
+	for (watch = link_watch->children; watch; watch = watch->next) {
+		if (xml_node_get_attr(watch, "nsna_ping"))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_team_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *migrate)
+{
+	ni_bool_t   modified = FALSE;
+	xml_node_t *ports, *port;
+	xml_node_t *policy;
+	xml_node_t *device;
+	const char *origin;
+	const char *owner;
+	const char *team;
+	ni_bool_t   l2v6;
+
+	if (!(ports = xml_node_get_child(migrate, "ports")))
+		return modified;
+
+	if (!(team = ni_ifconfig_get_ifname(config)))
+		return modified;
+
+	policy = ni_ifconfig_is_config(config) ? NULL : config->parent;
+	origin = ni_ifpolicy_get_origin(policy ?: config);
+	owner = ni_ifpolicy_get_owner(policy ?: config);
+
+	l2v6 = ni_ifconfig_team_l2v6(migrate);
+	for (port = ports->children; port; port = port->next) {
+		if (!ni_string_eq("port", port->name))
+			continue;
+
+		if (!(device = xml_node_get_child(port, "device")))
+			continue;
+
+		if (xml_node_get_attr(device, "namespace") ||
+		    ni_string_empty(device->cdata))
+			continue;
+
+		if (ni_ifxml_migrate_l2_port(docs,
+				team, device->cdata,
+				NI_IFTYPE_TEAM, port,
+				origin, owner, l2v6, !!policy))
+			modified = TRUE;
+
+		if (ni_ifpolicy_match_remove_child_ref(policy, device->cdata))
+			modified = TRUE;
+	}
+
+	if (ports && xml_node_delete_child_node(migrate, ports))
+		modified = TRUE;
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_bridge_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *migrate)
+{
+	ni_bool_t   modified = FALSE;
+	xml_node_t *ports, *port;
+	xml_node_t *policy;
+	xml_node_t *device;
+	const char *origin;
+	const char *owner;
+	const char *bridge;
+
+	if (!(ports = xml_node_get_child(migrate, "ports")))
+		return modified;
+
+	if (!(bridge = ni_ifconfig_get_ifname(config)))
+		return modified;
+
+	policy = ni_ifconfig_is_config(config) ? NULL : config->parent;
+	origin = ni_ifpolicy_get_origin(policy ?: config);
+	owner = ni_ifpolicy_get_owner(policy ?: config);
+
+	for (port = ports->children; port; port = port->next) {
+		if (!ni_string_eq("port", port->name))
+			continue;
+
+		if (!(device = xml_node_get_child(port, "device")))
+			continue;
+
+		if (xml_node_get_attr(device, "namespace") ||
+		    ni_string_empty(device->cdata))
+			continue;
+
+		if (ni_ifxml_migrate_l2_port(docs,
+				bridge, device->cdata,
+				NI_IFTYPE_BRIDGE, port,
+				origin, owner, FALSE, !!policy))
+			modified = TRUE;
+
+		if (ni_ifpolicy_match_remove_child_ref(policy, device->cdata))
+			modified = TRUE;
+	}
+
+	if (ports && xml_node_delete_child_node(migrate, ports))
+		modified = TRUE;
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_ovsbr_node(xml_document_array_t *docs,
+		xml_node_t *config, xml_node_t *migrate)
+{
+	ni_bool_t   modified = FALSE;
+	xml_node_t *ports, *port;
+	xml_node_t *policy;
+	xml_node_t *device;
+	const char *origin;
+	const char *owner;
+	const char *ovsbr;
+
+	if (!(ports = xml_node_get_child(migrate, "ports")))
+		return modified;
+
+	if (!(ovsbr = ni_ifconfig_get_ifname(config)))
+		return modified;
+
+	policy = ni_ifconfig_is_config(config) ? NULL : config->parent;
+	origin = ni_ifpolicy_get_origin(policy ?: config);
+	owner = ni_ifpolicy_get_owner(policy ?: config);
+
+	for (port = ports->children; port; port = port->next) {
+		if (!ni_string_eq("port", port->name))
+			continue;
+
+		if (!(device = xml_node_get_child(port, "device")))
+			continue;
+
+		if (xml_node_get_attr(device, "namespace") ||
+		    ni_string_empty(device->cdata))
+			continue;
+
+		if (ni_ifxml_migrate_l2_port(docs,
+				ovsbr, device->cdata,
+				NI_IFTYPE_OVS_BRIDGE, port,
+				origin, owner, FALSE, !!policy))
+			modified = TRUE;
+
+		if (ni_ifpolicy_match_remove_child_ref(policy, device->cdata))
+			modified = TRUE;
+	}
+
+	if (ports && xml_node_delete_child_node(migrate, ports))
+		modified = TRUE;
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifconfig_migrate_node(xml_document_array_t *docs,
+		ni_bool_t (*func)(xml_document_array_t *,
+				xml_node_t *, xml_node_t *),
+		const char *name, xml_node_t *config)
+{
+	xml_node_t *node;
+
+	if (!(node = xml_node_get_child(config, name)))
+		return FALSE;
+
+	if (!func(docs, config, node))
+		return FALSE;
+
+	ni_ifxml_node_set_migrated(config, TRUE);
+	return TRUE;
+}
+
+static ni_bool_t
+ni_ifpolicy_migrate_node(xml_document_array_t *docs,
+		ni_bool_t (*func)(xml_document_array_t *,
+				xml_node_t *, xml_node_t *),
+		const char *name, xml_node_t *policy)
+{
+	ni_bool_t modified = FALSE;
+	xml_node_t *config = NULL;
+
+	/* policy action contains the effective ifconfig data */
+	if ((config = xml_node_get_child(policy, NI_NANNY_IFPOLICY_MERGE))) {
+		if (ni_ifconfig_migrate_node(docs, func, name, config))
+			modified = TRUE;
+	}
+	if ((config = xml_node_get_child(policy, NI_NANNY_IFPOLICY_REPLACE))) {
+		if (ni_ifconfig_migrate_node(docs, func, name, config))
+			modified = TRUE;
+	}
+	if ((config = xml_node_get_child(policy, NI_NANNY_IFPOLICY_CREATE))) {
+		if (ni_ifconfig_migrate_node(docs, func, name, config))
+			modified = TRUE;
+	}
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifxml_migrate_ifconfig_node(xml_document_array_t *docs, const char *name,
+	ni_bool_t (*func)(xml_document_array_t *, xml_node_t *, xml_node_t *))
+{
+	ni_bool_t modified = FALSE;
+	xml_document_t *doc;
+	xml_node_t *root;
+	unsigned int i;
+
+	for (i = 0; i < docs->count; ++i) {
+		doc = docs->data[i];
+		root = xml_document_root(doc);
+
+		if (ni_ifconfig_is_config(root)) {
+			if (ni_ifconfig_migrate_node(docs, func, name, root))
+				modified = TRUE;
+			continue;
+		}
+
+		if (ni_ifconfig_is_policy(root)) {
+			if (ni_ifpolicy_migrate_node(docs, func, name, root))
+				modified = TRUE;
+			continue;
+		}
+	}
+
+	return modified;
+}
+
+static ni_bool_t
+ni_ifxml_migrate_nodes(xml_document_array_t *docs)
+{
+	struct migrate_node {
+		const char *name;
+		ni_bool_t (*func)(xml_document_array_t *, xml_node_t *, xml_node_t *);
+	} *migrate, map[] = {
+		{ "ethernet",	ni_ifconfig_migrate_ethernet_node	},
+		{ "wireless",	ni_ifconfig_migrate_wireless_node	},
+		{ "link",	ni_ifconfig_migrate_link_node		},
+		{ "bond",	ni_ifconfig_migrate_bond_node		},
+		{ "team",	ni_ifconfig_migrate_team_node		},
+		{ "bridge",	ni_ifconfig_migrate_bridge_node		},
+		{ "ovs-bridge",	ni_ifconfig_migrate_ovsbr_node		},
+
+		{ NULL }
+	};
+	ni_bool_t modified = FALSE;
+
+	for (migrate = map; migrate->name && migrate->func; ++migrate) {
+		if (ni_ifxml_migrate_ifconfig_node(docs,
+				migrate->name, migrate->func))
+			modified = TRUE;
+	}
+
+	return modified;
+}
+
+ni_bool_t
+ni_ifxml_migrate_docs(xml_document_array_t *docs)
+{
+	ni_bool_t modified = FALSE;
+	xml_document_t *doc;
+	xml_node_t *root;
+	ni_uuid_t uuid;
+	unsigned int i;
+
+	modified = ni_ifxml_migrate_nodes(docs);
+	for (i = 0; i < docs->count; ++i) {
+		doc = docs->data[i];
+		root = xml_document_root(doc);
+
+		if (!ni_ifxml_node_is_migrated(root))
+			continue;
+
+		/* calculate and update checksum uuid */
+		ni_ifconfig_generate_uuid(root, &uuid);
+		ni_ifpolicy_set_uuid(root, &uuid);
+	}
 
 	return modified;
 }
diff --git a/src/dbus-objects/bonding.c b/src/dbus-objects/bonding.c
index 22cf67945..e3d65a60a 100644
--- a/src/dbus-objects/bonding.c
+++ b/src/dbus-objects/bonding.c
@@ -412,181 +412,97 @@ __ni_objectmodel_bonding_set_arpmon(ni_dbus_object_t *object,
 }
 
 /*
- * Helper for bond slave info
+ * Bonding port (link-request) configuration
  */
-static dbus_bool_t
-__ni_objectmodel_bonding_slave_info_to_dict(const ni_bonding_slave_t *slave, ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_bonding_port_config(const ni_bonding_port_config_t *conf,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
-	if (!slave || !slave->info || !dict)
-		return FALSE;
-
-	if (slave->info->state != -1U)
-		ni_dbus_dict_add_uint32(dict, "state", slave->info->state);
-
-	if (slave->info->mii_status != -1U)
-		ni_dbus_dict_add_uint32(dict, "mii-status", slave->info->mii_status);
-
-	if (slave->info->perm_hwaddr.type == ARPHRD_ETHER && slave->info->perm_hwaddr.len)
-		__ni_objectmodel_dict_add_hwaddr(dict, "perm-hwaddr", &slave->info->perm_hwaddr);
+	(void)error;
 
-	if (slave->info->link_failure_count)
-		ni_dbus_dict_add_uint32(dict, "link-failures", slave->info->link_failure_count);
-
-	if (slave->info->queue_id != -1U)
-		ni_dbus_dict_add_uint16(dict, "queue-id", slave->info->queue_id);
+	if (!conf || !dict)
+		return FALSE;
 
-	if (slave->info->ad_aggregator_id != -1U)
-		ni_dbus_dict_add_uint16(dict, "ad-aggregator-id", slave->info->ad_aggregator_id);
+	if (conf->queue_id != -1U)
+		ni_dbus_dict_add_uint16(dict, "queue-id", conf->queue_id);
 
 	return TRUE;
 }
-
-static dbus_bool_t
-__ni_objectmodel_bonding_slave_info_from_dict(ni_bonding_slave_t *slave, const ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_bonding_port_config(ni_bonding_port_config_t *conf,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	uint32_t u32;
 	uint16_t u16;
 
-	if (!slave || !dict)
-		return FALSE;
-
-	if (dict->array.len == 0)
-		return TRUE;
+	(void)error;
 
-	if (slave->info)
-		ni_bonding_slave_info_reset(slave->info);
-	else if (!(slave->info = ni_bonding_slave_info_new()))
+	if (!conf || !dict)
 		return FALSE;
 
-	if (ni_dbus_dict_get_uint32(dict, "state", &u32))
-		slave->info->state = u32;
-
-	if (ni_dbus_dict_get_uint32(dict, "mii-status", &u32))
-		slave->info->mii_status = u32;
-
-	if (__ni_objectmodel_dict_get_hwaddr(dict, "perm-hwaddr", &slave->info->perm_hwaddr)) {
-		if (slave->info->perm_hwaddr.len == ni_link_address_length(ARPHRD_ETHER))
-			slave->info->perm_hwaddr.type = ARPHRD_ETHER;
-	}
-
-	if (ni_dbus_dict_get_uint32(dict, "link-failures", &u32))
-		slave->info->link_failure_count = u32;
-
 	if (ni_dbus_dict_get_uint16(dict, "queue-id", &u16))
-		slave->info->queue_id = u16;
-
-	if (ni_dbus_dict_get_uint16(dict, "ad-aggregator-id", &u16))
-		slave->info->ad_aggregator_id = u16;
+		conf->queue_id = u16;
 
 	return TRUE;
 }
 
 /*
- * Get/set the list of slaves
+ * Bonding port interface info properties
  */
-static dbus_bool_t
-__ni_objectmodel_bonding_get_slaves(const ni_dbus_object_t *object,
-				const ni_dbus_property_t *property,
-				ni_dbus_variant_t *result,
-				DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_bonding_port_info(const ni_bonding_port_info_t *info,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
-	const ni_bonding_t *bond;
-	unsigned int i;
+	(void)error;
 
-	if (!(bond = __ni_objectmodel_bonding_read_handle(object, error)))
+	if (!info || !dict)
 		return FALSE;
 
-	ni_dbus_dict_array_init(result);
-	for (i = 0; i < bond->slaves.count; ++i) {
-		const ni_bonding_slave_t *slave = bond->slaves.data[i];
-		const char *slave_name;
-		ni_dbus_variant_t *dict;
-
-		/* should not happen, but better safe than sorry */
-		if (!slave || ni_string_empty(slave->device.name))
-			continue;
-
-		slave_name = slave->device.name;
-		dict = ni_dbus_dict_array_add(result);
-
-		ni_dbus_dict_add_string(dict, "device", slave_name);
-		if (bond->primary_slave.name && ni_string_eq(bond->primary_slave.name, slave_name))
-			ni_dbus_dict_add_bool(dict, "primary", TRUE);
-		if (bond->active_slave.name && ni_string_eq(bond->active_slave.name, slave_name))
-			ni_dbus_dict_add_bool(dict, "active", TRUE);
-
-		__ni_objectmodel_bonding_slave_info_to_dict(slave, dict, error);
-	}
+	if (info->state != -1U)
+		ni_dbus_dict_add_uint32(dict, "state", info->state);
+	if (info->mii_status != -1U)
+		ni_dbus_dict_add_uint32(dict, "mii-status", info->mii_status);
+	if (info->queue_id != -1U)
+		ni_dbus_dict_add_uint16(dict, "queue-id", info->queue_id);
+	if (info->link_failure_count)
+		ni_dbus_dict_add_uint32(dict, "link-failures", info->link_failure_count);
+	if (info->perm_hwaddr.len)
+		__ni_objectmodel_dict_add_hwaddr(dict, "perm-hwaddr", &info->perm_hwaddr);
+	if (info->ad_aggregator_id != -1U)
+		ni_dbus_dict_add_uint16(dict, "ad-aggregator-id", info->ad_aggregator_id);
 
 	return TRUE;
 }
-
-static dbus_bool_t
-__ni_objectmodel_bonding_set_slaves(ni_dbus_object_t *object,
-				const ni_dbus_property_t *property,
-				const ni_dbus_variant_t *result,
-				DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_bonding_port_info(ni_bonding_port_info_t *info,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	ni_bonding_t *bond;
-	ni_dbus_variant_t *var;
-	unsigned int i;
+	uint32_t u32;
+	uint16_t u16;
 
-	if (!(bond = __ni_objectmodel_bonding_write_handle(object, error)))
-		return FALSE;
+	(void)error;
 
-	if (!ni_dbus_variant_is_dict_array(result)) {
-		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-				"%s.%s property - expected dict array",
-				object->path, property->name);
+	if (!info || !dict)
 		return FALSE;
-	}
 
-	ni_netdev_ref_destroy(&bond->primary_slave);
-	ni_netdev_ref_destroy(&bond->active_slave);
-	ni_bonding_slave_array_destroy(&bond->slaves);
-	for (i = 0, var = result->variant_array_value; i < result->array.len; ++i, ++var) {
-		dbus_bool_t is_primary = FALSE;
-		dbus_bool_t is_active = FALSE;
-		ni_bonding_slave_t *slave;
-		const char *slave_name;
-
-		if (!ni_dbus_dict_get_string(var, "device", &slave_name) ||
-				ni_string_empty(slave_name)) {
-			dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-					"%s.%s property - missing device attribute",
-					object->path, property->name);
-			return FALSE;
-		}
-		if (ni_bonding_has_slave(bond, slave_name)) {
-			dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-				"%s.%s property - duplicate slave device %s",
-				object->path, property->name, slave_name);
-			return FALSE;
-		}
-
-		if (ni_dbus_dict_get_bool(var, "primary", &is_primary) && is_primary) {
-			if (bond->primary_slave.name) {
-				dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-					"%s.%s property - duplicate primary device",
-					object->path, property->name);
-				return FALSE;
-			}
-			ni_string_dup(&bond->primary_slave.name, slave_name);
-		}
-		if (ni_dbus_dict_get_bool(var, "active", &is_active) && is_active) {
-			if (bond->active_slave.name) {
-				dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-					"%s.%s property - duplicate active device",
-					object->path, property->name);
-				return FALSE;
-			}
-			ni_string_dup(&bond->active_slave.name, slave_name);
-		}
-
-		slave = ni_bonding_add_slave(bond, slave_name);
-		if (slave)
-			__ni_objectmodel_bonding_slave_info_from_dict(slave, var, error);
+	if (ni_dbus_dict_get_uint32(dict, "state", &u32))
+		info->state = u32;
+	if (ni_dbus_dict_get_uint32(dict, "mii-status", &u32))
+		info->mii_status = u32;
+	if (ni_dbus_dict_get_uint32(dict, "link-failures", &u32))
+		info->link_failure_count = u32;
+	if (__ni_objectmodel_dict_get_hwaddr(dict, "perm-hwaddr", &info->perm_hwaddr)) {
+		if (info->perm_hwaddr.len == ni_link_address_length(ARPHRD_INFINIBAND))
+			info->perm_hwaddr.type = ARPHRD_INFINIBAND;
+		else if (info->perm_hwaddr.len == ni_link_address_length(ARPHRD_ETHER))
+			info->perm_hwaddr.type = ARPHRD_ETHER;
+		else
+			info->perm_hwaddr.type = ARPHRD_VOID;
 	}
+	if (ni_dbus_dict_get_uint16(dict, "queue-id", &u16))
+		info->queue_id = u16;
+	if (ni_dbus_dict_get_uint16(dict, "ad-aggregator-id", &u16))
+		info->ad_aggregator_id = u16;
+
 	return TRUE;
 }
 
@@ -676,15 +592,13 @@ static ni_dbus_property_t	ni_objectmodel_bond_properties[] = {
 	BONDING_UINT_PROPERTY(num-grat-arp, num_grat_arp, RO),
 	BONDING_UINT_PROPERTY(num-unsol-na, num_unsol_na, RO),
 	BONDING_UINT_PROPERTY(fail-over-mac, fail_over_mac, RO),
+	BONDING_STRING_PROPERTY(primary, primary_slave.name, RO),
 	BONDING_UINT_PROPERTY(primary-reselect, primary_reselect, RO),
 	BONDING_BOOL_PROPERTY(all-slaves-active, all_slaves_active, RO),
 	BONDING_UINT_PROPERTY(packets-per-slave, packets_per_slave, RO),
 	BONDING_BOOL_PROPERTY(tlb-dynamic-lb, tlb_dynamic_lb, RO),
 	BONDING_UINT_PROPERTY(lp-interval, lp_interval, RO),
 
-	__NI_DBUS_PROPERTY(
-			DBUS_TYPE_ARRAY_AS_STRING NI_DBUS_DICT_SIGNATURE,
-			slaves, __ni_objectmodel_bonding, RO),
 	__NI_DBUS_PROPERTY(
 			NI_DBUS_DICT_SIGNATURE,
 			miimon, __ni_objectmodel_bonding, RO),
diff --git a/src/dbus-objects/bridge.c b/src/dbus-objects/bridge.c
index 8ee6554fb..bb6f960b5 100644
--- a/src/dbus-objects/bridge.c
+++ b/src/dbus-objects/bridge.c
@@ -29,14 +29,6 @@
 #include "debug.h"
 
 static ni_netdev_t *	__ni_objectmodel_bridge_newlink(ni_netdev_t *, const char *, DBusError *);
-static dbus_bool_t	__ni_objectmodel_bridge_port_to_dict(const ni_bridge_port_t *port,
-				ni_dbus_variant_t *dict,
-				const ni_dbus_object_t *object,
-				int config_only);
-static dbus_bool_t	__ni_objectmodel_bridge_port_from_dict(ni_bridge_port_t *port,
-				const ni_dbus_variant_t *dict,
-				DBusError *error,
-				int config_only);
 
 /*
  * Return an interface handle containing all bridge-specific information provided
@@ -237,18 +229,6 @@ __ni_objectmodel_bridge_handle(const ni_dbus_object_t *object, ni_bool_t write_a
 	return bridge;
 }
 
-static ni_bridge_t *
-__ni_objectmodel_bridge_write_handle(const ni_dbus_object_t *object, DBusError *error)
-{
-	return __ni_objectmodel_bridge_handle(object, TRUE, error);
-}
-
-static const ni_bridge_t *
-__ni_objectmodel_bridge_read_handle(const ni_dbus_object_t *object, DBusError *error)
-{
-	return __ni_objectmodel_bridge_handle(object, FALSE, error);
-}
-
 void *
 ni_objectmodel_get_bridge(const ni_dbus_object_t *object, ni_bool_t write_access, DBusError *error)
 {
@@ -256,131 +236,99 @@ ni_objectmodel_get_bridge(const ni_dbus_object_t *object, ni_bool_t write_access
 }
 
 /*
- * Property ports
+ * Bridge port (link-request) configuration
  */
-static dbus_bool_t
-__ni_objectmodel_bridge_get_ports(const ni_dbus_object_t *object, const ni_dbus_property_t *property,
-				ni_dbus_variant_t *result, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_bridge_port_config(const ni_bridge_port_config_t *conf,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
-	const ni_bridge_t *bridge;
-	unsigned int i;
+	(void)error;
 
-	if (!(bridge = __ni_objectmodel_bridge_read_handle(object, error)))
+	if (!conf || !dict)
 		return FALSE;
 
-	ni_dbus_dict_array_init(result);
-	for (i = 0; i < bridge->ports.count; ++i) {
-		const ni_bridge_port_t *port = bridge->ports.data[i];
-		ni_dbus_variant_t *dict;
-
-		/* Append a new element to the array */
-		if (!(dict = ni_dbus_dict_array_add(result)))
-			return FALSE;
-		ni_dbus_variant_init_dict(dict);
+	if (conf->priority != NI_BRIDGE_VALUE_NOT_SET)
+		ni_dbus_dict_add_uint32(dict, "priority", conf->priority);
+	if (conf->path_cost != NI_BRIDGE_VALUE_NOT_SET)
+		ni_dbus_dict_add_uint32(dict, "path-cost", conf->path_cost);
 
-		if (!__ni_objectmodel_bridge_port_to_dict(port, dict, object, 0))
-			return FALSE;
-	}
 	return TRUE;
 }
-
-static dbus_bool_t
-__ni_objectmodel_bridge_set_ports(ni_dbus_object_t *object, const ni_dbus_property_t *property,
-				const ni_dbus_variant_t *argument, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_bridge_port_config(ni_bridge_port_config_t *conf,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	ni_dbus_variant_t *port_dict;
-	ni_bridge_t *bridge;
-	unsigned int i;
+	uint32_t value;
 
-	if (!(bridge = __ni_objectmodel_bridge_write_handle(object, error)))
-		return FALSE;
+	(void)error;
 
-	if (!ni_dbus_variant_is_dict_array(argument))
+	if (!conf || !dict)
 		return FALSE;
 
-	port_dict = argument->variant_array_value;
-	for (i = 0; i < argument->array.len; ++i, ++port_dict) {
-		ni_bridge_port_t *port;
-
-		port = ni_bridge_port_new(NULL, NULL, 0);
-		if (!__ni_objectmodel_bridge_port_from_dict(port, port_dict, error, TRUE)) {
-			ni_bridge_port_free(port);
-			return FALSE;
-		}
-
-		if (ni_bridge_add_port(bridge, port) < 0) {
-			ni_bridge_port_free(port); /* duplicate port */
-		}
-	}
+	if (ni_dbus_dict_get_uint32(dict, "priority", &value))
+		conf->priority = value;
+	if (ni_dbus_dict_get_uint32(dict, "path-cost", &value))
+		conf->path_cost = value;
 
 	return TRUE;
 }
 
 /*
- * Helper functions to represent ports as a dbus dict
+ * Bridge port interface info properties
  */
-static dbus_bool_t
-__ni_objectmodel_bridge_port_to_dict(const ni_bridge_port_t *port, ni_dbus_variant_t *dict,
-				const ni_dbus_object_t *object,
-				int config_only)
+extern dbus_bool_t
+ni_objectmodel_get_bridge_port_info(const ni_bridge_port_info_t *info,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
-	/*
-	 * Hmm... Resolving the complete tree and ports to devices
-	 * while serializing at client side does not work properly,
-	 * e.g. when the port does not exists yet...
-	 *
-	 * FIXME: should we resolve the object path here?
-	 */
-	if (ni_string_empty(port->ifname))
-		return FALSE;
+	(void)error;
 
-	ni_dbus_dict_add_string(dict, "device", port->ifname);
-	ni_dbus_dict_add_uint32(dict, "priority", port->priority);
-	ni_dbus_dict_add_uint32(dict, "path-cost", port->path_cost);
+	if (!info || !dict)
+		return FALSE;
 
-	if (config_only)
-		return TRUE;
+	if (info->state)
+		ni_dbus_dict_add_uint32(dict, "state", info->state);
+	if (info->port_no)
+		ni_dbus_dict_add_uint32(dict, "port-no", info->port_no);
+	if (info->port_id)
+		ni_dbus_dict_add_uint32(dict, "port-id", info->port_id);
 
-	ni_dbus_dict_add_uint32(dict, "state", port->status.state);
-	ni_dbus_dict_add_uint32(dict, "port-id", port->status.port_id);
-	ni_dbus_dict_add_uint32(dict, "port-no", port->status.port_no);
+	if (info->priority != NI_BRIDGE_VALUE_NOT_SET)
+		ni_dbus_dict_add_uint32(dict, "priority", info->priority);
+	if (info->path_cost != NI_BRIDGE_VALUE_NOT_SET)
+		ni_dbus_dict_add_uint32(dict, "path-cost", info->path_cost);
 
 	return TRUE;
 }
 
-static dbus_bool_t
-__ni_objectmodel_bridge_port_from_dict(ni_bridge_port_t *port, const ni_dbus_variant_t *dict,
-				DBusError *error,
-				int config_only)
+extern dbus_bool_t
+ni_objectmodel_set_bridge_port_info(ni_bridge_port_info_t *info,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	const char *string;
 	uint32_t value;
 
-	if (dict->array.len == 0)
-		return TRUE;
+	(void)error;
 
-	/* FIXME: should expect object path here and map that to an ifindex */
-	if (ni_dbus_dict_get_string(dict, "device", &string) && !ni_string_empty(string))
-		ni_string_dup(&port->ifname, string);
-	else
+	if (!info || !dict)
 		return FALSE;
 
-	if (ni_dbus_dict_get_uint32(dict, "priority", &value))
-		port->priority = value;
-	if (ni_dbus_dict_get_uint32(dict, "path-cost", &value))
-		port->path_cost = value;
-
-	/* FIXME: Really? I don't think so... */
 	if (ni_dbus_dict_get_uint32(dict, "state", &value))
-		port->status.state = value;
-	if (ni_dbus_dict_get_uint32(dict, "port-id", &value))
-		port->status.port_id = value;
+		info->state = value;
 	if (ni_dbus_dict_get_uint32(dict, "port-no", &value))
-		port->status.port_no = value;
+		info->port_no = value;
+	if (ni_dbus_dict_get_uint32(dict, "port-id", &value))
+		info->port_id = value;
+
+	if (ni_dbus_dict_get_uint32(dict, "priority", &value))
+		info->priority = value;
+	if (ni_dbus_dict_get_uint32(dict, "path-cost", &value))
+		info->path_cost = value;
 
 	return TRUE;
 }
 
+/*
+ * Additional, not bridge-specific MAC/HW-Address property
+ */
 static dbus_bool_t
 ni_objectmodel_bridge_get_address(const ni_dbus_object_t *object,
 				const ni_dbus_property_t *property,
@@ -432,10 +380,6 @@ static const ni_dbus_property_t	ni_objectmodel_bridge_property_table[] = {
 	BRIDGE_TIME_PROPERTY(hello-time, hello_time, RO),
 	BRIDGE_TIME_PROPERTY(max-age, max_age, RO),
 
-	/* ports is an array of dicts */
-	WICKED_BRIDGE_PROPERTY_SIGNATURE(DBUS_TYPE_ARRAY_AS_STRING NI_DBUS_DICT_SIGNATURE,
-			ports, RO),
-
 	BRIDGE_HWADDR_PROPERTY(address, RO),
 
 	{ NULL }
diff --git a/src/dbus-objects/interface.c b/src/dbus-objects/interface.c
index 86014c08a..00e8fb118 100644
--- a/src/dbus-objects/interface.c
+++ b/src/dbus-objects/interface.c
@@ -1498,7 +1498,7 @@ ni_objectmodel_netif_client_state_control_to_dict(const ni_client_state_control_
 
 	if (ni_tristate_is_set(ctrl->require_link)) {
 		if (!ni_dbus_dict_add_bool(var, NI_CLIENT_STATE_XML_REQUIRE_LINK_NODE,
-			(dbus_bool_t) ni_tristate_is_disabled(ctrl->require_link))) {
+			(dbus_bool_t) ni_tristate_is_enabled(ctrl->require_link))) {
 			return FALSE;
 		}
 	}
@@ -1683,12 +1683,159 @@ ni_objectmodel_netif_client_state_scripts_from_dict(ni_client_state_scripts_t *s
 	return TRUE;
 }
 
+static dbus_bool_t
+ni_objectmodel_netif_get_master(const ni_dbus_object_t *object,
+		const ni_dbus_property_t *property,
+		ni_dbus_variant_t *result,
+		DBusError *error)
+{
+	const ni_netdev_ref_t *ref;
+	const ni_netdev_t *dev;
+
+	if (!(dev = ni_dbus_object_get_handle(object)))
+		return FALSE;
+
+	ref = ni_netdev_get_master_ref(dev);
+	if (!ref || ni_string_empty(ref->name))
+		return FALSE;
+
+	ni_dbus_variant_set_string(result, ref->name);
+	return TRUE;
+}
+static dbus_bool_t
+ni_objectmodel_netif_set_master(ni_dbus_object_t *object,
+		const ni_dbus_property_t *property,
+		const ni_dbus_variant_t *argument,
+		DBusError *error)
+{
+	const char *str = NULL;
+	ni_netdev_t *dev;
+
+	if (!(dev = ni_dbus_object_get_handle(object)))
+		return FALSE;
+
+	if (ni_dbus_variant_get_string(argument, &str))
+		return ni_netdev_ref_set(&dev->link.masterdev, str, 0);
+
+	ni_netdev_ref_destroy(&dev->link.masterdev);
+	return TRUE;
+}
+
+static inline ni_dbus_variant_t *
+ni_objectmodel_netif_port_union_init(ni_dbus_variant_t *result, ni_iftype_t type)
+{
+	ni_dbus_variant_t *dict;
+	const char *name;
+
+	name = ni_linktype_type_to_name(type);
+	if (!result || !name)
+		return NULL;
+
+	ni_dbus_variant_init_struct(result);
+	ni_dbus_struct_add_string(result, name);
+	if ((dict = ni_dbus_struct_add(result)))
+		ni_dbus_variant_init_dict(dict);
+	return dict;
+}
+
+static dbus_bool_t
+ni_objectmodel_netif_get_port(const ni_dbus_object_t *object,
+		const ni_dbus_property_t *property,
+		ni_dbus_variant_t *result,
+		DBusError *error)
+{
+	ni_dbus_variant_t *dict;
+	ni_netdev_t *dev;
+
+	if (!(dev = ni_dbus_object_get_handle(object)))
+		return FALSE;
+
+	switch (dev->link.port.type) {
+	case NI_IFTYPE_BOND:
+		if (!(dict = ni_objectmodel_netif_port_union_init(result, dev->link.port.type)))
+			return FALSE;
+		return ni_objectmodel_get_bonding_port_info(dev->link.port.bond, dict, error);
+
+	case NI_IFTYPE_TEAM:
+		if (!(dict = ni_objectmodel_netif_port_union_init(result, dev->link.port.type)))
+			return FALSE;
+		return ni_objectmodel_get_team_port_info(dev->link.port.team, dict, error);
+
+	case NI_IFTYPE_BRIDGE:
+		if (!(dict = ni_objectmodel_netif_port_union_init(result, dev->link.port.type)))
+			return FALSE;
+		return ni_objectmodel_get_bridge_port_info(dev->link.port.bridge, dict, error);
+
+	case NI_IFTYPE_OVS_BRIDGE:
+		if (!(dict = ni_objectmodel_netif_port_union_init(result, dev->link.port.type)))
+			return FALSE;
+		return ni_objectmodel_get_ovs_bridge_port_info(dev->link.port.ovsbr, dict, error);
+
+	default:
+		return ni_dbus_error_property_not_present(error, object->path, property->name);
+	}
+}
+static dbus_bool_t
+ni_objectmodel_netif_set_port(ni_dbus_object_t *object,
+		const ni_dbus_property_t *property,
+		const ni_dbus_variant_t *argument,
+		DBusError *error)
+{
+	const ni_dbus_variant_t *dict;
+	ni_netdev_t *dev;
+	const char *name;
+
+	if (!(dev = ni_dbus_object_get_handle(object)))
+		return FALSE;
+
+	/* make sure we clear previously applied port info */
+	ni_netdev_port_info_destroy(&dev->link.port);
+
+	if (!ni_dbus_struct_get_string(argument, 0, &name) || ni_string_empty(name)) {
+		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+				"missing union switch type in property %s.%s",
+				object->path, property->name);
+		return FALSE;
+	}
+	if (!(dict = ni_dbus_struct_get(argument, 1)) || !ni_dbus_variant_is_dict(dict)) {
+		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+				"missing %s union data dict in property %s.%s",
+				name, object->path, property->name);
+		return FALSE;
+	}
+
+	if (!ni_netdev_port_info_data_init(&dev->link.port, ni_linktype_name_to_type(name)))
+		return FALSE;
+
+	switch (dev->link.port.type) {
+	case NI_IFTYPE_BOND:
+		return ni_objectmodel_set_bonding_port_info(dev->link.port.bond, dict, error);
+
+	case NI_IFTYPE_TEAM:
+		return ni_objectmodel_set_team_port_info(dev->link.port.team, dict, error);
+
+	case NI_IFTYPE_BRIDGE:
+		return ni_objectmodel_set_bridge_port_info(dev->link.port.bridge, dict, error);
+
+	case NI_IFTYPE_OVS_BRIDGE:
+		return ni_objectmodel_set_ovs_bridge_port_info(dev->link.port.ovsbr, dict, error);
+
+	default:
+		return FALSE;
+	}
+}
 
 /*
  * Properties of an interface
  */
 #define NETIF_PROPERTY_SIGNATURE(signature, __name, rw) \
 	__NI_DBUS_PROPERTY(signature, __name, __ni_objectmodel_netif, rw)
+#define NETIF_MASTER_DEVICE_PROPERTY(dbus_name, type_name, rw) \
+	___NI_DBUS_PROPERTY(NI_DBUS_SIGNATURE(STRING), dbus_name, type_name, \
+				ni_objectmodel_netif, rw)
+#define NETIF_PORT_INFO_UNION_PROPERTY(dbus_name, type_name, rw) \
+	___NI_DBUS_PROPERTY(NI_DBUS_DICT_SIGNATURE, dbus_name, type_name, \
+				ni_objectmodel_netif, rw)
 
 static ni_dbus_property_t	ni_objectmodel_netif_properties[] = {
 	NI_DBUS_GENERIC_STRING_PROPERTY(netdev, name, name, RO),
@@ -1698,7 +1845,8 @@ static ni_dbus_property_t	ni_objectmodel_netif_properties[] = {
 	NI_DBUS_GENERIC_UINT_PROPERTY(netdev, mtu, link.mtu, RO),
 	NI_DBUS_GENERIC_UINT_PROPERTY(netdev, txqlen, link.txqlen, RO),
 	NI_DBUS_GENERIC_STRING_PROPERTY(netdev, alias, link.alias, RO),
-	NI_DBUS_GENERIC_STRING_PROPERTY(netdev, master, link.masterdev.name, RO),
+	NETIF_MASTER_DEVICE_PROPERTY(master, master, RO),
+	NETIF_PORT_INFO_UNION_PROPERTY(port, port, RO),
 
 	___NI_DBUS_PROPERTY(NI_DBUS_DICT_SIGNATURE,
 				client-state, client_state,
@@ -1728,59 +1876,63 @@ ni_objectmodel_get_netdev_req(const ni_dbus_object_t *object, ni_bool_t write_ac
 	return ni_dbus_object_get_handle(object);
 }
 
+static inline ni_dbus_variant_t *
+ni_objectmodel_netdev_req_port_union_init(ni_dbus_variant_t *result, ni_iftype_t type)
+{
+	ni_dbus_variant_t *dict;
+	const char *name;
+
+	name = ni_linktype_type_to_name(type);
+	if (!result || !name)
+		return NULL;
+
+	ni_dbus_variant_init_struct(result);
+	ni_dbus_struct_add_string(result, name);
+	if ((dict = ni_dbus_struct_add(result)))
+		ni_dbus_variant_init_dict(dict);
+	return dict;
+}
+
 static dbus_bool_t
 __ni_objectmodel_netdev_req_get_port(const ni_dbus_object_t *object, const ni_dbus_property_t *property,
 					ni_dbus_variant_t *result, DBusError *error)
 {
 	const ni_netdev_req_t *req;
 	ni_dbus_variant_t *dict;
-	const char *name;
 
 	if (!(req = ni_objectmodel_unwrap_netif_request(object, error)))
 		return FALSE;
-	if (!req->port)
-		return FALSE;
 
-	switch (req->port->type) {
-	case NI_IFTYPE_TEAM:
+	switch (req->port.type) {
 	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
-	case NI_IFTYPE_OVS_BRIDGE:
-		if ((name = ni_linktype_type_to_name(req->port->type)))
-			break;
-		/* fall through */
-	default:
-		return ni_dbus_error_property_not_present(error, object->path, property->name);
-	}
+		if (!(dict = ni_objectmodel_netdev_req_port_union_init(result, req->port.type)))
+			return FALSE;
 
-	ni_dbus_variant_init_struct(result);
-	ni_dbus_struct_add_string(result, name);
-	dict = ni_dbus_struct_add(result);
-	ni_dbus_variant_init_dict(dict);
+		return ni_objectmodel_get_bonding_port_config(req->port.bond, dict, error);
 
-	switch (req->port->type) {
-	case NI_IFTYPE_TEAM: {
-			const ni_team_port_config_t *pconf = &req->port->team;
+	case NI_IFTYPE_TEAM:
+		if (!(dict = ni_objectmodel_netdev_req_port_union_init(result, req->port.type)))
+			return FALSE;
 
-			if (!__ni_objectmodel_get_team_port_config(pconf, dict, error))
-				return FALSE;
-		}
-		break;
+		return ni_objectmodel_get_team_port_config(req->port.team, dict, error);
 
-	case NI_IFTYPE_OVS_BRIDGE: {
-			const ni_ovs_bridge_port_config_t *pconf = &req->port->ovsbr;
+	case NI_IFTYPE_BRIDGE:
+		if (!(dict = ni_objectmodel_netdev_req_port_union_init(result, req->port.type)))
+			return FALSE;
 
-			if (!__ni_objectmodel_get_ovs_bridge_port_config(pconf, dict, error))
-				return FALSE;
-		}
-		break;
+		return ni_objectmodel_get_bridge_port_config(req->port.bridge, dict, error);
+
+	case NI_IFTYPE_OVS_BRIDGE:
+		if (!(dict = ni_objectmodel_netdev_req_port_union_init(result, req->port.type)))
+			return FALSE;
+
+		return ni_objectmodel_get_ovs_bridge_port_config(req->port.ovsbr, dict, error);
 
-	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
 	default:
 		break;
 	}
-	return TRUE;
+
+	return ni_dbus_error_property_not_present(error, object->path, property->name);
 }
 
 static dbus_bool_t
@@ -1790,70 +1942,51 @@ __ni_objectmodel_netdev_req_set_port(ni_dbus_object_t *object, const ni_dbus_pro
 	const ni_dbus_variant_t *dict;
 	ni_netdev_req_t *req;
 	const char *name;
-	ni_iftype_t type;
 
 	if (!(req = ni_objectmodel_unwrap_netif_request(object, error)))
 		return FALSE;
 
+	/*
+	 * this should not happen, but make sure we clear
+	 * previously applied netdev request port config
+	 */
+	ni_netdev_port_config_destroy(&req->port);
+
 	if (!ni_dbus_struct_get_string(argument, 0, &name)) {
 		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-				"bad value for property %s; missed type", property->name);
+				"missing union switch type in property %s.%s",
+				object->path, property->name);
 		return FALSE;
 	}
-
-	type = ni_linktype_name_to_type(name);
-	switch (type) {
-	case NI_IFTYPE_TEAM:
-	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
-	case NI_IFTYPE_OVS_BRIDGE:
-		break;
-	default:
+	if (!(dict = ni_dbus_struct_get(argument, 1)) || !ni_dbus_variant_is_dict(dict)) {
 		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
-				"bad value for property %s; unsupported type %s", property->name, name);
-		return FALSE;
-	}
-
-	if (!(dict = ni_dbus_struct_get(argument, 1))) {
-		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "missed netdev request port member dict");
-		return FALSE;
-	}
-	if (!ni_dbus_variant_is_dict(dict)) {
-		dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "netdev request port data is not a dict");
-		return FALSE;
-	}
-
-	if (req->port)
-		ni_netdev_port_req_free(req->port);
-	if (!(req->port = ni_netdev_port_req_new(type))) {
-		dbus_set_error(error, DBUS_ERROR_FAILED, "unable to allocate netdev request %s port data", name);
+				"missing union data dict in property %s.%s",
+				object->path, property->name);
 		return FALSE;
 	}
 
-	switch (req->port->type) {
-	case NI_IFTYPE_TEAM: {
-			ni_team_port_config_t *pconf = &req->port->team;
+	ni_netdev_port_config_init(&req->port, ni_linktype_name_to_type(name));
+	switch (req->port.type) {
+	case NI_IFTYPE_BOND:
+		return ni_objectmodel_set_bonding_port_config(req->port.bond, dict, error);
 
-			if (!__ni_objectmodel_set_team_port_config(pconf, dict, error))
-				return FALSE;
-		}
-		break;
+	case NI_IFTYPE_TEAM:
+		return ni_objectmodel_set_team_port_config(req->port.team, dict, error);
 
-	case NI_IFTYPE_OVS_BRIDGE: {
-			ni_ovs_bridge_port_config_t *pconf = &req->port->ovsbr;
+	case NI_IFTYPE_BRIDGE:
+		return ni_objectmodel_set_bridge_port_config(req->port.bridge, dict, error);
 
-			if (!__ni_objectmodel_set_ovs_bridge_port_config(pconf, dict, error))
-				return FALSE;
-		}
-		break;
+	case NI_IFTYPE_OVS_BRIDGE:
+		return ni_objectmodel_set_ovs_bridge_port_config(req->port.ovsbr, dict, error);
 
-	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
 	default:
-		dbus_set_error(error, DBUS_ERROR_FAILED, "unable to initialize netdev request %s port data", name);
-		return FALSE;
+		break;
 	}
-	return TRUE;
+
+	dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+			"unable to set netdev request %s port config from property %s.%s",
+			name, object->path, property->name);
+	return FALSE;
 }
 
 #define NETIF_REQUEST_UINT_PROPERTY(dbus_name, name, rw) \
diff --git a/src/dbus-objects/model.c b/src/dbus-objects/model.c
index 9f5d871fb..694050f61 100644
--- a/src/dbus-objects/model.c
+++ b/src/dbus-objects/model.c
@@ -55,6 +55,18 @@ static ni_dbus_service_t	ni_objectmodel_netif_root_interface;
 ni_dbus_server_t *		__ni_objectmodel_server;
 ni_xs_scope_t *			__ni_objectmodel_schema;
 
+extern unsigned int
+ni_objectmodel_class_registry_count(void)
+{
+	return ni_objectmodel_class_registry.count;
+}
+
+extern unsigned int
+ni_objectmodel_service_registry_count(void)
+{
+	return ni_objectmodel_service_registry.count;
+}
+
 /*
  * Create the dbus service
  */
diff --git a/src/dbus-objects/model.h b/src/dbus-objects/model.h
index c3263fd00..a114d5b3e 100644
--- a/src/dbus-objects/model.h
+++ b/src/dbus-objects/model.h
@@ -188,14 +188,40 @@ extern dbus_bool_t		__ni_objectmodel_get_domain_string(const ni_dbus_variant_t *
 extern dbus_bool_t		__ni_objectmodel_set_resolver_dict(ni_resolver_info_t **,
 						const ni_dbus_variant_t *, DBusError *);
 
-extern dbus_bool_t		__ni_objectmodel_get_team_port_config(const ni_team_port_config_t *,
+extern dbus_bool_t		ni_objectmodel_get_bonding_port_info(const ni_bonding_port_info_t *,
 						ni_dbus_variant_t *, DBusError *);
-extern dbus_bool_t		__ni_objectmodel_set_team_port_config(ni_team_port_config_t *,
+extern dbus_bool_t		ni_objectmodel_set_bonding_port_info(ni_bonding_port_info_t *,
+						const ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_get_bonding_port_config(const ni_bonding_port_config_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_bonding_port_config(ni_bonding_port_config_t *,
+						const ni_dbus_variant_t *, DBusError *);
+
+extern dbus_bool_t		ni_objectmodel_get_team_port_info(const ni_team_port_info_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_team_port_info(ni_team_port_info_t *,
+						const ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_get_team_port_config(const ni_team_port_config_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_team_port_config(ni_team_port_config_t *,
 						const ni_dbus_variant_t *, DBusError *);
 
-extern dbus_bool_t		__ni_objectmodel_get_ovs_bridge_port_config(const ni_ovs_bridge_port_config_t *,
+extern dbus_bool_t		ni_objectmodel_get_bridge_port_info(const ni_bridge_port_info_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_bridge_port_info(ni_bridge_port_info_t *,
+						const ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_get_bridge_port_config(const ni_bridge_port_config_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_bridge_port_config(ni_bridge_port_config_t *,
+						const ni_dbus_variant_t *, DBusError *);
+
+extern dbus_bool_t		ni_objectmodel_get_ovs_bridge_port_info(const ni_ovs_bridge_port_info_t *,
+						ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_set_ovs_bridge_port_info(ni_ovs_bridge_port_info_t *,
+						const ni_dbus_variant_t *, DBusError *);
+extern dbus_bool_t		ni_objectmodel_get_ovs_bridge_port_config(const ni_ovs_bridge_port_config_t *,
 						ni_dbus_variant_t *, DBusError *);
-extern dbus_bool_t		__ni_objectmodel_set_ovs_bridge_port_config(ni_ovs_bridge_port_config_t *,
+extern dbus_bool_t		ni_objectmodel_set_ovs_bridge_port_config(ni_ovs_bridge_port_config_t *,
 						const ni_dbus_variant_t *, DBusError *);
 
 extern dbus_bool_t		ni_objectmodel_bind_netdev_ref_index(const char *, const char *,
diff --git a/src/dbus-objects/ovs.c b/src/dbus-objects/ovs.c
index b64beb903..d9a4fb8e0 100644
--- a/src/dbus-objects/ovs.c
+++ b/src/dbus-objects/ovs.c
@@ -250,135 +250,52 @@ ni_objectmodel_get_ovs_bridge(const ni_dbus_object_t *object, ni_bool_t write_ac
 #endif
 
 /*
- * OVS Bridge port config helpers
+ * OVS Bridge interface info properties <-> dict
  */
-/* extern */ dbus_bool_t
-__ni_objectmodel_get_ovs_bridge_port_config(const ni_ovs_bridge_port_config_t *pconf, ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_ovs_bridge_port_info(const ni_ovs_bridge_port_info_t *info,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
 	(void)error;
 
-	if (!pconf || !dict)
+	if (!info || !dict)
 		return FALSE;
 
-	if (!ni_string_empty(pconf->bridge.name))
-		ni_dbus_dict_add_string(dict, "bridge", pconf->bridge.name);
-
 	return TRUE;
 }
-
-/* extern */ dbus_bool_t
-__ni_objectmodel_set_ovs_bridge_port_config(ni_ovs_bridge_port_config_t *pconf, const ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_ovs_bridge_port_info(ni_ovs_bridge_port_info_t *info,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	const char *string;
+	(void)error;
 
-	if (!pconf || !dict)
+	if (!info || !dict)
 		return FALSE;
 
-	if (ni_dbus_dict_get_string(dict, "bridge", &string) && !ni_string_empty(string))
-		ni_netdev_ref_set_ifname(&pconf->bridge, string);
-
 	return TRUE;
 }
 
 /*
- * Property ports (array)
+ * OVS Bridge (link-request) port configuration <-> dict
  */
-static dbus_bool_t
-__ni_objectmodel_ovs_bridge_port_to_dict(const ni_ovs_bridge_port_t *port, ni_dbus_variant_t *dict, DBusError *error)
-{
-	(void)error;
-
-	if (!port || !dict)
-		return FALSE;
-
-	ni_dbus_dict_add_string(dict, "device", port->device.name);
-
-	return TRUE;
-}
-
-static dbus_bool_t
-__ni_objectmodel_ovs_bridge_get_ports(const ni_dbus_object_t *object, const ni_dbus_property_t *property,
-					ni_dbus_variant_t *result, DBusError *error)
-{
-	ni_dbus_variant_t *dict;
-	const ni_ovs_bridge_t *ovsbr;
-	unsigned int i;
-
-	ni_dbus_dict_array_init(result);
-
-	if (!(ovsbr = __ni_objectmodel_ovs_bridge_read_handle(object, error)))
-		return ni_dbus_error_property_not_present(error, object->path, property->name);
-
-	for (i = 0; i < ovsbr->ports.count; ++i) {
-		const ni_ovs_bridge_port_t *port = ovsbr->ports.data[i];
-
-		if (!port || ni_string_empty(port->device.name))
-			continue;
-
-		if (!(dict = ni_dbus_dict_array_add(result)))
-			return FALSE;
-
-		ni_dbus_variant_init_dict(dict);
-		if (!__ni_objectmodel_ovs_bridge_port_to_dict(port, dict, error))
-			return FALSE;
-	}
-	return TRUE;
-}
-
-static dbus_bool_t
-__ni_objectmodel_ovs_bridge_port_from_dict(ni_ovs_bridge_port_t *port, const ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_ovs_bridge_port_config(const ni_ovs_bridge_port_config_t *conf,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
-	const char *string;
-
 	(void)error;
 
-	if (!port || !dict)
+	if (!conf || !dict)
 		return FALSE;
 
-	if (!ni_dbus_variant_is_dict(dict))
-		return FALSE;
-
-	if (!ni_dbus_dict_get_string(dict, "device", &string) || ni_string_empty(string))
-		return FALSE;
-
-	ni_netdev_ref_set_ifname(&port->device, string);
 	return TRUE;
 }
-
-static dbus_bool_t
-__ni_objectmodel_ovs_bridge_set_ports(ni_dbus_object_t *object, const ni_dbus_property_t *property,
-					const ni_dbus_variant_t *argument, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_ovs_bridge_port_config(ni_ovs_bridge_port_config_t *conf,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
-	const ni_dbus_variant_t *dict;
-	ni_ovs_bridge_t *ovsbr;
-	unsigned  int i;
-
-	if (!ni_dbus_variant_is_dict_array(argument))
-		return FALSE;
-
-	if (!(ovsbr = __ni_objectmodel_ovs_bridge_write_handle(object, error)))
+	if (!conf || !dict)
 		return FALSE;
 
-	/* note: property refresh may call it on a ovsbr that contains ports */
-	ni_ovs_bridge_port_array_destroy(&ovsbr->ports);
-	for (i = 0; i < argument->array.len; ++i) {
-		ni_ovs_bridge_port_t *port;
-
-		port = ni_ovs_bridge_port_new();
-		dict = &argument->variant_array_value[i];
-		if (!__ni_objectmodel_ovs_bridge_port_from_dict(port, dict, error)) {
-			ni_ovs_bridge_port_free(port);
-			return FALSE;
-		}
-		if (ni_ovs_bridge_port_array_find_by_name(&ovsbr->ports, port->device.name)) {
-			ni_ovs_bridge_port_free(port);
-			continue;
-		}
-		if (!ni_ovs_bridge_port_array_append(&ovsbr->ports, port)) {
-			ni_ovs_bridge_port_free(port);
-			return FALSE;
-		}
-	}
 	return TRUE;
 }
 
@@ -442,7 +359,7 @@ __ni_objectmodel_ovs_bridge_set_vlan(ni_dbus_object_t *object, const ni_dbus_pro
 
 static ni_dbus_property_t	ni_objectmodel_ovs_bridge_properties[] = {
 	OVS_BRIDGE_DICT_PROPERTY(vlan, vlan, RO),
-	OVS_BRIDGE_DICT_ARRAY_PROPERTY(ports, ports, RO),
+
 	{ NULL }
 };
 
diff --git a/src/dbus-objects/team.c b/src/dbus-objects/team.c
index 0fb9e931a..7097ebae8 100644
--- a/src/dbus-objects/team.c
+++ b/src/dbus-objects/team.c
@@ -279,14 +279,6 @@ __ni_objectmodel_team_read_handle(const ni_dbus_object_t *object, DBusError *err
 	return ni_objectmodel_get_team(object, FALSE, error);
 }
 
-#if 0
-static void *
-ni_objectmodel_get_team(const ni_dbus_object_t *object, ni_bool_t write_access, DBusError *error)
-{
-	return __ni_objectmodel_team_handle(object, write_access, error);
-}
-#endif
-
 static dbus_bool_t
 __ni_objectmodel_team_get_address(const ni_dbus_object_t *object, const ni_dbus_property_t *property,
 				ni_dbus_variant_t *result, DBusError *error)
@@ -823,160 +815,206 @@ __ni_objectmodel_team_set_link_watch(ni_dbus_object_t *object, const ni_dbus_pro
 /*
  * Helper function for team port config as dict
  */
-/* extern */ dbus_bool_t
-__ni_objectmodel_get_team_port_config(const ni_team_port_config_t *pconf, ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_get_team_port_config(const ni_team_port_config_t *conf,
+		ni_dbus_variant_t *dict, DBusError *error)
 {
 	(void)error;
 
-	if (!pconf || !dict)
+	if (!conf || !dict)
 		return FALSE;
 
-	if (pconf->queue_id != -1U)
-		ni_dbus_dict_add_uint32(dict, "queue_id", pconf->queue_id);
+	if (conf->queue_id != -1U)
+		ni_dbus_dict_add_uint32(dict, "queue_id", conf->queue_id);
 
-	if (pconf->ab.prio)
-		ni_dbus_dict_add_uint32(dict, "prio", pconf->ab.prio);
-	if (pconf->ab.sticky)
-		ni_dbus_dict_add_bool(dict, "sticky", pconf->ab.sticky);
+	if (conf->ab.prio)
+		ni_dbus_dict_add_uint32(dict, "prio", conf->ab.prio);
+	if (conf->ab.sticky)
+		ni_dbus_dict_add_bool(dict, "sticky", conf->ab.sticky);
 
-	if (pconf->lacp.prio)
-		ni_dbus_dict_add_uint32(dict, "lacp_prio", pconf->lacp.prio);
-	if (pconf->lacp.key)
-		ni_dbus_dict_add_uint32(dict, "lacp_key", pconf->lacp.key);
+	if (conf->lacp.prio)
+		ni_dbus_dict_add_uint32(dict, "lacp_prio", conf->lacp.prio);
+	if (conf->lacp.key)
+		ni_dbus_dict_add_uint32(dict, "lacp_key", conf->lacp.key);
 
 	return TRUE;
 }
 
-/* extern */ dbus_bool_t
-__ni_objectmodel_set_team_port_config(ni_team_port_config_t *pconf, const ni_dbus_variant_t *dict, DBusError *error)
+extern dbus_bool_t
+ni_objectmodel_set_team_port_config(ni_team_port_config_t *conf,
+		const ni_dbus_variant_t *dict, DBusError *error)
 {
 	dbus_bool_t b;
 	uint32_t u32;
 
 	(void)error;
 
-	if (!pconf || !dict)
+	if (!conf || !dict)
 		return FALSE;
 
 	if (ni_dbus_dict_get_uint32(dict, "queue_id", &u32))
-		pconf->queue_id = u32;
+		conf->queue_id = u32;
 
 	if (ni_dbus_dict_get_uint32(dict, "prio", &u32))
-		pconf->ab.prio = u32;
+		conf->ab.prio = u32;
 	if (ni_dbus_dict_get_bool(dict, "sticky", &b))
-		pconf->ab.sticky = b;
+		conf->ab.sticky = b;
 
 	if (ni_dbus_dict_get_uint32(dict, "lacp_prio", &u32))
-		pconf->lacp.prio = u32;
+		conf->lacp.prio = u32;
 
 	if (ni_dbus_dict_get_uint32(dict, "lacp_key", &u32))
-		pconf->lacp.key = u32;
+		conf->lacp.key = u32;
 
 	return TRUE;
 }
 
 /*
- * Helper function for team port device as dict
+ * team port interface info properties <-> dict
  */
-static dbus_bool_t
-__ni_objectmodel_team_port_to_dict(const ni_team_port_t *port, ni_dbus_variant_t *dict, DBusError *error)
+static inline dbus_bool_t
+ni_objectmodel_get_team_port_runner_lacp_info(const ni_team_port_runner_lacp_info_t *lacp,
+		ni_dbus_variant_t *dict)
 {
-	(void)error;
+	ni_dbus_dict_add_uint16(dict, "aggregator-id", lacp->aggregator.id);
+	ni_dbus_dict_add_bool(dict,   "selected",  lacp->selected);
+	ni_dbus_dict_add_string(dict, "state",  lacp->state);
+
+	return TRUE;
+}
+
+static inline dbus_bool_t
+ni_objectmodel_get_team_port_runner_info(const ni_team_port_runner_info_t *runner,
+		ni_dbus_variant_t *dict)
+{
+	ni_dbus_variant_t *rdict;
+	ni_dbus_variant_t *ldict;
+	const char *name;
 
-	if (!port || !dict || ni_string_empty(port->device.name))
+	if (!(name = ni_team_runner_type_to_name(runner->type)))
 		return FALSE;
 
-	ni_dbus_dict_add_string(dict, "device", port->device.name);
+	if (!(rdict = ni_dbus_dict_add(dict, "runner")))
+		return FALSE;
 
-	return __ni_objectmodel_get_team_port_config(&port->config, dict, error);
-}
+	ni_dbus_variant_init_struct(rdict);
+	ni_dbus_struct_add_string(rdict, name);
+	if (!(ldict = ni_dbus_struct_add(rdict)))
+		return FALSE;
 
-static dbus_bool_t
-__ni_objectmodel_team_port_from_dict(ni_team_port_t *port, const ni_dbus_variant_t *dict, DBusError *error)
+	ni_dbus_variant_init_dict(ldict);
+	switch (runner->type) {
+	case NI_TEAM_RUNNER_LACP:
+		ni_objectmodel_get_team_port_runner_lacp_info(&runner->lacp, ldict);
+		break;
+	default:
+		/* other runner types don't have any port specific info */
+		break;
+	}
+	return TRUE;
+}
+static inline dbus_bool_t
+ni_objectmodel_get_team_port_link_watches_info(const ni_team_port_link_watches_info_t *watches,
+		ni_dbus_variant_t *dict)
 {
-	const char *string;
-
-	(void)error;
+	ni_dbus_variant_t *wdict;
 
-	if (!port || !dict)
+	if (!(wdict = ni_dbus_dict_add(dict, "link_watches")))
 		return FALSE;
 
-	if (dict->array.len == 0)
-		return TRUE;
+	ni_dbus_variant_init_dict(wdict);
+	ni_dbus_dict_add_bool(wdict, "up", watches->up);
+	return TRUE;
+}
+extern dbus_bool_t
+ni_objectmodel_get_team_port_info(const ni_team_port_info_t *info,
+		ni_dbus_variant_t *dict, DBusError *error)
+{
+	(void)error;
 
-	if (ni_dbus_dict_get_string(dict, "device", &string) && !ni_string_empty(string))
-		ni_netdev_ref_set_ifname(&port->device, string);
-	else
+	if (!info || !dict)
 		return FALSE;
 
-	ni_team_port_config_init(&port->config);
-	return __ni_objectmodel_set_team_port_config(&port->config, dict, error);
+	ni_objectmodel_get_team_port_runner_info(&info->runner, dict);
+	ni_objectmodel_get_team_port_link_watches_info(&info->watches, dict);
+	return TRUE;
 }
 
-/*
- * Property ports (array)
- */
-static dbus_bool_t
-__ni_objectmodel_team_get_ports(const ni_dbus_object_t *object, const ni_dbus_property_t *property,
-				ni_dbus_variant_t *result, DBusError *error)
+static inline dbus_bool_t
+ni_objectmodel_set_team_port_runner_lacp_info(ni_team_port_runner_lacp_info_t *lacp,
+		const ni_dbus_variant_t *dict)
 {
-	ni_dbus_variant_t *dict;
-	const ni_team_t *team;
-	unsigned int i;
+	const char *str;
+	dbus_bool_t bv;
+	uint16_t u16;
 
-	if (!(team = __ni_objectmodel_team_read_handle(object, error)))
-		return FALSE;
+	if (ni_dbus_dict_get_uint16(dict, "aggregator-id", &u16))
+		lacp->aggregator.id = u16;
 
-	ni_dbus_dict_array_init(result);
-	for (i = 0; i < team->ports.count; ++i) {
-		const ni_team_port_t *port = team->ports.data[i];
+	if (ni_dbus_dict_get_bool(dict,   "selected", &bv))
+		lacp->selected = bv;
 
-		if (!(dict = ni_dbus_dict_array_add(result)))
-			return FALSE;
+	if (ni_dbus_dict_get_string(dict, "state", &str))
+		ni_string_dup(&lacp->state, str);
 
-		ni_dbus_variant_init_dict(dict);
-		if (!__ni_objectmodel_team_port_to_dict(port, dict, error))
-			return FALSE;
-	}
 	return TRUE;
 }
-
-static dbus_bool_t
-__ni_objectmodel_team_set_ports(ni_dbus_object_t *object, const ni_dbus_property_t *property,
-				const ni_dbus_variant_t *argument, DBusError *error)
+static inline dbus_bool_t
+ni_objectmodel_set_team_port_runner_info(ni_team_port_runner_info_t *runner,
+		const ni_dbus_variant_t *dict)
 {
-	const ni_dbus_variant_t *dict;
-	ni_team_t *team;
-	unsigned int i;
+	const ni_dbus_variant_t *rdict;
+	const ni_dbus_variant_t *ldict;
+	const char *name;
 
-	if (!(team = __ni_objectmodel_team_write_handle(object, error)))
+	if (!(rdict = ni_dbus_dict_get(dict, "runner")))
 		return FALSE;
 
-	if (!ni_dbus_variant_is_dict_array(argument))
+	if (!ni_dbus_struct_get_string(rdict, 0, &name))
 		return FALSE;
 
-	/* note: property refresh may call it on a team that contains ports */
-	ni_team_port_array_destroy(&team->ports);
-	for (i = 0; i < argument->array.len; ++i) {
-		ni_team_port_t *port;
+	if (!ni_team_runner_name_to_type(name, &runner->type))
+		return FALSE;
 
-		port = ni_team_port_new();
-		dict = &argument->variant_array_value[i];
-		if (!__ni_objectmodel_team_port_from_dict(port, dict, error)) {
-			ni_team_port_free(port);
-			return FALSE;
-		}
-		if (ni_team_port_array_find_by_name(&team->ports, port->device.name)) {
-			ni_team_port_free(port);
-			continue;
-		}
-		if (!ni_team_port_array_append(&team->ports, port)) {
-			ni_team_port_free(port);
-			return FALSE;
-		}
+	switch (runner->type) {
+	case NI_TEAM_RUNNER_LACP:
+		if ((ldict = ni_dbus_struct_get(rdict, 1)))
+			ni_objectmodel_set_team_port_runner_lacp_info(&runner->lacp, ldict);
+		break;
+	default:
+		/* other runner types don't have any port specific info */
+		break;
 	}
 	return TRUE;
 }
+static inline dbus_bool_t
+ni_objectmodel_set_team_port_link_watches_info(ni_team_port_link_watches_info_t *watches,
+		const ni_dbus_variant_t *dict)
+{
+	const ni_dbus_variant_t *wdict;
+	dbus_bool_t bval;
+
+	if (!(wdict = ni_dbus_dict_get(dict, "link_watches")))
+		return FALSE;
+
+	if (ni_dbus_dict_get_bool(wdict, "up", &bval))
+		watches->up = bval;
+
+	return TRUE;
+}
+extern dbus_bool_t
+ni_objectmodel_set_team_port_info(ni_team_port_info_t *info,
+		const ni_dbus_variant_t *dict, DBusError *error)
+{
+	(void)error;
+
+	if (!info || !dict)
+		return FALSE;
+
+	ni_objectmodel_set_team_port_runner_info(&info->runner, dict);
+	ni_objectmodel_set_team_port_link_watches_info(&info->watches, dict);
+	return TRUE;
+}
 
 #define TEAM_HWADDR_PROPERTY(dbus_name, rw) \
 	__NI_DBUS_PROPERTY(DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_BYTE_AS_STRING, \
@@ -998,7 +1036,7 @@ static ni_dbus_property_t	ni_objectmodel_team_properties[] = {
 	TEAM_DICT_PROPERTY(runner, runner, RO),
 	TEAM_UINT_PROPERTY(link_watch_policy, link_watch_policy, RO),
 	TEAM_DICT_PROPERTY(link_watch, link_watch, RO),
-	TEAM_DICT_ARRAY_PROPERTY(ports, ports, RO),
+
 	TEAM_HWADDR_PROPERTY(address, RO),
 
 	{ NULL }
diff --git a/src/dbus-xml.c b/src/dbus-xml.c
index 4c5560972..b85ab4423 100644
--- a/src/dbus-xml.c
+++ b/src/dbus-xml.c
@@ -50,8 +50,8 @@ static dbus_bool_t	ni_dbus_deserialize_xml_array(const ni_dbus_variant_t *, cons
 static dbus_bool_t	ni_dbus_deserialize_xml_dict(const ni_dbus_variant_t *, const ni_xs_type_t *, xml_node_t *);
 static char *		__ni_xs_type_to_dbus_signature(const ni_xs_type_t *, char *, size_t);
 static char *		ni_xs_type_to_dbus_signature(const ni_xs_type_t *);
-static ni_xs_service_t *ni_dbus_xml_get_service_schema(const ni_xs_scope_t *, const char *);
-static ni_xs_type_t *	ni_dbus_xml_get_properties_schema(const ni_xs_scope_t *, const ni_xs_service_t *);
+extern ni_xs_service_t *ni_dbus_xml_get_service_schema(const ni_xs_scope_t *, const char *);
+extern ni_xs_type_t *	ni_dbus_xml_get_properties_schema(const ni_xs_scope_t *, const ni_xs_service_t *);
 
 static ni_tempstate_t *	__ni_dbus_xml_global_temp_state;
 
@@ -1783,7 +1783,7 @@ ni_dbus_define_xml_notations(void)
 /*
  * Helper functions
  */
-static ni_xs_service_t *
+ni_xs_service_t *
 ni_dbus_xml_get_service_schema(const ni_xs_scope_t *scope, const char *interface_name)
 {
 	ni_xs_service_t *service;
@@ -1796,7 +1796,7 @@ ni_dbus_xml_get_service_schema(const ni_xs_scope_t *scope, const char *interface
 	return NULL;
 }
 
-static ni_xs_type_t *
+ni_xs_type_t *
 ni_dbus_xml_get_properties_schema(const ni_xs_scope_t *scope, const ni_xs_service_t *service)
 {
 	scope = ni_xs_scope_lookup_scope(scope, service->name);
@@ -1949,6 +1949,167 @@ ni_dbus_xml_get_method_metadata(const ni_dbus_method_t *method, const char *name
 	return count;
 }
 
+/*
+ * Traverse (system) property tree of the given service
+ * and process with user specified callback ...
+ */
+static dbus_bool_t	ni_dbus_xml_traverse(const ni_xs_type_t *, const char *,
+					const ni_dbus_xml_traverse_path_t *,
+					ni_dbus_xml_traverse_context_t *);
+
+static inline dbus_bool_t
+ni_dbus_xml_traverse_struct(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *path,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	ni_error("%s: not implemented yet", __func__);
+	return FALSE;
+}
+
+static inline dbus_bool_t
+ni_dbus_xml_traverse_union(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *path,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	const ni_xs_union_info_t *info;
+	unsigned int i;
+
+	info = ni_xs_union_info(type);
+	for (i = 0; i < info->children.count; ++i) {
+		const ni_xs_name_type_t *child = &info->children.data[i];
+
+		if (!child->type)
+			continue;
+
+		if (!ni_dbus_xml_traverse(child->type, child->name,
+				path, context))
+			return FALSE;
+	}
+	return TRUE;
+}
+
+static inline dbus_bool_t
+ni_dbus_xml_traverse_array(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *path,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	const ni_xs_array_info_t *info;
+	const char *element_name;
+
+	info = ni_xs_array_info(type);
+	element_name = info->element_name ?: "e";
+	return ni_dbus_xml_traverse(info->element_type, element_name,
+			path, context);
+}
+
+static inline dbus_bool_t
+ni_dbus_xml_traverse_dict(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *path,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	const ni_xs_dict_info_t *info;
+	unsigned int i;
+
+	info = ni_xs_dict_info(type);
+	for (i = 0; i < info->children.count; ++i) {
+		const ni_xs_name_type_t *child = &info->children.data[i];
+
+		if (!child->type) /* same as in validate … */
+			continue;
+
+		if (!ni_dbus_xml_traverse(child->type, child->name,
+				path, context))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static dbus_bool_t
+ni_dbus_xml_traverse(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *prev,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	const ni_dbus_xml_traverse_path_t path = { .prev = prev, .name = name };
+
+	if (!type || !name || !prev || !context || !context->process)
+		return FALSE;
+
+	if (!context->process(type, name, prev, context))
+		return FALSE;
+
+	switch (type->class) {
+	case NI_XS_TYPE_VOID:
+	case NI_XS_TYPE_SCALAR:
+		return TRUE;
+
+	case NI_XS_TYPE_STRUCT:
+		return ni_dbus_xml_traverse_struct(type, name, &path, context);
+
+	case NI_XS_TYPE_UNION:
+		return ni_dbus_xml_traverse_union(type, name, &path, context);
+
+	case NI_XS_TYPE_ARRAY:
+		return ni_dbus_xml_traverse_array(type, name, &path, context);
+
+	case NI_XS_TYPE_DICT:
+		return ni_dbus_xml_traverse_dict(type, name, &path, context);
+
+	default:
+		ni_error("unsupported xml type class %u", type->class);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+const char *
+ni_dbus_xml_traverse_path_print(ni_stringbuf_t *buf,
+		const ni_dbus_xml_traverse_path_t *path)
+{
+	if (!buf || !path || !path->name)
+		return NULL;
+
+	if (path->prev)
+		ni_dbus_xml_traverse_path_print(buf, path->prev);
+
+	ni_stringbuf_puts(buf, "/");
+	ni_stringbuf_puts(buf, path->name);
+
+	return buf->string;
+}
+
+
+dbus_bool_t
+ni_dbus_xml_traverse_service_properties(const ni_dbus_service_t *service,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	ni_dbus_xml_traverse_path_t path;
+	const ni_xs_service_t *xs_service;
+	const ni_xs_scope_t *xs_scope;
+	const ni_xs_type_t *xs_type;
+	const char *interface;
+
+	if (!service || !service->schema || !service->schema->interface)
+		return FALSE;
+
+	if (!(xs_scope = ni_objectmodel_init(NULL)))
+		return FALSE;
+
+	interface = service->schema->interface;
+	if (!(xs_service = ni_dbus_xml_get_service_schema(xs_scope, interface)))
+		return FALSE;
+
+	if (!(xs_type = ni_dbus_xml_get_properties_schema(xs_scope, xs_service)))
+		return FALSE;
+
+	/* xs service name as "root" path */
+	path.prev = NULL;
+	path.type = NULL;
+	path.name = service->schema->name;
+
+	return ni_dbus_xml_traverse(xs_type, "properties", &path, context);
+}
+
 /*
  * A different entry point for ni_xs_type_to_dbus_signature
  */
diff --git a/src/fsm-policy.c b/src/fsm-policy.c
index 86c2a05cf..b19975902 100644
--- a/src/fsm-policy.c
+++ b/src/fsm-policy.c
@@ -955,13 +955,16 @@ ni_fsm_template_input_free(ni_fsm_template_input_t *input)
 static void
 ni_fsm_policy_action_xml_lookup_next(xml_node_t *node, const char *name, xml_node_array_t *res)
 {
-	xml_node_t *child;
+	xml_node_t *child, *ref;
 	unsigned int found = 0;
 
 	for (child = node->children; child; child = child->next) {
 		if (ni_string_eq(child->name, name)) {
-			if (!child->final)
-				xml_node_array_append(res, child);
+			if (!child->final) {
+				ref = xml_node_clone_ref(child);
+				if (ref && !xml_node_array_append(res, ref))
+					xml_node_free(ref);
+			}
 			found++;
 		}
 	}
@@ -975,14 +978,21 @@ ni_fsm_policy_action_xml_lookup(xml_node_t *node, const char *path)
 {
 	xml_node_array_t *cur;
 	char *copy, *name;
+	xml_node_t *ref;
 
 	if (node->final) {
 		ni_error("%s: called with XML element that's marked final", __func__);
 		return NULL;
 	}
 
-	cur = xml_node_array_new();
-	xml_node_array_append(cur, node);
+	if (!(cur = xml_node_array_new()))
+		return NULL;
+
+	ref = xml_node_clone_ref(node);
+	if (ref && !xml_node_array_append(cur, ref)) {
+		xml_node_free(ref);
+		return NULL;
+	}
 
 	copy = strdup(path);
 	for (name = strtok(copy, "/"); name && cur->count; name = strtok(NULL, "/")) {
diff --git a/src/fsm.c b/src/fsm.c
index 603e8b440..c1f132a0c 100644
--- a/src/fsm.c
+++ b/src/fsm.c
@@ -65,11 +65,17 @@ static ni_bool_t		ni_ifworker_del_child_master(xml_node_t *);
 static void			ni_fsm_clear_hierarchy(ni_ifworker_t *);
 
 static void			ni_ifworker_update_client_state_control(ni_ifworker_t *w);
-static inline void		ni_ifworker_update_client_state_config(ni_ifworker_t *w);
+static void			ni_ifworker_update_client_state_config(ni_ifworker_t *w);
 static void			ni_ifworker_update_client_state_scripts(ni_ifworker_t *w);
+static void			ni_ifworker_update_client_state(ni_ifworker_t *w);
+static void			ni_ifworker_reset_client_state(ni_ifworker_t *w);
+
 static void			ni_fsm_events_destroy(ni_fsm_event_t **);
 static void			ni_fsm_process_event(ni_fsm_t *, ni_fsm_event_t *);
 
+static const ni_fsm_transition_t *	ni_fsm_transition_first(void);
+static const ni_fsm_transition_t *	ni_fsm_transition_next(const ni_fsm_transition_t *);
+static const ni_fsm_transition_t *	ni_fsm_transition_find_method(const char *);
 
 ni_fsm_t *
 ni_fsm_new(void)
@@ -1245,167 +1251,6 @@ ni_ifworker_resolve_reference(ni_fsm_t *fsm, xml_node_t *devnode, ni_ifworker_ty
 	return child;
 }
 
-static xml_node_t *
-__ni_generate_default_config(ni_ifworker_t *parent, const char *ifname)
-{
-	xml_node_t *link, *ipv4, *ipv6, *config = NULL;
-	xml_node_t *pconfig, *control, *port;
-
-	pconfig = parent->config.node;
-	control = xml_node_get_child(pconfig, NI_CLIENT_IFCONFIG_CONTROL);
-
-	/* Create <interface> */
-	if (!(config = xml_node_new(NI_CLIENT_IFCONFIG, NULL)))
-		goto error;
-	/* Add <name>$ifname</name> */
-	if (!xml_node_new_element(NI_CLIENT_IFCONFIG_MATCH_NAME, config, ifname))
-		goto error;
-	/* Add <link></link> */
-	if (!(link = xml_node_new(NI_CLIENT_IFCONFIG_LINK, config)))
-		goto error;
-	/* Add <ipv4></ipv4> and <ipv6></ipv6> */
-	if (!(ipv4 = xml_node_new(NI_CLIENT_IFCONFIG_IPV4, config)))
-		goto error;
-	 if (!(ipv6 = xml_node_new(NI_CLIENT_IFCONFIG_IPV6, config)))
-		 goto error;
-
-	switch (parent->iftype) {
-	/* for slaves */
-	case NI_IFTYPE_TEAM:
-	case NI_IFTYPE_BOND:
-		/*
-		 * STARTMODE="hotplug"
-		 * BOOTPROTO="none"
-		 */
-
-		/* Add <control></control> */
-		if (!(control = xml_node_new(NI_CLIENT_IFCONFIG_CONTROL, config)))
-			goto error;
-		/* Add <mode>hotplug</mode> */
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_MODE, control, "hotplug"))
-			goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv4, "false"))
-			 goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv6, "false"))
-			 goto error;
-	break;
-
-	case NI_IFTYPE_OVS_BRIDGE:
-		/*
-		 * STARTMODE="$pstartmode"
-		 * BOOTPROTO="none"
-		 */
-
-		/* Clone <control> */
-		if (!xml_node_is_empty(control) && !xml_node_clone(control, config))
-			goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv4, "false"))
-			 goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv6, "false"))
-			 goto error;
-
-		xml_node_new_element("master", link, ni_linktype_type_to_name(NI_IFTYPE_OVS_SYSTEM));
-		port = xml_node_new("port", link);
-		xml_node_add_attr(port, "type", ni_linktype_type_to_name(parent->iftype));
-		xml_node_new_element("bridge", port, parent->name);
-	break;
-
-	case NI_IFTYPE_BRIDGE:
-		/*
-		 * STARTMODE="$pstartmode"
-		 * BOOTPROTO="none"
-		 */
-
-		/* Clone <control> */
-		if (!xml_node_is_empty(control) && !xml_node_clone(control, config))
-			goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv4, "false"))
-			 goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv6, "false"))
-			 goto error;
-	break;
-
-	/* lowerdevs */
-	case NI_IFTYPE_VLAN:
-	case NI_IFTYPE_MACVLAN:
-	case NI_IFTYPE_MACVTAP:
-		/*
-		 * STARTMODE="$pstartmode"
-		 * BOOTPROTO="static"
-		 */
-
-		/* Clone <control> */
-		if (!xml_node_is_empty(control) && !xml_node_clone(control, config))
-			goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv4, "true"))
-			 goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_ARP_VERIFY, ipv4, "true"))
-			 goto error;
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_IP_ENABLED, ipv6, "true"))
-			 goto error;
-	break;
-
-	default:
-		goto error;
-	}
-
-	return config;
-
-error:
-	ni_error("%s: Unable to generate default XML config (parent type %s)",
-		ifname, ni_linktype_type_to_name(parent->iftype));
-	xml_node_free(config);
-	return NULL;
-}
-
-static void
-ni_ifworker_generate_default_config(ni_ifworker_t *parent, ni_ifworker_t *child)
-{
-	xml_node_t *config;
-
-	if (!parent || !parent->iftype || !parent->config.node ||
-			!child || ni_string_empty(child->name))
-		return;
-
-	if (parent->iftype == NI_IFTYPE_OVS_SYSTEM)
-		return;
-
-	ni_debug_application("%s: generating default config for %s child",
-			parent->name, child->name);
-
-	config = __ni_generate_default_config(parent, child->name);
-	if (config) {
-		ni_ifworker_set_config(child, config, parent->config.meta.origin);
-		xml_node_free(config);
-	}
-}
-
-static ni_bool_t
-ni_ifworker_add_child_master(xml_node_t *config, const char *name)
-{
-	xml_node_t *link, *master;
-
-	if (xml_node_is_empty(config) || ni_string_empty(name))
-		return FALSE;
-
-	if (!(link = xml_node_get_child(config, NI_CLIENT_IFCONFIG_LINK))) {
-		if (!(link = xml_node_new(NI_CLIENT_IFCONFIG_LINK, config)))
-			return FALSE;
-	}
-
-	if (!(master = xml_node_get_child(link, NI_CLIENT_IFCONFIG_MASTER))) {
-		if (!xml_node_new_element(NI_CLIENT_IFCONFIG_MASTER, link, name))
-			return FALSE;
-	}
-	else if (!ni_string_eq(master->cdata, name)) {
-		ni_error("Failed adding <master>%s</master> to <link> -"
-			"there is already one <master>%s</master>", name, master->cdata);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 static ni_bool_t
 ni_ifworker_del_child_master(xml_node_t *config)
 {
@@ -1488,10 +1333,6 @@ ni_ifworker_add_child(ni_ifworker_t *parent, ni_ifworker_t *child, xml_node_t *d
 		}
 	}
 
-	/* Generate missed slave config if needed */
-	if (xml_node_is_empty(child->config.node))
-		ni_ifworker_generate_default_config(parent, child);
-
 	/* Check if this child is already owned by the given parent. */
 	for (i = 0; i < parent->children.count; ++i) {
 		if (parent->children.data[i] == child)
@@ -1613,7 +1454,7 @@ ni_ifworker_update_client_state_control(ni_ifworker_t *w)
 	}
 }
 
-static inline void
+static void
 ni_ifworker_update_client_state_config(ni_ifworker_t *w)
 {
 	if (w && w->object && !w->readonly) {
@@ -1634,6 +1475,59 @@ ni_ifworker_update_client_state_scripts(ni_ifworker_t *w)
 	}
 }
 
+static void
+ni_ifworker_update_client_state(ni_ifworker_t *w)
+{
+	ni_ifworker_update_client_state_control(w);
+	ni_ifworker_update_client_state_config(w);
+	ni_ifworker_update_client_state_scripts(w);
+}
+
+static inline void
+ni_ifworker_reset_client_state_control(ni_ifworker_t *w)
+{
+	ni_client_state_control_t ctrl;
+
+	if (w && w->object && !w->readonly) {
+		ni_client_state_control_init(&ctrl);
+		ni_call_set_client_state_control(w->object, &ctrl);
+		ni_client_state_control_debug(w->name, &ctrl, "remove");
+	}
+}
+
+static inline void
+ni_ifworker_reset_client_state_config(ni_ifworker_t *w)
+{
+	ni_client_state_config_t config;
+
+	if (w && w->object && !w->readonly) {
+		ni_client_state_config_init(&config);
+		ni_call_set_client_state_config(w->object, &config);
+		ni_client_state_config_debug(w->name, &config, "remove");
+	}
+}
+
+static inline void
+ni_ifworker_reset_client_state_scripts(ni_ifworker_t *w)
+{
+	ni_client_state_scripts_t scripts = { .node = NULL };
+
+	if (w && w->object && !w->readonly) {
+		ni_call_set_client_state_scripts(w->object, &scripts);
+		ni_debug_application("%s: %s <%s> %s", w->name,
+				"remove", NI_CLIENT_STATE_XML_NODE,
+				NI_CLIENT_STATE_XML_SCRIPTS_NODE);
+	}
+}
+
+static inline void
+ni_ifworker_reset_client_state(ni_ifworker_t *w)
+{
+	ni_ifworker_reset_client_state_control(w);
+	ni_ifworker_reset_client_state_config(w);
+	ni_ifworker_reset_client_state_scripts(w);
+}
+
 static inline ni_bool_t
 ni_ifworker_empty_config(ni_ifworker_t *w)
 {
@@ -1664,9 +1558,10 @@ ni_ifworker_set_state(ni_ifworker_t *w, unsigned int new_state)
 
 		if ((new_state == NI_FSM_STATE_DEVICE_READY) && w->object && !w->readonly) {
 			ni_call_clear_event_filters(w->object);
-			ni_ifworker_update_client_state_control(w);
-			ni_ifworker_update_client_state_scripts(w);
-			ni_ifworker_update_client_state_config(w);
+			if (prev_state > new_state)
+				ni_ifworker_reset_client_state(w);
+			else
+				ni_ifworker_update_client_state(w);
 		}
 
 		if (w->target_state == new_state)
@@ -1816,15 +1711,11 @@ ni_ifworker_refresh_client_state(ni_ifworker_t *w, ni_client_state_t *cs)
 	if (!w || !cs)
 		return;
 
+	ni_client_state_debug(w->name, cs, "refresh");
+
 	w->control.persistent = cs->control.persistent;
 	w->control.usercontrol = cs->control.usercontrol;
 
-	w->config.meta.uuid = cs->config.uuid;
-	w->config.meta.owner = cs->config.owner;
-	ni_ifworker_set_config_origin(w, cs->config.origin);
-
-	ni_client_state_debug(w->name, cs, "refresh");
-
 	if (!w->state.node)
 		w->state.node = xml_node_new(ni_ifworker_type_to_string(w->type), NULL);
 	if (cs->scripts.node) {
@@ -2315,7 +2206,7 @@ struct ni_ifworker_require_resolver {
 struct ni_ifworker_check_state_req_check {
 	ni_ifworker_check_state_req_check_t *	next;
 	ni_ifworker_t *				worker;
-	ni_ifworker_require_resolver_t		resolver;
+	ni_ifworker_require_resolver_t *	resolver;
 	ni_uint_range_t				state;
 };
 struct ni_ifworker_check_state_req {
@@ -2323,13 +2214,10 @@ struct ni_ifworker_check_state_req {
 	ni_ifworker_check_state_req_check_t *	check;
 };
 
-static void					ni_ifworker_require_resolver_free(ni_fsm_require_t *);
-static inline ni_ifworker_require_resolver_t *	ni_ifworker_require_resolver_cast(ni_fsm_require_t *req)
-{
-	if (!req || req->destroy_fn != ni_ifworker_require_resolver_free)
-		return NULL;
-	return (ni_ifworker_require_resolver_t *)req->user_data;
-}
+static ni_ifworker_require_resolver_t *		ni_ifworker_require_resolver_new(ni_ifworker_type_t type,
+								xml_node_t *node, xml_node_t *meta);
+static void					ni_ifworker_require_resolver_free(ni_ifworker_require_resolver_t *);
+
 static void					ni_ifworker_check_state_req_free(ni_fsm_require_t *);
 static inline ni_ifworker_check_state_req_t *	ni_ifworker_check_state_req_cast(ni_fsm_require_t *req)
 {
@@ -2339,7 +2227,7 @@ static inline ni_ifworker_check_state_req_t *	ni_ifworker_check_state_req_cast(n
 }
 
 static inline ni_ifworker_check_state_req_check_t *
-ni_ifworker_check_state_req_check_new(ni_ifworker_t *cw, ni_ifworker_type_t cwtype,
+ni_ifworker_config_check_state_req_check_new(ni_ifworker_t *cw, ni_ifworker_type_t cwtype,
 					xml_node_t *cwnode, xml_node_t *cwmeta,
 					unsigned int min_state, unsigned int max_state)
 {
@@ -2347,9 +2235,21 @@ ni_ifworker_check_state_req_check_new(ni_ifworker_t *cw, ni_ifworker_type_t cwty
 
 	check = xcalloc(1, sizeof(*check));
 	check->worker = cw ?  ni_ifworker_get(cw) : NULL;
-	check->resolver.cwtype = cwtype;
-	check->resolver.cwnode = cwnode ? xml_node_clone_ref(cwnode) : NULL;
-	check->resolver.cwmeta = cwmeta ? xml_node_clone_ref(cwmeta) : NULL;
+	check->resolver = ni_ifworker_require_resolver_new(cwtype, cwnode, cwmeta);
+	check->state.min = min_state;
+	check->state.max = max_state;
+	return check;
+}
+
+static inline ni_ifworker_check_state_req_check_t *
+ni_ifworker_system_check_state_req_check_new(ni_ifworker_t *cw,
+					unsigned int min_state, unsigned int max_state)
+{
+	ni_ifworker_check_state_req_check_t *check;
+
+	check = xcalloc(1, sizeof(*check));
+	check->worker = cw ?  ni_ifworker_get(cw) : NULL;
+	check->resolver = NULL;
 	check->state.min = min_state;
 	check->state.max = max_state;
 	return check;
@@ -2359,13 +2259,9 @@ static inline void
 ni_ifworker_check_state_req_check_free(ni_ifworker_check_state_req_check_t *check)
 {
 	if (check) {
-		if (check->resolver.cwmeta) {
-			xml_node_free(check->resolver.cwmeta);
-			check->resolver.cwmeta = NULL;
-		}
-		if (check->resolver.cwnode) {
-			xml_node_free(check->resolver.cwnode);
-			check->resolver.cwnode = NULL;
+		if (check->resolver) {
+			ni_ifworker_require_resolver_free(check->resolver);
+			check->resolver = NULL;
 		}
 		if (check->worker) {
 			ni_ifworker_release(check->worker);
@@ -2461,7 +2357,7 @@ ni_ifworker_require_netif_resolve(ni_fsm_t *fsm, ni_ifworker_t *w, ni_ifworker_t
 
 static ni_ifworker_t *
 ni_ifworker_require_resolve(ni_fsm_t *fsm, ni_ifworker_t *w, ni_ifworker_type_t type,
-				xml_node_t *node, xml_node_t *meta)
+		xml_node_t *node, xml_node_t *meta)
 {
 	switch (type) {
 	case NI_IFWORKER_TYPE_NETDEV:
@@ -2475,11 +2371,9 @@ ni_ifworker_require_resolve(ni_fsm_t *fsm, ni_ifworker_t *w, ni_ifworker_type_t
 }
 
 static void
-ni_ifworker_require_resolver_free(ni_fsm_require_t *req)
+ni_ifworker_require_resolver_free(ni_ifworker_require_resolver_t *resolver)
 {
-	ni_ifworker_require_resolver_t *resolver;
-
-	if ((resolver = ni_ifworker_require_resolver_cast(req))) {
+	if (resolver) {
 		if (resolver->cwmeta) {
 			xml_node_free(resolver->cwmeta);
 			resolver->cwmeta = NULL;
@@ -2490,49 +2384,24 @@ ni_ifworker_require_resolver_free(ni_fsm_require_t *req)
 		}
 		free(resolver);
 	}
-	if (req)
-		req->user_data = NULL;
-}
-
-static ni_bool_t
-ni_ifworker_require_resolver_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require_t *req)
-{
-	ni_ifworker_require_resolver_t *resolver;
-
-	if (!(resolver = ni_ifworker_require_resolver_cast(req)))
-		return TRUE;
-
-	ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_APPLICATION, "%s: %s trying to resolve %s-reference",
-			w->name, __func__, resolver->cwtype == NI_IFWORKER_TYPE_NETDEV ? "netif" : "modem");
-
-	if (!ni_ifworker_require_resolve(fsm, w, resolver->cwtype, resolver->cwnode, resolver->cwmeta))
-		return FALSE;
-
-	ni_ifworker_require_resolver_free(req);
-	return TRUE;
 }
 
-static inline void
-ni_ifworker_require_resolver_new(ni_fsm_t *fsm, ni_ifworker_t *w, ni_ifworker_type_t type,
-					xml_node_t *node, xml_node_t *meta)
+static ni_ifworker_require_resolver_t *
+ni_ifworker_require_resolver_new(ni_ifworker_type_t type, xml_node_t *node, xml_node_t *meta)
 {
 	ni_ifworker_require_resolver_t *resolver;
-	ni_fsm_require_t *req;
-
-	ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_APPLICATION, "%s: %s to resolve a %s-reference",
-			w->name, __func__, type == NI_IFWORKER_TYPE_NETDEV ? "netif" : "modem");
 
-	req = ni_fsm_require_new(ni_ifworker_require_resolver_test, ni_ifworker_require_resolver_free);
 	resolver = xcalloc(1, sizeof(*resolver));
-	resolver->cwtype = type;
-	resolver->cwnode = xml_node_clone_ref(node);
-	resolver->cwmeta = xml_node_clone_ref(meta);
-	req->user_data = resolver;
-	ni_fsm_require_list_insert(&w->fsm.check_state_req_list, req);
+	if (resolver) {
+		resolver->cwtype = type;
+		resolver->cwnode = xml_node_clone_ref(node);
+		resolver->cwmeta = xml_node_clone_ref(meta);
+	}
+	return resolver;
 }
 
 static ni_bool_t
-ni_ifworker_check_state_req_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require_t *req)
+ni_ifworker_config_check_state_req_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require_t *req)
 {
 	ni_ifworker_check_state_req_check_t *check;
 	ni_ifworker_check_state_req_t *csr;
@@ -2544,18 +2413,16 @@ ni_ifworker_check_state_req_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require
 		return FALSE;
 
 	for (check = csr->check; check; check = check->next) {
-		if (check->worker)
+		if (check->worker || !check->resolver)
 			continue;
 
-		cw = ni_ifworker_require_resolve(fsm, w, check->resolver.cwtype,
-				check->resolver.cwnode, check->resolver.cwmeta);
+		cw = ni_ifworker_require_resolve(fsm, w, check->resolver->cwtype,
+				check->resolver->cwnode,  check->resolver->cwmeta);
 		if (cw) {
 			/* switch over to the worker */
 			check->worker = ni_ifworker_get(cw);
-			xml_node_free(check->resolver.cwmeta);
-			check->resolver.cwmeta = NULL;
-			xml_node_free(check->resolver.cwnode);
-			check->resolver.cwnode = NULL;
+			ni_ifworker_require_resolver_free(check->resolver);
+			check->resolver = NULL;
 		}
 	}
 
@@ -2632,6 +2499,59 @@ ni_ifworker_check_state_req_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require
 	return all_required_ok && state_reached > 0;
 }
 
+static ni_bool_t
+ni_ifworker_system_check_state_req_test(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_require_t *req)
+{
+	ni_ifworker_check_state_req_check_t *check;
+	ni_ifworker_check_state_req_t *csr;
+	ni_bool_t all_required_ok = TRUE;
+	ni_ifworker_t *cw;
+
+	if (!(csr = ni_ifworker_check_state_req_cast(req)))
+		return FALSE;
+
+	for (check = csr->check; check; check = check->next) {
+		ni_fsm_state_t wait_for_state;
+
+		if (!(cw = check->worker))
+			continue;
+
+		if (!ni_fsm_ifworker_by_ifindex(fsm, cw->ifindex)) {
+			ni_debug_application("%s: worker %s has been deleted",
+					w->name, cw->name);
+			continue;
+		}
+		if (!cw->device) {
+			ni_debug_application("%s: worker %s device has been deleted",
+					w->name, cw->name);
+			continue;
+		}
+		if (cw->failed) {
+			ni_debug_application("%s: worker %s failed", w->name, cw->name);
+			continue;
+		}
+
+		if (cw->fsm.state < check->state.min) {
+			wait_for_state = check->state.min;
+		} else if (cw->fsm.state > check->state.max) {
+			wait_for_state = check->state.max;
+		} else {
+			ni_debug_application("%s: worker %s reached %s state %s..%s",
+					w->name, cw->name, csr->method,
+					ni_ifworker_state_name(check->state.min),
+					ni_ifworker_state_name(check->state.max));
+			continue;
+		}
+
+		ni_debug_application("%s: waiting for worker %s to reach %s state %s",
+				w->name, cw->name, csr->method,
+				ni_ifworker_state_name(wait_for_state));
+		all_required_ok = FALSE;
+	}
+
+	return all_required_ok;
+}
+
 static void
 ni_ifworker_check_state_req_free(ni_fsm_require_t *req)
 {
@@ -2647,34 +2567,34 @@ ni_ifworker_check_state_req_free(ni_fsm_require_t *req)
 }
 
 static ni_fsm_require_t *
-ni_ifworker_check_state_req_new(const char *method, ni_ifworker_t *cw,
-			ni_ifworker_type_t cwtype, xml_node_t *cwnode, xml_node_t *cwmeta,
-			unsigned int min_state, unsigned int max_state)
+ni_ifworker_check_state_req_new(ni_ifworker_check_state_req_check_t *check,
+		const char *method, ni_fsm_require_fn_t *test_fn)
 {
-	ni_ifworker_check_state_req_check_t *check;
 	ni_ifworker_check_state_req_t *csr;
 	ni_fsm_require_t *req;
 
+	if (!check || ni_string_empty(method))
+		return NULL;
+
 	csr = xcalloc(1, sizeof(*csr));
 	ni_string_dup(&csr->method, method);
 
-	check = ni_ifworker_check_state_req_check_new(cw, cwtype, cwnode, cwmeta, min_state, max_state);
 	ni_ifworker_check_state_req_check_list_append(csr, check);
 
-	req = ni_fsm_require_new(ni_ifworker_check_state_req_test, ni_ifworker_check_state_req_free);
+	req = ni_fsm_require_new(test_fn, ni_ifworker_check_state_req_free);
 	req->user_data = csr;
 	return req;
 }
 
 static void
-ni_ifworker_add_check_state_req(ni_ifworker_t *w, const char *method, ni_ifworker_t *cw,
+ni_ifworker_add_config_check_state_req(ni_ifworker_t *w, const char *method, ni_ifworker_t *cw,
 			ni_ifworker_type_t cwtype, xml_node_t *cwnode, xml_node_t *cwmeta,
 			unsigned int min_state, unsigned int max_state)
 {
+	ni_ifworker_check_state_req_check_t *check;
 	ni_fsm_require_t *req;
 
 	for (req = w->fsm.check_state_req_list; req; req = req->next) {
-		ni_ifworker_check_state_req_check_t *check;
 		ni_ifworker_check_state_req_t *csr;
 
 		if (!(csr = ni_ifworker_check_state_req_cast(req)))
@@ -2686,17 +2606,51 @@ ni_ifworker_add_check_state_req(ni_ifworker_t *w, const char *method, ni_ifworke
 		if (cw && ni_ifworker_check_state_req_check_find_worker(csr, cw))
 			continue; /* try to not add worker check twice */
 
-		check = ni_ifworker_check_state_req_check_new(cw, cwtype, cwnode, cwmeta, min_state, max_state);
+		check = ni_ifworker_config_check_state_req_check_new(cw,
+				cwtype, cwnode, cwmeta, min_state, max_state);
+		ni_ifworker_check_state_req_check_list_append(csr, check);
+	}
+
+	check = ni_ifworker_config_check_state_req_check_new(cw,
+			cwtype, cwnode, cwmeta, min_state, max_state);
+	req = ni_ifworker_check_state_req_new(check, method,
+			ni_ifworker_config_check_state_req_test);
+	ni_fsm_require_list_insert(&w->fsm.check_state_req_list, req);
+}
+
+static void
+ni_ifworker_add_system_check_state_req(ni_ifworker_t *w, const char *method,
+		ni_ifworker_t *cw, unsigned int min_state, unsigned int max_state)
+{
+	ni_ifworker_check_state_req_check_t *check;
+	ni_fsm_require_t *req;
+
+	for (req = w->fsm.check_state_req_list; req; req = req->next) {
+		ni_ifworker_check_state_req_t *csr;
+
+		if (!(csr = ni_ifworker_check_state_req_cast(req)))
+			continue;
+
+		if (!ni_string_eq(csr->method, method))
+			continue;
+
+		if (cw && ni_ifworker_check_state_req_check_find_worker(csr, cw))
+			continue; /* try to not add worker check twice */
+
+		check = ni_ifworker_system_check_state_req_check_new(cw,
+				min_state, max_state);
 		ni_ifworker_check_state_req_check_list_append(csr, check);
-		return;
 	}
 
-	req = ni_ifworker_check_state_req_new(method, cw, cwtype, cwnode, cwmeta, min_state, max_state);
+	check = ni_ifworker_system_check_state_req_check_new(cw,
+			min_state, max_state);
+	req = ni_ifworker_check_state_req_new(check, method,
+			ni_ifworker_system_check_state_req_test);
 	ni_fsm_require_list_insert(&w->fsm.check_state_req_list, req);
 }
 
 static void
-__ni_ifworker_get_check_state_reqs_for_method(ni_ifworker_t *w, ni_fsm_transition_t *action)
+ni_ifworker_get_check_state_reqs_for_method(ni_ifworker_t *w, ni_fsm_transition_t *action)
 {
 	ni_fsm_require_t **list, *req;
 
@@ -2719,13 +2673,6 @@ __ni_ifworker_get_check_state_reqs_for_method(ni_ifworker_t *w, ni_fsm_transitio
 				w->name, csr->method, cw ? cw->name : "unresolved",
 				ni_ifworker_state_name(check->state.min),
 				ni_ifworker_state_name(check->state.max));
-
-#if 0			/* really? */
-			if (cw && check->state.min > cw->target_range.min)
-				cw->target_range.min = check->state.min;
-			if (cw && check->state.max < cw->target_range.max)
-				cw->target_range.max = check->state.max;
-#endif
 		}
 
 		/* Move this requirement to the action's req list */
@@ -2907,132 +2854,621 @@ ni_ifworker_type_from_object_path(const char *path, const char **suffix)
 /*
  * Get all interfaces matching some user-specified criteria
  */
-unsigned int
-ni_fsm_get_matching_workers(ni_fsm_t *fsm, ni_ifmatcher_t *match, ni_ifworker_array_t *result)
+static ni_bool_t
+ni_fsm_ifmatch_worker_name(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
 {
-	void (*logit)(const char *, ...) __fmtattr;
-	unsigned int i;
-
-	if (ni_string_eq(match->name, "all")) {
-		match->name = NULL;
-		logit = ni_info;
-	} else {
-		logit = ni_note;
+	/*
+	 * Note: match->name==NULL is matching all worker ("ifup all")
+	 */
+	if (match->name && !ni_string_eq(w->name, match->name)) {
+		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_APPLICATION,
+			"skipping '%s' %s: not requested (here)",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	} else if (match->name) {
+		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_APPLICATION,
+			"checking '%s' %s: requested by name",
+			w->name, ni_ifworker_type_to_string(w->type));
 	}
+	return TRUE;
+}
 
-	for (i = 0; i < fsm->workers.count; ++i) {
-		ni_ifworker_t *w = fsm->workers.data[i];
+static ni_bool_t
+ni_fsm_ifmatch_worker_type(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit, ni_ifworker_type_t type)
+{
+	if (w->type != type) {
+		logit("skipping '%s' %s: type is '%s'",
+			w->name, ni_ifworker_type_to_string(type),
+			ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
 
-		if (w->type != NI_IFWORKER_TYPE_NETDEV)
-			continue;
+static ni_bool_t
+ni_fsm_ifmatch_netif_worker(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	/*
+	 * well, we (still) don't handle modem workers yet and
+	 * most probably never via ifup|ifdown|ifreload <modem>
+	 * but either as reference/dependency or another action
+	 * and probably also different matches.
+	 */
+	return ni_fsm_ifmatch_worker_type(match, w, logit, NI_IFWORKER_TYPE_NETDEV);
+}
 
-		if (w->dead)
-			continue;
+static ni_bool_t
+ni_fsm_ifmatch_dead_worker(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	if (w->dead) {
+		/* seems to be not used any more ... */
+		logit("skipping '%s' %s: marked \"dead\"",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
 
-		if (!match->mode && !match->ignore_startmode) {
-			if (ni_string_eq_nocase(w->control.mode, "off"))
-				continue;
+static ni_bool_t
+ni_fsm_ifmatch_startmode(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	if (match->ifdown)
+		return TRUE;
 
-			if (!match->name && ni_string_eq_nocase(w->control.mode, "manual"))
-				continue;
-		}
+	if (match->mode && !ni_string_eq(w->control.mode, match->mode)) {
+		logit("skipping '%s' %s: control/start mode is \"%s\"",
+			w->name, ni_ifworker_type_to_string(w->type),
+			w->control.mode);
+		return FALSE;
+	}
 
-		if (match->name && !ni_string_eq(match->name, w->name))
-			continue;
+	/* ignore_startmode is a disabled testing hack */
+	if (match->ignore_startmode)
+		return TRUE;
 
-		/* skipping ifworkers without xml configuration */
-		if (match->require_config && !w->config.node) {
-			logit("skipping %s interface: "
-				"no configuration provided", w->name);
-			continue;
+	if (ni_string_eq_nocase(w->control.mode, "off")) {
+		logit("skipping '%s' %s: disabled in the configuration",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	if (!match->name && ni_string_eq_nocase(w->control.mode, "manual")) {
+		logit("skipping '%s' %s: control/start mode is \"%s\"",
+			w->name, ni_ifworker_type_to_string(w->type),
+			w->control.mode);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_config_to_apply(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	/* skipping ifworkers without (xml) configuration to apply */
+	if (match->require_config && !w->config.node) {
+		logit("skipping '%s' %s: no configuration to apply found",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_config_applied(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	ni_client_state_t *cs = w->device ? w->device->client_state : NULL;
+
+	/* skipping ifworkers of interfaces not configured in the past */
+	if (match->require_configured && (!cs || ni_string_empty(cs->config.origin))) {
+		logit("skipping '%s' %s: not configured by wicked yet",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_persistent(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	const ni_client_state_t *cs = w->device ? w->device->client_state : NULL;
+
+	/* skipping ifworkers of interfaces in persistent mode */
+	if (!match->allow_persistent && cs && cs->control.persistent) {
+		logit("skipping '%s' %s: persistent mode is on",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_user_controlled(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	/* skipping ifworkers of interfaces user must not control */
+	if (!w->control.usercontrol && geteuid() != 0) {
+		logit("skipping '%s' %s: user control is not allowed",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_boot_stage(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	if (match->boot_stage && !ni_string_eq(w->control.boot_stage, match->boot_stage)) {
+		logit("skipping '%s' %s: enabled in boot stage '%s'",
+			w->name, ni_ifworker_type_to_string(w->type),
+			w->control.boot_stage);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_skip_origin(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	if (match->skip_origin) {
+		ni_netdev_t *dev = w->device;
+		ni_client_state_t *cs = dev ? dev->client_state : NULL;
+		const char *origin = cs ? cs->config.origin : NULL;
+
+		if (ni_string_startswith(origin, match->skip_origin)) {
+			logit("skipping '%s' %s: config origin is '%s'",
+				w->name, ni_ifworker_type_to_string(w->type),
+				origin);
+			return FALSE;
 		}
-		/* skipping ifworkers of interfaces not configured in the past */
-		if (match->require_configured &&
-		    ni_string_empty(w->config.meta.origin)) {
-			logit("skipping %s interface: "
-				"device is not configured by wicked yet", w->name);
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_activated(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	/*
+	 * Skip active means omit running and succeeded worker
+	 * Hmm... obsolete direct setup without nanny only
+	 */
+	if (match->skip_active && w->kickstarted &&
+	    (ni_ifworker_is_running(w) || ni_ifworker_has_succeeded(w))) {
+		logit("skipping '%s' %s: already active",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_conditions(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	if (!ni_fsm_ifmatch_dead_worker(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_startmode(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_persistent(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_config_to_apply(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_config_applied(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_user_controlled(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_boot_stage(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_skip_origin(match, w, logit))
+		return FALSE;
+	if (!ni_fsm_ifmatch_activated(match, w, logit))
+		return FALSE;
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_reload_config(const ni_ifmatcher_t *match, const ni_ifworker_t *w,
+		ni_log_fn_t *logit)
+{
+	const ni_client_state_t *cs = w->device ? w->device->client_state : NULL;
+
+	if (!match->ifreload)
+		return TRUE;
+
+	if (w->config.node && cs && ni_uuid_equal(&cs->config.uuid, &w->config.meta.uuid)) {
+		logit("skipping '%s' %s: configuration not changed",
+			w->name, ni_ifworker_type_to_string(w->type));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static const char *		ni_ifworker_guard_print(ni_stringbuf_t *,
+						const ni_ifworker_array_t *,
+						const char *);
+
+static ni_bool_t		ni_ifworker_references_ok(
+						const ni_ifworker_array_t *,
+						ni_ifworker_t *);
+
+static ni_bool_t		ni_fsm_ifmatch_pull_up(ni_fsm_t *,
+						ni_ifmatcher_t *,
+						ni_ifworker_array_t *,
+						ni_ifworker_array_t *,
+						ni_log_fn_t *,
+						ni_ifworker_t *,
+						ni_bool_t, ni_bool_t,
+						ni_bool_t, ni_bool_t);
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_up_ports(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *master,
+		ni_bool_t ports, ni_bool_t links)
+{
+	ni_ifworker_t *port;
+	unsigned int i;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		port = fsm->workers.data[i];
+
+		if (port->masterdev != master)
 			continue;
-		}
-		/* skipping ifworkers of interfaces in the persistent mode */
-		if (!match->allow_persistent && w->control.persistent) {
-			logit("skipping %s interface: "
-				"persistent mode is on", w->name);
+
+		/* desired/requested, but not mandatory */
+		ni_fsm_ifmatch_pull_up(fsm, match, result, guard,
+				logit, port, FALSE, TRUE, ports, links);
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_up_links(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *lower,
+		ni_bool_t ports, ni_bool_t links)
+{
+	ni_ifworker_t *link;
+	unsigned int i;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		link = fsm->workers.data[i];
+
+		if (link->lowerdev == lower)
 			continue;
-		}
 
-		/* skipping ifworkers of interfaces user must not control */
-		if (!w->control.usercontrol && geteuid() != 0) {
-			logit("skipping %s interface: "
-				"user control is not allowed", w->name);
+		/* desired/requested, but not mandatory */
+		ni_fsm_ifmatch_pull_up(fsm, match, result, guard,
+				logit, link, TRUE, FALSE, ports, links);
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_up_lower(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *w,
+		ni_bool_t master, ni_bool_t lower,
+		ni_bool_t ports, ni_bool_t links)
+{
+	if (!w->lowerdev)
+		return TRUE; /* we don't have a lower */
+
+	if (!ni_fsm_ifmatch_pull_up(fsm, match, result, guard,
+			logit, w->lowerdev, master, lower, ports, FALSE)) {
+
+		logit("skipping '%s' %s: unable to setup lower %s '%s'",
+			w->name, ni_ifworker_type_to_string(w->type),
+			ni_ifworker_type_to_string(w->lowerdev->type),
+			w->lowerdev->name);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_up_master(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *w,
+		ni_bool_t master, ni_bool_t lower,
+		ni_bool_t ports, ni_bool_t links)
+{
+	if (!w->masterdev)
+		return TRUE; /* we don't have a master */
+
+	if (!ni_fsm_ifmatch_pull_up(fsm, match, result, guard,
+			logit, w->masterdev, master, lower, FALSE, links)) {
+
+		logit("skipping '%s' %s: unable to setup master %s '%s'",
+			w->name, ni_ifworker_type_to_string(w->type),
+			ni_ifworker_type_to_string(w->masterdev->type),
+			w->masterdev->name);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_up(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *w,
+		ni_bool_t master, ni_bool_t lower,
+		ni_bool_t ports, ni_bool_t links)
+{
+	if (ni_ifworker_array_index(result, w) != -1)
+		return TRUE;
+
+	/*
+	 * Check loop guard before pulling references.
+	 */
+	if (!ni_ifworker_references_ok(guard, w))
+		return FALSE;
+
+	ni_ifworker_array_append(guard, w);
+
+	if (lower && !ni_fsm_ifmatch_pull_up_lower(fsm, match, result,
+			guard, logit, w, TRUE, TRUE, ports, FALSE)) {
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	if (master && !ni_fsm_ifmatch_pull_up_master(fsm, match, result,
+			guard, logit, w, TRUE, TRUE, FALSE, links)) {
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	if (ports && !ni_fsm_ifmatch_pull_up_ports(fsm, match,
+			result, guard, logit, w, TRUE, FALSE)) {
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	if (links && !ni_fsm_ifmatch_pull_up_links(fsm, match,
+			result, guard, logit, w, FALSE, TRUE)) {
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	ni_ifworker_array_remove(guard, w);
+
+	if (!ni_fsm_ifmatch_conditions(match, w, logit))
+		return FALSE;
+
+	if (!ni_fsm_ifmatch_reload_config(match, w, logit))
+		return TRUE;
+
+	if (ni_ifworker_array_index(result, w) == -1)
+		ni_ifworker_array_append(result, w);
+
+	return TRUE;
+}
+
+static ni_bool_t		ni_fsm_ifmatch_pull_down(ni_fsm_t *,
+						ni_ifmatcher_t *,
+						ni_ifworker_array_t *,
+						ni_ifworker_array_t *,
+						ni_log_fn_t *,
+						ni_ifworker_t *);
+
+static ni_bool_t
+ni_fsm_ifmatch_pull_down_ports(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *master)
+{
+	ni_netdev_t *device, *dev;
+	ni_ifworker_t *port;
+	unsigned int i;
+	ni_bool_t ret = TRUE;
+
+	if (!(device = master->device))
+		return TRUE;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		port = fsm->workers.data[i];
+		if (!(dev = port ? port->device : NULL))
 			continue;
-		}
 
-		if (match->mode && !ni_string_eq(match->mode, w->control.mode))
+		if (!ni_string_eq(dev->link.masterdev.name, device->name))
 			continue;
 
-		if (match->boot_stage && !ni_string_eq(match->boot_stage, w->control.boot_stage))
+		/*
+		 * ignore / skip ifdown of ports we didn't configured
+		 * (e.g. VM interfaces in a bridge...) and permit to
+		 * shutdown the requested master (bridge) -- on ifdown
+		 * until master delete, the kernel will unenslave them.
+		 */
+		if (!ni_fsm_ifmatch_config_applied(match, port, logit))
 			continue;
 
-		if (match->skip_origin) {
-			ni_netdev_t *dev = w->device;
-			ni_client_state_t *cs = dev ? dev->client_state : NULL;
-			const char *origin = cs ? cs->config.origin : NULL;
+		if (!ni_fsm_ifmatch_pull_down(fsm, match, result,
+				guard, logit, port))
+			ret = FALSE;
+	}
+	return ret;
+}
 
-			if (ni_string_startswith(origin, match->skip_origin)) {
-				continue;
-			}
-		}
+static ni_bool_t
+ni_fsm_ifmatch_pull_down_links(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *lower)
+{
+	ni_netdev_t *device, *dev;
+	ni_ifworker_t *link;
+	unsigned int i;
+	ni_bool_t ret = TRUE;
 
-		/* Skip active means omit running and succeeded worker */
-		if (match->skip_active && w->kickstarted &&
-		    (ni_ifworker_is_running(w) || ni_ifworker_has_succeeded(w))) {
+	if (!(device = lower->device))
+		return TRUE;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		link = fsm->workers.data[i];
+		if (!(dev = link ? link->device : NULL))
 			continue;
-		}
 
-		if (match->name) { /* Check only when particular interface specified */
-			if (!match->ifdown) {
-				if (w->masterdev) { /* Pull in also masterdev */
-					if (ni_ifworker_array_index(result, w->masterdev) < 0)
-						ni_ifworker_array_append(result, w->masterdev);
-				}
-				if (w->lowerdev) {
-					if (ni_ifworker_array_index(result, w->lowerdev) < 0)
-						ni_ifworker_array_append(result, w->lowerdev);
-				}
-			}
-			else {
-				if (w->masterdev) {
-					if (ni_ifworker_array_index(result, w->masterdev) < 0) {
-						logit("skipping %s interface: "
-							"unable to ifdown due to master device dependency to: %s",
-							w->name, w->masterdev->name);
-						continue;
-					}
-				}
+		if (!ni_string_eq(dev->link.lowerdev.name, device->name))
+			continue;
 
-				if (w->lowerdev_for.count > 0) {
-					ni_bool_t missing_dep = FALSE;
-					unsigned int i;
+		/*
+		 * ignore / skip ifdown of links we didn't configured,
+		 * (e.g. manually created vlans on top) and permit to
+		 * shutdown the requested master -- on ifdown until
+		 * delete, kernel will cleanup and delete the links.
+		 */
+		if (!ni_fsm_ifmatch_config_applied(match, link, logit))
+			continue;
+
+		if (!ni_fsm_ifmatch_pull_down(fsm, match, result,
+				guard, logit, link))
+			ret = FALSE;
+	}
+	return ret;
+}
 
-					for (i = 0; i < w->lowerdev_for.count; i++) {
-						ni_ifworker_t *dep = w->lowerdev_for.data[i];
+static ni_bool_t
+ni_fsm_ifmatch_pull_down(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result, ni_ifworker_array_t *guard,
+		ni_log_fn_t *logit, ni_ifworker_t *w)
+{
+	ni_netdev_t *device;
 
-						if (ni_ifworker_array_index(result, dep) < 0) {
-							logit("skipping %s interface: "
-								"unable to ifdown due to lower device dependency to: %s",
-								w->name, dep->name);
-							missing_dep = TRUE;
-						}
-					}
+	if (!(device = w->device))
+		return TRUE;
 
-					if (missing_dep)
-						continue;
-				}
-			}
-		}
+	if (ni_ifworker_array_index(result, w) != -1)
+		return TRUE;
+
+	/*
+	 * Check loop guard before pulling references.
+	 */
+	if (!ni_ifworker_references_ok(guard, w))
+		return FALSE;
+
+	ni_ifworker_array_append(guard, w);
+
+	/*
+	 * There is no reason to trigger master or lower shutdown
+	 * we (as port or link) refer to -- they don't refer us.
+	 *
+	 * But we have to shutdown all ports and links that refer
+	 * to the worker device via their master/lower device ref.
+	 */
+	if (!ni_fsm_ifmatch_pull_down_ports(fsm, match, result,
+			guard, logit, w)) {
+
+		logit("skipping '%s' %s: unable to shutdown ports",
+			device->name, ni_ifworker_type_to_string(w->type));
+
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	if (!ni_fsm_ifmatch_pull_down_links(fsm, match, result,
+			guard, logit, w)) {
+
+		logit("skipping '%s' %s: unable to shutdown links",
+			device->name, ni_ifworker_type_to_string(w->type));
+
+		ni_ifworker_array_remove(guard, w);
+		return FALSE;
+	}
+
+	ni_ifworker_array_remove(guard, w);
+
+	if (!ni_fsm_ifmatch_conditions(match, w, logit))
+		return FALSE;
+
+	if (!ni_fsm_ifmatch_reload_config(match, w, logit))
+		return TRUE;
+
+	if (ni_ifworker_array_index(result, w) == -1)
+		ni_ifworker_array_append(result, w);
+
+	return TRUE;
+}
+
+unsigned int
+ni_fsm_get_matching_workers(ni_fsm_t *fsm, ni_ifmatcher_t *match,
+		ni_ifworker_array_t *result)
+{
+	ni_ifworker_array_t guard = NI_IFWORKER_ARRAY_INIT;
+	ni_log_fn_t *logit;
+	ni_ifworker_t *w;
+	unsigned int i;
+
+	if (!fsm || !match || !result)
+		return 0;
 
-		if (ni_ifworker_array_index(result,w) < 0)
-			ni_ifworker_array_append(result, w);
+	if (ni_string_eq(match->name, "all")) {
+		match->name = NULL;
+		logit = ni_info;
+	} else {
+		logit = ni_note;
+	}
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		w = fsm->workers.data[i];
+
+		if (!ni_fsm_ifmatch_netif_worker(match, w, logit) ||
+		    !ni_fsm_ifmatch_worker_name(match, w, logit))
+			continue;
+
+		if (!match->ifdown) {
+			/*
+			 * Get worker incl. config references for setup:
+			 *
+			 * - [1] this->lower reference: pull underlying requirements
+			 *       (we can't exist or start without them at all;
+			 *        e.g. eth0.42 vlan can't exist without eth0)
+			 * - [2] this->master reference: pull master we're port of
+			 *       (we can't finish link setup without them;
+			 *        e.g. port eth0 needs it's bridge br0)
+			 *
+			 * ports trigger [required]:
+			 *   = Desired to pick ports of the requested master,
+			 *     which is the "controlling top level interface".
+			 *   - [3] this<-ports: pull ports referring us as master
+			 *         (e.g. br42 requests it's port eth1.42)
+			 *     - [==1] port->lower: pull port lower requirements
+			 *             (e.g. eth1.42 needs it's underlying eth0)
+			 *
+			 * links trigger [undesired]:
+			 *   = Links depend on lower, but lower does not need any
+			 *     links on top -- lower (L3) setup is independent.
+			 *   - [4] this<-links: pull links referring us as lower
+			 *         (e.g. eth0 requestst to setup eth0.* too)
+			 *         - [==2] link->master: pull link masters
+			 *                 (eth0.* links require their masters)
+			 */
+			ni_fsm_ifmatch_pull_up(fsm, match, result, &guard,
+					logit, w, TRUE, TRUE, TRUE, FALSE);
+			ni_ifworker_array_destroy(&guard);
+		} else {
+			/*
+			 * Get worker incl. system references for shutdown:
+			 *
+			 * - [1] this<-links: pull links referencing us as lower
+			 *       (e.g. shutdown bond0.42 vlan on bond0 goes down)
+			 * - [2] this<-ports: pull ports referencing us as master
+			 *       (e.g. shutdown eth1,eth1 on bond0 goes down)
+			 */
+			ni_fsm_ifmatch_pull_down(fsm, match, result, &guard,
+					logit, w);
+			ni_ifworker_array_destroy(&guard);
+		}
 	}
 
 	return result->count;
@@ -3157,9 +3593,6 @@ __ni_fsm_pull_in_children(ni_ifworker_t *w, ni_ifworker_array_t *array)
 			continue;
 		}
 
-		if (xml_node_is_empty(child->config.node))
-			ni_ifworker_generate_default_config(w, child);
-
 		if (xml_node_is_empty(child->config.node)) {
 			ni_debug_application("%s: ignoring dependent child %s - no config",
 				w->name, child->name);
@@ -3232,10 +3665,6 @@ ni_fsm_mark_matching_workers(ni_fsm_t *fsm, ni_ifworker_array_t *marked, const n
 
 		w->target_range = marker->target_range;
 
-		/* Clean client-info origin and UUID on ifdown */
-		if (marker->target_range.max < NI_FSM_STATE_DEVICE_SETUP)
-			ni_client_state_config_reset(&w->config.meta);
-
 		if (marker->persistent)
 			ni_ifworker_control_set_persistent(w, TRUE);
 	}
@@ -3419,7 +3848,7 @@ ni_ifworker_get_check_state_req_for_methods(ni_ifworker_t *w)
 	 */
 	for (i = 0; i < at[i].next_state; ++i) {
 		ni_fsm_require_list_destroy(&at[i].require.list);
-		__ni_ifworker_get_check_state_reqs_for_method(w, &at[i]);
+		ni_ifworker_get_check_state_reqs_for_method(w, &at[i]);
 	}
 }
 
@@ -3491,8 +3920,9 @@ ni_ifworker_start(ni_fsm_t *fsm, ni_ifworker_t *w, unsigned long timeout)
 static int
 ni_ifworker_bind_device_factory_api(ni_ifworker_t *w)
 {
+	const unsigned int services_max = ni_objectmodel_service_registry_count();
+	const ni_dbus_service_t *list_services[services_max];
 	const ni_dbus_method_t *method;
-	const ni_dbus_service_t *list_services[128];
 	const char *link_type;
 	unsigned int i, count;
 	int rv;
@@ -3536,7 +3966,7 @@ ni_ifworker_bind_device_factory_api(ni_ifworker_t *w)
 		/* We try to locate the factory service by looping over all services compatible
 		 * with netif-list */
 		netif_list_class = ni_objectmodel_get_class(NI_OBJECTMODEL_NETIF_LIST_CLASS);
-		count = ni_objectmodel_compatible_services_for_class(netif_list_class, list_services, 128);
+		count = ni_objectmodel_compatible_services_for_class(netif_list_class, list_services, services_max);
 	}
 
 	for (i = 0; i < count; ++i) {
@@ -3619,8 +4049,100 @@ static dbus_bool_t	ni_ifworker_netif_resolve_cb(xml_node_t *, const ni_xs_type_t
 static int		ni_ifworker_prompt_cb(xml_node_t *, const ni_xs_type_t *, const xml_node_t *, void *);
 static int		ni_ifworker_prompt_later_cb(xml_node_t *, const ni_xs_type_t *, const xml_node_t *, void *);
 
+static inline int
+ni_ifworker_bind_config_service_method_early(ni_ifworker_t *w, ni_fsm_t *fsm,
+		ni_dbus_xml_validate_context_t *context,
+		const ni_dbus_service_t *service,
+		const ni_dbus_method_t *method,
+		xml_node_t *config)
+{
+	xml_node_t *args = NULL;
+	int rv;
+
+	rv = ni_dbus_xml_map_method_argument(method,
+			0, config, &args, NULL);
+	if (rv != NI_SUCCESS) {
+		ni_ifworker_fail(w, "document error: can't map %s.%s() arguments",
+				service->name, method->name);
+		return -NI_ERROR_DOCUMENT_ERROR;
+	}
+
+	if (args) {
+		ni_debug_wicked_xml(args, NI_LOG_DEBUG3,
+			"%s(%s) mapped %s.%s() arguments",
+			__func__, w->name, service->name, method->name);
+
+		if (!ni_dbus_xml_validate_argument(method, 0, args, context)) {
+			ni_ifworker_fail(w, "failed to validate %s.%s() arguments",
+					service->name, method->name);
+				return -NI_ERROR_DOCUMENT_ERROR;
+		}
+	}
+	return NI_SUCCESS;
+}
+
+static inline int
+ni_ifworker_bind_config_service_actions_early(ni_ifworker_t *w, ni_fsm_t *fsm,
+		ni_dbus_xml_validate_context_t *context,
+		const ni_dbus_service_t *service)
+{
+	const ni_fsm_transition_t *action;
+	const ni_dbus_method_t *method;
+	int rv;
+
+	action = ni_fsm_transition_first();
+	for ( ; action; action = ni_fsm_transition_next(action)) {
+		if (ni_fsm_transition_is_down(action))
+			continue;
+
+		if (ni_string_empty(action->common.method_name))
+			continue;
+
+		if (!(method = ni_dbus_service_get_method(service,
+				action->common.method_name)))
+			continue;
+
+		rv = ni_ifworker_bind_config_service_method_early(
+			w, fsm, context, service, method, w->config.node
+		);
+		if (rv != NI_SUCCESS)
+			return rv;
+	}
+
+	return NI_SUCCESS;
+}
+
+int
+ni_ifworker_bind_config_services_early(ni_ifworker_t *w, ni_fsm_t *fsm,
+		ni_dbus_xml_validate_context_t *context)
+{
+	const unsigned int max = ni_objectmodel_service_registry_count();
+	const ni_dbus_service_t *services[max], *service;
+	const ni_dbus_class_t *class;
+	unsigned int count, i;
+	int rv;
+
+	/* Hmm.. modem should not differ, but...*/
+	if (w->type != NI_IFWORKER_TYPE_NETDEV)
+		return NI_SUCCESS;
+
+	if (!(class = ni_objectmodel_get_class(NI_OBJECTMODEL_NETIF_CLASS)))
+		return NI_SUCCESS;
+
+	count = ni_objectmodel_compatible_services_for_class(class, services, max);
+	for (i = 0; i < count; ++i) {
+		service = services[i];
+		rv = ni_ifworker_bind_config_service_actions_early(
+			w, fsm, context, service
+		);
+		if (rv != NI_SUCCESS)
+			return rv;
+	}
+	return NI_SUCCESS;
+}
+
 int
-ni_ifworker_bind_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_bool_t prompt_now)
+ni_ifworker_bind_config_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_bool_t prompt_now)
 {
 	struct ni_ifworker_xml_validation_user_data user_data = {
 		.fsm = fsm, .worker = w,
@@ -3632,6 +4154,9 @@ ni_ifworker_bind_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_bool_t prompt_now)
 	};
 	int rv = 0;
 
+	if (xml_node_is_empty(w->config.node))
+		return NI_SUCCESS;
+
 	if (prompt_now)
 		context.prompt_callback = ni_ifworker_prompt_cb;
 
@@ -3641,20 +4166,269 @@ ni_ifworker_bind_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_bool_t prompt_now)
 
 	if (w->device_api.factory_method && w->device_api.config) {
 		/* The XML validation code will do a pass over the part of our XML
-		 * document that's used for the deviceNew() call, and call us for
+		 * document that's used for the newDevice() call, and call us for
 		 * every bit of metadata it finds.
 		 * This includes elements marked by <meta:netif-reference/>
 		 * in the schema.
 		 */
-		if (!ni_dbus_xml_validate_argument(w->device_api.factory_method, 1, w->device_api.config, &context))
+		if (!ni_dbus_xml_validate_argument(w->device_api.factory_method,
+				1, w->device_api.config, &context))
 			return -NI_ERROR_DOCUMENT_ERROR;
 	}
 
-	ni_ifworker_get_check_state_req_for_methods(w);
+	/*
+	 * Also other methods as linkUp contain <meta:netif-reference/>
+	 * we have to resolve in order to rebuild the config hierarchy.
+	 */
+	ni_ifworker_bind_config_services_early(w, fsm, &context);
+
 done:
 	return rv;
 }
 
+struct process_system_requires_context {
+	ni_dbus_xml_traverse_context_t  base;
+	ni_fsm_t *			fsm;
+	ni_ifworker_t *			worker;
+	ni_ifworker_t *			linked;
+};
+
+static ni_bool_t
+ni_ifworker_meta_add_system_netif_check_state_require(ni_ifworker_t *w,
+		ni_ifworker_t *cw, xml_node_t *meta)
+{
+	ni_uint_range_t range = { NI_FSM_STATE_NONE, __NI_FSM_STATE_MAX };
+	const ni_fsm_transition_t *action;
+	const char *attr;
+
+	if (!(attr = xml_node_get_attr(meta, "op"))) {
+		ni_error("%s: invalid netif-check-state method=\"%s\"",
+				xml_node_location(meta), attr ?: "");
+		return FALSE;
+	}
+
+	action = ni_fsm_transition_find_method(attr);
+	if (!action || !ni_fsm_transition_is_down(action))
+		return FALSE;
+
+	attr = xml_node_get_attr(meta, "min-state");
+	if (attr && !ni_ifworker_state_from_name(attr, &range.min)) {
+		ni_error("%s: invalid state name min-state=\"%s\"",
+				xml_node_location(meta), attr);
+		return FALSE;
+	}
+
+	attr = xml_node_get_attr(meta, "max-state");
+	if (attr && !ni_ifworker_state_from_name(attr, &range.max)) {
+		ni_error("%s: invalid state name max-state=\"%s\"",
+				xml_node_location(meta), attr);
+		return FALSE;
+	}
+
+	ni_ifworker_add_system_check_state_req(w,
+		action->common.method_name, cw,
+		range.min, range.max);
+
+	return TRUE;
+}
+
+static ni_bool_t
+ni_ifworker_meta_add_system_netif_require(ni_ifworker_t *w,
+		ni_ifworker_t *cw, xml_node_t *meta)
+{
+	const char *check;
+
+	check = xml_node_get_attr(meta, "check");
+	if (ni_string_eq(check, "netif-check-state")) {
+		ni_ifworker_meta_add_system_netif_check_state_require(
+			w, cw, meta
+		);
+	}
+	return TRUE;
+}
+
+static dbus_bool_t
+process_system_requires(const ni_xs_type_t *type, const char *name,
+		const ni_dbus_xml_traverse_path_t *prev,
+		ni_dbus_xml_traverse_context_t *context)
+{
+	const ni_dbus_xml_traverse_path_t path = { .prev = prev, .name = name };
+	ni_stringbuf_t buf = NI_STRINGBUF_INIT_DYNAMIC;
+	struct process_system_requires_context *ctx;
+	ni_bool_t subordinate = FALSE;	/* port -> master */
+	ni_bool_t shared = FALSE;	/* link -> lower  */
+	ni_netdev_ref_t *ref = NULL;
+	xml_node_t *meta;
+	const char *attr;
+
+	/* cast and verify it's the effective context */
+	ctx = (struct process_system_requires_context *)context;
+	if (context->process != process_system_requires)
+		return FALSE; /* something wrong; abort */
+
+	/* make sure we have some metadata node */
+	if (!type->meta || !type->meta->children)
+		return TRUE; /* don't abort processing */
+
+	ni_debug_wicked_xml(type->meta, NI_LOG_DEBUG3, "%s(%s) property meta",
+			__func__, ni_dbus_xml_traverse_path_print(&buf, &path));
+	ni_stringbuf_destroy(&buf);
+
+	/* little paranoia guard before accessing them */
+	if (!ctx->worker->device || !ctx->linked->device)
+		return TRUE; /* don't abort processing */
+
+	if (!(meta = xml_node_get_child(type->meta, "netif-reference")))
+		return TRUE; /* don't abort processing */
+
+	if ((attr = xml_node_get_attr(meta, "subordinate")))
+		subordinate = ni_string_eq(attr, "true");
+	if (!subordinate && (attr = xml_node_get_attr(meta, "shared")))
+		shared = ni_string_eq(attr, "true");
+
+	if (subordinate) {
+		ref = &ctx->linked->device->link.masterdev;
+		if (!ni_string_eq(ctx->worker->device->name, ref->name))
+			return TRUE; /* don't abort processing */
+
+		for (meta = type->meta->children; meta; meta = meta->next) {
+			if (ni_string_eq(meta->name, "require")) {
+				ni_ifworker_meta_add_system_netif_require(
+					ctx->linked, ctx->worker, meta
+				);
+			}
+		}
+	} else if (shared) {
+		ref = &ctx->linked->device->link.lowerdev;
+		if (!ni_string_eq(ctx->worker->device->name, ref->name))
+			return TRUE; /* don't abort processing */
+
+		for (meta = type->meta->children; meta; meta = meta->next) {
+			if (ni_string_eq(meta->name, "require")) {
+				ni_ifworker_meta_add_system_netif_require(
+					ctx->worker, ctx->linked, meta
+				);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+int
+ni_ifworker_bind_system_netif_ref_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_ifworker_t *ref)
+{
+	struct process_system_requires_context context = {
+		.base = {
+			.process = process_system_requires,
+		},
+		.fsm = fsm,
+		.worker = w,
+		.linked = ref,
+	};
+	const ni_dbus_service_t *service;
+	const ni_dbus_class_t *class;
+	int rv = NI_SUCCESS;
+
+	/*
+	 * the object is bound to the netif-<iftype> class/service
+	 * which extends the generic netif service (superclass).
+	 */
+	class = ref->object->class;
+	if (class && (service = ni_objectmodel_service_by_class(class))) {
+		rv = ni_dbus_xml_traverse_service_properties(service,
+				&context.base);
+		if (rv < NI_SUCCESS)
+			return rv;
+	}
+
+	if (ref->device->link.type != NI_IFTYPE_UNKNOWN && class->superclass &&
+	    (service = ni_objectmodel_service_by_class(class->superclass))) {
+		rv = ni_dbus_xml_traverse_service_properties(service,
+				&context.base);
+		if (rv < NI_SUCCESS)
+			return rv;
+	}
+
+	return rv;
+}
+
+int
+ni_ifworker_bind_system_links_early(ni_ifworker_t *w, ni_fsm_t *fsm)
+{
+	const ni_netdev_ref_t *lower;
+	ni_ifworker_t *link;
+	int rv = NI_SUCCESS;
+	unsigned int i;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		link = fsm->workers.data[i];
+
+		if (!link || !link->device || !link->object)
+			continue;
+
+		lower = &link->device->link.lowerdev;
+		if (!ni_string_eq(lower->name, w->name))
+			continue;
+
+		rv = ni_ifworker_bind_system_netif_ref_early(w, fsm, link);
+		if (rv < NI_SUCCESS)
+			return rv;
+	}
+	return rv;
+}
+
+int
+ni_ifworker_bind_system_ports_early(ni_ifworker_t *w, ni_fsm_t *fsm)
+{
+	const ni_netdev_ref_t *master;
+	ni_ifworker_t *port;
+	int rv = NI_SUCCESS;
+	unsigned int i;
+
+	for (i = 0; i < fsm->workers.count; ++i) {
+		port = fsm->workers.data[i];
+
+		if (!port || !port->device || !port->object)
+			continue;
+
+		master = &port->device->link.masterdev;
+		if (!ni_string_eq(master->name, w->name))
+			continue;
+
+		rv = ni_ifworker_bind_system_netif_ref_early(w, fsm, port);
+		if (rv < NI_SUCCESS)
+			return rv;
+	}
+	return rv;
+}
+
+int
+ni_ifworker_bind_system_early(ni_ifworker_t *w, ni_fsm_t *fsm)
+{
+	int rv = NI_SUCCESS;
+
+	ni_ifworker_bind_system_links_early(w, fsm);
+	ni_ifworker_bind_system_ports_early(w, fsm);
+
+	return rv;
+}
+
+int
+ni_ifworker_bind_early(ni_ifworker_t *w, ni_fsm_t *fsm, ni_bool_t prompt_now)
+{
+	int rv;
+
+	ni_ifworker_bind_system_early(w, fsm);
+
+	rv = ni_ifworker_bind_config_early(w, fsm, prompt_now);
+	if (rv < NI_SUCCESS)
+		return rv;
+
+	ni_ifworker_get_check_state_req_for_methods(w);
+	return rv;
+}
+
 /*
  * Build the hierarchy of devices.
  *
@@ -3662,6 +4436,14 @@ done:
  * eth interface needs to come up before any of the VLANs that reference
  * it.
  */
+#define ni_fsm_print_hierarchy(logfn, fmt, args...)		\
+	do {							\
+		if (logfn)					\
+			logfn(fmt, ##args);			\
+		else						\
+			ni_debug_application(fmt, ##args);	\
+	} while (0)
+
 static const char *
 ni_fsm_print_worker_name_info(ni_stringbuf_t *info, const ni_ifworker_t *w)
 {
@@ -3678,8 +4460,20 @@ ni_fsm_print_worker_name_info(ni_stringbuf_t *info, const ni_ifworker_t *w)
 	return info->string;
 }
 
+static const char *
+ni_fsm_config_hierarchy_mark(const ni_ifworker_array_t *marked,
+	const ni_ifworker_t *w)
+{
+	if (w && marked && ni_ifworker_array_index(marked, w) != -1)
+		return "+ ";
+	else
+		return "  ";
+}
+
 static void
-ni_fsm_print_config_device_worker_hierarchy(const ni_ifworker_t *w, unsigned int depth)
+ni_fsm_print_config_device_worker_hierarchy(const ni_fsm_t *fsm,
+		const ni_ifworker_t *w, unsigned int depth,
+		const ni_ifworker_array_t *marked, ni_log_fn_t *logfn)
 {
 	ni_stringbuf_t info = NI_STRINGBUF_INIT_DYNAMIC;
 	unsigned int i;
@@ -3688,35 +4482,52 @@ ni_fsm_print_config_device_worker_hierarchy(const ni_ifworker_t *w, unsigned int
 		return;
 
 	if (!depth) {
-		ni_debug_application("%s", ni_fsm_print_worker_name_info(&info, w));
+		ni_fsm_print_hierarchy(logfn, "%s%s",
+			ni_fsm_config_hierarchy_mark(marked, w),
+			ni_fsm_print_worker_name_info(&info, w));
 		ni_stringbuf_destroy(&info);
 	}
 
 	depth += 4;
-	for (i = 0; i < w->children.count; i++) {
-		ni_ifworker_t *c = w->children.data[i];
+	for (i = 0; i < fsm->workers.count; ++i) {
+		ni_ifworker_t *c = fsm->workers.data[i];
 
 		if (!c || c->type != w->type)
 			continue;
 
-		if (c->masterdev == w)
-			ni_debug_application("%*s %s", depth, "*--", ni_fsm_print_worker_name_info(&info, c));
-		else if (w->lowerdev == c)
-			ni_debug_application("%*s %s", depth, "+--", ni_fsm_print_worker_name_info(&info, c));
-		else /* unknown/other relation ?!? */
-			ni_debug_application("%*s %s", depth, "?--", ni_fsm_print_worker_name_info(&info, c));
+		if (w->lowerdev == c) {
 
-		ni_stringbuf_destroy(&info);
-		ni_fsm_print_config_device_worker_hierarchy(c, depth + 1);
+			ni_fsm_print_hierarchy(logfn, "%s%*s %s",
+					ni_fsm_config_hierarchy_mark(marked, c),
+					depth, "+--",
+					ni_fsm_print_worker_name_info(&info, c));
+			ni_stringbuf_destroy(&info);
+
+			ni_fsm_print_config_device_worker_hierarchy(fsm, c,
+					depth + 1, marked, logfn);
+			continue;
+		}
+		if (c->masterdev == w) {
+
+			ni_fsm_print_hierarchy(logfn, "%s%*s %s",
+					ni_fsm_config_hierarchy_mark(marked, c),
+					depth, "*--",
+					ni_fsm_print_worker_name_info(&info, c));
+			ni_stringbuf_destroy(&info);
+
+			ni_fsm_print_config_device_worker_hierarchy(fsm, c,
+					depth + 1, marked, logfn);
+		}
 	}
 }
 
 void
-ni_fsm_print_config_hierarchy(const ni_fsm_t *fsm)
+ni_fsm_print_config_hierarchy(const ni_fsm_t *fsm,
+		const ni_ifworker_array_t *marked, ni_log_fn_t *logfn)
 {
 	unsigned int i;
 
-	ni_debug_application("Config device hierarchy structure:");
+	ni_fsm_print_hierarchy(logfn, "Config interface hierarchy structure:");
 	if (!fsm)
 		return;
 
@@ -3724,7 +4535,8 @@ ni_fsm_print_config_hierarchy(const ni_fsm_t *fsm)
 		ni_ifworker_t *w = fsm->workers.data[i];
 
 		if (w && w->type == NI_IFWORKER_TYPE_NETDEV && !w->masterdev)
-			ni_fsm_print_config_device_worker_hierarchy(w, 0);
+			ni_fsm_print_config_device_worker_hierarchy(fsm, w,
+					0, marked, logfn);
 	}
 }
 
@@ -3734,12 +4546,25 @@ ni_fsm_print_device_name_info(ni_stringbuf_t *info, const ni_ifworker_t *w)
 	if (!info || !w || !w->device)
 		return NULL;
 
-	ni_stringbuf_printf(info, "%s[%u]", w->device->name, w->device->link.ifindex);
+	ni_stringbuf_printf(info, "%s[%u]",
+			w->device->name, w->device->link.ifindex);
 	return info->string;
 }
 
+static const char *
+ni_fsm_system_hierarchy_mark(const ni_ifworker_array_t *marked,
+		const ni_ifworker_t *w)
+{
+	if (w && marked && ni_ifworker_array_index(marked, w) != -1)
+		return "- ";
+	else
+		return "  ";
+}
+
 static void
-ni_fsm_print_system_device_worker_hierarchy(const ni_fsm_t *fsm, const ni_ifworker_t *w, unsigned int depth)
+ni_fsm_print_system_device_worker_hierarchy(const ni_fsm_t *fsm,
+		const ni_ifworker_t *w, unsigned int depth,
+		const ni_ifworker_array_t *marked, ni_log_fn_t *logfn)
 {
 	ni_stringbuf_t info = NI_STRINGBUF_INIT_DYNAMIC;
 	unsigned int i;
@@ -3748,7 +4573,9 @@ ni_fsm_print_system_device_worker_hierarchy(const ni_fsm_t *fsm, const ni_ifwork
 		return;
 
 	if (!depth) {
-		ni_debug_application("%s", ni_fsm_print_device_name_info(&info, w));
+		ni_fsm_print_hierarchy(logfn, "%s%s",
+				ni_fsm_system_hierarchy_mark(marked, w),
+				ni_fsm_print_device_name_info(&info, w));
 		ni_stringbuf_destroy(&info);
 	}
 
@@ -3760,76 +4587,83 @@ ni_fsm_print_system_device_worker_hierarchy(const ni_fsm_t *fsm, const ni_ifwork
 			continue;
 
 		if (ni_string_eq(w->device->link.lowerdev.name, c->name)) {
-			ni_debug_application("%*s %s", depth, "+--", ni_fsm_print_device_name_info(&info, c));
+
+			ni_fsm_print_hierarchy(logfn, "%s%*s %s",
+					ni_fsm_system_hierarchy_mark(marked, c),
+					depth, "+--",
+					ni_fsm_print_device_name_info(&info, c));
 			ni_stringbuf_destroy(&info);
-			ni_fsm_print_system_device_worker_hierarchy(fsm, c, depth + 1);
-		} else
+
+			ni_fsm_print_system_device_worker_hierarchy(fsm, c,
+					depth + 1, marked, logfn);
+			continue;
+		}
 		if (ni_string_eq(c->device->link.masterdev.name, w->name)) {
-			ni_debug_application("%*s %s", depth, "*--", ni_fsm_print_device_name_info(&info, c));
+
+			ni_fsm_print_hierarchy(logfn, "%s%*s %s",
+					ni_fsm_system_hierarchy_mark(marked, c),
+					depth, "*--",
+					ni_fsm_print_device_name_info(&info, c));
 			ni_stringbuf_destroy(&info);
-			ni_fsm_print_system_device_worker_hierarchy(fsm, c, depth + 1);
+
+			ni_fsm_print_system_device_worker_hierarchy(fsm, c,
+					depth + 1, marked,  logfn);
 		}
 	}
 }
 
 void
-ni_fsm_print_system_hierarchy(const ni_fsm_t *fsm)
+ni_fsm_print_system_hierarchy(const ni_fsm_t *fsm,
+		const ni_ifworker_array_t *marked, ni_log_fn_t *logfn)
 {
 	ni_ifworker_t *w;
 	unsigned int i;
 
-	ni_debug_application("System device hierarchy structure:");
+	if (!fsm)
+		return;
+
+	ni_fsm_print_hierarchy(logfn, "System interface hierarchy structure:");
 	for (i = 0; i < fsm->workers.count; ++i) {
 		w = fsm->workers.data[i];
 
-		if (w && w->type == NI_IFWORKER_TYPE_NETDEV && w->device &&
-		    ni_string_empty(w->device->link.masterdev.name))
-			ni_fsm_print_system_device_worker_hierarchy(fsm, w, 0);
+		if (!w || w->type != NI_IFWORKER_TYPE_NETDEV || !w->device)
+			continue;
+
+		if (!ni_string_empty(w->device->link.masterdev.name))
+			continue;
+
+		ni_fsm_print_system_device_worker_hierarchy(fsm, w, 0,
+				marked, logfn);
 	}
 }
 
 int
 ni_fsm_build_hierarchy(ni_fsm_t *fsm, ni_bool_t destructive)
 {
+	ni_ifworker_t *w;
 	unsigned int i;
+	int rv;
+
+	if (!fsm)
+		return NI_ERROR_INVALID_ARGS;
 
 	ni_fsm_events_block(fsm);
 	for (i = 0; i < fsm->workers.count; ++i) {
-		ni_ifworker_t *w = fsm->workers.data[i];
-		int rv;
-
-		/* A worker without an ifnode is one that we discovered in the
-		 * system, but which we've not been asked to configure. */
-		if (!w->config.node)
-			continue;
+		w = fsm->workers.data[i];
 
 		if ((rv = ni_ifworker_bind_early(w, fsm, FALSE)) < 0) {
 			if (destructive) {
-				if (-NI_ERROR_DOCUMENT_ERROR == rv)
-					ni_debug_application("%s: configuration failed", w->name);
 				ni_fsm_destroy_worker(fsm, w);
 				i--;
 			}
-		}
-	}
-
-	for (i = 0; i < fsm->workers.count; ++i) {
-		ni_ifworker_t *w = fsm->workers.data[i];
-
-		if (w->masterdev) {
-			if (!ni_ifworker_add_child_master(w->config.node, w->masterdev->name))
-				continue;
-			ni_ifworker_generate_uuid(w);
+			continue;
 		}
 	}
 
 	ni_ifworkers_break_loops(fsm);
 	ni_fsm_events_unblock(fsm);
 
-	if (ni_log_facility(NI_TRACE_APPLICATION))
-		ni_fsm_print_config_hierarchy(fsm);
-
-	return 0;
+	return NI_SUCCESS;
 }
 
 dbus_bool_t
@@ -3924,7 +4758,7 @@ ni_ifworker_netif_resolve_cb(xml_node_t *node, const ni_xs_type_t *type, const x
 			}
 
 			requires++;
-			ni_ifworker_add_check_state_req(w, method, cw, cwtype, cw ? NULL : node,
+			ni_ifworker_add_config_check_state_req(w, method, cw, cwtype, cw ? NULL : node,
 							cw ? NULL : cwmeta, min_state, max_state);
 		}
 	}
@@ -4972,6 +5806,111 @@ ni_ifworker_do_wait_device_ready_call(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_tr
 	return ni_ifworker_do_common_call(fsm, w, action);
 }
 
+static unsigned int
+ni_ifworker_get_ports_by_name(ni_fsm_t *fsm, const char *name, ni_ifworker_array_t *ports)
+{
+	unsigned int i, count;
+
+	if (!fsm || ni_string_empty(name))
+		return 0;
+
+	count = ports ? ports->count : 0;
+	for (i = 0;  i < fsm->workers.count; ++i) {
+		ni_ifworker_t *w = fsm->workers.data[i];
+
+		if (!w->masterdev)
+			continue;
+		if (!ni_string_eq(w->masterdev->name, name))
+			continue;
+
+		if (ports)
+			ni_ifworker_array_append(ports, w);
+		else
+			count++;
+	}
+	return ports ? ports->count - count : count;
+}
+
+static ni_tristate_t
+ni_ifworker_link_detection_guess(ni_fsm_t *fsm, ni_ifworker_t *w)
+{
+	ni_netdev_t *dev;
+
+	/*
+	 * At the time of this call, the interface is configured
+	 * by changeDevice (if needed), we're setting it UP (linkUp)
+	 * and when it's not defined via config, we need to decide
+	 * whether to wait for 'link-up' event signaling carrier
+	 * and running state (incl. wpa on wlan) or not, before we
+	 * continue with the L3 setup aka assign IP addresses, ...
+	 *
+	 * For some interfaces, we don't wait for link-up by default.
+	 */
+	if (!fsm || !w)
+		return NI_TRISTATE_DEFAULT;
+
+	switch (w->masterdev ? w->masterdev->iftype : NI_IFTYPE_UNKNOWN) {
+	case NI_IFTYPE_BOND:
+	case NI_IFTYPE_TEAM:
+	case NI_IFTYPE_BRIDGE:
+	case NI_IFTYPE_OVS_BRIDGE:
+	case NI_IFTYPE_OVS_SYSTEM:
+		/*
+		 * Don't wait for L2 ports controlled by master,
+		 * which inherit it's carrier from it's ports
+		 * according to own logic (incl. monitoring)
+		 * and we don't wait for link-up of the ports
+		 * and also don't setup L3 on ports.
+		 */
+		return NI_TRISTATE_DISABLE;
+	default:
+		break;
+	}
+
+	switch (w->iftype) {
+	case NI_IFTYPE_OVS_SYSTEM:
+		/*
+		 * The link state is unused on ovs datapaths.
+		 */
+		return NI_TRISTATE_DISABLE;
+
+	case NI_IFTYPE_TUN:
+	case NI_IFTYPE_TAP:
+		/*
+		 * Tun/Tap interfaces need a daemon started by
+		 * an external service, PRE_UP hook, ...
+		 * It may need to open a connection somewhere
+		 * in the internet, ... don't require link-up.
+		 */
+		return NI_TRISTATE_DISABLE;
+
+	case NI_IFTYPE_BRIDGE:
+		/*
+		 * A bridge inherits the carrier from a port;
+		 * - When the config defines ports, wait until
+		 *   they're added and the bridge gets link-up.
+		 * - When the config does not define any ports:
+		 *   - with STP=off: the bridge will get link-up
+		 *   - with STP=on: will not reach link-up until
+		 *     a port has been added externally, e.g. by
+		 *     adding a VM interface to it at VM start.
+		 *     Don't require the link-up, but continue to
+		 *     set up L3 (e.g. static IPs), so services
+		 *     (e.g. dhcp-server) are started on the
+		 *     bridge to serve the VMs (while it's added).
+		 */
+		dev = w->device; /* changeDevice is applied */
+		if (dev && dev->bridge && dev->bridge->stp &&
+		    ni_ifworker_get_ports_by_name(fsm, w->name, NULL))
+			return NI_TRISTATE_DISABLE;
+		return NI_TRISTATE_DEFAULT;
+
+	default:
+		return NI_TRISTATE_DEFAULT;
+	}
+
+}
+
 static int
 ni_ifworker_link_detection_call(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_transition_t *action)
 {
@@ -4979,8 +5918,11 @@ ni_ifworker_link_detection_call(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_transiti
 
 	ret = ni_ifworker_do_common_call(fsm, w, action);
 
-	if (!ni_tristate_is_set(w->control.link_required) && w->device)
-		w->control.link_required = ni_netdev_guess_link_required(w->device);
+	if (!ni_tristate_is_set(w->control.link_required)) {
+		w->control.link_required = ni_ifworker_link_detection_guess(fsm, w);
+		if (ni_tristate_is_set(w->control.link_required))
+			ni_ifworker_update_client_state_control(w);
+	}
 
 	if (ret >= 0 && w->fsm.wait_for) {
 		if (w->control.link_timeout != NI_IFWORKER_INFINITE_SECONDS) {
@@ -5222,6 +6164,38 @@ static ni_fsm_transition_t	ni_iftransitions[] = {
 	{ .from_state = NI_FSM_STATE_NONE, .next_state = NI_FSM_STATE_NONE, .call_func = NULL }
 };
 
+static const ni_fsm_transition_t *
+ni_fsm_transition_first(void)
+{
+	const ni_fsm_transition_t *action = ni_iftransitions;
+
+	if (action && action->call_func)
+		return action;
+	return NULL;
+}
+
+static const ni_fsm_transition_t *
+ni_fsm_transition_next(const ni_fsm_transition_t *action)
+{
+	if (!action || !action->call_func)
+		return NULL;
+	if (++action && action->call_func)
+		return action;
+	return NULL;
+}
+
+static const ni_fsm_transition_t *
+ni_fsm_transition_find_method(const char *method)
+{
+	const ni_fsm_transition_t *action = ni_fsm_transition_first();
+
+	for ( ; action; action = ni_fsm_transition_next(action)) {
+		if (ni_string_eq(method, action->common.method_name))
+			return action;
+	}
+	return NULL;
+}
+
 static int
 ni_fsm_schedule_init(ni_fsm_t *fsm, ni_ifworker_t *w, unsigned int from_state, unsigned int target_state)
 {
@@ -5666,9 +6640,6 @@ ni_fsm_process_worker_event(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_event_t *ev)
 	switch (event_type) {
 	case NI_EVENT_DEVICE_READY:
 	case NI_EVENT_DEVICE_UP:
-		/* Rebuild hierarchy in case a new device shows up */
-		ni_fsm_build_hierarchy(fsm, FALSE);
-
 		/* Handle devices which were not present on ifup */
 		if(w->pending) {
 			w->pending = FALSE;
@@ -5755,15 +6726,8 @@ ni_fsm_process_worker_event(ni_fsm_t *fsm, ni_ifworker_t *w, ni_fsm_event_t *ev)
 
 	ni_ifworker_advance_state(w, event_type);
 
-	if (event_type == NI_EVENT_DEVICE_DELETE) {
-		if (ni_ifworker_is_factory_device(w))
-			ni_ifworker_device_delete(w);
-		else
-			ni_fsm_destroy_worker(fsm, w);
-
-		/* Rebuild hierarchy since one device is gone */
-		ni_fsm_build_hierarchy(fsm, FALSE);
-	}
+	if (event_type == NI_EVENT_DEVICE_DELETE)
+		ni_ifworker_device_delete(w);
 
 done: ;
 }
diff --git a/src/ifconfig.c b/src/ifconfig.c
index 4020c55df..a8e4e5134 100644
--- a/src/ifconfig.c
+++ b/src/ifconfig.c
@@ -126,8 +126,9 @@ static int	__ni_rtnl_link_down(const ni_netdev_t *);
 static int	__ni_rtnl_link_unenslave(const ni_netdev_t *);
 static int	__ni_rtnl_link_delete(const ni_netdev_t *);
 
-static int	__ni_rtnl_link_add_port_up(const ni_netdev_t *, const char *, unsigned int);
-static int	__ni_rtnl_link_bond_enslave(const ni_netdev_t *, const char *, unsigned int);
+static int	ni_rtnl_link_port_to_bridge(const ni_netdev_t *, const char *, unsigned int);
+static int	ni_rtnl_link_port_to_bond(const ni_netdev_t *, const char *, unsigned int);
+static int	ni_rtnl_link_port_config_change(const ni_netdev_t *, const ni_netdev_port_config_t *);
 
 static int	__ni_rtnl_send_deladdr(ni_netdev_t *, const ni_address_t *);
 static int	__ni_rtnl_send_newaddr(ni_netdev_t *, const ni_address_t *, int);
@@ -168,16 +169,19 @@ ni_system_bond_enslave_update(ni_netconfig_t *nc, ni_netdev_t *dev, ni_netdev_t
 }
 
 static int
-ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *master, ni_netdev_t *dev,
+ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *dev, ni_netdev_t *master,
 				const ni_netdev_req_t *req)
 {
+	ni_netdev_t *current = NULL;
 	int ret = -1;
 
 	if (!master || !dev || !req)
 		return -1;
 
 	if (dev->link.masterdev.index) {
-		if (dev->link.masterdev.index != master->link.ifindex) {
+		current = ni_netdev_resolve_master(dev, nc);
+
+		if (current && current->link.ifindex != master->link.ifindex) {
 			ni_error("%s: already enslaved into %s[#%u]", dev->name,
 					dev->link.masterdev.name,
 					dev->link.masterdev.index);
@@ -207,11 +211,19 @@ ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *master, ni_netdev_t
 		if (dev->link.masterdev.index)
 			return 0;
 
-		ret = __ni_rtnl_link_bond_enslave(dev, master->name,
-						master->link.ifindex);
+		ret = ni_rtnl_link_port_to_bond(dev, master->name, master->link.ifindex);
 		if (ret == 0) {
 			ni_netdev_ref_set(&dev->link.masterdev,
 					master->name, master->link.ifindex);
+			if (master->link.type == req->port.type) {
+				ni_rtnl_link_port_config_change(dev, &req->port);
+			} else if (req->port.type != NI_IFTYPE_UNKNOWN) {
+				const char *ptype = ni_linktype_type_to_name(req->port.type);
+				const char *mtype = ni_linktype_type_to_name(master->link.type);
+
+				ni_warn("%s: omitting %s port config not matching %s %s",
+					dev->name, ptype, mtype, master->name);
+			}
 			ni_system_bond_enslave_update(nc, dev, master);
 		}
 		break;
@@ -219,12 +231,12 @@ ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *master, ni_netdev_t
 		if (!ni_config_teamd_enabled())
 			return -1;
 
-		if (req->port && master->link.type != req->port->type) {
+		if (master->link.type != req->port.type) {
 			ni_error("%s: port configuration type mismatch", dev->name);
 			return -1;
 		}
-		ret = ni_teamd_port_enslave(master, dev, req->port ? &req->port->team : NULL);
 
+		ret = ni_teamd_port_enslave(master, dev, req->port.team);
 		if (ret == 0) {
 			ni_netdev_ref_set(&dev->link.masterdev,
 					master->name, master->link.ifindex);
@@ -235,30 +247,47 @@ ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *master, ni_netdev_t
 		ni_teamd_discover(master);
 		break;
 	case NI_IFTYPE_BRIDGE:
-		ret = __ni_rtnl_link_add_port_up(dev, master->name,
-						master->link.ifindex);
+		ret = ni_rtnl_link_port_to_bridge(dev, master->name, master->link.ifindex);
 		if (ret == 0) {
 			ni_netdev_ref_set(&dev->link.masterdev,
 					master->name, master->link.ifindex);
-
+			if (master->link.type == req->port.type) {
+				ni_rtnl_link_port_config_change(dev, &req->port);
+			} else if (req->port.type != NI_IFTYPE_UNKNOWN) {
+				const char *ptype = ni_linktype_type_to_name(req->port.type);
+				const char *mtype = ni_linktype_type_to_name(master->link.type);
+
+				ni_warn("%s: omitting %s port config not matching %s %s",
+					dev->name, ptype, mtype, master->name);
+			}
 			if (dev->link.type == NI_IFTYPE_WIRELESS)
 				ni_wireless_connect(dev);
 		}
 		break;
-	case NI_IFTYPE_OVS_SYSTEM:
-		if (!req->port || req->port->type != NI_IFTYPE_OVS_BRIDGE) {
+	case NI_IFTYPE_OVS_BRIDGE:
+		if (req->port.type != NI_IFTYPE_OVS_BRIDGE) {
 			ni_error("%s: port configuration type mismatch", dev->name);
 			return -1;
 		}
-		if (ni_string_empty(req->port->ovsbr.bridge.name)) {
-			ni_error("%s: missing ovs-bridge name in port config", dev->name);
-			return -1;
-		}
 
-		ret = ni_ovs_vsctl_bridge_port_add(dev->name, &req->port->ovsbr, TRUE);
+		ret = ni_ovs_vsctl_bridge_port_add(master->name, dev->name,
+				req->port.ovsbr, TRUE);
 		if (ret == 0)  {
-			ni_netdev_ref_set(&dev->link.masterdev,
-					master->name, master->link.ifindex);
+			ni_netdev_t *datapath;
+			const char *kind;
+
+			/* Mock the expected "ovs trampoline" relation... */
+			if ((datapath = ni_netdev_by_iftype(nc, NI_IFTYPE_OVS_SYSTEM))) {
+				ni_netdev_ref_set(&dev->link.masterdev, datapath->name,
+						datapath->link.ifindex);
+			}
+			ni_netdev_port_info_destroy(&dev->link.port);
+			kind = ni_linkinfo_type_to_kind(NI_IFTYPE_OVS_UNSPEC);
+			ni_netdev_port_info_init(&dev->link.port, kind);
+			if (ni_netdev_port_info_data_init(&dev->link.port, NI_IFTYPE_OVS_BRIDGE)) {
+				ni_netdev_ref_set(&dev->link.port.ovsbr->bridge, master->name,
+						master->link.ifindex);
+			}
 		} else {
 			/* TODO */
 			ret = -1;
@@ -272,72 +301,37 @@ ni_system_interface_enslave(ni_netconfig_t *nc, ni_netdev_t *master, ni_netdev_t
 }
 
 static int
-ni_system_interface_unenslave(ni_netconfig_t *nc, ni_netdev_t *dev)
+ni_system_interface_unenslave(ni_netconfig_t *nc, ni_netdev_t *dev, ni_netdev_t *master)
 {
-	ni_netdev_t *master;
-	char *bridge = NULL;
-
-	if (!nc || !dev || !dev->link.masterdev.index)
-		return -1;
-
-	master = ni_netdev_by_index(nc, dev->link.masterdev.index);
-	if (!master) {
-		ni_error("%s: unable to find master interface %s#%u",
-				dev->name, dev->link.masterdev.name,
-				dev->link.masterdev.index);
+	if (!nc || !dev || !dev->link.masterdev.index || !master)
 		return -1;
-	}
 
 	switch (master->link.type) {
-		case NI_IFTYPE_OVS_SYSTEM:
-			if (ni_ovs_vsctl_bridge_port_to_bridge(dev->name, &bridge)) {
-				ni_error("%s: unable to find ovs bridge interface to unenslave",
-						dev->name);
-				return -1;
-			}
-			if (ni_ovs_vsctl_bridge_port_del(bridge, dev->name)) {
-				ni_error("%s: unable to unenslave port from ovs-bridge %s",
-						dev->name, bridge);
-				return -1;
-			}
-			break;
-
-		case NI_IFTYPE_TEAM:
-			if (ni_teamd_port_unenslave(master, dev)) {
-				ni_error("%s: unable to unenslave port from team %s",
-						dev->name, master->name);
-				return -1;
-			}
-			break;
-
-		case NI_IFTYPE_BOND:
-			if (__ni_rtnl_link_unenslave(dev) != NLE_SUCCESS)
-				return -1;
-
-			if (dev->bonding) {
-				ni_bonding_slave_array_t *slaves = &dev->bonding->slaves;
-				unsigned int i;
-
-				i = ni_bonding_slave_array_index_by_ifindex(slaves, dev->link.ifindex);
-				ni_bonding_slave_array_delete(slaves, i);
-			}
-			break;
-
-		case NI_IFTYPE_BRIDGE:
-			if (__ni_rtnl_link_unenslave(dev) != NLE_SUCCESS)
-				return -1;
+	case NI_IFTYPE_OVS_BRIDGE:
+		if (ni_ovs_vsctl_bridge_port_del(master->name, dev->name)) {
+			ni_error("%s: unable to unenslave port from ovs-bridge %s",
+					dev->name, master->name);
+			return -1;
+		}
+		break;
 
-			if (dev->bridge) {
-				ni_bridge_del_port_ifindex(dev->bridge, dev->link.ifindex);
-			}
-			break;
+	case NI_IFTYPE_TEAM:
+		if (ni_teamd_port_unenslave(master, dev)) {
+			ni_error("%s: unable to unenslave port from team %s",
+					dev->name, master->name);
+			return -1;
+		}
+		break;
 
-		default:
-			if (__ni_rtnl_link_unenslave(dev) != NLE_SUCCESS)
-				return -1;
-			break;
+	case NI_IFTYPE_BOND:
+	case NI_IFTYPE_BRIDGE:
+	default:
+		if (__ni_rtnl_link_unenslave(dev) != NLE_SUCCESS)
+			return -1;
+		break;
 	}
 
+	/* refresh link info only to cleanup references */
 	__ni_device_refresh_link_info(nc, &dev->link);
 
 	return 0;
@@ -347,66 +341,66 @@ int
 ni_system_interface_link_change(ni_netdev_t *dev, const ni_netdev_req_t *req)
 {
 	ni_netconfig_t *nc = ni_global_state_handle(0);
-	ni_netdev_t *master = NULL, *current;
+	ni_netdev_t *master = NULL, *current = NULL;
 	unsigned int ifflags;
 	int ret;
 
 	if (dev == NULL)
 		return -NI_ERROR_INVALID_ARGS;
 
+	/*
+	 * refresh to ensure, our netdev references are up-to-date
+	 */
 	__ni_system_refresh_interface(nc, dev);
 	ni_debug_ifconfig("%s(%s)", __func__, dev->name);
 
-	/* FIXME: perform sanity check on configuration data,
-	 *        cleanup the tweaks we've added
+	/*
+	 * resolve current master (if any) and refresh it's details
 	 */
-	ifflags = req ? req->ifflags : 0;
-	if (ifflags & (NI_IFF_DEVICE_UP|NI_IFF_LINK_UP|NI_IFF_NETWORK_UP)) {
+	if (dev->link.masterdev.index) {
+		current = ni_netdev_resolve_master(dev, nc);
+		if (current)
+			__ni_system_refresh_interface(nc, current);
+		else
+			__ni_system_refresh_interfaces(nc);
 
-		/* make sure, our reference devices are up-to-date */
-		if (dev->link.masterdev.index) {
-			current = ni_netdev_by_index(nc, dev->link.masterdev.index);
-			if (current)
-				__ni_system_refresh_interface(nc, current);
-			else
-				__ni_system_refresh_interfaces(nc);
+		if (!current && !(current = ni_netdev_resolve_master(dev, nc))) {
+			ni_error("%s: unable to resolve current master '%s[%u]",
+					dev->name, dev->link.masterdev.name,
+					dev->link.masterdev.index);
+			return -1;
 		}
+	}
+
+	ifflags = req ? req->ifflags : 0;
+	if (ifflags & (NI_IFF_DEVICE_UP|NI_IFF_LINK_UP|NI_IFF_NETWORK_UP)) {
 
 		if (req && !ni_string_empty(req->master.name)) {
-			master = ni_netdev_by_name(nc, req->master.name);
+			master = ni_netdev_ref_resolve(&req->master, nc);
 			if (!master) {
 				ni_error("%s: unable to find requested master interface '%s'",
 						dev->name, req->master.name);
 				return -1;
 			}
 
-			if (dev->link.masterdev.index &&
-			    dev->link.masterdev.index != master->link.ifindex) {
+			if (current && current->link.ifindex != master->link.ifindex) {
 				ni_note("%s: unenslave from current master %s[#%u]",
-						dev->name, dev->link.masterdev.name,
-						dev->link.masterdev.index);
+						dev->name, current->name,
+						current->link.ifindex);
 
-				if ((ret = ni_system_interface_unenslave(nc, dev)))
+				if ((ret = ni_system_interface_unenslave(nc, dev, current)))
 					return ret;
-
-				current = ni_netdev_by_index(nc, dev->link.masterdev.index);
-				if (current) {
-					__ni_system_refresh_interface(nc, current);
-					__ni_system_refresh_interface(nc, dev);
-				} else {
-					__ni_system_refresh_interfaces(nc);
-				}
 			}
 
-			if ((ret = ni_system_interface_enslave(nc, master, dev, req)))
+			if ((ret = ni_system_interface_enslave(nc, dev, master, req)))
 				return ret;
-		} else
-		if (dev->link.masterdev.index) {
+
+		} else if (current) {
 			ni_note("%s: unenslave from current master %s[#%u]",
-					dev->name, dev->link.masterdev.name,
-					dev->link.masterdev.index);
+					dev->name, current->name,
+					current->link.ifindex);
 
-			if ((ret = ni_system_interface_unenslave(nc, dev)))
+			if ((ret = ni_system_interface_unenslave(nc, dev, current)))
 				return ret;
 		}
 
@@ -436,12 +430,14 @@ ni_system_interface_link_change(ni_netdev_t *dev, const ni_netdev_req_t *req)
 		ni_system_lldp_down(dev);
 
 		/* Now take down the link for real */
-		ni_debug_ifconfig("shutting down interface %s", dev->name);
-		if (dev->link.masterdev.index && ni_system_interface_unenslave(nc, dev)) {
-			ni_error("unable to shut down and unenslave %s", dev->name);
+		ni_debug_ifconfig("%s: shutting down interface", dev->name);
+		if (current && ni_system_interface_unenslave(nc, dev, current)) {
+			ni_error("%s: unable to shut down and unenslave from %s",
+					dev->name, current->name);
 			return -1;
-		} else if (__ni_rtnl_link_down(dev)) {
-			ni_error("unable to shut down interface %s", dev->name);
+		}
+		if (__ni_rtnl_link_down(dev)) {
+			ni_error("%s: unable to shut down interface", dev->name);
 			return -1;
 		}
 
@@ -1438,14 +1434,9 @@ ni_system_bridge_setup(ni_netconfig_t *nc, ni_netdev_t *dev, const ni_bridge_t *
 int
 ni_system_bridge_shutdown(ni_netdev_t *dev)
 {
-	ni_bridge_t *bridge;
-
-	if (!dev || !(bridge = dev->bridge))
+	if (!dev || !dev->bridge)
 		return -1;
 
-	/* port unenslave themself on linkDown() */
-	ni_bridge_ports_destroy(bridge);
-
 	return 0;
 }
 
@@ -1462,123 +1453,6 @@ ni_system_bridge_delete(ni_netconfig_t *nc, ni_netdev_t *dev)
 	return 0;
 }
 
-/*
- * Add a port to a bridge interface
- */
-int
-ni_system_bridge_add_port(ni_netconfig_t *nc, ni_netdev_t *brdev, const ni_bridge_port_t *port)
-{
-	ni_bridge_t *bridge = ni_netdev_get_bridge(brdev);
-	ni_netdev_t *pif = NULL;
-	ni_bridge_port_t *new_port;
-	int rv;
-
-	if (port->ifindex)
-		pif = ni_netdev_by_index(nc, port->ifindex);
-	else if (port->ifname)
-		pif = ni_netdev_by_name(nc, port->ifname);
-
-	if (pif == NULL) {
-		ni_error("%s: cannot add port - interface not known",
-			brdev->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-	if (!ni_netdev_device_is_ready(pif)) {
-		ni_error("%s: cannot add port %s - interface is not ready",
-			brdev->name, pif->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-	if (pif->link.ifindex == 0) {
-		ni_error("%s: cannot add port - %s has no ifindex?!",
-			brdev->name, pif->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-
-	/* This should be a more elaborate check - neither device can be an ancestor of
-	 * the other, or we create a loop.
-	 */
-	if (pif == brdev) {
-		ni_error("%s: cannot add interface as its own bridge port",
-			brdev->name);
-		return -NI_ERROR_DEVICE_BAD_HIERARCHY;
-	}
-
-	if (pif->link.masterdev.index &&
-			pif->link.masterdev.index != brdev->link.ifindex) {
-		ni_error("%s: interface %s already has a master",
-			brdev->name, pif->name);
-		return -NI_ERROR_DEVICE_BAD_HIERARCHY;
-	}
-
-	if (pif->link.masterdev.index &&
-			pif->link.masterdev.index == brdev->link.ifindex) {
-		/* already a port of this bridge -- make sure the device is up */
-		if (!ni_netdev_device_is_up(pif) && __ni_rtnl_link_up(pif, NULL) < 0) {
-			ni_warn("%s: Cannot set up link on bridge port %s",
-				brdev->name, pif->name);
-		}
-		return 0; /* part of the bridge and hopefully up now */
-	}
-
-	if (__ni_rtnl_link_add_port_up(pif, brdev->name, brdev->link.ifindex) == 0) {
-		ni_netdev_ref_set(&pif->link.masterdev, brdev->name,
-				brdev->link.ifindex);
-		return 0;
-	}
-
-	if (!ni_netdev_device_is_up(pif) && __ni_rtnl_link_up(pif, NULL) < 0) {
-		ni_warn("%s: Cannot set up link on bridge port %s",
-			brdev->name, pif->name);
-	}
-
-	if ((rv = __ni_brioctl_add_port(brdev->name, pif->link.ifindex)) < 0) {
-		ni_error("%s: cannot add port %s: %s", brdev->name, pif->name,
-				ni_strerror(rv));
-		return rv;
-	}
-
-	/* Now configure the newly added port */
-	if ((rv = ni_sysfs_bridge_port_update_config(pif->name, port)) < 0) {
-		ni_error("%s: failed to configure port %s: %s",
-			brdev->name, pif->name, ni_strerror(rv));
-		return rv;
-	}
-
-	/* when this fails, next event will update/add it... */
-	new_port = ni_bridge_port_clone(port);
-	new_port->ifindex = pif->link.ifindex;
-	if (!ni_string_eq(new_port->ifname, pif->name))
-		ni_string_dup(&new_port->ifname, pif->name);
-
-	if (!ni_bridge_add_port(bridge, new_port))
-		ni_bridge_port_free(new_port);
-	return 0;
-}
-
-/*
- * Remove a port from a bridge interface
- * ni_system_bridge_remove_port
- */
-int
-ni_system_bridge_remove_port(ni_netdev_t *dev, unsigned int port_ifindex)
-{
-	ni_bridge_t *bridge = ni_netdev_get_bridge(dev);
-	int rv;
-
-	if (port_ifindex == 0) {
-		ni_error("%s: cannot remove port: bad ifindex", dev->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-
-	if ((rv = __ni_brioctl_del_port(dev->name, port_ifindex)) < 0) {
-		ni_error("%s: cannot remove port: %s", dev->name, ni_strerror(rv));
-		return rv;
-	}
-
-	ni_bridge_del_port_ifindex(bridge, port_ifindex);
-	return 0;
-}
-
 /*
  * OVS bridge system operations
  */
@@ -1740,6 +1614,7 @@ ni_system_bond_create(ni_netconfig_t *nc, const ni_netdev_t *cfg, ni_netdev_t **
 static int
 ni_system_bond_setup_sysfs(ni_netconfig_t *nc, ni_netdev_t *dev, const ni_netdev_t *cfg)
 {
+	unsigned int ports;
 	ni_bonding_t *bond;
 	ni_bool_t is_up;
 
@@ -1750,13 +1625,14 @@ ni_system_bond_setup_sysfs(ni_netconfig_t *nc, ni_netdev_t *dev, const ni_netdev
 
 	is_up = ni_netdev_device_is_up(dev);
 	ni_bonding_parse_sysfs_attrs(dev->name, bond);
+	ports = ni_netdev_get_ports(dev, NULL, nc);
 
 	ni_debug_ifconfig("%s: configuring bonding device (stage 0.%u.%u)",
-			dev->name, is_up, bond->slaves.count);
+			dev->name, is_up, ports);
 	if (ni_bonding_write_sysfs_attrs(dev->name, cfg->bonding, bond,
-					is_up, bond->slaves.count > 0) < 0) {
+					is_up, ports > 0) < 0) {
 		ni_error("%s: cannot configure bonding device (stage 0.%u.%u)",
-			dev->name, is_up, bond->slaves.count);
+			dev->name, is_up, ports);
 		return -1;
 	}
 	ni_bonding_parse_sysfs_attrs(dev->name, bond);
@@ -1849,97 +1725,6 @@ ni_system_bond_delete(ni_netconfig_t *nc, ni_netdev_t *dev)
 	return 0;
 }
 
-/*
- * Add slave to a bond
- */
-int
-ni_system_bond_add_slave(ni_netconfig_t *nc, ni_netdev_t *dev, unsigned int slave_idx)
-{
-	ni_string_array_t slave_names = NI_STRING_ARRAY_INIT;
-	ni_bonding_t *bond = dev->bonding;
-	ni_netdev_t *slave_dev;
-
-	if (bond == NULL) {
-		ni_error("%s: %s is not a bonding device", __func__, dev->name);
-		return -NI_ERROR_DEVICE_NOT_COMPATIBLE;
-	}
-
-	slave_dev = ni_netdev_by_index(nc, slave_idx);
-	if (slave_dev == NULL) {
-		ni_error("%s: trying to add unknown interface to bond %s",
-			__func__, dev->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-
-	if (!ni_netdev_device_is_ready(slave_dev)) {
-		ni_error("%s: trying to enslave %s, which is not ready",
-			dev->name, slave_dev->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-
-	if (ni_netdev_network_is_up(slave_dev)) {
-		ni_error("%s: trying to enslave %s, which is in use",
-			dev->name, slave_dev->name);
-		return -NI_ERROR_DEVICE_NOT_DOWN;
-	}
-
-	/* Silently ignore duplicate slave attach */
-	if (ni_bonding_has_slave(bond, slave_dev->name))
-		return 0;
-
-	ni_bonding_get_slave_names(bond, &slave_names);
-	ni_string_array_append(&slave_names, slave_dev->name);
-	if (ni_sysfs_bonding_set_list_attr(dev->name, "slaves", &slave_names) < 0) {
-		ni_string_array_destroy(&slave_names);
-		ni_error("%s: could not update list of slaves", dev->name);
-		return -NI_ERROR_PERMISSION_DENIED;
-	}
-	ni_string_array_destroy(&slave_names);
-	ni_bonding_add_slave(bond, slave_dev->name);
-
-	return 0;
-}
-
-/*
- * Remove a slave from a bond
- */
-int
-ni_system_bond_remove_slave(ni_netconfig_t *nc, ni_netdev_t *dev, unsigned int slave_idx)
-{
-	ni_string_array_t slave_names = NI_STRING_ARRAY_INIT;
-	ni_bonding_t *bond = dev->bonding;
-	ni_netdev_t *slave_dev;
-	unsigned int idx;
-
-	if (bond == NULL) {
-		ni_error("%s: %s is not a bonding device", __func__, dev->name);
-		return -NI_ERROR_DEVICE_NOT_COMPATIBLE;
-	}
-
-	slave_dev = ni_netdev_by_index(nc, slave_idx);
-	if (slave_dev == NULL) {
-		ni_error("%s: trying to add unknown interface to bond %s", __func__, dev->name);
-		return -NI_ERROR_DEVICE_NOT_KNOWN;
-	}
-
-	/* Silently ignore duplicate slave removal */
-	if ((idx = ni_bonding_slave_array_index_by_ifindex(&bond->slaves, slave_idx)) == -1U) {
-		if ((idx = ni_bonding_slave_array_index_by_ifname( &bond->slaves, slave_dev->name)) == -1U)
-			return 0;
-	}
-
-	ni_bonding_slave_array_delete(&bond->slaves, idx);
-	ni_bonding_get_slave_names(bond, &slave_names);
-	if (ni_sysfs_bonding_set_list_attr(dev->name, "slaves", &slave_names) < 0) {
-		ni_string_array_destroy(&slave_names);
-		ni_error("%s: could not update list of slaves", dev->name);
-		return -NI_ERROR_PERMISSION_DENIED;
-	}
-	ni_string_array_destroy(&slave_names);
-
-	return 0;
-}
-
 /*
  * Create a team device
  */
@@ -2773,10 +2558,10 @@ __ni_rtnl_link_put_bond(ni_netconfig_t *nc,	struct nl_msg *msg, ni_netdev_t *dev
 		unsigned int	attr;		/* netlink attribute constant      */
 		unsigned int	modes;		/* bonding mode constraint mask    */
 		int		bstate;		/* <0: bond down, >0: bond up      */
-		int		slaves;		/* <0: no slaves, >0: wants slaves */
+		int		ports;		/* <0: no ports, >0: wants ports   */
 	} ni_bonding_opt_table[] = {
 #define map_opt(opt,args...)	{ .name = #opt, .attr = opt, ##args }
-		map_opt(IFLA_BOND_MODE,			.bstate = -1, .slaves = -1),
+		map_opt(IFLA_BOND_MODE,			.bstate = -1, .ports = -1),
 		map_opt(IFLA_BOND_MIIMON),
 		map_opt(IFLA_BOND_UPDELAY),
 		map_opt(IFLA_BOND_DOWNDELAY),
@@ -2809,11 +2594,11 @@ __ni_rtnl_link_put_bond(ni_netconfig_t *nc,	struct nl_msg *msg, ni_netdev_t *dev
 		map_opt(IFLA_BOND_ACTIVE_SLAVE,		.modes  = NI_BIT(NI_BOND_MODE_ACTIVE_BACKUP)
 								| NI_BIT(NI_BOND_MODE_BALANCE_ALB)
 								| NI_BIT(NI_BOND_MODE_BALANCE_TLB),
-							.bstate = 1, .slaves = 1),
+							.bstate = 1, .ports = 1),
 		map_opt(IFLA_BOND_TLB_DYNAMIC_LB,	.modes  = NI_BIT(NI_BOND_MODE_BALANCE_TLB),
 							.bstate = -1),
 		map_opt(IFLA_BOND_MIN_LINKS),
-		map_opt(IFLA_BOND_FAIL_OVER_MAC,	.slaves = -1),
+		map_opt(IFLA_BOND_FAIL_OVER_MAC,	.ports = -1),
 		map_opt(IFLA_BOND_ALL_SLAVES_ACTIVE),
 		map_opt(IFLA_BOND_PACKETS_PER_SLAVE,	.modes  = NI_BIT(NI_BOND_MODE_BALANCE_RR)),
 		map_opt(IFLA_BOND_RESEND_IGMP),
@@ -2829,6 +2614,7 @@ __ni_rtnl_link_put_bond(ni_netconfig_t *nc,	struct nl_msg *msg, ni_netdev_t *dev
 	ni_bonding_t *		bond;
 	ni_bool_t		is_up;
 	unsigned int		count;
+	unsigned int		ports;
 	int			ret;
 
 	if (!cfg || !cfg->bonding || ni_string_empty(ifname))
@@ -2847,6 +2633,7 @@ __ni_rtnl_link_put_bond(ni_netconfig_t *nc,	struct nl_msg *msg, ni_netdev_t *dev
 		goto nla_put_failure;
 
 	is_up = ni_netdev_device_is_up(dev);
+	ports = ni_netdev_get_ports(dev, NULL, nc);
 	for (count = 0, opt = ni_bonding_opt_table; opt->name; opt++) {
 		if (opt->modes && !(opt->modes & NI_BIT(bond->mode))) {
 			ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
@@ -2864,15 +2651,15 @@ __ni_rtnl_link_put_bond(ni_netconfig_t *nc,	struct nl_msg *msg, ni_netdev_t *dev
 					"%s: skip attr %s -- bond is down", ifname, opt->name);
 			continue;
 		}
-		if (opt->slaves < 0 && bond->slaves.count) {
+		if (opt->ports < 0 && ports) {
 			ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
-					"%s: skip attr %s -- bond has %u slave%s", ifname, opt->name,
-					bond->slaves.count, bond->slaves.count > 1 ? "s" : "");
+					"%s: skip attr %s -- bond has %u port%s", ifname, opt->name,
+					ports, ports > 1 ? "s" : "");
 			continue;
 		}
-		if (opt->slaves > 0 && !bond->slaves.count) {
+		if (opt->ports > 0 && !ports) {
 			ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
-					"%s: skip attr %s -- bond has no slaves yet", ifname, opt->name);
+					"%s: skip attr %s -- bond has no ports yet", ifname, opt->name);
 			continue;
 		}
 
@@ -2900,7 +2687,6 @@ nla_put_failure:
 	return -1;
 }
 
-
 static int
 __ni_rtnl_link_put_vlan(struct nl_msg *msg,	const ni_netdev_t *cfg)
 {
@@ -3980,11 +3766,171 @@ nl_talk_failed:
 	goto cleanup;
 }
 
+static void
+ni_rtnl_link_put_uattr_debug(const char *ifname, const char *attr,
+		unsigned int skip, unsigned int val)
+{
+	unsigned int level = NI_LOG_DEBUG1 + (skip == val);
+
+	if (skip == val) {
+		ni_debug_verbose(level, NI_TRACE_IFCONFIG,
+				"%s: skip attr %s", ifname, attr);
+	} else {
+		ni_debug_verbose(level, NI_TRACE_IFCONFIG,
+				"%s: set  attr %s=%u", ifname, attr, val);
+	}
+}
+
+static inline int
+ni_rtnl_link_put_bond_port(const ni_netdev_t *port,
+		struct nl_msg *msg, const ni_bonding_port_config_t *conf)
+{
+	const char *attr;
+	int skipped = 1;
+
+	if (!msg || !conf)
+		return -1;
+
+	attr = "IFLA_BOND_SLAVE_QUEUE_ID";
+	if (conf->queue_id != -1U) {
+		NLA_PUT_U16(msg, IFLA_BOND_SLAVE_QUEUE_ID, conf->queue_id);
+		skipped = 0;
+	}
+	ni_rtnl_link_put_uattr_debug(port->name, attr, -1U, conf->queue_id);
+
+	return skipped;
+
+nla_put_failure:
+	ni_error("%s: unable format bonding port attr %s", port->name, attr);
+	return -1;
+}
+
+static inline int
+ni_rtnl_link_put_bridge_port(const ni_netdev_t *port,
+		struct nl_msg *msg, const ni_bridge_port_config_t *conf)
+{
+	const char *attr;
+	int skipped = 1;
+
+	if (!msg || !conf)
+		return -1;
+
+	attr = "IFLA_BRPORT_PRIORITY";
+	if (conf->priority != NI_BRIDGE_VALUE_NOT_SET) {
+		NLA_PUT_U16(msg, IFLA_BRPORT_PRIORITY, conf->priority);
+		skipped = 0;
+	}
+	ni_rtnl_link_put_uattr_debug(port->name, attr,
+			NI_BRIDGE_VALUE_NOT_SET, conf->priority);
+
+	attr = "IFLA_BRPORT_COST";
+	if (conf->path_cost != NI_BRIDGE_VALUE_NOT_SET) {
+		NLA_PUT_U32(msg, IFLA_BRPORT_COST, conf->path_cost);
+		skipped = 0;
+	}
+	ni_rtnl_link_put_uattr_debug(port->name, attr,
+			NI_BRIDGE_VALUE_NOT_SET, conf->path_cost);
+
+	return skipped;
+
+nla_put_failure:
+	ni_error("%s: unable format bridge port attr %s", port->name, attr);
+	return -1;
+}
+
+static int
+ni_rtnl_link_port_config_change(const ni_netdev_t *port,
+		const ni_netdev_port_config_t *conf)
+{
+	struct ifinfomsg ifi;
+	struct nl_msg *msg;
+	struct nlattr *info;
+	struct nlattr *data;
+	const char *kind;
+	int ret = -1;
+
+	if (!port || !conf || !(kind = ni_linkinfo_type_to_kind(conf->type)))
+		return -1;
+
+	switch (conf->type) {
+	case NI_IFTYPE_BOND:
+	case NI_IFTYPE_BRIDGE:
+		ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
+				"%s(%s[#%u], kind: %s)", __func__,
+				port->name, port->link.ifindex, kind);
+		break;
+	default:
+		ni_warn("%s(%s[#%u], kind: %s): unsupported port config",
+				__func__, port->name, port->link.ifindex, kind);
+		return 1;
+	}
+
+	memset(&ifi, 0, sizeof(ifi));
+	ifi.ifi_family = AF_UNSPEC;
+	ifi.ifi_index = port->link.ifindex;
+
+	msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST);
+	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	if (nla_put_string(msg, IFLA_INFO_SLAVE_KIND, kind))
+		goto nla_put_failure;
+
+	if (!(data = nla_nest_start(msg, IFLA_INFO_SLAVE_DATA)))
+		goto nla_put_failure;
+
+	switch (conf->type) {
+	case NI_IFTYPE_BOND:
+		ret = ni_rtnl_link_put_bond_port(port, msg, conf->bond);
+		if (ret < 0)
+			goto nla_put_failure;
+		if (ret > 0)
+			goto cleanup;
+		break;
+
+	case NI_IFTYPE_BRIDGE:
+		ret = ni_rtnl_link_put_bridge_port(port, msg, conf->bridge);
+		if (ret < 0)
+			goto nla_put_failure;
+		if (ret > 0)
+			goto cleanup;
+		break;
+
+	default:
+		ret = 1;
+		goto cleanup;
+	}
+
+	nla_nest_end(msg, data);
+	nla_nest_end(msg, info);
+
+	if ((ret = ni_nl_talk(msg, NULL)) == NLE_SUCCESS) {
+		ni_debug_ifconfig("%s: successfully changed %s port config",
+				port->name, kind);
+		ret =  0;
+	} else {
+		ni_error("%s: failed to change %s port config: %s",
+				port->name, kind, nl_geterror(ret));
+		ret = -1;
+	}
+	goto cleanup;
+
+nla_put_failure:
+	ni_error("%s: failed to encode netlink message to change %s port config",
+			port->name, kind);
+cleanup:
+	nlmsg_free(msg);
+	return ret;
+}
+
 /*
  * Bring up an interface and enslave (bridge port) to master
  */
 int
-__ni_rtnl_link_add_port_up(const ni_netdev_t *port, const char *mname, unsigned int mindex)
+ni_rtnl_link_port_to_bridge(const ni_netdev_t *port, const char *mname, unsigned int mindex)
 {
 	struct ifinfomsg ifi;
 	struct nl_msg *msg;
@@ -3992,6 +3938,10 @@ __ni_rtnl_link_add_port_up(const ni_netdev_t *port, const char *mname, unsigned
 	if (!port || !mname || !mindex)
 		return -1;
 
+	ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
+			"%s(%s[#%u], bridge: %s[#%u])", __func__,
+			port->name, port->link.ifindex, mname, mindex);
+
 	memset(&ifi, 0, sizeof(ifi));
 	ifi.ifi_family = AF_UNSPEC;
 	ifi.ifi_index = port->link.ifindex;
@@ -4004,16 +3954,16 @@ __ni_rtnl_link_add_port_up(const ni_netdev_t *port, const char *mname, unsigned
 
 	NLA_PUT_U32(msg, IFLA_MASTER, mindex);
 
-	if (ni_nl_talk(msg, NULL))
+	if (ni_nl_talk(msg, NULL) != NLE_SUCCESS)
 		goto failed;
 
-	ni_debug_ifconfig("successfully added port %s into master %s",
+	ni_debug_ifconfig("%s: successfully linked port to bridge %s",
 			port->name, mname);
 	nlmsg_free(msg);
 	return 0;
 
 nla_put_failure:
-	ni_error("failed to encode netlink message to add port %s into %s",
+	ni_error("%s: failed to encode netlink message to link port to bridge %s",
 			port->name, mname);
 failed:
 	nlmsg_free(msg);
@@ -4024,17 +3974,21 @@ failed:
  * Enslave into a bond master
  */
 int
-__ni_rtnl_link_bond_enslave(const ni_netdev_t *slave, const char *mname, unsigned int mindex)
+ni_rtnl_link_port_to_bond(const ni_netdev_t *port, const char *mname, unsigned int mindex)
 {
 	struct ifinfomsg ifi;
 	struct nl_msg *msg;
 
-	if (!slave || !mname || !mindex)
+	if (!port || !mname || !mindex)
 		return -1;
 
+	ni_debug_verbose(NI_LOG_DEBUG1, NI_TRACE_IFCONFIG,
+			"%s(%s[#%u], bond: %s[#%u])", __func__,
+			port->name, port->link.ifindex, mname, mindex);
+
 	memset(&ifi, 0, sizeof(ifi));
 	ifi.ifi_family = AF_UNSPEC;
-	ifi.ifi_index = slave->link.ifindex;
+	ifi.ifi_index = port->link.ifindex;
 
 	msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST);
 	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
@@ -4042,15 +3996,17 @@ __ni_rtnl_link_bond_enslave(const ni_netdev_t *slave, const char *mname, unsigne
 
 	NLA_PUT_U32(msg, IFLA_MASTER, mindex);
 
-	if (ni_nl_talk(msg, NULL) < 0)
+	if (ni_nl_talk(msg, NULL) != NLE_SUCCESS)
 		goto failed;
 
-	ni_debug_ifconfig("successfully enslaved %s into master %s", slave->name, mname);
+	ni_debug_ifconfig("%s: successfully linked port to bond %s",
+			port->name, mname);
 	nlmsg_free(msg);
 	return 0;
 
 nla_put_failure:
-	ni_error("failed to encode netlink message to enslave %s into %s", slave->name, mname);
+	ni_error("%s: failed to encode netlink message to link port to bond %s",
+			port->name, mname);
 failed:
 	nlmsg_free(msg);
 	return -1;
diff --git a/src/iflist.c b/src/iflist.c
index d2ba63100..62dc4e05f 100644
--- a/src/iflist.c
+++ b/src/iflist.c
@@ -418,16 +418,6 @@ ni_netconfig_rules_drop_by_seq(ni_netconfig_t *nc, unsigned int seq)
 	}
 }
 
-static inline void
-__ni_refresh_bonding_master_bind(ni_netdev_t *master, ni_linkinfo_t *link, const char *ifname)
-{
-	const ni_netdev_ref_t ref = { .name = (char *)ifname, .index = link->ifindex };
-	ni_bonding_slave_t *slave;
-
-	slave = ni_bonding_bind_slave(master->bonding, &ref, master->name);
-	ni_bonding_slave_set_info(slave, link->slave.bond);
-}
-
 static void
 __ni_refresh_bind_master(ni_netconfig_t *nc, ni_netdev_t *dev)
 {
@@ -439,19 +429,6 @@ __ni_refresh_bind_master(ni_netconfig_t *nc, ni_netdev_t *dev)
 	if (!(master = ni_netdev_ref_bind_ifname(&dev->link.masterdev, nc))) {
 		ni_info("Interface %s references unknown master device (ifindex %u)",
 				dev->name, dev->link.masterdev.index);
-		return;
-	}
-
-	if (master->link.type != dev->link.slave.type)
-		return;
-
-	switch (dev->link.slave.type) {
-	case NI_IFTYPE_BOND:
-		__ni_refresh_bonding_master_bind(master, &dev->link, dev->name);
-		break;
-
-	default:
-		break;
 	}
 }
 
@@ -467,39 +444,6 @@ __ni_refresh_bind_lower(ni_netconfig_t *nc, ni_netdev_t *dev)
 	}
 }
 
-static inline void
-__ni_refresh_bonding_master_unbind(ni_netdev_t *master, ni_linkinfo_t *link, const char *ifname)
-{
-	const ni_netdev_ref_t ref = { .name = (char *)ifname, .index = link->ifindex };
-
-	ni_bonding_unbind_slave(master->bonding, &ref, master->name);
-}
-
-static void
-__ni_refresh_unbind_master(ni_netconfig_t *nc, ni_netdev_t *dev)
-{
-	ni_netdev_t *master;
-
-	if (!dev->link.masterdev.index)
-		return;
-
-	if (!(master = ni_netdev_by_index(nc, dev->link.masterdev.index)))
-		return;
-
-	if (master->link.type != dev->link.slave.type)
-		return;
-
-	switch (dev->link.slave.type) {
-	case NI_IFTYPE_BOND:
-		__ni_refresh_bonding_master_unbind(master, &dev->link, dev->name);
-		break;
-
-	default:
-		break;
-	}
-}
-
-
 /*
  * Refresh all interfaces
  */
@@ -633,7 +577,6 @@ __ni_system_refresh_all(ni_netconfig_t *nc, ni_netdev_t **del_list)
 		if (dev->seq != seqno) {
 			*tail = dev->next;
 			if (del_list == NULL) {
-				__ni_refresh_unbind_master(nc, dev);
 				ni_client_state_drop(dev->link.ifindex);
 				ni_netdev_put(dev);
 			} else {
@@ -1126,7 +1069,7 @@ __ni_process_ifinfomsg_linktype(ni_linkinfo_t *link, const char *ifname, ni_netc
 	const char *base;
 
 	/* Try to get linktype from kind string. */
-	if (!__ni_linkinfo_kind_to_type(link->kind, &tmp_link_type))
+	if (!ni_linkinfo_kind_to_type(link->kind, &tmp_link_type))
 		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_IFCONFIG,
 				"%s: unknown link-info kind: %s", ifname, link->kind);
 
@@ -1255,36 +1198,17 @@ static inline void
 __ni_process_ifinfomsg_masterdev_unbind(ni_linkinfo_t *link, const char *ifname,
 					unsigned int oindex, ni_netconfig_t *nc)
 {
-	const ni_netdev_ref_t ref = { .name = (char *)ifname, .index = link->ifindex };
-	ni_netdev_t *master;
-
-	if ((master = ni_netdev_by_index(nc, oindex))) {
-		switch (master->link.type) {
-		case NI_IFTYPE_BOND:
-			ni_bonding_unbind_slave(master->bonding, &ref, master->name);
-			break;
-		default:
-			break;
-		}
-	}
 	ni_netdev_ref_destroy(&link->masterdev);
+	ni_netdev_port_info_destroy(&link->port);
 }
 
 static inline ni_netdev_t *
 __ni_process_ifinfomsg_masterdev_bind(ni_linkinfo_t *link, const char *ifname,
 				unsigned int mindex, ni_netconfig_t *nc)
 {
-	const ni_netdev_ref_t ref = { .name = (char *)ifname, .index = link->ifindex };
 	ni_netdev_t *master;
 
 	if ((master = ni_netdev_by_index(nc, mindex))) {
-		switch (master->link.type) {
-		case NI_IFTYPE_BOND:
-			ni_bonding_bind_slave(master->bonding, &ref, master->name);
-			break;
-		default:
-			break;
-		}
 		ni_netdev_ref_set(&link->masterdev, master->name, mindex);
 	} else {
 		ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
@@ -1320,9 +1244,10 @@ __ni_process_ifinfomsg_masterdev(ni_linkinfo_t *link, const char *ifname,
 }
 
 static inline void
-__ni_process_ifinfomsg_bond_slave_data(ni_linkinfo_t *link, const char *ifname, struct nlattr *data)
+ni_rtnl_link_get_bond_port_info(const char *ifname, ni_linkinfo_t *link,
+		struct nlattr *data)
 {
-	/* static const */ struct nla_policy	__slave_policy[IFLA_BOND_SLAVE_MAX+1] = {
+	/* static const */ struct nla_policy	policy[IFLA_BOND_SLAVE_MAX+1] = {
 		[IFLA_BOND_SLAVE_STATE]			= { .type = NLA_U8      },
 		[IFLA_BOND_SLAVE_MII_STATUS]		= { .type = NLA_U8      },
 		[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT]	= { .type = NLA_U32     },
@@ -1331,7 +1256,7 @@ __ni_process_ifinfomsg_bond_slave_data(ni_linkinfo_t *link, const char *ifname,
 		[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID]	= { .type = NLA_U16     },
 	};
 #define map_attr(attr)  [attr] = #attr
-	static const char *			__slave_attrs[IFLA_BOND_SLAVE_MAX+1] = {
+	static const char *			attrs[IFLA_BOND_SLAVE_MAX+1] = {
 		map_attr(IFLA_BOND_SLAVE_STATE),
 		map_attr(IFLA_BOND_SLAVE_MII_STATUS),
 		map_attr(IFLA_BOND_SLAVE_LINK_FAILURE_COUNT),
@@ -1341,34 +1266,37 @@ __ni_process_ifinfomsg_bond_slave_data(ni_linkinfo_t *link, const char *ifname,
 	};
 #undef  map_attr
 	struct nlattr *tb[IFLA_BOND_SLAVE_MAX+1], *aptr;
-	ni_bonding_slave_info_t *info;
+	ni_bonding_port_info_t *info;
 	unsigned int attr, alen;
 	const char *mapped;
 	const char *name;
 
+	if (!ifname || !link || !link->port.bond || !data)
+		return;
+
 	memset(tb, 0, sizeof(tb));
-	if (nla_parse_nested(tb, IFLA_BOND_SLAVE_MAX, data, __slave_policy) < 0) {
+	if (nla_parse_nested(tb, IFLA_BOND_SLAVE_MAX, data, policy) < 0) {
 		ni_warn("%s: unable to parse bond slave data", ifname);
 		return;
 	}
 
-	info = link->slave.bond;
+	info = link->port.bond;
 	for (attr = IFLA_BOND_SLAVE_STATE; attr <= IFLA_BOND_SLAVE_MAX; ++attr) {
 		if (!(aptr = tb[attr]))
 			continue;
 
-		name = __slave_attrs[attr];
+		name = attrs[attr];
 		switch (attr) {
 		case IFLA_BOND_SLAVE_STATE:
 			info->state = nla_get_u8(aptr);
-			mapped = ni_bonding_slave_state_name(info->state);
+			mapped = ni_bonding_port_state_name(info->state);
 			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
 						"%s: get attr %s=%u (%s)", ifname, name,
 						info->state, mapped);
 			break;
 		case IFLA_BOND_SLAVE_MII_STATUS:
 			info->mii_status = nla_get_u8(aptr);
-			mapped = ni_bonding_slave_mii_status_name(info->mii_status);
+			mapped = ni_bonding_port_mii_status_name(info->mii_status);
 			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
 						"%s: get attr %s=%u (%s)", ifname, name,
 						info->mii_status, mapped);
@@ -1412,43 +1340,193 @@ __ni_process_ifinfomsg_bond_slave_data(ni_linkinfo_t *link, const char *ifname,
 }
 
 static inline void
-__ni_process_ifinfomsg_slave_data(ni_linkinfo_t *link, const char *ifname,
-		ni_netdev_t *master, const char *kind, struct nlattr *data)
+ni_rtnl_link_get_bridge_port_info(const char *ifname, ni_linkinfo_t *link,
+		struct nlattr *data)
+{
+	/* static const */ struct nla_policy	policy[IFLA_BRPORT_MAX+1] = {
+		/*
+		 * the constants are in the linux/if_link.h enum order, for
+		 * types see kernel br_port_info_size() + br_port_fill_attrs()
+		 */
+		[IFLA_BRPORT_STATE]			= { .type = NLA_U8	},
+		[IFLA_BRPORT_PRIORITY]			= { .type = NLA_U16	},
+		[IFLA_BRPORT_COST]			= { .type = NLA_U32	},
+		[IFLA_BRPORT_ID]			= { .type = NLA_U16	},
+		[IFLA_BRPORT_NO]			= { .type = NLA_U16	},
+	};
+#define map_attr(attr)[attr] = #attr
+	static const char *			attrs[IFLA_BRPORT_MAX+1] = {
+		map_attr(IFLA_BRPORT_STATE),
+		map_attr(IFLA_BRPORT_PRIORITY),
+		map_attr(IFLA_BRPORT_COST),
+		map_attr(IFLA_BRPORT_ID),
+		map_attr(IFLA_BRPORT_NO),
+	};
+#undef  map_attr
+	struct nlattr *tb[IFLA_BRPORT_MAX+1], *aptr;
+	ni_bridge_port_info_t *info;
+	unsigned int attr;
+	const char *name;
+
+	if (!ifname || !link || !link->port.bridge || !data)
+		return;
+
+	memset(tb, 0, sizeof(tb));
+	if (nla_parse_nested(tb, IFLA_BRPORT_MAX, data, policy) < 0) {
+		ni_warn("%s: unable to parse netlink bridge port info data",
+				ifname);
+		return;
+	}
+
+	info = link->port.bridge;
+	for (attr = IFLA_BRPORT_STATE; attr <= IFLA_BRPORT_MAX; ++attr) {
+		if (!(aptr = tb[attr]))
+			continue;
+
+		name = attrs[attr];
+		switch (attr) {
+		case IFLA_BRPORT_STATE:
+			info->state = nla_get_u8(aptr);
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: get attr %s=%u", ifname, name,
+					info->state);
+			break;
+
+		case IFLA_BRPORT_PRIORITY:
+			info->priority = nla_get_u32(aptr);
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: get attr %s=%u", ifname, name,
+					info->priority);
+			break;
+
+		case IFLA_BRPORT_COST:
+			info->path_cost = nla_get_u32(aptr);
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: get attr %s=%u", ifname, name,
+					info->path_cost);
+			break;
+
+		case IFLA_BRPORT_ID:
+			info->port_id = nla_get_u16(aptr);
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: get attr %s=%u", ifname, name,
+					info->port_id);
+			break;
+
+		case IFLA_BRPORT_NO:
+			info->port_no = nla_get_u16(aptr);
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: get attr %s=%u", ifname, name,
+					info->port_no);
+			break;
+
+		default:
+			break;
+		}
+	}
+}
+
+static inline void
+ni_rtnl_link_get_port_info(const char *ifname, ni_linkinfo_t *link,
+		const char *kind, struct nlattr *data,
+		ni_netdev_t *master, ni_netconfig_t *nc)
 {
-	ni_netdev_slaveinfo_destroy(&link->slave);
+	ni_iftype_t type;
+
+	if (!ni_string_eq(link->port.kind, kind)) {
+		ni_netdev_port_info_destroy(&link->port);
+		ni_netdev_port_info_init(&link->port, kind);
+	}
 
-	ni_string_dup(&link->slave.kind, kind);
-	if (!__ni_linkinfo_kind_to_type(kind, &link->slave.type))
-		link->slave.type = NI_IFTYPE_UNKNOWN;
+	if (!ni_linkinfo_kind_to_type(kind, &type))
+		type = NI_IFTYPE_UNKNOWN;
 
-	switch (link->slave.type) {
+	switch (type) {
 	case NI_IFTYPE_BOND:
-		if (master && master->link.type != link->slave.type) {
-			ni_warn("%s: master %s link type does not match slaveinfo kind type",
-					master->name, ifname);
+		if (master && master->link.type != type) {
+			ni_warn("%s: %s port info type does not match %s link type %s",
+					ifname, ni_linktype_type_to_name(type),
+					master->name,
+					ni_linktype_type_to_name(master->link.type));
 			return;
 		}
-
 		if (!data) {
+			ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
+					"%s: %s port info type without any data",
+					ifname, ni_linktype_type_to_name(type));
+			return;
+		}
+
+		ni_netdev_port_info_data_destroy(&link->port);
+		if (!ni_netdev_port_info_data_init(&link->port, type)) {
+			ni_warn("%s: unable to initialize/allocate %s port info",
+					ifname, ni_linktype_type_to_name(type));
+			return;
+		}
+		ni_rtnl_link_get_bond_port_info(ifname, link, data);
+		break;
+
+	case NI_IFTYPE_TEAM:
+		if (ni_netconfig_discover_filtered(nc, NI_NETCONFIG_DISCOVER_LINK_EXTERN)) {
 			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
-					"%s: slave info does not provide any data", ifname);
+					"%s: omitting external %s port info discovery",
+					ifname, ni_linktype_type_to_name(type));
 			return;
 		}
 
-		if (master) {
-			ni_bonding_slave_t *slave;
+		ni_netdev_port_info_data_destroy(&link->port);
+		if (!ni_netdev_port_info_data_init(&link->port, type)) {
+			ni_warn("%s: unable to initialize/allocate %s port info",
+					ifname, ni_linktype_type_to_name(type));
+			return;
+		}
+		ni_teamd_port_info_discover(&link->port, link->masterdev.name, ifname);
+		break;
 
-			slave = ni_bonding_slave_array_get_by_ifindex(&master->bonding->slaves,	link->ifindex);
-			link->slave.bond = ni_bonding_slave_info_ref(ni_bonding_slave_get_info(slave));
-		} else {
-			link->slave.bond = ni_bonding_slave_info_new();
+	case NI_IFTYPE_BRIDGE:
+		if (master && master->link.type != type) {
+			ni_warn("%s: %s port info type does not match %s link type %s",
+					ifname, ni_linktype_type_to_name(type),
+					master->name,
+					ni_linktype_type_to_name(master->link.type));
+			return;
+		}
+		if (!data) {
+			ni_debug_verbose(NI_LOG_DEBUG, NI_TRACE_EVENTS,
+					"%s: %s port info type without any data",
+					ifname, ni_linktype_type_to_name(type));
+			return;
 		}
 
-		if (link->slave.bond)
-			__ni_process_ifinfomsg_bond_slave_data(link, ifname, data);
+		ni_netdev_port_info_data_destroy(&link->port);
+		if (!ni_netdev_port_info_data_init(&link->port, type)) {
+			ni_warn("%s: unable to initialize/allocate %s port info",
+					ifname, ni_linktype_type_to_name(type));
+			return;
+		}
+		ni_rtnl_link_get_bridge_port_info(ifname, link, data);
+		break;
+
+	case NI_IFTYPE_OVS_UNSPEC:
+		if (ni_netconfig_discover_filtered(nc, NI_NETCONFIG_DISCOVER_LINK_EXTERN)) {
+			ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+					"%s: omitting external %s port info discovery",
+					ifname, ni_linktype_type_to_name(type));
+			return;
+		}
+
+		ni_ovs_port_info_discover(&link->port, ifname, nc);
 		break;
 
 	default:
+		/*
+		 * actually unused / unhandled port info kind:
+		 * destroy old data if any and track kind only.
+		 */
+		ni_debug_verbose(NI_LOG_DEBUG2, NI_TRACE_EVENTS,
+				"%s: received %s port info kind %s data",
+				ifname, kind, data ? "with" : "without");
+		ni_netdev_port_info_data_destroy(&link->port);
 		break;
 	}
 }
@@ -1504,6 +1582,14 @@ __ni_process_ifinfomsg_linkinfo(ni_linkinfo_t *link, const char *ifname,
 	if (tb[IFLA_QDISC])
 		ni_string_dup(&link->qdisc, nla_get_string(tb[IFLA_QDISC]));
 
+	if (tb[IFLA_OPERSTATE]) {
+		/* get the RFC 2863 operational status - IF_OPER_* */
+		link->oper_state = nla_get_u8(tb[IFLA_OPERSTATE]);
+	}
+
+	if (tb[IFLA_IFALIAS])
+		ni_string_dup(&link->alias, nla_get_string(tb[IFLA_IFALIAS]));
+
 	if (tb[IFLA_LINK]) {
 		link->lowerdev.index = nla_get_u32(tb[IFLA_LINK]);
 		if (!ni_netdev_ref_bind_ifname(&link->lowerdev, nc)) {
@@ -1523,47 +1609,6 @@ __ni_process_ifinfomsg_linkinfo(ni_linkinfo_t *link, const char *ifname,
 		master = NULL;
 	}
 
-	if (tb[IFLA_IFALIAS])
-		ni_string_dup(&link->alias, nla_get_string(tb[IFLA_IFALIAS]));
-	if (tb[IFLA_OPERSTATE]) {
-		/* get the RFC 2863 operational status - IF_OPER_* */
-		link->oper_state = nla_get_u8(tb[IFLA_OPERSTATE]);
-	}
-
-	if (tb[IFLA_STATS]) {
-		struct rtnl_link_stats *s = nla_data(tb[IFLA_STATS]);
-		ni_link_stats_t *n;
-
-		if (!link->stats)
-			link->stats = calloc(1, sizeof(*n));
-
-		if ((n = link->stats)) {
-			n->rx_packets = s->rx_packets;
-			n->tx_packets = s->tx_packets;
-			n->rx_bytes = s->rx_bytes;
-			n->tx_bytes = s->tx_bytes;
-			n->rx_errors = s->rx_errors;
-			n->tx_errors = s->tx_errors;
-			n->rx_dropped = s->rx_dropped;
-			n->tx_dropped = s->tx_dropped;
-			n->multicast = s->multicast;
-			n->collisions = s->collisions;
-			n->rx_length_errors = s->rx_length_errors;
-			n->rx_over_errors = s->rx_over_errors;
-			n->rx_crc_errors = s->rx_crc_errors;
-			n->rx_frame_errors = s->rx_frame_errors;
-			n->rx_fifo_errors = s->rx_fifo_errors;
-			n->rx_missed_errors = s->rx_missed_errors;
-			n->tx_aborted_errors = s->tx_aborted_errors;
-			n->tx_carrier_errors = s->tx_carrier_errors;
-			n->tx_fifo_errors = s->tx_fifo_errors;
-			n->tx_heartbeat_errors = s->tx_heartbeat_errors;
-			n->tx_window_errors = s->tx_window_errors;
-			n->rx_compressed = s->rx_compressed;
-			n->tx_compressed = s->tx_compressed;
-		}
-	}
-
 	/* Extended link info. Let's use it to try to determine link->type.
 	 *
 	 * IFLA_LINKINFO is a nested set of attrs. It always contains
@@ -1603,9 +1648,9 @@ __ni_process_ifinfomsg_linkinfo(ni_linkinfo_t *link, const char *ifname,
 		}
 
 		if (nl_linkinfo[IFLA_INFO_SLAVE_KIND]) {
-			__ni_process_ifinfomsg_slave_data(link, ifname, master,
+			ni_rtnl_link_get_port_info(ifname, link,
 					nla_get_string(nl_linkinfo[IFLA_INFO_SLAVE_KIND]),
-					nl_linkinfo[IFLA_INFO_SLAVE_DATA]);
+					nl_linkinfo[IFLA_INFO_SLAVE_DATA], master, nc);
 		}
 	}
 
@@ -3486,8 +3531,6 @@ static int
 __ni_discover_bridge(ni_netdev_t *dev)
 {
 	ni_bridge_t *bridge;
-	ni_string_array_t ports;
-	unsigned int i;
 
 	if (dev->link.type != NI_IFTYPE_BRIDGE)
 		return 0;
@@ -3497,28 +3540,6 @@ __ni_discover_bridge(ni_netdev_t *dev)
 	ni_sysfs_bridge_get_config(dev->name, bridge);
 	ni_sysfs_bridge_get_status(dev->name, &bridge->status);
 
-	ni_string_array_init(&ports);
-	ni_sysfs_bridge_get_port_names(dev->name, &ports);
-	ni_bridge_ports_destroy(bridge);
-
-	for (i = 0; i < ports.count; ++i) {
-		const char *ifname = ports.data[i];
-		unsigned int index;
-		ni_bridge_port_t *port;
-
-		if ((index = ni_netdev_name_to_index(ifname)) == 0) {
-			/* Looks like someone is renaming interfaces while we're
-			 * trying to discover them :-( */
-			ni_error("%s: port interface %s has index 0?!", __func__, ifname);
-			continue;
-		}
-		port = ni_bridge_port_new(bridge, ifname, index);
-
-		ni_sysfs_bridge_port_get_config(port->ifname, port);
-		ni_sysfs_bridge_port_get_status(port->ifname, &port->status);
-	}
-	ni_string_array_destroy(&ports);
-
 	return 0;
 }
 
diff --git a/src/names.c b/src/names.c
index 15881d0e9..9f3790e4d 100644
--- a/src/names.c
+++ b/src/names.c
@@ -51,7 +51,7 @@ ni_tristate_by_name(const char *name, ni_tristate_t *tristate)
 /*
  * Map interface link layer types to strings and vice versa
  */
-static const ni_intmap_t	__linktype_names[] = {
+static const ni_intmap_t	ni_linktype_name_map[] = {
 	{ "unknown",		NI_IFTYPE_UNKNOWN },
 	{ "loopback",		NI_IFTYPE_LOOPBACK },
 	{ "ethernet",		NI_IFTYPE_ETHERNET },
@@ -89,7 +89,7 @@ ni_linktype_name_to_type(const char *name)
 {
 	unsigned int value;
 
-	if (ni_parse_uint_mapped(name, __linktype_names, &value) < 0)
+	if (ni_parse_uint_mapped(name, ni_linktype_name_map, &value) < 0)
 		return -1;
 	return value;
 }
@@ -97,15 +97,17 @@ ni_linktype_name_to_type(const char *name)
 const char *
 ni_linktype_type_to_name(unsigned int type)
 {
-	return ni_format_uint_mapped(type, __linktype_names);
+	return ni_format_uint_mapped(type, ni_linktype_name_map);
 }
 
 /*
- * Map kernel kind to type
+ * Map kernel kind to wicked iftype
  *
- * Note: this is an initial, incomplete, one-direction match!
+ * Note: the kind string is what the kernel is using,
+ *       but it's not yet / 1:1 the final wicked type,
+ *       see e.g. tun & tap or ovs kind and types.
  */
-static const ni_intmap_t	__linkinfo_kind_names[] = {
+static const ni_intmap_t	ni_linkinfo_kind_map[] = {
 	{ "bridge",		NI_IFTYPE_BRIDGE },
 	{ "bond",		NI_IFTYPE_BOND },
 	{ "team",		NI_IFTYPE_TEAM },
@@ -120,20 +122,27 @@ static const ni_intmap_t	__linkinfo_kind_names[] = {
 	{ "gre",		NI_IFTYPE_GRE },
 	{ "openvswitch",	NI_IFTYPE_OVS_UNSPEC }, /* new in 4.4 kernels */
 
-	{ NULL }
+	{ NULL,			NI_IFTYPE_UNKNOWN }
 };
 
 ni_bool_t
-__ni_linkinfo_kind_to_type(const char *name, ni_iftype_t *iftype)
+ni_linkinfo_kind_to_type(const char *kind, ni_iftype_t *type)
 {
 	unsigned int value;
 
-	if (!iftype || ni_parse_uint_mapped(name, __linkinfo_kind_names, &value) < 0)
+	if (!type || ni_parse_uint_mapped(kind, ni_linkinfo_kind_map, &value) < 0)
 		return FALSE;
-	*iftype = value;
+
+	*type = value;
 	return TRUE;
 }
 
+const char *
+ni_linkinfo_type_to_kind(ni_iftype_t type)
+{
+	return ni_format_uint_mapped(type, ni_linkinfo_kind_map);
+}
+
 /*
  * Map addrconf name to type constant and vice versa
  */
diff --git a/src/netdev.c b/src/netdev.c
index d51104986..6d117c964 100644
--- a/src/netdev.c
+++ b/src/netdev.c
@@ -80,18 +80,88 @@ ni_netdev_clear_routes(ni_netdev_t *dev)
 	ni_route_tables_destroy(&dev->routes);
 }
 
+ni_bool_t
+ni_netdev_port_info_data_init(ni_netdev_port_info_t *info, ni_iftype_t type)
+{
+	if (info) {
+		switch (type) {
+		case NI_IFTYPE_BOND:
+			if (!(info->bond = ni_bonding_port_info_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_TEAM:
+			if (!(info->team = ni_team_port_info_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_BRIDGE:
+			if (!(info->bridge = ni_bridge_port_info_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_OVS_BRIDGE:
+			if (!(info->ovsbr = ni_ovs_bridge_port_info_new()))
+				return FALSE;
+			break;
+
+		default:
+			info->bond = NULL; /* union ptr */
+			break;
+		}
+
+		info->type = type;
+		return TRUE;
+	}
+	return FALSE;
+}
+
 void
-ni_netdev_slaveinfo_destroy(ni_slaveinfo_t *slave)
+ni_netdev_port_info_data_destroy(ni_netdev_port_info_t *info)
 {
-	switch (slave->type) {
-	case NI_IFTYPE_BOND:
-		ni_bonding_slave_info_free(slave->bond);
-		break;
-	default:
-		break;
+	if (info) {
+		switch (info->type) {
+		case NI_IFTYPE_BOND:
+			ni_bonding_port_info_free(info->bond);
+			break;
+
+		case NI_IFTYPE_TEAM:
+			ni_team_port_info_free(info->team);
+			break;
+
+		case NI_IFTYPE_BRIDGE:
+			ni_bridge_port_info_free(info->bridge);
+			break;
+
+		case NI_IFTYPE_OVS_BRIDGE:
+			ni_ovs_bridge_port_info_free(info->ovsbr);
+			break;
+
+		default:
+			break;
+		}
+		info->bond = NULL; /* union ptr */
+		info->type = NI_IFTYPE_UNKNOWN;
+	}
+}
+
+ni_bool_t
+ni_netdev_port_info_init(ni_netdev_port_info_t *info, const char *kind)
+{
+	if (info) {
+		ni_netdev_port_info_data_init(info, NI_IFTYPE_UNKNOWN);
+		return ni_string_dup(&info->kind, kind);
+	}
+	return FALSE;
+}
+
+void
+ni_netdev_port_info_destroy(ni_netdev_port_info_t *info)
+{
+	if (info) {
+		ni_netdev_port_info_data_destroy(info);
+		ni_string_free(&info->kind);
 	}
-	free(slave->kind);
-	memset(slave, 0, sizeof(*slave));
 }
 
 void
@@ -118,12 +188,11 @@ ni_netdev_reset(ni_netdev_t *dev)
 
 	ni_netdev_ref_destroy(&dev->link.lowerdev);
 	ni_netdev_ref_destroy(&dev->link.masterdev);
-	ni_netdev_slaveinfo_destroy(&dev->link.slave);
+	ni_netdev_port_info_destroy(&dev->link.port);
 
 	ni_string_free(&dev->link.qdisc);
 	ni_string_free(&dev->link.kind);
 
-	ni_netdev_set_link_stats(dev, NULL);
 	ni_netdev_set_client_state(dev, NULL);
 
 	ni_netdev_clear_addresses(dev);
@@ -215,29 +284,6 @@ ni_netdev_device_is_ready(ni_netdev_t *dev)
 	return dev ? dev->link.ifflags & NI_IFF_DEVICE_READY : FALSE;
 }
 
-ni_tristate_t
-ni_netdev_guess_link_required(const ni_netdev_t *dev)
-{
-	ni_tristate_t link_required = NI_TRISTATE_DEFAULT;
-
-	switch (dev->link.type) {
-	case NI_IFTYPE_OVS_SYSTEM:
-	case NI_IFTYPE_TUN:
-	case NI_IFTYPE_TAP:
-		ni_tristate_set(&link_required, FALSE);
-		break;
-
-	case NI_IFTYPE_BRIDGE:
-		if (dev->bridge && dev->bridge->stp && !dev->bridge->ports.count)
-			ni_tristate_set(&link_required, FALSE);
-		break;
-
-	default:
-		break;
-	}
-	return link_required;
-}
-
 /*
  * Get the interface's VLAN information
  */
@@ -599,17 +645,6 @@ ni_netdev_set_dcb(ni_netdev_t *dev, ni_dcb_t *dcb)
 	dev->dcb = dcb;
 }
 
-/*
- * Set the interface's link stats
- */
-void
-ni_netdev_set_link_stats(ni_netdev_t *dev, ni_link_stats_t *stats)
-{
-	if (dev->link.stats)
-		free(dev->link.stats);
-	dev->link.stats = stats;
-}
-
 /*
  * Set the PCI device info
  */
diff --git a/src/netinfo.c b/src/netinfo.c
index c01e80d30..28617b858 100644
--- a/src/netinfo.c
+++ b/src/netinfo.c
@@ -72,7 +72,7 @@ static int
 __ni_init_gcrypt(void)
 {
 /*
- * gcry_check_version checks for minmum version
+ * gcry_check_version checks for minimum version
  * we want consider sufficient and returns NULL
  * on failures.
  *
@@ -580,13 +580,15 @@ ni_netconfig_device_append(ni_netconfig_t *nc, ni_netdev_t *dev)
 }
 
 static inline void
-ni_netconfig_device_unbind_slave_index(ni_netconfig_t *nc, unsigned int master)
+ni_netconfig_device_unbind_ports(ni_netconfig_t *nc, unsigned int master)
 {
 	ni_netdev_t *dev;
 
 	for (dev = nc->interfaces; dev; dev = dev->next) {
-		if (dev->link.masterdev.index == master)
+		if (dev->link.masterdev.index == master) {
 			ni_netdev_ref_destroy(&dev->link.masterdev);
+			ni_netdev_port_info_destroy(&dev->link.port);
+		}
 	}
 }
 
@@ -598,7 +600,7 @@ ni_netconfig_device_remove(ni_netconfig_t *nc, ni_netdev_t *dev)
 	for (pos = &nc->interfaces; (cur = *pos) != NULL; pos = &cur->next) {
 		if (cur == dev) {
 			*pos = cur->next;
-			ni_netconfig_device_unbind_slave_index(nc, cur->link.ifindex);
+			ni_netconfig_device_unbind_ports(nc, cur->link.ifindex);
 			ni_netdev_put(cur);
 			return;
 		}
@@ -814,6 +816,9 @@ ni_netdev_by_name(ni_netconfig_t *nc, const char *name)
 {
 	ni_netdev_t *dev;
 
+	if (!nc && !(nc = ni_global_state_handle(0)))
+		return NULL;
+
 	for (dev = nc->interfaces; dev; dev = dev->next) {
 		if (dev->name && ni_string_eq(dev->name, name))
 			return dev;
@@ -830,6 +835,9 @@ ni_netdev_by_index(ni_netconfig_t *nc, unsigned int ifindex)
 {
 	ni_netdev_t *dev;
 
+	if (!nc && !(nc = ni_global_state_handle(0)))
+		return NULL;
+
 	for (dev = nc->interfaces; dev; dev = dev->next) {
 		if (dev->link.ifindex == ifindex)
 			return dev;
@@ -849,6 +857,9 @@ ni_netdev_by_hwaddr(ni_netconfig_t *nc, const ni_hwaddr_t *lla)
 	if (!lla || !lla->len)
 		return NULL;
 
+	if (!nc && !(nc = ni_global_state_handle(0)))
+		return NULL;
+
 	for (dev = nc->interfaces; dev; dev = dev->next) {
 		if (ni_link_address_equal(&dev->link.hwaddr, lla))
 			return dev;
@@ -857,6 +868,25 @@ ni_netdev_by_hwaddr(ni_netconfig_t *nc, const ni_hwaddr_t *lla)
 	return NULL;
 }
 
+/*
+ * Find network interface by iftype
+ */
+ni_netdev_t *
+ni_netdev_by_iftype(ni_netconfig_t *nc, ni_iftype_t iftype)
+{
+	ni_netdev_t *dev;
+
+	if (!nc && !(nc = ni_global_state_handle(0)))
+		return NULL;
+
+	for (dev = nc->interfaces; dev; dev = dev->next) {
+		if (dev->link.type == iftype)
+			return dev;
+	}
+
+	return NULL;
+}
+
 /*
  * Find VLAN interface by its tag
  */
@@ -867,6 +897,10 @@ ni_netdev_by_vlan_name_and_tag(ni_netconfig_t *nc, const char *parent_name, uint
 
 	if (!parent_name || !tag)
 		return NULL;
+
+	if (!nc && !(nc = ni_global_state_handle(0)))
+		return NULL;
+
 	for (dev = nc->interfaces; dev; dev = dev->next) {
 		if (dev->link.type == NI_IFTYPE_VLAN
 		 && dev->vlan
@@ -923,6 +957,113 @@ ni_netdev_make_name(ni_netconfig_t *nc, const char *stem, unsigned int first)
 	return NULL;
 }
 
+/*
+ * Get port count and optionally an array of port netdev
+ * references for the given netdev (index).
+ */
+static unsigned int
+ni_netdev_get_ovsbr_ports_by_index(unsigned int index, ni_netdev_ref_array_t *ports,
+		ni_netconfig_t *nc)
+{
+	unsigned int count = ports ? ports->count : 0;
+	ni_ovs_bridge_port_info_t *pinfo;
+	ni_netdev_t *dev;
+
+	if (!index || (!nc && !(nc = ni_global_state_handle(0))))
+		return count;
+
+	for (dev = nc->interfaces; dev; dev = dev->next) {
+		if (dev->link.port.type != NI_IFTYPE_OVS_BRIDGE)
+			continue;
+
+		if (!(pinfo = dev->link.port.ovsbr))
+			continue;
+
+		if (index != pinfo->bridge.index)
+			continue;
+
+		if (ports)
+			ni_netdev_ref_array_append(ports, dev->name,
+					dev->link.ifindex);
+		else
+			count++;
+	}
+
+	return ports ? ports->count - count : count;
+}
+
+static unsigned int
+ni_netdev_get_ports_by_index(unsigned int index, ni_netdev_ref_array_t *ports,
+		ni_netconfig_t *nc)
+{
+	unsigned int count = ports ? ports->count : 0;
+	ni_netdev_t *dev;
+
+	if (!index || (!nc && !(nc = ni_global_state_handle(0))))
+		return count;
+
+	for (dev = nc->interfaces; dev; dev = dev->next) {
+		if (dev->link.masterdev.index != index)
+			continue;
+
+		if (ports)
+			ni_netdev_ref_array_append(ports, dev->name,
+					dev->link.ifindex);
+		else
+			count++;
+	}
+
+	return ports ? ports->count - count : count;
+}
+
+extern unsigned int
+ni_netdev_get_ports(const ni_netdev_t *dev, ni_netdev_ref_array_t *ports,
+		ni_netconfig_t *nc)
+{
+	unsigned int index;
+
+	if (!dev)
+		return ports ? ports->count : 0;
+
+	index = dev->link.ifindex;
+	if (dev->link.type == NI_IFTYPE_OVS_BRIDGE)
+		return ni_netdev_get_ovsbr_ports_by_index(index, ports, nc);
+	else
+		return ni_netdev_get_ports_by_index(index, ports, nc);
+}
+
+/*
+ * Resolve port's master reference to netdev
+ */
+const ni_netdev_ref_t *
+ni_netdev_get_master_ref(const ni_netdev_t *dev)
+{
+	if (!dev)
+		return NULL;
+
+	if (dev->link.port.type == NI_IFTYPE_OVS_BRIDGE) {
+		ni_ovs_bridge_port_info_t *ovsbr;
+
+		if (!(ovsbr = dev->link.port.ovsbr))
+			return NULL;
+
+		return &ovsbr->bridge;
+	}
+
+	return &dev->link.masterdev;
+}
+
+ni_netdev_t *
+ni_netdev_resolve_master(const ni_netdev_t *dev, ni_netconfig_t *nc)
+{
+	const ni_netdev_ref_t *ref;
+
+	if ((ref = ni_netdev_get_master_ref(dev)))
+		return ni_netdev_ref_resolve(ref, nc);
+
+	return NULL;
+}
+
 /*
  * netdev reference
  */
@@ -1134,63 +1275,68 @@ ni_netdev_ref_array_destroy(ni_netdev_ref_array_t *array)
 /*
  * Handle netdev request port config
  */
-static void
-ni_netdev_port_req_init(ni_netdev_port_req_t *port)
-{
-	switch (port->type) {
-        case NI_IFTYPE_TEAM:
-		ni_team_port_config_init(&port->team);
-		break;
-
-	case NI_IFTYPE_OVS_BRIDGE:
-		ni_ovs_bridge_port_config_init(&port->ovsbr);
-		break;
-
-	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
-	default:
-		break;
-	}
-}
-
-ni_netdev_port_req_t *
-ni_netdev_port_req_new(ni_iftype_t master)
+ni_bool_t
+ni_netdev_port_config_init(ni_netdev_port_config_t *conf, ni_iftype_t type)
 {
-	ni_netdev_port_req_t *port;
+	if (conf) {
+		memset(conf, 0, sizeof(*conf));
 
-	switch (master) {
-	case NI_IFTYPE_TEAM:
-	case NI_IFTYPE_BOND:
-	case NI_IFTYPE_BRIDGE:
-	case NI_IFTYPE_OVS_BRIDGE:
-		port = xcalloc(1, sizeof(*port));
-		port->type = master;
-		ni_netdev_port_req_init(port);
-		return port;
-	default:
-		return NULL;
+		switch (type) {
+		case NI_IFTYPE_BOND:
+			if (!(conf->bond = ni_bonding_port_config_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_TEAM:
+			if (!(conf->team = ni_team_port_config_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_BRIDGE:
+			if (!(conf->bridge = ni_bridge_port_config_new()))
+				return FALSE;
+			break;
+
+		case NI_IFTYPE_OVS_BRIDGE:
+			if (!(conf->ovsbr = ni_ovs_bridge_port_config_new()))
+				return FALSE;
+			break;
+
+		default:
+			break;
+		}
+		conf->type = type;
+
+		return TRUE;
 	}
+	return FALSE;
 }
-
 void
-ni_netdev_port_req_free(ni_netdev_port_req_t *port)
+ni_netdev_port_config_destroy(ni_netdev_port_config_t *conf)
 {
-	if (port) {
-		switch (port->type) {
+	if (conf) {
+		switch (conf->type) {
+		case NI_IFTYPE_BOND:
+			ni_bonding_port_config_free(conf->bond);
+			break;
+
 		case NI_IFTYPE_TEAM:
-			ni_team_port_config_destroy(&port->team);
+			ni_team_port_config_free(conf->team);
+			break;
+
+		case NI_IFTYPE_BRIDGE:
+			ni_bridge_port_config_free(conf->bridge);
 			break;
 
 		case NI_IFTYPE_OVS_BRIDGE:
-			ni_ovs_bridge_port_config_destroy(&port->ovsbr);
+			ni_ovs_bridge_port_config_free(conf->ovsbr);
 			break;
 
-		case NI_IFTYPE_BOND:
-		case NI_IFTYPE_BRIDGE:
 		default:
 			break;
 		}
-		free(port);
+
+		memset(conf, 0, sizeof(*conf));
 	}
 }
 
@@ -1211,7 +1357,7 @@ ni_netdev_req_free(ni_netdev_req_t *req)
 {
 	ni_string_free(&req->alias);
 	ni_netdev_ref_destroy(&req->master);
-	ni_netdev_port_req_free(req->port);
+	ni_netdev_port_config_destroy(&req->port);
 	free(req);
 }
 
diff --git a/src/netinfo_priv.h b/src/netinfo_priv.h
index 3f764566b..afbc4f6ce 100644
--- a/src/netinfo_priv.h
+++ b/src/netinfo_priv.h
@@ -64,7 +64,8 @@ extern ni_bool_t	ni_netconfig_discover_filtered(ni_netconfig_t *, unsigned int);
 extern ni_bool_t	ni_netconfig_set_family_filter(ni_netconfig_t *, unsigned int);
 extern unsigned int	ni_netconfig_get_family_filter(ni_netconfig_t *);
 
-extern ni_bool_t	__ni_linkinfo_kind_to_type(const char *, ni_iftype_t *);
+extern ni_bool_t	ni_linkinfo_kind_to_type(const char *, ni_iftype_t *);
+extern const char *	ni_linkinfo_type_to_kind(ni_iftype_t);
 
 extern void		__ni_netdev_list_append(ni_netdev_t **, ni_netdev_t *);
 extern void		__ni_netdev_list_destroy(ni_netdev_t **);
@@ -256,16 +257,4 @@ extern void				ni_arp_notify_destroy(ni_arp_notify_t *);
 extern unsigned int			ni_arp_notify_add_address(ni_arp_notify_t *,  ni_address_t *);
 extern ni_bool_t			ni_arp_notify_send(ni_arp_socket_t *, ni_arp_notify_t *, ni_timeout_t *);
 
-/* netdev reques port config */
-struct ni_netdev_port_req {
-	ni_iftype_t				type;
-	union {
-		ni_team_port_config_t		team;
-		ni_ovs_bridge_port_config_t	ovsbr;
-	};
-};
-
-extern ni_netdev_port_req_t *	ni_netdev_port_req_new(ni_iftype_t);
-extern void			ni_netdev_port_req_free(ni_netdev_port_req_t *);
-
 #endif /* __NETINFO_PRIV_H__ */
diff --git a/src/ovs.c b/src/ovs.c
index 6b3395b15..9164f7023 100644
--- a/src/ovs.c
+++ b/src/ovs.c
@@ -1,7 +1,7 @@
 /*
  *	OVS (bridge) device support
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,13 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -35,7 +30,6 @@
 #include "util_priv.h"
 #include "array_priv.h"
 
-#define NI_OVS_BRIDGE_PORT_ARRAY_CHUNK		4
 
 ni_ovs_bridge_t *
 ni_ovs_bridge_new(void)
@@ -52,7 +46,6 @@ ni_ovs_bridge_free(ni_ovs_bridge_t *ovsbr)
 {
 	if (ovsbr) {
 		ni_ovs_bridge_config_destroy(&ovsbr->config);
-		ni_ovs_bridge_port_array_destroy(&ovsbr->ports);
 		free(ovsbr);
 	}
 }
@@ -70,84 +63,51 @@ ni_ovs_bridge_config_destroy(ni_ovs_bridge_config_t *conf)
 	ni_ovs_bridge_config_init(conf);
 }
 
-
-ni_ovs_bridge_port_t *
-ni_ovs_bridge_port_new(void)
-{
-	ni_ovs_bridge_port_t *port;
-
-	port = xcalloc(1, sizeof(*port));
-	return port;
-}
-
-void
-ni_ovs_bridge_port_free(ni_ovs_bridge_port_t *port)
+/*
+ * OVS bridge port config
+ */
+ni_bool_t
+ni_ovs_bridge_port_config_init(ni_ovs_bridge_port_config_t *conf)
 {
-	if (port) {
-		ni_netdev_ref_destroy(&port->device);
-		free(port);
+	if (conf) {
+		memset(conf, 0, sizeof(*conf));
+		return TRUE;
 	}
+	return FALSE;
 }
 
-extern ni_define_ptr_array_init(ni_ovs_bridge_port);
-extern ni_define_ptr_array_destroy(ni_ovs_bridge_port);
-static ni_define_ptr_array_realloc(ni_ovs_bridge_port, NI_OVS_BRIDGE_PORT_ARRAY_CHUNK);
-extern ni_define_ptr_array_append(ni_ovs_bridge_port);
-extern ni_define_ptr_array_delete_at(ni_ovs_bridge_port);
-
-ni_ovs_bridge_port_t *
-ni_ovs_bridge_port_array_add_new(ni_ovs_bridge_port_array_t *ports, const char *pname)
+ni_ovs_bridge_port_config_t *
+ni_ovs_bridge_port_config_new(void)
 {
-	ni_ovs_bridge_port_t *port;
-
-	if (!ports || ni_string_empty(pname))
-		return NULL;
-
-	if (ni_ovs_bridge_port_array_find_by_name(ports, pname))
-		return NULL;
-
-	if (!(port = ni_ovs_bridge_port_new()))
-		return NULL;
-
-	ni_netdev_ref_set_ifname(&port->device, pname);
+	ni_ovs_bridge_port_config_t *conf;
 
-	if (ni_ovs_bridge_port_array_append(ports, port))
-		return port;
+	conf = malloc(sizeof(*conf));
+	if (ni_ovs_bridge_port_config_init(conf))
+		return conf;
 
-	ni_ovs_bridge_port_free(port);
-	return NULL;
-}
-
-ni_ovs_bridge_port_t *
-ni_ovs_bridge_port_array_find_by_name(ni_ovs_bridge_port_array_t *array, const char *name)
-{
-	unsigned int i;
-
-	if (!array || !name)
-		return NULL;
-
-	for (i = 0; i < array->count; ++i) {
-		ni_ovs_bridge_port_t *port = array->data[i];
-		if (ni_string_eq(name, port->device.name))
-			return port;
-	}
+	free(conf);
 	return NULL;
 }
 
 void
-ni_ovs_bridge_port_config_init(ni_ovs_bridge_port_config_t *conf)
+ni_ovs_bridge_port_config_destroy(ni_ovs_bridge_port_config_t *conf)
 {
-	memset(conf, 0, sizeof(*conf));
+	if (conf) {
+		ni_var_array_destroy(&conf->args);
+		ni_ovs_bridge_port_config_init(conf);
+	}
 }
 
+/*
+ * OVS bridge port state info
+ */
 void
-ni_ovs_bridge_port_config_destroy(ni_ovs_bridge_port_config_t *conf)
+ni_ovs_bridge_port_config_free(ni_ovs_bridge_port_config_t *conf)
 {
-	ni_netdev_ref_destroy(&conf->bridge);
-	ni_ovs_bridge_port_config_init(conf);
+	ni_ovs_bridge_port_config_destroy(conf);
+	free(conf);
 }
 
-
 static const char *
 ni_ovs_vsctl_tool_path(void)
 {
@@ -320,7 +280,7 @@ failure:
 
 
 int /* process run codes (for now) */
-ni_ovs_vsctl_bridge_ports(const char *brname, ni_ovs_bridge_port_array_t *ports)
+ni_ovs_vsctl_bridge_ports(const char *brname, ni_netdev_ref_array_t *ports)
 {
 	ni_stringbuf_t pname = NI_STRINGBUF_INIT_DYNAMIC;
 	const char *ovs_vsctl;
@@ -364,7 +324,7 @@ ni_ovs_vsctl_bridge_ports(const char *brname, ni_ovs_bridge_port_array_t *ports)
 		if (cc == '\n') {
 			if (pname.string)
 				pname.string[strcspn(pname.string, "\n\r")] = '\0';
-			ni_ovs_bridge_port_array_add_new(ports, pname.string);
+			ni_netdev_ref_array_append(ports, pname.string, 0);
 			ni_stringbuf_destroy(&pname);
 		} else {
 			ni_stringbuf_putc(&pname, cc);
@@ -372,7 +332,7 @@ ni_ovs_vsctl_bridge_ports(const char *brname, ni_ovs_bridge_port_array_t *ports)
 	}
 	if (pname.string)
 		pname.string[strcspn(pname.string, "\n\r")] = '\0';
-	ni_ovs_bridge_port_array_add_new(ports, pname.string);
+	ni_netdev_ref_array_append(ports, pname.string, 0);
 	ni_stringbuf_destroy(&pname);
 
 failure:
@@ -476,14 +436,16 @@ failure:
 }
 
 int /* process run codes (for now) */
-ni_ovs_vsctl_bridge_port_add(const char *pname, const ni_ovs_bridge_port_config_t *pconf, ni_bool_t may_exist)
+ni_ovs_vsctl_bridge_port_add(const char *brname, const char *pname,
+		const ni_ovs_bridge_port_config_t *pconf,
+		ni_bool_t may_exist)
 {
 	const char *ovs_vsctl;
 	ni_shellcmd_t *cmd;
 	ni_process_t *pi;
 	int rv = NI_PROCESS_FAILURE;
 
-	if (ni_string_empty(pname) || !pconf || ni_string_empty(pconf->bridge.name))
+	if (ni_string_empty(brname) || ni_string_empty(pname) || !pconf)
 		return rv;
 
 	if (!(ovs_vsctl = ni_ovs_vsctl_tool_path()))
@@ -501,7 +463,7 @@ ni_ovs_vsctl_bridge_port_add(const char *pname, const ni_ovs_bridge_port_config_
 	if (!ni_shellcmd_add_arg(cmd, "add-port"))
 		goto failure;
 
-	if (!ni_shellcmd_add_arg(cmd, pconf->bridge.name))
+	if (!ni_shellcmd_add_arg(cmd, brname))
 		goto failure;
 
 	if (!ni_shellcmd_add_arg(cmd, pname))
@@ -598,7 +560,7 @@ ni_ovs_vsctl_bridge_port_to_bridge(const char *pname, char **brname)
 
 	rv = ni_process_run_and_capture_output(pi, &buf);
 	ni_process_free(pi);
-	if (rv) {
+	if (rv != NI_PROCESS_SUCCESS) {
 		ni_error("%s: unable to query port bridge", pname);
 		goto failure;
 	}
@@ -619,30 +581,69 @@ int
 ni_ovs_bridge_discover(ni_netdev_t *dev, ni_netconfig_t *nc)
 {
 	ni_ovs_bridge_t *ovsbr;
-	unsigned int i;
 
 	if (!dev || dev->link.type != NI_IFTYPE_OVS_BRIDGE)
 		return -1;
 
 	ovsbr = ni_ovs_bridge_new();
 	if (ni_ovs_vsctl_bridge_to_parent(dev->name, &ovsbr->config.vlan.parent.name) ||
-	    ni_ovs_vsctl_bridge_to_vlan(dev->name, &ovsbr->config.vlan.tag) ||
-	    ni_ovs_vsctl_bridge_ports(dev->name, &ovsbr->ports)) {
+	    ni_ovs_vsctl_bridge_to_vlan(dev->name, &ovsbr->config.vlan.tag)) {
 		ni_ovs_bridge_free(ovsbr);
 		return -1;
 	}
 
-	if (nc) {
-		if (ovsbr->config.vlan.parent.name)
-			ni_netdev_ref_bind_ifindex(&ovsbr->config.vlan.parent, nc);
+	if (ovsbr->config.vlan.parent.name)
+		ni_netdev_ref_bind_ifindex(&ovsbr->config.vlan.parent, nc);
 
-		for (i = 0; i < ovsbr->ports.count;  ++i) {
-			ni_ovs_bridge_port_t *port = ovsbr->ports.data[i];
-			if (port->device.name)
-				ni_netdev_ref_bind_ifindex(&port->device, nc);
-		}
-	}
 	ni_netdev_set_ovs_bridge(dev, ovsbr);
 	return 0;
 }
 
+int
+ni_ovs_port_info_discover(ni_netdev_port_info_t *port, const char *name,
+		ni_netconfig_t *nc)
+{
+	char *bridge = NULL;
+	int rv;
+
+	if (!port || ni_string_empty(name))
+		return -1;
+
+	rv = ni_ovs_vsctl_bridge_port_to_bridge(name, &bridge);
+	if (rv == NI_PROCESS_SUCCESS && !ni_string_empty(bridge)) {
+
+		ni_netdev_port_info_data_destroy(port);
+		if (!ni_netdev_port_info_data_init(port, NI_IFTYPE_OVS_BRIDGE))
+			goto failure;
+
+		ni_string_move(&port->ovsbr->bridge.name, &bridge);
+
+		/* we may not be able to find bridge index while
+		 * initial interface fetch at start/bootstrap. */
+		ni_netdev_ref_bind_ifindex(&port->ovsbr->bridge, nc);
+		return 0;
+	}
+
+failure:
+	ni_string_free(&bridge);
+	return -1;
+}
+
+ni_ovs_bridge_port_info_t *
+ni_ovs_bridge_port_info_new(void)
+{
+	ni_ovs_bridge_port_info_t *info;
+
+	info = calloc(1, sizeof(*info));
+	return info;
+}
+
+void
+ni_ovs_bridge_port_info_free(ni_ovs_bridge_port_info_t *info)
+{
+	if (info) {
+		ni_netdev_ref_destroy(&info->bridge);
+		free(info);
+	}
+}
+
diff --git a/src/ovs.h b/src/ovs.h
index c7f37cc0e..62b7c63d5 100644
--- a/src/ovs.h
+++ b/src/ovs.h
@@ -1,7 +1,7 @@
 /*
  *	OVS (bridge) device ctl operations
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,13 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifndef NI_WICKED_OVS_CTL_H
 #define NI_WICKED_OVS_CTL_H
@@ -32,13 +27,15 @@ extern int	ni_ovs_vsctl_bridge_del(const char *);
 extern int	ni_ovs_vsctl_bridge_exists(const char *);
 extern int	ni_ovs_vsctl_bridge_to_vlan(const char *, uint16_t *);
 extern int	ni_ovs_vsctl_bridge_to_parent(const char *, char **);
-extern int	ni_ovs_vsctl_bridge_ports(const char *, ni_ovs_bridge_port_array_t *);
+extern int	ni_ovs_vsctl_bridge_ports(const char *, ni_netdev_ref_array_t *);
 
-extern int	ni_ovs_vsctl_bridge_port_add(const char *, const ni_ovs_bridge_port_config_t *,
-							ni_bool_t);
+extern int	ni_ovs_vsctl_bridge_port_add(const char *, const char *,
+				const ni_ovs_bridge_port_config_t *, ni_bool_t);
 extern int	ni_ovs_vsctl_bridge_port_del(const char *, const char *);
 extern int	ni_ovs_vsctl_bridge_port_to_bridge(const char *, char **);
 
 extern int	ni_ovs_bridge_discover(ni_netdev_t *, ni_netconfig_t *);
+extern int	ni_ovs_port_info_discover(ni_netdev_port_info_t *,
+				const char *, ni_netconfig_t *);
 
 #endif /* NI_WICKED_OVS_CTL_H */
diff --git a/src/sysconfig.c b/src/sysconfig.c
index b34979874..2aed349c8 100644
--- a/src/sysconfig.c
+++ b/src/sysconfig.c
@@ -30,22 +30,27 @@ ni_sysconfig_new(const char *pathname)
 {
 	ni_sysconfig_t *sc;
 
-	sc = calloc(1, sizeof(ni_sysconfig_t));
-	sc->pathname = xstrdup(pathname);
+	if (!(sc = calloc(1, sizeof(ni_sysconfig_t))))
+		return NULL;
 
-	return sc;
+	if (ni_string_dup(&sc->pathname, pathname))
+		return sc;
+
+	ni_sysconfig_free(sc);
+	return NULL;
 }
 
 /*
- * Destroy the sysconfig object
- * FIXME: should be called ni_sysconfig_free
+ * free the sysconfig object
  */
 void
-ni_sysconfig_destroy(ni_sysconfig_t *sc)
+ni_sysconfig_free(ni_sysconfig_t *sc)
 {
-	ni_var_array_destroy(&sc->vars);
-	ni_string_free(&sc->pathname);
-	free(sc);
+	if (sc) {
+		ni_var_array_destroy(&sc->vars);
+		ni_string_free(&sc->pathname);
+		free(sc);
+	}
 }
 
 /*
@@ -61,11 +66,16 @@ __ni_sysconfig_read(const char *filename, const char **varnames)
 
 	ni_debug_readwrite("ni_sysconfig_read(%s)", filename);
 	if (!(fp = fopen(filename, "r"))) {
-		ni_error("unable to open %s: %m", filename);
+		ni_error("unable to open sysconfig file '%s': %m", filename);
+		return NULL;
+	}
+
+	if (!(sc = ni_sysconfig_new(filename))) {
+		ni_error("unable to allocate sysconfig struct for '%s': %m", filename);
+		fclose(fp);
 		return NULL;
 	}
 
-	sc = ni_sysconfig_new(filename);
 	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
 		char *name, *value;
 		char *sp = linebuf;
@@ -362,40 +372,45 @@ quote(char *string)
 	return quoted;
 }
 
-void
+ni_bool_t
 ni_sysconfig_set(ni_sysconfig_t *sc, const char *name, const char *value)
 {
-	ni_var_array_set(&sc->vars, name, value);
+	return sc ? ni_var_array_set(&sc->vars, name, value) : FALSE;
 }
 
-void
+ni_bool_t
 ni_sysconfig_set_integer(ni_sysconfig_t *sc, const char *name, unsigned int value)
 {
 	char buffer[32];
 
 	snprintf(buffer, sizeof(buffer), "%u", value);
-	ni_sysconfig_set(sc, name, buffer);
+	return ni_sysconfig_set(sc, name, buffer);
 }
 
-void
+ni_bool_t
 ni_sysconfig_set_boolean(ni_sysconfig_t *sc, const char *name, int value)
 {
-	ni_sysconfig_set(sc, name, value? "yes" : "no");
+	return ni_sysconfig_set(sc, name, value ? "yes" : "no");
 }
 
 ni_var_t *
 ni_sysconfig_get(const ni_sysconfig_t *sc, const char *name)
 {
-	return ni_var_array_get(&sc->vars, name);
+	return sc ? ni_var_array_get(&sc->vars, name) : NULL;
 }
 
-int
+unsigned int
 ni_sysconfig_find_matching(const ni_sysconfig_t *sc, const char *prefix,
 		ni_string_array_t *res)
 {
-	unsigned int i, pfxlen;
+	unsigned int i, count;
+	size_t pfxlen;
 	ni_var_t *var;
 
+	if (!sc || !prefix || !res)
+		return 0;
+
+	count = res->count;
 	pfxlen = strlen(prefix);
 	for (i = 0, var = sc->vars.data; i < sc->vars.count; ++i, ++var) {
 		const char *value = var->value;
@@ -403,7 +418,7 @@ ni_sysconfig_find_matching(const ni_sysconfig_t *sc, const char *prefix,
 		if (value && *value && !strncmp(var->name, prefix, pfxlen))
 			ni_string_array_append(res, var->name);
 	}
-	return res->count;
+	return res->count - count;
 }
 
 const char *
diff --git a/src/sysfs.c b/src/sysfs.c
index 32c29ef6d..a5b5a57a0 100644
--- a/src/sysfs.c
+++ b/src/sysfs.c
@@ -517,14 +517,14 @@ ni_sysfs_bridge_get_port_names(const char *ifname, ni_string_array_t *names)
 }
 
 void
-ni_sysfs_bridge_port_get_config(const char *ifname, ni_bridge_port_t *port)
+ni_sysfs_bridge_port_get_config(const char *ifname, ni_bridge_port_config_t *port)
 {
 	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/priority", &port->priority);
 	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/path_cost", &port->path_cost);
 }
 
 int
-ni_sysfs_bridge_port_update_config(const char *ifname, const ni_bridge_port_t *port)
+ni_sysfs_bridge_port_update_config(const char *ifname, const ni_bridge_port_config_t *port)
 {
 	int rv = 0;
 
@@ -539,28 +539,6 @@ ni_sysfs_bridge_port_update_config(const char *ifname, const ni_bridge_port_t *p
 	return rv;
 }
 
-void
-ni_sysfs_bridge_port_get_status(const char *ifname, ni_bridge_port_status_t *ps)
-{
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/priority", &ps->priority);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/path_cost", &ps->path_cost);
-
-	ni_sysfs_netif_get_int(ifname, SYSFS_BRIDGE_PORT_ATTR "/state", &ps->state);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/port_no", &ps->port_no);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/port_id", &ps->port_no);
-	ni_sysfs_netif_get_string(ifname, SYSFS_BRIDGE_PORT_ATTR "/designated_root", &ps->designated_root);
-	ni_sysfs_netif_get_string(ifname, SYSFS_BRIDGE_PORT_ATTR "/designated_bridge", &ps->designated_bridge);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/designated_port", &ps->designated_port);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/designated_cost", &ps->designated_cost);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/change_ack", &ps->change_ack);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/hairpin_mode", &ps->hairpin_mode);
-	ni_sysfs_netif_get_uint(ifname, SYSFS_BRIDGE_PORT_ATTR "/config_pending", &ps->config_pending);
-
-	ni_sysfs_netif_get_ulong(ifname, SYSFS_BRIDGE_PORT_ATTR "/hold_timer", &ps->hold_timer);
-	ni_sysfs_netif_get_ulong(ifname, SYSFS_BRIDGE_PORT_ATTR "/message_age_timer", &ps->message_age_timer);
-	ni_sysfs_netif_get_ulong(ifname, SYSFS_BRIDGE_PORT_ATTR "/forward_delay_timer", &ps->forward_delay_timer);
-}
-
 /*
  * Get/set IPv4 sysctls
  */
diff --git a/src/sysfs.h b/src/sysfs.h
index a6457703d..f308b1c61 100644
--- a/src/sysfs.h
+++ b/src/sysfs.h
@@ -59,9 +59,8 @@ extern void	ni_sysfs_bridge_get_config(const char *, ni_bridge_t *);
 extern int	ni_sysfs_bridge_update_config(const char *, const ni_bridge_t *);
 extern void	ni_sysfs_bridge_get_status(const char *, ni_bridge_status_t *);
 extern int	ni_sysfs_bridge_get_port_names(const char *, ni_string_array_t *);
-extern void	ni_sysfs_bridge_port_get_config(const char *, ni_bridge_port_t *);
-extern int	ni_sysfs_bridge_port_update_config(const char *, const ni_bridge_port_t *);
-extern void	ni_sysfs_bridge_port_get_status(const char *, ni_bridge_port_status_t *);
+extern void	ni_sysfs_bridge_port_get_config(const char *, ni_bridge_port_config_t *);
+extern int	ni_sysfs_bridge_port_update_config(const char *, const ni_bridge_port_config_t *);
 extern ni_pci_dev_t *ni_sysfs_netdev_get_pci(const char *ifname);
 
 extern int	ni_sysctl_ipv6_ifconfig_is_present(const char *ifname);
diff --git a/src/team.c b/src/team.c
index 1451296cb..da3eec94e 100644
--- a/src/team.c
+++ b/src/team.c
@@ -1,7 +1,7 @@
 /*
- *	Team device support
+ *	Team network interface support
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,14 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -35,8 +29,6 @@
 #include "array_priv.h"
 
 #define NI_TEAM_LINK_WATCH_ARRAY_CHUNK		4
-#define NI_TEAM_PORT_ARRAY_CHUNK		4
-
 
 /*
  * Map teamd mode names to constants
@@ -223,6 +215,30 @@ ni_team_ab_hwaddr_policy_name_to_type(const char *name, ni_team_ab_hwaddr_policy
 	return TRUE;
 }
 
+void
+ni_team_runner_init(ni_team_runner_t *runner, ni_team_runner_type_t type)
+{
+	memset(runner, 0, sizeof(*runner));
+	runner->type = type;
+
+	/* apply non-zero type depending defaults here */
+	switch (runner->type) {
+	case NI_TEAM_RUNNER_ROUND_ROBIN:
+	case NI_TEAM_RUNNER_ACTIVE_BACKUP:
+	case NI_TEAM_RUNNER_LOAD_BALANCE:
+	case NI_TEAM_RUNNER_BROADCAST:
+	case NI_TEAM_RUNNER_RANDOM:
+	case NI_TEAM_RUNNER_LACP:
+	default:
+		break;
+	}
+}
+
+void
+ni_team_runner_destroy(ni_team_runner_t *runner)
+{
+}
+
 /*
  * Map teamd link watch names to constants
  */
@@ -346,61 +362,8 @@ extern ni_define_ptr_array_append(ni_team_link_watch);
 extern ni_define_ptr_array_delete_at(ni_team_link_watch);
 
 /*
- * team port
+ * team interface
  */
-ni_team_port_t *
-ni_team_port_new(void)
-{
-	ni_team_port_t *port;
-
-	port = xcalloc(1, sizeof(*port));
-	ni_team_port_config_init(&port->config);
-	return port;
-}
-
-void
-ni_team_port_free(ni_team_port_t *port)
-{
-	ni_netdev_ref_destroy(&port->device);
-	ni_team_port_config_destroy(&port->config);
-	free(port);
-}
-
-static ni_define_ptr_array_init(ni_team_port);
-extern ni_define_ptr_array_destroy(ni_team_port);
-static ni_define_ptr_array_realloc(ni_team_port, NI_TEAM_PORT_ARRAY_CHUNK);
-extern ni_define_ptr_array_append(ni_team_port);
-extern ni_define_ptr_array_delete_at(ni_team_port);
-
-extern ni_team_port_t *
-ni_team_port_array_find_by_name(ni_team_port_array_t *array, const char *name)
-{
-	unsigned int i;
-
-	if (!array || !name)
-		return NULL;
-
-	for (i = 0; i < array->count; ++i) {
-		ni_team_port_t *port = array->data[i];
-		if (ni_string_eq(name, port->device.name))
-			return port;
-	}
-	return NULL;
-}
-
-void
-ni_team_port_config_init(ni_team_port_config_t *pc)
-{
-	if (pc) {
-		pc->queue_id = -1U;
-	}
-}
-
-void
-ni_team_port_config_destroy(ni_team_port_config_t *pc)
-{
-}
-
 static void
 ni_team_init(ni_team_t *team)
 {
@@ -410,9 +373,6 @@ ni_team_init(ni_team_t *team)
 	team->mcast_rejoin.interval = -1U;
 }
 
-/*
- * team device
- */
 ni_team_t *
 ni_team_new(void)
 {
@@ -430,32 +390,102 @@ ni_team_free(ni_team_t *team)
 	if (team) {
 		ni_team_runner_destroy(&team->runner);
 		ni_team_link_watch_array_destroy(&team->link_watch);
-		ni_team_port_array_destroy(&team->ports);
 		free(team);
 	}
 }
 
+/*
+ * team port link-request configuration
+ */
+ni_bool_t
+ni_team_port_config_init(ni_team_port_config_t *conf)
+{
+	if (conf) {
+		memset(conf, 0, sizeof(*conf));
+		conf->queue_id = -1U;
+		return TRUE;
+	}
+	return FALSE;
+}
+
 void
-ni_team_runner_init(ni_team_runner_t *runner, ni_team_runner_type_t type)
+ni_team_port_config_destroy(ni_team_port_config_t *conf)
 {
-	memset(runner, 0, sizeof(*runner));
-	runner->type = type;
+	ni_team_port_config_init(conf);
+}
 
-	/* apply non-zero type depending defaults here */
+ni_team_port_config_t *
+ni_team_port_config_new(void)
+{
+	ni_team_port_config_t *conf;
+
+	conf = malloc(sizeof(*conf));
+	if (ni_team_port_config_init(conf))
+		return conf;
+
+	free(conf);
+	return NULL;
+}
+
+void
+ni_team_port_config_free(ni_team_port_config_t *conf)
+{
+	ni_team_port_config_destroy(conf);
+	free(conf);
+}
+
+/*
+ * team port interface info properties
+ */
+static ni_bool_t
+ni_team_port_info_init(ni_team_port_info_t *info)
+{
+	if (info) {
+		memset(info, 0, sizeof(*info));
+		/* apply "not set" defaults */
+		info->runner.type = -1U;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+ni_team_port_info_t *
+ni_team_port_info_new(void)
+{
+	ni_team_port_info_t *info;
+
+	info = malloc(sizeof(*info));
+	if (ni_team_port_info_init(info))
+		return info;
+
+	free(info);
+	return NULL;
+}
+
+static inline void
+ni_team_port_runner_lacp_info_free(ni_team_port_runner_lacp_info_t *lacp)
+{
+	ni_string_free(&lacp->state);
+}
+
+static inline void
+ni_team_port_runner_info_free(ni_team_port_runner_info_t *runner)
+{
 	switch (runner->type) {
-	case NI_TEAM_RUNNER_ROUND_ROBIN:
-	case NI_TEAM_RUNNER_ACTIVE_BACKUP:
-	case NI_TEAM_RUNNER_LOAD_BALANCE:
-	case NI_TEAM_RUNNER_BROADCAST:
-	case NI_TEAM_RUNNER_RANDOM:
 	case NI_TEAM_RUNNER_LACP:
+		ni_team_port_runner_lacp_info_free(&runner->lacp);
+		break;
 	default:
 		break;
 	}
 }
 
 void
-ni_team_runner_destroy(ni_team_runner_t *runner)
+ni_team_port_info_free(ni_team_port_info_t *info)
 {
+	if (info) {
+		ni_team_port_runner_info_free(&info->runner);
+		free(info);
+	}
 }
 
diff --git a/src/teamd.c b/src/teamd.c
index 0a5d0a0ed..25ed3d956 100644
--- a/src/teamd.c
+++ b/src/teamd.c
@@ -1,7 +1,7 @@
 /*
  *	Interfacing with teamd through dbus interface
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,14 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -69,6 +63,7 @@
 #define NI_TEAMD_CALL_PORT_ADD			"PortAdd"
 #define NI_TEAMD_CALL_PORT_REMOVE		"PortRemove"
 #define NI_TEAMD_CALL_PORT_CONFIG_UPDATE	"PortConfigUpdate"
+#define NI_TEAMD_CALL_PORT_CONFIG_DUMP		"PortConfigDump"
 
 
 typedef struct ni_teamd_client_ops {
@@ -80,6 +75,7 @@ typedef struct ni_teamd_client_ops {
 	int	(*ctl_port_add)(ni_teamd_client_t *, const char *);
 	int	(*ctl_port_remove)(ni_teamd_client_t *, const char *);
 	int	(*ctl_port_config_update)(ni_teamd_client_t *, const char *, const char *);
+	int	(*ctl_port_config_dump)(ni_teamd_client_t *, const char *, char **);
 } ni_teamd_client_ops_t;
 
 struct ni_teamd_client {
@@ -329,7 +325,7 @@ ni_teamd_dbus_ctl_port_config_update(ni_teamd_client_t *tdc, const char *port_na
 	int rv = 0;
 
 	if (ni_string_empty(port_name))
-		return FALSE;
+		return -NI_ERROR_INVALID_ARGS;
 
 	dbus_error_init(&error);
 	call = ni_dbus_object_call_new(tdc->proxy, NI_TEAMD_CALL_PORT_CONFIG_UPDATE, 0);
@@ -350,6 +346,27 @@ ni_teamd_dbus_ctl_port_config_update(ni_teamd_client_t *tdc, const char *port_na
 	return rv;
 }
 
+static int
+ni_teamd_dbus_ctl_port_config_dump(ni_teamd_client_t *tdc, const char *port_name, char **result)
+{
+	int rv = 0;
+
+	if (ni_string_empty(port_name))
+		return -NI_ERROR_INVALID_ARGS;
+
+	ni_string_free(result);
+	rv = ni_dbus_object_call_simple(tdc->proxy,
+		NI_TEAMD_INTERFACE, NI_TEAMD_CALL_PORT_CONFIG_DUMP,
+		DBUS_TYPE_STRING, &port_name,
+		DBUS_TYPE_STRING, result);
+
+	if (rv < 0) {
+		ni_debug_application("Call to %s."NI_TEAMD_CALL_PORT_CONFIG_DUMP"(%s) failed: %s",
+				ni_dbus_object_get_path(tdc->proxy), port_name, ni_strerror(rv));
+	}
+	return rv;
+}
+
 /*
  * === unix client ===
  */
@@ -408,7 +425,7 @@ ni_teamd_unix_ctl_config_dump(ni_teamd_client_t *tdc, ni_bool_t actual, char **r
 	int rv;
 
 	if (!result)
-		return -1;
+		return -NI_ERROR_INVALID_ARGS;
 
 	ni_buffer_init_dynamic(&buf, 1024);
 	if (!(pi = ni_process_new(tdc->cmd)))
@@ -421,7 +438,7 @@ ni_teamd_unix_ctl_config_dump(ni_teamd_client_t *tdc, ni_bool_t actual, char **r
 
 	rv = ni_process_run_and_capture_output(pi, &buf);
 	ni_process_free(pi);
-	if (rv) {
+	if (rv != NI_PROCESS_SUCCESS) {
 		ni_error("%s: unable to dump team config", tdc->instance);
 		goto failure;
 	}
@@ -456,7 +473,7 @@ ni_teamd_unix_ctl_port_add(ni_teamd_client_t *tdc, const char *port_name)
 
 	rv = ni_process_run_and_wait(pi);
 	ni_process_free(pi);
-	if (rv) {
+	if (rv != NI_PROCESS_SUCCESS) {
 		ni_error("%s: unable to add team port %s", tdc->instance, port_name);
 		return -1;
 	}
@@ -481,7 +498,7 @@ ni_teamd_unix_ctl_port_remove(ni_teamd_client_t *tdc, const char *port_name)
 
 	rv = ni_process_run_and_wait(pi);
 	ni_process_free(pi);
-	if (rv) {
+	if (rv != NI_PROCESS_SUCCESS) {
 		ni_error("%s: unable to remove team port %s", tdc->instance, port_name);
 		return -1;
 	}
@@ -508,13 +525,51 @@ ni_teamd_unix_ctl_port_config_update(ni_teamd_client_t *tdc, const char *port_na
 
 	rv = ni_process_run_and_wait(pi);
 	ni_process_free(pi);
-	if (rv) {
+	if (rv != NI_PROCESS_SUCCESS) {
 		ni_error("%s: unable to update team port %s config", tdc->instance, port_name);
 		return -1;
 	}
 	return 0;
 }
 
+int
+ni_teamd_unix_ctl_port_config_dump(ni_teamd_client_t *tdc, const char *port_name, char **result)
+{
+	ni_process_t *pi;
+	ni_buffer_t buf;
+	int rv;
+
+	if (!tdc || ni_string_empty(port_name) || !result)
+		return -1;
+
+	ni_buffer_init_dynamic(&buf, 1024);
+	if (!(pi = ni_process_new(tdc->cmd)))
+		goto failure;
+
+	ni_string_array_append(&pi->argv, "port");
+	ni_string_array_append(&pi->argv, "config");
+	ni_string_array_append(&pi->argv, "dump");
+	ni_string_array_append(&pi->argv, port_name);
+
+	rv = ni_process_run_and_capture_output(pi, &buf);
+	ni_process_free(pi);
+	if (rv != NI_PROCESS_SUCCESS) {
+		ni_error("%s: unable to update team port %s config", tdc->instance, port_name);
+		goto failure;
+	}
+
+	ni_buffer_put(&buf, "\0", 1);
+	ni_string_free(result);
+	*result = (char *)buf.base;
+	buf.base = NULL;
+	ni_buffer_destroy(&buf);
+	return 0;
+
+failure:
+	ni_buffer_destroy(&buf);
+	return -1;
+}
+
 /*
  *  === teamd client ===
  */
@@ -527,6 +582,7 @@ static const ni_teamd_client_ops_t	teamd_dbus_ops = {
 	.ctl_port_add		= ni_teamd_dbus_ctl_port_add,
 	.ctl_port_remove	= ni_teamd_dbus_ctl_port_remove,
 	.ctl_port_config_update	= ni_teamd_dbus_ctl_port_config_update,
+	.ctl_port_config_dump	= ni_teamd_dbus_ctl_port_config_dump,
 };
 
 static const ni_teamd_client_ops_t	teamd_unix_ops = {
@@ -535,6 +591,7 @@ static const ni_teamd_client_ops_t	teamd_unix_ops = {
 	.ctl_port_add		= ni_teamd_unix_ctl_port_add,
 	.ctl_port_remove	= ni_teamd_unix_ctl_port_remove,
 	.ctl_port_config_update	= ni_teamd_unix_ctl_port_config_update,
+	.ctl_port_config_dump	= ni_teamd_unix_ctl_port_config_dump,
 };
 
 ni_bool_t
@@ -714,6 +771,14 @@ ni_teamd_ctl_port_config_update(ni_teamd_client_t *tdc, const char *port_name, c
 	return tdc->ops.ctl_port_config_update(tdc, port_name, port_conf);
 }
 
+int
+ni_teamd_ctl_port_config_dump(ni_teamd_client_t *tdc, const char *port_name, char **result)
+{
+	if (!tdc || !tdc->ops.ctl_port_config_dump)
+		return -1;
+	return tdc->ops.ctl_port_config_dump(tdc, port_name, result);
+}
+
 static ni_json_t *
 ni_teamd_port_config_json(const ni_team_port_config_t *config)
 {
@@ -792,9 +857,70 @@ failure:
 	return ret;
 }
 
+/*
+ * teamd port config discovery
+ */
+static inline int
+ni_teamd_port_config_parse_json(ni_team_port_config_t *conf, ni_json_t *object)
+{
+	int64_t i64;
+	ni_bool_t b;
+
+	if (!conf)
+		return -NI_ERROR_INVALID_ARGS;
+
+	if (!ni_json_is_object(object))
+		return 1; /* no/empty/null config from teamd */
+
+	if (ni_json_int64_get(ni_json_object_get_value(object, "queue_id"), &i64))
+		conf->queue_id = i64;
+
+	if (ni_json_int64_get(ni_json_object_get_value(object, "prio"), &i64))
+		conf->ab.prio = i64;
+	if (ni_json_bool_get(ni_json_object_get_value(object, "sticky"), &b))
+		conf->ab.sticky = b;
+
+	if (ni_json_int64_get(ni_json_object_get_value(object, "lacp_prio"), &i64))
+		conf->lacp.prio = i64;
+	if (ni_json_int64_get(ni_json_object_get_value(object, "lacp_key"), &i64))
+		conf->lacp.key = i64;
+
+	return 0;
+}
+
+extern int
+ni_teamd_port_config_discover(ni_team_port_config_t *conf,
+		const char *team, const char *name)
+{
+	ni_teamd_client_t *tdc = NULL;
+	ni_json_t *json = NULL;
+	char *jstr = NULL;
+	int rv = -1;
+
+	if (!conf || ni_string_empty(team) || ni_string_empty(name))
+		return -NI_ERROR_INVALID_ARGS;
+
+	if (!(tdc = ni_teamd_client_open(team)))
+		goto cleanup;
+
+	if (ni_teamd_ctl_port_config_dump(tdc, name, &jstr) < 0)
+		goto cleanup;
+
+	if (!(json = ni_json_parse_string(jstr)))
+		goto cleanup;
+
+	rv = ni_teamd_port_config_parse_json(conf, json);
+
+cleanup:
+	ni_teamd_client_free(tdc);
+	ni_string_free(&jstr);
+	ni_json_free(json);
+	return rv;
+}
 
 /*
- * teamd discovery
+ * team interface config discovery
+ * note: it's config, not state
  */
 static int
 ni_teamd_discover_runner(ni_team_t *team, ni_json_t *conf)
@@ -1048,76 +1174,6 @@ failure:
 	return -1;
 }
 
-static int
-ni_teamd_discover_port_details(ni_team_port_t *port, ni_json_t *details)
-{
-	int64_t i64;
-	ni_bool_t b;
-
-	if (!ni_json_is_object(details))
-		return 1;
-
-	if (ni_json_int64_get(ni_json_object_get_value(details, "queue_id"), &i64))
-		port->config.queue_id = i64;
-
-	if (ni_json_int64_get(ni_json_object_get_value(details, "prio"), &i64))
-		port->config.ab.prio = i64;
-	if (ni_json_bool_get(ni_json_object_get_value(details, "sticky"), &b))
-		port->config.ab.sticky = b;
-
-	if (ni_json_int64_get(ni_json_object_get_value(details, "lacp_prio"), &i64))
-		port->config.lacp.prio = i64;
-	if (ni_json_int64_get(ni_json_object_get_value(details, "lacp_key"), &i64))
-		port->config.lacp.key = i64;
-
-	return 0;
-}
-
-static int
-ni_teamd_discover_ports(ni_team_t *team, ni_json_t *conf)
-{
-	ni_team_port_t *port;
-	ni_json_t *ports;
-	unsigned int i, count;
-
-	if (!team || !conf)
-		return -1;
-
-	if (!(ports = ni_json_object_get_value(conf, "ports")))
-		return 0;
-
-	if (!ni_json_is_object(ports))
-		return 1;
-
-	count = ni_json_object_entries(ports);
-	for (i = 0; i < count; ++i) {
-		ni_json_pair_t *pair;
-		ni_json_t *details;
-		const char *name;
-
-		if (!(pair = ni_json_object_get_pair_at(ports, i)))
-			continue;
-
-		name = ni_json_pair_get_name(pair);
-		if (ni_string_empty(name))
-			continue;
-		port = ni_team_port_new();
-		ni_netdev_ref_set_ifname(&port->device, name);
-
-		details = ni_json_pair_get_value(pair);
-		if (ni_teamd_discover_port_details(port, details) < 0) {
-			ni_team_port_free(port);
-			continue;
-		}
-
-		if (!ni_team_port_array_append(&team->ports, port)) {
-			ni_team_port_free(port);
-			continue;
-		}
-	}
-	return 0;
-}
-
 static void
 ni_teamd_discover_notify_peers(ni_team_t *team, ni_json_t *conf)
 {
@@ -1190,9 +1246,6 @@ ni_teamd_discover(ni_netdev_t *dev)
 	if (ni_teamd_discover_link_watch(team, conf) < 0)
 		goto failure;
 
-	if (ni_teamd_discover_ports(team, conf) < 0)
-		goto failure;
-
 	ni_netdev_set_team(dev, team);
 	ni_teamd_client_free(tdc);
 	ni_json_free(conf);
@@ -1207,6 +1260,155 @@ failure:
 	return -1;
 }
 
+/*
+ * Discover port info from teamd state
+ *
+ * The teamdctl (dbus, unix) API does not provide separate
+ * PortStateDump (port state dump), but only a "StateDump"
+ * (state dump) containing both, teamd interface state and
+ * state for all ports.
+ *
+ * Currently, we discover only the runner and the overall
+ * link watches up/down summary state.
+ */
+static ni_bool_t
+ni_teamd_state_get_runner_type(ni_json_t *state, ni_team_runner_type_t *type)
+{
+	ni_json_t *setup = ni_json_object_get_value(state, "setup");
+	ni_json_t *runner = ni_json_object_get_value(setup, "runner_name");
+	char *name = NULL;
+	ni_bool_t ret;
+
+	ni_json_string_get(runner, &name);
+	ret = ni_team_runner_name_to_type(name, type);
+	ni_string_free(&name);
+	return ret;
+}
+
+static ni_json_t *
+ni_teamd_state_get_port_info(ni_json_t *state, const char *name)
+{
+	ni_json_t *ports = ni_json_object_get_value(state, "ports");
+
+	return ni_json_object_get_value(ports, name);
+}
+
+static int
+ni_teamd_state_port_runner_lacp_discover(ni_team_port_info_t *info, ni_json_t *runner)
+{
+	ni_team_port_runner_lacp_info_t *lacp = &info->runner.lacp;
+	ni_json_t *aggregator;
+	int64_t i64;
+
+	/*
+	 * See "3.7 Configuring LACP for 802.3ad mode in a more secure way"
+	 * described in linux-<version>/Documentation/networking/bonding.rst.
+	 * Take care to not expose lacp system mac,key,prio "secrets", which
+	 * may be chosen randomly or administratively.
+	 * While a teamdctl "state dump" command contains all (json) details,
+	 * the "state view" command shows aggregator id,selected and state
+	 * info only, and omits the key and actor/partner lacpdu info too.
+	 */
+
+	/*
+	 * The aggregator id is an uint16_t (in bond rtnetlink), just json
+	 * has only one number type (parsed by _our_ json API to either an
+	 * int64_t or double C type).
+	 */
+	aggregator = ni_json_object_get_value(runner, "aggregator");
+	if (ni_json_int64_get(ni_json_object_get_value(aggregator, "id"), &i64))
+		lacp->aggregator.id = i64 >= 0 ? i64 : 0;
+
+	ni_json_bool_get(ni_json_object_get_value(runner, "selected"), &lacp->selected);
+	ni_json_string_get(ni_json_object_get_value(runner, "state"), &lacp->state);
+
+	return 0;
+}
+
+static int
+ni_teamd_state_port_runner_discover(ni_team_port_info_t *info, ni_json_t *port)
+{
+	ni_json_t *runner;
+
+	switch (info->runner.type) {
+	case NI_TEAM_RUNNER_LACP:
+		runner = ni_json_object_get_value(port, "runner");
+		ni_teamd_state_port_runner_lacp_discover(info, runner);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int
+ni_teamd_state_port_watches_discover(ni_team_port_info_t *info, ni_json_t *port)
+{
+	ni_json_t *watches;
+
+	watches = ni_json_object_get_value(port, "link_watches");
+
+	/*
+	 * Currently, we don't show link_watch instance info details,
+	 * but just their summary / resulting "up" state only.
+	 */
+	ni_json_bool_get(ni_json_object_get_value(watches, "up"), &info->watches.up);
+
+	return 0;
+}
+
+static int
+ni_teamd_state_port_info_discover(ni_team_port_info_t *info, const char *name, ni_json_t *state)
+{
+	ni_json_t *port;
+
+	if ((port = ni_teamd_state_get_port_info(state, name))) {
+		if (ni_teamd_state_get_runner_type(state, &info->runner.type))
+			ni_teamd_state_port_runner_discover(info, port);
+		ni_teamd_state_port_watches_discover(info, port);
+	}
+	return 0;
+}
+
+extern int
+ni_teamd_port_info_discover(ni_netdev_port_info_t *info, const char *team, const char *name)
+{
+	ni_teamd_client_t *tdc = NULL;
+	ni_json_t *state = NULL;
+	char *jstr = NULL;
+
+	if (!info || ni_string_empty(team) || ni_string_empty(name))
+		return -1;
+
+	if (info->type != NI_IFTYPE_TEAM || !info->team)
+		return -1;
+
+	if (!(tdc = ni_teamd_client_open(team)))
+		goto failure;
+
+	if (ni_teamd_ctl_state_dump(tdc, &jstr) < 0)
+		goto failure;
+
+	if (!(state = ni_json_parse_string(jstr)))
+		goto failure;
+
+	ni_string_free(&jstr);
+
+	if (ni_teamd_state_port_info_discover(info->team, name, state) < 0)
+		goto failure;
+
+	ni_json_free(state);
+	ni_teamd_client_free(tdc);
+	return 0;
+
+failure:
+	ni_json_free(state);
+	ni_string_free(&jstr);
+	ni_teamd_client_free(tdc);
+	return -1;
+}
+
 /*
  * teamd startup config file
  */
diff --git a/src/teamd.h b/src/teamd.h
index e163e2ac7..56ab95e51 100644
--- a/src/teamd.h
+++ b/src/teamd.h
@@ -1,7 +1,7 @@
 /*
  *	Interfacing with teamd through dbus interface
  *
- *	Copyright (C) 2015 SUSE Linux GmbH, Nuernberg, Germany.
+ *	Copyright (C) 2015-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,14 +13,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- *	Boston, MA 02110-1301 USA.
- *
- *	Authors:
- *		Pawel Wieczorkiewicz <pwieczorkiewicz@suse.de>
- *		Marius Tomaschewski <mt@suse.de>
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifndef NI_TEAMD_CLIENT_H
 #define NI_TEAMD_CLIENT_H
@@ -41,11 +35,17 @@ extern int				ni_teamd_ctl_state_set_item(ni_teamd_client_t *, const char *,
 extern int				ni_teamd_ctl_port_add(ni_teamd_client_t *, const char *);
 extern int				ni_teamd_ctl_port_remove(ni_teamd_client_t *, const char *);
 extern int				ni_teamd_ctl_port_config_update(ni_teamd_client_t *, const char *, const char *);
+extern int				ni_teamd_ctl_port_config_dump(ni_teamd_client_t *, const char *, char **);
 
 extern int				ni_teamd_port_enslave(const ni_netdev_t *, const ni_netdev_t *, const ni_team_port_config_t *);
 extern int				ni_teamd_port_unenslave(const ni_netdev_t *, const ni_netdev_t *);
 
+extern int				ni_teamd_port_config_discover(ni_team_port_config_t *,
+								const char *, const char *);
+
 extern int				ni_teamd_discover(ni_netdev_t *);
+extern int				ni_teamd_port_info_discover(ni_netdev_port_info_t *,
+								const char *, const char *);
 
 extern int				ni_teamd_service_start(const ni_netdev_t *);
 extern int				ni_teamd_service_stop (const char *);
diff --git a/src/util.c b/src/util.c
index 50dfd97cf..1ae6b4f31 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1552,6 +1552,20 @@ ni_string_set(char **pp, const char *value, size_t len)
 	return TRUE;
 }
 
+ni_bool_t
+ni_string_move(char **dst, char **src)
+{
+	if (!src || !dst)
+		return FALSE;
+
+	if (*dst)
+		free(*dst);
+	*dst = *src;
+	*src = NULL;
+
+	return TRUE;
+}
+
 const char *
 ni_string_printf(char **str, const char *fmt, ...)
 {
diff --git a/src/xml.c b/src/xml.c
index 59c31924b..4a5d20e78 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -1,7 +1,8 @@
 /*
  *	XML objects - document and node
  *
- *	Copyright (C) 2009-2012  Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2012 Olaf Kirch <okir@suse.de>
+ *	Copyright (C) 2009-2023 SUSE LLC
  *
  *	This program is free software; you can redistribute it and/or modify
  *	it under the terms of the GNU General Public License as published by
@@ -13,11 +14,8 @@
  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *	GNU General Public License for more details.
  *
- *	You should have received a copy of the GNU General Public License along
- *	with this program; if not, see <http://www.gnu.org/licenses/> or write 
- *	to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 
- *	Boston, MA 02110-1301 USA.
- *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -25,11 +23,12 @@
 
 #include <wicked/xml.h>
 #include <wicked/logging.h>
+#include "array_priv.h"
 #include "util_priv.h"
 #include <inttypes.h>
 
-#define XML_DOCUMENTARRAY_CHUNK		1
-#define XML_NODEARRAY_CHUNK		8
+#define XML_DOCUMENT_ARRAY_CHUNK	8
+#define XML_NODE_ARRAY_CHUNK		8
 
 xml_document_t *
 xml_document_new()
@@ -48,7 +47,7 @@ xml_document_root(xml_document_t *doc)
 }
 
 const char *
-xml_document_type(const xml_document_t *doc)
+xml_document_dtd(const xml_document_t *doc)
 {
 	return doc ? doc->dtd : NULL;
 }
@@ -84,6 +83,63 @@ xml_document_free(xml_document_t *doc)
 	}
 }
 
+xml_document_t *
+xml_document_create(const char *dtd, xml_node_t *root)
+{
+	xml_document_t *doc;
+
+	if ((doc = calloc(1, sizeof(*doc)))) {
+		if (ni_string_dup(&doc->dtd, dtd)) {
+			doc->root = root;
+			return doc;
+		}
+		xml_document_free(doc);
+	}
+	return NULL;
+}
+
+ni_bool_t
+xml_document_expand(xml_document_array_t *docs, xml_document_t *raw)
+{
+	ni_bool_t ret = TRUE;
+	xml_document_t *doc;
+	xml_node_t *node;
+	xml_node_t *next;
+
+	if (!docs || !raw || !raw->root)
+		return FALSE;
+
+	/*
+	 * Append if "raw" contains single named document root.
+	 */
+	if (!ni_string_empty(raw->root->name))
+		return xml_document_array_append(docs, raw);
+
+	/*
+	 * Otherwise, expand the just parsed data into multiple
+	 * "normalized" documents with one named root node each.
+	 */
+	for (node = raw->root->children; node; node = next) {
+		next = node->next;
+
+		if (ni_string_empty(node->name))
+			continue;
+
+		xml_node_detach(node);
+
+		if (!(doc = xml_document_create(raw->dtd, node)))
+			xml_node_free(node);
+		else if (!xml_document_array_append(docs, doc))
+			xml_document_free(doc);
+		else continue;
+
+		ret = FALSE;
+	}
+	if (ret)
+		xml_document_free(raw);
+	return ret;
+}
+
 /*
  * Helper functions for xml node list management
  */
@@ -308,79 +364,65 @@ xml_node_free(xml_node_t *node)
 	free(node);
 }
 
-void
+ni_bool_t
 xml_node_set_cdata(xml_node_t *node, const char *cdata)
 {
-	ni_string_dup(&node->cdata, cdata);
+	return ni_string_dup(&node->cdata, cdata);
 }
 
-void
+ni_bool_t
 xml_node_set_int(xml_node_t *node, int value)
 {
-	char buffer[32];
 
-	snprintf(buffer, sizeof(buffer), "%d", value);
-	ni_string_dup(&node->cdata, buffer);
+	return node && ni_string_printf(&node->cdata, "%d", value);
 }
 
-void
+ni_bool_t
 xml_node_set_int64(xml_node_t *node, int64_t value)
 {
-	char buffer[32];
-
-	snprintf(buffer, sizeof(buffer), "%"PRId64, value);
-	ni_string_dup(&node->cdata, buffer);
+	return node && ni_string_printf(&node->cdata, "%"PRId64, value);
 }
 
-void
+ni_bool_t
 xml_node_set_uint(xml_node_t *node, unsigned int value)
 {
-	char buffer[32];
-
-	snprintf(buffer, sizeof(buffer), "%u", value);
-	ni_string_dup(&node->cdata, buffer);
+	return node && ni_string_printf(&node->cdata, "%u", value);
 }
 
-void
+ni_bool_t
 xml_node_set_uint64(xml_node_t *node, uint64_t value)
 {
-	char buffer[32];
-
-	snprintf(buffer, sizeof(buffer), "%"PRIu64, value);
-	ni_string_dup(&node->cdata, buffer);
+	return node && ni_string_printf(&node->cdata, "%"PRIu64, value);
 }
 
-void
+ni_bool_t
 xml_node_set_uint_hex(xml_node_t *node, unsigned int value)
 {
-	char buffer[32];
-
-	snprintf(buffer, sizeof(buffer), "0x%x", value);
-	ni_string_dup(&node->cdata, buffer);
+	return node && ni_string_printf(&node->cdata, "0x%x", value);
 }
 
-void
+ni_bool_t
 xml_node_add_attr(xml_node_t *node, const char *name, const char *value)
 {
-	ni_var_array_set(&node->attrs, name, value);
+	return ni_var_array_set(&node->attrs, name, value);
 }
 
-void
+ni_bool_t
 xml_node_add_attr_uint(xml_node_t *node, const char *name, unsigned int value)
 {
-	ni_var_array_set_uint(&node->attrs, name, value);
+	return ni_var_array_set_uint(&node->attrs, name, value);
 }
 
-void
+ni_bool_t
 xml_node_add_attr_ulong(xml_node_t *node, const char *name, unsigned long value)
 {
-	ni_var_array_set_ulong(&node->attrs, name, value);
+	return ni_var_array_set_ulong(&node->attrs, name, value);
 }
 
-void
+ni_bool_t
 xml_node_add_attr_double(xml_node_t *node, const char *name, double value)
 {
-	ni_var_array_set_double(&node->attrs, name, value);
+	return ni_var_array_set_double(&node->attrs, name, value);
 }
 
 const ni_var_t *
@@ -471,12 +513,22 @@ xml_node_get_next_child(const xml_node_t *top, const char *name, const xml_node_
 	return NULL;
 }
 
-inline xml_node_t *
+xml_node_t *
 xml_node_get_child(const xml_node_t *node, const char *name)
 {
 	return xml_node_get_next_child(node, name, NULL);
 }
 
+const char *
+xml_node_get_child_cdata(const xml_node_t *node, const char *name)
+{
+	const xml_node_t *child;
+
+	if ((child = xml_node_get_child(node, name)))
+		return child->cdata;
+	return NULL;
+}
+
 /*
  * Find a child element given its name and a list of attributes
  */
@@ -720,85 +772,20 @@ xml_node_match_attrs(const xml_node_t *node, const ni_var_array_t *attrlist)
 /*
  * XML document arrays
  */
-void
-xml_document_array_init(xml_document_array_t *array)
-{
-	memset(array, 0, sizeof(*array));
-}
-
-void
-xml_document_array_destroy(xml_document_array_t *array)
-{
-	unsigned int i;
-
-	for (i = 0; i < array->count; ++i)
-		xml_document_free(array->data[i]);
-
-	if (array->data)
-		free(array->data);
-	memset(array, 0, sizeof(*array));
-}
-
-xml_document_array_t *
-xml_document_array_new(void)
-{
-	xml_document_array_t *array;
-
-	array = xcalloc(1, sizeof(*array));
-	return array;
-}
-
-void
-xml_document_array_free(xml_document_array_t *array)
-{
-	xml_document_array_destroy(array);
-	free(array);
-}
-
-static void
-__xml_document_array_realloc(xml_document_array_t *array, unsigned int newsize)
-{
-	xml_document_t **newdata;
-	unsigned int i;
-
-	newsize = (newsize + XML_DOCUMENTARRAY_CHUNK) + 1;
-	newdata = xrealloc(array->data, newsize * sizeof(xml_document_t *));
-
-	array->data = newdata;
-	for (i = array->count; i < newsize; ++i)
-		array->data[i] = NULL;
-}
-
-void
-xml_document_array_append(xml_document_array_t *array, xml_document_t *doc)
-{
-	if ((array->count % XML_DOCUMENTARRAY_CHUNK) == 0)
-		__xml_document_array_realloc(array, array->count);
-
-	array->data[array->count++] = doc;
-}
+extern ni_define_ptr_array_init(xml_document);
+extern ni_define_ptr_array_destroy(xml_document);
+static ni_define_ptr_array_realloc(xml_document, XML_DOCUMENT_ARRAY_CHUNK);
+extern ni_define_ptr_array_append(xml_document);
+extern ni_define_ptr_array_insert(xml_document);
+extern ni_define_ptr_array_index(xml_document);
 
 /*
  * XML node arrays
  */
-void
-xml_node_array_init(xml_node_array_t *array)
-{
-	memset(array, 0, sizeof(*array));
-}
-
-void
-xml_node_array_destroy(xml_node_array_t *array)
-{
-	unsigned int i;
-
-	for (i = 0; i < array->count; ++i)
-		xml_node_free(array->data[i]);
-
-	if (array->data)
-		free(array->data);
-	memset(array, 0, sizeof(*array));
-}
+extern ni_define_ptr_array_init(xml_node);
+extern ni_define_ptr_array_destroy(xml_node);
+static ni_define_ptr_array_realloc(xml_node, XML_NODE_ARRAY_CHUNK)
+extern ni_define_ptr_array_append(xml_node);
 
 xml_node_array_t *
 xml_node_array_new(void)
@@ -816,32 +803,6 @@ xml_node_array_free(xml_node_array_t *array)
 	free(array);
 }
 
-static void
-__xml_node_array_realloc(xml_node_array_t *array, unsigned int newsize)
-{
-	xml_node_t **newdata;
-	unsigned int i;
-
-	newsize = (newsize + XML_NODEARRAY_CHUNK) + 1;
-	newdata = xrealloc(array->data, newsize * sizeof(array->data[0]));
-
-	array->data = newdata;
-	for (i = array->count; i < newsize; ++i)
-		array->data[i] = NULL;
-}
-
-void
-xml_node_array_append(xml_node_array_t *array, xml_node_t *node)
-{
-	if (!array || !node)
-		return;
-
-	if ((array->count % XML_NODEARRAY_CHUNK) == 0)
-		__xml_node_array_realloc(array, array->count);
-
-	array->data[array->count++] = xml_node_clone_ref(node);
-}
-
 xml_node_t *
 xml_node_create(xml_node_t *parent, const char *name)
 {
diff --git a/testing/teamd-test.c b/testing/teamd-test.c
index d7e1dda55..3c5d7f78c 100644
--- a/testing/teamd-test.c
+++ b/testing/teamd-test.c
@@ -45,6 +45,8 @@ int main(int argc, char **argv)
 		rv = ni_teamd_ctl_config_dump(tdc, FALSE, &val);
 	else if (ni_string_eq(command, "config-dump-actual"))
 		rv = ni_teamd_ctl_config_dump(tdc, TRUE, &val);
+	else if (ni_string_eq(command, "port-config-dump"))
+		rv = ni_teamd_ctl_port_config_dump(tdc, param1, &val);
 
 	printf("%s\n", val ? val : ni_format_boolean(!!rv));
 
diff --git a/util/mkconst.c b/util/mkconst.c
index a5bedae16..be57481ce 100644
--- a/util/mkconst.c
+++ b/util/mkconst.c
@@ -22,6 +22,7 @@
 #include <wicked/logging.h>
 #include <wicked/wireless.h>
 #include <wicked/bonding.h>
+#include <wicked/bridge.h>
 #include <wicked/team.h>
 #include <wicked/route.h>
 #include <wicked/infiniband.h>
@@ -78,8 +79,9 @@ static struct generic_map	generic_maps[] = {
 	MAP(BONDING_AD_SELECT, ni_bonding_ad_select_name),
 	MAP(BONDING_FAIL_OVER_MAC, ni_bonding_fail_over_mac_name),
 	MAP(BONDING_PRIMARY_RESELECT, ni_bonding_primary_reselect_name),
-	MAP(BONDING_SLAVE_STATE, ni_bonding_slave_state_name),
-	MAP(BONDING_SLAVE_MII_STATUS, ni_bonding_slave_mii_status_name),
+	MAP(BONDING_PORT_STATE, ni_bonding_port_state_name),
+	MAP(BONDING_PORT_MII_STATUS, ni_bonding_port_mii_status_name),
+	MAP(BRIDGE_PORT_STATE, ni_bridge_port_state_name),
 	MAP(ROUTE_TYPE, ni_route_type_type_to_name),
 	MAP(ROUTE_PROTOCOL, ni_route_protocol_type_to_name),
 	MAPN(ROUTE_SCOPE, ni_route_scope_type_to_name, 256),
-- 
2.35.3

