Akira's Commentary

概要

Windowsのリモートデスクトップはなかなか便利なものですが、クライアントはごく限られたプラットフォームでしか使えません。この点については、クライアントの豊富なVNCに負けているところです。

しかし、Xvnc経由でrdesktopを使うと、VNCビューワさえあればどのようなプラットフォームからでもリモートデスクトップを使うことができるようになります。なら、そこでもう一歩踏み込んで、Xvncとrdesktopを統合してしまうと、途中のXサーバ無しでVNCビューワからリモートデスクトップを使うことができるようになります。このようなアイデアから作られたものがRDP2VNCプログラムです。

rdesktop+Xvnc rdp2vnc
structure of rdesktop + Xvnx structure of rdp2vnc

rdp2vncは、プロトコルコンバータとして動作します。rdesktopと同様に、接続先のWindowsマシン、(仮想)画面サイズ、キーボードタイプを指定して起動しますが、その時点ではまだ Windowsのリモートデスクトップには接続しません。実際の接続は、VNC viewerからの接続が確立した時点で行なわれます。以降、VNC viewerからWindowsリモートデスクトップが使えるようになります。

入手方法とインストール

開発者が入れ替わって各種バージョンが散在していますが、現時点での最新版は、http://sourceforge.net/projects/libvncserverで入手することができます。このサイトでは組み込み用のVNCサーバライブラリのlibvncserverrdp2vncとを配布しています。rdp2vncはlibvncserverライブラリを使っていますので、両方ともダウンロードしてください。

補遺:rdp2vncは現在rdesktopに統合されているようです。rdesktopのmake targetにrdp2vncも含まれています。このrdp2vncでは、RDPプロトコルはrdesktopで処理し、VNCプロトコル処理はLibVNCServerで処理しているようです。

rdp2vncはlibvncserverライブラリを使っていますので、最初にlibvncserverをインストールする必要があります。libvncserverは、単純に展開してmakeするだけでOKです。

rdp2vncを展開してmakeしてもVNCパッチ付きのrdesktopしか作成されません。rdp2vncを作るには、

./configure --with-libvncserver=<libvncserverのデイレクトリ>

とした上でmakeします。手元のLinux(RH7.1)ではこれだけでOKでした。

使い方

基本的な使い方は rdesktop と同じです。単純には

rdp2vnc <Windowsホスト>

で起動することができます。

ただ、この場合には、すべての設定がデフォルトになり、以下の問題が出てきますので、いくつかのオプションは指定しておいた方がいいと思います。

  • キーボードタイプを指定していないと、VNC viewer接続時にキーボードタイプ選択画面が表示されます。起動時に -k <キーボードタイプ> で指定しておいた方がいいと思います。
  • デフォルトではポート5900で VNC viewer からのコネクションを受け付けます。変更したい場合には、 -v <VNCポート番号> で指示します。普段使わないVNCポートを指定しておくといいでしょう。
  • 画面サイズはデフォルトでは 800x600 になります。変更したい場合には -g <w>x<h> で指定してください。

キーボードタイプですが、選択画面では実に多数のキーボードが表示されますが、実際に使えるのはそれらの一部だけです。実際には存在しないキーボードタイプを選択した場合にはエラーになって接続が切られてしまいます。

キーボードタイプに Japanese 106 なるエントリがありますが、これではキーマップがずれてしまいます(数字と記号がずれました)。試しに -k EN で起動したら、数字のずれはなくなりました。しかし一部の記号が入力できません。キーボードマッピングの周りはまだ怪しいようです。

キーマップ問題とフィックス

jp106での数字、記号がずれてしまう問題は、現在のキーマップコードではどうしようもないようです。現在のキーマップコードでは最終的に us102キーボードのスキャンキーコードにマッピングしてリモートデスクトップに送出するようになっています。そして、個別のキーボードの文字コード(厳密にはVNCのXkeysym)とスキャンコードの対応表を持っているのですが、jp106us102よりも多くのキーを持っていますので、そもそも対応付けが不可能です。

一方、us102での一部キーが入力できない問題ですが、入力できなかったものは;:'でした。で、これらのキーは、US/JPでシフト状態が異なっているのですね。結局リモートデスクトップは、スキャンコードだけではなくシフト状態も見ているようです。そこで、キーマップ側のシフト状態とキーボードからのシフト状態がずれている場合に、ダミーのシフトキーを送ってやるようにしたところ、これらのキーも無事に入力できるようになりました。

修正したコード(関数 vncKey ファイル vnc.c、部分)を以下に付けておきます。

修正その1
最初にこのように修正して、rdp2vncの作者に送ってみたのですが、この手の修正は(現在のコードではコメントアウトされている)modifierKludgeに入れるべきだ、と言われました。そこで以下のように修正し直しました。
修正その2
まじめにシフトキーの状態を管理するようにし、シフトキーに関するkludgeをすべてmodifierKludgeに移動しました。なお、この修正では、元のコードでの modifierKludgeの呼び出し位置が、不適切になってしまう(関係ない時にエラーメッセージが表示される)ので、呼び出し位置をずらしてみました。

その他の問題とフィックス

まだ 1.0 になっていないソフトだけのことはあって、他にも問題があるようです。

zombie増殖
私の場合、接続先のXPマシンは一台しかないので、(Linux上で)initでそのXPマシンをターゲットにしてrdp2vncを起動しておき、必要に応じてVNC viewerから接続して呼び出すようにしています。ところが、こうして使い続けているとどんどんzombieプロセスが増殖していってしまいます。
原因は、ある意味単純なんですが、rdp2vncはVNCビューワからの接続を受け付けたところで実際の処理を行なうプロセスをforkしているのですが、その子プロセスを放置しています。子プロセスはセッションが終了すると止まるわけですが、後処理していないのでzombieが増殖していくことになります。
とりあえずは、SIGCHLDのハンドラを登録してその中でwaitしてやるように改造しました。
coreを吐く
実は上の子プロセスは、VNCビューワ側から切断された時にはコアを吐いて落ちます。直接の問題は libvncserver側 のクライアント切断時の処理コードにあります。
libvncserverの ファイル sockets.c の 関数 rfbCloseClient でソケットの切断に合わせて maxFd を更新する処理がありますが、そのソケットだけしか登録されていなかった場合にはmaxFdがマイナスになり、後続の FD_ISSET で落ちてしまいます。while の条件に maxFd >= 0 を追加しておけば防止できます。
この修正を入れると、そこでは落ちなくなりますが、今度は同じ関数の先頭で落ちるようになります。rdp2vnc側のクライアント終了処理で、空のクライアントリストに対してこの関数が呼び出されるためです。本質的にはrdp2vnc側の問題ではありますが、rfbCloseClientの先頭でパラメタのNULLチェックを追加しておいた方がいいでしょう。
rdp2vnc側のクライアント終了処理
rdp2vnc側ではクライアントが切断されるとrfbCloseClient、rfbClientConnectionGoneが順次呼び出されてlibvncserverのサーバ構造体のクライアントリスト(rfbClientHead)から当該クライアントが削除されるようになっています。で、その結果クライアントリストが空(rfbClientHead == NULL)になった時には rdp側も終了させるようになっています。
現在のコードでは、このクライアントが無くなったかどうかのチェックはファイル vnc.c の ui_select 関数内で行なわれ、直接RDP側のソケットをクローズしています。ui_select 自体は RDP側の受信関数内で呼び出されて います。この結果、RDPのソケットがクローズされてからRDPの通信処理が 実行され、それらがエラーになることによってRDPセッションが終了します。
ところが、RDP側の通信処理ではこの場合にエラーメッセージを表示しますので、かなり気持の悪い状態になります。本質的にはui_select、これはRDPのローカル側のイベント処理用ですが、内部でRDP側の切断まで行なってしまうことに無理があるかと思います。私は終了チェック部分のみを別関数として分離し、それをRDP側のメインループ(rdp_main_loop)内でチェックし、クライアントが無くなった場合にはrdp_disconnectでRDP側の終了を行なう形式に書き換えました。

とりあえず以上の修正で zombie が増殖することもなく、怪しい core ファイルが生成されることもなく、rdp2vncは元気よく動いております。

ただ、わけのわからないプロセスが残るんですよね。 なんとなくスレッドぽいんですが、スレッドは使っていないはずだし、また大抵はその内に消えていくんですが...