i want to add ssl-support to an existing TCP-server which is based on the SocketServer.TCPServer class.
So i overrode the default constructor of the TCPServer class and added the ssl.wrap_socket(...)-call:
class MyTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
# See SocketServer.TCPServer.__init__
# (added ssl-support):
SocketServer.BaseServer.__init__(self, server_address,
RequestHandlerClass)
self.socket = ssl.wrap_socket(
socket.socket(self.address_family, self.socket_type),
server_side=True,
certfile='cert.pem'
)
if bind_and_activate:
self.server_bind()
self.server_activate()
When starting the server, no error occurrs.
So i modified my simple test-client to support ssl, too:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = ssl.wrap_socket(s)
sock.connect(('192.168.1.1', 54321))
Again no error occurrs, but the connect-call is blocking. When closing the client using Ctrl+C it shows the following:
Traceback (most recent call last):
File "exampleClient.py", line 10, in <module>
sock.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
KeyboardInterrupt
So the do_handshake is blocking when connecting. Does anyone knows how to fix the problem? I simply want to use an encrypted TCP-connection :)
The handshake is blocking because you are wrapping the socket after binding; the socket is listening for new connections, there is no client yet to accept your connections.
Wrap the socket when accepting a connection instead:
class MyTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def get_request(self):
(socket, addr) = SocketServer.TCPServer.get_request(self)
return (ssl.wrap_socket(socket, server_side=True, certfile="cert.pem"),
addr)
Now the handshake succeeds because there is a client on the other side to shake hands with.
There is no additional work necessary for the stream handler; the python ssl library gives you objects with the same interface as socket.socket().
You can also wrap the socket early, but do postpone the handshake until you accept a connection:
class MyTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def server_bind(self):
SocketServer.TCPServer.server_bind(self)
self.socket = ssl.wrap_socket(
self.socket, server_side=True, certfile="cert.pem",
do_handshake_on_connect=False)
def get_request(self):
(socket, addr) = SocketServer.TCPServer.get_request(self)
socket.do_handshake()
return (socket, addr)
Ok, i found a solution. Now i use something similar to
this using the OpenSSL-package:
Inside the MyTCPServer-Constructor:
SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
ctx = SSL.Context(SSL.SSLv23_METHOD)
cert = 'cert.pem'
ctx.use_privatekey_file(cert)
ctx.use_certificate_file(cert)
self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
self.socket_type))
if bind_and_activate:
self.server_bind()
self.server_activate()
And in the setup-method of the StreamRequestHandler:
self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
This seems to work fine :-)
That's because you have to set the do_handshake_on_connect argument in your call to ssl.wrap_socket:
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.
Source: http://docs.python.org/library/ssl.html
Related
I have the following code:
class Server:
def __init__(self, port, isWithThread):
self.isWithThread = isWithThread
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setblocking(0)
log.info("Socket created...")
def __enter__(self):
self.sock.bind(('127.0.0.1', self.port))
self.sock.listen(5)
log.info("Listening on %s:%s", '127.0.0.1', self.port)
return self
def __exit__(self, type, value, traceback):
self.sock.setblocking(1)
self.sock.close()
log.info("Socket closed.")
log.info("Bye")
def run(self):
#client, addr = self.sock.accept()
log.info('Selecting...')
readers, writers, errors = select.select([self.sock], [], [], 10)
log.debug(readers)
if readers:
client, addr = readers[0].accept()
log.info('Client: %s', client.recv(2048).decode())
client.sendall("Hippee!".encode())
client.close()
log.info("Disconnected from %s:%s", *addr)
What's interesting about this is that when I have the select.select and setblocking(0), it ends out keeping the address in use. If I remove the setblocking code and change the run function to:
def run(self):
client, addr = self.sock.accept()
log.info('Client: %s', client.recv(2048).decode())
client.sendall("Hippee!".encode())
client.close()
log.info("Disconnected from %s:%s", *addr)
Then I can immediately re-run the server. With the select() call, I get the following error:
python3.3 test.py server
Socket created...
Traceback (most recent call last):
File "test.py", line 89, in <module>
with Server(12345, False) as s:
File "test.py", line 57, in __enter__
self.sock.bind(('127.0.0.1', self.port))
OSError: [Errno 98] Address already in use
So why does it appear that the select is keeping my socket open, and how do I ensure it closes?
Magic. Magic is the only reason to see any difference with or without select.select(). According to this page, the reason that a socket will stay in use even after calling .close() is that the TIME_WAIT has not yet expired.
The solution is to use .setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1).
I tried this but it didn't work, and so I asked this question. Later, I realized that hey, I knew that things like Flask or SimpleHTTPServer will allow you to restart the server immediately. So I used the source, and examined the library code contained in socketserver.py. It was here that I discovered the use of .setsocketopt() but the call came before the call to .bind().
To explain setsocketopt(), let's see what does the docs say?
socket.setsockopt(level, optname, value)
Set the value of the given socket option (see the Unix manual page setsockopt(2)). The needed symbolic constants are defined in the socket module (SO_* etc.). The value can be an integer or a string representing a buffer. In the latter case it is up to the caller to ensure that the string contains the proper bits (see the optional built-in module struct for a way to encode C structures as strings)
level refers to the level of the TCP/IP stack you want to talk about. In this case we don't want the IP layer but the socket itself. The socket option is SO_REUSEADDR, and we're setting the flag (value=1). So somewhere down in the kernel or drivers, we're effectively saying, "SHHhhhhhh... It's OK. I don't care that you're in TIME_WAIT right now. I want to .bind() to you anyway."
So I changed up my code to have:
sock.setsocketopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', self.port))
And it works perfectly.
\o/
Here is my code to run the server:
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
#....
PORT = 8089
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)
httpd.allow_reuse_address = True
print "Serving forever at port", PORT
try:
httpd.serve_forever()
except:
print "Closing the server."
httpd.server_close()
raise
Yet this is what happens:
^CClosing the server.
Traceback (most recent call last):
File "server.py", line 118, in <module>
self.send_error(400, "Unimplemented GET command: %s" % (self.path,))
File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 224, in serve_forever
r, w, e = select.select([self], [], [], poll_interval)
KeyboardInterrupt
(.virtualenv)claudiu#xxx:~/xxx$ python server.py
Traceback (most recent call last):
File "server.py", line 122, in <module>
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)
File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 402, in __init__
self.server_bind()
File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 413, in server_bind
self.socket.bind(self.server_address)
File "<string>", line 1, in bind
socket.error: [Errno 98] Address already in use
Why? I close the server and set allow_reuse_address to True... Using python 2.6.8.
Thanks to the other answers, I figured it out. allow_reuse_address should be on the class, not on the instance:
SocketServer.TCPServer.allow_reuse_address = True
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)
I'm still not sure why closing the socket didn't free it up for the next run of the server, though.
The [Err 98] Address already in use is due to the fact the socket was .close() but it's still waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request (see TIME_WAIT). By default you are not allowed to bind a socket if there a socket bound to that port but you can override that with allow_reuse_address (SO_REUSEADDR)
Although is possible to mutate TCPServer.allow_reuse_addr (as proposed in this other answer), I think is more clean to your own subclass of TCPServer where allow_reuse_address is set to True:
import SocketServer
import SimpleHTTPServer
import time
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET():
time.sleep(60)
self.request.sendall("I'm here!")
class ReuseAddrTCPServer(SocketServer.TCPServer):
allow_reuse_address = True
PORT = 8089
httpd = ReuseAddrTCPServer(("", PORT), MyRequestHandler)
httpd.daemon_threads = True
print "Serving forever at port", PORT
try:
httpd.serve_forever()
except:
print "Closing the server."
httpd.server_close()
raise
You can use definitely set allow_reuse_address on the instance itself (without messing with classes) but you need to use TCPServer(..., bind_and_activate=False), otherwise the socket will be bound before you have a chance to change the allow_reuse_address setting. Then you need to manually call .server_bind() and .server_activate() before serve_forever():
...
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.daemon_threads = True
...
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()
It is because TCP TIME_WAIT.
Somebody discovered this exact problem.
However, if I try to stop and start the server again to test any modifications, I get a random “socket.error: [Errno 98] Address
already in use” error. This happens only if a client has already
connected to the server.
Checking with netstat and ps, I found that although the process it
self is no longer running, the socket is still listening on the port
with status “TIME_WAIT”. Basically the OS waits for a while to make
sure this connection has no remaining packets on the way.
It is because you have to set SO_REUSEADDRESS before you bind the socket. As you are creating and binding the socket all in one step and then setting it, it is already too late.
I'm trying to implement a simple XML-RPC server on Python 3, and I want it to run over HTTPS using the standard ssl library (included in Python 2.6 and Python 3.x).
I've seen some code that does it with OpenSSL or M2Crypto modules, but I want to avoid any dependency.
I implemented the next code which should wrap the SSL protocol over the socket:
"""Monkey patching standard xmlrpc.server.SimpleXMLRPCServer
to run over TLS (SSL)
Changes inspired on http://www.cs.technion.ac.il/~danken/SecureXMLRPCServer.py
"""
import socket
import socketserver
import ssl
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCDispatcher, SimpleXMLRPCRequestHandler
try:
import fcntl
except ImportError:
fcntl = None
class SimpleXMLRPCServerTLS(SimpleXMLRPCServer):
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
"""Overriding __init__ method of the SimpleXMLRPCServer
The method is an exact copy, except the TCPServer __init__
call, which is rewritten using TLS
"""
self.logRequests = logRequests
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
"""This is the modified part. Original code was:
socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
which executed:
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
self.server_bind()
self.server_activate()
"""
socketserver.BaseServer.__init__(self, addr, requestHandler)
self.socket = ssl.wrap_socket(
socket.socket(self.address_family, self.socket_type),
server_side=True,
cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_TLSv1,
)
if bind_and_activate:
self.server_bind()
self.server_activate()
"""End of modified part"""
# [Bug #1222790] If possible, set close-on-exec flag; if a
# method spawns a subprocess, the subprocess shouldn't have
# the listening socket open.
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
But for some reason I couldn't determine, it's raising this error:
[Errno 8] _ssl.c:502: EOF occurred in violation of protocol
before calling the remote method.
Does anyone knows what could be happening, or has any idea on how to gather more information to get a clue on what could be the problem?
Thank you so much in advance!
UPDATE:
Fixed. There were two errors on the code. First, it's necessary to specify the certificate file (which can include the key too).
Second is that xmlrpc.client.ServerProxy (included in Python) is using SSLv2, so TLSv1 does not work.
Working code is:
"""Monkey patching standard xmlrpc.server.SimpleXMLRPCServer
to run over TLS (SSL)
Changes inspired on http://www.cs.technion.ac.il/~danken/SecureXMLRPCServer.py
"""
import socket
import socketserver
import ssl
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCDispatcher, SimpleXMLRPCRequestHandler
try:
import fcntl
except ImportError:
fcntl = None
class SimpleXMLRPCServerTLS(SimpleXMLRPCServer):
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
"""Overriding __init__ method of the SimpleXMLRPCServer
The method is an exact copy, except the TCPServer __init__
call, which is rewritten using TLS
"""
self.logRequests = logRequests
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
"""This is the modified part. Original code was:
socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
which executed:
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
self.server_bind()
self.server_activate()
"""
socketserver.BaseServer.__init__(self, addr, requestHandler)
self.socket = ssl.wrap_socket(
socket.socket(self.address_family, self.socket_type),
server_side=True,
certfile='cert.pem',
cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_SSLv23,
)
if bind_and_activate:
self.server_bind()
self.server_activate()
"""End of modified part"""
# [Bug #1222790] If possible, set close-on-exec flag; if a
# method spawns a subprocess, the subprocess shouldn't have
# the listening socket open.
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
Why don't you write just like this:
server = SimpleXMLRPCServer(...)
...
server.socket = ssl.wrap_socket(srv.socket, ...)
server.serve_forever()
Move your Python server behind a reverse proxy like Apache, NGinx or whatever and let the reverse proxy to the SSL job...this works much better and smoother than on the Python level.
I'd like to manually (using the socket and ssl modules) make an HTTPS request through a proxy which itself uses HTTPS.
I can perform the initial CONNECT exchange just fine:
import ssl, socket
PROXY_ADDR = ("proxy-addr", 443)
CONNECT = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"
sock = socket.create_connection(PROXY_ADDR)
sock = ssl.wrap_socket(sock)
sock.sendall(CONNECT)
s = ""
while s[-4:] != "\r\n\r\n":
s += sock.recv(1)
print repr(s)
The above code prints HTTP/1.1 200 Connection established plus some headers, which is what I expect. So now I should be ready to make the request, e.g.
sock.sendall("GET / HTTP/1.1\r\n\r\n")
but the above code returns
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</body></html>
This makes sense too, since I still need to do an SSL handshake with the example.com server to which I'm tunneling. However, if instead of immediately sending the GET request I say
sock = ssl.wrap_socket(sock)
to do the handshake with the remote server, then I get an exception:
Traceback (most recent call last):
File "so_test.py", line 18, in <module>
ssl.wrap_socket(sock)
File "/usr/lib/python2.6/ssl.py", line 350, in wrap_socket
suppress_ragged_eofs=suppress_ragged_eofs)
File "/usr/lib/python2.6/ssl.py", line 118, in __init__
self.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [Errno 1] _ssl.c:480: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
So how can I do the SSL handshake with the remote example.com server?
EDIT: I'm pretty sure that no additional data is available before my second call to wrap_socket because calling sock.recv(1) blocks indefinitely.
This should work if the CONNECT string is rewritten as follows:
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
Not sure why this works, but maybe it has something to do with the proxy I'm using. Here's an example code:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("proxy.example.com", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(CONNECT)
print s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print cert.get_subject()
ss.shutdown()
ss.close()
Note how the socket is first opened and then open socket placed in SSL context. Then I manually initialize SSL handshake. And output:
HTTP/1.1 200 Connection established
<X509Name object '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com'>
It's based on pyOpenSSL because I needed to fetch invalid certificates too and Python built-in ssl module will always try to verify the certificate if it's received.
Judging from the API of the OpenSSL and GnuTLS library, stacking a SSLSocket onto a SSLSocket is actually not straightforwardly possible as they provide special read/write functions to implement the encryption, which they are not able to use themselves when wrapping a pre-existing SSLSocket.
The error is therefore caused by the inner SSLSocket directly reading from the system socket and not from the outer SSLSocket. This ends in sending data not belonging to the outer SSL session, which ends badly and for sure never returns a valid ServerHello.
Concluding from that, I would say there is no simple way to implement what you (and actually myself) would like to accomplish.
Finally I got somewhere expanding on #kravietz and #02strich answers.
Here's the code
import threading
import select
import socket
import ssl
server = 'mail.google.com'
port = 443
PROXY = ("localhost", 4433)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
class ForwardedSocket(threading.Thread):
def __init__(self, s, **kwargs):
threading.Thread.__init__(self)
self.dest = s
self.oursraw, self.theirsraw = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
self.theirs = socket.socket(_sock=self.theirsraw)
self.start()
self.ours = ssl.wrap_socket(socket.socket(_sock=self.oursraw), **kwargs)
def run(self):
rl, wl, xl = select.select([self.dest, self.theirs], [], [], 1)
print rl, wl, xl
# FIXME write may block
if self.theirs in rl:
self.dest.send(self.theirs.recv(4096))
if self.dest in rl:
self.theirs.send(self.dest.recv(4096))
def recv(self, *args):
return self.ours.recv(*args)
def send(self, *args):
return self.outs.recv(*args)
def test():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY)
s = ssl.wrap_socket(s, ciphers="ALL:aNULL:eNULL")
s.send(CONNECT)
resp = s.read(4096)
print (resp, )
fs = ForwardedSocket(s, ciphers="ALL:aNULL:eNULL")
fs.send("foobar")
Don't mind custom cihpers=, that only because I didn't want to deal with certificates.
And there's depth-1 ssl output, showing CONNECT, my response to it ssagd and depth-2 ssl negotiation and binary rubbish:
[dima#bmg ~]$ openssl s_server -nocert -cipher "ALL:aNULL:eNULL"
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
-----BEGIN SSL SESSION PARAMETERS-----
MHUCAQECAgMDBALAGQQgmn6XfJt8ru+edj6BXljltJf43Sz6AmacYM/dSmrhgl4E
MOztEauhPoixCwS84DL29MD/OxuxuvG5tnkN59ikoqtfrnCKsk8Y9JtUU9zuaDFV
ZaEGAgRSnJ81ogQCAgEspAYEBAEAAAA=
-----END SSL SESSION PARAMETERS-----
Shared ciphers: [snipped]
CIPHER is AECDH-AES256-SHA
Secure Renegotiation IS supported
CONNECT mail.google.com:443 HTTP/1.0
Connection: close
sagq
�u\�0�,�(�$��
�"�!��kj98���� �m:��2�.�*�&���=5�����
��/�+�'�#�� ����g#32��ED���l4�F�1�-�)�%���</�A������
�� ������
�;��A��q�J&O��y�l
It doesn't sound like there's anything wrong with what you're doing; it's certainly possible to call wrap_socket() on an existing SSLSocket.
The 'unknown protocol' error can occur (amongst other reasons) if there's extra data waiting to be read on the socket at the point you call wrap_socket(), for instance an extra \r\n or an HTTP error (due to a missing cert on the server end, for instance). Are you certain you've read everything available at that point?
If you can force the first SSL channel to use a "plain" RSA cipher (i.e. non-Diffie-Hellman) then you may be able to use Wireshark to decrypt the stream to see what's going on.
Building on #kravietz answer. Here is a version that works in Python3 through a Squid proxy:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("<proxy_server>", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(str.encode(CONNECT))
s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print(cert.get_subject())
ss.shutdown()
ss.close()
This works in Python 2 also.
I want to send data from a client to the server in a TLS TCP socket from multiple client subprocesses so I share the same ssl socket with all subprocesses. Communication works with one subprocess, but if I use more than one subprocesses, the TLS server crashes with an ssl.SSLError (SSL3_GET_RECORD:decryption failed or bad record mac).
More specific: It does not depend which process first calls the SSLSocket.write() method, but this process is the only one from this time on which can call it. If another process calls write(), the server will result in the exception described above.
I used this basic code:
tlsserver.py
import socket, ssl
def deal_with_client(connstream):
data = connstream.read()
while data:
print data
data = connstream.read()
connstream.close()
bindsocket = socket.socket()
bindsocket.bind(('127.0.0.1', 9998))
bindsocket.listen(5)
while True:
newsocket, fromaddr = bindsocket.accept()
connstream = ssl.wrap_socket(newsocket,
server_side=True,
certfile="srv.crt",
keyfile="srv.key",
ssl_version=ssl.PROTOCOL_TLSv1)
deal_with_client(connstream)
tlsclient.py
import socket, ssl
import multiprocessing
class SubProc:
def __init__(self, sock):
self.sock = sock
def do(self):
self.sock.write("Test")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = ssl.wrap_socket(s)
ssl_sock.connect(('127.0.0.1', 9998))
print "Connected to", repr(ssl_sock.getpeername())
for x in (1,2):
subproc = SubProc(ssl_sock)
proc = multiprocessing.Process(target=subproc.do)
And this is the backtrace:
Traceback (most recent call last):
File "tlsserver.py", line 21, in <module>
deal_with_client(connstream)
File "tlsserver.py", line 7, in deal_with_client
data = connstream.read()
File "/usr/lib64/python2.6/ssl.py", line 136, in read
return self._sslobj.read(len)
ssl.SSLError: [Errno 1] _ssl.c:1325: error:1408F119:SSL routines:SSL3_GET_RECORD:decryption failed or bad record mac
The problem is that you're re-using the same connection for both processes. The way SSL encrypts data makes this fail -- the two processes would have to communicate with each other about the state of the shared SSL connection. Even if you do make it work, or if you didn't use SSL, the data would arrive at the server all jumbled up; you would have no real way of distinguishing which bytes came from which process.
What you need to do is give each process its own SSL connection, by making the connection in subproc.do. Alternatively, don't have the subprocesses communicate with the server at all, but rather communicate with the main process, and have the main process relay it over the SSL connection.