[Spice-devel] [PATCH spice ] Add support for clients connecting with the WebSocket protocol.

Jeremy White jwhite at codeweavers.com
Mon Nov 2 13:20:25 PST 2015


Hi Pavel,

Thanks for the review.

>>
>> +static void reds_handle_new_link(RedLinkInfo *link);
>>  static void reds_handle_read_header_done(void *opaque)
>>  {
>>      RedLinkInfo *link = (RedLinkInfo *)opaque;
>> @@ -2182,6 +2183,11 @@ static void reds_handle_read_header_done(void *opaque)
>>      header->size = GUINT32_FROM_LE(header->size);
>>  
>>      if (header->magic != SPICE_MAGIC) {
>> +        if (reds_stream_is_websocket(link->stream,
>> +            (guchar *) header, sizeof(*header))) {
>> +            reds_handle_new_link(link);
>> +            return;
>> +        }
> This looks hacky, it would be nice to detect that we are dealing with websockets
> before reds_handle_read_header_done() is called.

The problem is that we cannot know we're dealing with websockets until
we've done an initial read.  And a websocket connection is going to have
to write at least 16 bytes, so it seemed to me that we may as well read
the 16 bytes in a SpiceLinkHeader.

The alternate I considered was to inject a 3 byte read before
read_header_done and then have that do an async read of
sizeof(SpiceLinkHeader) - 3 if we do not have the 'GET' that signals a
websocket connection.  That didn't seem any better to me; I'm happy to
defer to others if they feel that would be superior (or if there is an
altogether different approach I've missed).

In reviewing this code, there is a flaw I've skipped over.  That is, the
current implementation makes the assumption that the websocket header
will arrive in a single packet; that there is no chance we'll get a few
bytes, and then need another read in order to get the remaining bytes.
I think that, in practice, this will always be the case.  Further,
afaik, we don't really have a mechanism to do a variable length/timed
read in the current code base.  My instinct is to say that limitation is
acceptable; that the complexity of a theoretically correct
implementation would do more harm than accepting this assumption.  But I
bring it up to give others the chance to correct me <grin>.

[snip]
>> +        websocket_create_reply(rbuf, len, outbuf);
>> +        rc = stream->priv->write(stream, outbuf, strlen(outbuf));
>> +        if (rc == strlen(outbuf)) {
>> +            stream->priv->ws = spice_malloc0(sizeof(*stream->priv->ws));
> 
> It is not freed

Yes, right, will fix it.

>> +int websocket_is_start(gchar *buf, int len)
> 
> It sounds like boolean

Fair enough, I'll fix it.

> 
>> +{
>> +    if (len < 4)
>> +        return 0;
>> +
>> +    if (find_str(buf, "GET", len) == (buf + 3) &&
>> +            find_str(buf, "Sec-WebSocket-Protocol: binary", len) &&
>> +            find_str(buf, "Sec-WebSocket-Key:", len) &&
>> +            buf[len - 2] == '\r' && buf[len - 1] == '\n')
>> +        return 1;
>> +
>> +    return 0;
>> +}
>> +
>> +int websocket_create_reply(gchar *buf, int len, gchar *outbuf)
> 
> It always returns 0

Yep, should be a void, I'll fix it.

> 
>> +{
>> +    gchar *key;
>> +
>> +    key = generate_reply_key(buf, len);
> 
> Can NULL be valid value? Should we have a warning for it, or change the return
> value?

NULL is not a valid value; this code path should only be exercised if
we've found the Sec-WebSocket-Key already.

> 
>> +    sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
>> +                    "Upgrade: websocket\r\n"
>> +                    "Connection: Upgrade\r\n"
>> +                    "Sec-WebSocket-Accept: %s\r\n"
>> +                    "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
>> +    g_free(key);
>> +    return 0;
>> +}
>> diff --git a/server/websocket.h b/server/websocket.h
>> new file mode 100644
>> index 0000000..b35cb5e
>> --- /dev/null
>> +++ b/server/websocket.h
>> @@ -0,0 +1,62 @@
>> +/*
>> + *  Copyright (C) 2015 Jeremy White
>> + *
>> + *  This library is free software; you can redistribute it and/or
>> + *  modify it under the terms of the GNU Lesser General Public
>> + *  License as published by the Free Software Foundation; either
>> + *  version 2.1 of the License, or (at your option) any later version.
>> + *
>> + *  This library is distributed in the hope that it will be useful,
>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + *  Lesser General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU Lesser General Public
>> + *  License along with this library; if not, see <http://www.gnu.org/licenses
>> />.
>> + */
>> +
>> +#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
>> +
>> +#define FIN_FLAG        0x80
>> +#define TYPE_MASK       0x0F
>> +
>> +#define BINARY_FRAME    0x2
>> +#define CLOSE_FRAME     0x8
>> +#define PING_FRAME      0x9
>> +#define PONG_FRAME      0xA
>> +
>> +#define LENGTH_MASK     0x7F
>> +#define LENGTH_16BIT    0x7E
>> +#define LENGTH_64BIT    0x7F
>> +
>> +#define MASK_FLAG       0x80
>> +
>> +#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
>> +
>> +typedef struct
>> +{
>> +    int type;
>> +    int masked;
>> +    guint8 header[WEBSOCKET_MAX_HEADER_SIZE];
>> +    int header_pos;
>> +    int frame_ready:1;
>> +    guint8 mask[4];
>> +    guint64 relayed;
>> +    guint64 expected_len;
>> +} websocket_frame_t;
> 
> I would prefer to have these ^ definitions private.

I could probably use some guidance; there may be conventions I've
missed.  It would be easy to move most of the websocket specific
constants into websocket.c, I'm happy to do that.

It would be a bit more work to make the websocket_frame_t type be
opaque; it would require an allocation and a free I'd find untidy.  I'm
willing to do the work if that's the expected standard; but I don't see
the harm of having a non private WEBSOCKET_MAX_HEADER_SIZE and
websocket_frame_t structure.

> 
>> +
>> +typedef size_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int
>> iovcnt);
>> +typedef size_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t
>> nbyte);
>> +typedef size_t (*websocket_read_cb_t)(void *opaque, const void *buf, size_t
>> nbyte);
> 
> RedsStreamPrivate has these ^ as ssize_t

Yes, right, I'll fix it.

> 
>> +
>> +int websocket_is_start(gchar *buf, int len);
>> +int websocket_create_reply(gchar *buf, int len, gchar *outbuf);
>> +int websocket_read(void *opaque, guchar *buf, int len, websocket_frame_t
>> *frame,
>> +         websocket_read_cb_t read_cb,
>> +         websocket_write_cb_t write_cb);
>> +int websocket_write(void *opaque, const guchar *buf, int len, guint64
>> *remainder,
>> +         websocket_write_cb_t write_cb);
>> +int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, guint64
>> *remainder,
>> +         websocket_writev_cb_t writev_cb);
>> +void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
>> +
> extra line

Right, will fix.

Thanks again for the review.

Cheers,

Jeremy


More information about the Spice-devel mailing list