放射状

勉強のメモ。

コードリーディング:ping 四日目

これまでのあらすじ~ なんか部下の中の人が普通に上から順にソースを読んでいる。苦戦している。あと、「このpingのオプションが全部掲載されている日本語のページをぐぐっても見つかりません」とか言っている。こ、こらー! なんのためにコンパイル方法を教えたのか。
 *部下の中の人と放射氏はフィクションであり、実在の人物・団体とは一切関係ありません。そういう体裁をとっておくと記事が書きやすいのです。そういうことにしておいてください。

ソースコードの読み方(放射氏流)

勉強のための読み方なので、割と深く読み込んでいきます。さっと把握したい場合には不向きです。また、pingのように小さいプログラムだから通じる手段だと思います。
基本的に、

  • コマンドに一切オプションをつけない、デフォルトの挙動ができる最低限のところまでソースコードを削る。
  • 最低限のソースコードを読み込んで把握する
  • オプションを一つずつ戻していく

という感じです。
今回はそのソースコードの削り方を説明します。

何をどこまで削るか

centos上でpingをオプションを指定せずに実行したときとまったく同じ挙動を得るために必要なソースコード以外の部分を削ります。
↓たとえばこれが基準です。

[root@localhost ~]# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=1.00 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=1.79 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=1.23 ms

削った後で以下のようにコンパイルして、こんな感じで同じように動けばOKということです。

[root@localhost iputils-s20121221]# gcc ping.c ping_common.c
[root@localhost iputils-s20121221]# ./a.out 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=1.49 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=1.91 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=1.90 ms
その1:#ifdefとか#ifndefなところを削る

プリプロセッサ、嫌いですね。読み辛いもんね。ないと困るんだけどね。古いソースコードのメンテナンスの仕事でこれの#ifndefの嵐を見て「*ね!!!!!!!!」とか思ったりしますよね! なのでまず最初にこれを削ります。

#ifdef USE_IDN
	setlocale(LC_ALL, "");
#endif

こういうの。IDNは国際ドメイン名のことですが、そんなもん使わなくてもipを直接指定すればpingコマンドは動作するので削ります。

#ifdef CAPABILITIES
static cap_value_t cap_raw = CAP_NET_RAW;
static cap_value_t cap_admin = CAP_NET_ADMIN;
#endif

ケイパビリティーとか、いつか今度勉強しようと思うので、今回はいいです。削ります。

#ifndef WITHOUT_IFADDRS

なにやってるかよくわかんねーけど削っても動くので削ります。

という感じで、最初の慣れないうちは一つ削るたびにコンパイルして動作を確認しながらやっていくといいと思います。
たまに削ったらコンパイル通らなくなった! というのもあるので↓ そういうときは元に戻しましょう。

#ifndef ICMP_FILTER
#define ICMP_FILTER	1
struct icmp_filter {
	//__u32 : 32bit integer type
	__u32	data;
};
#endif

ここまで削ったソースコードここにサンプルとしておいてきます。

その2:オプションに関係するソースコードを削る

オプションまで把握するなんて最初は難しいので削ります。

int
main(int argc, char **argv)

とあるので、「argv」とかで検索してそれっぽいところを削ります。また、ping_common.hで「options」といういかにもな変数がexternされているので、それも検索してそれっぽいところを削ります。
例えば、

while ((ch = getopt(argc, argv, COMMON_OPTSTR "bRT:")) != EOF) {

いかにもオプションの処理しかしてないので削ります。

if ((options&F_STRICTSOURCE) &&
    bind(icmp_sock, (struct sockaddr*)&source, sizeof(source)) == -1) {
	perror("bind");
	exit(2);
}

削ります。

if (device || (options&F_STRICTSOURCE))
	printf("from %s %s: ", inet_ntoa(source.sin_addr), device ?: "");

これはだめ。

#ifdefのときと同じように、最初は一回ずつコンパイルして動作確認するといいと思います。変なところを消すと改行が消えたりIPアドレスが表示されなくなったりします。
ここまで削ったものをここに置いておきます。

その3:これぐらいでよいのではないでしょうか

ここまで削ったらソースコードの分量はかなり減ったはずです。mainも読みやすくなったはずです。
次回はそこからの読み方について書きます。

「man ping」のオプションの部分ぐらいは日本語訳して書いておこうと思ったけれど

あと15分でぷよクエのガチャピン祭りボーナスタイムが終わってしまうので、次回にします。
英語読めないとオープンソース系は詰むので読めるようになってください。>部下の中の人 人のことが言えるほど自分も読めるわけではないですが。

SICP五日目~ 問題 1.8

schemeとF#を平行にやっているとじんわりと胸に広がる、「やっぱり副作用がないHaskellやっとかないとだめなんじゃ…」疑惑。でも「そんなに言語ばっかりやってどうすんの」疑惑もあり。もっと勉強するべきことがあるのでは(Hadoopとか)(もっと基本的な数学とか)疑惑もあり。
迷ってばかりですが、とりあえず手を動かします。

問題1.8

問題はここから平方根の章もようやく最後です。今回は簡単でした。
問題1.7で改善したプログラムの、average関数を立方根の近似値を求めるapproximate関数に置き換えるなどしました。

(use slib)
(require 'trace)

(define (cube x) (* x (square x)))

(define (cubeRootRec guess x)
  (if (good-enough? guess x)
      guess
      (cubeRootRec (improve guess x) x)
  ))

(define (good-enough? guess x)
  (if (< (abs (- (improve guess x) guess)) 0.0001)
     #t
    (< (abs (- (cube guess) x)) 0.001)
    )      
  )

(define (improve guess x)
  (approximate x guess)
  )

;近似値を求める計算
(define (approximate x y) 
  (/ (+ (/ x (square y)) (* 2 y)) 3) 
)

(define (cubeRoot x)
  (cubeRootRec 1.0 x)
  )

;トレース指定
(trace cubeRootRec)
;実行
(cubeRoot 3.0)

これを実行すると、

gosh> CALL cubeRootRec 1.0 3.0
  CALL cubeRootRec 1.6666666666666667 3.0
    CALL cubeRootRec 1.471111111111111 3.0
      CALL cubeRootRec 1.442812098249343 3.0
        CALL cubeRootRec 1.4422497895989996 3.0
        RETN cubeRootRec 1.4422497895989996
      RETN cubeRootRec 1.4422497895989996
    RETN cubeRootRec 1.4422497895989996
  RETN cubeRootRec 1.4422497895989996
RETN cubeRootRec 1.4422497895989996
1.4422497895989996

良い感じに動いているようですね!

ここまでやってみて

lispはシンプルできれいで強力で触っていて非常に楽しいのですが、括弧の数をとにかく間違えます。驚くほど間違えます。そしてGaucheに怒られます。
記載されているソースコードのインデントや改行のタイミングにも注意してSICPを読み込まないとだめですね。

コードリーディング:ping 三日目

これまでのあらすじ~ 放射氏は知らなかった。部下の中の人がICMPプロトコルについてそもそも知らないという事実を~

ICMP

wikipediaの記事ならここです。

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

もっともベタな入門書はこれです。もう5版が最新なのかー。うちには2版と3版しかないな。

パケットキャプチャ

コードを読みながら、変えながら、実際のパケットも見てみたいかと思います。windowsでのパケットキャプチャリングは煩雑なので(1分ぐらい調べてブラウザを閉じた)、linux上でキャプチャします。ほら、読むpingのコードもcentos7のですし。ぬるいプログラマは時としてwindowsを嫌うこともある。

インストール
yum install tcpdump
実行

細かいオプションは丁寧に解説して下さっているサイトがたくさんありますので、そちらに譲りたいと思います。

[root@localhost ~]# tcpdump icmp -X
実行結果

localから外部へping

21:15:41.752059 IP localhost.localdomain > 192.168.1.5: ICMP echo request, id 11918, seq 1, length 64
        0x0000:  4500 0054 44ae 4000 4001 71dd c0a8 01c8  E..TD.@.@.q.....
        0x0010:  c0a8 0105 0800 6146 2e8e 0001 6d88 9255  ......aF....m..U
        0x0020:  0000 0000 9e79 0b00 0000 0000 1011 1213  .....y..........
        0x0030:  1415 1617 1819 1a1b 1c1d 1e1f 2021 2223  .............!"#
        0x0040:  2425 2627 2829 2a2b 2c2d 2e2f 3031 3233  $%&'()*+,-./0123
        0x0050:  3435 3637                                4567
21:15:41.752343 IP 192.168.1.5 > localhost.localdomain: ICMP echo reply, id 11918, seq 1, length 64
        0x0000:  4500 0054 3d05 0000 8001 7986 c0a8 0105  E..T=.....y.....
        0x0010:  c0a8 01c8 0000 6946 2e8e 0001 6d88 9255  ......iF....m..U
        0x0020:  0000 0000 9e79 0b00 0000 0000 1011 1213  .....y..........
        0x0030:  1415 1617 1819 1a1b 1c1d 1e1f 2021 2223  .............!"#
        0x0040:  2425 2627 2829 2a2b 2c2d 2e2f 3031 3233  $%&'()*+,-./0123
        0x0050:  3435 3637

外部からlocalにping

21:16:26.210471 IP 192.168.1.5 > localhost.localdomain: ICMP echo request, id 1, seq 25, length 40
        0x0000:  4500 003c 3d30 0000 8001 7973 c0a8 0105  E..<=0....ys....
        0x0010:  c0a8 01c8 0800 4d42 0001 0019 6162 6364  ......MB....abcd
        0x0020:  6566 6768 696a 6b6c 6d6e 6f70 7172 7374  efghijklmnopqrst
        0x0030:  7576 7761 6263 6465 6667 6869            uvwabcdefghi
21:16:26.210527 IP localhost.localdomain > 192.168.1.5: ICMP echo reply, id 1, seq 25, length 40
        0x0000:  4500 003c 44b3 0000 4001 b1f0 c0a8 01c8  E..<D...@.......
        0x0010:  c0a8 0105 0000 5542 0001 0019 6162 6364  ......UB....abcd
        0x0020:  6566 6768 696a 6b6c 6d6e 6f70 7172 7374  efghijklmnopqrst
        0x0030:  7576 7761 6263 6465 6667 6869            uvwabcdefghi

というわけで、これを見て強く生きてほしい。

ほかの素晴らしい参考書

基礎からわかるTCP/IP アナライザ作成とパケット解析―Linux/FreeBSD対応

基礎からわかるTCP/IP アナライザ作成とパケット解析―Linux/FreeBSD対応

パケットキャプチャそのものを実装する手段が解説されています。
かなりの良書ですので、一読の価値、というか一通り読みながら自分で実装してみる価値はあると思います。ちなみについ先日自宅の本棚から発掘されたため、私はまだやっていません。今度やります。

しかしまあ

なかなかソースコードリーディングそのものまでたどりつきませんね! でもこうやって枝を辿っていくのも勉強になります。