読者です 読者をやめる 読者になる 読者になる

めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

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チェーンのフィルタリングへと処理が進んでいます。