cURL / Mailing Lists / curl-library / Single Mail

curl-library

DarwinSSL in infinite loop when server stops arbitrarily?

From: Aki Koskinen <curl_at_akikoskinen.info>
Date: Sat, 02 Mar 2013 19:58:02 +0200

Hello, curl people. My first time here.

I have a weird case with curl using the DarwinSSL functionality. Below
is a simple HTTPS server written in Python that is useful in
demonstrating the issue.

The server receives HTTPS connections on port 55556. When a GET request
arrives it returns a 200 OK code and a body with the string "Hello,
World!". Then it terminates the server after a one second delay (this
termination isn't strictly needed to demonstrate the issue but it makes
really sure that the server isn't there any more after the request).

The issue is that with DarwinSSL curl just waits for data from the
server forever even though the server is long gone.

The issue possibly raises from the fact that the server doesn't close
the connection very cleanly. I suppose there is some concept called
"close notify" in TLS and apparently the server doesn't do that. I don't
know if this confuses curl and/or SecureTransport but it might be
related. There might be other "bad behaviour" in the server as well.

The command line curl program can be used for curl. I used version
7.29.0, compiled it myself on x86_64 OS X 10.7. Relevant configure flags
include --without-libssh2 --without-ssl --with-darwinssl --enable-http.

The issue can be demonstrated by starting the Python server and then
running the following curl command:

curl -kv https://localhost:55556/

The output for that command is like this:

* About to connect() to localhost port 55556 (#0)
* Trying 127.0.0.1...
* 0x7ff2ca008a08 is at send pipe head!
* STATE: CONNECT => WAITCONNECT handle 0x108c17178; line 1032
(connection #0)
* Connected to localhost (127.0.0.1) port 55556 (#0)
* STATE: WAITCONNECT => PROTOCONNECT handle 0x108c17178; line 1145
(connection #0)
* TLS 1.0 connection using TLS_RSA_WITH_AES_128_CBC_SHA
* STATE: PROTOCONNECT => DO handle 0x108c17178; line 1164 (connection #0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:55556
> Accept: */*
>
* STATE: DO => DO_DONE handle 0x108c17178; line 1236 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x108c17178; line 1352
(connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x108c17178; line 1363
(connection #0)
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.3 Python/2.7.1
< Date: Sat, 02 Mar 2013 17:18:23 GMT
< Content-type: text/plain
<

Then it hangs there for ever.

For comparison here's the same execution with curl 7.21.4 (which uses
OpenSSL apparently). This version seems to be installed on my OS X
machine so that's why I used it for comparison:

* About to connect() to localhost port 55556 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 55556 (#0)
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using AES256-SHA
* Server certificate:
* subject: C=FI; ST=Some-State; CN=********
* start date: 2013-01-02 15:48:17 GMT
* expire date: 2021-12-12 09:15:48 GMT
* common name: ********* (does not match 'localhost')
* issuer: C=FI; ST=Some-State; CN=**********
* SSL certificate verify result: self signed certificate (18),
continuing anyway.
> GET / HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4
OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:55556
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.3 Python/2.7.1
< Date: Sat, 02 Mar 2013 17:17:38 GMT
< Content-type: text/plain
<
* Closing connection #0
* SSLv3, TLS alert, Client hello (1):
Hello, World!

That version of curl exits cleanly as soon as the transfer is finished -
even if the server doesn't have the auto-termination code active.

I tried to debug this issue but couldn't find the root cause. I tried
the SecureTransport example code provided by Apple [1]. I see that
curl's DarwinSSL code is somewhat influenced by this example. The
difference is that SSLSample doesn't hang on the test.

The actual difference between SSLSample and curl - as far as I was able
to pinpoint - is that when calling read() on the socket a failure
happens (0 is returned). The real error at this point is in errno. For a
reason that I don't know SSLSample receives ENOENT but curl receives
EAGAIN. Hence, SSLSample notices that the other end of the socket is no
more but curl just keeps on trying again and again.

Hopefully someone with more understanding on the subject can get
something useful out of this long rant and find some answers to this.

[1]
https://developer.apple.com/library/mac/#samplecode/SSLSample/Introduction/Intro.html#//apple_ref/doc/uid/DTS10001088

NB. for the server to start you need to have a server.pem certificate
(can be self signed) file in the same directory.

--------- Server code starts ----------

#!/usr/bin/env python

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
import ssl, time

servers = []

def shutdown():
         time.sleep(1)
         for s in servers:
                 s.shutdown()

class MyRequestHandler(BaseHTTPRequestHandler):
         def do_GET(self):
                 self.send_response(200)
                 self.send_header("Content-type", "text/plain")
                 self.end_headers()
                 self.wfile.write("Hello, World!")

                 Thread(target=shutdown).start()

def start_server():
         httpd = HTTPServer(('localhost', 55556), MyRequestHandler)
         servers.append(httpd)
         httpd.socket = ssl.wrap_socket(httpd.socket,
certfile='server.pem', server_side=True)

         try:
                 httpd.serve_forever()
         except KeyboardInterrupt:
                 shutdown()

if __name__ == "__main__":
         start_server()

-- 
Aki Koskinen
http://www.akikoskinen.info/
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html
Received on 2013-03-03