Linux Kernel(2.6)の実装に関するメモ書き

IPv4 送信処理


1. 概要

IP層(IPv4)のパケット送信処理に関するメモ。

2. 処理の流れ

ソケットを使ってデータを送信する際のIP層の処理の流れを図1に示す。


図1 送信時の流れ

3. 送信処理

3.1 IPパケットの作成

UDP,RAWソケットを使用している場合は、プロトコル層は送信データをip_append_data()を使ってsk_buffに格納する。ip_append_data()はsk_buffを割り当てて、データを格納する。

送信データを格納したsk_buffを作成した後は、RAWソケットならip_push_pending_frames()を呼び出して、さきほどip_append_data()で作成したsk_buffにIPヘッダを付けてdst_output()を呼び出す。UDPの場合はudp_push_pending_frames()を呼び出して、UDPヘッダを付けた後、RAWソケットと同じくip_push_pending_frames()を呼び出す。

TCPの場合は、データの送信が必要になった場合は、TCP層からip_queue_xmit()が呼び出される。ip_queue_xmit()はIPヘッダを付けてdst_output()を呼び出す。

3.2 dst_output()

dst_output()はsk_buffに登録されている宛先(struct dst_entry)のoutputハンドラ(skb->dst->output(skb))を呼び出すマクロ。IPv4ではip_output()を呼び出すことになる。

3.3 ip_output()

ip_output()はフィルタ処理(NF_IP_POST_ROUTING)を行なった後、ip_finish_output()を呼び出す。ip_finish_output()は送信データ長をチェックしてMTUを越えるようならip_fragment()を呼び出しIPフラグメント処理を行う。MTU内に収まっていればip_finish_output2()を呼び出して、送信処理に入る。

3.4 ip_finish_output2()

ip_finish_output2()ではIPパケットを送信するため、下位層(Neighborモジュール)にsk_buffを渡す。

この後、NeighborモジュールはパケットにMACヘッダを付けてパケットを送信する(送信キューに積む)。NextHopのMACアドレスが未解決なら、アドレス解決プロトコル(IPv4ではARP)でMACアドレスを解決してからパケットが送信される。Neighbourモジュールについては「アドレス解決プロトコル」参照。

なお、Linuxではある宛先に対するMACヘッダを作った場合、それをキャッシュ(Hard Header Cache)を作成しておき、次回からはそれをコピーするようにして、ヘッダ作成の手間を省いている。ip_finish_output2()では、Neighbourに処理を渡す前にHard Header Cacheが存在するかチェックし、まだキャッシュがなければ、そのままNeighbourに処理を渡す。キャッシュがあった場合は、キャッシュからMACヘッダをコピーしてからhh->hh_output()を呼び出す。hh->hh_output()では、もう既にMACヘッダがついているので、なにもせずデータを送信キューに積む。

ip_finish_output2()の処理概要
:
if (hh) {
    /* dstにHardHeaderCacheが存在する */

    /* HardHeaderCacheからレイヤ2ヘッダをパケットにコピー */

    /* パケットを送信 */
    return hh->hh_output(skb);
} else if (dst->neighbour)   (*1)
    /* HardHeaderCacheがない場合の送信 */
    return dst->neighbour->output(skb);  (*2)

(*1) dst->neighbourはrt_intern_hash()でルーティングエントリをキャッシュする時に arp_bind_neighbour()をコールしてARPエントリを指すようにしている。ARPエントリがないときは空のエントリが作成されそこを指す。
(*2) アドレス解決プロトコル参照。ARP未解決ならこの中でアドレス解決処理が開始される。

4. その他のメモ

4.1 ip_append_data()

UDP,Rawソケットレイヤが送信データを格納したsk_buffを構築するために呼び出すip_append_data()関数。この関数は図2に示すようなsk_buffを構築することになる。


図2 ip_append_data()で構築されるデータ

UDP,Rawソケットでsendmsg()システムコールを実行すると、送信用sk_buffを構築するip_append_data()を呼び出す。最初はsk_write_queueが空なので新しくsk_buffを確保し、データを格納する(*1)。作成したsk_buffはsk_write_queueに入れられる。また、IPパケットのサイズがMTU長を越えてIPフラグメントが必要な場合は、再度sk_buffが確保されそちらにデータが入れられる。つまりデータのフラグメント処理はこの時点で行われていることになる。

UDP,Rawソケットの通常の使い方では、sendmsg()すればそれを1つのデータグラムとして送信するので、ip_append_data()が一回呼び出されたら、次にip(udp)_push_pending_frames()が呼び出されsk_write_queueのsk_buffが送信されることになる(sk_write_queueは空になる)。

ただし、MSG_MOREソケットオプションを使うと複数回のsendmsg()で1つのデータグラムを構築して送信することができる。この場合は、ip_push_pending_frames()でsk_write_queueのsk_buffが送信される前に複数回ip_append_data()が呼び出されることになる。ip_append_data()は呼び出された時に、sk_write_queueにsk_buffがあった場合は、最後のsk_buffにデータを追加していくようになっている。(MTUを越えたら新たにsk_buffが取られるのは同じ)

(*1) 格納されたデータの前には、後からMACヘッダを追加できるだけの余白が取られている。また、各sk_buffのデータ(図2中灰色部分)の先頭にはIPヘッダ分の領域が取られている(ヘッダ自体はまだ作られていない)。

4.3 ip_push_pending_frames()

この関数はsk_write_queueにチェーンされているsk_buffを送信する。

sk_write_queueにチェーンされているsk_buffは全て取り出し、2番目以降のsk_buffは先頭のsk_buffのfrag_listにチェーンされる。そして、先頭sk_buffのパケットのIPヘッダを作成する。パケットが完成したらフック処理(NF_IP_LOCAL_OUT)を行い、dst_output()を呼び出してIP層の送信処理へ回す。

なお、frag_listにチェーンされているsk_buffのIPヘッダの作成はip_fragment()内で行われる。



図3 frag_listチェーン

関連ページ



最終更新 2007/04/20 17:38:11 - kztomita
(2007/04/20 13:29:28 作成)
添付ファイル
ip_snd.png - kztomita
ip_append_data.png - kztomita
ip_push_pending_frames.png - kztomita


リンク
最近更新したページ
検索