如何在Python中将INET套接字连接到PTY设备

时间:2021-05-20 21:02:05

I am trying to duplicate the behavior of this socat command in Python:

我试图在Python中复制这个socat命令的行为:

socat PTY,link=/tmp/mypty tcp-listen:1234

A TCP connection to the server port 1234 will be connected to /tmp/mypty (a link to /dev/pts/N), and running screen /tmp/mypty will allow a user to interact with whatever is on the other end of that connection. In other words, if this is used to connect to that listening port:

与服务器端口1234的TCP连接将连接到/ tmp / mypty(指向/ dev / pts / N的链接),并且运行screen / tmp / mypty将允许用户与另一端的任何内容进行交互。连接。换句话说,如果这用于连接到该侦听端口:

socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp-connect:172.16.30.3:1234

Then running screen /tmp/mypty will connect the user to the instance of bash running on the other end of the TCP connection.

然后运行screen / tmp / mypty将用户连接到TCP连接另一端运行的bash实例。

Getting the TCP socket is straightforward. And I can get a PTY using os.openpty(), which yields me a pair of file descriptors, which are just integers.

获取TCP套接字非常简单。我可以使用os.openpty()来获取一个PTY,这会产生一对文件描述符,它们只是整数。

What I am unable to do is hook that socket and those file descriptors in a way that will seamlessly pass data back and forth. If they were both sockets, it would be trivial; use a loop with select() to pass new read data to the other socket's write.

我无法做的是以一种无缝地来回传递数据的方式挂钩该套接字和那些文件描述符。如果它们都是插座,那将是微不足道的;使用带select()的循环将新读取数据传递给另一个套接字的写入。

What I have tried:

我试过的:

  1. Using socket.fromfd to build a socket using one of the PTY file descriptors. This results in OSError: [Errno 88] Socket operation on non-socket. Example of this code, where clisock is a valid socket:
  2. 使用socket.fromfd使用其中一个PTY文件描述符构建套接字。这导致OSError:[Errno 88]非套接字上的套接字操作。此代码的示例,其中clisock是有效的套接字:


    def sockToPty(clisock,addr):
        (master, slave) = os.openpty()
        print('PTY: Opening {}'.format(os.ttyname(slave)))
        ptymsock = socket.fromfd(master, socket.AF_UNIX, socket.SOCK_STREAM)
        ptyssock = socket.fromfd(slave,  socket.AF_UNIX, socket.SOCK_STREAM)
        print('CLISOCK: {}'.format(clisock))
        print('PTYMSOCK: {}'.format(ptymsock))
        print('PTYSSOCK: {}'.format(ptyssock))
        peer[clisock] = ptymsock
        peer[ptyssock] = clisock
        while True:
            iready, oready, eready = select.select([clisock, ptyssock], [], [])

            for sock in iready:
                data = sock.recv(4096)
                if not len(data):
                    return
                peer[sock].send(data)

which results in this error:

这会导致此错误:


    PTY: Opening /dev/pts/2
    CLISOCK: 
    PTYMSOCK: 
    PTYSSOCK: 
    Exception in thread Thread-1:
    Traceback (most recent call last):
      File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
        self.run()
      File "/usr/lib/python3.6/threading.py", line 864, in run
        self._target(*self._args, **self._kwargs)
      File "./tcpmux.py", line 62, in sockToPty
        peer[sock].send(data)
    OSError: [Errno 88] Socket operation on non-socket

and it makes no difference if I use AF_INET instead of AF_UNIX.

如果我使用AF_INET而不是AF_UNIX,它没有任何区别。

  1. Using os.dup2() to map the SSLsocket's fileno() to stdin, stdout, and stderr and then running os.openpty() (a la tcp_pty_bind.py). This does not generate any errors, but there's no apparent data flow when I open the PTY using screen.
  2. 使用os.dup2()将SSLsocket的fileno()映射到stdin,stdout和stderr,然后运行os.openpty()(la tcp_pty_bind.py)。这不会产生任何错误,但是当我使用屏幕打开PTY时,没有明显的数据流。

Almost all of the Python PTY examples out there are focused on pty.spawn(), making it hard to find any example code of this (admittedly obscure) use of PTY.

几乎所有的Python PTY示例都集中在pty.spawn()上,因此很难找到使用PTY的任何示例代码(无可置疑的模糊)。

Side note: I'm using os.openpty instead of pty.openpty simply because there doesn't seem to be a point. The pty module seems to be advertised for more portability, but not to have much more portability, and I'm working on Linux so portability isn't an issue. And since I'm getting valid file descriptors to a correct os.ttyname(), I've no reason to question that openpty() isn't working properly.

旁注:我使用os.openpty而不是pty.openpty只是因为似乎没有意义。 pty模块似乎被宣传为更具可移植性,但没有更多的可移植性,而且我正在开发Linux,因此可移植性不是问题。因为我正在获得正确的os.ttyname()的有效文件描述符,所以我没有理由怀疑openpty()无法正常工作。

1 个解决方案

#1


0  

This can be done using select.poll() instead of select.select(), because select.poll() will work with both File Descriptors and Sockets.

这可以使用select.poll()而不是select.select()来完成,因为select.poll()将同时使用文件描述符和套接字。

def sockToPty(clisock,addr):
    (master, slave) = os.openpty()
    tty.setraw(master, termios.TCSANOW)
    print('PTY: Opened {} for {}'.format(os.ttyname(slave), addr))
    mypoll = select.poll()
    mypoll.register(clisock, select.POLLIN)
    mypoll.register(master, select.POLLIN)
    try:
        while True:
            fdlist = mypoll.poll(1000)
            for fd,event in fdlist:
                # We treat sockets and FDs differently
                if fd == master:
                    data = os.read(fd, 4096)
                    if len(data) == 0:
                        print('PTY: {}, {} exiting due to Zero read.'.format(addr, os.ttyname(slave)))
                        raise GetOut
                    clisock.send(data)
                else:
                    data = clisock.recv(4096)
                    if len(data) == 0:
                        print('PTY: {}, {} exiting due to Zero read.'.format(addr, os.ttyname(slave)))
                        raise GetOut
                    os.write(master, data)
    except GetOut:
        os.close(master)
        os.close(slave)
        clisock.shutdown(socket.SHUT_RDWR)
        clisock.close()

This code is more complex than it needs to be for simple sockets - with a TCP socket, you can simply use the fileno() for that socket as a File Descriptor and then you treat both sides the same way (os.read(), os.write()). However, if you're going to use this with an SSLSocket, you need the more complicated version above, because if you grab the fileno() from an SSLSocket it expects you're going to handle the TLS layer as well as the data.

这个代码比简单套接字需要的更复杂 - 使用TCP套接字,您可以简单地将该套接字的fileno()用作文件描述符,然后以相同的方式处理双方(os.read(), os.write())。但是,如果您要将其与SSLSocket一起使用,则需要上面更复杂的版本,因为如果从SSLSocket获取fileno(),它会期望您将处理TLS层以及数据。

#1


0  

This can be done using select.poll() instead of select.select(), because select.poll() will work with both File Descriptors and Sockets.

这可以使用select.poll()而不是select.select()来完成,因为select.poll()将同时使用文件描述符和套接字。

def sockToPty(clisock,addr):
    (master, slave) = os.openpty()
    tty.setraw(master, termios.TCSANOW)
    print('PTY: Opened {} for {}'.format(os.ttyname(slave), addr))
    mypoll = select.poll()
    mypoll.register(clisock, select.POLLIN)
    mypoll.register(master, select.POLLIN)
    try:
        while True:
            fdlist = mypoll.poll(1000)
            for fd,event in fdlist:
                # We treat sockets and FDs differently
                if fd == master:
                    data = os.read(fd, 4096)
                    if len(data) == 0:
                        print('PTY: {}, {} exiting due to Zero read.'.format(addr, os.ttyname(slave)))
                        raise GetOut
                    clisock.send(data)
                else:
                    data = clisock.recv(4096)
                    if len(data) == 0:
                        print('PTY: {}, {} exiting due to Zero read.'.format(addr, os.ttyname(slave)))
                        raise GetOut
                    os.write(master, data)
    except GetOut:
        os.close(master)
        os.close(slave)
        clisock.shutdown(socket.SHUT_RDWR)
        clisock.close()

This code is more complex than it needs to be for simple sockets - with a TCP socket, you can simply use the fileno() for that socket as a File Descriptor and then you treat both sides the same way (os.read(), os.write()). However, if you're going to use this with an SSLSocket, you need the more complicated version above, because if you grab the fileno() from an SSLSocket it expects you're going to handle the TLS layer as well as the data.

这个代码比简单套接字需要的更复杂 - 使用TCP套接字,您可以简单地将该套接字的fileno()用作文件描述符,然后以相同的方式处理双方(os.read(), os.write())。但是,如果您要将其与SSLSocket一起使用,则需要上面更复杂的版本,因为如果从SSLSocket获取fileno(),它会期望您将处理TLS层以及数据。