diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index a3dcb30d22..5344125b21 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -41,6 +41,8 @@ module Libvirtd_qemu = let remote_display_entry = int_entry "remote_display_port_min" | int_entry "remote_display_port_max" + | int_entry "remote_websocket_port_min" + | int_entry "remote_websocket_port_max" let security_entry = str_entry "security_driver" | bool_entry "security_default_confined" diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 0f0a24c20e..cdf1ec4cbf 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -153,6 +153,12 @@ #remote_display_port_min = 5900 #remote_display_port_max = 65535 +# VNC WebSocket port policies, same rules apply as with remote display +# ports. VNC WebSockets use similar display <-> port mappings, with +# the exception being that ports starts from 5700 instead of 5900. +# +#remote_websocket_port_min = 5700 +#remote_websocket_port_max = 65535 # The default security driver is SELinux. If SELinux is disabled # on the host, then the security driver will automatically disable diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index c711e92c05..49cb7a6645 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -228,6 +228,7 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAST, "scsi-generic.bootindex", /* 145 */ "mem-merge", + "vnc-websocket", ); struct _virQEMUCaps { @@ -2602,6 +2603,10 @@ virQEMUCapsInitQMP(virQEMUCapsPtr qemuCaps, if (qemuCaps->version >= 1003000) virQEMUCapsSet(qemuCaps, QEMU_CAPS_MACHINE_USB_OPT); + /* WebSockets were introduced between 1.3.0 and 1.3.1 */ + if (qemuCaps->version >= 1003001) + virQEMUCapsSet(qemuCaps, QEMU_CAPS_VNC_WEBSOCKET); + if (virQEMUCapsProbeQMPCommands(qemuCaps, mon) < 0) goto cleanup; if (virQEMUCapsProbeQMPEvents(qemuCaps, mon) < 0) diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 477e526ad7..609f3f9390 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -185,6 +185,7 @@ enum virQEMUCapsFlags { QEMU_CAPS_DEVICE_SCSI_GENERIC = 144, /* -device scsi-generic */ QEMU_CAPS_DEVICE_SCSI_GENERIC_BOOTINDEX = 145, /* -device scsi-generic.bootindex */ QEMU_CAPS_MEM_MERGE = 146, /* -machine mem-merge */ + QEMU_CAPS_VNC_WEBSOCKET = 147, /* -vnc x:y,websocket */ QEMU_CAPS_LAST, /* this must always be the last item */ }; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 5941f706bd..588316649e 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -6076,6 +6076,17 @@ qemuBuildGraphicsVNCCommandLine(virQEMUDriverConfigPtr cfg, } if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_COLON)) { + if (!graphics->data.vnc.socket && + graphics->data.vnc.websocket) { + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_WEBSOCKET)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("VNC WebSockets are not supported " + "with this QEMU binary")); + goto error; + } + virBufferAsprintf(&opt, ",websocket=%d", graphics->data.vnc.websocket); + } + if (graphics->data.vnc.auth.passwd || cfg->vncPassword) virBufferAddLit(&opt, ",password"); @@ -9915,6 +9926,7 @@ virDomainDefPtr qemuParseCommandLine(virCapsPtr qemuCaps, * -vnc some.host.name:4 */ char *opts; + char *port; const char *sep = ":"; if (val[0] == '[') sep = "]:"; @@ -9925,11 +9937,12 @@ virDomainDefPtr qemuParseCommandLine(virCapsPtr qemuCaps, _("missing VNC port number in '%s'"), val); goto error; } - if (virStrToLong_i(tmp+strlen(sep), &opts, 10, + port = tmp + strlen(sep); + if (virStrToLong_i(port, &opts, 10, &vnc->data.vnc.port) < 0) { virDomainGraphicsDefFree(vnc); virReportError(VIR_ERR_INTERNAL_ERROR, - _("cannot parse VNC port '%s'"), tmp+1); + _("cannot parse VNC port '%s'"), port); goto error; } if (val[0] == '[') @@ -9942,6 +9955,50 @@ virDomainDefPtr qemuParseCommandLine(virCapsPtr qemuCaps, virDomainGraphicsDefFree(vnc); goto no_memory; } + + if (*opts == ',') { + char *orig_opts = strdup(opts + 1); + if (!orig_opts) { + virDomainGraphicsDefFree(vnc); + goto no_memory; + } + opts = orig_opts; + + while (opts && *opts) { + char *nextopt = strchr(opts, ','); + if (nextopt) + *(nextopt++) = '\0'; + + if (STRPREFIX(opts, "websocket")) { + char *websocket = opts + strlen("websocket"); + if (*(websocket++) == '=' && + *websocket) { + /* If the websocket continues with + * '=', we'll parse it */ + if (virStrToLong_i(websocket, + NULL, 0, + &vnc->data.vnc.websocket) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot parse VNC " + "WebSocket port '%s'"), + websocket); + virDomainGraphicsDefFree(vnc); + VIR_FREE(orig_opts); + goto error; + } + } else { + /* Otherwise, we'll compute the port the same + * way QEMU does, by adding a 5700 to the + * display value. */ + vnc->data.vnc.websocket = + vnc->data.vnc.port + 5700; + } + } + + opts = nextopt; + } + VIR_FREE(orig_opts); + } vnc->data.vnc.port += 5900; vnc->data.vnc.autoport = false; } diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index ba42bb9ecc..36dfa6bc9f 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -48,6 +48,9 @@ # define QEMU_REMOTE_PORT_MIN 5900 # define QEMU_REMOTE_PORT_MAX 65535 +# define QEMU_WEBSOCKET_PORT_MIN 5700 +# define QEMU_WEBSOCKET_PORT_MAX 65535 + virCommandPtr qemuBuildCommandLine(virConnectPtr conn, virQEMUDriverPtr driver, diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index c16b90d3fe..a625ae7a76 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -227,6 +227,9 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) cfg->remotePortMin = QEMU_REMOTE_PORT_MIN; cfg->remotePortMax = QEMU_REMOTE_PORT_MAX; + cfg->webSocketPortMin = QEMU_WEBSOCKET_PORT_MIN; + cfg->webSocketPortMax = QEMU_WEBSOCKET_PORT_MAX; + #if defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R /* For privileged driver, try and find hugepage mount automatically. * Non-privileged driver requires admin to create a dir for the @@ -403,6 +406,35 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, GET_VALUE_STR("spice_password", cfg->spicePassword); + GET_VALUE_LONG("remote_websocket_port_min", cfg->webSocketPortMin); + if (cfg->webSocketPortMin < QEMU_WEBSOCKET_PORT_MIN) { + /* if the port is too low, we can't get the display name + * to tell to vnc (usually subtract 5700, e.g. localhost:1 + * for port 5701) */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s: remote_websocket_port_min: port must be greater " + "than or equal to %d"), + filename, QEMU_WEBSOCKET_PORT_MIN); + goto cleanup; + } + + GET_VALUE_LONG("remote_websocket_port_max", cfg->webSocketPortMax); + if (cfg->webSocketPortMax > QEMU_WEBSOCKET_PORT_MAX || + cfg->webSocketPortMax < cfg->webSocketPortMin) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s: remote_websocket_port_max: port must be between " + "the minimal port and %d"), + filename, QEMU_WEBSOCKET_PORT_MAX); + goto cleanup; + } + + if (cfg->webSocketPortMin > cfg->webSocketPortMax) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s: remote_websocket_port_min: min port must not be " + "greater than max port"), filename); + goto cleanup; + } + GET_VALUE_LONG("remote_display_port_min", cfg->remotePortMin); if (cfg->remotePortMin < QEMU_REMOTE_PORT_MIN) { /* if the port is too low, we can't get the display name diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 30a2a22094..cdfb985594 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -115,6 +115,9 @@ struct _virQEMUDriverConfig { int remotePortMin; int remotePortMax; + int webSocketPortMin; + int webSocketPortMax; + char *hugetlbfsMount; char *hugepagePath; char *bridgeHelperName; @@ -212,6 +215,9 @@ struct _virQEMUDriver { /* Immutable pointer, self-locking APIs */ virPortAllocatorPtr remotePorts; + /* Immutable pointer, self-locking APIs */ + virPortAllocatorPtr webSocketPorts; + /* Immutable pointer, lockless APIs*/ virSysinfoDefPtr hostsysinfo; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 9453c2284a..5225568108 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -663,6 +663,11 @@ qemuStateInitialize(bool privileged, cfg->remotePortMax)) == NULL) goto error; + if ((qemu_driver->webSocketPorts = + virPortAllocatorNew(cfg->webSocketPortMin, + cfg->webSocketPortMax)) == NULL) + goto error; + if (qemuSecurityInit(qemu_driver) < 0) goto error; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 6499f4af96..dbbb7bf983 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -3236,6 +3236,29 @@ qemuSetUnprivSGIO(virDomainDiskDefPtr disk) return ret; } +static int +qemuProcessVNCAllocatePorts(virQEMUDriverPtr driver, + virDomainGraphicsDefPtr graphics) +{ + unsigned short port; + + if (graphics->data.vnc.socket) + return 0; + + if (graphics->data.vnc.autoport) { + if (virPortAllocatorAcquire(driver->remotePorts, &port) < 0) + return -1; + graphics->data.vnc.port = port; + } + + if (graphics->data.vnc.websocket == -1) { + if (virPortAllocatorAcquire(driver->webSocketPorts, &port) < 0) + return -1; + graphics->data.vnc.websocket = port; + } + + return 0; +} static int qemuProcessSPICEAllocatePorts(virQEMUDriverPtr driver, @@ -3470,13 +3493,9 @@ int qemuProcessStart(virConnectPtr conn, for (i = 0 ; i < vm->def->ngraphics; ++i) { virDomainGraphicsDefPtr graphics = vm->def->graphics[i]; - if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC && - !graphics->data.vnc.socket && - graphics->data.vnc.autoport) { - unsigned short port; - if (virPortAllocatorAcquire(driver->remotePorts, &port) < 0) + if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) { + if (qemuProcessVNCAllocatePorts(driver, graphics) < 0) goto cleanup; - graphics->data.vnc.port = port; } else if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) { if (qemuProcessSPICEAllocatePorts(driver, cfg, graphics) < 0) goto cleanup; @@ -4154,10 +4173,15 @@ retry: */ for (i = 0 ; i < vm->def->ngraphics; ++i) { virDomainGraphicsDefPtr graphics = vm->def->graphics[i]; - if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC && - graphics->data.vnc.autoport) { - ignore_value(virPortAllocatorRelease(driver->remotePorts, - graphics->data.vnc.port)); + if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) { + if (graphics->data.vnc.autoport) { + ignore_value(virPortAllocatorRelease(driver->remotePorts, + graphics->data.vnc.port)); + } + if (graphics->data.vnc.websocket) { + ignore_value(virPortAllocatorRelease(driver->webSocketPorts, + graphics->data.vnc.port)); + } } if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE && graphics->data.spice.autoport) { diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 26ca0688d8..d4e4fae717 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -17,6 +17,8 @@ module Test_libvirtd_qemu = { "spice_password" = "XYZ12345" } { "remote_display_port_min" = "5900" } { "remote_display_port_max" = "65535" } +{ "remote_websocket_port_min" = "5700" } +{ "remote_websocket_port_max" = "65535" } { "security_driver" = "selinux" } { "security_default_confined" = "1" } { "security_require_confined" = "1" } diff --git a/tests/qemuargv2xmltest.c b/tests/qemuargv2xmltest.c index 528b8bf65f..58fabdb0ad 100644 --- a/tests/qemuargv2xmltest.c +++ b/tests/qemuargv2xmltest.c @@ -200,6 +200,7 @@ mymain(void) DO_TEST("disk-usb"); DO_TEST("graphics-vnc"); DO_TEST("graphics-vnc-socket"); + DO_TEST("graphics-vnc-websocket"); DO_TEST("graphics-vnc-sasl"); DO_TEST("graphics-vnc-tls"); diff --git a/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.args b/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.args new file mode 100644 index 0000000000..b0c59b1c91 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.args @@ -0,0 +1,4 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test \ +QEMU_AUDIO_DRV=none /usr/bin/qemu -S -M pc -m 214 -smp 1 \ +-monitor unix:/tmp/test-monitor,server,nowait -no-acpi -boot c \ +-usb -net none -serial none -parallel none -vnc 127.0.0.1:0,websocket=5700 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.xml b/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.xml new file mode 100644 index 0000000000..dd0bb574db --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-graphics-vnc-websocket.xml @@ -0,0 +1,28 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219100 + 219100 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + + + + + + + diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index a2226f8dcf..d0b49b0fe4 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -605,6 +605,7 @@ mymain(void) DO_TEST("graphics-vnc", QEMU_CAPS_VNC); DO_TEST("graphics-vnc-socket", QEMU_CAPS_VNC); + DO_TEST("graphics-vnc-websocket", QEMU_CAPS_VNC, QEMU_CAPS_VNC_WEBSOCKET); driver.config->vncSASL = 1; VIR_FREE(driver.config->vncSASLdir); diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index ddf3230d38..92b7383fbe 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -188,6 +188,7 @@ mymain(void) DO_TEST_FULL("disk-mirror", true, WHEN_INACTIVE); DO_TEST("graphics-listen-network"); DO_TEST("graphics-vnc"); + DO_TEST("graphics-vnc-websocket"); DO_TEST("graphics-vnc-sasl"); DO_TEST("graphics-vnc-tls"); DO_TEST("graphics-sdl");