SSH X11 forwarding is extremely slow over VPN

The problem with forwarding contemporary X (not that "old" X when its network transparency was invented) is with font smoothing: to properly smooth each glyph of text rendered on some surface, the X server has to get the bitmap which is located under the bounding box of that glyph from the client which wishes to render that glyph. (This is neede for the smoothing algorythm to work properly as it takes the context on which the glyph is rendered into account.)

Hence with contemporary GUI toolkits the amount of traffic shoveled between the X server and its clients is huge: you can see this by enabling TCP in your local X server (these days they are typically started with -nolisten tcp) and force some GTK- or Qt-based X client to talk to the server via TCP:

$ DISPLAY=localhost:x11 /usr/bin/that/x-app

(see grep x11 </etc/services for the X server's standard ports). You will immediately notice how sluggishly that client behaves even though the X traffic is not leaving the local host: that's simply because normally the X traffic is carried over a Unix-domain socket which basically just copies bytes between buffers in memory, thus having quite low overhead, and now it traverses the full TCP/IP stack with all its queues and complicated logic. Now consider what happens when this traffic is sent in your case—wrapped in three layers of data transfer protocols: SSH tunnel carried by VPN tunnel carried by TCP/IP carried by the wire.

As to what to do about this, I'm not so sure.

With mosh being out of the game, I'd try playing with the IPQoS option of the OpenSSH client.

Another approach is to attack the problem from another angle: try VNC-based access to your application. Options vary here:

  • You can start simply by exporting the whole display via x11nvc or something like this.
  • Use software packages which are able to "export" specific application or window—Xpra and winswitch.
  • Try a more heavy-weight though complete solution such as X2Go.

I'm facing a problem that really looks like yours : some gtk applications (e.g. meld) are slow to start over ssh, some others not (e.g. synaptic). I actually may be more precise on how slow it is :

$ echo "$TIMEFORMAT"
        %R
$ time meld --version
meld 3.20.0
        25.293

This is the exact same time as the other slow applications I've found (nemo, gedit).

Inspecting meld with strace, it appears that it is waiting for some event that never comes, and exists on a timeout that is exactly of 25s.

It is very likely that this problem is the same as the one that was reported here: https://bbs.archlinux.org/viewtopic.php?id=230036

This is a dbus problem - kind of session environment that is not set over ssh. Unfortunately, I don't know how to fix this.

The only workaround I've found with meld is to launch one in background before I can use it the normal way.

Edit

Found it ! Launch dbus simply and export reported variables:

$ dbus-launch 
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-2EzslkNeji,guid=c9dec1622d6575f468559f8b5d9ee0e0
DBUS_SESSION_BUS_PID=4745

Set the above and export, then:

$ time meld --version
meld 3.20.0
        0.268

I'll put this in my startup script on remote and report here.

Follow-up

This is rather verbose and largely out of scope, but as I do like easy-to-try copy/paste solutions in the posts I see, I may except that you too do. I've tested the following as a session script to feed ssh with (e.g. ssh -X my@dark-side ~/bin/session):

#!/bin/bash

LAUNCH=(
    #   xload
    #   thunderbird
      dbus
      konsole
)

Tmp=$(mktemp -d)
mkdir -p $Tmp

KILLS=()
WAITS=()

echo "info: starting session" >&2

app_dbus() { # # Launch dbus and remember to kill
  # redirect error because of "create ~/.dbus/session-bus/" 
  # deal with failure by checking DBUS_SESSION_BUS_PID afterwards
  dbus-launch > $Tmp/dbus.sh 2>/dev/null
  . $Tmp/dbus.sh

  if [ "$DBUS_SESSION_BUS_PID" ] && 
       ps --no-header -o pid -p "$DBUS_SESSION_BUS_PID" \
          > /dev/null 2>&1
  then
    export DBUS_SESSION_BUS_ADDRESS DBUS_SESSION_BUS_PID
    KILLS+=( $DBUS_SESSION_BUS_PID )
  else
    echo "warning: failed dbus" >&2
  fi
}
app_konsole() { # # Launch konsole and remember to wait for
  konsole &
  WAITS+=( $! )
}

for APP in "${LAUNCH[@]}"
do
  app_$APP
done

rm -r $Tmp  # not needed anymore

wait "${WAITS[@]}"

echo "info: ending session" >&2

for ID in "${KILLS[@]}"
do
  kill $ID
done