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

From: Willy Tarreau <[email-masked]>
Date: Tue, 6 Jul 2010 23:00:39 +0200

Hi,

I was having a private discussion with Ian last week about a recent
issue introduced in draft 76, but I realized it would be more useful
and constructive to bring the issue to the list than to privately
discuss possible fixes.

Last week, it was reported to me that a site that was running fine
on draft 75 could not get the draft 76 handshake to complete via a
HAProxy load balancer, which runs as an HTTP reverse proxy. The
connection would remain open between the client and haproxy, and
between haproxy and the server, with the server never responding.
The same client (Chromium 6.0.414.0) directly connected to the
server worked fine.

The guy was kind enough to send me some network captures which show
an obvious problem : the 8-bytes nonce from the client is not advertised
as a content-length, so it is not forwarded by the reverse proxy as
it is either part of a next request or pending data for when the
handshake completes. Unfortunately, the server wants those bytes to
complete the handshake, so we have a dirty deadlock not even detectable
by the end user.

Ian proposed to upgrade the reverse proxy to detect the WebSocket
handshake in the request (before it completes) and that it accepts
to forward those 8 bytes.

I can't agree with that because until the handshake completes, the
proxy does not know whether the server will handle the request as
a WS handshake or anything else, and it must absolutely not accept
to blindly trust any random client who sets an Upgrade header that
any server is free to ignore. Doing so would make the reverse-proxy
vulnerable to HTTP request smuggling attacks [1] or even to any type
of filtering bypass depending on the length of the data it lets go.
Even with 8 bytes it is possible to send a "GET /xx\n" which is a
valid HTTP/0.9 request and is accepted by some servers in a keep-alive
connection (including Apache).

Example :

        GET /index.html HTTP/1.1
        Host: example.com
        Connection: Upgrade
        Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
        Sec-WebSocket-Protocol: sample
        Upgrade: WebSocket
        Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
        Origin: http://example.com

        GET /..

If the server does not handle WebSocket for this ressource, it will
happily return the index and/or a 404 and proceed with next request
contained in the 8 bytes it received.

Conversely, having no Content-Length header in the request means that
we don't know what a reverse proxy will do if it receives a valid one.
For instance, we could very well imagine that some reverse proxies
which will assume that Content-Length == 8 for any request containing
"Upgrade: WebSocket" will have trouble when receiving a different
Content-Length header. This could be used to pass larger amounts of
data than what is allowed by the protocol to a second reverse-proxy,
which, if it is able to parallelize pipelined requests, will forward
the first one to the server and the second one (embedded in the apparent
data) to another server.

The first obvious solution that comes to mind is to comply with the
HTTP protocol which will be implemented along the whole chain and to
simply add a "Content-Length: 8" header in the request. This fixes
the dirty hang, this fixes the fact that reverse proxies have to blindly
trust the client, this fixes the case with the different content length,
and it even makes it possible for WebSocket aware reverse proxies to
refuse requests which don't have exactly "Content-Length: 8".

But this raises a second point : shouldn't we switch to POST instead of
GET then ? After all, GET + content-length is not well defined, httpbis
p1 says :

   The presence of a message-body in a request is signaled by the
   inclusion of a Content-Length or Transfer-Encoding header field in
   the request's header fields.  When a request message contains both a
   message-body of non-zero length and a method that does not define any
   semantics for that request message-body, then an origin server SHOULD
   either ignore the message-body or respond with an appropriate error
   message (e.g., 413).  A proxy or gateway, when presented the same
   request, SHOULD either forward the request inbound with the message-
   body or ignore the message-body when determining a response.

So that means that we're not certain again that the data will pass
through all reverse proxies. That said, we could consider that WebSocket
aware reverse proxies will let the data flow, but the problem of the hung
connection remains if the reverse proxy eats the data before forwarding
to the server.

The POST solves all that. POST + content-length is normal and well
supported everywhere. The POST also has the nice advantage that we're
sure we won't get a reply from a cache (and the POST method is even
one of the 3 defined methods which must invalidate caches).

The POST also has a nice advantage for various client implementations
that it is easier to implement than GET with some data.

After some thinking, I'm wondering why we want to pass the nonce as
data in the request instead of passing it as headers. After all, we're
interested in data in the response to ensure we're able to let data
flow and that the whole chain understands the Upgrade request. In fact,
if any one intermediate ignores the 101 and takes it as a 100 (as is
explicitly permitted by httpbis-p2), the response will be aborted
because the pending data does not look like an HTTP response, and this
is perfect, it's what we're looking for. But in the request ? I fail
to see the added value of having it in the data. Probably that it would
be easier to put it in a header and keep the GET.

Anyway, we have to do something now because we've reached the point Ian
tried to ensure we would avoid a long time ago : the deadlock which is
undetectable by the client. And it already happens through a common
reverse proxy since draft 76, and will do through load balancers, IDS
and HTTP-aware firewalls for the same reasons, without any simple way
to fix it without breaking HTTP security on those components. And it's
not like if we could imagine those components will not be in use where
WebSocket is deployed !

Best regards,
Willy

[1]   http://www.owasp.org/index.php/HTTP_Request_Smuggling

---

From: Greg Wilkins <[email-masked]>
Date: Wed, 7 Jul 2010 09:14:27 +1000

Willy

thanks for giving a real world example to the problems of non HTTP
compatibility that were predicted to occur if -76 was adopted.

My understanding is that the nonce is sent as a non-HTTP non-Websocket
data packet precisely to detect if there is an intermediary that
cannot handle websocket - and thus fail fast. Your example show that
this mechanism is neither a good test for websocket capability, nor
does it actually fail fast. I believe HAproxy is a load balancer that
will handle the first HTTP Request/Response on a connection as HTTP,
before turning into a byte forwarding pipe.   Such a proxy will work
fine with websocket if the handshake is compliant HTTP, but fails on
-76 because it is not compliant.

The proposal that I have made several times on this list is that the
handshake should include the nonce as a header.  The 16 byte check
code can be sent as non-HTTP data after the 101, as there is no longer
a requirement to be HTTP then - however I think that if we wish to
test websocket capability, it would be far better to send that in a
websocket frame:

Send:

    GET /index.html HTTP/1.1
    Host: example.com
    Connection: Upgrade
    Sec-WebSocket-Nonce: 4ABC2FEE237CD165

Receive:

        HTTP/1.1 101 WebSocket Protocol Handshake
        Upgrade: WebSocket
        Connection: Upgrade
        Sec-WebSocket-Origin: http://example.com
        Sec-WebSocket-Location: ws://example.com/demo
        Sec-WebSocket-Protocol: sample

        0x81 0x10  <16 bytes>

Only after the 16 byte binary websocket frame is received and
validated should the client trigger the onOpen handling. This
handshake is compliant HTTP and includes a check of websocket
capability server to client without an extra round trip. The only
think it lacks is a check of client to server websocket capability,
but:
   a) we can see the current -76 check doesn't work anyway
   b) all the intermediaries say the upgrade request and 101 response,
so they had the opportunity to refuse the upgrade
   c) it would be easy to add a client to server websocket keep-alive
send after the handshake that would check that path.  If server could
timeout the connection if a no message is received within a reasonable
timeout.  To check connectivity there is no way to avoid having such a
timeout since you can't test for something not being received any
other way... at least this way, the message could actually be a real
application message and thus a useful message and not another round
trip.

But all this has been said before and ignored by the editor, who wont
even remove the inappropriate redirection in the intro of
   http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
so don't hold your breath waiting for good sense or good process to
prevail.  Sorry for the sour grapes... but I don't know what else to
say.

regards

On 7 July 2010 07:00, Willy Tarreau <[email-masked]> wrote:
> _______________________________________________
> hybi mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/hybi
>

---

From: Micheil Smith <[email-masked]>
Date: Wed, 7 Jul 2010 12:44:32 +1000

Willy,

Thanks for raising this point. I'm an author of a node.js powered websocket 
server, and this was actually a really awkward part of the websocket spec to 
comply to when writing my server.

Without having a content-length header, the http parser in node doesn't know 
how to handle the extra data it receives in that initial handshake. What we 
ended up doing as a fix, was just to provide an upgradeHead as a slice of the
buffer from where the headers ended to the end of the packet.

In short, I'd be all for having a content-length header sent to declare the body 
as a body, or to declare it as just extra data that should be handled post upgrade.
From what I can tell from what Ian is telling me over irc, he'd be more inclined to 
add in content-length: 0 as a header, then content-length: 8.

Hopefully this reply does make somewhat sense. 

Yours,
Micheil Smith
--
BrandedCode.com

ps: I'm hoping this does actually reply to the mailing list, rather then the individual.

> [1]   http://www.owasp.org/index.php/HTTP_Request_Smuggling

Micheil Smith
--
BrandedCode.com

---

From: Willy Tarreau <[email-masked]>
Date: Wed, 7 Jul 2010 06:41:29 +0200

> Hopefully this reply does make somewhat sense.

Content-length: 0 also makes sense but it means that the nonce will
be sent *after* the handshake, which means we'd have a second
round-trip. For some sites who deal with hundreds of thousands of
concurrent connections (and I know some), having two more packets
in a connectionn is an unneeded increase of network traffic. For
this reason, I think that the idea of the nonce in the request header
is good.

However, I think that having content-length: 0 in the *response* will
definitely help eliminating incompatible intermediates : those who
properly support 101 will forward the data regardless of the content
length because the tunnel is established. Those who don't completely
support 101 will stop at the content-length and not forward the
response. Alternatively, it might be nice to use a transfer-encoding:
chunked in the response with only an end of chunk ("0\r\n") before the
nonce. That would permit the client to check if it receives only the
"0\r\n" or if it receives the nonce too. An increased check could also
consist in setting "Connection: close" in the response in addition with
the transfer-encoding or content-length. That way, incompatible
intermediates won't remain open waiting for anything and will quickly
close the connection before the part they don't support.

Regards,
Willy

---

From: Willy Tarreau <[email-masked]>
Date: Wed, 7 Jul 2010 06:55:26 +0200

> compatibility that were predicted to occur if -76 was adopted.

In fact, I'd say that it's often hard to predict the risk of breakage
before observing it, the only thing we know is that if we respect
what others understand, the risk is low and that if we patently
ignore standards, the risk is high.

> cannot handle websocket - and thus fail fast.

That's what I understood too and it is inefficient.

> Your example show that
> before turning into a byte forwarding pipe.

yes, it now properly handles the 101, which means that it becomes a
pipe once it sees the 101 in the response (once the handshake completes).
So there is no way it will send unadvertised data before the server is
OK with receiving them.

> Such a proxy will work

exactly.

> test websocket capability, it would be far better to send that in a

Yes, that's what I think would be the cleaner (and the last proposal
in my mail too).

> capability server to client without an extra round trip.

And it's also how TLS is supposed to work over HTTP/1.1 since May 2000 !

> The only

In my opinion, it is not a problem because if an intermediate does not
understand the 101, the response data will not be sent to the client
(and we may have to ensure a bit more that those data can't be sent, eg
with content-length 0 in the response, and connection: close). If those
data flow to the client, then it must mean that the pipe is established.

I'm now thinking we could even further strengthen the handshake via non
compliant intermediates by adding connection: close in both directions.
This will make incompatible reverse proxies fail the earliest possible,
without breaking compatibility with the protocols implemented on the
components which transport the handshake.

Regards,
Willy

---

From: Roberto Peon <[email-masked]>
Date: Tue, 6 Jul 2010 22:13:15 -0700

> this reason, I think that the idea of the nonce in the request header
If we're worried about hundreds of thousands of connections, then we need
multiplexing of sessions over one TCP connection between the browser and the
server.
I, for one, am extremely worried about it, and am perhaps more worried that
many seem to be just shrugging it off-- even ignoring the server scalability
problems, NAT boxes do really funny and nasty things when their connection
tables overflow.

I do love that we're thinking about ways to make fail-fast happen within the
context of HTTP. This makes so much more sense to me.
-=R

---

From: "Thomson, Martin" <[email-masked]>
Date: Wed, 7 Jul 2010 13:23:22 +0800

> round-trip. 

The round-trip thing is a fallacy.  Just as you can pipeline requests, so can you send extra handshakey parts after the headers.

Solution:  The handshake includes a complete HTTP message, PLUS extra stuff.  All of this is sent at once, but the HTTP stuff stops half way.

This is only true if the extra stuff is dependent on information from the peer, which is not the case in this scenario.

---

From: Willy Tarreau <[email-masked]>
Date: Wed, 7 Jul 2010 07:31:17 +0200

> This is only true if the extra stuff is dependent on information from the peer, which is not the case in this scenario.

You're perfectly right Martin, this was a stupid comment of mine in fact.
The extra round-trip will only be between the reverse proxy and the server,
which is not a problem since both are supposed to be very close one to the
other. So, let's have content-length: 0 in both directions and have the
nonce transported *after* the handshake and tested by the client.

Willy

---

From: Greg Wilkins <[email-masked]>
Date: Thu, 8 Jul 2010 01:01:31 +1000

Martin,

You are correct that it is not an extra round trip.  But I do not
think it is a good solution to send a complete HTTP message PLUS extra
stuff in the request.

If the handshake is legal HTTP, the server should be able to rejects
the websocket upgrade without closing the connection.  This would
allow the connection to remain in the browsers pool of connections and
avoid an extra round trip to establish another connection if the
application falls back to non-websocket transports.

Sending extra stuff after the response is OK.

cheers

---

From: "Thomson, Martin" <[email-masked]>
Date: Thu, 8 Jul 2010 06:40:20 +0800

That's reasonable.

The alternative is to have Content-Length set to the length of the extra stuff - make the extra handshake stuff part of the message body.  That pulls it up into the HTTP.  That only applies on the request - the response to a successful handshake can do anything it likes once the HTTP part is out of the way.

> -----Original Message-----
> From: Greg Wilkins [mailto:[email-masked]]
> Sent: Thursday, 8 July 2010 1:02 AM
> To: Thomson, Martin
> Cc: Roberto Peon; Willy Tarreau; [email-masked]
> Subject: Re: [hybi] WebSocket -76 is incompatible with HTTP reverse
> proxies
> 
> Martin,
> 
> > The round-trip thing is a fallacy.  Just as you can pipeline
> requests, so can you send extra handshakey parts after the headers.
> >
> > Solution:  The handshake includes a complete HTTP message, PLUS extra
> stuff.  All of this is sent at once, but the HTTP stuff stops half way.
> >
> > This is only true if the extra stuff is dependent on information from
> the peer, which is not the case in this scenario.

---

From: Ian Hickson <[email-masked]>
Date: Wed, 21 Jul 2010 21:24:16 +0000 (UTC)

On Tue, 6 Jul 2010, Willy Tarreau wrote:
> 
> Last week, it was reported to me that a site that was running fine on 
> draft 75 could not get the draft 76 handshake to complete via a HAProxy 
> load balancer, which runs as an HTTP reverse proxy. The connection would 
> remain open between the client and haproxy, and between haproxy and the 
> server, with the server never responding. The same client (Chromium 
> 6.0.414.0) directly connected to the server worked fine.
> 
> The guy was kind enough to send me some network captures which show an 
> obvious problem : the 8-bytes nonce from the client is not advertised as 
> a content-length, so it is not forwarded by the reverse proxy as it is 
> either part of a next request or pending data for when the handshake 
> completes.

Right, you need to update all the server-side components to support 
WebSocket. WebSocket is a new protocol. Similar updates would be needed to 
support other new protocols.

> I can't agree with that because until the handshake completes, the proxy 
> does not know whether the server will handle the request as a WS 
> handshake or anything else, and it must absolutely not accept to blindly 
> trust any random client who sets an Upgrade header that any server is 
> free to ignore.

Obviously all server-side components have to be configured to know the 
setup that they are in. This includes telling load balancers and other 
front-end intermediaries which hosts are ready to handle WebSocket 
connections and which are not. Just like a reverse proxy would not be 
configured to forward a connection to an SMTP server behind the firewall, 
it wouldn't be configured to send WebSocket traffic to HTTP servers.

> Conversely, having no Content-Length header in the request means that we 
> don't know what a reverse proxy will do if it receives a valid one. For 
> instance, we could very well imagine that some reverse proxies which 
> will assume that Content-Length == 8 for any request containing 
> Content-Length header. This could be used to pass larger amounts of data 
> than what is allowed by the protocol to a second reverse-proxy, which, 
> if it is able to parallelize pipelined requests, will forward the first 
> one to the server and the second one (embedded in the apparent data) to 
> another server.

The spec is very clear about how a server side is to parse the handshake. 
I don't think there's any ambiguity here. There's no need for the reverse 
proxy to "assume a Content-Length" or anything like that; if it decides 
that the request is a WebSocket request (e.g. based on the presence of an 
"Upgrade: WebSocket" field, or based on the target IP or the given 
resource name), then it should follow the Web Socket spec.

> The first obvious solution that comes to mind is to comply with the HTTP 
> protocol which will be implemented along the whole chain and to simply 
> add a "Content-Length: 8" header in the request.

As far as I can tell there is nothing here that contradicts the HTTP spec. 
If there is a specific requirment in the HTTP spec that is being 
contradicted, please cite it.

We could add Content-Length: 0, but as far as I can tell that's implied 
for GET anyway, so it wouldn't change anything in conforming software. 
(This isn't very clear in the HTTP spec though.)

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.

> GET then?

We could use another method, but there doesn't seem to be much reason to 
do so. GET turns out to be the simplest to deal with in a variety of 
situations.

> undetectable by the client.

A deadlock isn't a big deal. The problem was a false-positive situation, 
where the handshake works but frames don't go through.

On Wed, 7 Jul 2010, Thomson, Martin wrote:
> > 
> > Content-length: 0 also makes sense but it means that the nonce will be 
> > sent *after* the handshake, which means we'd have a second round-trip.
> 
> The round-trip thing is a fallacy.  Just as you can pipeline requests, 
> so can you send extra handshakey parts after the headers.
> 

That's exactly what the spec does.

On Thu, 8 Jul 2010, Greg Wilkins wrote:
>
> You are correct that it is not an extra round trip.  But I do not think 
> it is a good solution to send a complete HTTP message PLUS extra stuff 
> in the request.
>
> If the handshake is legal HTTP, the server should be able to rejects the 
> websocket upgrade without closing the connection.  This would allow the 
> connection to remain in the browsers pool of connections and avoid an 
> extra round trip to establish another connection if the application 
> falls back to non-websocket transports.

The browser can't know if the server is really an HTTP server, so it can't 
possibly reuse the connection. It could in fact be a huge security hole, 
depending on how we did this. It is, in either case, far more complexity 
than is in any way justified here.

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.

-- 
Ian Hickson               U+1047E                )\._.,--....,'``.    fL
http://ln.hixie.ch/       U+263A                /,   _.. \   _\  ;`._ ,.
Things that are impossible just take longer.   `._.-(,_..'--(,_..'`-.;.'

---

From: Willy Tarreau <[email-masked]>
Date: Thu, 22 Jul 2010 00:10:31 +0200

> support other new protocols.

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.

What you are suggesting is that a shared component which would have to
support both protocols would have to trust any request that contains an
Upgrade header without even knowing if the server will see it. That's
simply not acceptable. Anyone will be able to play with HTTP servers
behind HTTP reverse-proxies pretending to be talking WebSocket. That's
silly and undesired.

HTTP spec is clear : the protocol resulting from an Upgrade is switched
*AFTER* the "101" response, not *BEFORE*. So the reverse-proxy MUST see
the 101 to accept to pass non-HTTP bytes to the other side if they are
not advertised in the request.

> it wouldn't be configured to send WebSocket traffic to HTTP servers.

No, this is totally different here, because requests are routed through
multiple layers of reverse-proxies on server-side which fortunately don't
all have to know every single bit of URLs. The outer ones just know how
to process global confs and *some* inner components may specifically be
tuned to know that *some* URLs will be working differently. But that's
clearly not even remotely thinkable for hosting providers. They offer
you a host name, everyone behind the same IP and they don't want to know
how you will be managing your URLs, they just forward you the traffic
for that Host: header and you do whatever you want with it. And if you
want to change your WS URL twice a day, it's your problem and they don't
have to reconfigure all of their components just because of you.

> resource name), then it should follow the Web Socket spec.

Please see above for the nth time why it cannot "decide" that it is a WS
request. All the problem comes from that. It can only decide based on what
the client decides to tell it, and when the server responds, it's too late.

> contradicted, please cite it.

I really think you're trying to make all of us waste our time while we're
trying to help you release something which works instead of it becoming a
major failure that will be attempted to be used for 6 months then abandonned
due to massive failures everywhere. That's a real pity. So here it comes,
from RFC2616 (and this part remained unchanged in http-bis-p1-10) :

4.4 Message Length
...
  HTTP/1.1 requests containing a message-body MUST include a valid Content-Length
  header field unless the server is known to be HTTP/1.1 compliant. If a request
  contains a message-body and a Content-Length is not given, the server SHOULD
  respond with 400 (bad request) if it cannot determine the length of the message,
  or with 411 (length required) if it wishes to insist on receiving a valid
  Content-Length.

  All HTTP/1.1 applications that receive entities MUST accept the "chunked"
  transfer-coding (section 3.6), thus allowing this mechanism to be used for
  messages when the message length cannot be determined in advance.

  Messages MUST NOT include both a Content-Length header field and a non-identity
  transfer-coding. If the message does include a non-identity transfer-coding,
  the Content-Length MUST be ignored.

  When a Content-Length is given in a message where a message-body is allowed,
  its field value MUST exactly match the number of OCTETs in the message-body.
  HTTP/1.1 user agents MUST notify the user when an invalid length is received
  and detected.

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.

> (This isn't very clear in the HTTP spec though.)

Indeed, it would change nothing, it would just clarify the fact that you
want to send nothing, which is already implicit when there's no content-length
nor transfer-encoding in the request.

> 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, 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.

> where the handshake works but frames don't go through.

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.

> than is in any way justified here.

I don't agree. As long as the server has not responded with 101, it *IS* HTTP.
So you can do whatever you want on the connection before the 101, including
authenticating with a challenge if you like.

> 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. 

Willy