ssl module, non-blocking sockets and asyncore integration
Hi, I try to revamp a discussion I raised some time ago and which seems to be not solved yet. I'm interested in using the ssl module with asyncore but since there's no real documentation about how using ssl module with non-blocking sockets I've not been able to write something useful with it. I took a look at Test/test_ssl.py which seems to include a test consisting in an asyncore-based secure server but I noticed something strange: def __init__(self, conn, certfile): asyncore.dispatcher_with_send.__init__(self, conn) self.socket = ssl.wrap_socket(conn, server_side=True, certfile=certfile, do_handshake_on_connect=True) I'm not sure about the real meaning of the do_handshake_on_connect parameter but if it does what I think it should block the application as long as the handshake isn't finished, which is not acceptable in an asynchronous environment. Am I misunderstanding something? Does the ssl module can actually be used with non-blocking sockets? If so which is the proper/recommended way to do that? Thanks in advance --- Giampaolo http://code.google.com/p/pyftpdlib/
Hi, Giampaolo.
If you look a bit further in Lib/test/test_ssl.py, you'll see a
non-blocking use of the "do_handshake" method. Basically, the flag
"do_handshake_on_connect" says whether you want this to happen
automatically and blockingly (True), or whether you want to do it
yourself (False). In the test suite, the function
"testNonBlockingHandshake" does the async client-side handshake; the
server side logic is just the same, only it would happen in the server's
"handle new connection" code -- you'd have to add a state variable, and
bind handlers for "read_event" and "write_event", which would consult
the state variable to see whether they had finished the handshake yet.
I just made the server do it automatically to make life easier.
The hard part isn't really doing the non-blocking, it's trying to figure
out how to use asyncore correctly, IMO.
Giampaolo Rodola'
I'm interested in using the ssl module with asyncore but since there's no real documentation about how using ssl module with non-blocking
If you'd like to contribute a doc patch, that would be great. Here's what it current says for do_handshake_on_connect: The parameter do_handshake_on_connect specifies whether to do the SSL handshake automatically after doing a socket.connect(), or whether the application program will call it explicitly, by invoking the SSLSocket.do_handshake() method. Calling SSLSocket.do_handshake() explicitly gives the program control over the blocking behavior of the socket I/O involved in the handshake. and here's what the docs for do_handshake() says: SSLSocket.do_handshake()¦ Perform a TLS/SSL handshake. If this is used with a non-blocking socket, it may raise SSLError with an arg[0] of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, in which case it must be called again until it completes successfully. For example, to simulate the behavior of a blocking socket, one might write: while True: try: s.do_handshake() break except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([s], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [s], []) else: raise Bill
I've tried to modify my existing asyncore-based code but I'm
encountering a lot of different problems I didn't manage to fix.
It seems that playing with the do_handshake_on_connect flag doesn't
make any difference.
I guess that without some kind of documentation describing how to deal
with non-blocking "ssl-wrapped" sockets I won't get too far.
I try to ask two questions in case the answers may help me in some
way:
1 - What pending() method is supposed to do (it's not documented)?
2 - By reading ssl.py code I noticed that when do_handshake_on_connect
flag is False the do_handshake() method is never called. Is it
supposed to be manually called when dealing with non-blocking sockets?
In the meanwhile I noticed something in the ssl.py code which seems to
be wrong:
def recv (self, buflen=1024, flags=0):
if self._sslobj:
if flags != 0:
raise ValueError(
"non-zero flags not allowed in calls to sendall()
on %s" %
self.__class__)
while True:
try:
return self.read(buflen)
except SSLError, x:
if x.args[0] == SSL_ERROR_WANT_READ:
continue
else:
raise x
else:
return socket.recv(self, buflen, flags)
I don't know the low levels but that while statement which continues
in case of SSL_ERROR_WANT_READ seems to be wrong (blocking), at least
when dealing with non-blocking sockets. I think the proper way of
doing recv() here is letting SSL_ERROR_WANT_READ propagate and let the
upper application (e.g. asyncore) deal with it.
Hope this helps,
--- Giampaolo
http://code.google.com/p/pyftpdlib/downloads/list
On 15 Set, 04:50, Bill Janssen
Hi, Giampaolo.
If you look a bit further in Lib/test/test_ssl.py, you'll see a non-blocking use of the "do_handshake" method. Basically, the flag "do_handshake_on_connect" says whether you want this to happen automatically and blockingly (True), or whether you want to do it yourself (False). In the test suite, the function "testNonBlockingHandshake" does the async client-side handshake; the server side logic is just the same, only it would happen in the server's "handle new connection" code -- you'd have to add a state variable, and bind handlers for "read_event" and "write_event", which would consult the state variable to see whether they had finished the handshake yet.
I just made the server do it automatically to make life easier.
The hard part isn't really doing the non-blocking, it's trying to figure out how to use asyncore correctly, IMO.
Giampaolo Rodola'
wrote: I'm interested in using the ssl module with asyncore but since there's no real documentation about how using ssl module with non-blocking
If you'd like to contribute a doc patch, that would be great.
Here's what it current says for do_handshake_on_connect:
The parameter do_handshake_on_connect specifies whether to do the SSL handshake automatically after doing a socket.connect(), or whether the application program will call it explicitly, by invoking the SSLSocket.do_handshake() method. Calling SSLSocket.do_handshake() explicitly gives the program control over the blocking behavior of the socket I/O involved in the handshake.
and here's what the docs for do_handshake() says:
SSLSocket.do_handshake()¦ Perform a TLS/SSL handshake. If this is used with a non-blocking socket, it may raise SSLError with an arg[0] of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, in which case it must be called again until it completes successfully. For example, to simulate the behavior of a blocking socket, one might write:
while True: try: s.do_handshake() break except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([s], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [s], []) else: raise
Bill _______________________________________________ Python-Dev mailing list Python-...@python.orghttp://mail.python.org/mailman/listinfo/python-dev Unsubscribe:http://mail.python.org/mailman/options/python-dev/python-dev2-garchiv...
Sorry, ignore my 2nd question, I see now that you already gave a very
clear answer in your first message.
I change my question: how am I supposed to know when the SSL hanshake
is completed? When pending() returns False?
If so I'd recommend to document the method.
--- Giampaolo
http://code.google.com/p/pyftpdlib/
On 17 Set, 03:24, "Giampaolo Rodola'"
I've tried to modify my existing asyncore-based code but I'm encountering a lot of different problems I didn't manage to fix. It seems that playing with the do_handshake_on_connect flag doesn't make any difference. I guess that without some kind of documentation describing how to deal with non-blocking "ssl-wrapped" sockets I won't get too far. I try to ask two questions in case the answers may help me in some way:
1 - What pending() method is supposed to do (it's not documented)? 2 - By reading ssl.py code I noticed that when do_handshake_on_connect flag is False the do_handshake() method is never called. Is it supposed to be manually called when dealing with non-blocking sockets?
In the meanwhile I noticed something in the ssl.py code which seems to be wrong:
def recv (self, buflen=1024, flags=0): if self._sslobj: if flags != 0: raise ValueError( "non-zero flags not allowed in calls to sendall() on %s" % self.__class__) while True: try: return self.read(buflen) except SSLError, x: if x.args[0] == SSL_ERROR_WANT_READ: continue else: raise x else: return socket.recv(self, buflen, flags)
I don't know the low levels but that while statement which continues in case of SSL_ERROR_WANT_READ seems to be wrong (blocking), at least when dealing with non-blocking sockets. I think the proper way of doing recv() here is letting SSL_ERROR_WANT_READ propagate and let the upper application (e.g. asyncore) deal with it.
Hope this helps,
--- Giampaolohttp://code.google.com/p/pyftpdlib/downloads/list
On 15 Set, 04:50, Bill Janssen
wrote: Hi, Giampaolo.
If you look a bit further in Lib/test/test_ssl.py, you'll see a non-blocking use of the "do_handshake" method. Basically, the flag "do_handshake_on_connect" says whether you want this to happen automatically and blockingly (True), or whether you want to do it yourself (False). In the test suite, the function "testNonBlockingHandshake" does the async client-side handshake; the server side logic is just the same, only it would happen in the server's "handle new connection" code -- you'd have to add a state variable, and bind handlers for "read_event" and "write_event", which would consult the state variable to see whether they had finished the handshake yet.
I just made the server do it automatically to make life easier.
The hard part isn't really doing the non-blocking, it's trying to figure out how to use asyncore correctly, IMO.
Giampaolo Rodola'
wrote: I'm interested in using the ssl module with asyncore but since there's no real documentation about how using ssl module with non-blocking
If you'd like to contribute a doc patch, that would be great.
Here's what it current says for do_handshake_on_connect:
The parameter do_handshake_on_connect specifies whether to do the SSL handshake automatically after doing a socket.connect(), or whether the application program will call it explicitly, by invoking the SSLSocket.do_handshake() method. Calling SSLSocket.do_handshake() explicitly gives the program control over the blocking behavior of the socket I/O involved in the handshake.
and here's what the docs for do_handshake() says:
SSLSocket.do_handshake()¦ Perform a TLS/SSL handshake. If this is used with a non-blocking socket, it may raise SSLError with an arg[0] of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, in which case it must be called again until it completes successfully. For example, to simulate the behavior of a blocking socket, one might write:
while True: try: s.do_handshake() break except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([s], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [s], []) else: raise
Bill _______________________________________________ Python-Dev mailing list Python-...@python.orghttp://mail.python.org/mailman/listinfo/python-dev Unsubscribe:http://mail.python.org/mailman/options/python-dev/python-dev2-garchiv...
_______________________________________________ Python-Dev mailing list Python-...@python.orghttp://mail.python.org/mailman/listinfo/python-dev Unsubscribe:http://mail.python.org/mailman/options/python-dev/python-dev2-garchiv...- Nascondi testo citato
- Mostra testo citato -
Giampaolo Rodola'
2 - By reading ssl.py code I noticed that when do_handshake_on_connect flag is False the do_handshake() method is never called. Is it supposed to be manually called when dealing with non-blocking sockets?
Yes. Look at the example client in Lib/test/test_ssl.py. The server code should work the same way. Bill
Giampaolo Rodola'
In the meanwhile I noticed something in the ssl.py code which seems to be wrong:
def recv (self, buflen=1024, flags=0): if self._sslobj: if flags != 0: raise ValueError( "non-zero flags not allowed in calls to sendall() on %s" % self.__class__) while True: try: return self.read(buflen) except SSLError, x: if x.args[0] == SSL_ERROR_WANT_READ: continue else: raise x else: return socket.recv(self, buflen, flags)
I don't know the low levels but that while statement which continues in case of SSL_ERROR_WANT_READ seems to be wrong (blocking), at least when dealing with non-blocking sockets. I think the proper way of doing recv() here is letting SSL_ERROR_WANT_READ propagate and let the upper application (e.g. asyncore) deal with it.
It's an interesting point. I'm not sure the underlying code will ever raise this exception. Please file a bug report to help us track this. Thanks. Bill
Ah, now I remember. It seems that sometimes when SSL_ERROR_WANT_READ
was returned, things would block; that is, the "handle_read" method on
asyncore.dispatcher was never called again, so the SSLSocket.recv()
method was never re-called. There are several levels of buffering going
on, and I never figured out just why that was. This (very rare) re-call
of "read" is to handle that.
Bill
Bill Janssen
Giampaolo Rodola'
wrote: In the meanwhile I noticed something in the ssl.py code which seems to be wrong:
def recv (self, buflen=1024, flags=0): if self._sslobj: if flags != 0: raise ValueError( "non-zero flags not allowed in calls to sendall() on %s" % self.__class__) while True: try: return self.read(buflen) except SSLError, x: if x.args[0] == SSL_ERROR_WANT_READ: continue else: raise x else: return socket.recv(self, buflen, flags)
I don't know the low levels but that while statement which continues in case of SSL_ERROR_WANT_READ seems to be wrong (blocking), at least when dealing with non-blocking sockets. I think the proper way of doing recv() here is letting SSL_ERROR_WANT_READ propagate and let the upper application (e.g. asyncore) deal with it.
It's an interesting point. I'm not sure the underlying code will ever raise this exception. Please file a bug report to help us track this.
Thanks.
Bill _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/janssen%40parc.com
On Wed, 17 Sep 2008 10:40:01 PDT, Bill Janssen
Ah, now I remember. It seems that sometimes when SSL_ERROR_WANT_READ was returned, things would block; that is, the "handle_read" method on asyncore.dispatcher was never called again, so the SSLSocket.recv() method was never re-called. There are several levels of buffering going on, and I never figured out just why that was. This (very rare) re-call of "read" is to handle that.
You certainly do need to call read again if OpenSSL fails an SSL_read with a want-read error, but in asyncore, you don't want to do it right away, you want to wait until the socket becomes readable again, otherwise you *do* block waiting for bytes from the network. See the SSL support in Twisted for an example of the correct way to handle this. Jean-Paul
Jean-Paul Calderone
On Wed, 17 Sep 2008 10:40:01 PDT, Bill Janssen
wrote: Ah, now I remember. It seems that sometimes when SSL_ERROR_WANT_READ was returned, things would block; that is, the "handle_read" method on asyncore.dispatcher was never called again, so the SSLSocket.recv() method was never re-called. There are several levels of buffering going on, and I never figured out just why that was. This (very rare) re-call of "read" is to handle that.
You certainly do need to call read again if OpenSSL fails an SSL_read with a want-read error, but in asyncore, you don't want to do it right away, you want to wait until the socket becomes readable again, otherwise you *do* block waiting for bytes from the network. See the SSL support in Twisted for an example of the correct way to handle this.
Jean-Paul
Yes, I understand, and that's how I started out. The bug we were seeing was that "handle_read" wasn't being called again by asyncore. Bill
Bill Janssen wrote:
Jean-Paul Calderone
wrote: On Wed, 17 Sep 2008 10:40:01 PDT, Bill Janssen
wrote: Ah, now I remember. It seems that sometimes when SSL_ERROR_WANT_READ was returned, things would block; that is, the "handle_read" method on asyncore.dispatcher was never called again, so the SSLSocket.recv() method was never re-called. There are several levels of buffering going on, and I never figured out just why that was. This (very rare) re-call of "read" is to handle that.
You certainly do need to call read again if OpenSSL fails an SSL_read with a want-read error, but in asyncore, you don't want to do it right away, you want to wait until the socket becomes readable again, otherwise you *do* block waiting for bytes from the network. See the SSL support in Twisted for an example of the correct way to handle this.
Jean-Paul
Yes, I understand, and that's how I started out. The bug we were seeing was that "handle_read" wasn't being called again by asyncore.
It's probably worth sticking a comment in the code explaining why we're taking the risk of temporarily blocking on a non-blocking socket (i.e. until someone figures out how to reproduce that bug reliably so that a more correct fix can be devised). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://www.boredomandlaziness.org
Ok, here's some news, in case they can be of some interest. I managed to write an asyncore disptacher which seems to work. I used my test suite against it and 70 tests passed and 2 failed. The tests failed because at a certain point a call to do_handhsake results in an EOF exception, which is very strange since it is supposed to raise SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE only. I'll keep you updated in case I have some news. --- Exception --- File "C:\python26\lib\ssl.py", line 293, in do_handshake self._sslobj.do_handshake() SSLError: [Errno 8] _ssl.c:480: EOF occurred in violation of protocol --- SSL dispatcher ---- class SSLConnection(asyncore.dispatcher): def __init__(self): self.ssl_handshake_pending = False def do_ssl_handshake(self): try: self.socket.do_handshake() except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: self.ssl_handshake_pending = 'read' elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: self.ssl_handshake_pending = 'write' else: raise else: self.ssl_handshake_pending = False def handle_read_event(self): if self.ssl_handshake_pending == 'read': self.do_ssl_handshake() ## if not self.ssl_handshake_pending: ## asyncore.dispatcher.handle_read_event(self) else: asyncore.dispatcher.handle_read_event(self) def handle_write_event(self): if self.ssl_handshake_pending == 'write': self.do_ssl_handshake() ## if not self.ssl_handshake_pending: ## asyncore.dispatcher.handle_write_event(self) else: asyncore.dispatcher.handle_write_event(self) --- Giampaolo http://code.google.com/p/pyftpdlib/
Some good news: I finally figured out how to modify asyncore to make
it properly handle the non-blocking ssl-handshake.
I provided a patch for test_ssl.py in issue 3899.
Bill, could you please review it?
--- Giampaolo
http://code.google.com/p/pyftpdlib/
On 18 Set, 00:49, "Giampaolo Rodola'"
Ok, here's some news, in case they can be of some interest. I managed to write an asyncore disptacher which seems to work. I used my test suite against it and 70 tests passed and 2 failed. The tests failed because at a certain point a call to do_handhsake results in an EOF exception, which is very strange since it is supposed to raise SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE only. I'll keep you updated in case I have some news.
--- Exception ---
File "C:\python26\lib\ssl.py", line 293, in do_handshake self._sslobj.do_handshake() SSLError: [Errno 8] _ssl.c:480: EOF occurred in violation of protocol
--- SSL dispatcher ----
class SSLConnection(asyncore.dispatcher):
def __init__(self): self.ssl_handshake_pending = False
def do_ssl_handshake(self): try: self.socket.do_handshake() except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: self.ssl_handshake_pending = 'read' elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: self.ssl_handshake_pending = 'write' else: raise else: self.ssl_handshake_pending = False
def handle_read_event(self): if self.ssl_handshake_pending == 'read': self.do_ssl_handshake() ## if not self.ssl_handshake_pending: ## asyncore.dispatcher.handle_read_event(self) else: asyncore.dispatcher.handle_read_event(self)
def handle_write_event(self): if self.ssl_handshake_pending == 'write': self.do_ssl_handshake() ## if not self.ssl_handshake_pending: ## asyncore.dispatcher.handle_write_event(self) else: asyncore.dispatcher.handle_write_event(self)
--- Giampaolohttp://code.google.com/p/pyftpdlib/ _______________________________________________ Python-Dev mailing list Python-...@python.orghttp://mail.python.org/mailman/listinfo/python-dev Unsubscribe:http://mail.python.org/mailman/options/python-dev/python-dev2-garchiv...
participants (4)
-
Bill Janssen
-
Giampaolo Rodola'
-
Jean-Paul Calderone
-
Nick Coghlan