スタックの階層
スタックの特徴
全体図
ネットワークの実装
プロトコル登録・削除
ソケットシステムコール
ラッパーとして実装
送受信データ
glibc によるラッパー
sockfs
struct sk_buff
NICとカーネルIF
IPv4ソケット作成
ルーティング仕組み
ルーティング処理
受信処理
送信処理
IPヘッダチェックサム
リアセンブル処理
ARP解決
ICMP
UDP

 LinuxのTCP/IPプロトコルスタックの階層
 ・システムコールインターフェース
 ・(VFS)
 ・ソケット層
任意のプロセス間での通信インターフェース
下位層へ様々な通信プロトコルを定義
遠隔地ホスト上のプロセスとも通信可
 ・INET
 ・TCP、UDP、IP
 ・IP層(ネットワーク層)
 ・イーサネット

 LinuxのTCP/IPプロトコルスタックの特徴
 ・新規にストレートな実装を採用(STREAMS等の汎用機構は使わない)
 ・階層構造をもつ
イーサネット等の物理デバイスを制御するデバイスドライバ上にIPプロトコルドライバ
を配置、IPプロトコルドライバ上にTCP・UDP・ICMPプロトコルドライバを配置
 ・積極的に遅延処理を用いて、スループットを向上
 ・マルチプロセッサ構成時に実行並列度が向上するように工夫
 ・ネットワーク実装の全体図

  プロセス  
 
  system call  
sockfs
struct socket
struct net_proto_family* struct sock    
  (TCP/IP)NWプロトコル  
neighbour rtable struct packet_type  
       struct Qdisk  
struct netdevice  
 
イーサネット

 Linuxのネットワーク(Berkeley ソケットインターフェース)実装
 ・プロセス(APがNWを利用)
 ・socket/file interface (system call)
 ・sockfs、struct file、struct dentry
 ・struct socket、struct proto_ops
ソケットインターフェースの実装
プロトコルにあまり依存しない処理を行う。
struct socket、ソケットの実態
struct socketを小さく保つため、struct sock(sock 構造体は大きい)と分離
struct proto_ops、メソッド
 ・struct sock、struct proto (TCP/IPネットワークプロトコル)
よりプロトコルに依存した処理を行う。
TCPは、socket構造体とsock構造体を切り離して管理する。
struct sock、ソケットの実態
struct proto、メソッド
・・・・
 ・struct packet_type (TCP/IPネットワークプロトコル)
 ・(パケットスケジューラ)
 ・ネットワークデバイス
 ・イーサネット

 ソケット専用システムコール
 ・ソケット作成(socket システムコール)、ファイルデスクリプタを得る。
 ・通信先へ接続(connnect/bind/listen/accept システムコール)
 ・データ授受(send/recv システムコール)
 ・接続終了(shutdown システムコール)
 ・ファイルデスクリプタを得る(close)

 ソケットAPIを、glibc によるラッパーとして実装
 ・ソケット関連のシステムコールへ1つのシステムコール番号だけ割当て(CPUによる)
システムコールの実態は、socketcall
 ・socket API→glibc→socketcall→system call発行→sys_socketcall→switch分岐→sys_XXX
int socketcall (ソケットAPI, 引数)
 ・ソケット作成(sys_socket システムコール)、ファイルデスクリプタを得る。
 ・通信先へ接続(sys_connnect/sys_bind/sys_listen/sys_accept システムコール)
 ・データ授受(sys_send/sys_recv システムコール)
 ・接続終了(sys_shutdown システムコール)
 ・ファイルデスクリプタを得る(close)

 ソケットが管理する送受信データ
 ・ソケットは疑似ファイルシステム(sockfs)として実装
 ・ソケットバッファ(struct sk_buff)が管理
プロトコル処理でデータのコピーを発生させない作り
ソケットバッファのデータ部を共有する。
メモリ上の任意のページをソケットバッファのデータとしても登録できる。

 sockfs(疑似ファイルシステム)
 ・マウントできない(MS_NOUSERフラグチェックで、EINVALエラー終了)
openシステムコールで、ファイルデスクリプタを取得できない。
対応するカーネル内の構造体も生成できない。
 ・socket/socket_pair/accept システムコールで構造体確保、ファイルデスクリプタと関連付け
プロトコル処理でデータのコピーを発生させない作り

 struct sk_buff
 ・プロトコル層の移動
各層のヘッダへのポインタを持つ(すばやくアクセス)
クローン
sk_buff 構造体のみコピーし、それが指しているデータはコピーしない。
コピー
sk_buff 構造体が指しているデータまでコピー
全て(skb_copy)
ヘッダだけ(pskb_copy)
 ・パケット処理機能

 NICとカーネルのインターフェース
 ・割り込みを契機としたコンテキストで処理するAPI
 ・NWからくるデータのパケットタイプと処理する関数を struct packet_type へ登録
該当パケット受信時にSW割込み発生、パケットタイプに応じた関数を呼び出す。
 ・NWから受け取ったデータをIP層へ渡す。
NICはパケットを受け取ると割込みを上げる。
対応する割込みパンドラが受信処理を行い、
プロトコル層へパケットを引き渡すため、netif_rx() を呼び出す。
netif_rx() 処理後、後の処理は、SW割込みで行う。

 IPv4通信
 IPv4ソケット作成
 ・作成関数の登録
「struct net_prot_family inet_family_ops」のcreateメンバーへ、
「inet_create」関数を指定

static struct net_prot_family inet_family_ops = {
.family = PF_NET,
.create = inet_create,
.owner = THIS_MODULL,
};

 ・「sock_register」関数を実行
 ・IPv4ソケットタイプ(TCP、UDP、RAW)の登録、削除
inet_protosw構造体(struct inet_protosw)を用いる。
配列inetsw_array
inet_register_protosw 登録
inet_unregister_protosw 削除
 ・sock構造体(struct sock
ソケット固有の情報を保持
 ・inet_sock構造体(struct inet_sock)
IP固有の情報を保持
 ・tcp_sock/udp_sock構造体(struct tcp_sock、struct udp_sock)
TCP/UDP固有の情報を保持
 ・sock構造体から各構造体への変換関数
inet_sk、tcp_sk、udp_sk
 ・パケット受信パンドラ(処理ルーチン)の登録
dev_add_packで登録
受信したいパケットタイプとしてip_packet_typeを指定
「struct packet_type ip_packet_type」の.funcメンバーへ、
ip_rcv」関数(パケット受信関数)を指定

static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
};

IPヘッダのプロトコルによって処理関数が異なる。
net_proprotcol構造体(struct net_proprotcol)とプロトコル値を指定
inet_add_protocol 登録
inet_del_protocol 削除
上位のプロトコルの受信関数(ip_rcvと互いにリンク)
tcp_v4_rcv()
icmp_rcv()
udp_rcv()
 ・各NWデバイスのHWアドレス、ネットマスク、MTU情報の保持
in_device構造体

 ルーティング仕組み
 ・flowi構造体 (ルーティング処理に必要な情報を格納)
 ・fib (forwarding information base)「静的ルーティングテーブル」
ネットマスク毎に管理
ip_fib_main_table (ユニキャスト、ルータへの送信経路情報)
fib_local_table (ローカルホスト上の転送経路、マルチキャスト、ブロードキャスト)
 ・カーネルのコンフィグレーション設定で複数のテーブルを追加使用
 ・inet_peer構造体 (宛先IPアドレス毎に管理する情報)
IPパケットのid、TCPで使用するタイムスタンプ
AVLツリーで管理
 ・hh_cache構造体
データリンク層で使用するアドレスのキャッシュ、パケット送信情報
 ・近隣キャッシュ(neighbour)
 ・dst_entry構造体、rtable構造体

 ルーティング処理
 ・TCP/IPでパケットを送るときに参照
比較基準は、longest match
全ての通信の宛先IPアドレスは、テーブル内の行のいずれかに必ず合致
 ・ルーティングテーブルキャッシュを検索
 ・見つからなかったら、ルーティングテーブルを検索
 ・検索結果の各種チェック
 ・正当なものならば、rtable構造体を確保してメンバーを初期化
 ・rt_intern_hash()でルーティングテーブルキャッシュへ追加

 ・ルーティングテーブルとの比較
Destination Addressとネットマスクからネットワークを算出
ネットワークとテーブルの受信先サイトが一致していれば、その行は合致する候補
複数の行が合致候補になった場合、ネットマスクが一番大きい行が最終的な合致結果
ネットマスクも同じ大きさの場合は、Metricが小さいほうが最終的な合致結果

 受信処理(関数の流れ)
 ・ネットワークデバイス
 ・netif_receive_skb()
 ・deliver_skb()
 ・ip_rcv()
IPパケット・Mのソフトウェア割り込み(コンテキスト)で、ip_rcvへソケットバッファが来る。
ip_rcv関数でipヘッダのチェックサム計算、OKならip_rcv_finishへ処理を渡す。
 ・ip_rcv_finish関数
必要があればルーティング処理(ip_route_input())を行う。
必要があればipヘッダのオプション解析(ip_options_compile())を行う。
dst_input関数を呼び出す。
 ・dst_input()
 ・自ホストで受信の場合、ip_local_deliver()
必要ならリアッセンブル、ip_defrag()
すべてのフラグメントがそろうと受信処理を呼ぶ、ip_local_deliver_finish()
 ・転送処理の場合、ip_forward()、dst_output()
 ・上位レイヤーへ
tcp_v4_rcv()
icmp_rcv()
udp_rcv()

 送信処理(関数の流れ)
 ・ip_queue_xmit()
 ・ソケットの宛先のキャッシュの確認 (__sk_dst_check())
 ・キャッシュされてない場合ルーティング処理(ip_route_output_flow())を行う。
ルーティングテーブルキャッシュ、ルーティングテーブルの順に調べる(fib_lookup())
 ・IPのidの割り当て (ip_select_ident_more())
 ・dst_output()からip_output()を呼ぶ。
 ・パケットが大きい場合、フラグメント処理 (ip_fragment())
 ・ARPが解決されていれば、dev_queue_xmit()を呼びパケットを送信
ARPが解決されていなければ、neigh_resolve_output()
 ・ARP解決を処理するタイマーを開始
 ・ARPが解決された再び、neigh_resolve_output()

 送信
 ・送信に使われるメディアに合わせてアドレス解決等の処理を行い、
 ・実際の送信は、直接相閧ノ送るか、Gatewayに転送するのどちらかをとる
 ・メディアがイーサネットの場合
Interfaceのところで示されているNICを使って送信
必要なMACアドレスをARPを使い取得後、NICからイーサネットに送り出す
(MACアドレスは一定時間記憶され、再利用)

 IPヘッダチェックサム
 ・IPパケットにある、データの通信経路上での破損を確認できるデータ
 ・IPヘッダ部分のみ対象
 ・計算
チェックサムフィールドを0にする。
ヘッダーに対して16ビットの1の補数合計を計算
その結果に対して1の補数を計算し、それをチェックサムとする。
 ・受信側は、ヘッダーを合計してすべてのビットが0を確認する。
 ・転送する場合は、TTLを1減じて、チェックサムに1を足す。

 リアセンブル処理
 ・フラグメント化された複数のIPパケットを元のIPパケットに組み立てる。
 ・IPパケットの組み立てAPI
ip_append_data
ip_append_page
ip_push_pending_frames

 ARP解決
 ・タイマーがneigh_timer_handler関数呼び出し
 ・arp_solicit関数からARP要求パケットを作成し送信
 ・ARP応答パケット受信(arp_rcv())
パケットを解析(arp_process())し、neighbour構造体を更新(neigh_update())
 ・ARPが解決された場合、ハードウェアヘッダーキャッシュを更新(neigh_update_hhs())
 ・送信処理の関数ポインタの書き換え(neigh_connect())
 ・ARP待ちになっていたパケットを送信

 ICMP
 ・ICMPパケット受信処理(icmp_rcv())
それぞれタイプに応じた処理を行う。
ICMPメッセージタイプをインデックスとした配列(icmp_pointers)で処理を分岐
 ・ICMPパケット送信処理(icmp_reply() or icmp_send())
短時間に多量に送信しない。

 UDP
 ・UDP受信処理 (関数の流れ)
チェックサム計算の初期化処理(udp_checksum_init())
受信したパケットに対応するソケットを検索(udp_v4_lookup())
見つかれば受信キューにつなぐ(udp_queue_rcv_skb()、sock_queue_rcv_skb())
見つからなければ、チェックサム計算を完了し、パケットを破棄
チェックサムが正しければ、ICMPエラーを返す
データ到着をソケットに通知
プロセス受信処理(udp_recvmsg())
受信待ちキューからソケットバッファを取り出し、ユーザー空間にデータコピー
 ・UDP送信処理 (関数の流れ)
プロセスから(udp_sendmsg() or udp_sendpage())が呼ばれる。
データ作成(ip_append_data()、ip_append_page())
UDPヘッダ完成(udp_push_pending_frames())させ、送信(ip_push_pending_frames())