2016-04-19

FIFO試してみた

名前付きパイプのFIFOに関する備忘録

コマンドで作成

$ mkfifo ./hoge.fifo

パイプの簡単な動作確認

書き込み側
$ echo "hoge" > {fifoファイル}

読み取り側

$ cat {fifoファイル}

どちらが先でもOKで、両サイドでファイルをオープンした段階で処理が始まります(ブロックする)

リモートポートフォワードで使う

任意のクライアントAがグローバルIPが割り振られていないホストBに対してSSHにログインしたい場合は、グローバルIPが割り当てられているホストC経由でポートフォワードすることにより、SSHログインすることが出来ます。

具体的には、グローバルIPが割り当てられているホストCで以下を実行して

$ mkfifo myfifo
$ nc -l -p {接続用のポート} < myfifo \
 | nc -l {Cのホスト名} {B-C間で接続する用のポート番号} > myfifo

ホストBで以下を実行

$ mkfifo myfifo
$ nc {Cのホスト名} {B-C間で接続する用のポート番号} < myfifo \
 | nc localhost {BのSSHのポート} > myfifo

これで任意のクライアントAで以下を実行すると接続できます。

$ ssh {Bのユーザ名}@{Cのホスト名} -p {接続用のポート}

netcatはTCP通信したり、TCPサーバを立てれるコマンドになります。netcatの標準入力は接続先に渡され、接続先からのデータは標準出力に渡されます。

FIFOを使ったリモートフォワードの仕組みとしてはだいたいこんな感じ(説明がかなり冗長…)

  1. ホストBとホストCが接続される
  2. ホストAからホストCの接続用ポート対してSSHを実行(データを入力)
  3. 2の標準出力はホストCのB-C間接続しているnetcatの標準入力として渡される
  4. ホストBのB-C間接続しているnetcatの入力として渡される
  5. 4の標準出力はホストBのlocal sshしているnetcatの標準入力として渡される
  6. ホストBのlocal SSHのレスポンスが標準出力としてFIFOに渡される
  7. ホストBのFIFOに渡されたデータはB-C間接続しているnetcatの標準入力として渡される
  8. ホストCのB-C間接続しているnetcatにデータが渡され、ホストCのFIFOに標準出力として渡される
  9. ホストCのFIFOに渡されたデータはA-C間接続しているnetcatの標準入力として渡される
  10. ホストAにSSHのレスポンスが返る
ちなみに上記の方法だと一回で接続が切れてしまうので、実際に利用する場合はwhileループかけるのが良さそうです。

selectで試してみる

多重I/Oシステムコールのselectで試してみました
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import select

PATH = '/path/to/file/'
FIFO_SIZE = 3

select_list = []
for num in range(FIFO_SIZE):
    f = open(PATH + 'hoge{0}.fifo'.format(num+1), 'r') # ここでブロックする
    select_list.append(f)

while True:
    rready, wready, xready = select.select(select_list, [], [])
    for fd in rready:
        line = fd.read()
        if len(line) == 0:
            continue
        print(line)

ここで注意しないといけないのが、デフォルトではFIFOのオープン時にもう一つの読み書きの口がオープンされるまでは処理がブロックされるということ。上記スクリプトを起動したときに、他のプロセスが対象FIFOに対して書き込みオープンしなければ、ブロック状態が解除されず処理が進みません。さらに上記例だと、複数のFIFOをオープンしているのでhoge1.fifo→hoge2.fifo→hoge3.fifoの順に書き込みオープンしないと先の処理にすすめません。(オープン後は正常に読取り処理が行われます)

以下のようにノンブロックでファイルをオープンすればブロックを回避できます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import select
import os

PATH = '/path/to/file/'
FIFO_SIZE = 3

select_list = []
for num in range(FIFO_SIZE):
    f = os.open(PATH + 'hoge{0}.fifo'.format(num+1), os.O_RDONLY | os.O_NONBLOCK)
    select_list.append(f)

while True:
    rready, wready, xready = select.select(select_list, [], [])
    for fd in rready:
        line = os.read(fd, 1024)
        if len(line) == 0:
            continue
        print(line)

Pythonの場合はopen関数の代わりにos.open関数を使ってos.O_NONBLOCKのフラグを立ててオープンすることになります。

参考URL

このエントリーをはてなブックマークに追加