Re: [hybi] WebSocket -76 is incompatible with HTTP reverse proxies

Willy Tarreau <w@1wt.eu> Thu, 22 July 2010 21:38 UTC

Return-Path: <w@1wt.eu>
X-Original-To: hybi@core3.amsl.com
Delivered-To: hybi@core3.amsl.com
Received: from localhost (localhost [127.0.0.1]) by core3.amsl.com (Postfix) with ESMTP id 588E13A69C5 for <hybi@core3.amsl.com>; Thu, 22 Jul 2010 14:38:09 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.7
X-Spam-Level:
X-Spam-Status: No, score=-2.7 tagged_above=-999 required=5 tests=[AWL=-1.257, BAYES_00=-2.599, HELO_IS_SMALL6=0.556, J_CHICKENPOX_24=0.6]
Received: from mail.ietf.org ([64.170.98.32]) by localhost (core3.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id eECGRiI6s6TK for <hybi@core3.amsl.com>; Thu, 22 Jul 2010 14:38:08 -0700 (PDT)
Received: from 1wt.eu (1wt.eu [62.212.114.60]) by core3.amsl.com (Postfix) with ESMTP id 3745B3A69B9 for <hybi@ietf.org>; Thu, 22 Jul 2010 14:38:07 -0700 (PDT)
Received: (from willy@localhost) by mail.home.local (8.14.4/8.14.4/Submit) id o6MLcL3W017639; Thu, 22 Jul 2010 23:38:21 +0200
Date: Thu, 22 Jul 2010 23:38:21 +0200
From: Willy Tarreau <w@1wt.eu>
To: Ian Hickson <ian@hixie.ch>
Message-ID: <20100722213821.GB16808@1wt.eu>
References: <068.da8db0c773647cb0ed73d576f39e93ee@tools.ietf.org> <20100717023749.GA2426@shareable.org> <AANLkTil36SNqlpqq2zNMVSgsA_27kqnuioi0qFTKQR1m@mail.gmail.com> <20100721223010.GB14589@shareable.org> <20100721223947.GD6475@1wt.eu> <Pine.LNX.4.64.1007222024430.7242@ps20323.dreamhostps.com>
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: inline
In-Reply-To: <Pine.LNX.4.64.1007222024430.7242@ps20323.dreamhostps.com>
User-Agent: Mutt/1.4.2.3i
Cc: "hybi@ietf.org" <hybi@ietf.org>
Subject: Re: [hybi] WebSocket -76 is incompatible with HTTP reverse proxies
X-BeenThere: hybi@ietf.org
X-Mailman-Version: 2.1.9
Precedence: list
List-Id: Server-Initiated HTTP <hybi.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/listinfo/hybi>, <mailto:hybi-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/hybi>
List-Post: <mailto:hybi@ietf.org>
List-Help: <mailto:hybi-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/hybi>, <mailto:hybi-request@ietf.org?subject=subscribe>
X-List-Received-Date: Thu, 22 Jul 2010 21:38:09 -0000

On Thu, Jul 22, 2010 at 08:44:12PM +0000, Ian Hickson wrote:
> On Thu, 22 Jul 2010, Willy Tarreau wrote:
> > 
> > Ian, I don't know how to explain it to you now. I've exhausted every bit 
> > of possibility a normal human is able to understand, so I think that you 
> > are deliberately acting to refuse to understand the facts :-(
> > 
> > I've told you *several* times that it's not a matter of "updating" 
> > server- side components, but that your cross-dressed protocol will not 
> > be mergeable with HTTP on reverse-proxies. So even if the 
> > reverse-proxies are "updated" to use your terms, then they will have to 
> > be either configured to support WebSocket *OR* configured to support 
> > HTTP, but not both on the same IP:port couple.
> 
> I've already explained several ways that this could be done. Saying that 
> it simply can't be done seems quite fatalistic.

It's not fatalistic, it's just that you seem to be refusing to admit that
what is written in draft 76 is causing that to happen. As we said in several
earlier exchanges, if you slightly change the spec so that the server does
not have to wait for the 8 additional bytes in order to respond, the problem
is gone. But right now the HTTP reverse proxy must not send those bytes until
to sees the server's response and the WS server must not respond until it
gets those bytes.

(... rules ...)
> So again, I ask, if there is a specific requirment in the HTTP spec 
> that is being contradicted, please cite it. Ideally specifically the 
> requirement you think is being violated and not an entire section.

I'm happy, you've demonstrated that there is no body, so those bytes
MUST not be forwarded to the server as they're not part of the message.
So you now agree that your repeated suggestion of "just upgrading the
reverse proxy to make it support websocket and forward those bytes to
the server" is not possible.

> > In short, there is a message body, so either you advertise it using 
> > Content-Length or you advertise it using Transfer-Encoding: chunked, 
> > though the later requires that you're certain that the server supports 
> > HTTP/1.1 which is not necessarily the case.
> 
> There's no message body in this case. The 8 bytes are part of the Web 
> Socket connection, not part of the HTTP connection. They couldn't be part 
> of the HTTP connection; if they were, their entire purpose would be made 
> moot since man-in-the-middle proxies would simply pass the data along with 
> the original request and thus give a false-positive result.

But that's what I'm stating since the beginning !!! Those bytes will not
be forwarded because they're not part of the message. So either you make
them part of the message by specifying a content-length OR you make the
server respond without waiting for those bytes so that once the handshake
completes, they're correctly forwarded.

> > > We can't add Content-Length: 8, since that would mean the data would 
> > > be sent through with the first request even in non-WebSocket-aware 
> > > man-in- the-middle proxies, which defeats the point.
> > 
> > No, this is *what you need*. You need any HTTP-compliant component to 
> > reliably deliver this request because HTTP is the medium over which 
> > WebSocket will be used, like it or not. What you're currently doing is 
> > ensuring that HTTP-compliant software that are already working correctly 
> > everywhere will not be able to pass WebSocket requests to their peers, 
> 
> The entire point here is to make man-in-the-middle proxies _not_ forward 
> this data. What you are proposing defeats the entire point of this part of 
> the handshake.

I understood, so indeed, let's make the server respond without waiting for
them ! That was proposed several times, the only drawback is that it adds
round trips. In my opinion, round-trips between a reverse-proxy and a server
are not much big trouble because generally they're on the same LAN.

> > which will result in the protocol never being used beyond what it's 
> > always been since the beginning : tests between your local browser and 
> > your local hand-written server.
> 
> Given that it's already being used, I don't see this as being a realistic 
> concern.

It's being tested. And people already report issues, otherwise I would not
have joined the list to try explain them.

> > No, the handshake does not work because the server does never get the 
> > first 8 bytes so it does not respond. I would have no problem if those 8 
> > bytes were not required before the 101 response, but it happens the 
> > server needs them before responding, which is causing the 
> > chicken-and-egg problem :
> > 
> >   client : hey, I'm sending you my handshake, and don't care about my extra bytes
> >   rev-proxy: hey server, I'm sending you a handshake, and if you reply with 101,
> >              I will send you the extra bytes
> >   server : I won't complete my handshake until you send me those bytes.
> 
> The reverse proxy and the server in this scenario are both implementing 
> the Web Socket specification's concept of "server". If they don't do it 
> correctly, then they're not conforming to the specification. There is 
> nothing that stops the "server" part in your component from sending the 
> 101 line to the reverse proxy before it gets the last 8 bytes of the 
> handshake. This is an artificial requirement you are placing on your 
> setup. It would be like saying "you server send the character 'W', 
> therefore the handshake can't be implemented".

No I don't agree that *I* am placing this requirement. *Your* spec is
worded so that the request handshake is understood as a single-piece
message and the response handshake is as well. Example :

   The concatenation of the number obtained from processing the |Sec-
   WebSocket-Key1| field, expressed as a big-endian 32 bit number, the
   number obtained from processing the |Sec-WebSocket-Key2| field, again
   expressed as a big-endian 32 bit number, and finally the eight bytes
   at the end of the handshake, form a 128 bit string whose MD5 sum is
   then used by the server to prove that it read the handshake.

...

   The server can also set cookie-related option fields to _set_
   cookies, as in HTTP.

   After the fields, the server sends the aforementioned MD5 sum, a 16
   byte (128 bit) value, shown here as if interpreted as ASCII:

        fQJ,fN/4F4!~K~MH

   This value depends on what the client sends, as described above.  If
   it doesn't match what the client is expecting, the client would
   disconnect.

   Having part of the handshake appear after the fields ensures that
   both the server and the client verify that the connection is not
   being interrupted by an HTTP intermediary such as a man-in-the-middle
   cache or proxy.

And if you're telling me that it's not explicitly stated that it's a
single piece message, I'd like to know where it's indicated what the
server must wait for before sending each line of response.

Now please add to your handshake description that "the server may have
to send all the fields in order to get the 8 bytes from the request
that are needed to compute the MD5 sum", and it will suddenly work
better.

> > As long as the server has not responded with 101, it *IS* HTTP.
> 
> I disagree with the premise of this statement.

I know.

> > > All of these problems come from thinking of Web Sockets as a 
> > > subprotocol of HTTP. It isn't. Web Sockets is its own high-level 
> > > protocol built on top of TCP. It just happens to look enough like HTTP 
> > > that you can reuse the port, but that doesn't mean it's an HTTP-based 
> > > protocol. Thinking of Web Sockets as having anything to do with HTTP 
> > > is a mistake.
> > 
> > Please stop denying the initial goals, you know it won't be used it you 
> > can't share the port, and it's even still written in draft-76 :
> > 
> >    When a connection is to be made to a port that is shared by an HTTP
> >    server (a situation that is quite likely to occur with traffic to
> >    ports 80 and 443), the connection will appear to the HTTP server to
> >    be a regular GET request with an Upgrade offer.  In relatively simple
> >    setups with just one IP address and a single server for all traffic
> >    to a single hostname, this might allow a practical way for systems
> >    based on the WebSocket protocol to be deployed. 
> 
> Just because you can share the port doesn't mean it's the same protocol.

No, but they must be reliably distinguishable enough to leverage any
intentional or accidental ambiguity. Otherwise one has to be backwards-
compatible with the other one.

There are many examples of shared ports between HTTP and HTTPS. There's
no ambiguity between those, and any routing decision error will be detected
wherever it happens without dangerous side effects.

> I can point my SSH client to an SSH server listening on port 80 also, that 
> doesn't make SSH into HTTP.

No, but it least it will not pretend to be the other one either.

BTW, I'd like to ask you a simple question, if the handshake is not HTTP.
How do you recognize an HTTP request when you get an incoming connection
on a port with some data ? Personally, when I see a line with a method
matching a well-defined one in the HTTP protocol, followed by exactly one
space, followed by a URI, followed by exactly one space, followed by the
word "HTTP" followed by a "/" followed by two integers delimited with a
dot, then I can say for sure that it's an HTTP/X.Y request signature.

In other words, what in the request handshake can be used to know that
this is *NOT* HTTP ? If the whole handshake 100% matches a valid HTTP
request, there is no way to know it is in fact not. The "Upgrade:"
header is a perfectly valid and well-documented HTTP header which says
that once the server returns 101, then we're switching to the specified
protocol. So this cannot be used to distinguish between your imitation
of HTTP and a valid HTTP request. All other headers cannot be used as
such either because they could very well be used by any private
implementation. So either you admit that it's HTTP at the beginning, or
you must explain how to unambiguously distinguish between that imitation
and a real HTTP request that would make use of the same fields.

Thanks,
Willy