[hybi] Re: Questions about RFC6455 (implementing websockets)

Adam Rice <ricea@google.com> Thu, 12 September 2024 08:27 UTC

Return-Path: <ricea@google.com>
X-Original-To: hybi@ietfa.amsl.com
Delivered-To: hybi@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id EA0D3C1CAF58 for <hybi@ietfa.amsl.com>; Thu, 12 Sep 2024 01:27:38 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -17.607
X-Spam-Level:
X-Spam-Status: No, score=-17.607 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, ENV_AND_HDR_SPF_MATCH=-0.5, HTML_MESSAGE=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01, URIBL_BLOCKED=0.001, URIBL_DBL_BLOCKED_OPENDNS=0.001, URIBL_ZEN_BLOCKED_OPENDNS=0.001, USER_IN_DEF_DKIM_WL=-7.5, USER_IN_DEF_SPF_WL=-7.5] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (2048-bit key) header.d=google.com
Received: from mail.ietf.org ([50.223.129.194]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 0prl-Kc-lGo2 for <hybi@ietfa.amsl.com>; Thu, 12 Sep 2024 01:27:35 -0700 (PDT)
Received: from mail-il1-x12b.google.com (mail-il1-x12b.google.com [IPv6:2607:f8b0:4864:20::12b]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (P-256) server-digest SHA256) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 3175EC1D4A7E for <hybi@ietf.org>; Thu, 12 Sep 2024 01:27:35 -0700 (PDT)
Received: by mail-il1-x12b.google.com with SMTP id e9e14a558f8ab-39d47a9ffd5so3252775ab.2 for <hybi@ietf.org>; Thu, 12 Sep 2024 01:27:35 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1726129654; x=1726734454; darn=ietf.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=Ydt0qA/Sdoa+UX/FzelAW1q/IC90tVywgoPSuOLbYvk=; b=fIiAvurrRbUjZn9vaPBmeQ43aULmlTDHvcqX7Aqzw8VcDZxykUtOStWZNC6G1I0f+w PHRMb+Fl31jxMZmTFOxxgxY9B6FM6IlIdA7b8swQHT10DXmJx/VtdEcDnf6cBCIdiYm0 if3oGt/jVjnuzSCreRLEnIsvxswGtkD8zwTrN402gdHWQ8VxURIfrSAQ3xzJpX2AsKol JyocF2m+USUl6+IcWB8gGASHWeVk4+2285E/zPxXCFsPkeuSIHCZC+736adyayzWGMWv rVtXKKOZF6KgsiaKpJL2c13w0kPK+j7gQHcGqktJ2AxCLJVdYLJOCftO3rgbubxoLchN 6qpA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1726129654; x=1726734454; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=Ydt0qA/Sdoa+UX/FzelAW1q/IC90tVywgoPSuOLbYvk=; b=ZCpU0mHH4K6Xwq3PuysmnKdxKvQrmnAbg8btcohL+Aq2/djH/61QmiaaCQg9CclAOJ MuBbfZX9mjprF2RBRNO2mBDA159Ih9gEnDTc74ryEnHxfS/l3n1QcQ3sM3up+Gt+Huwn aGZBbXuOdo0kl9QG1Vh6Y4U9R87uJ0i1hsjXFiu+W+YnWc7KrJUY2B+oQixtPv3wbpDB Ts9hIu2bgZDR6hiHsyekiaoWFyNX80dE6RfBrf1QeFrYvQEhr2Xk+aljv7EmTbwtE/vI QbDWD0pDJn6jSRvTMCBCikgSklEQk2P+yExLdqDMJZz4EyZVe5Op9GsujOjtmIZpUbQD 8F8w==
X-Gm-Message-State: AOJu0YxzjtPFzfvFpnHu6ihUmT4QbXI1+5JrjwRxnFIOzPwwSkxRYnfQ Fo9LL3LyecJF95CMuHF7UItIa1kM03pjDXFcNgtCS5UbJLRk+SZRWZSpOdavlxB+bzeQYX5SMss km+Rd4h5dY/oWC5Meo3zocQDslO3vSx7rGU9eVAxIlYBdzvECaKm3
X-Google-Smtp-Source: AGHT+IEzqzMcYWM5BJUZcC+eb9gvZrIPrKcCWPssy+0XtkkNL8CtG4w7xAujaNDLl8LDM2+m4mTXFtHxxRD+9XzGpTo=
X-Received: by 2002:a05:6e02:219d:b0:375:dc39:cfd2 with SMTP id e9e14a558f8ab-3a0848f9666mr16888335ab.11.1726129653770; Thu, 12 Sep 2024 01:27:33 -0700 (PDT)
MIME-Version: 1.0
References: <9c213fb1-8f31-4c9a-a8a8-562693a3f7b7@amichais.net>
In-Reply-To: <9c213fb1-8f31-4c9a-a8a8-562693a3f7b7@amichais.net>
From: Adam Rice <ricea@google.com>
Date: Thu, 12 Sep 2024 17:27:20 +0900
Message-ID: <CAHixhFp1+bgvfFrro-a-SaqBHkVUG3PX8Fwfv9+uu_pApWVSVA@mail.gmail.com>
To: "A. Rothman" <amichai2@amichais.net>
Content-Type: multipart/alternative; boundary="000000000000d9f67f0621e7e0cd"
Message-ID-Hash: BEKK3YLPXKHSUU6VD3WMZCGGARD6QISZ
X-Message-ID-Hash: BEKK3YLPXKHSUU6VD3WMZCGGARD6QISZ
X-MailFrom: ricea@google.com
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-hybi.ietf.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
CC: hybi@ietf.org
X-Mailman-Version: 3.3.9rc4
Precedence: list
Subject: [hybi] Re: Questions about RFC6455 (implementing websockets)
List-Id: Server-Initiated HTTP <hybi.ietf.org>
Archived-At: <https://mailarchive.ietf.org/arch/msg/hybi/i0APXBzwlSf3_a4THtnJ98skL6A>
List-Archive: <https://mailarchive.ietf.org/arch/browse/hybi>
List-Help: <mailto:hybi-request@ietf.org?subject=help>
List-Owner: <mailto:hybi-owner@ietf.org>
List-Post: <mailto:hybi@ietf.org>
List-Subscribe: <mailto:hybi-join@ietf.org>
List-Unsubscribe: <mailto:hybi-leave@ietf.org>

Good analysis. You can see existing errata at
https://www.rfc-editor.org/errata_search.php?rfc=6455 and report new errata
using the link at the bottom of the page.

You should be aware that since the IETF hybi working group was closed, it
is unclear who is responsible for updates to RFC6455, and in practice
updates haven't been happening.

I was not involved in the standardisation of RFC6455, but I've been working
with WebSockets for a long time and I'm one of the maintainers of
pywebsocket, a server that is used in browser testing. I will comment from
the point-of-view of what pywebsocket does.

On Thu, 5 Sept 2024 at 02:31, A. Rothman <amichai2@amichais.net> wrote:

> 1: 1.8
>
>    "At the time of writing of this
>    specification, it should be noted that connections on ports 80 and
>    443 have significantly different success rates, with connections on
>    port 443 being significantly more likely to succeed, though this may
>    change with time."
>
>    I could neither understand what this is trying to say (Is this a
> general statistic about port-scanning? Or a fun fact specific to websocket
> deployments?) nor why "it should be noted" - how is it relevant to the
> websocket spec? Would removing this sentence entirely make any difference
> in understanding, implementing or using websockets? I'm not sure if I'm
> missing an important point or this is really redundant.
>

Safe to ignore.


> 2: 4.2.1.3
>
>    "An |Upgrade| header field containing the value "websocket""
>
>    Does "containing" imply it can have other elements as well (as the
> Upgrade field is normally defined), or does it mean the entire value must
> exactly equal "websocket"? The former makes more sense, being standard, but
> I've seen interpretations both ways, and mention of clients that reject
> anything but the single exact value. Perhaps this can be clarified in the
> spec.
>

pywebsocket requires an exact case-insensitive match for "websocket", so if
you only have browser clients it's safe to do the same.


> 4: 4.2.2.4 (regarding /key/)
>
>   "It is not necessary for the server to base64-decode the
> |Sec-WebSocket-Key| value."
>
>   Although technically correct, this is somewhere between a half-truth and
> misleading: section 4.2.1 states that
>
>   "If the server, while reading the handshake, finds that the client did
>    not send a handshake that matches the description below [...]
>    the server MUST stop processing the client's handshake and return an
> HTTP
>    response with an appropriate error code"
>
>    and the "description below" of the key field is:
>
>    "A |Sec-WebSocket-Key| header field with a base64-encoded (see
>    Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
>    length."
>

In practice, pywebsocket checks that the string looks like base64 using the
regular expression /^[+/0-9A-Za-z]{21}[AQgw]==$/ and then decodes it to
check the length. I think the first step is probably sufficient by itself,
but performance isn't a priority for pywebsocket.


> 6: 5.1
>
>   "a client MUST mask all frames that it sends to the server" and
>   "A server MUST NOT mask any frames that it sends to the client"
>
>   However in 5.2, referring to the Mask field definition, it only says:
>
>   "All frames sent from client to server have this bit set to 1."
>
>   I think it should also say "All frames sent from server to client have
> this bit set to 0". It's strange to mention the requirement in one
> direction and not the other here, even though both are MUST (NOT) earlier.
> It would make sense to mention either both directions or none, not just one.
>
My understanding is that there was an open question of whether masking
would be required in both directions, so the standard left it easy to
change.

7.1.5
>   "_The WebSocket Connection Close Code_ is defined as the status code (Section
> 7.4 <https://datatracker.ietf.org/doc/html/rfc6455#section-7.4>)
> contained in
>   the first Close control frame received by the application implementing
> this protocol."
>
>   "the first Close control frame received" implies there can be more than
> one (also in 7.1.6)? What scenario is this? Why not explicitly disallow
> sending more than one? How is the receiver supposed to react to the second
> one?
>
My interpretation is that this is just protection against a badly-behaved
peer that sends multiple close codes.

Chrome ignores an unexpected Close frame. pywebsocket always closes the
connection immediately after receiving a Close frame, so the issue cannot
arise.


> 9: 6.1.1
>
>   "If at any point the state of the WebSocket connection changes, the
> endpoint MUST abort the following steps."
>
>   It isn't entirely clear what "at any point" means here - in between the
> listed steps? within a step? more specifically, in step 7 it transmits
> potentially multiple frames - is that an atomic operation? or MUST it abort
> also in between frames/fragments? or MUST it abort in the middle of a
> single (possibly very long) frame? This has significant implications to
> what happens next e.g. if aborted in the middle of a frame, or even in
> between frames in a multi-frame message, the framing protocol is then
> broken and no close frame can be sent, etc., whereas if step 7 is
> all-or-nothing with regards to the MUST abort, then the closing can
> continue gracefully and no data is lost). This should really be clarified,
> as this is a MUST that can be interpreted in significantly different ways.
>

My philosophy is that frames are atomic, but messages are not. In practice,
many implementations avoid the question completely by sending every message
in a single frame. Obviously if the underlying connection goes down in the
middle of sending a frame we can't continue anyway, so the question is moot.

Chrome's implementation may be slightly wrong here, as we have an internal
queue of frames to be written to the OS's socket buffer, and we don't have
a way for a Close frame to jump the queue.


>   2 - More generally, it's not entirely clear why it would be required to
> close the socket in any case if the handshake fails, whether according to
> this section or other sections. To my understanding the handshake is still
> using the HTTP protocol, so sending an error status code should be enough,
> and the client should be free to reuse the connection to try again, or to
> send unrelated non-websocket requests for other HTTP resources on the same
> TCP connection. Specifically, section 4.4 describes such a scenario for
> protocol version negotiations - why should it be necessary to close the
> connection in the middle before the second request with the corrected
> version number is sent? (section 4.2.2, under /version/, requires aborting
> the handshake).
>

As a practical matter, in order for NTLM or Negotiate HTTP authentication
to work it's necessary for the client to be able to receive a 401 response
and send another request with updated credentials on the same connection. I
was never sure whether RFC6455 permits this, but I implemented it for
Chrome anyway as it was required for interoperability.

Thanks,
Adam Rice
Chromium Networking