2

I start socat on a terminal by executing socat - UNIX-listen:/tmp/sock

Then I go to another terminal and start a program (by python) in such a way, that it appears to run in the first terminal, here is an example:

s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/tmp/sock")
proc=subprocess.Popen(['prg'], stdin=s, stdout=s, stderr=s, shell=False)
proc.wait()

Programs with simple input/output like "cat" for example are working fine, but more complex ones (like "vim" or "bash") are not very happy, obviously. Beside of that, when I hit Cntrl-C on the first terminal, the socat is killed, while I would prefer the keyboard interrupt to be delivered to the prg program (started in the python code above).

My question is: if it's possible and how to tell it to socat to behave like the prg is the terminal owner and not socat itself.

PS. It might appear that this question is a duplicate of this one, but it's not the case, because in that question the prg is called directly by socat, so it is possible to use EXEC options.

ilya
  • 135
  • 4

1 Answers1

2

Signals nor tty ownership cannot be 'magically' carried over a socket.

Over AF_UNIX, you could send the tty file descriptor itself (using sendmsg and recvmsg) and have the receiving side use it (and not the socket) as the subprocess' stdout/stderr. This cannot be done with plain socat, but this method is used internally by e.g. the tmux tool.


With plain socat, the most you can do is make it send a literal 0x03 byte (^C) when Ctrl+C is pressed (that's what telnet and ssh do). You can test this by pressing Ctrl+VCtrl+C to send it once, or by running stty raw before socat to enter "raw mode" permanently.

That won't be enough to make the receiving end actually understand the keypress, though. The most basic method would be to check each byte manually – send a SIGINT when it sees 0x03; write it to the subprocess' stdin otherwise.

What telnetd and sshd do, however, is add a 'pseudo-tty' in between the socket and the subprocess – in Python you can create one using os.openpty(); the 'slave' side needs to be given as the subprocess' stdout/stderr, and your script will need to run in a loop copying data between the socket and the 'master' side.

That's still not quite enough – a full-featured terminal login protocol, such as SSH or Telnet, would also have messages to update the pty's dimensions, and react to termios setting changes (e.g. the "local echo" setting and others seen in stty).

It would be useful to read the source code of the telnet client & telnetd server, e.g. from GNU inetutils, as they do exactly that.

u1686_grawity
  • 426,297
  • 64
  • 894
  • 966
  • Thank you for the answer! Alas it's not so easy as I hoped to. May it be that my problem will be easier to solve if I formulate it in a less general style? What I want to achieve is this: I have a "main" python program, which opens a new window in a given tmux session and start another program "xxx" in this new window, but the main python process must have full control over it (so "xxx" must be a "subprocess" instance or similar). So first I ask libtmux to open a new window with window_shell="socat - UNIX-listen:/tmp/sock" and then I connect as described in the question above. Will this help? – ilya Jul 13 '16 at 15:59
  • Could you ask libtmux to just give you the window's pty name or fd directly? – u1686_grawity Jul 14 '16 at 04:46
  • Yes, after I created a tmux **window**, I can see in `window.panes[0]['pane_tty']` a pseudo-tty device name, for example `/dev/pts/57` – ilya Jul 14 '16 at 08:43
  • Could you please elaborate your answer a bit: after I call `(master, slave)=os.openpty()` and then call `subprocess.Popen(stdout=slave, stderr=slave....)` what should I give as `stdin` parameter? Is it a good idea to open `/dev/pts/XXX` directly and give the descriptor as `stdin` to `subprocess.Popen(...)`? And the second question: what do you mean by _copying data between the socket and the 'master' side_? From where exactly should I read and where to write? – ilya Jul 14 '16 at 10:29
  • The pty slave is used both as input _and_ output for the subprocess. Likewise, the pty master needs to be copied from _and_ to the socket. (You might need select.poll() or a similar eventloop.) – u1686_grawity Jul 14 '16 at 11:29
  • Thanks again for clarification. I have written [this example script](http://pastebin.com/gfD3gGtw) using your suggestions. On the terminal side I start `socat` by the following command: `socat file:$(tty),raw,echo=0 UNIX-listen:/tmp/blah-socket` and on the python side I just start the script with arguments saying what program is to be started. I tried `cat` and `vim` and both are working fine. If you have any comments on the script, please don't hesitate to comment. – ilya Jul 14 '16 at 13:37