2013/07/15

iproute2 コマンドでルーティングテーブル/アドレス設定の保存/復元

はじめに

iproute2 はLinuxでネットワーク関係の設定を変更するためのコマンドスイートです。もはや非推奨となった ifconfig の代替として利用が推奨されており、ifconfig コマンドではでは行えない設定も可能です。iproute2 コマンドスイートの中には、ルーティングやアドレス設定を行う ip、トラフィック制御を行う tc、ネットワーク統計情報を取得する lnstat, ifstat コマンドなどが含まれます。iproute2 には非常に多くの機能が含まれているのですが、linux-net に併せて開発がとても早く、ドキュメントの整備が追いついていないのが難点です。

今回は ip コマンドを用いてルーティングテーブルおよび、アドレス設定の保存/復元を行う方法についてご紹介します。仮想ネットワークの構築を行うとき等に、ネットワーク設定を素早く手軽に復元できるこれらの機能が有用です。

設定の保存

設定のルーティングテーブル/アドレス設定の保存はそれぞれ ip route/ ip addr コマンドにより行います。
設定はバイナリ形式です。
ルーティングテーブルの保存:
% ip route save > iproute.conf.bin

アドレス設定の保存:
% ip addr save > ipaddr.conf.bin

設定の確認

設定はバイナリ形式なので、人手での確認は showdump オプションを使います。
% ip addr showdump < ipaddr.conf.bin
if1:
    inet 127.0.0.1/8 scope host lo
if2:
    inet 192.168.122.132/24 brd 192.168.122.255 scope global eth0
if2:
    inet 192.168.122.133/24 scope global secondary eth0
if1:
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
if2:
    inet6 fe80::5054:ff:fe30:28ac/64 scope link 
       valid_lft forever preferred_lft forever
% ip route showdump < iproute.conf.bin 
default via 192.168.122.1 dev if2  proto static 
192.168.122.0/24 dev if2  proto kernel  scope link  src 192.168.122.132

設定の復元

restoreコマンドで復元します。
% ip addr restore < ipaddr.conf.bin
% ip route restore < iproute.conf.bin

余談

iproute2 はドキュメント整備が不足していると述べましたが、これはかなりのつらみを感じます。今回もコマンドの使い方を学ぶためにソースコードとコミットログを参照しました。
コミットログによると、今回紹介した ip addr save/restore や ip route save/restore はcheckpoint-restartでも用いられているようです。
Add ip route save/restore
iproute: Add ability to save, restore and show the interfaces' addresses (resend)

参考文献

iproute2 - official
kernel/git/shemminger/iproute2.git
git://git.kernel.org/pub/scm/linux/kernel/git/shemminger/iproute2.git

2013/06/23

OSSの開発活動を可視化するWebサービスをつくりました

Linux Foundation の報告書 "Linux Kernel Development: How Fast it is Going, Who is Doing It, What They are Doing, and Who is Sponsoring It" には Linux Kernle 開発の統計情報が数多く含まれており、開発の規模やスピード感が読み取れます。こういった統計情報は Linux Kernel のみならず、他の多くの OSS 開発プロジェクトにおいても有用です。今回作成した Web サービス "OSS Development Statistics" は git レポジトリのログから、月ごとのコミット数やディベロッパ数、コミット数ランキング、ホットな開発キーワードを表示します。

現在登録してあるプロジェクトは、自分が独断で選んだ以下のものです。要望があれば他にも追加いたします。@Etsukataまでお気軽にどうぞ。git レポジトリであれば簡単に追加できます。
  • libvirt 
  • linux 
  • mongo 
  • node 
  • openstack_nova
  • openstack_quantum 
  • openstack_swift 
  • qemu 
  • riak
現在、一日に一回 git pull してデータベースを更新しています。

サービスの作成にあたっては、MEAN Stack を使用しました。
MEAN Stack とは M : Mongodb, E : Express, A : Angular JS, N : Node.js を指し示します。クライアントサイド、サーバサイドともにJavaScriptで記述できる生産性の高いフレームワーク群です。MEAN Stackについては、mongodb blog に寄稿されている記事: The MEAN Stack: MongoDB, ExpressJS, AngularJS and Node.js が参考になります。

類似のことをするソフトウェアとしては、LWN.net の Jonathan Corbet 氏が主に開発している git-dm があります。Corbet 氏は 新しい Linux Kernel がリリースされる度に開発の統計情報をまとめた記事を投稿しますが、git-dmはその記事を書くために用いられているようです。
Linux Kernel 3.9 リリース時の記事:

今回本サービスを使ってみて、OpenStack-Nova の開発規模が QEMU を上回っていることに驚きました。OpenStack 全体では Linux Kernel の開発規模に迫りつつあるかもしれません。
今後は OSS プロジェクト相互の開発者の乗り入れ状況や、プロジェクト同士の開発規模、開発加速度の比較をしたいと考えています。

参考情報

2013/05/16

CVE-2013-2094(perfバグによるLinux権限昇格の脆弱性)まとめ

NIST により2013/5/14 に、 RHEL6.1 - 6.4 をはじめとする Linux ディストリビューションに、perf のバグをついて権限昇格される脆弱性があることがアナウンスされました。
Vulnerability Summary for CVE-2013-2094

影響範囲が大きいと思いますので、情報をまとめておきました。

Exploit Code:semtex.c
手元のCentOS 6.4 で試したところ、rootを取ることが出きました。

Does CVE-2013-2094 affect Red Hat Enterprise Linux and Red Hat Enterprise MRG?
解決策として、Systemtap スクリプトを guru モードで動作させ、動的にパッチを当てる手法が掲載されています。

RedHat Bugzilla CVE-2013-2094
書き込み「Petr Matousek 2013-05-14 19:36:43 EDT」において、上記のexploit:semtex.cでroot権限昇格についての詳しい説明があります。ありがたや。

IT media「Linuxに権限昇格の脆弱性、エクスプロイトも出まわる」

原因となったコミット

修正コミット

2013/05/09

Qemu のトレース新機能 "ftrace backend" 紹介

はじめに

Qemu のトレース新機能 "ftace backend" は Linux 標準のトレース機構 ftrace を使って Qemu と Linux Kernel(KVM) のトレース情報を併せて取得する機能です。Qemu で KVM を使う場合は、ユーザ空間(Qemu)とカーネル空間(Kernel)を頻繁に遷移するため、両空間のトレース情報を併せて取得できると、デバッグや性能解析がよりはかどります。

ftrace backend の実装には ftrace marker が用いられています。ftrace marker は debugfs の marker file への書き込みを ftrace のリングバッファに送る機能です。Qemu ftrace backend は、Qemu のトレース情報出力先を marker file にすることで実現しています。

関連コミット:
trace: Add ftrace tracing backend

2013年5月3日に Qemu Mainline にマージされました。おそらく Qemu 1.5 で使用できるはずです。Author の名前が自分と酷似していますが。。。

使い方

ftrace backend を使うためには、まず configure 時に trace backend として "ftrace" を指定する必要があります。
# ./configure --trace-backend=ftrace

このままでも ftrace backend はトレース情報を debugfs の trace file に記録できますが、今回は KVM を使うのでKVM 関係のトレースイベント情報も併せて取得するよう設定します。
# echo 1 > /sys/kernel/debug/tracing/events/kvm/enable

Qemu 起動時に、取得対象の Qemu trace イベントを指定します。
Qemu のトレースイベント一覧は Qemu ソースコードの trace-events ファイルに記載されています。また、 Qemu monitor から "info trace-events" コマンドによっても取得できます。
ここでは、すべてのイベントを取得するような設定にします。
% cat /home/eiichi/events 
*
Qemu を起動します。ftrace を使うため、必ず root 権限で起動しましょう。
# ./qemu-system-x86_64 -enable-kvm -trace events=/home/eiichi/events

得られるトレース出力は以下のようになります。
 # less /sys/kernel/debug/tracing/trace
snip...
 qemu-system-x86-23226 [002] d... 116142.685922: kvm_entry: vcpu 0
 qemu-system-x86-23226 [002] d... 116142.685923: kvm_exit: reason IO_INSTRUCTION rip 0xc45b info 700040 0
 qemu-system-x86-23226 [002] .... 116142.685924: kvm_pio: pio_write at 0x70 size 1 count 1
 qemu-system-x86-23226 [002] .... 116142.685925: kvm_userspace_exit: reason KVM_EXIT_IO (2)
 qemu-system-x86-23226 [002] ...1 116142.685943: tracing_mark_write: cpu_set_apic_base 00000000fee00900
 qemu-system-x86-23226 [002] ...1 116142.685946: tracing_mark_write: kvm_run_exit cpu_index 0, reason 2
 qemu-system-x86-23226 [002] ...1 116142.685947: tracing_mark_write: cpu_out addr 0x70 value 143
 qemu-system-x86-23226 [002] ...1 116142.685951: tracing_mark_write: kvm_vcpu_ioctl cpu_index 0, type 44672, arg (nil)
 qemu-system-x86-23226 [002] d... 116142.685954: kvm_entry: vcpu 0
snip...
tracing_mark_write と書かれているのが、Qemu(ユーザ空間)のトレース情報です。ここでは、ゲストCPUのIO port write命令を受けて VM_EXIT(reason KVM_EXIT_IO) したのちユーザ空間でエミュレーションが行われているのがわかります。

libvirt で使う場合

libvirt で ftrace backend を使う場合は、libvirtd の設定ファイルを以下のように変更し、Qemu を起動するユーザを root にする必要があります。
/etc/libvirt/qemu.conf に以下を追加:
user = "root"

 余談

ftrace marker を使うためのライブラリがあれば、もっとftrace backend のコードを短くできますし、他のアプリケーションでも手軽に ftrace marker が使えるようになって便利です。ライブラリ名は、"libftrace" などという名前になるのでしょうか。
最近、ftrace は snapshot 機能や、multiple buffer 機能が追加されています。ユーザ空間のプログラム内からそれらの機能を利用するライブラリがあるとさらに嬉しいです。

2013/03/24

ftrace marker と uprobe-based event tracer オーバヘッド比較

はじめに

ユーザ空間のプログラムのトレース情報を取得する方法には、さまざまなものがありますが、ここでは "ftrace marker" と "uprobe-based event tracer" を取り上げ、トレースオーバヘッドを測定してみます。
"ftrace marker" とは ftrace trace_marker ファイルにトレース情報を書き込むことで、ftrace でトレース情報を取得する機能です。Kernel のトレース情報とユーザプログラムのトレース情報を照らしあわせたい時等に便利です。
使い方は簡単で、tracing ディレクトリの trace_marker ファイルに write するだけです。

# echo test > /sys/kernel/debug/tracing/ftrace_marker
# cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 1/1   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
           <...>-23108 [003] ...1 34500.124363: tracing_mark_write: test

"uprobe-based event tracer" とは kprobe-based event tracer のユーザ空間版で、kprobe のように動的にプローブを差し込むことができる機能です。以下でも若干解説しますが、詳しい使い方については、ドキュメントをご覧ください。

比較方法
"ftrace marker" と "uprobe-based event tracer" をそれぞれ使って、トレースポイントを 1000回踏むのにかかる時間を測定します。
測定プログラムは以下を用意しました。
下の3つの関数をそれぞれ 1000回ずつ呼び出し、かかった時間を測定する簡単なものです。
do_nothing()  : 関数呼び出しのオーバヘッドを考慮するための何もしない関数
marker_write() : ftrace marker に write する関数
uprobe_test() :  uprobe を差し込む対象の関数

probench.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>

int marker_fd;

long time_usec(void) {
    struct timeval tv;

    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

void probe_bench(void (*func)()) {
    long start_us, end_us, diff_us;
    int i, loop_max;

    loop_max = 1000;
    start_us = time_usec();
    for (i = 0; i < loop_max; i++) {
        func();
    }
    end_us = time_usec();
    diff_us = end_us - start_us;
    printf("Elapsed time(us): %ld\n", diff_us);
    printf("per trace point(us): %f\n", (double) diff_us / loop_max);
    printf("\n");
}

void do_nothing(void) {
}

void marker_write(void) {
    write(marker_fd, "test\n", 5);
}

void uprobe_test(void) {
}


int main(int argc, char *argv)
{
    char path[256];
    int trace_fd;

    strcpy(path, "/sys/kernel/debug/tracing/tracing_on");
    trace_fd = open(path, O_WRONLY);
    if (trace_fd >= 0)
        write(trace_fd, "1", 1);

    strcpy(path, "/sys/kernel/debug/tracing/trace_marker");
    marker_fd = open(path, O_WRONLY);

    printf("-- nothing --\n");
    probe_bench(do_nothing);

    printf("-- ftrace marker --\n");
    probe_bench(marker_write);

    printf("-- uprobe --\n");
    probe_bench(uprobe_test);

    close(marker_fd);
}
Makefile
probench: probench.c
 gcc -g -o $@ $< -O2

clean:
 rm probench

上記のプログラムを make したあと、uprobe を差し込む場所を指定します。
まず、ELF を読んでプログラムヘッダからテキストセグメント開始アドレスを探しましょう。
# readelf -e probench | less
...
プログラムヘッダ:
  タイプ        オフセット          仮想Addr           物理Addr
            ファイルサイズ        メモリサイズ         フラグ 整列
...
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000be4 0x0000000000000be4  R E    200000
テキストセグメント開始アドレスは0x400000とわかりました。
次に、uprobe_test 関数のアドレスは以下のコマンドで取得できます。
 # readelf -s probench | grep uprobe_test
    59: 0000000000400830     2 FUNC    GLOBAL DEFAULT   13 uprobe_test
アドレスは0x400830とわかりました。
uprobe_test にプローブを差し込みましょう。uprobe_test 関数のアドレス0x400830 から テキストセグメント開始アドレス 0x400000を引き算した 0x830 をuprobe_events に書き込みます。書き込み後はイベントをオンにしてやります。
# echo 'p:test /path/to/probench:0x830' > /sys/kerenel/debug/tracing/uprobe_events
# echo 1 > /sys/kerenel/debug/tracing/events/uprobes/test/enable
以上で準備がととのいました。ベンチマークプログラム : probench を実行しましょう。
# ./probench       
-- nothing --
Elapsed time(us): 8
per trace point(us): 0.008000

-- ftrace marker --
Elapsed time(us): 1742
per trace point(us): 1.742000

-- uprobe --
Elapsed time(us): 4255
per trace point(us): 4.255000

結果は、
ftrace marker : 1.7us/TracePoint
uprobe : 4.3us/TracePoint
でした。関数呼び出しのオーバヘッドは無視できるようです。
uprobe は ftrace marker と比べて、若干オーバヘッドが嵩むようです。

余談
uprobe-based event tracer ではなく、systemtap を使った場合でも測って見ました。
以下のようなスクリプトを実行したあとで probench を走らせます。
#!/usr/bin/stap

probe begin {
 printf("start\n")
}

probe process("/home/eiichi/git/probench/probench").function("uprobe_test") {
}

# ./probench
-- nothing --
Elapsed time(us): 8
per trace point(us): 0.008000

-- ftrace marker --
Elapsed time(us): 1766
per trace point(us): 1.766000

-- uprobe --
Elapsed time(us): 3994
per trace point(us): 3.994000
systemtap の場合は 4.0us/TracePoint でした。uprobe-based event tracer と変わらないようです。

まとめ
"ftrace marker" と "uprobe-based event tracer" のオーバヘッドを比較すると、前者より後者のほうが2倍以上大きいことがわかりました。トレースオーバヘッドを気にする かつ ユーザプログラムを改変できる場合は ftrace marker を使ったほうがいいでしょう。

参考情報
kernel/Documentation/trace/ftrace.txt
kernel/Documentation/trace/kprobetracer.txt
kernel/Documentation/trace/uprobetracer.txt

2013/03/11

Netperf でネットワークレイテンシ測定

Netperf は iperf と並ぶ多機能ネットワークベンチマークソフトです。 Netperf を使って latency(RTT: Round Trip Time) を測定する方法についてまとめました。

omni-test による latency 測定
最近の netperf は "omni-test" による測定を行うのが一般的のようです。
omni test で latency を測定しましょう。
ここで、latency とは送信サーバと受信サーバでパケットが往復するのにかかる時間(RTT)のことを言うとします。 まずは受信側で受信準備します。
# netserver
送信側で UDP で RTT を測定するコマンドを発行します。
# netperf -t omni -H localhost -- -d rr -T UDP -k MIN_LATENCY,MEAN_LATENCY,P90_LATENCY,P99_LATENCY,MAX_LATENCY,STDDEV_LATENCY
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET
MIN_LATENCY=11
MEAN_LATENCY=44.34
P90_LATENCY=50
P99_LATENCY=74
MAX_LATENCY=3079
STDDEV_LATENCY=13.49
latency の単位は micro second です。 オプションについて簡単に説明します。
  -t : テストモード指定。ここでは omni テスト。
  -H : 受信サーバのホスト名またはIP addressを指定。
  -- : 以降が omni テストのためのオプションになります。
  -d : direction 指定。rr は request/response = 往復時間測定。
  -T : プロトコルを指定。ここでは UDP。他には TCP, SCTP も指定可能。
  -k : omni test 特有の出力項目セレクタ。
出力は レイテンシの最小値、平均値、90%分位点、99%分位点、最大値、標準偏差、などが出力可能です。

-k オプションで omni test で出力する項目を指定する項目は非常にたくさんあります
-k all とすると、すべて出力されるようになります。測定ログとして残すには十分でしょう。
# netperf -t omni -H localhost -- -d rr -T UDP -k all           
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET
SOCKET_TYPE=Stream
PROTOCOL=UDP
DIRECTION=Send|Recv
ELAPSED_TIME=10.00
THROUGHPUT=22333.41
THROUGHPUT_UNITS=Trans/s
...
UUID=46412b12-8a4a-11e2-985e-e91ceb3a7ad7
MIN_LATENCY=11
MAX_LATENCY=2478
P50_LATENCY=44
P90_LATENCY=52
P99_LATENCY=77
MEAN_LATENCY=43.90
STDDEV_LATENCY=15.74
LOCAL_SOCKET_PRIO=-1
REMOTE_SOCKET_PRIO=-1
LOCAL_SOCKET_TOS=0xffffffff
REMOTE_SOCKET_TOS=0xffffffff
LOCAL_CONG_CONTROL=
REMOTE_CONG_CONTROL=
LOCAL_FILL_FILE=
REMOTE_FILL_FILE=
COMMAND_LINE="netperf -t omni -H localhost -- -d rr -T UDP -k all"

ヒストグラム出力

レイテンシの測定では、分位点などの要約統計量だけでなくヒストグラムが欲しいことがあります。
netperf でヒストグラムを出力する際は、コンパイルする際に、configure に --enable-histogram オプションを渡します。
# ./configure --enable-histogram
測定の際には -v 2 オプションを付け、 verbosity を 1 より大きくします。
# netperf -t omni -H localhost -v 2 -- -d rr -T UDP -k MIN_LATENCY,MEAN_LATENCY,P90_LATENCY,P99_LATENCY,MAX_LATENCY,STDDEV_LATENCY
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET : histogram
MIN_LATENCY=11
MEAN_LATENCY=44.87
P90_LATENCY=46
P99_LATENCY=71
MAX_LATENCY=1447
STDDEV_LATENCY=7.58

Histogram of request/response times
UNIT_USEC     :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
TEN_USEC      :    0: 3031: 1094:  413: 197318: 9043: 4774: 1942:  441:  135
HUNDRED_USEC  :    0:  163:    4:    2:    3:    0:    1:    0:    0:    0
UNIT_MSEC     :    0:    1:    0:    0:    0:    0:    0:    0:    0:    0
TEN_MSEC      :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
HUNDRED_MSEC  :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
UNIT_SEC      :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
TEN_SEC       :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
>100_SECS: 0
HIST_TOTAL:      218365

余談

上記の測定には、対象として自身(localhost)を指定していました。
レイテンシの最大値: 1.4 ms という数字は、ping コマンドで RTT を測定した場合に比べて大きいです。これは、Kernel 内でパケットが折り返す ping(ICMP) とは違い、ユーザ空間までのパケット往復時間で図っているためです。 パケットがユーザ空間まで上がる分、スケジュールの関係で netperf や netserver プロセスの実行が遅れてしまうことがあるからです。

 参考文献
Care and Feeding of Netperf

2013/03/09

Qemu ゲストからの virtio-blk cache 設定確認

Qemu/KVM を用いた VPS(IaaS) を使っていると、Qemu virtio-blk Disk の cache  設定がどうなっているのか気になることがあります。実は、ゲストから以下のコマンドで cache 設定を確認できます。
(virtioXの数字Xはlspciコマンドなどで確認してください。)
[eiichi@f1 vda]$ cat /sys/bus/virtio/devices/virtio1/block/vda/cache_type
write back
"write through" ならば Disk Write Cache なし
(Qemu cache mode は "directsync", "writethrough" のどちらか)、
"write back" ならば Disk Write Cache あり
("none", "writeback", "unsafe" のどれか)です。
Qemu の cache mode について詳しくは、拙稿 Qemu cache mode まとめをご覧ください。

ゲストの kernel バージョンによっては上記の cache_type が用意されていないこともあります。そのときは以下のコマンドで virtio-blk デバイスの feature bits を参照することで確認できます。左から10番目が disk cache の有無を示す bit です。 1 なら有り、0なら無しです。
[eiichi@f1]~% cat /sys/bus/virtio/devices/virtio1/features
0010101101110000000000000000110000000000000000000000000000000000
この例では左から10番目が1なので disk cache "有り"です。
ちなみに、自分が借りている、"さくらのVPS"での feature bits は以下でした。
0010101001100000000000000000110000000000000000000000000000000000
さくらのVPSも disk cache 有りのようです。
自分のローカル環境との違いは SCSI コマンドサポートの有無と、disk cache の動的変更の可否です。
他の Feature bits に関する情報は qemu の hw/virtio-blk.h または Linux カーネルの virtio-blk デバイスドライバ drivers/block/virtio_blk.c から手に入ります。
 /* Feature bits */ 
 #define VIRTIO_BLK_F_BARRIER    0       /* Does host support barriers? */
 #define VIRTIO_BLK_F_SIZE_MAX   1       /* Indicates maximum segment size */
 #define VIRTIO_BLK_F_SEG_MAX    2       /* Indicates maximum # of segments */
 #define VIRTIO_BLK_F_GEOMETRY   4       /* Indicates support of legacy geometry */
 #define VIRTIO_BLK_F_RO         5       /* Disk is read-only */
 #define VIRTIO_BLK_F_BLK_SIZE   6       /* Block size of disk is available*/
 #define VIRTIO_BLK_F_SCSI       7       /* Supports scsi command passthru */
 /* #define VIRTIO_BLK_F_IDENTIFY   8       ATA IDENTIFY supported, DEPRECATED */
 #define VIRTIO_BLK_F_WCE        9       /* write cache enabled */
 #define VIRTIO_BLK_F_TOPOLOGY   10      /* Topology information is available */
 #define VIRTIO_BLK_F_CONFIG_WCE 11      /* write cache configurable */
 
 #define VIRTIO_BLK_ID_BYTES     20      /* ID string length */
新しく Qemu/KVM で構築された VPS などを借りてみたときは virtio-blk cache 設定を確認されてはいかがでしょうか。