Hatena::ブログ(Diary)

めもめも

2012-02-08

FORWARDチェーンによるフィルタリングとルーティング処理のタイミング


前置き

プロのためのLinuxシステム・ネットワーク管理技術の第2章(iptablesの解説)に、


・FORWARDチェーンによるフィルタリング処理の前後にパケットのルーティング処理が入る


との記述があるのですが、


FORWARDチェーンの後にルーティング処理がありません


とのご指摘をいただきました。


結論を言うと、私の勘違いで、ご指摘の通り、ルーティング処理はFORWARDチェーンに入る前(PREROUTINGチェーンの後)にすべて完了しており、FORWARDチェーンを抜けたパケットは、そのまま、POSTROUTINGチェーンに送り込まれます。


お詫びを兼ねて該当部分のカーネルソースを解説しておきます。m(_ _)m


#書籍の方は、2刷のタイミングで修正するようにします。



前提知識

まず、前提知識を2つほど。


Linux KernelのL2/L3スタックが扱う個々のパケット情報は、基本的にすべて、構造体「sk_buff *skb」に押し込まれています。L2/L3スタックは、個々のパケットについて、この構造体をいじり回していくことで、パケット送受信に伴う処理を実施してきます。


iptables(netfilter)のフィルタリング処理は、NF_HOOK()マクロから呼び出されます。



受信パケットが転送されるまで

外部から受信したパケットのsk_buffは、まずip_rcv()で処理されます。


net/ipv4/ip_input.c

/*
 *      Main IP Receive routine.
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
・・・

        return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
                       ip_rcv_finish);

ip_rcv()は、パケットに対するSanity Checkを行います。最後に、NF_HOOK()でPREROUTINGチェーンによるフィルタリングを行なってから、ip_rcv_finish()を呼びます。


net/ipv4/ip_input.c

static int ip_rcv_finish(struct sk_buff *skb)
{
・・・
        if (skb_dst(skb) == NULL) {
                int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
                                         skb->dev);

・・・
        return dst_input(skb);

ip_rcv_finish()は、ip_route_input()を呼んで該当パケットのルーティング処理を行います。具体的には、ルーティングテーブルを参照して、次の行き先に関する情報をskbにセットします。特に、ローカル受信パケット/フォワード対象パケットに応じて、次に呼ぶ関数を決定して、skb.dst.inputにそのポインタを格納します。その後、dst_input()を呼びます。


skb.dst.inputに次の行き先を格納する部分は、少し長くなるので、コールフローだけ記載しておきます。フォワード対象パケットの場合は、


net/ipv4/route.c

・ip_route_input() -> ip_mkroute_input() -> __mkroute_input()


と流れて、


net/ipv4/route.c

rth->u.dst.input = ip_forward;
rth->u.dst.output = ip_output;

に行きます。dst.outputに入ったip_output()はもう少し後で登場します。


ローカル受信パケットの場合は、


net/ipv4/route.c

・ip_route_input() -> ip_route_input_slow()


と流れて、


net/ipv4/route.c

rth->u.dst.input= ip_local_deliver;

に行きます。


続いて、dst_input()で、実際にセットした関数が呼び出されます。


/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
        return skb_dst(skb)->input(skb);
}


フォワード処理の場合のip_forward()を見ておきます。


net/ipv4/ip_forward.c

int ip_forward(struct sk_buff *skb)
{
・・・
        return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
                       ip_forward_finish);

ここで、NF_HOOK()により、FORWARDチェーンのフィルタリング処理が入って、その後、ip_forward_finish()からdst_output()へと行きます。


net/ipv4/ip_forward.c

static int ip_forward_finish(struct sk_buff *skb)
{
・・・
        return dst_output(skb);
}

include/net/dst.h

static inline int dst_output(struct sk_buff *skb)
{
        return skb_dst(skb)->output(skb);
}

ここで、先に仕込んだ、


rth->u.dst.output = ip_output;

にしたがって、ip_output()へと行きます。


net/ipv4/ip_output.c

int ip_output(struct sk_buff *skb)
{
・・・
        return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
                            ip_finish_output,
                            !(IPCB(skb)->flags & IPSKB_REROUTED));
}

ここからは、ローカルからの送出パケットと処理が合流しており、NF_HOOK(_COND)で、POSTROUTINGチェーンのフィルタリングへと処理が進んでいます。

AKAK 2012/02/12 15:16 もやもやしてた点がすっきりしました。
また、カーネルコードの解説ありがとうございます。
とっかかりを頂いたので読み解いてみます。
思い切ってコメントして良かったです。

元々は、Iptables Tutorialに載っているチェーンの行程の画像(FOWARDの後にルーティングがある)を信じて予想通りに動作しなかったのが始まりでした。
その後に実験して、少なくとも実動はエントリ頂いた内容でありそうだとは考えていましたが。

また、コメントする際に気づいたのですが、Iptables Tutorialにおいては、OUTPUTチェーンあたりもどうも怪しげに思います。
同サイトの表での解説(Table 6-2)、画像、また、英語Wikipedia-iptablesのチェーン行程画像がどれも一致していないようなので…。
現状の私の認識は書籍の内容と同じです(少し実験しました)。
歴史的経緯でもあるのかなと考えていますが、カーネルコードの方を解説頂いたので、現状についてはこれをとっかかりにコードレベルで解析できたら良いなと思います。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/enakai00/20120208/1328664346