【问题标题】:Linux Socket Input Notification on a Client-Server application客户端-服务器应用程序上的 Linux 套接字输入通知
【发布时间】:2018-06-13 13:04:42
【问题描述】:

这个问题是this question and answer的后续问题

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

问题出现在以下协议链中

  1. 客户端发送命令
  2. 服务器收到它
  3. 服务器生成一个子节点,输入、输出和错误 dup-ed 使用 server-client 套接字(因此输入和输出将直接通过套接字)
  4. 服务器等待子进程并等待新命令

使用 SSL 时,不能直接使用读写操作,这意味着孩子使用 SSL 套接字发送纯数据(因为它不会使用 SSL_write 或 SSL_read,但客户端会,这会产生问题)。

因此,正如您从 answer 中看到的那样,一种解决方案是创建 3 组额外的 本地套接字,只有服务器及其子级才能共享,所以数据可以在未加密的情况下流动,然后才使用正确的SSL 命令将其发送给客户端。

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

我想应该创建一些线程,它们将监视并锁定 SSL 结构以保持顺序,但我仍然无法想象当子应用程序遇到 scanf("%d") 时服务器将如何得到通知或者别的什么。

【问题讨论】:

    标签: linux sockets ssl concurrency


    【解决方案1】:

    为了说明需要做什么,请使用以下 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 连接后,我们将分叉程序。该程序将读取标准输入并写入标准输出,我们希望将来自 SSL 客户端的解密输入作为程序的输入转发,并将加密的程序输出转发给 SSL 客户端。为此,我们创建了一个套接字对,用于在父进程和派生程序之间交换数据,并将套接字对的一侧重新映射到 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。读取数据后,我们将其写入另一端。在 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 获得。

    【讨论】:

    • 非常感谢您的工作。自从我开始编写程序的线程版本以来已经有几分钟了。基本上,我在两个不同的线程中同时读取和写入,将互斥锁保存到单个 SSL 结构。我意识到应用程序是否想要读取我的输入并不重要,我不必显式等待通知
    • 我一直在等待用户输入,如果我得到它 - 我会发送它。与输出相同
    猜你喜欢
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多