客户端-服务器应用程序上的Linux套接字输入通知 [英] Linux Socket Input Notification on a Client-Server application

查看:71
本文介绍了客户端-服务器应用程序上的Linux套接字输入通知的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题是此问题和答案

$ b的后续操作
$ b

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



问题出现在以下协议链中


  1. 客户端发送命令

  2. 服务器接收到命令

  3. 服务器使用 server-client 套接字生成一个带有输入,输出和错误 dup -ed的子对象(因此输入和输出将直接流过套接字)

  4. 服务器等待子进程并等待新命令

使用SSL时,不能直接使用读取和写入操作,这意味着使用SSL套接字的子级将发送纯数据(因为它不会使用SSL_write或SSL_read,但是客户端会使用,这会造成问题)。 / p>

因此,您可以从 answer 中读取,一种解决方案是是要另外创建3组本地套接字,该组服务器和它的子级服务器将共享该套接字,因此数据可以不加密地流动,然后才使用正确的发送给客户端SSL 命令。



所以问题是-我什至不知道孩子什么时候想读书,所以我可以要求来自客户端的输入。或者我怎么知道孩子什么时候输出某些东西,以便将其转发给客户端



我想应该创建一些线程来监视和在SSL结构上设置了锁以保持顺序,但是我仍然无法想象当子应用程序命中 scanf(%d)

解决方案

要说明需要完成的工作,请使用以下Python程序。我之所以使用Python,是因为它易于阅读,但是在C语言中也可以做到这一点,只需要更多的代码行,并且难以阅读。



首先进行一些初始化,即创建一些套接字服务器,SSL上下文,接受新客户端,将客户端fd包装到SSL套接字中以及在客户端和套接字服务器之间进行一些初始通信。根据您先前的问题,您可能已经知道如何使用C进行此操作,而Python代码与您在C中所做的操作相距不远:

  import socket 
import ssl
import select
import os

local_addr =('',8888)#我们在哪里收听
cmd = ['./cmd.pl']#做一些命令来读取标准输入,写入标准输出

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)

试试:
cl,addr = srv.accept()
除外:
pass
cl = ctx.wrap_socket(cl,server_side = True)
print(来自{}的新连接。.format(addr))

buf = cl.read(1024)
print(接收到'{}' .format(buf))
cl.write( hi!\n)

完成此设置后,我们与客户端建立了SSL连接,我们将派生该程序。该程序将在stdin上读取并写入stdout,我们希望将来自SSL客户端的解密输入作为程序的输入转发,并将加密后的程序的输出转发给SSL客户端。为此,我们创建了一个套接字对,用于在父进程和分支程序之间交换数据,并在 execv 程序之前将套接字对的一侧重新映射到分支子进程中的stdin / stdout。这也非常类似于C:

  print( forking subprocess)
tokid,toparent = socket.socketpair ()
pid = os.fork()
如果pid == 0:
tokid.close()
#子级:重新映射stdin / stdout和exec cmd
os.dup2(toparent.fileno(),0)
os.dup2(toparent.fileno(),1)
toparent.close()
os.execv(cmd [0], cmd)
#不会返回

#父进程
toparent.close()

现在,我们需要从SSL客户端读取数据并派生命令,然后将其转发到另一端。虽然可能可以通过阻止线程内部的读取来做到这一点,但我更喜欢使用select的事件。 Python中select的语法与C中的语法有些不同(即更简单),但思想是完全相同的:当我们从客户端或派生命令获得数据或无数据的一秒钟时,将返回它的调用方式已经过去:

 #父级:等待来自客户端和子流程的数据并将其转发给
#子流程和客户端
done = False
而未完成:
可读,_,_ = select.select([cl,tokid],[],[],1)
如果不可读:
打印(一秒钟无数据)
继续

可读不为空,我们有新数据等待我们读取。在Python中,我们在文件句柄上使用 recv ,在C语言中,我们需要在SSL套接字上使用 SSL_read recv read 在普通套接字上(来自套接字对)。读取数据后,我们将其写入另一侧。在Python中,我们可以使用 sendall ,在C语言中,我们需要在SSL套接字上使用 SSL_write 在普通套接字上发送 write -我们还需要确保所有数据均已写入,即尝试多次。



与SSL套接字一起使用select时,有一件值得注意的事情。如果 SSL_read 小于最大SSL帧大小,则可能是SSL帧的有效负载大于 SSL_read 。在这种情况下,其余数据将由OpenSSL在内部进行缓冲,即使存在已缓冲的数据,对select的下一次调用也可能不会显示更多可用数据。要解决此问题,要么需要检查 SSL_pending 是否有缓冲数据,要么只使用 SSL_read 始终使用最大SSL帧大小:

 为可读的fd:
#始终尝试读取16k,因为这是$的最大大小b $ b#SSL帧。使用较小的读取大小,我们将需要显式地
#处理来自SSL的待处理数据(man SSL_pending)
buf = fd.recv(16384)

print( got {}来自{}。format(len(buf), client的字节,如果fd == cl否则为子进程))
writeto = tokid如果fd == cl否则cl
如果buf ==' ':
#eof
writeto.close()
完成=真
休息#从程序
返回其他:
writeto.sendall(buf)

print(连接完成)

就是这样。完整程序也可以在此处获得,而我曾经测试过的小程序此处


This question is a followup to this question and answer

I'm building a minimalistic remote access programm on Linux using SSL and socket programming.

The problem arose in the following protocol chain

  1. Client sends command
  2. Server recieves it
  3. Server spawns a child, with input, output and error dup-ed with the server-client socket (so the input and output would flow directly though the sockets)
  4. Server waits for the child and waits for a new command

When using SSL, you cannot use read and write operations directly, meaning the child using SSL sockets with send plain data (because it won't use SSL_write or SSL_read, but the client will, and this will create problems).

So, as you could read from the answer, one solution would be to create 3 additional sets of local sockets, that only server and it's child will share, so the data could flow unencrypted, and only then send it to the client with a proper SSL command.

So the question is - how do I even know when a child wants to read, so I could ask for input from the client. Or how do I know when the child outputs something so I could forward that to the client

I suppose there should be created some threads, that will monitor and put locks on the SSL structure to keep the order, but I still can't imagine how the server would get notified, when the child application hit a scanf("%d") or something else.

解决方案

To illustrate what need to be done use the following Python program. I've used Python only because it is easy to read but the same can be done in C, only with more lines of code and harder to read.

Let's first do some initialization, i.e. create some socket server, SSL context, accept a new client, wrap the client fd into an SSL socket and do some initial communication between client and socket server. Based on your previous question you probably know how to this in C already and the Python code is not that far away from what you do in 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")

After this setup is done and we have a SSL connection to the client we will fork the program. This program will read on stdin and write to stdout and we want to forward the decrypted input from the SSL client as input to the program and forward the output of the program encrypted to the SSL client. To do this we create a socketpair for exchanging data between parent process and forked program and remap the one side of the socketpair to stdin/stdout in the forked child before execv the program. This works also very similar to 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()

Now we need to read the data from SSL client and forked command and forward it to the other side. While one might probably do this with blocking reads inside threads I prefer it event based using select. The syntax for select in Python is a bit different (i.e. simpler) than in C but the idea is exactly the same: the way we call it it will return when we have data from either client or forked command or if one second of no data has elapsed:

# 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

Since readable is not empty we have new data waiting for us to read. In Python we use recv on the file handle, in C we would need to use SSL_read on the SSL socket and recv or read on the plain socket (from socketpair). After the data is read we write it to the other side. In Python we can use sendall, in C we would need to use SSL_write on the SSL socket and send or write on the plain socket - and we would also need to make sure that all data were written, i.e. maybe try multiple times.

There is one notable thing when using select in connection with SSL sockets. If you SSL_read less than the maximum SSL frame size it might be that the payload of the SSL frame was larger than what was requested within SSL_read. In this case the remaining data will be buffered internally by OpenSSL and the next call to select might not show more available data even though there are the already buffered data. To work around this one either needs to check with SSL_pending for buffered data or just use SSL_read always with the maximum SSL frame size:

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

And that's it. The full program is also available here and the small program I've used to test is available here.

这篇关于客户端-服务器应用程序上的Linux套接字输入通知的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆