systrace

世間では新しく出たiOS9を入れたら、遅くなったとかLINEが落ちるようになったとかの 不幸な話が、ちらほらと聞こえてくるけど、オイラーの所も大変よ。

暫く前からWebのブラウジングが遅くなった。ipadをTV代わりにしてユーチューブ見放題を やってる女房からも遅いとクレーム。

そんなのばかり見てるからプロバイダーはネットTVへ移動させたくていじわるしてんだよ。 自粛すればきっといやがらせも解けるよとご忠告。で、一向に改善のきざしが無い。

パソコンでもipadでもってなると、共通項は2つ。プロバイダーがいやがらせで、文句を つけても、インターネットって努力はするけど保障はしないって代物でっせと返されるに 決まっている。もう一つの共通項は無線LAN。

この所パソコンの有線LAN用NICはさっぱり使っていないんで、点検の意味も込めて動作確認 してみっか。つないで点検。Webはすいすい見れる。ファイルのダウンロードもすいすい。

どうやら、プロバイダーのいやがらせの嫌疑は晴れた。するともう一つの懸案事項は無線LAN。 親機がいかれかけてる? こやつプロバイダーのキャンペーンで貰ったやつだからなあ。 ちょいと山田君の所へ行って、新しいやつを仕入れてくる?

アマチュア無線家なら、その前にやる事あるっしょ。お空が混んでいないかい? 無線LAN モニターでスペアナしたら、超強力広帯域の局が出てて、おいらの電波とかぶっていたぞ。 おいらは1CHしか占有してないのに、広帯域局は数CHも握ってる。おいらのスペックが54Mに 対し、あちらさんは300M。6倍もチャンネルを占めてるって、どこ製のやつかと思ったら、 日本電気製。こういう贅沢品は、電波利用税をかけろよな。

しょうがないので、おいらの親機を一度電源断。それから電源投入。この時に空きチャンネルが 検索されて、混信回避が出来た。安物親機なんで、チャンネルホッピングなんて機能は 付いていないのさ。

Windowsのタスクマネージャ・ネットワーク項にリンク速度が表示されるのね。混信時は、 5-11Mbpsあたりをうろうろしてたけど、今は安定に65Mbspをキープしてる。 こんな情報を提示してくれてるなんて、今まで知らんかったわい。

ipad proが出たけど、日本では幾らで発売されるのかな。画面が広くなって眼には嬉しい だろうけど、財布に厳しいのは困るぞ。iフォン何とかは眼のいい若者用。ipad proは、 眼が悪い年寄り用。で、年寄り割引設定してね。

systrace

前回、何気に使った、OpenBSDのsystrace。どうやって見つけたかと言うと、man -k trace した だけだったりします。そして出てきたのをmanして、使い方をちょっと悩み。。。

色々情報がネットに転がっているだろうと思ったら、さにあらず。このコマンドはどうも、 OpenBSD特有な物みたいだ。Systrace を見ると ちょっと物騒な事が書いてあります。それより時代は、 アンドロドロイドのSystraceに へシフトしてるのかな。

このコマンド、何処に鎮座してるかと言うと

[ob: ~]$ ls /bin
[*           cpio*        ed*          ls*          pwd*         sha512*
cat*         csh*         eject*       md5*         rksh*        sleep*
chgrp*       date*        expr*        mkdir*       rm*          stty*
chio*        dd*          hostname*    mt*          rmdir*       sync*
chmod*       df*          kill*        mv*          sh*          systrace*
cksum*       domainname*  ksh*         pax*         sha1*        tar*
cp*          echo*        ln*          ps*          sha256*      test*

こういう、一等地に配置されてました。親分のお気に入りなんですな。 どんな事が出来るかと言うと

[ob: ~]$ systrace -A ls /tmp
uscreens   vi.recover

こうして、lsコマンドで発行されるシステムコールを記録します。続いて

[ob: ~]$ systrace -a ls /etc
ls: /etc: Operation not permitted

今度はlsの対象を/etcに変えたんですが、先ほどの記録と違うので拒否されました。-e オプションを付けると、拒否理由の説明が出てきます。

[ob: ~]$ systrace -ae ls /etc
systrace: deny user: sakae, prog: /bin/ls, pid: 22261(0)[0], policy: /bin/ls, filters: 20, syscall: native-fsread(42), filename: /etc
 :

記録はどうなってるかと言うと、

[ob: ~]$ cat .systrace/bin_ls
Policy: /bin/ls, Emulation: native
          :
        native-fsread: filename eq "/tmp" then permit
        native-fsread: filename eq "/home/sakae/." then permit
        native-fchdir: permit
           :

こんなルールに照らし合わせて、/etcへのfsreadは拒否されたんですね。

これ、どうやって実現してる? ちょっと考えたけど、思いつかず。だったらソース嫁。 ちょい見、8000行ぐらいで見ごたえあるなあ。etag作って、ソース間を飛び回るか、それとも、 gdbにかけられるようにコンパイルするか。

両方を満足させるには、ソース軍を取り出してきて、自在に加工出来るようにすればいいな。 やってみたら、ヘッダー見つからんエラー。で、Makefileを手直し。

[ob: z]$ diff -u /usr/src/bin/systrace/Makefile Makefile
--- /usr/src/bin/systrace/Makefile 
+++ Makefile    
@@ -1,9 +1,9 @@
 #      $OpenBSD: Makefile,v 1.15 2006/07/02 12:34:15 sturm Exp $

 PROG=  systrace
-CFLAGS+=-I. -I${.CURDIR} -I${.CURDIR}/../../sys
+CFLAGS+=-I. -I/usr/src/sys/
 CFLAGS+=-Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith \
-       -Wno-uninitialized
+       -Wno-uninitialized -g -O0
 CFLAGS+=-DYY_NO_UNPUT
 DPADD+=        ${LIBEVENT}
 LDADD+=        -levent

注目は変更点よりも、libevent をリンクしてる事ですかね。systrace.cの中にmainが有って、それを ざっと見て行くと、event_set とか event_dispatch なんて関数が出てきています。 野生の感で、これらが肝になりそうなんで、libeventの事を先に調べておきます。

そういえば、このlibeventはcrystalをコンパイルした時に出てきたと記憶があるぞ。

libevent

そりゃ、manセー。いきなり英語と格闘じゃきついので、予習しましょ。

libeventの関数とか メモメモ

libeventでechoサーバをつくってみた

ネットに公開されてたコードを頂いてきた。

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <event.h>
#define BUF     256

void file_read(int fd, short event, void *arg) {
        char            buf[BUF];
        int             len;
        //struct event  *ev     = arg;

        len     = read(fd, buf, sizeof(buf) - 1);
        if (len == -1) {
                warn("can not read");
                return;
        } else if (len == 0) {
                sleep(1);
                return;
        }

        buf[len] = '\0';
        (void) printf("%s", buf);
}

int main (int argc, char *argv[]) {
        int     fd;
        int     flags   = O_RDONLY;
        char    *file   = argv[1];
        struct event      watch;

        if ( (fd = open(file, flags) ) == -1) {
                err(EXIT_FAILURE, "can not open(%s,%d)", file, flags); }

        event_init();
        event_set(&watch, fd, EV_READ | EV_PERSIST, file_read, &watch);
        event_add(&watch, NULL);
        event_dispatch();
        exit(0);  // don't come here, this is dummy
}

tailもどきだそうです。touch hogeとかして、空ファイルを作成後、a.out hogeで起動。 別端末から echo hello >>hoge すると、起動した、a.outの端末にhelloが現れるデモ。 終了はCtl-c です。

コードを解説と共に読むと、ファイルをオープンして、それを引数にして、監視するんですね。 変化が有ったら、登録しといた(file_read)が呼び出される。これって、GUIの仕組みと同じ 考え。

read_file内では、fdからreadして読み出せたらstdoutに出力。読み出し出来るデータが無い 場合は、暫く待ってからreturn。この待ちを無くすと、CPUの使用率が上昇するので注意。 まあ、非同期なんで、定期的に読み出せるデータが無いか確認するのは、しょうがない事。

データが届くまで、ブロックするのを避けるって意味では、マウスでクリックされるまで 先に処理が進まないGUIじゃ困るのと一緒で、こうせざるを得ないんだな。

kqueue

で、これを旨くどのOSでも実現しようとしたのがlibeventだけど、OS限定なら、もっと簡便に 行きそう。BSD業界では、kqueueとkeventってのがシステムコールに用意されてるとな。

*BSD で kqueue・kevent を使ってみよう

kqueue(2)

kqueueを試してみた

poll/epoll/kqueueを任意に切り替えられるコード

リナの場合はepollとかpollってのを使えとな。なる程ね。そんじゃ、OpenBSDに実装されてる libeventはどんなシステムコールを使ってるのだろう? そんなのソース嫁じゃ当たり前 過ぎるんで、既存のコマンドを使って調べてみますか。

ktraceで調べる

思い付くのは、ktraceかな。

[ob: t]$ touch /tmp/hoge
[ob: t]$ ktrace -tc ./a.out /tmp/hoge
123
^C

別端末から123を書き込んで、促成tailがそれを処理した後、止めました。後はkdumpすれば いいんだけど、余計なシステムコールが頻発してるんで、一度ログにでも落としてから、 後でゆっくり眺めればよい。

  4743 a.out    CALL  kqueue()
  4743 a.out    RET   kqueue 4
   :
  4743 a.out    CALL  kevent(4,0x78ffc000,0,0x78ffc800,64,0)
  4743 a.out    RET   kevent 1
  4743 a.out    CALL  clock_gettime(CLOCK_MONOTONIC,0xcfbe2010)
  4743 a.out    RET   clock_gettime 0
  4743 a.out    CALL  read(3,0xcfbe1f20,0xff)
  4743 a.out    RET   read 0
  4743 a.out    CALL  nanosleep(0xcfbe1ee4,0xcfbe1ed8)
  4743 a.out    RET   nanosleep 0
  4743 a.out    CALL  kevent(4,0x78ffc000,0,0x78ffc800,64,0)
  4743 a.out    RET   kevent 1
  4743 a.out    CALL  clock_gettime(CLOCK_MONOTONIC,0xcfbe2010)
  4743 a.out    RET   clock_gettime 0
  4743 a.out    CALL  read(3,0xcfbe1f20,0xff)
  4743 a.out    RET   read 4

kqueueとkeventを使ってますなあ。readした時読み出せるのが無かったのでnanosleepで待って、 喪一度keventして、今度は4バイト届いていたとな。

systraceで調べる

そもそもこの記事を書く発端になった、systraceで調べるって手も有るな。

[ob: t]$ systrace -A ./a.out /tmp/hoge
123
456
^C

今度は、systraceが残した許可ファイルを覗きます。

[ob: ~]$ cat .systrace/home_sakae_z_t_a_out
Policy: /home/sakae/z/t/a.out, Emulation: native
         :
        native-fsread: filename eq "/usr/lib/libevent.so.4.1" then permit
         :
        native-clock_gettime: permit
        native-socketpair: permit
        native-poll: permit

あれ? こちらにはkeventとかが含まれていないぞ。代わりに何とリナっぽくpollが 含まれてる。そもそもOpenBSDにリナっぽいやつが有っていいの?

[ob: ~]$ grep poll /usr/include/sys/syscall.h
/* syscall: "ppoll" ret: "int" args: "struct pollfd *" "u_int" "const struct timespec *" "const sigset_t *" */
#define SYS_ppoll       109
/* syscall: "poll" ret: "int" args: "struct pollfd *" "u_int" "int" */
#define SYS_poll        252

有りますねぇ。マニュアルを調べてみると、

     Consider the following usage of select(2) that implements a read from the
     standard input with a 60 second time out:

           struct timeval timeout;
           fd_set readfds;
           char buf[BUFSIZ];
           int nready;

           timeout.tv_sec = 60;
           timeout.tv_usec = 0;
           FD_ZERO(&readfds);
           FD_SET(STDIN_FILENO);
           nready = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);
           if (nready == -1)
                   err(1, "select");
           if (nready == 0)
                   errx(1, "time out");
           if (FD_ISSET(STDIN_FILENO, &readfds)) {
                   if (read(STDIN_FILENO, buf, sizeof(buf)) == -1)
                           err(1, "read");
           }

     This can be converted to poll() as follows:

           struct pollfd pfd[1];
           char buf[BUFSIZ];
           int nready;

           pfd[0].fd = STDIN_FILENO;
           pfd[0].events = POLLIN;
           nready = poll(pfd, 1, 60 * 1000);
           if (nready == -1)
                   err(1, "poll");
           if (nready == 0)
                   errx(1, "time out");
           if ((pfd[0].revents & (POLLERR|POLLNVAL)))
                   errx(1, "bad fd %d", pfd[0].fd);
           if ((pfd[0].revents & (POLLIN|POLLHUP)))
                   if (read(STDIN_FILENO, buf, sizeof(buf)) == -1)
                           err(1, "read");
           }

こんなのも出てくるし。昔はselectを使ったけど、今はpollがお勧めらしい。 それはそうと、keventとpoll一体どちらを使ってるの?

舞台をウブに移して、前回作ったOpenBSDのdebug環境でtailもどきを実行してみた。 breakはsys_keventとsys_pollの両方を設定。keventの方はうるさいぐらいにbreakしてくる んだけど(きっと1秒ごとに)、pollの方は忘れたぐらいにbreakしてきた。

これはいかんと言う事で、tailもどきをgdbにかけてみた。そしたら今度はgdbが五月蝿いぐらいに pollでbreakしてきた。gdbってpollを使ってるのか。

libeventのソースが有るので、追ってみるとevent_initを終了した時

(gdb) p base->evsel->name
$2 = 0x20b4e399 "kqueue"

BSDのやつが選択されてた。だったらpollは難だ?

systraceを読む

と言うか、gdbでなぞってみた。そしたら、こんなのに出会った。

739          /* Initialize libevent but without kqueue because of systrace fd */
740  =>      setenv("EVENT_NOKQUEUE", "yes", 0);
741          event_init();

これからlibeventを初期化するけど、systraceで扱うfdにはkqueueは不向きなんで、避けて くれ。そのために、EVENT_NOKQUEUEって言う環境変数をセットしてから、event_initを 呼ぶよ。

(gdb) p base->evsel->name
$1 = 0x2762f3a0 "poll"

確かに、pollに切り替わってました。ついでに、libeventの中でどんな環境変数が使えるか 調べておくと、

[ob: libevent]$ grep getenv *.[ch]
event-internal.h:const char *evutil_getenv(const char *varname);
event.c:        if (!issetugid() && getenv("EVENT_SHOW_METHOD"))
evutil.c:evutil_getenv(const char *varname)
evutil.c:       return getenv(varname);
kqueue.c:       if (!issetugid() && getenv("EVENT_NOKQUEUE"))
poll.c: if (!issetugid() && getenv("EVENT_NOPOLL"))
select.c:       if (!issetugid() && getenv("EVENT_NOSELECT"))

ざっと見、こんな感じでしたよ。多分、優先順序を付けているんだな。manしたら

ADDITIONAL NOTES
     It is possible to disable support for kqueue, poll or select by setting
     the environment variable EVENT_NOKQUEUE, EVENT_NOPOLL or EVENT_NOSELECT,
     respectively.  By setting the environment variable EVENT_SHOW_METHOD,
     libevent displays the kernel notification method that it uses.

しっかり書いてありましたよ。見落としていたんですなあ。(何時もこれだ)

以上の調査をまとめると、systraceを使ってtailもどきを実行すると、systraceが使うlibeventは 環境変数経由でpollが指定される。systraceの引数であるもどきは、親から環境を継承する。 よって、もどきの中で使われるlibeventもpollが選択される。あー、すっきりしたわい。

で、一応検証しとく。もどきをちょっと改変。親から貰った遺伝子を破壊してみる。そして 自分が選んだのを公表させる。

        setenv("EVENT_SHOW_METHOD", "yes", 0);
        unsetenv("EVENT_NOKQUEUE");
        event_init();

これで実行すると

[ob: t]$ systrace -A ./a.out /tmp/hoge
[msg] libevent using: kqueue
123

systraceの許可リストにもちゃんと載ってきた。

        native-kqueue: permit
        native-getpid: permit
        native-kevent: permit

このlibeventも今はlibevent2が出て進化してんのね。OpenBSDが採用してるのは、どうやら 古い方みたいだけど。おっかけしたいなら libevent ? an event notification libraryを参照せいとな。 今まで、systraceとは?ってのに良い答えを見出せなかったけど、このHPに簡潔・明瞭な説明が有った。 Systrace ? a system call sandbox. なるほどね。確かにその通りだ!

おいらは今まで、システムコールの収集器だと思っていたぞ。正しい使い方は

systrace -g xsystrace ls

とかをX Window上で実行。こうすると一つづつシステムコール内容が出てきて、それを 許可するか否か指定できるようになる。こうなっていると、確かにやばいシステムコールは 拒否出来るな。

sakae@uB:~$ ./a.out /tmp/hoge
[warn] Epoll ADD(1) on fd 3 failed.  Old events were 0; read change was 1 (add); write change was 0 (none): Operation not permitted

tailもどきをリナに持ってきて実行すると、上記のエラーになるんだけど、libeventの バージョン変更で使い方が変わったの? よう分からん。

last log clear

OSを長年使っていると、last用のログが肥大化してく。FreeBSDだと、毎月1日の午前5時 だったかに、これをクリアしてくれるからいいんだけど、そういう気配りが無いOSが多い。

安全にクリアするにはどうするんだっけ? 昔やったな。思い出したら忘れないように メモしとこう。ここにメモした事を忘れたらどうする?

ぐぐるさんに頼る。んで、上記は検索用のキーワードの積もり。最近ぐぐる八分ぽいんだけど、 禁止ワードを頻発してるから?

[sakae@fb10 /var/log]$ sudo cp /dev/null utx.log

ログファイルと言うか記録用DBは、/var/logの中に置いてある。名前は man lastすれば 適切なのが案内される。

ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ

ハロワ攻略本が出たようだ。著者はあの方。

ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ

案内によると、手を動かして体験するような体裁になってるらしい。飢えた人に魚を 与えるんじゃなくて、独り立ち出来るように釣りの方法を体験させてくれるってのは、 願ってもない事。