前置き
プロのためのLinuxシステム・ネットワーク管理技術の第2章(iptablesの解説)に、
・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()で処理されます。
/* * 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()を呼びます。
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()
と流れて、
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()
と流れて、
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()を見ておきます。
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()へと行きます。
static int ip_forward_finish(struct sk_buff *skb) { ・・・ return dst_output(skb); }
static inline int dst_output(struct sk_buff *skb) { return skb_dst(skb)->output(skb); }
ここで、先に仕込んだ、
rth->u.dst.output = ip_output;
にしたがって、ip_output()へと行きます。
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チェーンのフィルタリングへと処理が進んでいます。