totofugaのブログ

ネットワークとかc言語、perlの話。

linuxカーネルのIPフラグメント処理(1)

IPフラグメント処理

第一フラグメント便乗攻撃を調べるためlinuxのIPフラグメント処理を調べてみた。

長くなりそうなのでとりあえずinet_fragmentのメモ

ソースコード

  • net/ipv4/ip_fragment
  • net/ipv4/inet_fragment

inet_fragmentはプロトコルに依存しない処理を実装して、 ip_fragmentはipプロトコルに関わる処理を実装している

inet_fragment

ネットワーク層プロトコルに依存しないフラグメント処理をしている。 主にやっていることは下記の3つ

  • フラグメント可されたパケットの管理
  • タイムアウト処理
  • リミット処理

procによるパラメータの設定

ipv4の場合、タイムアウト処理とリミット処理の閾値は下記proc経由で設定可能

  • /proc/sys/net/ipv4/ipfrag_time (フラグメント可されたパケットを最初に受信してから有効な秒数)
  • /proc/sys/net/ipv4/ipfrag_high_thresh (メモリ使用量がこの値を超えるとフラグメント可されたパケットを受信できなくなる)
  • /proc/sys/net/ipv4/ipfrag_low_thresh (メモリ使用量がこの値を超えると値を下回るまで削除が始まる)

カーネル内部でパラメータの格納はnetns_fragsで行われる

struct netns_frags {
    struct percpu_counter   mem; // 現在使用しているメモリ量
    int         timeout; // フラグメントのタイムアウト
    int         high_thresh; // 削除を開始するメモリ上限
    int         low_thresh; // 処理を中止するメモリ上限
}

この値はネームスペース毎に保持してあり、プロセスからアクセスするには

task_struct->nsproxy->net_ns->ipv4->frags

となる

フラグメント可されたパケットの管理

フラグメント可されたパケットはinet_fragsのハッシュで管理されており、値はinet_frag_queueとなる プロトコルに関わるところは抽象化されている 主なパラメータは下記の通り

struct inet_frag_queue {
    struct timer_list   timer; // タイムアウトの設定
    struct hlist_node   list; // ハッシュリスト
    struct sk_buff      *fragments; // 同一パケット内のフラグメント可されたパケットのリスト
    struct sk_buff      *fragments_tail; // fragmentsの最後の要素
    int         len;   // 現在格納されているパケットの最終位置
    int         meat;   // 現在格納されてるパケットのバイト数 (最後のパケットが格納されていてlen == meatになると全て格納されたことになる)
    __u8            flags; // 下で説明
    struct netns_frags  *net; // 対象名前空間の総計と制限値へのポインタ
};  

struct inet_frags {
    struct inet_frag_bucket hash[INETFRAGS_HASHSZ];
    unsigned int        (*hashfn)(const struct inet_frag_queue *); // プロトコル毎のハッシュ値の計算 (必須)
    bool            (*match)(const struct inet_frag_queue *q, const void *arg); // プロトコル毎の値比較(必須)
    void            (*destructor)(struct inet_frag_queue *); // プロトコル固有の情報削除用フック(任意)
    void            (*skb_free)(struct sk_buff *); // sk_buff削除時のフック(任意)
    void            (*frag_expire)(unsigned long data); // タイムアウトまたはメモリ上限が超えて削除される時のフック(必須)
    struct kmem_cache   *frags_cachep; // inet_frag_initで設定される名前がfrags_cache_nameでサイズがqsizeのスラブキャッシュ
    const char      *frags_cache_name; // スラブキャッシュに使用される名前(必須)
    int         qsize;  // プロトコル固有データとinet_frag_queueデータの合計サイズ(必須)
}

構造体自体は使用する側で確保して、必須となっているパラメータの設定を行い、

inet_frag_init

を呼ぶことにより使用可能となる。 inet_frag_init内ではhashの初期化とfrag_cache_name、qsizeからスラブハッシュを構築する

タイムアウト処理

IPフラグメントは各セグメント毎にタイムアウトが設定されるため、 inet_frag_queueにタイマーを持っている。

作成と同時にタイマーが設定され、 タイムアウト時の呼び出しはinet_fragsに設定されたfrag_expire関数が呼ばれる

inet_frag_create
    inet_frag_alloc
        setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
    inet_flag_intern
        mod_timer(&qp->timer, jiffies + nf->timeout)

リミット処理

制限値を超えた場合は、カーネルのワークキューを使用してメモリの解放処理が行われている。

トリガーとなるのはソフトリミットを超えた場合で、ハードリミットを超えると処理を中断する。 ワーカースレッドが実行する関数は

inet_frag_worker

で、ソフトリミットを超えなくなるまでハッシュの要素を削除する。

expireが呼ばれる時のタイムアウトとリミット処理判断

expireはタイムアウトとリミット処理の両方で、リミットを超えて削除される場合は

INET_FRAG_EVICTED

フラグが設定される。 呼び出し元で判断を行いたい場合はこのフラグを参照する。

参考にさててもらったサイト

RFC 791