This patch allows the following to be specified in a qemu domain: <channel type='pipe'> <source path='/tmp/guestfwd'/> <target type='guestfwd' address='10.0.2.1' port='4600'/> </channel> This will output the following on the qemu command line: -chardev pipe,id=channel0,path=/tmp/guestfwd \ -net user,guestfwd=tcp:10.0.2.1:4600-chardev:channel0 * docs/schemas/domain.rng: Add <channel> and <guestfwd> elements * proxy/Makefile.am: add network.c as dep of domain_conf.c * src/conf/domain_conf.[ch]: Add xml parsing/formatting for channel and guestfwd * src/qemu/qemu_conf.c: Add argument output for guestfwd * tests/qemuxml2(argv|xml)test.c: Add test for guestfwd domain syntax --- docs/schemas/domain.rng | 89 ++++++---- proxy/Makefile.am | 1 + src/conf/domain_conf.c | 189 ++++++++++++++++++-- src/conf/domain_conf.h | 6 + src/qemu/qemu_conf.c | 61 +++++++ .../qemuxml2argv-channel-guestfwd.args | 1 + .../qemuxml2argv-channel-guestfwd.xml | 26 +++ tests/qemuxml2argvtest.c | 4 +- tests/qemuxml2xmltest.c | 1 + 9 files changed, 329 insertions(+), 49 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.xml diff --git a/docs/schemas/domain.rng b/docs/schemas/domain.rng index 0a6ab61..b75f17e 100644 --- a/docs/schemas/domain.rng +++ b/docs/schemas/domain.rng @@ -930,6 +930,19 @@ definition doesn't fully specify the constraints on this node. --> <define name="qemucdev"> + <ref name="qemucdevSrcType"/> + <interleave> + <ref name="qemucdevSrcDef"/> + <optional> + <element name="target"> + <optional> + <attribute name="port"/> + </optional> + </element> + </optional> + </interleave> + </define> + <define name="qemucdevSrcType"> <attribute name="type"> <choice> <value>dev</value> @@ -944,43 +957,36 @@ <value>pty</value> </choice> </attribute> - <interleave> - <optional> - <oneOrMore> - <element name="source"> - <optional> - <attribute name="mode"/> - </optional> - <optional> - <attribute name="path"/> - </optional> - <optional> - <attribute name="host"/> - </optional> - <optional> - <attribute name="service"/> - </optional> - <optional> - <attribute name="wiremode"/> - </optional> - </element> - </oneOrMore> - </optional> - <optional> - <element name="protocol"> + </define> + <define name="qemucdevSrcDef"> + <optional> + <oneOrMore> + <element name="source"> <optional> - <attribute name="type"/> + <attribute name="mode"/> </optional> - </element> - </optional> - <optional> - <element name="target"> <optional> - <attribute name="port"/> + <attribute name="path"/> + </optional> + <optional> + <attribute name="host"/> + </optional> + <optional> + <attribute name="service"/> + </optional> + <optional> + <attribute name="wiremode"/> </optional> </element> - </optional> - </interleave> + </oneOrMore> + </optional> + <optional> + <element name="protocol"> + <optional> + <attribute name="type"/> + </optional> + </element> + </optional> </define> <!-- The description for a console @@ -1044,6 +1050,24 @@ <ref name="qemucdev"/> </element> </define> + <define name="guestfwdTarget"> + <element name="target"> + <attribute name="type"> + <value>guestfwd</value> + </attribute> + <attribute name="address"/> + <attribute name="port"/> + </element> + </define> + <define name="channel"> + <element name="channel"> + <ref name="qemucdevSrcType"/> + <interleave> + <ref name="qemucdevSrcDef"/> + <ref name="guestfwdTarget"/> + </interleave> + </element> + </define> <define name="input"> <element name="input"> <attribute name="type"> @@ -1158,6 +1182,7 @@ <ref name="console"/> <ref name="parallel"/> <ref name="serial"/> + <ref name="channel"/> </choice> </zeroOrMore> <optional> diff --git a/proxy/Makefile.am b/proxy/Makefile.am index 3e0050b..42f6a81 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -17,6 +17,7 @@ libvirt_proxy_SOURCES = libvirt_proxy.c \ @top_srcdir@/src/util/buf.c \ @top_srcdir@/src/util/logging.c \ @top_srcdir@/src/util/memory.c \ + @top_srcdir@/src/util/network.c \ @top_srcdir@/src/util/threads.c \ @top_srcdir@/src/util/util.c \ @top_srcdir@/src/util/uuid.c \ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index e050453..4689bac 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -40,6 +40,7 @@ #include "buf.h" #include "c-ctype.h" #include "logging.h" +#include "network.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN @@ -132,7 +133,8 @@ VIR_ENUM_IMPL(virDomainChrTarget, VIR_DOMAIN_CHR_TARGET_TYPE_LAST, "monitor", "parallel", "serial", - "console") + "console", + "guestfwd") VIR_ENUM_IMPL(virDomainChr, VIR_DOMAIN_CHR_TYPE_LAST, "null", @@ -412,6 +414,12 @@ void virDomainChrDefFree(virDomainChrDefPtr def) if (!def) return; + switch (def->targetType) { + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + VIR_FREE(def->target.addr); + break; + } + switch (def->type) { case VIR_DOMAIN_CHR_TYPE_PTY: case VIR_DOMAIN_CHR_TYPE_DEV: @@ -541,6 +549,7 @@ void virDomainDefFree(virDomainDefPtr def) for (i = 0 ; i < def->nnets ; i++) virDomainNetDefFree(def->nets[i]); VIR_FREE(def->nets); + for (i = 0 ; i < def->nserials ; i++) virDomainChrDefFree(def->serials[i]); VIR_FREE(def->serials); @@ -549,6 +558,10 @@ void virDomainDefFree(virDomainDefPtr def) virDomainChrDefFree(def->parallels[i]); VIR_FREE(def->parallels); + for (i = 0 ; i < def->nchannels ; i++) + virDomainChrDefFree(def->channels[i]); + VIR_FREE(def->channels); + virDomainChrDefFree(def->console); for (i = 0 ; i < def->nsounds ; i++) @@ -1332,7 +1345,10 @@ virDomainChrDefParseXML(virConnectPtr conn, char *path = NULL; char *mode = NULL; char *protocol = NULL; + const char *nodeName; const char *targetType = NULL; + const char *addrStr = NULL; + const char *portStr = NULL; virDomainChrDefPtr def; if (VIR_ALLOC(def) < 0) { @@ -1346,18 +1362,15 @@ virDomainChrDefParseXML(virConnectPtr conn, else if ((def->type = virDomainChrTypeFromString(type)) < 0) def->type = VIR_DOMAIN_CHR_TYPE_NULL; - targetType = (const char *) node->name; - if (targetType == NULL) { - /* Shouldn't be possible */ - virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, - "node->name is NULL at %s:%i", - __FILE__, __LINE__); - return NULL; - } - if ((def->targetType = virDomainChrTargetTypeFromString(targetType)) < 0) { - virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, - _("unknown target type for character device: %s"), - targetType); + nodeName = (const char *) node->name; + if ((def->targetType = virDomainChrTargetTypeFromString(nodeName)) < 0) { + /* channel is handled below */ + if(STRNEQ(nodeName, "channel")) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("unknown target type for character device: %s"), + nodeName); + return NULL; + } def->targetType = VIR_DOMAIN_CHR_TARGET_TYPE_NULL; } @@ -1406,6 +1419,89 @@ virDomainChrDefParseXML(virConnectPtr conn, } else if (xmlStrEqual(cur->name, BAD_CAST "protocol")) { if (protocol == NULL) protocol = virXMLPropString(cur, "type"); + } else if (xmlStrEqual(cur->name, BAD_CAST "target")) { + /* If target type isn't set yet, expect it to be set here */ + if(def->targetType == VIR_DOMAIN_CHR_TARGET_TYPE_NULL) { + targetType = virXMLPropString(cur, "type"); + if(targetType == NULL) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("character device target does " + "not define a type")); + goto error; + } + if ((def->targetType = + virDomainChrTargetTypeFromString(targetType)) < 0) + { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("unknown target type for " + "character device: %s"), + targetType); + goto error; + } + } + + unsigned int port; + switch (def->targetType) { + case VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL: + case VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL: + case VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE: + portStr = virXMLPropString(cur, "port"); + if(portStr == NULL) { + /* Not required. It will be assigned automatically + * later */ + break; + } + + if(virStrToLong_ui(portStr, NULL, 10, &port) < 0) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("Invalid port number: %s"), + portStr); + goto error; + } + break; + + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + addrStr = virXMLPropString(cur, "address"); + portStr = virXMLPropString(cur, "port"); + + if(addrStr == NULL) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("guestfwd channel does not " + "define a target address")); + goto error; + } + if(VIR_ALLOC(def->target.addr) < 0) { + virReportOOMError(conn); + goto error; + } + if(virSocketParseAddr(addrStr, def->target.addr, 0) < 0) + { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("%s is not a valid address"), + addrStr); + goto error; + } + + if(portStr == NULL) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("guestfwd channel does " + "not define a target port")); + goto error; + } + if(virStrToLong_ui(portStr, NULL, 10, &port) < 0) { + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("Invalid port number: %s"), + portStr); + goto error; + } + virSocketSetPort(def->target.addr, port); + break; + + default: + virDomainReportError(conn, VIR_ERR_INVALID_DOMAIN, + _("unexpected target type type %u"), + def->targetType); + } } } cur = cur->next; @@ -1535,6 +1631,9 @@ cleanup: VIR_FREE(connectHost); VIR_FREE(connectService); VIR_FREE(path); + VIR_FREE(targetType); + VIR_FREE(addrStr); + VIR_FREE(portStr); return def; @@ -3007,6 +3106,25 @@ static virDomainDefPtr virDomainDefParseXML(virConnectPtr conn, } } + if ((n = virXPathNodeSet(conn, "./devices/channel", ctxt, &nodes)) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot extract channel devices")); + goto error; + } + if (n && VIR_ALLOC_N(def->channels, n) < 0) + goto no_memory; + + for (i = 0 ; i < n ; i++) { + virDomainChrDefPtr chr = virDomainChrDefParseXML(conn, + nodes[i], + flags); + if (!chr) + goto error; + + def->channels[def->nchannels++] = chr; + } + VIR_FREE(nodes); + /* analysis of the input devices */ if ((n = virXPathNodeSet(conn, "./devices/input", ctxt, &nodes)) < 0) { @@ -3992,13 +4110,26 @@ virDomainChrDefFormat(virConnectPtr conn, { const char *type = virDomainChrTypeToString(def->type); const char *targetName = virDomainChrTargetTypeToString(def->targetType); + const char *elementName; + + const char *addr = NULL; + int ret = 0; + + switch (def->targetType) { + /* channel types are in a common channel element */ + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + elementName = "channel"; + break; - const char *elementName = targetName; /* Currently always the same */ + default: + elementName = targetName; + } if (!type) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unexpected char type %d"), def->type); - return -1; + ret = -1; + goto cleanup; } /* Compat with legacy <console tty='/dev/pts/5'/> syntax */ @@ -4080,6 +4211,25 @@ virDomainChrDefFormat(virConnectPtr conn, } switch (def->targetType) { + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + addr = virSocketFormatAddr(def->target.addr); + if (addr == NULL) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to format guestfwd address")); + ret = -1; + goto cleanup; + } + int port = virSocketGetPort(def->target.addr); + if (port < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to format guestfwd port")); + ret = -1; + goto cleanup; + } + virBufferVSprintf(buf, " <target type='guestfwd' address='%s' port='%d'/>\n", + addr, port); + break; + case VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL: case VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL: case VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE: @@ -4097,7 +4247,10 @@ virDomainChrDefFormat(virConnectPtr conn, virBufferVSprintf(buf, " </%s>\n", elementName); - return 0; +cleanup: + VIR_FREE(addr); + + return ret; } static int @@ -4563,6 +4716,10 @@ char *virDomainDefFormat(virConnectPtr conn, goto cleanup; } + for (n = 0 ; n < def->nchannels ; n++) + if (virDomainChrDefFormat(conn, &buf, def->channels[n], flags) < 0) + goto cleanup; + for (n = 0 ; n < def->ninputs ; n++) if (def->inputs[n]->bus == VIR_DOMAIN_INPUT_BUS_USB && virDomainInputDefFormat(conn, &buf, def->inputs[n]) < 0) diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 7bd8c63..e826cc7 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -34,6 +34,7 @@ #include "util.h" #include "threads.h" #include "hash.h" +#include "network.h" /* Private component of virDomainXMLFlags */ typedef enum { @@ -217,6 +218,7 @@ enum virDomainChrTargetType { VIR_DOMAIN_CHR_TARGET_TYPE_PARALLEL, VIR_DOMAIN_CHR_TARGET_TYPE_SERIAL, VIR_DOMAIN_CHR_TARGET_TYPE_CONSOLE, + VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD, VIR_DOMAIN_CHR_TARGET_TYPE_LAST }; @@ -249,6 +251,7 @@ struct _virDomainChrDef { int targetType; union { int port; /* parallel, serial, console */ + virSocketAddrPtr addr; /* guestfwd */ } target; int type; @@ -623,6 +626,9 @@ struct _virDomainDef { int nparallels; virDomainChrDefPtr *parallels; + int nchannels; + virDomainChrDefPtr *channels; + /* Only 1 */ virDomainChrDefPtr console; virSecurityLabelDef seclabel; diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 3e37c66..0fbc190 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -50,6 +50,7 @@ #include "xml.h" #include "nodeinfo.h" #include "logging.h" +#include "network.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -1472,6 +1473,35 @@ static int qemudBuildCommandLineChrDevChardevStr(virDomainChrDefPtr dev, return 0; } +static int qemudBuildCommandLineChrDevTargetStr(virDomainChrDefPtr dev, + const char *const id, + char *buf, + int buflen) +{ + int ret = 0; + const char *addr = NULL; + + switch (dev->targetType) { + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + { + addr = virSocketFormatAddr(dev->target.addr); + int port = virSocketGetPort(dev->target.addr); + + if (snprintf(buf, buflen, "user,guestfwd=tcp:%s:%i-chardev:%s", + addr, port, id) >= buflen) { + ret = -1; + goto cleanup; + } + break; + } + } + +cleanup: + VIR_FREE(addr); + + return ret; +} + /* This function outputs an all-in-one character device command line option */ static int qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev, char *buf, @@ -2150,6 +2180,37 @@ int qemudBuildCommandLine(virConnectPtr conn, } } + for (i = 0 ; i < def->nchannels ; i++) { + char buf[4096]; + char id[16]; + + virDomainChrDefPtr channel = def->channels[i]; + + if (snprintf(id, sizeof(id), "channel%i", i) > sizeof(id)) + goto error; + + switch(channel->targetType) { + case VIR_DOMAIN_CHR_TARGET_TYPE_GUESTFWD: + if (!(qemuCmdFlags & QEMUD_CMD_FLAG_CHARDEV)) { + qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("guestfwd requires QEMU to support -chardev")); + goto error; + } + + if (qemudBuildCommandLineChrDevChardevStr(channel, id, + buf, sizeof(buf)) < 0) + goto error; + ADD_ARG_LIT("-chardev"); + ADD_ARG_LIT(buf); + + if (qemudBuildCommandLineChrDevTargetStr(channel, id, + buf, sizeof(buf)) < 0) + goto error; + ADD_ARG_LIT("-net"); + ADD_ARG_LIT(buf); + } + } + ADD_ARG_LIT("-usb"); for (i = 0 ; i < def->ninputs ; i++) { virDomainInputDefPtr input = def->inputs[i]; diff --git a/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.args b/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.args new file mode 100644 index 0000000..b5bb46d --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.args @@ -0,0 +1 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M pc -m 214 -smp 1 -nographic -monitor unix:/tmp/test-monitor,server,nowait -no-acpi -boot c -hda /dev/HostVG/QEMUGuest1 -net none -serial none -parallel none -chardev pipe,id=channel0,path=/tmp/guestfwd -net user,guestfwd=tcp:10.0.2.1:4600-chardev:channel0 -usb diff --git a/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.xml b/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.xml new file mode 100644 index 0000000..51a0c1e --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-channel-guestfwd.xml @@ -0,0 +1,26 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory>219200</memory> + <currentMemory>219200</currentMemory> + <vcpu cpuset='1-4,8-20,525'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + </disk> + <channel type='pipe'> + <source path='/tmp/guestfwd'/> + <target type='guestfwd' address='10.0.2.1' port='4600'/> + </channel> + </devices> +</domain> diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 3255146..c948379 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -268,11 +268,13 @@ mymain(int argc, char **argv) DO_TEST("serial-many", 0); DO_TEST("parallel-tcp", 0); DO_TEST("console-compat", 0); + + DO_TEST("channel-guestfwd", QEMUD_CMD_FLAG_CHARDEV); + DO_TEST("sound", 0); DO_TEST("hostdev-usb-product", 0); DO_TEST("hostdev-usb-address", 0); - DO_TEST("hostdev-pci-address", QEMUD_CMD_FLAG_PCIDEVICE); DO_TEST_FULL("restore-v1", QEMUD_CMD_FLAG_MIGRATE_KVM_STDIO, "stdio"); diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 2cba47b..25ef2ce 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -129,6 +129,7 @@ mymain(int argc, char **argv) DO_TEST("serial-many"); DO_TEST("parallel-tcp"); DO_TEST("console-compat"); + DO_TEST("channel-guestfwd"); DO_TEST("hostdev-usb-product"); DO_TEST("hostdev-usb-address"); -- 1.6.2.5 -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list