🌀

Xpra(Xアプリリモートビューワー)について

2021/12/17に公開

XpraというリモートのXアプリを表示できるパッケージについて紹介します。

概要

特徴

  • アプリの画面だけをシームレスに表示できる(RDPやVNCとは違う)
  • youtubeが普通に見れるくらい転送速度がはやい(X11Forward over SSHとは違う)
  • プロトコルが多様(TCP/UDP/SSH/SSL/WS/WSS/HTTP/HTTPS)
  • ブラウザからアクセスできる!

微妙な点

  • ストレージを食う(Ubuntu Serverに入れると1GBくらい増加)
  • 音が出なそう(音が出るはずだが出ない)
  • Fcitxで日本語入力できなさそう

ざっくりな使い方

リモート・ローカルともにパッケージをインストール

remote bash
sudo apt install -y xpra python-cryptography gedit # geditはテスト用

ローカル

local bash
sudo apt install -y xpra python-cryptography

リモート
Xwrapper.configでallowed_usersをconsole -> anybodyに変更

/etc/X11/Xwrapper.config
allowed_users=anybody

ローカル

local bash
xpra start ssh:REMOTE --start-child=gedit

※REMOTEは~/.ssh/configに追加しておいてください。
これでリモートで実行したgeditがローカルPCに表示されます。個人的にはローカルのgeditを使ってるのと変わりません。

背景

リモートホスト上のDockerコンテナで稼働するGUIアプリをローカルホストの画面に表示したくて、X11Forwarding over SSHを試したが実用に耐えませんでした。

remote bash
[ec2-user@ip-10-0-100-159 ~]$ sudo yum install xorg-x11-xauth xeyes
[ec2-user@ip-10-0-100-159 ~]$ xeyes

EC2上のAmazon Linuxにssh -XCで接続してアプリを起動しても、まともに使えない。エラーにならないのはxeyesくらいで、firefoxはもちろんgeditですら使えない。圧縮方式のチューニングなどがあるようだが、RC4を使うなど現実的ではないので全く別の策を探していた。

その理由について、Super Userに丁寧に解説してくれている人がいました。

The X11 protocol was never meant to handle graphically (in terms of bitmaps/textures) intensive operations. Back in the day when X11 was first designed computer graphics were a lot simpler than they are today.
……
This is very efficient if the display to be rendered consists of a limited number of simple graphical shapes and only a low refresh frequency (no animations and such) is needed. Which was the case back in the days when X11 was first developed.
……
Other protocols (like RDP and VNC) are more designed to let the remote system do all the hard work and let that system decide which updates to send to the client (as compressed bitmaps) as efficiently as possible. Often that turns out to be more efficient for modern GUI's.

https://superuser.com/questions/1217280/why-is-x11-forwarding-so-inefficient/1217295

要は、X11は描画命令を扱うため昔のコンピューターには適していたが、現代によく使われるブラウザみたいな画像処理を行うのには向いていないということらしいです。

Xpra

ドキュメント

man xpraと以下の公式ドキュメントが参考になりました。
https://github.com/Xpra-org/xpra/tree/master/docs

サポートプロトコル

ハンズオンの前にざっくり要素を見ておきます。

エンコード

  • png
  • png/P
  • png/L
  • rgb
  • webp
  • jpeg
  • vp8
  • vp9
  • h264
  • h265
    指定しないと自動で選択されます。依存関係でffmpeg等も入るので、筆者環境ではh264が選択されていました。

通信プロトコル

  • tcp
  • udp
  • ws_ウェブソケット
  • wss_セキュアウェブソケット
  • ssl
  • ssh
    ...
    ※この中でsshだけは別枠扱いで、sshを指定するだけで認証と暗号化を兼ねられます。

暗号化

  • ssl_公開鍵暗号
  • AES_共通鍵暗号

認証

  • env_環境変数でパスワードを設定
  • password_コマンド上でパスワードを指定
  • file_ファイルにパスワードを指定
    ...他にもldapやら

http/httpsサポート

本体のモジュールとは別にxpra-html5というモジュールをインストールすれば、ブラウザでアクセスするだけで、アプリを開けます。

ハンズオン

準備

リモート・ローカルともにパッケージをインストール
※検証ではリモート側はaws ec2 ubuntu server 20.04 t3a.xlargeを利用
(geditくらいならmicroでも動くが、ブラウザ使おうとするとこの辺が必要)
リモート

remote bash
sudo apt install -y xpra python-cryptography gedit firefox # gedit/firefoxはテスト用

ローカル

local bash
sudo apt install -y xpra python-cryptography

リモート
Xwrapper.configでallowed_usersをconsole -> anybodyに変更

/etc/X11/Xwrapper.config
allowed_users=anybody

起動

ローカル

local bash
xpra

コマンド単体で実行するとなんだかよくわからないGUIが表示されますが、今回は使いません。

※以下では、環境によって変わる変数をREMOTE,PASSWORDなど大文字で表現するので、適宜変更してください。

SSHでローカル側から起動

ローカル

local bash
xpra start ssh:REMOTE --start-child=gedit

geditが表示される

リモート側で事前に起動してローカルからSSHで接続

リモート

remote bash
xpra start :1111
DISPLAY=:1111 gedit

:1111はローカルホストの1111番ディスプレイという意味です。任意の数字を設定できます。DISPLAY=:1111 geditはローカルの1111番ディスプレイでgeditを起動という意味になります。
ローカル

local bash
xpra attach ssh:REMOTE:1111

※筆者環境では上記コマンドはなぜか受け付けられずxpra --ssh="ssh -i ~/.ssh/SSHKEY" attach ssh:USERNAME@REMOTEIP:1111自前で鍵を指定するオプションを追加しています。

※ローカル側からアプリを起動する場合も、リモート側でセッション開始して待機する場合もstartというコマンドを使うので、混同しやすいですので気をつけてください。

任意のTCPポート経由

リモート

remote bash
xpra start --start=gedit --bind-tcp=0.0.0.0:10000

ローカル

local bash
xpra attach tcp://REMOTE:10000/

暗号化なし、認証なしです。tcp://REMOTE:10000/の最後の/がないとエラーになった気がします。

HTTP経由

リモート

remote bash
git clone https://github.com/Xpra-org/xpra-html5
cd xpra-html5/
sudo ./setup.py install /usr/share/xpra/www
sudo apt install websockify
xpra start --start=gedit --bind-tcp=0.0.0.0:10000 --html=on

ローカル
ブラウザでURLを開いてください。

local bash
firefox http://REMOTEIP:10000/

なんと、ブラウザ内にgeditが起動されています。この機能だけならローカル側にはパッケージインストールの必要はありません。ブラウザだけあれば利用できます。

Desktop Session

RDPみたいなのができるらしいのですが、何なのよくわかりませんでしたが、一応やり方だけ。
ローカル

local bash
xpra start-desktop ssh:REMOTE

SSL経路経由

ここらからが本番です。TCPレイヤをSSLで経路暗号化します。これはxpraのネイティブプロトコルです。
下記を参考に証明書を作ります。
https://pcvogel.sarakura.net/2019/05/09/31935
ローカル

local bash
./make_ip_cert.sh
cat REMOTEIP.key REMOTEIP.crt > ssl-cert.pem

上記で作成したssl-cert.pemをリモート側に移してください。
移したら証明書を指定してxpraを起動します。
リモート

remote bash
xpra start --start=gedit --bind-tcp=0.0.0.0:10000 --ssl-cert=ssl-cert.pem

ローカル
ローカルでは、REMOTEIP.crtを指定してアタッチします。

local bash
xpra attach ssl://REMOTEIP:10000/ --ssl-ca-certs=REMOTEIP.crt

geditが開きます。Wiresharkで確認しましたが、TLSで暗号化されていました。

ローカルで証明書を指定しないと接続できません。

local bash
xpra attach ssl://REMOTEIP:10000/

これは認証失敗ではありません。証明書バリデーションエラーで弾かれています。

HTTPS経由

リモート
再掲ですがhtmlモジュールをインストールします。

remote bash
git clone https://github.com/Xpra-org/xpra-html5
cd xpra-html5
./setup.py install /usr/share/xpra/www
xpra start --start=gedit --bind-tcp=0.0.0.0:6919 --ssl-cert=ssl-cert.pem

ローカル

local bash
firefox https://REMOTEIP:10000/

証明書エラーは出ますが、無理やり進めば、ブラウザ内にgeditが表示されます。

でも、

local bash
firefox http://REMOTE:10000/

でもアクセスできるからだめです。

状況によって勝手にfallbackされるので、htmlアクセスについて簡単にパターンを検証します。

  • リモート側で証明書指定なし_httpでのみアクセス可能
  • xpra-html5モジュールを削除_ブラウザでのアクセス不可
  • bind-tcpをbind-sslに変更_ブラウザはhttpsのみ可能、CLIは証明書指定のみ可能
  • bind-tcpをbind-sslに変更し--html=offを追加_証明書を指定したCLIのみアクセス可能

上記をまとめると暗号化して使いたい場合は、
リモート

remote bash
xpra start --start=gedit --bind-ssl=0.0.0.0:10000 --ssl-cert=ssl-cert.pem

ローカル

local bash
xpra attach ssl://REMOTEIP:10000/ --ssl-ca-certs=REMOTEIP.crt

ブラウザサポートをやめたい場合--html=offを追加もしくは、xpra-html5モジュールを削除。

認証

sslを指定すれば暗号化はされますが、接続クライアントの認証はされていないので誰でも接続できます。

パスワード認証

リモート

remote bash
xpra start --start=gedit --bind-ssl=0.0.0.0:10000 --ssl-auth=password:value=PASSWORD --ssl-cert=ssl-cert.pem

ローカル
xpra attach ssl://:PASSWORD@REMOTEIP:10000/ --ssl-ca-certs=REMOTEIP.crt
当然ですが、ローカルでするパスワードが間違っていれば弾かれます。

ENV認証

環境変数を指定してパスワード設定するので、プロセス一覧に表示されません。
リモート

remote bash
XPRA_PASSWORD=PASSWORD xpra start --start=gedit --bind-ssl=0.0.0.0:10000 --ssl-auth=env --ssl-cert=ssl-cert.pem

ローカル

local bash
xpra attach ssl://:PASSWORD@REMOTEIP:10000/ --ssl-ca-certs=REMOTEIP.crt

使うなら、ファイルか環境変数だと思いますが、取り回しを考えると環境変数がイイですかね。
また、それぞれの場合にブラウザでアクセスするとパスワードフォームが出てきます。

音声出力

色々やりましたが音を出すことができませんでした。
debugすると、下記エラーが出ていました。

local bash
missing ['mad'] from ('mpegaudioparse ! mad', None)
missing ['mad'] from ('mpegaudioparse ! mad', 'qtdemux')

https://stackoverflow.com/questions/56132573/how-to-fix-warning-erroneous-pipeline-no-element-mad
とかが関係ありそうな気がします。

InputMethod

--input-method=METHOD
       Specify  which input method to configure.  This sets a number of
       environment variables which should be honoured  by  applications
       started with the start-child option.
       The following METHODs are currently supported:
       none   Disable  input methods completely and prevent it from in‐
              terfering with keyboard input. This is the default.
       keep   Keeps the environment unchanged. You are responsible  for
              ensuring it is correct.
       xim    Enables the X Input Method.
       IBus   Enables the Intelligent Input Bus.
       SCIM   Enables the Smart Common Input Method.
       uim    Enables the Universal Input Method.

man xpraより引用
fcitxがありません。一応全部してしてみましたが、日本語入力できませんでした。今後追加調査予定です。

まとめ

もともとの目的であるリモートサーバで実行しているアプリをローカルマシンに表示することができそうです。HTTPSでセッション貼れるので、AWS FargateにALB経由でトンネル出来るんではないかと画策中です。DockerコンテナGUIアプリと組み合わせ、アプリをリモートやローカルに移し替えられるポータビリティを確保出来る仕組みを考えていきますので、興味があればご参照ください。

追記

https://mybyways.com/blog/running-linux-gui-applications-in-a-docker-container-using-xpra
上記記事を見つけました。
xpra専用のコンテナとアプリコンテナを準備し、双方をボリューム共有でつなぎ、クライアントはxpraコンテナに接続するというアーキテクチャです。これなら、分割することでイメージの肥大化を防げるかもしれません。

Discussion