Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

warp-tls with http2 emits "send: resource vanished (Broken pipe)" on google chrome requests #421

Closed
aemixdp opened this issue Aug 10, 2015 · 9 comments

Comments

Projects
None yet
4 participants
@aemixdp
Copy link

commented Aug 10, 2015

Here's a simple application using wai-3.0.3.0, warp-3.1.2, warp-tls-3.1.0:

main = do
    Warp.runTLS tlsSettings Warp.defaultSettings app
    -- Warp.runSettings Warp.defaultSettings app    
  where
    app request respond = do
        print request
        respond $ Wai.responseLBS status200 [] (BS.pack "")
    tlsSettings = Warp.defaultTlsSettings
        { Warp.certFile = "cert.pem"
        , Warp.keyFile  = "key.pem" }

When performing requests from Chrome 44, it emits the output in subj (though it's like warning: except the fact of the presence of such output, it works fine), while in Firefox everything is ok. Here's output:

-- chrome with tls

<interactive>: send: resource vanished (Broken pipe)

Request { requestMethod = "GET", httpVersion = HTTP/2.0, rawPathInfo = "/breergere", rawQueryString = ""
        , requestHeaders =
            [ ("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
            , ("accept-encoding","gzip, deflate, sdch")
            , ("accept-language","en-US,en;q=0.8")
            , ("upgrade-insecure-requests","1")
            , ("user-agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36") ]
        , isSecure = True, remoteHost = 127.0.0.1:50111, pathInfo = ["breergere"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = ChunkedBody
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

-- firefox with tls

Request { requestMethod = "GET", httpVersion = HTTP/2.0, rawPathInfo = "/dfgsgse", rawQueryString = ""
        , requestHeaders =
            [ ("user-agent","Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0")
            , ("accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
            , ("accept-language","en-US,en;q=0.5")
            , ("accept-encoding","gzip, deflate") ]
        , isSecure = True, remoteHost = 127.0.0.1:50112, pathInfo = ["dfgsgse"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = ChunkedBody
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

And if we turn off https (second line of main), chrome doesn't produce that too:

-- chrome without tls

Request { requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/fsdfdsf", rawQueryString = ""
        , requestHeaders =
            [ ("Host","localhost:3000")
            , ("Connection","keep-alive")
            , ("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
            , ("Upgrade-Insecure-Requests","1")
            , ("User-Agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36")
            , ("Accept-Encoding","gzip, deflate, sdch")
            , ("Accept-Language","en-US,en;q=0.8") ]
        , isSecure = False, remoteHost = 127.0.0.1:50133, pathInfo = ["fsdfdsf"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = KnownLength 0
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

Request { requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/favicon.ico", rawQueryString = ""
        , requestHeaders =
            [ ("Host","localhost:3000")
            , ("Connection","keep-alive")
            , ("User-Agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36")
            , ("Accept","*/*")
            , ("Referer","http://localhost:3000/fsdfdsf")
            , ("Accept-Encoding","gzip, deflate, sdch")
            , ("Accept-Language","en-US,en;q=0.8") ]
        , isSecure = False, remoteHost = 127.0.0.1:50133, pathInfo = ["favicon.ico"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = KnownLength 0
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

-- firefox without tls

Request { requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/56y596iyi04", rawQueryString = ""
        , requestHeaders =
            [ ("Host","localhost:3000")
            , ("User-Agent","Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0")
            , ("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
            , ("Accept-Language","en-US,en;q=0.5")
            , ("Accept-Encoding","gzip, deflate")
            , ("Connection","keep-alive") ]
        , isSecure = False, remoteHost = 127.0.0.1:50155, pathInfo = ["56y596iyi04"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = KnownLength 0
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

Request { requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/favicon.ico", rawQueryString = ""
        , requestHeaders =
            [ ("Host","localhost:3000")
            , ("User-Agent","Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0")
            , ("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
            , ("Accept-Language","en-US,en;q=0.5")
            , ("Accept-Encoding","gzip, deflate")
            , ("Connection","keep-alive") ]
        , isSecure = False, remoteHost = 127.0.0.1:50155, pathInfo = ["favicon.ico"], queryString = []
        , requestBody = <IO ByteString>, vault = <Vault>, requestBodyLength = KnownLength 0
        , requestHeaderHost = Just "localhost:3000", requestHeaderRange = Nothing }

Notice that httpVersion is "HTTP/1.1" here.
Operating system:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.10
Release:    14.10
Codename:   utopic
@awpr-google

This comment has been minimized.

Copy link

commented Aug 10, 2015

Just as a note, I've been seeing this too. It's possible it affects all HTTP/2 connections in Chrome, because connection upgrades aren't supported, so non-TLS connections won't ever discover that HTTP/2 can be used.

I've been assuming this had something to do with Chrome being uncomfortable with my self-signed testing certificate, but I can check out net-internals later to see if I can figure out what's going on.

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 11, 2015

If I remember correctly, Chrome makes two TCP connections first (probably because of Happy Eyeballs defined RFC6555). If HTTP/2 is selected, one of them must be terminated.

@kazu-yamamoto kazu-yamamoto added the http2 label Aug 11, 2015

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2015

I analyzed the packet sequence. Chrome closes the first TLS/TCP connection by sending TCP FIN after sending TLS CCS and establishes the second TLS/TCP connection. TLS is not terminated by TLS alert.

So, the TLS layer (nor the application layer) cannot check if the connection is open without sending some data. In other words, this is not avoidable.

What I can do is define Haskell level errors to convert this low level errors. But that does not change anything essentially.

What do you think, guys?

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2015

More concretely:

  • Chrome sends TCP FIN after sending CCS
  • The server sends CCS
  • Chrome sends TCP RST
@awpr

This comment has been minimized.

Copy link
Member

commented Aug 13, 2015

If this is an expected (or, at least, accepted) way for a browser to terminate an unneeded connection, I think Warp should probably just ignore it (and probably call the connection-closed callback if feasible/applicable). Otherwise we should file a bug against Chrome to stop doing it.

It sounds like the error might be detected by the TLS library here, and it's probably not a universally acceptable way to break a TLS connection, so that would probably mean formalizing this case as an exception or failure value in the TLS library, then catching/discarding it in Warp?

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2015

I noticed that Warp has ConnectionClosedByPeer. Since it belongs to InvalidRequest, it is a little bit awkward. But converting "resource vanished" to ConnectionClosedByPeer make sense.

Since Warp can specify backends (e.g. send) to TLS, this can control by WarpTLS, I guess.

kazu-yamamoto added a commit that referenced this issue Aug 14, 2015

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2015

With the above patch, the error message is now "Client closed connection prematurely". I'm satisfied with this code.

kazu-yamamoto added a commit that referenced this issue Aug 17, 2015

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 17, 2015

The patch is merged. I would close this issue.

@kazu-yamamoto

This comment has been minimized.

Copy link
Contributor

commented Aug 18, 2015

A new version has been released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.