One of the top questions by libvirt users is how to create a host
bridge device so that guests can be directly on the physical
network. There are several example documents that explain how to do
this manually, but following them often results in confusion and
failure. virt-manager does a good job of creating a bridge based on an
existing network device, but not everyone wants to use virt-manager.
This patch adds a new command, iface-bridge that makes it just about
as simple as possible to create a new bridge device based on an
existing ethernet/vlan/bond device (including associating IP
configuration with the bridge rather than the now-attached device),
and start that new bridge up ready for action, eg:
virsh iface-bridge eth0 br0
For symmetry's sake, it also adds a command to remove a device from a
bridge, restoring the IP config to the now-unattached device:
virsh iface-unbridge br0
(I had a short debate about whether to do "iface-unbridge eth0"
instead, but that would involve searching through all bridge devices
for the one that contained eth0, which seems like a bit too much
trouble).
NOTE: These two commands require that the netcf library be available
on the host. Hopefully this will provide some extra incentive for
people using suse, debian, ubuntu, and other similar systems to polish
up (and push downstream) the ports to those distros recently pushed to
the upstream netcf repo by Dan Berrange. Anyone interested in helping
with that effort in any way should join the netcf-devel mailing list
(subscription info at
https://fedorahosted.org/mailman/listinfo/netcf-devel)
During creation of the bridge, it's possible to specify whether or not
the STP protocol should be started up on the bridge and, if so, how
many seconds the bridge should squelch traffic from newly added
devices while learning new topology (defaults are stp='on' and
delay='0', which seems to usually work best for bridges used in the
context of libvirt guests).
There is also an option to not immediately start the bridge (and a
similar option to not immediately start the un-attached device after
dfestroying the bridge. Default is to start the new device, because in
the case of iface-unbridge not starting is strongly discouraged as it
will leave the system with no network connectivity on that interface
(because it's necessary to destroy/undefine the bridge device before
the unattached device can be defined), and it seemed better to make
the option for iface-bridge behave consistently.
Aside from adding the code for these two functions, and the two
entries into the command table, the only other change to virsh.c was
to add the option name to vshCommandOptInterfaceBy(), because the
iface-unbridge command names its interface option as "bridge".
After being hounded by Eric to always put documentation in with any
new code changes, this patch seems a bit naked without some html bits
describing it :-), but it looks like docs/virshcmdref.html.in has been
deprecated in favor of the version in the separate repo at
http://libvirt.org/git/?p=libvirt-virshcmdref.git.
---
tools/virsh.c | 430 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 425 insertions(+), 5 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c
index af80e4b..ddfd341 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -330,12 +330,14 @@ static virNWFilterPtr vshCommandOptNWFilterBy(vshControl *ctl, const
vshCmd *cmd
VSH_BYUUID|VSH_BYNAME)
static virInterfacePtr vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd,
+ const char *optname,
+
const char **name, int flag);
/* default is lookup by Name and MAC */
#define vshCommandOptInterface(_ctl, _cmd, _name) \
- vshCommandOptInterfaceBy(_ctl, _cmd, _name, \
- VSH_BYMAC|VSH_BYNAME)
+ vshCommandOptInterfaceBy(_ctl, _cmd, NULL, _name, \
+ VSH_BYMAC|VSH_BYNAME)
static virStoragePoolPtr vshCommandOptPoolBy(vshControl *ctl, const vshCmd *cmd,
const char *optname, const char **name, int flag);
@@ -6807,7 +6809,7 @@ cmdInterfaceName(vshControl *ctl, const vshCmd *cmd)
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
- if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL,
+ if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
VSH_BYMAC)))
return false;
@@ -6837,7 +6839,7 @@ cmdInterfaceMAC(vshControl *ctl, const vshCmd *cmd)
if (!vshConnectionUsability(ctl, ctl->conn))
return false;
- if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL,
+ if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
VSH_BYNAME)))
return false;
@@ -7137,6 +7139,417 @@ cmdInterfaceRollback(vshControl *ctl, const vshCmd *cmd
ATTRIBUTE_UNUSED)
}
/*
+ * "iface-bridge" command
+ */
+static const vshCmdInfo info_interface_bridge[] = {
+ {"help", N_("create a bridge device and attach an existing network
device to it")},
+ {"desc", N_("bridge an existing network device")},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_interface_bridge[] = {
+ {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("existing interface
name")},
+ {"bridge", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new bridge device
name")},
+ {"no-stp", VSH_OT_BOOL, 0, N_("do not enable STP for this
bridge")},
+ {"delay", VSH_OT_INT, 0, N_("number of seconds to squelch traffic on
newly connected ports")},
+ {"no-start", VSH_OT_BOOL, 0, N_("don't start the bridge
immediately")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdInterfaceBridge(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ bool ret = false;
+ virInterfacePtr if_handle = NULL, br_handle = NULL;
+ const char *if_name, *br_name;
+ char *if_type = NULL, *if2_name = NULL, *delay_str = NULL;
+ bool stp = false, nostart = false;
+ unsigned int delay = 0;
+ char *if_xml = NULL;
+ xmlChar *br_xml = NULL;
+ int br_xml_size;
+ xmlDocPtr xml_doc = NULL;
+ xmlXPathContextPtr ctxt = NULL;
+ xmlNodePtr top_node, br_node, if_node, cur;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ goto cleanup;
+
+ /* Get a handle to the original device */
+ if (!(if_handle = vshCommandOptInterfaceBy(ctl, cmd, "interface",
+ &if_name, VSH_BYNAME))) {
+ goto cleanup;
+ }
+
+ /* Name for new bridge device */
+ if (vshCommandOptString(cmd, "bridge", &br_name) <= 0) {
+ vshError(ctl, "%s", _("Missing bridge device name in
command"));
+ goto cleanup;
+ }
+
+ /* make sure "new" device doesn't already exist */
+ if ((br_handle = virInterfaceLookupByName(ctl->conn, br_name))) {
+ vshError(ctl, _("Network device %s already exists"), br_name);
+ goto cleanup;
+ }
+
+ /* use "no-stp" because we want "stp" to default true */
+ stp = !vshCommandOptBool(cmd, "no-stp");
+
+ if (vshCommandOptUInt(cmd, "delay", &delay) < 0) {
+ vshError(ctl, "%s", _("Unable to parse delay parameter"));
+ goto cleanup;
+ }
+
+ nostart = vshCommandOptBool(cmd, "no-start");
+
+ /* Get the original interface into an xmlDoc */
+ if (!(if_xml = virInterfaceGetXMLDesc(if_handle, VIR_INTERFACE_XML_INACTIVE)))
+ goto cleanup;
+ if (!(xml_doc = virXMLParseStringCtxt(if_xml,
+ _("(interface definition)"),
&ctxt))) {
+ vshError(ctl, _("Failed to parse configuration of %s"), if_name);
+ goto cleanup;
+ }
+ top_node = ctxt->node;
+
+ /* Verify that the original device isn't already a bridge. */
+ if (!(if_type = virXMLPropString(top_node, "type"))) {
+ vshError(ctl, _("Existing device %s has no type"), if_name);
+ goto cleanup;
+ }
+
+ if (STREQ(if_type, "bridge")) {
+ vshError(ctl, _("Existing device %s is already a bridge"), if_name);
+ goto cleanup;
+ }
+
+ /* verify the name in the XML matches the device name */
+ if (!(if2_name = virXMLPropString(top_node, "name")) ||
+ STRNEQ(if2_name, if_name)) {
+ vshError(ctl, _("Interface name from config %s doesn't match given
supplied name %s"),
+ if2_name, if_name);
+ goto cleanup;
+ }
+
+ /* Create a <bridge> node under <interface>. */
+ if (!(br_node = xmlNewChild(top_node, NULL, BAD_CAST "bridge", NULL))) {
+ vshError(ctl, "%s", _("Failed to create bridge node in xml
document"));
+ goto cleanup;
+ }
+
+ /* Set stp and delay attributes in <bridge> according to the
+ * commandline options.
+ */
+ if (!xmlSetProp(br_node, BAD_CAST "stp", BAD_CAST (stp ? "on" :
"off"))) {
+ vshError(ctl, "%s", _("Failed to set stp attribute in xml
document"));
+ goto cleanup;
+ }
+
+ if ((delay || stp) &&
+ ((virAsprintf(&delay_str, "%d", delay) < 0) ||
+ !xmlSetProp(br_node, BAD_CAST "delay", BAD_CAST delay_str))) {
+ vshError(ctl, _("Failed to set bridge delay %d in xml document"),
delay);
+ goto cleanup;
+ }
+
+ /* Change the type of the outer/master interface to "bridge" and the
+ * name to the provided bridge name.
+ */
+ if (!xmlSetProp(top_node, BAD_CAST "type", BAD_CAST "bridge")) {
+ vshError(ctl, "%s", _("Failed to set bridge interface type to
'bridge' in xml document"));
+ goto cleanup;
+ }
+
+ if (!xmlSetProp(top_node, BAD_CAST "name", BAD_CAST br_name)) {
+ vshError(ctl, _("Failed to set master bridge interface name to '%s'
in xml document"),
+ br_name);
+ goto cleanup;
+ }
+
+ /* Create an <interface> node under <bridge> that uses the
+ * original interface's type and name.
+ */
+ if (!(if_node = xmlNewChild(br_node, NULL, BAD_CAST "interface", NULL))) {
+ vshError(ctl, "%s", _("Failed to create interface node under
bridge node in xml document"));
+ goto cleanup;
+ }
+
+ /* set the type of the inner/slave interface to the original
+ * if_type, and the name to the original if_name.
+ */
+ if (!xmlSetProp(if_node, BAD_CAST "type", BAD_CAST if_type)) {
+ vshError(ctl, _("Failed to set new slave interface type to '%s' in
xml document"),
+ if_name);
+ goto cleanup;
+ }
+
+ if (!xmlSetProp(if_node, BAD_CAST "name", BAD_CAST if_name)) {
+ vshError(ctl, _("Failed to set new slave interface name to '%s' in
xml document"),
+ br_name);
+ goto cleanup;
+ }
+
+ /* Cycle through all the nodes under the original <interface>,
+ * moving all <mac>, <bond> and <vlan> nodes down into the new
+ * lower level <interface>.
+ */
+ cur = top_node->children;
+ while (cur) {
+ xmlNodePtr old = cur;
+
+ cur = cur->next;
+ if ((old->type == XML_ELEMENT_NODE) &&
+ (xmlStrEqual(old->name, BAD_CAST "mac") || /* ethernet stuff to
move down */
+ xmlStrEqual(old->name, BAD_CAST "bond") || /* bond stuff to
move down */
+ xmlStrEqual(old->name, BAD_CAST "vlan"))) { /* vlan stuff to
move down */
+ xmlUnlinkNode(old);
+ if (!xmlAddChild(if_node, old)) {
+ vshError(ctl, _("Failed to move '%s' element in xml
document"), old->name);
+ xmlFreeNode(old);
+ goto cleanup;
+ }
+ }
+ }
+
+ /* The document should now be fully converted; write it out to a string. */
+ xmlDocDumpMemory(xml_doc, &br_xml, &br_xml_size);
+
+ if (!br_xml || br_xml_size <= 0) {
+ vshError(ctl, _("Failed to format new xml document for bridge %s"),
br_name);
+ goto cleanup;
+ }
+
+
+ /* br_xml is the new interface to define. It will automatically undefine the
+ * independent original interface.
+ */
+ if (!(br_handle = virInterfaceDefineXML(ctl->conn, (char *) br_xml, 0))) {
+ vshError(ctl, _("Failed to define new bridge interface %s"),
+ br_name);
+ goto cleanup;
+ }
+
+ vshPrint(ctl, _("Created bridge %s with attached device %s\n"),
+ if_name, br_name);
+
+ /* start it up unless requested not to */
+ if (!nostart) {
+ if (virInterfaceCreate(br_handle, 0) < 0) {
+ vshError(ctl, _("Failed to start bridge interface %s"), br_name);
+ goto cleanup;
+ }
+ vshPrint(ctl, _("Bridge interface %s started\n"), br_name);
+ }
+
+ ret = true;
+ cleanup:
+ if (if_handle)
+ virInterfaceFree(if_handle);
+ if (br_handle)
+ virInterfaceFree(br_handle);
+ VIR_FREE(if_xml);
+ VIR_FREE(br_xml);
+ VIR_FREE(if_type);
+ VIR_FREE(if2_name);
+ VIR_FREE(delay_str);
+ xmlXPathFreeContext(ctxt);
+ xmlFreeDoc(xml_doc);
+ return ret;
+}
+
+/*
+ * "iface-unbridge" command
+ */
+static const vshCmdInfo info_interface_unbridge[] = {
+ {"help", N_("undefine a bridge device after detaching its slave
device")},
+ {"desc", N_("unbridge a network device")},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_interface_unbridge[] = {
+ {"bridge", VSH_OT_DATA, VSH_OFLAG_REQ, N_("current bridge device
name")},
+ {"no-start", VSH_OT_BOOL, 0, N_("don't start the un-slaved
interface immediately (not recommended)")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdInterfaceUnbridge(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ bool ret = false;
+ virInterfacePtr if_handle = NULL, br_handle = NULL;
+ const char *br_name;
+ char *if_type = NULL, *if_name = NULL;
+ bool nostart = false;
+ char *br_xml = NULL;
+ xmlChar *if_xml = NULL;
+ int if_xml_size;
+ xmlDocPtr xml_doc = NULL;
+ xmlXPathContextPtr ctxt = NULL;
+ xmlNodePtr top_node, br_node, if_node, cur;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ goto cleanup;
+
+ /* Get a handle to the original device */
+ if (!(br_handle = vshCommandOptInterfaceBy(ctl, cmd, "bridge",
+ &br_name, VSH_BYNAME))) {
+ goto cleanup;
+ }
+
+ nostart = vshCommandOptBool(cmd, "no-start");
+
+ /* Get the bridge xml into an xmlDoc */
+ if (!(br_xml = virInterfaceGetXMLDesc(br_handle, VIR_INTERFACE_XML_INACTIVE)))
+ goto cleanup;
+ if (!(xml_doc = virXMLParseStringCtxt(br_xml,
+ _("(bridge interface definition)"),
+ &ctxt))) {
+ vshError(ctl, _("Failed to parse configuration of %s"), br_name);
+ goto cleanup;
+ }
+ top_node = ctxt->node;
+
+ /* Verify that the device really is a bridge. */
+ if (!(if_type = virXMLPropString(top_node, "type"))) {
+ vshError(ctl, _("Existing device %s has no type"), br_name);
+ goto cleanup;
+ }
+
+ if (STRNEQ(if_type, "bridge")) {
+ vshError(ctl, _("Device %s is not a bridge"), br_name);
+ goto cleanup;
+ }
+ VIR_FREE(if_type);
+
+ /* verify the name in the XML matches the device name */
+ if (!(if_name = virXMLPropString(top_node, "name")) ||
+ STRNEQ(if_name, br_name)) {
+ vshError(ctl, _("Interface name from config %s doesn't match given
supplied name %s"),
+ if_name, br_name);
+ goto cleanup;
+ }
+ VIR_FREE(if_name);
+
+ /* Find the <bridge> node under <interface>. */
+ if (!(br_node = virXPathNode("./bridge", ctxt))) {
+ vshError(ctl, "%s", _("No bridge node in xml document"));
+ goto cleanup;
+ }
+
+ if ((if_node = virXPathNode("./bridge/interface[2]", ctxt))) {
+ vshError(ctl, "%s", _("Multiple interfaecs attached to
bridge"));
+ goto cleanup;
+ }
+
+ if (!(if_node = virXPathNode("./bridge/interface", ctxt))) {
+ vshError(ctl, "%s", _("No interface attached to bridge"));
+ goto cleanup;
+ }
+
+ /* Change the type and name of the outer/master interface to
+ * the type/name of the attached slave interface.
+ */
+ if (!(if_name = virXMLPropString(if_node, "name"))) {
+ vshError(ctl, _("Device attached to bridge %s has no name"), br_name);
+ goto cleanup;
+ }
+
+ if (!(if_type = virXMLPropString(if_node, "type"))) {
+ vshError(ctl, _("Attached device %s has no type"), if_name);
+ goto cleanup;
+ }
+
+ if (!xmlSetProp(top_node, BAD_CAST "type", BAD_CAST if_type)) {
+ vshError(ctl, _("Failed to set interface type to '%s' in xml
document"),
+ if_type);
+ goto cleanup;
+ }
+
+ if (!xmlSetProp(top_node, BAD_CAST "name", BAD_CAST if_name)) {
+ vshError(ctl, _("Failed to set interface name to '%s' in xml
document"),
+ if_name);
+ goto cleanup;
+ }
+
+ /* Cycle through all the nodes under the attached <interface>,
+ * moving all <mac>, <bond> and <vlan> nodes up into the toplevel
+ * <interface>.
+ */
+ cur = if_node->children;
+ while (cur) {
+ xmlNodePtr old = cur;
+
+ cur = cur->next;
+ if ((old->type == XML_ELEMENT_NODE) &&
+ (xmlStrEqual(old->name, BAD_CAST "mac") || /* ethernet stuff to
move down */
+ xmlStrEqual(old->name, BAD_CAST "bond") || /* bond stuff to
move down */
+ xmlStrEqual(old->name, BAD_CAST "vlan"))) { /* vlan stuff to
move down */
+ xmlUnlinkNode(old);
+ if (!xmlAddChild(top_node, old)) {
+ vshError(ctl, _("Failed to move '%s' element in xml
document"), old->name);
+ xmlFreeNode(old);
+ goto cleanup;
+ }
+ }
+ }
+
+ /* The document should now be fully converted; write it out to a string. */
+ xmlDocDumpMemory(xml_doc, &if_xml, &if_xml_size);
+
+ if (!if_xml || if_xml_size <= 0) {
+ vshError(ctl, _("Failed to format new xml document for un-enslaved interface
%s"),
+ if_name);
+ goto cleanup;
+ }
+
+ /* Destroy and Undefine the bridge device, since we otherwise
+ * can't safely define the unattached device.
+ */
+ if (virInterfaceDestroy(br_handle, 0) < 0) {
+ vshError(ctl, _("Failed to destroy bridge interface %s"), br_name);
+ goto cleanup;
+ }
+ if (virInterfaceUndefine(br_handle) < 0) {
+ vshError(ctl, _("Failed to undefine bridge interface %s"), br_name);
+ goto cleanup;
+ }
+
+ /* if_xml is the new interface to define.
+ */
+ if (!(if_handle = virInterfaceDefineXML(ctl->conn, (char *) if_xml, 0))) {
+ vshError(ctl, _("Failed to define new interface %s"), if_name);
+ goto cleanup;
+ }
+
+ vshPrint(ctl, _("Device %s un-attached from bridge %s\n"),
+ if_name, br_name);
+
+ /* unless requested otherwise, undefine the bridge device */
+ if (!nostart) {
+ if (virInterfaceCreate(if_handle, 0) < 0) {
+ vshError(ctl, _("Failed to start interface %s"), if_name);
+ goto cleanup;
+ }
+ vshPrint(ctl, _("Interface %s started\n"), if_name);
+ }
+
+ ret = true;
+ cleanup:
+ if (if_handle)
+ virInterfaceFree(if_handle);
+ if (br_handle)
+ virInterfaceFree(br_handle);
+ VIR_FREE(if_xml);
+ VIR_FREE(br_xml);
+ VIR_FREE(if_type);
+ VIR_FREE(if_name);
+ xmlXPathFreeContext(ctxt);
+ xmlFreeDoc(xml_doc);
+ return ret;
+}
+
+/*
* "nwfilter-define" command
*/
static const vshCmdInfo info_nwfilter_define[] = {
@@ -14199,6 +14612,10 @@ static const vshCmdDef nodedevCmds[] = {
static const vshCmdDef ifaceCmds[] = {
{"iface-begin", cmdInterfaceBegin, opts_interface_begin,
info_interface_begin, 0},
+ {"iface-bridge", cmdInterfaceBridge, opts_interface_bridge,
+ info_interface_bridge, 0},
+ {"iface-unbridge", cmdInterfaceUnbridge, opts_interface_unbridge,
+ info_interface_unbridge, 0},
{"iface-commit", cmdInterfaceCommit, opts_interface_commit,
info_interface_commit, 0},
{"iface-define", cmdInterfaceDefine, opts_interface_define,
@@ -15101,11 +15518,14 @@ vshCommandOptNWFilterBy(vshControl *ctl, const vshCmd *cmd,
static virInterfacePtr
vshCommandOptInterfaceBy(vshControl *ctl, const vshCmd *cmd,
+ const char *optname,
const char **name, int flag)
{
virInterfacePtr iface = NULL;
const char *n = NULL;
- const char *optname = "interface";
+
+ if (!optname)
+ optname = "interface";
if (!cmd_has_option (ctl, cmd, optname))
return NULL;
--
1.7.7.1