2013年12月03日

Mac OSX で vmnet が BIOCSETIF できなくてハマった話し

はてなブックマークに登録

1年くらいブログ記事書くのをサボっていたので、ちょっとマニアックなネタを投下します。ISUCON3 は予選で敗退してしまった @pandax381 です。

vmnet が BIOCSETIF できない!

Mac OSX(marvericks)で VMware Fusion 6 を使っているのですが、ホスト側の仮想インタフェース(vmnetX)のパケットがキャプチャできないという問題に遭遇しました。具体的には、パケットをキャプチャするためにBPFデバイスをオープンして、vmnetX を ioctl(BIOCSETIF) でアタッチするの処理で失敗してしまうのです。一見普通のEthernetデバイスに見えるのに、どうしてBPFでパケットをキャプチャできないのか調べてみました。

tcpdump で試してみる

僕の作っていたプログラムがイケてないのかもしれないので、まずは tcpdump を使ってキャプチャできるか試してみます。そして、tcpdump先生でもダメならあきらめましょう。

tcpdump に -i オプションで vmnet8 を指定して実行してみます。

$ sudo tcpdump -i vmnet8
tcpdump: vmnet8: No such device exists
(BIOCSETIF failed: Device not configured)

あれ、うまくいかない。。そういえば、OSX の tcpdump はインタフェース指定しなければ全てのインタフェースからキャプチャできたはずなので、-i オプションなしで実行してみます。

$ sudo tcpdump
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Packet Tap), capture size 65535 bytes
18:18:51.715943 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 54865, seq 1, length 64
18:18:51.715969 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 54865, seq 1, length 64
18:18:52.715822 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 54865, seq 2, length 64
18:18:52.715862 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 54865, seq 2, length 64
18:18:53.716669 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 54865, seq 3, length 64
18:18:53.716705 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 54865, seq 3, length 64

ちゃんと vmnet8 に接続されている仮想マシンとのパケットが拾えています。tcpdump は何かしらの方法で vmnet8 を覗けているようです。

なにかオプションを指定する必要があるかもしれないので、マニュアルを見てみると・・・

On Darwin systems version 13 or later, when the interface is unspecified, tcpdump will use a pseudo interface to capture packets on a set of interfaces determined by the kernel (excludes by default loopback and tunnel interfaces).

Alternatively, to capture on more than one interface at a time, one may use "pktap" as the interface parameter followed by an optional list of comma separated interface names to include. For example, to capture on the loopback and en0 interface:

       tcpdump -i pktap,lo0,en0

-i オプションの項目に、ずばり答えが書いてありました...^^;

・OSX(Darwin)の場合には、-i オプションを指定しないと「擬似インタフェース」を使用して全インタフェースからキャプチャする
・任意の複数のインタフェースでキャプチャする場合には -i pktap,lo0,en0 と指定する

どうやら pktap というのが「擬似インタフェース」で、これを使用すれば vmnet8 のパケットがキャプチャできそうなので、早速試してみます。

$ sudo tcpdump -i pktap,vmnet8
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap,vmnet8, link-type PKTAP (Packet Tap), capture size 65535 bytes
18:48:35.799610 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 56686, seq 13, length 64
18:48:35.799679 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 56686, seq 13, length 64
18:48:36.802213 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 56686, seq 14, length 64
18:48:36.802252 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 56686, seq 14, length 64
18:48:37.803839 IP 192.168.2.128 > 192.168.2.1: ICMP echo request, id 56686, seq 15, length 64
18:48:37.803876 IP 192.168.2.1 > 192.168.2.128: ICMP echo reply, id 56686, seq 15, length 64

ばっちりキャプチャできました!

tcpdump はどんな裏技を使っているのか

tcpdump がどんな処理をしているのかソースを解析してみます。

とりあえず、カレントバージョンの tcpdump と libpcap のソースを grep みるも「pktap」や「PKTAP」というキーワードが見当たらない。。。

仕方ないので、tcpdumpのバージョンを確認してみると

$ tcpdump -h
tcpdump version 4.3.0 -- Apple version 56
libpcap version 1.3.0 - Apple version 41
Usage: tcpdump [-aAbdDefhHgIJkKlLnNOpPqQ:RStuUvxX] [ -B size ] [ -c count ]
		[ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ]
		[ -i interface ] [ -j tstamptype ] [ -M secret ]
		[ -Q metadata-filter-expression ]
		[ -r file ] [ -s snaplen ] [ -T type ] [ -w file ]
		[ -W filecount ] [ -y datalinktype ] [ -z command ]
		[ -Z user ] [ expression ]

ほほう、Apple version ですとな。カスタムバージョンのようなので、http://www.opensource.apple.com/source/ から tcpdump と libpcap を探します。

この中をひたすら漁っていると、libpcap の中に「pcap-darwin.c」という、いかにもそれっぽいソースファイルが見つかりました。

pcap_setup_pktap_interface() や pcap_cleanup_pktap_interface() などの関数があるので、これで当たりのようです。

ざっとソースを眺めて、以下のことが分かりました。

  • ioctl(SIOCIFCREATE) :PKTAPインタフェースを作成
  • ioctl(SIOCSDRVSPEC) :PKTAPインタフェースのフィルタを設定(キャプチャするデバイスと除外するデバイスを設定できる)
  • ioctl(SIOCIFDESTROY):PKTAPインタフェースを削除

そして、一番の知りたかった BPF で BIOCSETIF する際の処理では、実デバイスではなくPKTAPインタフェースを指定していました。

なるほど、謎は全て解けた!

自力でやってみる

なにをすれば良いか分かったので、早速自分でコードを書いてみました。

https://github.com/pandax381/pktap_demo/

  • PKTAPインタフェースを作成
  • 引数で指定されたデバイスをキャプチャ対象としてフィルタを設定(複数可、指定がなければ全てのデバイスが対象)
  • BPFデバイスをオープンしてPKTAPインタフェース(pktapX)を指定
  • 明示的にプロミスキャスモードにはしない(BIOCPROMISC が失敗する)
  • BPFからパケットをキャプチャしてデバッグ出力

PKTAPインタフェースから取得したパケットには、struct pktap_header型のリンクヘッダが付いているので、詳細出力します。

※ /usr/include/ 配下に net/pktap.h が見当たらなかったので、XNU のソースツリーから拝借するようにしています

実行結果はこんな感じ

$ sudo ./pktap_demo vmnet8
### pktap_debug_print ###
pth_length: 108
pth_type_next: 1
pth_dlt: 1
pth_ifname: vmnet8
pth_flags: 1
pth_protocol_family: 2
pth_frame_pre_length: 14
pth_frame_post_length: 0
pth_pid: -1
pth_comm: 
pth_svc: 0
pth_iftype: 6
pth_ifunit: 8
pth_epid: -1
pth_ecomm: 
+------+-------------------------------------------------+------------------+
| 0000 | 00 50 56 c0 00 08 00 0c 29 6b b6 e9 08 00 45 00 | .PV.....)k....E. |
| 0010 | 00 54 00 00 40 00 40 01 b4 d7 c0 a8 02 80 c0 a8 | .T..@.@......... |
| 0020 | 02 01 08 00 ac fe ec 31 00 01 1e 14 9b 52 00 00 | .......1.....R.. |
| 0030 | 00 00 e5 94 01 00 00 00 00 00 10 11 12 13 14 15 | ................ |
| 0040 | 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 | .......... !"#$% |
| 0050 | 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 | &'()*+,-./012345 |
| 0060 | 36 37                                           | 67               |
+------+-------------------------------------------------+------------------+
### pktap_debug_print ###
pth_length: 108
pth_type_next: 1
pth_dlt: 1
pth_ifname: vmnet8
pth_flags: 2
pth_protocol_family: 2
pth_frame_pre_length: 14
pth_frame_post_length: 0
pth_pid: -1
pth_comm: 
pth_svc: 0
pth_iftype: 6
pth_ifunit: 8
pth_epid: -1
pth_ecomm: 
+------+-------------------------------------------------+------------------+
| 0000 | 00 0c 29 6b b6 e9 00 50 56 c0 00 08 08 00 45 00 | ..)k...PV.....E. |
| 0010 | 00 54 b5 a8 40 00 40 01 ff 2e c0 a8 02 01 c0 a8 | .T..@.@......... |
| 0020 | 02 80 00 00 b4 fe ec 31 00 01 1e 14 9b 52 00 00 | .......1.....R.. |
| 0030 | 00 00 e5 94 01 00 00 00 00 00 10 11 12 13 14 15 | ................ |
| 0040 | 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 | .......... !"#$% |
| 0050 | 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 | &'()*+,-./012345 |
| 0060 | 36 37                                           | 67               |
+------+-------------------------------------------------+------------------+

余談

Google先生に聞いても、唯一ヒットする情報は Apple先生のソースコードだけだったので、意地になって動くコードを書く所までやってみました。社内でドヤってみたけど、レイヤが低すぎたのか誰も反応してくれないのでブログを書いてみたという落ちです。後悔はしていない。

なお、PKTAP は ifconfig でも作成できます。pktap_demo のコード書き終わってから ifconfig のコードを読んでいて発見しました...orz

$ sudo ifconfig pktap create
pktap0
$ sudo ifconfig pktap destroy

コマンドで PKTAP にフィルタを設定するには、pktapctl を自分でコンパイルすればできます。

http://opensource.apple.com/source/network_cmds/network_cmds-433/pktapctl/pktapctl.c

あと、そもそもの目的は vmnet に対して出力もしたかったのですが、PKTAPだとキャプチャ専用で出力はできないっぽいです。


@pandax381

klab_gijutsu2 at 20:47│Comments(0)TrackBack(0)network | mac

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント