I am trying to duplicate the behavior of this socat command in Python:
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:
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.
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.
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.
What I have tried:
- 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:
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.
- 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.
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.
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.