Piping Server を介した双方向パイプによる,任意のネットワークコネクションの確立


Piping Server

nwtgck 氏によって HTTP を用いて2つのホスト間でパイプを構成するサーバ実装が公開されました.
https://qiita.com/nwtgck/items/78309fc529da7776cba0
名前を Piping Server と呼ぶそうです.

nwtgck 氏は

  • 枯れた技術である HTTP 上に実装されている
  • HTTP 上に実装されているため,ウェブブラウザ,wgetcurlなどの慣れ親しんだツール群でデータのやり取りができる
  • パイプ経由で効率的にデータを転送でき,従って送信側・受信側ともにコマンドライン上でパイプをつなげることによりデータの加工がバイトストリーム上で行える
  • データの送信者,受信者ともに TCP ポート番号80または443への外向きのコネクションさえ確立できれば良い

といったあたりが Piping Server のアピールポイントであると主張されています.

本エントリでは, nwtgck 氏が言及していない Piping Server の利用方法を提案します.すなわち, TCP ポート番号80への外向きのコネクションのみを許しているファイアウォールの背後にあるホスト(以下,「クライアントホスト」と呼ぶ),および同じ条件下にある別のホスト(以下,「サーバホスト」と呼ぶ)に関して, Piping Server に対する HTTP によるアップロード (PUT) およびダウンロード (GET) に基づいて,クライアントホストとサーバホストとの間で通常のネットワークエンドポイントや UNIX ドメインソケットを介した任意のネットワークプロトコルによる通信を行う手法を提案します.

本エントリでは, Piping Server によって,上記制約下のクライアントホストとサーバホストとの間でパイプを確立できる,すなわちバイトストリームを転送できる点に着目します. nwtgck 氏自身が上記記事内で言及しているように, Piping Server は TURN サーバのように振る舞うと理解できるものです.以下で提案する手法は, Piping Server を任意のネットワークプロトコルに対する TURN サーバのように使用しつつ,それを通常のネットワークエンドポイントや UNIX ドメインソケットとして提供するものです.

通常のネットワークプロトコルによる通信を確立するためには,順方向および逆方向の2つのバイトストリームを転送できる必要があります.パイプは1方向のみのバイトストリームであるため,順方向と逆方向の2つのパイプを用意することにより両方向のバイトストリームを転送できます.しかしながら,このままではあくまでパイプが2つあるだけであり,ネットワークエンドポイントなどのような通常のネットワークプロトコルが期待するインタフェイスと齟齬をきたします.

本エントリは, Piping Server を介して確立した順方向・逆方向の2つのバイトストリームを, socat によって通常のネットワークエンドポイントや UNIX ドメインソケットとして提供する手法を提案するものです.

socat

socat http://www.dest-unreach.org/socat/ は,入出力を持つ対象(socat の文脈では「アドレス」と呼びます.)の出力を入出力を持つ別の対象の入力に転送し,またその逆方向の転送も行う,極めて汎用かつ強力なツールです. socat が対象として取れるものは,ネットワークポートへの接続要求を listen するネットワークソケット,ネットワークエンドポイントへのコネクションを確立したネットワークソケット,プログラム(の標準入力・標準出力・標準エラー出力),ファイルなど,入力もしくは出力が定義できるありとあらゆるものです.

本エントリで利用する socat の機能は以下の通りです.

  • ネットワークポートへの接続要求を listen するネットワークソケットの入出力を転送できること.
  • ネットワークエンドポイントへのコネクションを確立したネットワークソケットの入出力を転送できること.
  • プログラムの標準入力を転送できること.
  • プログラムの標準出力を転送できること.そして,
  • 入力のみおよび出力のみの対象を組み合わせて入出力を持つ1つの仮想的な対象として転送の対象にできること.

以上を使います.

以下では,便宜上, TCP/IP に基づくネットワークコネクションを確立する場合に限定して説明します.

概念図を以下に示します.

上記概念図の詳細について説明します.

まず,サーバホストにおいて,指定した Piping Server の URL (上記概念図における http://ppng.ml/xxx)からの HTTP GET を行うプログラム (上記概念図では具体的に curl を例示.以下同様) の標準出力を,サーバプログラムが listen している(ローカルホストの)ネットワークエンドポイントに接続したネットワークソケットの入力へ転送するよう socat を構成します.この socat は同時に,前述のネットワークソケットの出力を, Piping Server の URL (上記概念図における http://ppng.ml/yyy)への HTTP PUT 行うプログラムの標準入力へ転送するように構成します.

次に,クライアントホストにおいて, TCP の特定のポート番号を listen するネットワークソケットを作成し,その出力を, Piping Server の URL (上記概念図における http://ppng.ml/xxx)への HTTP PUT を行うプログラムの標準入力へ転送する socat を構成します.この socat は同時に, Piping Server の URL (上記概念図における http://ppng.ml/yyy)からの HTTP GET を行うプログラムの標準出力を,前述のネットワークソケットの入力へ転送するように構成します.

最後に,クライアントホストのクライアントプログラムを,前述のクライアントホスト socat が作成したネットワークソケットが listen しているネットワークエンドポイントに接続します.

以上により,クライアントホストの socat のネットワークソケットへのバイトストリーム入力は,最終的にサーバ側のサーバプログラムが listen しているネットワークエンドポイントへと転送されます.同時に,サーバ側のサーバプログラムから出力されるバイトストリームは,最終的にクライアント側の socat のネットワークソケットに転送されます.これにより,クライアント側の socat が作成したネットワークソケットが listen しているネットワークエンドポイントと,サーバプログラムが listen しているネットワークエンドポイントとの間で双方向のバイトストリーム通信が確立します.

上記概念図における重要な点は以下の2点です.

第1点.クライアントプログラム側(左側)から見れば,接続先は(クライアント側 socat が作成したネットワークソケットが listen する)単なるネットワークエンドポイントでしかなく, Piping Server を介した HTTP PUT/GET によって通信路が実装されていることが完全に隠蔽されます.サーバプログラム側(右側)も同様です.これによって,通常のネットワークプロトコルがそのまま動作します.

第2点.本システムを外側から観測したときの挙動が, curl から Piping Server への外向きの HTTP コネクションによる GET および PUT 以外の何物でもないことです.これにより,クライアント側およびサーバ側ともに TCP ポート番号80への外向きのコネクションのみを許したファイアウォールの背後から,概念図に示したネットワークコネクションを確立できます.

構成

上記概念図に相当する構成は,典型的な Linux シェル環境において,サーバ側・クライアント側で各々コマンド1つを実行することにより構築できます.

以下で示すコマンドにおいて, http\://ppng.ml/xxx および http\://ppng.ml/yyy はただの例示です.どちらも Piping Server 上の任意の URL を使用できます.これらの URL が他人が使用するものと重複した場合,ネットワークコネクションの確立に失敗するため,他人が使用しうる URL と重複しないものに適宜置き換えてください.また,各方向のパイプで使用する Piping Server は当然ながら別のサーバを使用することもできます.

サーバホストおよびクライアントホストにおいて実行すべきコマンドは以下の通りです.これらのコマンドが具体的にどういう意味なのかは socat のドキュメントを参照してください.

サーバホスト
$ socat 'EXEC:curl -NsS http\://ppng.ml/xxx!!EXEC:curl -NsST - http\://ppng.ml/yyy' TCP:127.0.0.1:PORT

上記サーバホストコマンドの PORT は,サーバプログラムが listen している TCP ポート番号に置き換えてください.

クライアントホスト
$ socat TCP-LISTEN:31376 'EXEC:curl -NsS http\://ppng.ml/yyy!!EXEC:curl -NsST - http\://ppng.ml/xxx'

上記クライアントホストコマンドは,確立するネットワークコネクションが TCP である場合です.ポート番号 31376 はただの例示で,ローカルホストで空いている任意のポート番号を使用することができます.

以上です.後はクライアント側で,クライアントプログラムを TCP ポート番号 31376 へ接続させれば完了です.

具体的に,あるホストから SSH サーバが稼働している別のホストへ接続した例を以下に示します.

画像に示す通り,サーバ側でコマンド1つ,クライアント側でコマンド2つ(うち1つはクライアントプログラム ssh の起動)を実行することにより, SSH のネットワークコネクションが確立しています.

以上です.

提案の限界

(TODO)