客户端 - 服务器应用程序上的Linux套接字输入通知

时间:2018-01-03 20:09:23

标签: linux sockets ssl concurrency

此问题是this question and answer

的后续问题

我正在使用SSL和套接字编程在Linux上构建一个简约的远程访问程序。

问题出现在以下协议链中

  1. 客户端发送命令
  2. 服务器收到它
  3. 服务器生成一个子进程,输入,输出和错误dup - 使用server-client套接字(因此输入和输出将直接通过套接字流动)
  4. 服务器等待子进程并等待新命令
  5. 使用SSL时,不能直接使用读写操作,这意味着使用SSL套接字的子进程发送普通数据(因为它不会使用SSL_write或SSL_read,但是客户端会这样做,这会产生问题)。 / p>

    因此,正如您可以从答案中读到的,一个解决方案是创建另外3组本地套接字,只有服务器和它的孩子才会共享,所以数据可以不加密流动,然后使用适当的SSL命令将其发送到客户端。

    所以问题是 - 我怎么知道孩子什么时候想读,所以我可以要求客户输入。或者我如何知道孩子何时输出内容以便我可以将其转发给客户

    我认为应该创建一些线程,它们将监视并锁定SSL结构以保持顺序,但我仍然无法想象当子应用程序命中{{1时服务器时如何通知服务器或者别的什么。

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,在普通套接字上使用recvread(来自socketpair) 。读取数据后,我们将其写入另一侧。在Python中我们可以使用sendall,在C中我们需要在SSL套接字上使用SSL_write,在普通套接字上使用sendwrite - 我们还需要确保所有数据都已写入,即可能多次尝试。

使用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")

就是这样。完整的程序也可用here,我用来测试的小程序可用here