我正在使用SSL和套接字编程在Linux上构建一个简约的远程访问程序。
问题出现在以下协议链中
dup
- 使用server-client
套接字(因此输入和输出将直接通过套接字流动)使用SSL时,不能直接使用读写操作,这意味着使用SSL套接字的子进程发送普通数据(因为它不会使用SSL_write或SSL_read,但是客户端会这样做,这会产生问题)。 / p>
因此,正如您可以从答案中读到的,一个解决方案是创建另外3组本地套接字,只有服务器和它的孩子才会共享,所以数据可以不加密流动,然后使用适当的SSL
命令将其发送到客户端。
所以问题是 - 我怎么知道孩子什么时候想读,所以我可以要求客户输入。或者我如何知道孩子何时输出内容以便我可以将其转发给客户
我认为应该创建一些线程,它们将监视并锁定SSL结构以保持顺序,但我仍然无法想象当子应用程序命中{{1时服务器时如何通知服务器或者别的什么。
答案 0 :(得分:2)
为了说明需要做什么,请使用以下Python程序。我之所以使用Python只是因为它很容易阅读,但同样可以在C中完成,只需要更多行代码并且难以阅读。
让我们先做一些初始化,即创建一些套接字服务器,SSL上下文,接受新客户端,将客户端fd包装到SSL套接字中,并在客户端和套接字服务器之间进行一些初始通信。根据您之前的问题,您可能已经知道如何使用C语言,并且Python代码与您在C中所做的相去甚远:
import socket
import ssl
import select
import os
local_addr = ('',8888) # where we listen
cmd = ['./cmd.pl'] # do some command reading stdin, writing stdout
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ctx.load_cert_chain('server_cert_and_key.pem')
srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(local_addr)
srv.listen(10)
try:
cl,addr = srv.accept()
except:
pass
cl = ctx.wrap_socket(cl,server_side=True)
print("new connection from {}".format(addr))
buf = cl.read(1024)
print("received '{}'".format(buf))
cl.write("hi!\n")
完成此设置并且我们与客户端建立SSL连接后,我们将分叉程序。该程序将读取stdin并写入stdout,我们希望将来自SSL客户端的解密输入作为输入转发给程序,并将加密程序的输出转发给SSL客户端。为此,我们创建一个socketpair,用于在父进程和forked程序之间交换数据,并在程序execv
之前将套接字对的一侧重新映射到分叉子进程中的stdin / stdout。这也与C:
print("forking subprocess")
tokid, toparent = socket.socketpair()
pid = os.fork()
if pid == 0:
tokid.close()
# child: remap stdin/stdout and exec cmd
os.dup2(toparent.fileno(),0)
os.dup2(toparent.fileno(),1)
toparent.close()
os.execv(cmd[0],cmd)
# will not return
# parent process
toparent.close()
现在我们需要从SSL客户端和分叉命令中读取数据并将其转发到另一端。虽然有人可能会通过阻塞内部线程读取来执行此操作,但我更喜欢使用select的事件。 Python中select的语法与C语言略有不同(即更简单),但思路完全相同:当我们从客户端或分叉命令获取数据或者没有数据的一秒时,我们称之为它的方式将返回已经过去了:
# parent: wait for data from client and subprocess and forward these to
# subprocess and client
done = False
while not done:
readable,_,_ = select.select([ cl,tokid ], [], [], 1)
if not readable:
print("no data for one second")
continue
由于readable
不为空,我们有新数据等待我们阅读。在Python中,我们在文件句柄上使用recv
,在C中我们需要在SSL套接字上使用SSL_read
,在普通套接字上使用recv
或read
(来自socketpair) 。读取数据后,我们将其写入另一侧。在Python中我们可以使用sendall
,在C中我们需要在SSL套接字上使用SSL_write
,在普通套接字上使用send
或write
- 我们还需要确保所有数据都已写入,即可能多次尝试。
使用select连接SSL套接字时有一个值得注意的事情。如果SSL_read
小于最大SSL帧大小,则可能是SSL帧的有效负载大于SSL_read
中请求的有效负载。在这种情况下,剩余数据将由OpenSSL在内部缓冲,即使存在已缓冲的数据,下一次调用select也可能不会显示更多可用数据。要解决此问题,需要使用SSL_pending
检查缓冲数据,或者只使用SSL_read
始终使用最大SSL帧大小:
for fd in readable:
# Always try to read 16k since this is the maximum size for an
# SSL frame. With lower read sizes we would need to explicitly
# deal with pending data from SSL (man SSL_pending)
buf = fd.recv(16384)
print("got {} bytes from {}".format(len(buf),"client" if fd == cl else "subprocess"))
writeto = tokid if fd == cl else cl
if buf == '':
# eof
writeto.close()
done = True
break # return from program
else:
writeto.sendall(buf)
print("connection done")