How X over SSH really works

10:57:00 PM 0 Comments






Imagine you are sitting in front of a machine named "home" that has a keyboard, mouse, display, and is running an X server. Now you open a terminal and ssh to a machine called "remote"
(which doesn't need to have an X server running), and run a program
like "firefox" which pops a window in your screen. How does this all
work?

First, let's clear up the client/server terminology confusion. When talking about X:
  • X client: a process (like firefox or xemacs) which uses the X client API to display things and receive mouse/keyboard events.
  • X server: a process (usually just "X") which X clients connect to. At times (as we'll see below) other processes can act as X servers.
Whenever an X client starts up, it reads the local $DISPLAY environment variable, whose value looks like: [hostname]:display_number[.screen_number]. The X client immediately opens a connection to that X server. If it can't, it fails:

user@home: echo $DISPLAY
:0 # hostname is "localhost" by default
user
@home: xcalc # pops up a calculator on my screen
user@home: DISPLAY="nosuchhost:99"
user@home: xcalc
Error: Can't open display: nosuchhost:99

Now let's see what happens when you ssh to another machine:

user@home: ps aux | grep X # yep, X server running @home
root ... /usr/bin/X :0 ...
user@home: ssh -X user@remote # -X enables X-forwarding
user@remote: ps aux | grep X # nope, no X server here
user@remote: echo $DISPLAY
:11.0
user@remote: xcalc # pops up screen @home

Wait a minute, the $DISPLAY variable is pointing to the localhost ("remote"). Natural questions to ask at this point:
  • There is no X server @remote, so why didn't xcalc just fail on startup?
  • Why was the display number "11".
  • How did xcalc show something @home?
The answer has to do with the ssh-daemon running @remote:

user@remote: ps aux | grep user
root ... sshd:user@pts/11

What's happening is that there's an "X emulator" running @remote that was setup just for your ssh session, that is listening on display 11.

To review, here's a play-by-play:
  1. You type "ssh -X user@remote" in your terminal
  2. The ssh process connects to the sshd server @remote.
  3. sshd spawns a new process that is an X-server-emulator listening on some display number, e.g. "11"
  4. sshd sets the $DISPLAY to point to that local "X-server" (e.g. ":11")
  5. xcalc reads this $DISPLAY and conncects to this X-server. xcalc thinks it's displaying to the local machine.
  6. the X-server-emulator simply forwards the X commands from xcalc through the ssh connection, to the original ssh process.
  7. The ssh process @home now acts as a normal X-client and sends those commands to the X-server @home.
Some of you might be wondering: wasn't the X protocol designed to go over the network? Can't you do all this without ssh? You might be tempted to try something like:
user@remote: DISPLAY="home:0"
user@remote: xcalc

This doesn't work because the X server @home won't let other hosts connect to it. To change this (not that you should -- see below) you can do:

user@home: xhost +remote

xhost
is a command which says "that host can connect to our X-server".
However, everybody uses ssh X forwarding instead. Here are some security reasons why:
  • Normally, X-traffic (like your keystrokes) is sent unencrypted from X-client to X-server.
  • Ssh nicely sends that data through an encrypted channel, so it doesn't go over the internet in the clear.
  • "xhost +remote" is putting a lot of trust in 'remote' being a nice guy. If remote ever gets hacked, it could connect to the X-server @home and listen to all its keystrokes.
There's also an issue with firewalls: by doing "DISPLAY=home:0", you're assuming that a connection can be established from remote -> home. But this isn't always possible -- home might be sitting behind a firewall (like your Netgear router). Since the ssh-connection was setup from home-> remote, it takes advantage of this already-established connection.

Other notes for the curious:
- if $DISPLAY is set to "localhost:0" (or any explicitly named host) it uses tcp-ip to send the X-traffic locally.
- if $DISPLAY is just ":0" it uses a special (more efficient, non-tcp-ip) connection.

Some say he’s half man half fish, others say he’s more of a seventy/thirty split. Either way he’s a fishy bastard.