パスワードを忘れた? アカウント作成
1091001 journal
日記

miyuriの日記: paddingを考慮しない 4

日記 by miyuri

この辺の流れ。

例えば、
struct ip_header;
struct tcp_header;
っていうのがあるとして、IPヘッダ長は20[octet]固定の場合で。

struct ip_header *ip_packet;
struct tcp_header *tcp_packet = (struct tcp_header *)(ip_packet+sizeof (struct ip_header));
って感じにしてしまうって事なのかな。

ヘッダ+データの場合、大抵はデータ位置を指す値をヘッダに含んでいるから、こういう書き方をしないよね。
okky氏の『padding だけでも結構頭痛が』っていうのは、どういう事なのだろう。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • by manmos (29892) on 2011年12月21日 17時34分 (#2069794) 日記

    structureのサイズは、環境によって変わるからそのコードは(v6ヘッダの様にサイズ固定でも)危険。

    #ip_packetはunsigned char *で定義されてないと、400バイトくらい先をみちゃいますよお

  • IP header は長いから例として不適切なのだが…例えば、

    struct _ip_header {
        __uint8       ver_ihl;   /* 面倒なので4bitは今回相手にしない */
        __uint8       type_of_service;
        __uint16     total_length;
     
        __uint16     identification;
        __uint16     flags_and_fragmentoffset; /* ここもそうだな */
     
        __uint8       time_to_live;
        __uint8       protocol;
        __uint16     header_checksum;
     
        __uint32     source_ip;
     
        __uint32     dest_ip;
    };
    typedef struct _ip_header ip_header;

    話を簡単にするために、上記の部分だけに限定する。

    よくあるのが、Ethernet の ペイロードの先頭へのポインタを p とした時に、

        ip_header     *iphP = (ip_header *)p;

    のように型キャストして、それ以降各要素を

      iphP->source_ip;

    のようにアクセスする、というコードだ。ちょっとふるい目のネットワークプログラミング関係の本には、こうしろ、と出ている。

    これが正しいためには sizeof( ip_header ) が 20byte でなくちゃいけない。つまり上記の構造体は padding が一切入っちゃいけないのだ。ところが、2^n alignment …それも 32bit alignment にすると非常に座りが良いCPU…大抵のRISC CPUはそうだが…用のコンパイラは、ちゃんとオプションを指定しないと、実体としてこうなる。

    struct _ip_header {
        __uint8       ver_ihl;   /* 面倒なので4bitは今回相手にしない */
        __uint8       dummy1[3];
     
        __uint8       type_of_service;
        __uint8       dummy2[3]
     
        __uint16     total_length;
        __uint8       dummy3[2];
     
        __uint16     identification;
        __uint8       dummy4[2];
     
        __uint16     flags_and_fragmentoffset; /* ここもそうだな */
        __uint8       dummy5[2];
     
        __uint8       time_to_live;
        __uint8       dummy6[3];
     
        __uint8       protocol;
        __uint8       dummy7[3];
     
        __uint16     header_checksum;
        __uint8       dummy8[2];
     
        __uint32     source_ip;
     
        __uint32     dest_ip;
    };
    typedef struct _ip_header ip_header;

    sizeof( ip_header )は 40byte にもなる。各変数の後ろに来る alignment 用 padding が悪さをしまくる。例えば

      iphP->source_ip;

    の結果は、本来の場所よりも 20byte ほど後ろを指す事になる。
    # もっと贅沢なCPU用コンパイラになると、全体を64byteの倍数に合わせようとしやがって…

    判ると思いますが、一般には alignment をきちんと合わせたIOはCPUからみて高速になります。なので、特に指示がないなら、padding を入れるのは妥当。本当は構造体から数字を1つづつ取り出して、1byteづつ unsigned char の配列へ代入していく/取り出して構造体のメンバーに代入していくのが、C言語における唯一確実なコードなのです。遅すぎないか…という噂もありますが、実は言うほど遅くなかったりもします。コード的にはすごく「いらいらする」コードになりますが。

    で、移植性を多少犠牲にしてでも…となってしまうのですが、そのコードを後で
    「まさにお前らが切り捨てた移植性問題を抱えた環境」
    に移植する側の身にもなってみやがれ!! という状態に陥るのです。なにしろ、プログラミング言語レベルでは一切のバグがないまま、構造体のサイズに対する仮定が崩れるので、どの構造体がどうずれていて、それは正しいのか間違っているのかさっぱり…

    上記はそれでも一応 IP header なので何とかなりますが、独自プロトコルの独自ヘッダーなど持って来られた日には、実は大元のコードが作られていたコンパイラの癖のせいでじつは padding が入っているのが正しいだとかいうことに… orz

    --
    fjの教祖様
typodupeerror

計算機科学者とは、壊れていないものを修理する人々のことである

読み込み中...