aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hmp-commands-info.hx138
-rw-r--r--hmp.c2
-rw-r--r--hw/usb/Makefile.objs3
-rw-r--r--hw/usb/bus.c4
-rw-r--r--hw/usb/ccid-card-passthru.c2
-rw-r--r--hw/vfio/pci-quirks.c114
-rw-r--r--hw/vfio/pci.c17
-rw-r--r--hw/vfio/pci.h4
-rw-r--r--include/io/channel-websock.h2
-rw-r--r--io/channel-websock.c496
-rw-r--r--io/trace-events7
-rw-r--r--monitor.c7
-rw-r--r--ui/vnc-auth-vencrypt.c3
-rw-r--r--ui/vnc-ws.c6
-rw-r--r--ui/vnc.c4
15 files changed, 574 insertions, 235 deletions
diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
index 4f1ece93e5..54c3e5eac6 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
@@ -23,7 +23,7 @@ ETEXI
STEXI
@item info version
-@findex version
+@findex info version
Show the version of QEMU.
ETEXI
@@ -37,7 +37,7 @@ ETEXI
STEXI
@item info network
-@findex network
+@findex info network
Show the network state.
ETEXI
@@ -51,7 +51,7 @@ ETEXI
STEXI
@item info chardev
-@findex chardev
+@findex info chardev
Show the character devices.
ETEXI
@@ -66,7 +66,7 @@ ETEXI
STEXI
@item info block
-@findex block
+@findex info block
Show info of one block device or all block devices.
ETEXI
@@ -80,7 +80,7 @@ ETEXI
STEXI
@item info blockstats
-@findex blockstats
+@findex info blockstats
Show block device statistics.
ETEXI
@@ -94,7 +94,7 @@ ETEXI
STEXI
@item info block-jobs
-@findex block-jobs
+@findex info block-jobs
Show progress of ongoing block device operations.
ETEXI
@@ -108,7 +108,7 @@ ETEXI
STEXI
@item info registers
-@findex registers
+@findex info registers
Show the cpu registers.
ETEXI
@@ -125,7 +125,7 @@ ETEXI
STEXI
@item info lapic
-@findex lapic
+@findex info lapic
Show local APIC state
ETEXI
@@ -141,7 +141,7 @@ ETEXI
STEXI
@item info ioapic
-@findex ioapic
+@findex info ioapic
Show io APIC state
ETEXI
@@ -155,7 +155,7 @@ ETEXI
STEXI
@item info cpus
-@findex cpus
+@findex info cpus
Show infos for each CPU.
ETEXI
@@ -169,7 +169,7 @@ ETEXI
STEXI
@item info history
-@findex history
+@findex info history
Show the command line history.
ETEXI
@@ -183,7 +183,7 @@ ETEXI
STEXI
@item info irq
-@findex irq
+@findex info irq
Show the interrupts statistics (if available).
ETEXI
@@ -197,7 +197,7 @@ ETEXI
STEXI
@item info pic
-@findex pic
+@findex info pic
Show i8259 (PIC) state.
ETEXI
@@ -211,7 +211,7 @@ ETEXI
STEXI
@item info pci
-@findex pci
+@findex info pci
Show PCI information.
ETEXI
@@ -228,7 +228,7 @@ ETEXI
STEXI
@item info tlb
-@findex tlb
+@findex info tlb
Show virtual to physical memory mappings.
ETEXI
@@ -244,7 +244,7 @@ ETEXI
STEXI
@item info mem
-@findex mem
+@findex info mem
Show the active virtual memory mappings.
ETEXI
@@ -259,7 +259,7 @@ ETEXI
STEXI
@item info mtree
-@findex mtree
+@findex info mtree
Show memory tree.
ETEXI
@@ -275,7 +275,7 @@ ETEXI
STEXI
@item info jit
-@findex jit
+@findex info jit
Show dynamic compiler info.
ETEXI
@@ -291,7 +291,7 @@ ETEXI
STEXI
@item info opcount
-@findex opcount
+@findex info opcount
Show dynamic compiler opcode counters
ETEXI
@@ -305,7 +305,7 @@ ETEXI
STEXI
@item info kvm
-@findex kvm
+@findex info kvm
Show KVM information.
ETEXI
@@ -319,7 +319,7 @@ ETEXI
STEXI
@item info numa
-@findex numa
+@findex info numa
Show NUMA information.
ETEXI
@@ -333,7 +333,7 @@ ETEXI
STEXI
@item info usb
-@findex usb
+@findex info usb
Show guest USB devices.
ETEXI
@@ -347,7 +347,7 @@ ETEXI
STEXI
@item info usbhost
-@findex usbhost
+@findex info usbhost
Show host USB devices.
ETEXI
@@ -361,7 +361,7 @@ ETEXI
STEXI
@item info profile
-@findex profile
+@findex info profile
Show profiling information.
ETEXI
@@ -375,7 +375,7 @@ ETEXI
STEXI
@item info capture
-@findex capture
+@findex info capture
Show capture information.
ETEXI
@@ -389,7 +389,7 @@ ETEXI
STEXI
@item info snapshots
-@findex snapshots
+@findex info snapshots
Show the currently saved VM snapshots.
ETEXI
@@ -403,7 +403,7 @@ ETEXI
STEXI
@item info status
-@findex status
+@findex info status
Show the current VM status (running|paused).
ETEXI
@@ -417,7 +417,7 @@ ETEXI
STEXI
@item info mice
-@findex mice
+@findex info mice
Show which guest mouse is receiving events.
ETEXI
@@ -431,7 +431,7 @@ ETEXI
STEXI
@item info vnc
-@findex vnc
+@findex info vnc
Show the vnc server status.
ETEXI
@@ -447,7 +447,7 @@ ETEXI
STEXI
@item info spice
-@findex spice
+@findex info spice
Show the spice server status.
ETEXI
@@ -461,7 +461,7 @@ ETEXI
STEXI
@item info name
-@findex name
+@findex info name
Show the current VM name.
ETEXI
@@ -475,7 +475,7 @@ ETEXI
STEXI
@item info uuid
-@findex uuid
+@findex info uuid
Show the current VM UUID.
ETEXI
@@ -489,7 +489,7 @@ ETEXI
STEXI
@item info cpustats
-@findex cpustats
+@findex info cpustats
Show CPU statistics.
ETEXI
@@ -505,7 +505,7 @@ ETEXI
STEXI
@item info usernet
-@findex usernet
+@findex info usernet
Show user network stack connection states.
ETEXI
@@ -519,7 +519,7 @@ ETEXI
STEXI
@item info migrate
-@findex migrate
+@findex info migrate
Show migration status.
ETEXI
@@ -533,7 +533,7 @@ ETEXI
STEXI
@item info migrate_capabilities
-@findex migrate_capabilities
+@findex info migrate_capabilities
Show current migration capabilities.
ETEXI
@@ -547,7 +547,7 @@ ETEXI
STEXI
@item info migrate_parameters
-@findex migrate_parameters
+@findex info migrate_parameters
Show current migration parameters.
ETEXI
@@ -561,7 +561,7 @@ ETEXI
STEXI
@item info migrate_cache_size
-@findex migrate_cache_size
+@findex info migrate_cache_size
Show current migration xbzrle cache size.
ETEXI
@@ -575,7 +575,7 @@ ETEXI
STEXI
@item info balloon
-@findex balloon
+@findex info balloon
Show balloon information.
ETEXI
@@ -589,7 +589,7 @@ ETEXI
STEXI
@item info qtree
-@findex qtree
+@findex info qtree
Show device tree.
ETEXI
@@ -603,7 +603,7 @@ ETEXI
STEXI
@item info qdm
-@findex qdm
+@findex info qdm
Show qdev device model list.
ETEXI
@@ -617,7 +617,7 @@ ETEXI
STEXI
@item info qom-tree
-@findex qom-tree
+@findex info qom-tree
Show QOM composition tree.
ETEXI
@@ -631,7 +631,7 @@ ETEXI
STEXI
@item info roms
-@findex roms
+@findex info roms
Show roms.
ETEXI
@@ -647,7 +647,7 @@ ETEXI
STEXI
@item info trace-events
-@findex trace-events
+@findex info trace-events
Show available trace-events & their state.
ETEXI
@@ -661,7 +661,7 @@ ETEXI
STEXI
@item info tpm
-@findex tpm
+@findex info tpm
Show the TPM device.
ETEXI
@@ -675,7 +675,7 @@ ETEXI
STEXI
@item info memdev
-@findex memdev
+@findex info memdev
Show memory backends
ETEXI
@@ -689,7 +689,7 @@ ETEXI
STEXI
@item info memory-devices
-@findex memory-devices
+@findex info memory-devices
Show memory devices.
ETEXI
@@ -703,7 +703,7 @@ ETEXI
STEXI
@item info iothreads
-@findex iothreads
+@findex info iothreads
Show iothread's identifiers.
ETEXI
@@ -717,7 +717,7 @@ ETEXI
STEXI
@item info rocker @var{name}
-@findex rocker
+@findex info rocker
Show rocker switch.
ETEXI
@@ -730,8 +730,8 @@ ETEXI
},
STEXI
-@item info rocker_ports @var{name}-ports
-@findex ocker-ports
+@item info rocker-ports @var{name}-ports
+@findex info rocker-ports
Show rocker ports.
ETEXI
@@ -744,8 +744,8 @@ ETEXI
},
STEXI
-@item info rocker_of_dpa_flows @var{name} [@var{tbl_id}]
-@findex rocker-of-dpa-flows
+@item info rocker-of-dpa-flows @var{name} [@var{tbl_id}]
+@findex info rocker-of-dpa-flows
Show rocker OF-DPA flow tables.
ETEXI
@@ -759,7 +759,7 @@ ETEXI
STEXI
@item info rocker-of-dpa-groups @var{name} [@var{type}]
-@findex rocker-of-dpa-groups
+@findex info rocker-of-dpa-groups
Show rocker OF-DPA groups.
ETEXI
@@ -775,7 +775,7 @@ ETEXI
STEXI
@item info skeys @var{address}
-@findex skeys
+@findex info skeys
Display the value of a storage key (s390 only)
ETEXI
@@ -791,7 +791,7 @@ ETEXI
STEXI
@item info cmma @var{address}
-@findex cmma
+@findex info cmma
Display the values of the CMMA storage attributes for a range of pages (s390 only)
ETEXI
@@ -805,7 +805,7 @@ ETEXI
STEXI
@item info dump
-@findex dump
+@findex info dump
Display the latest dump status.
ETEXI
@@ -819,7 +819,7 @@ ETEXI
STEXI
@item info ramblock
-@findex ramblock
+@findex info ramblock
Dump all the ramblocks of the system.
ETEXI
@@ -833,16 +833,10 @@ ETEXI
STEXI
@item info hotpluggable-cpus
-@findex hotpluggable-cpus
+@findex info hotpluggable-cpus
Show information about hotpluggable CPUs
ETEXI
-STEXI
-@item info vm-generation-id
-@findex vm-generation-id
-Show Virtual Machine Generation ID
-ETEXI
-
{
.name = "vm-generation-id",
.args_type = "",
@@ -852,10 +846,9 @@ ETEXI
},
STEXI
-@item info memory_size_summary
-@findex memory_size_summary
-Display the amount of initially allocated and present hotpluggable (if
-enabled) memory in bytes.
+@item info vm-generation-id
+@findex info vm-generation-id
+Show Virtual Machine Generation ID
ETEXI
{
@@ -868,6 +861,13 @@ ETEXI
},
STEXI
+@item info memory_size_summary
+@findex info memory_size_summary
+Display the amount of initially allocated and present hotpluggable (if
+enabled) memory in bytes.
+ETEXI
+
+STEXI
@end table
ETEXI
diff --git a/hmp.c b/hmp.c
index ace729d03f..739d330f4e 100644
--- a/hmp.c
+++ b/hmp.c
@@ -2394,6 +2394,7 @@ void hmp_info_memdev(Monitor *mon, const QDict *qdict)
monitor_printf(mon, "\n");
qapi_free_MemdevList(memdev_list);
+ hmp_handle_error(mon, &err);
}
void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
@@ -2432,6 +2433,7 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
}
qapi_free_MemoryDeviceInfoList(info_list);
+ hmp_handle_error(mon, &err);
}
void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs
index 9255234c63..bdfead6701 100644
--- a/hw/usb/Makefile.objs
+++ b/hw/usb/Makefile.objs
@@ -42,11 +42,12 @@ redirect.o-cflags = $(USB_REDIR_CFLAGS)
redirect.o-libs = $(USB_REDIR_LIBS)
# usb pass-through
-ifeq ($(CONFIG_LIBUSB)$(CONFIG_USB),yy)
+ifeq ($(CONFIG_USB_LIBUSB)$(CONFIG_USB),yy)
common-obj-y += host-libusb.o host-legacy.o
else
common-obj-y += host-stub.o
endif
+common-obj-$(CONFIG_ALL) += host-stub.o
host-libusb.o-cflags := $(LIBUSB_CFLAGS)
host-libusb.o-libs := $(LIBUSB_LIBS)
diff --git a/hw/usb/bus.c b/hw/usb/bus.c
index d910f849e7..e56dc3348a 100644
--- a/hw/usb/bus.c
+++ b/hw/usb/bus.c
@@ -341,9 +341,7 @@ static USBDevice *usb_try_create_simple(USBBus *bus, const char *name,
object_property_set_bool(OBJECT(dev), true, "realized", &err);
if (err) {
error_propagate(errp, err);
- error_prepend(errp, "Failed to initialize USB device '%s': ",
- name);
- object_unparent(OBJECT(dev));
+ error_prepend(errp, "Failed to initialize USB device '%s': ", name);
return NULL;
}
return dev;
diff --git a/hw/usb/ccid-card-passthru.c b/hw/usb/ccid-card-passthru.c
index 45d96b03c6..117711862e 100644
--- a/hw/usb/ccid-card-passthru.c
+++ b/hw/usb/ccid-card-passthru.c
@@ -9,11 +9,11 @@
*/
#include "qemu/osdep.h"
+#include <cacard/vscard_common.h>
#include "chardev/char-fe.h"
#include "qemu/error-report.h"
#include "qemu/sockets.h"
#include "ccid.h"
-#include "cacard/vscard_common.h"
#define DPRINTF(card, lvl, fmt, ...) \
do { \
diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c
index 349085ea12..14291c2a16 100644
--- a/hw/vfio/pci-quirks.c
+++ b/hw/vfio/pci-quirks.c
@@ -14,6 +14,7 @@
#include "qemu/error-report.h"
#include "qemu/range.h"
#include "qapi/error.h"
+#include "qapi/visitor.h"
#include "hw/nvram/fw_cfg.h"
#include "pci.h"
#include "trace.h"
@@ -1850,3 +1851,116 @@ void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev)
break;
}
}
+
+/*
+ * The NVIDIA GPUDirect P2P Vendor capability allows the user to specify
+ * devices as a member of a clique. Devices within the same clique ID
+ * are capable of direct P2P. It's the user's responsibility that this
+ * is correct. The spec says that this may reside at any unused config
+ * offset, but reserves and recommends hypervisors place this at C8h.
+ * The spec also states that the hypervisor should place this capability
+ * at the end of the capability list, thus next is defined as 0h.
+ *
+ * +----------------+----------------+----------------+----------------+
+ * | sig 7:0 ('P') | vndr len (8h) | next (0h) | cap id (9h) |
+ * +----------------+----------------+----------------+----------------+
+ * | rsvd 15:7(0h),id 6:3,ver 2:0(0h)| sig 23:8 ('P2') |
+ * +---------------------------------+---------------------------------+
+ *
+ * https://lists.gnu.org/archive/html/qemu-devel/2017-08/pdfUda5iEpgOS.pdf
+ */
+static void get_nv_gpudirect_clique_id(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint8_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_uint8(v, name, ptr, errp);
+}
+
+static void set_nv_gpudirect_clique_id(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint8_t value, *ptr = qdev_get_prop_ptr(dev, prop);
+ Error *local_err = NULL;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint8(v, name, &value, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (value & ~0xF) {
+ error_setg(errp, "Property %s: valid range 0-15", name);
+ return;
+ }
+
+ *ptr = value;
+}
+
+const PropertyInfo qdev_prop_nv_gpudirect_clique = {
+ .name = "uint4",
+ .description = "NVIDIA GPUDirect Clique ID (0 - 15)",
+ .get = get_nv_gpudirect_clique_id,
+ .set = set_nv_gpudirect_clique_id,
+};
+
+static int vfio_add_nv_gpudirect_cap(VFIOPCIDevice *vdev, Error **errp)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ int ret, pos = 0xC8;
+
+ if (vdev->nv_gpudirect_clique == 0xFF) {
+ return 0;
+ }
+
+ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID)) {
+ error_setg(errp, "NVIDIA GPUDirect Clique ID: invalid device vendor");
+ return -EINVAL;
+ }
+
+ if (pci_get_byte(pdev->config + PCI_CLASS_DEVICE + 1) !=
+ PCI_BASE_CLASS_DISPLAY) {
+ error_setg(errp, "NVIDIA GPUDirect Clique ID: unsupported PCI class");
+ return -EINVAL;
+ }
+
+ ret = pci_add_capability(pdev, PCI_CAP_ID_VNDR, pos, 8, errp);
+ if (ret < 0) {
+ error_prepend(errp, "Failed to add NVIDIA GPUDirect cap: ");
+ return ret;
+ }
+
+ memset(vdev->emulated_config_bits + pos, 0xFF, 8);
+ pos += PCI_CAP_FLAGS;
+ pci_set_byte(pdev->config + pos++, 8);
+ pci_set_byte(pdev->config + pos++, 'P');
+ pci_set_byte(pdev->config + pos++, '2');
+ pci_set_byte(pdev->config + pos++, 'P');
+ pci_set_byte(pdev->config + pos++, vdev->nv_gpudirect_clique << 3);
+ pci_set_byte(pdev->config + pos, 0);
+
+ return 0;
+}
+
+int vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp)
+{
+ int ret;
+
+ ret = vfio_add_nv_gpudirect_cap(vdev, errp);
+ if (ret) {
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 31e1edf447..9e86db7c3b 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -1826,15 +1826,23 @@ static int vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp)
if (next) {
ret = vfio_add_std_cap(vdev, next, errp);
if (ret) {
- goto out;
+ return ret;
}
} else {
/* Begin the rebuild, use QEMU emulated list bits */
pdev->config[PCI_CAPABILITY_LIST] = 0;
vdev->emulated_config_bits[PCI_CAPABILITY_LIST] = 0xff;
vdev->emulated_config_bits[PCI_STATUS] |= PCI_STATUS_CAP_LIST;
+
+ ret = vfio_add_virt_caps(vdev, errp);
+ if (ret) {
+ return ret;
+ }
}
+ /* Scale down size, esp in case virt caps were added above */
+ size = MIN(size, vfio_std_cap_max_size(pdev, pos));
+
/* Use emulated next pointer to allow dropping caps */
pci_set_byte(vdev->emulated_config_bits + pos + PCI_CAP_LIST_NEXT, 0xff);
@@ -1862,7 +1870,7 @@ static int vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp)
ret = pci_add_capability(pdev, cap_id, pos, size, errp);
break;
}
-out:
+
if (ret < 0) {
error_prepend(errp,
"failed to add PCI capability 0x%x[0x%x]@0x%x: ",
@@ -2962,6 +2970,8 @@ static void vfio_instance_init(Object *obj)
vdev->host.bus = ~0U;
vdev->host.slot = ~0U;
vdev->host.function = ~0U;
+
+ vdev->nv_gpudirect_clique = 0xFF;
}
static Property vfio_pci_dev_properties[] = {
@@ -2986,6 +2996,9 @@ static Property vfio_pci_dev_properties[] = {
DEFINE_PROP_UINT32("x-pci-sub-device-id", VFIOPCIDevice,
sub_device_id, PCI_ANY_ID),
DEFINE_PROP_UINT32("x-igd-gms", VFIOPCIDevice, igd_gms, 0),
+ DEFINE_PROP_UNSIGNED_NODEFAULT("x-nv-gpudirect-clique", VFIOPCIDevice,
+ nv_gpudirect_clique,
+ qdev_prop_nv_gpudirect_clique, uint8_t),
/*
* TODO - support passed fds... is this necessary?
* DEFINE_PROP_STRING("vfiofd", VFIOPCIDevice, vfiofd_name),
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index a8366bb2a7..502a5755b9 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -135,6 +135,7 @@ typedef struct VFIOPCIDevice {
int32_t bootindex;
uint32_t igd_gms;
uint8_t pm_cap;
+ uint8_t nv_gpudirect_clique;
bool pci_aer;
bool req_enabled;
bool has_flr;
@@ -160,6 +161,9 @@ void vfio_bar_quirk_setup(VFIOPCIDevice *vdev, int nr);
void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr);
void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr);
void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev);
+int vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp);
+
+extern const PropertyInfo qdev_prop_nv_gpudirect_clique;
int vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp);
diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h
index 3c9ff84727..ff32d8651b 100644
--- a/include/io/channel-websock.h
+++ b/include/io/channel-websock.h
@@ -60,11 +60,13 @@ struct QIOChannelWebsock {
Buffer encoutput;
Buffer rawinput;
Buffer rawoutput;
+ Buffer ping_reply;
size_t payload_remain;
QIOChannelWebsockMask mask;
guint io_tag;
Error *io_err;
gboolean io_eof;
+ uint8_t opcode;
};
/**
diff --git a/io/channel-websock.c b/io/channel-websock.c
index 5a3badbec2..d1d471f86e 100644
--- a/io/channel-websock.c
+++ b/io/channel-websock.c
@@ -25,6 +25,8 @@
#include "crypto/hash.h"
#include "trace.h"
+#include <time.h>
+
/* Max amount to allow in rawinput/rawoutput buffers */
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
@@ -44,13 +46,40 @@
#define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade"
#define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket"
-#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE \
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
+ "Server: QEMU VNC\r\n" \
+ "Date: %s\r\n"
+
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK \
"HTTP/1.1 101 Switching Protocols\r\n" \
+ QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"Sec-WebSocket-Protocol: binary\r\n" \
"\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND \
+ "HTTP/1.1 404 Not Found\r\n" \
+ QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
+ "Connection: close\r\n" \
+ "\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST \
+ "HTTP/1.1 400 Bad Request\r\n" \
+ QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
+ "Connection: close\r\n" \
+ "Sec-WebSocket-Version: " \
+ QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION \
+ "\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR \
+ "HTTP/1.1 500 Internal Server Error\r\n" \
+ QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
+ "Connection: close\r\n" \
+ "\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE \
+ "HTTP/1.1 403 Request Entity Too Large\r\n" \
+ QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
+ "Connection: close\r\n" \
+ "\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
@@ -81,13 +110,12 @@
/* Magic 7-bit length to indicate use of 64-bit payload length */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127
-/* Bitmasks & shifts for accessing header fields */
+/* Bitmasks for accessing header fields */
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f
-#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN 7
-#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK 7
+#define QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK 0x8
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
@@ -123,8 +151,55 @@ enum {
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
};
+static void qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
+ const char *resmsg,
+ ...)
+{
+ va_list vargs;
+ char *response;
+ size_t responselen;
+
+ va_start(vargs, resmsg);
+ response = g_strdup_vprintf(resmsg, vargs);
+ responselen = strlen(response);
+ buffer_reserve(&ioc->encoutput, responselen);
+ buffer_append(&ioc->encoutput, response, responselen);
+ va_end(vargs);
+}
+
+static gchar *qio_channel_websock_date_str(void)
+{
+ struct tm tm;
+ time_t now = time(NULL);
+ char datebuf[128];
+
+ gmtime_r(&now, &tm);
+
+ strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
+
+ return g_strdup(datebuf);
+}
+
+static void qio_channel_websock_handshake_send_res_err(QIOChannelWebsock *ioc,
+ const char *resdata)
+{
+ char *date = qio_channel_websock_date_str();
+ qio_channel_websock_handshake_send_res(ioc, resdata, date);
+ g_free(date);
+}
+
+enum {
+ QIO_CHANNEL_WEBSOCK_STATUS_NORMAL = 1000,
+ QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR = 1002,
+ QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA = 1003,
+ QIO_CHANNEL_WEBSOCK_STATUS_POLICY = 1008,
+ QIO_CHANNEL_WEBSOCK_STATUS_TOO_LARGE = 1009,
+ QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR = 1011,
+};
+
static size_t
-qio_channel_websock_extract_headers(char *buffer,
+qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
+ char *buffer,
QIOChannelWebsockHTTPHeader *hdrs,
size_t nhdrsalloc,
Error **errp)
@@ -145,7 +220,7 @@ qio_channel_websock_extract_headers(char *buffer,
nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
if (!nl) {
error_setg(errp, "Missing HTTP header delimiter");
- return 0;
+ goto bad_request;
}
*nl = '\0';
@@ -158,18 +233,20 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
error_setg(errp, "Unsupported HTTP method %s", buffer);
- return 0;
+ goto bad_request;
}
buffer = tmp + 1;
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP version delimiter");
- return 0;
+ goto bad_request;
}
*tmp = '\0';
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) {
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND);
error_setg(errp, "Unexpected HTTP path %s", buffer);
return 0;
}
@@ -178,7 +255,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
error_setg(errp, "Unsupported HTTP version %s", buffer);
- return 0;
+ goto bad_request;
}
buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
@@ -203,7 +280,7 @@ qio_channel_websock_extract_headers(char *buffer,
sep = strchr(buffer, ':');
if (!sep) {
error_setg(errp, "Malformed HTTP header");
- return 0;
+ goto bad_request;
}
*sep = '\0';
sep++;
@@ -213,7 +290,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (nhdrs >= nhdrsalloc) {
error_setg(errp, "Too many HTTP headers");
- return 0;
+ goto bad_request;
}
hdr = &hdrs[nhdrs++];
@@ -231,6 +308,11 @@ qio_channel_websock_extract_headers(char *buffer,
} while (nl != NULL);
return nhdrs;
+
+ bad_request:
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
+ return 0;
}
static const char *
@@ -250,14 +332,14 @@ qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
}
-static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
- const char *key,
- Error **errp)
+static void qio_channel_websock_handshake_send_res_ok(QIOChannelWebsock *ioc,
+ const char *key,
+ Error **errp)
{
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
- char *accept = NULL, *response = NULL;
- size_t responselen;
+ char *accept = NULL;
+ char *date = qio_channel_websock_date_str();
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
@@ -271,105 +353,108 @@ static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
QIO_CHANNEL_WEBSOCK_GUID_LEN,
&accept,
errp) < 0) {
- return -1;
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR);
+ return;
}
- response = g_strdup_printf(QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE, accept);
- responselen = strlen(response);
- buffer_reserve(&ioc->encoutput, responselen);
- buffer_append(&ioc->encoutput, response, responselen);
+ qio_channel_websock_handshake_send_res(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
+ g_free(date);
g_free(accept);
- g_free(response);
-
- return 0;
}
-static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
- char *buffer,
- Error **errp)
+static void qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
+ char *buffer,
+ Error **errp)
{
QIOChannelWebsockHTTPHeader hdrs[32];
size_t nhdrs = G_N_ELEMENTS(hdrs);
const char *protocols = NULL, *version = NULL, *key = NULL,
*host = NULL, *connection = NULL, *upgrade = NULL;
- nhdrs = qio_channel_websock_extract_headers(buffer, hdrs, nhdrs, errp);
+ nhdrs = qio_channel_websock_extract_headers(ioc, buffer, hdrs, nhdrs, errp);
if (!nhdrs) {
- return -1;
+ return;
}
protocols = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
if (!protocols) {
error_setg(errp, "Missing websocket protocol header data");
- return -1;
+ goto bad_request;
}
version = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
if (!version) {
error_setg(errp, "Missing websocket version header data");
- return -1;
+ goto bad_request;
}
key = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
if (!key) {
error_setg(errp, "Missing websocket key header data");
- return -1;
+ goto bad_request;
}
host = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST);
if (!host) {
error_setg(errp, "Missing websocket host header data");
- return -1;
+ goto bad_request;
}
connection = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION);
if (!connection) {
error_setg(errp, "Missing websocket connection header data");
- return -1;
+ goto bad_request;
}
upgrade = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE);
if (!upgrade) {
error_setg(errp, "Missing websocket upgrade header data");
- return -1;
+ goto bad_request;
}
if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
error_setg(errp, "No '%s' protocol is supported by client '%s'",
QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
- return -1;
+ goto bad_request;
}
if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
error_setg(errp, "Version '%s' is not supported by client '%s'",
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
- return -1;
+ goto bad_request;
}
if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
error_setg(errp, "Key length '%zu' was not as expected '%d'",
strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
- return -1;
+ goto bad_request;
}
- if (!g_strrstr(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE)) {
+ if (strcasecmp(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) != 0) {
error_setg(errp, "No connection upgrade requested '%s'", connection);
- return -1;
+ goto bad_request;
}
- if (!g_str_equal(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET)) {
+ if (strcasecmp(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET) != 0) {
error_setg(errp, "Incorrect upgrade method '%s'", upgrade);
- return -1;
+ goto bad_request;
}
- return qio_channel_websock_handshake_send_response(ioc, key, errp);
+ qio_channel_websock_handshake_send_res_ok(ioc, key, errp);
+ return;
+
+ bad_request:
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
}
static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
@@ -393,20 +478,20 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
if (!handshake_end) {
if (ioc->encinput.offset >= 4096) {
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE);
error_setg(errp,
"End of headers not found in first 4096 bytes");
- return -1;
+ return 1;
} else {
return 0;
}
}
*handshake_end = '\0';
- if (qio_channel_websock_handshake_process(ioc,
- (char *)ioc->encinput.buffer,
- errp) < 0) {
- return -1;
- }
+ qio_channel_websock_handshake_process(ioc,
+ (char *)ioc->encinput.buffer,
+ errp);
buffer_advance(&ioc->encinput,
handshake_end - (char *)ioc->encinput.buffer +
@@ -430,7 +515,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
&err);
if (ret < 0) {
- trace_qio_channel_websock_handshake_fail(ioc);
+ trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
@@ -438,8 +523,16 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
buffer_advance(&wioc->encoutput, ret);
if (wioc->encoutput.offset == 0) {
- trace_qio_channel_websock_handshake_complete(ioc);
- qio_task_complete(task);
+ if (wioc->io_err) {
+ trace_qio_channel_websock_handshake_fail(
+ ioc, error_get_pretty(wioc->io_err));
+ qio_task_set_error(task, wioc->io_err);
+ wioc->io_err = NULL;
+ qio_task_complete(task);
+ } else {
+ trace_qio_channel_websock_handshake_complete(ioc);
+ qio_task_complete(task);
+ }
return FALSE;
}
trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT);
@@ -458,7 +551,12 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
ret = qio_channel_websock_handshake_read(wioc, &err);
if (ret < 0) {
- trace_qio_channel_websock_handshake_fail(ioc);
+ /*
+ * We only take this path on a fatal I/O error reading from
+ * client connection, as most of the time we have an
+ * HTTP 4xx err response to send instead
+ */
+ trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
@@ -469,6 +567,10 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
return TRUE;
}
+ if (err) {
+ error_propagate(&wioc->io_err, err);
+ }
+
trace_qio_channel_websock_handshake_reply(ioc);
qio_channel_add_watch(
wioc->master,
@@ -480,7 +582,9 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
}
-static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
+static void qio_channel_websock_encode_buffer(QIOChannelWebsock *ioc,
+ Buffer *output,
+ uint8_t opcode, Buffer *buffer)
{
size_t header_size;
union {
@@ -488,39 +592,66 @@ static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
QIOChannelWebsockHeader ws;
} header;
- if (!ioc->rawoutput.offset) {
- return;
- }
-
- header.ws.b0 = (1 << QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN) |
- (QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &
- QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
- if (ioc->rawoutput.offset <
- QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
- header.ws.b1 = (uint8_t)ioc->rawoutput.offset;
+ header.ws.b0 = QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN |
+ (opcode & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
+ if (buffer->offset < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
+ header.ws.b1 = (uint8_t)buffer->offset;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
- } else if (ioc->rawoutput.offset <
+ } else if (buffer->offset <
QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT;
- header.ws.u.s16.l16 = cpu_to_be16((uint16_t)ioc->rawoutput.offset);
+ header.ws.u.s16.l16 = cpu_to_be16((uint16_t)buffer->offset);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
} else {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT;
- header.ws.u.s64.l64 = cpu_to_be64(ioc->rawoutput.offset);
+ header.ws.u.s64.l64 = cpu_to_be64(buffer->offset);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
}
header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK;
- buffer_reserve(&ioc->encoutput, header_size + ioc->rawoutput.offset);
- buffer_append(&ioc->encoutput, header.buf, header_size);
- buffer_append(&ioc->encoutput, ioc->rawoutput.buffer,
- ioc->rawoutput.offset);
+ trace_qio_channel_websock_encode(ioc, opcode, header_size, buffer->offset);
+ buffer_reserve(output, header_size + buffer->offset);
+ buffer_append(output, header.buf, header_size);
+ buffer_append(output, buffer->buffer, buffer->offset);
+}
+
+
+static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
+{
+ if (!ioc->rawoutput.offset) {
+ return;
+ }
+ qio_channel_websock_encode_buffer(
+ ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME,
+ &ioc->rawoutput);
buffer_reset(&ioc->rawoutput);
}
-static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
- Error **errp)
+static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *, Error **);
+
+
+static void qio_channel_websock_write_close(QIOChannelWebsock *ioc,
+ uint16_t code, const char *reason)
+{
+ buffer_reserve(&ioc->rawoutput, 2 + (reason ? strlen(reason) : 0));
+ *(uint16_t *)(ioc->rawoutput.buffer + ioc->rawoutput.offset) =
+ cpu_to_be16(code);
+ ioc->rawoutput.offset += 2;
+ if (reason) {
+ buffer_append(&ioc->rawoutput, reason, strlen(reason));
+ }
+ qio_channel_websock_encode_buffer(
+ ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
+ &ioc->rawoutput);
+ buffer_reset(&ioc->rawoutput);
+ qio_channel_websock_write_wire(ioc, NULL);
+ qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+}
+
+
+static int qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
+ Error **errp)
{
unsigned char opcode, fin, has_mask;
size_t header_size;
@@ -532,6 +663,9 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
error_setg(errp,
"Decoding header but %zu bytes of payload remain",
ioc->payload_remain);
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR,
+ "internal server error");
return -1;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT) {
@@ -539,33 +673,57 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
return QIO_CHANNEL_ERR_BLOCK;
}
- fin = (header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN) >>
- QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN;
+ fin = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN;
opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE;
- has_mask = (header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK) >>
- QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK;
+ has_mask = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK;
payload_len = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN;
+ /* Save or restore opcode. */
+ if (opcode) {
+ ioc->opcode = opcode;
+ } else {
+ opcode = ioc->opcode;
+ }
+
+ trace_qio_channel_websock_header_partial_decode(ioc, payload_len,
+ fin, opcode, (int)has_mask);
+
if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
/* disconnect */
return 0;
}
/* Websocket frame sanity check:
- * * Websocket fragmentation is not supported.
- * * All websockets frames sent by a client have to be masked.
- * * Only binary encoding is supported.
+ * * Fragmentation is only supported for binary frames.
+ * * All frames sent by a client MUST be masked.
+ * * Only binary and ping/pong encoding is supported.
*/
if (!fin) {
- error_setg(errp, "websocket fragmentation is not supported");
- return -1;
+ if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
+ error_setg(errp, "only binary websocket frames may be fragmented");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_POLICY ,
+ "only binary frames may be fragmented");
+ return -1;
+ }
+ } else {
+ if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PING &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PONG) {
+ error_setg(errp, "unsupported opcode: %#04x; only binary, close, "
+ "ping, and pong websocket frames are supported", opcode);
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA ,
+ "only binary, close, ping, and pong frames are supported");
+ return -1;
+ }
}
if (!has_mask) {
- error_setg(errp, "websocket frames must be masked");
- return -1;
- }
- if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
- error_setg(errp, "only binary websocket frames are supported");
+ error_setg(errp, "client websocket frames must be masked");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
+ "client frames must be masked");
return -1;
}
@@ -573,6 +731,12 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
ioc->payload_remain = payload_len;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
ioc->mask = header->u.m;
+ } else if (opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
+ error_setg(errp, "websocket control frame is too large");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
+ "control frame is too large");
+ return -1;
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT &&
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) {
ioc->payload_remain = be16_to_cpu(header->u.s16.l16);
@@ -588,54 +752,90 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
return QIO_CHANNEL_ERR_BLOCK;
}
+ trace_qio_channel_websock_header_full_decode(
+ ioc, header_size, ioc->payload_remain, ioc->mask.u);
buffer_advance(&ioc->encinput, header_size);
- return 1;
+ return 0;
}
-static ssize_t qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
- Error **errp)
+static int qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
+ Error **errp)
{
size_t i;
- size_t payload_len;
+ size_t payload_len = 0;
uint32_t *payload32;
- if (!ioc->payload_remain) {
- error_setg(errp,
- "Decoding payload but no bytes of payload remain");
- return -1;
- }
+ if (ioc->payload_remain) {
+ /* If we aren't at the end of the payload, then drop
+ * off the last bytes, so we're always multiple of 4
+ * for purpose of unmasking, except at end of payload
+ */
+ if (ioc->encinput.offset < ioc->payload_remain) {
+ /* Wait for the entire payload before processing control frames
+ * because the payload will most likely be echoed back. */
+ if (ioc->opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
+ } else {
+ payload_len = ioc->payload_remain;
+ }
+ if (payload_len == 0) {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
- /* If we aren't at the end of the payload, then drop
- * off the last bytes, so we're always multiple of 4
- * for purpose of unmasking, except at end of payload
- */
- if (ioc->encinput.offset < ioc->payload_remain) {
- payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
- } else {
- payload_len = ioc->payload_remain;
- }
- if (payload_len == 0) {
- return QIO_CHANNEL_ERR_BLOCK;
+ ioc->payload_remain -= payload_len;
+
+ /* unmask frame */
+ /* process 1 frame (32 bit op) */
+ payload32 = (uint32_t *)ioc->encinput.buffer;
+ for (i = 0; i < payload_len / 4; i++) {
+ payload32[i] ^= ioc->mask.u;
+ }
+ /* process the remaining bytes (if any) */
+ for (i *= 4; i < payload_len; i++) {
+ ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
+ }
}
- ioc->payload_remain -= payload_len;
+ trace_qio_channel_websock_payload_decode(
+ ioc, ioc->opcode, ioc->payload_remain);
- /* unmask frame */
- /* process 1 frame (32 bit op) */
- payload32 = (uint32_t *)ioc->encinput.buffer;
- for (i = 0; i < payload_len / 4; i++) {
- payload32[i] ^= ioc->mask.u;
- }
- /* process the remaining bytes (if any) */
- for (i *= 4; i < payload_len; i++) {
- ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
- }
+ if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
+ if (payload_len) {
+ /* binary frames are passed on */
+ buffer_reserve(&ioc->rawinput, payload_len);
+ buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
+ }
+ } else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
+ /* close frames are echoed back */
+ error_setg(errp, "websocket closed by peer");
+ if (payload_len) {
+ /* echo client status */
+ qio_channel_websock_encode_buffer(
+ ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
+ &ioc->encinput);
+ qio_channel_websock_write_wire(ioc, NULL);
+ qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+ } else {
+ /* send our own status */
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_NORMAL, "peer requested close");
+ }
+ return -1;
+ } else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_PING) {
+ /* ping frames produce an immediate reply */
+ buffer_reset(&ioc->ping_reply);
+ qio_channel_websock_encode_buffer(
+ ioc, &ioc->ping_reply, QIO_CHANNEL_WEBSOCK_OPCODE_PONG,
+ &ioc->encinput);
+ } /* pong frames are ignored */
- buffer_reserve(&ioc->rawinput, payload_len);
- buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
- buffer_advance(&ioc->encinput, payload_len);
- return payload_len;
+ if (payload_len) {
+ buffer_advance(&ioc->encinput, payload_len);
+ }
+ return 0;
}
@@ -688,6 +888,7 @@ static void qio_channel_websock_finalize(Object *obj)
buffer_free(&ioc->encoutput);
buffer_free(&ioc->rawinput);
buffer_free(&ioc->rawoutput);
+ buffer_free(&ioc->ping_reply);
object_unref(OBJECT(ioc->master));
if (ioc->io_tag) {
g_source_remove(ioc->io_tag);
@@ -715,8 +916,8 @@ static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
if (ret < 0) {
return ret;
}
- if (ret == 0 &&
- ioc->encinput.offset == 0) {
+ if (ret == 0 && ioc->encinput.offset == 0) {
+ ioc->io_eof = TRUE;
return 0;
}
ioc->encinput.offset += ret;
@@ -728,10 +929,6 @@ static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
if (ret < 0) {
return ret;
}
- if (ret == 0) {
- ioc->io_eof = TRUE;
- break;
- }
}
ret = qio_channel_websock_decode_payload(ioc, errp);
@@ -748,7 +945,13 @@ static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc,
{
ssize_t ret;
ssize_t done = 0;
- qio_channel_websock_encode(ioc);
+
+ /* ping replies take priority over binary data */
+ if (!ioc->ping_reply.offset) {
+ qio_channel_websock_encode(ioc);
+ } else if (!ioc->encoutput.offset) {
+ buffer_move_empty(&ioc->encoutput, &ioc->ping_reply);
+ }
while (ioc->encoutput.offset > 0) {
ret = qio_channel_write(ioc->master,
@@ -823,7 +1026,7 @@ static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
return;
}
- if (ioc->encoutput.offset) {
+ if (ioc->encoutput.offset || ioc->ping_reply.offset) {
cond |= G_IO_OUT;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER &&
@@ -985,6 +1188,7 @@ static int qio_channel_websock_close(QIOChannel *ioc,
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+ trace_qio_channel_websock_close(ioc);
return qio_channel_close(wioc->master, errp);
}
@@ -996,14 +1200,12 @@ struct QIOChannelWebsockSource {
};
static gboolean
-qio_channel_websock_source_prepare(GSource *source,
- gint *timeout)
+qio_channel_websock_source_check(GSource *source)
{
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0;
- *timeout = -1;
- if (wsource->wioc->rawinput.offset) {
+ if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) {
cond |= G_IO_IN;
}
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
@@ -1014,19 +1216,11 @@ qio_channel_websock_source_prepare(GSource *source,
}
static gboolean
-qio_channel_websock_source_check(GSource *source)
+qio_channel_websock_source_prepare(GSource *source,
+ gint *timeout)
{
- QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
- GIOCondition cond = 0;
-
- if (wsource->wioc->rawinput.offset) {
- cond |= G_IO_IN;
- }
- if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
- cond |= G_IO_OUT;
- }
-
- return cond & wsource->condition;
+ *timeout = -1;
+ return qio_channel_websock_source_check(source);
}
static gboolean
@@ -1036,17 +1230,9 @@ qio_channel_websock_source_dispatch(GSource *source,
{
QIOChannelFunc func = (QIOChannelFunc)callback;
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
- GIOCondition cond = 0;
-
- if (wsource->wioc->rawinput.offset) {
- cond |= G_IO_IN;
- }
- if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
- cond |= G_IO_OUT;
- }
return (*func)(QIO_CHANNEL(wsource->wioc),
- (cond & wsource->condition),
+ qio_channel_websock_source_check(source),
user_data);
}
diff --git a/io/trace-events b/io/trace-events
index 3d233698d0..801b5dcb61 100644
--- a/io/trace-events
+++ b/io/trace-events
@@ -46,8 +46,13 @@ qio_channel_websock_new_server(void *ioc, void *master) "Websock new client ioc=
qio_channel_websock_handshake_start(void *ioc) "Websock handshake start ioc=%p"
qio_channel_websock_handshake_pending(void *ioc, int status) "Websock handshake pending ioc=%p status=%d"
qio_channel_websock_handshake_reply(void *ioc) "Websock handshake reply ioc=%p"
-qio_channel_websock_handshake_fail(void *ioc) "Websock handshake fail ioc=%p"
+qio_channel_websock_handshake_fail(void *ioc, const char *msg) "Websock handshake fail ioc=%p err=%s"
qio_channel_websock_handshake_complete(void *ioc) "Websock handshake complete ioc=%p"
+qio_channel_websock_header_partial_decode(void *ioc, size_t payloadlen, unsigned char fin, unsigned char opcode, unsigned char has_mask) "Websocket header decoded ioc=%p payload-len=%zu fin=0x%x opcode=0x%x has_mask=0x%x"
+qio_channel_websock_header_full_decode(void *ioc, size_t headerlen, size_t payloadlen, uint32_t mask) "Websocket header decoded ioc=%p header-len=%zu payload-len=%zu mask=0x%x"
+qio_channel_websock_payload_decode(void *ioc, uint8_t opcode, size_t payload_remain) "Websocket header decoded ioc=%p opcode=0x%x payload-remain=%zu"
+qio_channel_websock_encode(void *ioc, uint8_t opcode, size_t payloadlen, size_t headerlen) "Websocket encoded ioc=%p opcode=0x%x header-len=%zu payload-len=%zu"
+qio_channel_websock_close(void *ioc) "Websocket close ioc=%p"
# io/channel-command.c
qio_channel_command_new_pid(void *ioc, int writefd, int readfd, int pid) "Command new pid ioc=%p writefd=%d readfd=%d pid=%d"
diff --git a/monitor.c b/monitor.c
index 94fb197c0d..fe0d1bdbb4 100644
--- a/monitor.c
+++ b/monitor.c
@@ -2697,6 +2697,7 @@ static const mon_cmd_t *search_dispatch_table(const mon_cmd_t *disp_table,
* the command is found in a sub-command table.
*/
static const mon_cmd_t *monitor_parse_command(Monitor *mon,
+ const char *cmdp_start,
const char **cmdp,
mon_cmd_t *table)
{
@@ -2712,7 +2713,7 @@ static const mon_cmd_t *monitor_parse_command(Monitor *mon,
cmd = search_dispatch_table(table, cmdname);
if (!cmd) {
monitor_printf(mon, "unknown command: '%.*s'\n",
- (int)(p - *cmdp), *cmdp);
+ (int)(p - cmdp_start), cmdp_start);
return NULL;
}
@@ -2724,7 +2725,7 @@ static const mon_cmd_t *monitor_parse_command(Monitor *mon,
*cmdp = p;
/* search sub command */
if (cmd->sub_table != NULL && *p != '\0') {
- return monitor_parse_command(mon, cmdp, cmd->sub_table);
+ return monitor_parse_command(mon, cmdp_start, cmdp, cmd->sub_table);
}
return cmd;
@@ -3108,7 +3109,7 @@ static void handle_hmp_command(Monitor *mon, const char *cmdline)
trace_handle_hmp_command(mon, cmdline);
- cmd = monitor_parse_command(mon, &cmdline, mon->cmd_table);
+ cmd = monitor_parse_command(mon, cmdline, &cmdline, mon->cmd_table);
if (!cmd) {
return;
}
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index f0bec204b3..7833631275 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -75,6 +75,9 @@ static void vnc_tls_handshake_done(QIOTask *task,
vnc_client_error(vs);
error_free(err);
} else {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
start_auth_vencrypt_subauth(vs);
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index aeaafe2c21..6ccad22cef 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -37,6 +37,9 @@ static void vncws_tls_handshake_done(QIOTask *task,
error_free(err);
} else {
VNC_DEBUG("TLS handshake complete, starting websocket handshake\n");
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
vs->ioc_tag = qio_channel_add_watch(
QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL);
}
@@ -97,6 +100,9 @@ static void vncws_handshake_done(QIOTask *task,
} else {
VNC_DEBUG("Websock handshake complete, starting VNC protocol\n");
vnc_start_protocol(vs);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
}
diff --git a/ui/vnc.c b/ui/vnc.c
index af810f0547..9f8d5a1b1f 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1122,6 +1122,7 @@ static void vnc_disconnect_start(VncState *vs)
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
}
qio_channel_close(vs->ioc, NULL);
vs->disconnecting = TRUE;
@@ -2934,6 +2935,9 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qio_channel_set_blocking(vs->ioc, false, NULL);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
if (websocket) {
vs->websocket = 1;
if (vd->tlscreds) {