diff options
-rw-r--r-- | hmp-commands-info.hx | 138 | ||||
-rw-r--r-- | hmp.c | 2 | ||||
-rw-r--r-- | hw/usb/Makefile.objs | 3 | ||||
-rw-r--r-- | hw/usb/bus.c | 4 | ||||
-rw-r--r-- | hw/usb/ccid-card-passthru.c | 2 | ||||
-rw-r--r-- | hw/vfio/pci-quirks.c | 114 | ||||
-rw-r--r-- | hw/vfio/pci.c | 17 | ||||
-rw-r--r-- | hw/vfio/pci.h | 4 | ||||
-rw-r--r-- | include/io/channel-websock.h | 2 | ||||
-rw-r--r-- | io/channel-websock.c | 496 | ||||
-rw-r--r-- | io/trace-events | 7 | ||||
-rw-r--r-- | monitor.c | 7 | ||||
-rw-r--r-- | ui/vnc-auth-vencrypt.c | 3 | ||||
-rw-r--r-- | ui/vnc-ws.c | 6 | ||||
-rw-r--r-- | ui/vnc.c | 4 |
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 @@ -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" @@ -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); } @@ -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) { |