Unbounded Above

Alex Teichman

SSH, X11 Forwarding, and Terminal Multiplexers

Terminal multiplexers like GNU Screen and tmux are indispensable for running remote jobs, but they don’t always play nicely with X11 forwarding (i.e. ssh -X or ssh -Y). This can result in remotely-launched GUI programs failing to display and similar difficulties. Typically, I’ll start a job in screen while at lab, go home, and reconnect remotely, only to find that X11 forwarding works correctly outside of but not inside of screen. The same happens with tmux.

Most aggravating to me in this situation is the failure of a bash alias cpd that I use quite frequently:

1
alias cpd='echo -en `pwd`/ | xclip -selection clipboard'

This alias copies the current working directory into the clipboard, making it easier to navigate the filesystem, copy files between machines, and so on. Unfortunately, it requires X11 forwarding to work.

The root cause of the problem here is that the DISPLAY environment variable needs to be set appropriately or X11 forwarding breaks. For example, when running locally DISPLAY is usually :0, but when logged in remotely it should be localhost:x.0, where x is some number. Normally you don’t have to think about this, but when reconnecting to a screen session the terminal doesn’t know when DISPLAY needs to change. Setting DISPLAY manually is possible but entirely too much work to do on a regular basis.

A solution …

To resolve this problem in a way that is transparent to the user, we can automatically cache the value of DISPLAY before every command run outside of screen, and set the value of DISPLAY to the cache before every command run inside of screen.

You can paste this into your .bashrc. Beware the potential complication if you use PROMPT_COMMAND.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -- Improved X11 forwarding through GNU Screen (or tmux).
# If not in screen or tmux, update the DISPLAY cache.
# If we are, update the value of DISPLAY to be that in the cache.
function update-x11-forwarding
{
    if [ -z "$STY" -a -z "$TMUX" ]; then
        echo $DISPLAY > ~/.display.txt
    else
        export DISPLAY=`cat ~/.display.txt`
    fi
}

# This is run before every command.
preexec() {
    # Don't cause a preexec for PROMPT_COMMAND.
    # Beware!  This fails if PROMPT_COMMAND is a string containing more than one command.
    [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return 

    update-x11-forwarding

    # Debugging.
    #echo DISPLAY = $DISPLAY, display.txt = `cat ~/.display.txt`, STY = $STY, TMUX = $TMUX  
}
trap 'preexec' DEBUG

… that mostly works

This can break down when you are doing more complicated things. For example:

  • Create a screen session on a work machine.
  • Go home.
  • In terminal A, ssh in and re-attach.
  • In terminal B, ssh in again.
  • Disconnect terminal B.

The screen session running at work, visible in terminal A, ends up with a bad DISPLAY setting and X11 forwarding fails. Fortunately, it is easy to recover from these kinds of failures: Just detach and re-attach the screen session.

A note on security

If you’re using X11 forwarding, be aware there are security risks involved. It’s best to use X11 forwarding selectively, either with ssh -X on the terminal or by specifically configuring your ~/.ssh/config file to allow it for only certain machines. For example, this appears in my ssh config file for a remote machine capek that I trust. All other connections have X11 forwarding disabled by default.

1
2
3
4
5
6
7
8
9
Host capek
     Hostname capek.stanford.edu
     User teichman
     ForwardX11 yes
     ForwardX11Trusted yes

Host *
     ForwardX11 no
     ForwardAgent no