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ヘッダがついているので、なにもせずデータを送信キューに積む。
: 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を構築することになる。
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()内で行われる。
関連ページ