aleclearmind/nested-tmux

Nested tmux session over ssh

sourya7 opened this issue · 21 comments

Hi, first of all, this is pretty sweet. One question I have is about nesting over ssh. In my client, I have tmux and I want to be able to ssh over to a server, run tmux and switch between the two. Is this something that is possible? I'm not very competent with tmux to know if we can even achieve this but it would be pretty cool if we can. If not, I can live with two terminals.

I've thought about that, it should possible to notify an inner tmux session over SSH by sending a key (we send Alt + F12). However notifying the outer tmux session is more of an issue since we do this by writing on the tmux socket specified through an environment variable (TMUX_PARENT). However, that socket is on another machine.
We should redesign this approach and try to switch to a key-based approach to communicate to outer tmux too. However, this is not obvious.

Hi there.
I'll preface this suggestion with the following acknowledgement- What I'm proposing adds a really decent amount of complexity, and turns this from being a tmux conf into being something more complex.

One way to potentially support this is to use remote port fort forwarding in the ssh session, as in:

# Tunnel client's localhost:8008 => remote-host's localhost:8008
ssh -R 8008:localhost:8008 remote-host

OR more robustly in the ssh config

Host remote-host
   RemoteForward 8008 localhost:8008

On the remote host, we can redirect the TMUX_PARENT socket to localhost:8008, probably using socat or something.

On the client (local) machine, we can use socat to redirect localhost:8008 to our actual TMUX socket.

There's a few caveats - A lot of enterprise/corporate servers are going to restrict what you can & can't do with ssh port forwarding (legitimate security reasons)- there's a decent chance you just can't for some servers.

To enable portforwarding, the remote's sshd_config needs to have:

AllowTcpForwarding yes

To allow remote forwarding, it needs

GatewayPorts yes

Actually, according to
https://serverfault.com/a/753733/615908 since OpenSSH 6.7 , you can forward local unix sockets directly (this blew my mind).

Of course, a lot of enterprise servers run old versions of OpenSSH (My workplace's HPC runs OpenSSH 5.3), so we'll have to settle with using socat to redirect in those instances.

Since ssh_configs don't allow for env substitution (for example, substituting TMUX as the socket to forward), chances are, you're going to need either an alias, or a script/function to service this connection, so that you can forward the correct socket dynamically.

EDIT

actually looks like you can only localforward unix domain sockets, so without some more redirection via socat, chances are you can't directly do this - checking now

nevermind, apparently it's valid to remote forward unix sockets. SO cool my gosh.

Since ssh_configs don't allow for env substitution (for example, substituting TMUX as the socket to forward), chances are, you're going to need either an alias, or a script/function to service this connection, so that you can forward the correct socket dynamically.

I didn't know about forwarding UNIX sockets! Nice!
Before thinking about embedding this in ssh_config or supporting old SSH versions, I'd like to see this work with an ssh wrapper we can provide (e.g., ssh-nested-tmux).

Have you tried it? Does it work?

Here's a (probably) working solution for both client & server OpenSSH >= 6.7:

#!/usr/bin/env bash

# -R remote_socket:local_socket
ssh -R "/tmp/tmux_parent:${TMUX}" 

on the remote host, do the following:

# append this env var set to the ssh rc
cat << EOF >> ~/.ssh/rc
# check socket exists
if [ -S /tmp/tmux_parent ]; then 
   echo "export TMUX=/tmp/tmux_parent" >> ~/.ssh/rc
fi
EOF

I think this should do the trick

EDIT Just saw your reply, I'd finished writing this wrapper/conf before I saw it

@aleclearmind

I'd like to see this work with an ssh wrapper we can provide (e.g., ssh-nested-tmux).

Have you tried it? Does it work?

Well, here it is, but I haven't tried it yet, but also, there's a few details that you probably understand better than me about what env vars influence what.

I'd like your feedback on whether we should be setting:
TMUX vs TMUX_PARENT to /tmp/tmux_parent in our ~/.ssh/rc

For context on ~/.ssh/rc , from the man page:

~/.ssh/rc
Commands in this file are executed by ssh when the user logs in, just before the user's shell (or command) is started. See the sshd(8) manual page for more information

(this executes at ssh login, right before the shell starts)

Mmmh, why do you rename the remote socket? This does not support multiple nested sessions over the same machine.
Also, can't you use SendEnv instead of ~/.ssh/rc?

Mmmh, why do you rename the remote socket? This does not support multiple nested sessions over the same machine.

Good point, renamed because I'm not sure what we can write to, but I guess we can just forward the socket 1:1, meaning we can directly forward TMUX as well

Also, can't you use SendEnv instead of ~/.ssh/rc?

I think so, I'm not sure about portability (requires sshd_config to AcceptEnv yes), but assuming you have root on both machines, that's simpler, and would work better for dynamically forwarding TMUX if we don't rename the socket.

I'll rewrite the script & hide the previous attempt

Local ~/.ssh/config

Redundant
Host remote-host
   SendEnv TMUX
   # Maybe wildcard? TMUX_* 
#!/usr/bin/env bash

# -R remote_socket:local_socket
ssh -R "${TMUX}:${TMUX}" -o SendEnv=TMUX  remote-host

-o SendEnv=TMUX?

-o SendEnv=TMUX?

Updated

I actually haven't applied nested-tmux to my own tmux conf, btw, so I can't test this until I do so, if you're able to test it out I'd love to hear about it- Thank you for your feedback!

If it works, I'd love to be the one to open the PR if that's okay

Regarding directly forwarding the tmux socket, I just checked and it looks like my UID is encoded into the directory of the tmux socket, we may need to hack around that a bit for systems where the client uid != remote uid, idk

Please, reduce the amount of messages, test things and then make a PR.
Otherwise I'll look into that when I get the chance.

Are there possibly any other ways to do this? I don't know about tmux's security model, but forwarding sockets leaves me a bit uncomfortable. - or is that unwarranted?

Yeah, it'd be nice to limit what can be communicated back to the client only a signal to reactivate the outer layer.

Ok so there are (at least?) two ways to do this

  1. wait for a "signal" (side-channel) from the inner session to change the tmux state
  2. directly modify the parent state
  3. use terminal escape sequences?

nested-tmux curently does the latter, which is what introduces the socket requirement.
The problem with the former is you need to find a signal, that you can observe, which is obscure enough as to generally not impair functionality if it is co-opted. This also limity flexibility and youhave to define a protocol to use over the sidechannel.

Looking through the tmux man page,

All the notifications listed in the CONTROL MODE section are hooks (without any arguments), except %exit.  The following additional hooks are available:

Items that caught my eye for tunneling information: pane-set-clipboard,
Things that are 1-bit signals seem tedious, but one-hot encoding two 1-bit signals should be feasible at least for triggering "up/down" navigation.

If the clipboard hook combined with OSC 52 (apparently some sort of clipboard copy escape sequence; needs more research; example: https://gist.github.com/yudai/95b20e3da66df1b066531997f982b57b ) allows us to be notified on data transfer via the clipboard, we could parse arbitrary information. The main downside i see is having to deal with clobbering the clipboard, but apparently the xterm escape sequence (section P c ; P d section in https://www.xfree86.org/current/ctlseqs.html) can specify a target buffer? I'm not sure if that's the same thing though. tmux/tmux#1477 also looks very relevant to this approach.

Anyway lot of this seems rather hacky and maybe it's worth asking the tmux developer for suggestions once there's a clear question.

I don't immediately see any other hooks we could use.

Another question is how to handle something with the ssh connection breaking, timing out, or accidentally outputting bad data on the terminal. This is generally a risk with in-band signaling.

Making this work also would/could allow assimilating existing (nested) tmux sessions?
I've done some experiments trying to do that but I had some issues I don't understand yet.

Should we just try to get upstream to support nested tmux better by design? :P

Should we just try to get upstream to support nested tmux better by design? :P

By upstream, do you mean nicm & the tmux maintainers?

They're great maintainers, but they're pretty hard to influence, I guess you can try your luck lmao, but I'm sure this has already been raised with them