libpcap and pkttools

Raspberry Pi 4

これからずっとWindows10を進化させて行くよと言ったのは誰だ? どうやら、その約束が破られて、Windows11が出るらしい。Upgradeかと思っていたら、どうもそうじゃ無いらしい。Hardが新しくないと駄目みたい。詳細はじきに発表されるみたいだから、それが出てからでもいいんだけど、MicroSoftとIntelの陰謀ですなあ。

10年ぶりのアーキテクチャ刷新、「Armv9」で何が変わる? こういうのを見るまでも無く、ネーミングは営業戦略によって決定される。驕る平家は久しからずでIntelさん大変。アプルからは離婚されるし、スマホの石には喰い込めないし、IoT向けの石にしても、しかり。

もうすがる先は、パソコンしかありません。そこでMicrosoftに泣きついた。Windows10 22H2あたりの奴のネーミングを変えて。ただ変えるんじゃ意味ないから、過去のハードをばっさり切り捨てて。そうすれば、大手を振って、Windows11に出来るから。これでパソコン用の石が売れるな。そしてその頃までに、製造ラインの逼迫も収まっているだろう。

そうそう、ただハードを一新させようと企むと、ユーザーの総スカンを喰らう恐れがある。アプル対抗で、セキュリティーをハードで守りますって宣伝しとけ。

もう、こういう連中には付き合いきれないぞ。ソフトは兎も角として、hardが問題。アプルさんもARMに舵を切ったんで、今後はARMでしょう。ならば、ラズパイだな。久しぶりにトレースしてみるか。

Raspberry Pi4 のスターターキット

売れてるね。メモリーが4G版と8G版が有るとな。選ぶならCPUスピードうんぬんよりメモリーをどれだけ積んでるかを重視したい。

デスクトップPCとして使うRaspberry Pi のキモはSSDドライブ

TVに繋げて、脇にでも置いておく。キーボードとマウスは、無線接続だな。Bluetoothと2.4Gが有るのか。

OSなウブは重いので絶対に避ける事だな。とか、妄想してますよ。

tmux 画面分割

emacsで画面を分割し、その画面間を移動する技を、前回調べた。同じ事をtmuxも出来るに違い無いと思って、チートシートを少々参照させてもらった。画面の事をペインと言うんだそうな。そんな専門用語知らなかったぞ。

%            左右にペイン分割
"            上下にペイン分割
q            ペイン番号を表示
カーソル      指定方向のペインへ移動 ※連続押しでプレフィックス継続
!            ペインを解除してウインドウ化
x            ペインの破棄
;            以前のペインへ移動
z            現在のペインを最大化/復帰
スペース      レイアウトを変更
Alt-1-5      レイウトを変更

matzさんのDeskTopは、terminalが2つとemacs、それにChomeだそうだ。ブラウザーは兎も角として、一杯に拡げたtarminalを3ペインに分割出来れば、彼っぽくなるな。

pkttools

libpcapがらみでggしてたら、 簡易パケット操作ツール群(pkttools) なんてのに出会った。面白そうだから、手を出してみるか。

at OpenBSD

コンパイルを始めたら、いきなりethernet.hが無いんですけどと言われたぞ。こういう時は、向こう三軒両隣のFreeBSDから借りてくればいいだろう(多分)。で、今度は

cc -O -Wall -g      bpf.c -c -o bpf.o
bpf.c:134:19: error: use of undeclared identifier 'BIOCSSEESENT'
    if (ioctl(fd, BIOCSSEESENT, &val) < 0)
                  ^
1 error generated.
 *** Error 1 in /tmp/pkttools-1.16 (Makefile:71 'bpf.o')

こういう時も親戚のFreeBSDに相談 bpf(4)

BIOCSSEESENT

BIOCGSEESENT    (u_int) These commands are obsolete but left for
                compatibility.  Use BIOCSDIRECTION and BIOCGDIRECTION
                instead.  Sets or gets the flag determining whether
                locally generated packets on the interface should be
                returned by BPF.  Set to zero to see only incoming
                packets on the interface.  Set to one to see packets
                originating locally and remotely on the interface.  This
                flag is initialized to one by default.

別名でもいいのかな。どれを試しても、そんなの無いエラー。もう、諦めて、コメントアウト。 それで、無事にコンパイル完了。

ob$ ./pkt-recv -i em0
Cannot open bpf.
ob$ ls -l /dev/bpf*
crw-------  1 root  wheel   23,   0 May  1 15:23 /dev/bpf
crw-------  1 root  wheel   23,   0 May  1 15:23 /dev/bpf0

bpfを直接アクセスしてるみたいだけど、権利無し。/dev/bpfに権利を付けてあげたら、無事にパケットを受け取ってくれた。

ob$ ./pkt-recv -i em0 -a TCP.SRC_PORT==8080
LINKTYPE: 1 (Ethernet)
-- 1 --
TIME: 1624170533.826411 Sun Jun 20 15:28:53 2021
SIZE: 78/78
000000: 00 0C 29 4D  18 2D 00 0C  29 59 E8 BA  08 00 45 00 : ..)M.-.. )Y....E.
000010: 00 40 1B 64  40 00 40 06  BF 01 C0 A8  6F 80 C0 A8 : .@.d@.@. ....o...
 :
-- 2 --
TIME: 1624170533.833583 Sun Jun 20 15:28:53 2021
SIZE: 220/220
000000: 00 0C 29 4D  18 2D 00 0C  29 59 E8 BA  08 00 45 00 : ..)M.-.. )Y....E.
000010: 00 CE 00 12  40 00 40 06  D9 C5 C0 A8  6F 80 C0 A8 : ....@.@. ....o...
000020: 6F 81 1F 90  E0 76 01 BF  94 B8 BE AA  1E 22 80 18 : o....v.. ....."..
000030: 01 0F 61 13  00 00 01 01  08 0A 02 22  EE 83 4F 06 : ..a..... ..."..O.
000040: 1A 0C 48 54  54 50 2F 31  2E 30 20 32  30 30 20 4F : ..HTTP/1 .0 200 O
 :
ETHERNET.DST_ADDR:      00:0c:29:4d:18:2d
ETHERNET.SRC_ADDR:      00:0c:29:59:e8:ba
ETHERNET.TYPE:          0x800
IP.VERSION:             0x4
IP.HEADER_SIZE:         0x14
IP.TOS:                 0x0
IP.TOTAL_SIZE:          0xce
IP.ID:                  0x11eb
IP.FRAGMENT:            0x4000
IP.TTL:                 0x40
IP.PROTOCOL:            0x6
 :

簡易的なフィルタリングが出来る。-aを付けておくと、パケットを解析して表示してくれる。

見どころ

READMEマニュアルにざっと目を通す。BSD系はパケットキャプチャにBPFを使ってるよ。Linux系は素のsocketを使ってるよ。Windows系は、libpcap 系だよと説明が有った。

ソースをザッピングしたら、lib.cに面白い記述を発見。

struct pkt_handler pkthandler = {
#ifdef USE_LIBPCAP
  libpcap_open_recv, libpcap_open_send,
  libpcap_get_linktype,
  libpcap_recv, libpcap_send, libpcap_close,
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || 
  defined(__DragonFly__) || defined(__APPLE__)
  bpf_open_recv, bpf_open_send,
  bpf_get_linktype,
  bpf_recv, bpf_send, bpf_close,
#elif defined(__linux__)
  rawsock_open_recv, rawsock_open_send,
  rawsock_get_linktype,
  rawsock_recv, rawsock_send, rawsock_close,
#else
#warning Unknown plathome. Cannot use pkt-recv/pkt-send.
  NULL, NULL, NULL, NULL, NULL, NULL,
#endif
};

lib.h

struct pkt_handler {
  pktif_t (*open_recv)(char *ifname, unsigned long flags, int *bufsizep);
  pktif_t (*open_send)(char *ifname, unsigned long flags);
  int (*get_linktype)(pktif_t pktif);
  int (*recv)(pktif_t pktif, char *buffer, int size, int *linktypep,
              int *origsizep, struct timeval *tm);
  int (*send)(pktif_t pktif, char *buffer, int size, int linktype,
              int origsize, struct timeval *tm);
  int (*close)(pktif_t pktif);
};

Makefileの冒頭付近の設定を変更してから、再コンパイルして libpkt.a を含めて作り直すと、色々試せるな。

libpcap and BPF

前回集めた資料の中に、BPFはカーネル側、libpcapはユーザーランド側って説明が有った。 今回のpkttoolsを動かすのに/dev/bpfのパーミションをユーザーでもR/W出来るようにしとけってのが出てた。

確かにこの設定をすると、これまた前回のmypc.cが、rootの権限無しでも動く。mypcはlibpcapしか使っていない。そのlibpcapが/dev/bpfをアクセスしてるので、こういう特権を引き継げるのだな。

但し、同じlibpcapを使っているtcpdumpでは、そうは問屋が卸せない。tcpdump自身がガチガチにルート特権を要求してるからだ。

だって、tcpdumpは、よそ様宛のパケットも覗きみれるツールだからね。節度ある人が使ってねっていう、安全弁をかましてるんだ。

じゃ、100歩譲って、自ホスト宛のパケットだけならどうよ。その制御どうなってる?

pcap_handle = pcap_open_live(device, 4096, 1, 1000, errbuf);

第三引数が1だと、何でも見ちゃう。0だと自ホスト宛のみって事になってる。

/usr/src/lib/libpcap/pcap-bpf.c

pcap_open_live(const char *source, int snaplen, int promisc, int to_ms,
    char *errbuf)
{
       :
       status = pcap_set_promisc(p, promisc);

pcap.c の該当関数で p->opt.promisc = promisc; こんなになってから pcap-bpf.cの中で

pcap_activate(pcap_t *p)
        :
        if (p->opt.promisc) {
                /* set promiscuous mode, just warn if it fails */
                if (ioctl(p->fd, BIOCPROMISC, NULL) == -1) {
                        snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "BIOCPROMISC: %s",
                            pcap_strerror(errno));
                        status = PCAP_WARNING_PROMISC_NOTSUP;
                }
        }        

これって、もろにbpfの操作ですな。他にtimeoutの設定やらbufの設定もしてる。 /sys/net/bpf.c 経由でpromiscのset/clear操作が行われるんだけど、NICはブリッジとかVLANとか色々な場面に顔を出す。その元締めはif.c。その中のifpromisc関数で実際の操作が行われているみたいだ。

ちょいとNIC em0について追いかけてみると /sys/dev/pci/if_em_hw.h に、石のコントロールBITをヘッダーにしたものが見つかった。

/* Receive Control */
#define E1000_RCTL_RST            0x00000001    /* Software reset */
#define E1000_RCTL_EN             0x00000002    /* enable */
#define E1000_RCTL_SBP            0x00000004    /* store bad packet */
#define E1000_RCTL_UPE            0x00000008    /* unicast promiscuous enable */
#define E1000_RCTL_MPE            0x00000010    /* multicast promiscuous enab */
#define E1000_RCTL_LPE            0x00000020    /* long packet enable */
 :

unicast/multicast で、独立に設定出来るのね。bad packetって、checksumが合わないやつかな? そんなんでも、ハンドリングを許可出来るのね。もう一つお馴染みのfxp0についてみておく。 /sys/dev/ic/fxpreg.h

#define FXP_VENDORID_INTEL      0x8086
#define FXP_DEVICEID_i82557     0x1229

ベンダーコードが8086なんて、よく取れたな。プラチナコードですよ。いや、平家だから、威張りちらしていたのさ(俗に言う、権力を笠に着て)。

普段はパケットの一番入り口の所で、自ホスト宛のパケットだけを選り分けて通すハードなパケットフィルターが動いているんだな。これを捻じ曲げて、何でも受け取ってしまうと、カーネルが大忙しになる(可能性がある)。努々、promiscで使わないようにね。

kernel観光

上でソースをちょいとトレースした。今度は久しぶりにカーネル観光します。って、BP張って、狙った所に飛んで来るかの確認です。

(gdb) bt
#0  ifpromisc (ifp=0xd184b030, pswitch=1) at /usr/src/sys/net/if.c:2928
#1  0xd0d7c655 in bpfioctl (dev=136960, cmd=536887913, addr=0xf1cef520 "", flag=1, p=0xd17c3008) at /usr/src/sys/net/bpf.c:781
#2  0xd07ad125 in spec_ioctl (v=0xf1cef378) at /usr/src/sys/kern/spec_vnops.c:370
#3  0xd057ae88 in VOP_IOCTL (vp=0xd1680b00, command=536887913, data=0xf1cef520, fflag=1, cred=0xd17f4840, p=0xd17c3008) at /usr/src/sys/kern/vfs_vops.c:290
     :

やって着ましたねぇ。記念に、NIC関係を収めている構造体をちょい見(全部見ようとすると死ねるからね)。

(gdb) p *ifp
  :
  if_rtrequest = 0xd05ceed0 <ether_rtrequest>,
  if_xname = "em0", '\000' <repeats 12 times>,
  if_pcount = 0,
  if_bridgeidx = 0,
  if_bpf = 0xd17fc3c0 "",
  if_switchport = 0x0,
  if_mcast = 0x0,
  if_mcast6 = 0x0,
  if_pf_kif = 0xd1845200 "em0",
  :
(gdb) p pswitch
$5 = 1

qemu上の実行

vm$ doas tcpdump
doas (sakae@vm.my.domain) password:
tcpdump: listening on em0, link-type EN10MB
^C
0 packets received by filter
0 packets dropped by kernel

tcpdumpを起動した時に呼ばれてます。

(gdb) c
Continuing.

Breakpoint 1, ifpromisc (ifp=0xd184b030, pswitch=0) at /usr/src/sys/net/if.c:29\
28
2928            oif_flags = ifp->if_flags;
(gdb) p pswitch
$6 = 0

そして、tcpdumpを行儀良く停止させた時も呼ばれてます。じゃ、tcpdumpが kill -9 とかで、突然死したら? モードが残ったままになりますなあ。心配だったので、実験してみた。

結果は、突然死でも、やばいpromiscモードはクリアされてました。世に憂いを残さない設計ですなあ。更に、ifconfigからは、設定出来ない安全設計になってました。危険エリアは最小にって言う安全原則がしっかりと守られていました。流石、OpenBSD。そんじょそこらのOSとは設計思想が違うんです。

後で思い出したら、リナでも確認してみるか。FreeBSDはどうかな? 興味は尽きないぞ。

promisc モード

promiscモードに設定すると、自ホスト宛以外のパケットも盗聴出来るそうだ。本当か実証実験してみる。題材は上でも出てきたmypc.cだ。ちょいと書き換えるだけで、promiscにしたり、しなかったり出来るんで便利だ。まずは、盗聴をOpenBSD側で行ってみる。

パケット発生器はdebian側のブラウザー。これなら、OpenBSDには関係無いはず。

sakae@pen:~$ w3m http://news.yahoo.co.jp/pickup/6396646
ob$ ./a.out
device = em0
 :
packet size = 409
 00 50 56 f3 2a e1 00 0c 29 4d 18 2d 08 00 45 00| .PV.*...)M.-..E.
 01 8b 2d 36 40 00 40 06 0b fb c0 a8 6f 81 b6 16| ..-6@.@.....o...
 19 fc 8d 54 00 50 74 81 f5 30 4e d4 09 0a 50 18| ...T.Pt..0N...P.
 fa f0 fc d5 00 00 47 45 54 20 2f 70 69 63 6b 75| ......GET /picku
 70 2f 36 33 39 36 36 34 36 20 48 54 54 50 2f 31| p/6396646 HTTP/1

もろに、debian機が発したパケットを受信してる。盗聴の恐ろしさである。FBIだかCIAが、闇夜に紛れて電柱に登り、電話線に針を立てて(tapping)盗聴する時代ではない。海底ケーブルの陸揚げ地に、息のかかったハードをちょいと設置するだけ。中華製のハードは信用ならんと言っても、シスコは米製だからなあ。愛国者法とかを持ち出せば、なんとでもなる。だから、ドイツのおばちゃんの携帯を盗聴しても許される。

今度は盗聴無しモードに切り替えてみる。OpenBSDにはパケットが流れてこないので、待てど暮らせど、パケットはキャプチャ出来ない。

この覗き見を発見出来ないか?  Detection of Promiscuous Nodes Using ARP Packets こんな資料が公開されてた。

なお、今まで、自ホスト宛ってのを断りもなく使ってきた。自ホストってのはIPアドレスじゃなくて、MACアドレスの事ね。専門用語で言うと、L2の世界の事。IPの世界はL3の世界。L4の世界はTCP(UDP)な世界。

じゃ、L1の世界も有りそう。ええ、あるんですよ。物理層、電子屋の世界ね。

L1 な世界

そうです。光回線がどうとか、イーサネットケーブルがどうとか、頑張ればRS232Cのケーブルだってパケットを送れちゃうぞとか、NICの中に鎮座してる、蟹さんチップがどうのとかです。

自分でチップを設計する身になって考えてみよう。

promiscモードっては自MACアドレスとの一致だな。2ゲートのEXOR回路を48個並べて作る。ブロードアドレス検出は、2ゲートのAND回路をやはり48個並べて作る。

シリアルに流れて来るデータをパラレルデータに変換しなければならないので、シリーパラ変換器が必要。データが来たら、その旨を割り込みでOSに通知する必要もあるな。

こういったロジック部と、シリアルデータをネットに送受信するための、アナログっぽい部分が必要だな。これはよくPHYなんて言ったりする。

OpenBSDのソースが有るなら、 /sys/dev/pci/if_em* を見ればいいんだけど、無い人は、qemu/hw/net/e1000* あたりを見ると吉。両者共Intelの支援を受けている。

OpenBSDのteoさんが嘆いていた。Wi-Fiの石の仕様が公開されない事が多々あり、どうしても手探り状態で開発せざるを得ない。そんなんで、なかなか性能が出ないのよと。

slip and ppp

シリアル回線が有れば、パケットを流せる。今大流行してるのはUSBだ。スピードが版を重ねる度にアップしてって、留まる所を知らない。

シリアル回線の事なら、wiresharkに任せてねって事で、悪乗りしてUSBなパケットも観察出来るらしい。タッピングはどうやるのか、知らないけど。

昔は、 SLIPを使った実験 なんてのを行った事があるぞ。頭が硬かったから、何でRS232CラインにTCP/IPのパケットを流せるのと大いに悩んだものだ。今では、シリアル回線、ああ、パケットを流せるね、ぐらいに、すっかり世慣れた人になっちまったい。

slipと言う規約に圧縮機構を持ち込んで、cslipになり、その発展形がpppだ。今でも現役ですよ。

とまあ、昔の事を思い出した。ついでに、クロスしたLANケーブルを用意して、パソコン間を直結。これで、ファイルのやり取りとかをよくやったな。今の人だと、OneDriveとかに一度上げて、それをDLしてもらうとかの退化した無駄な事をやるんだろうね。


This year's Index

Home