SCTPによるネットワーキングの向上

TCPとUDPの利点を兼ね備えたStream Control Transmission Protocol

SCTPは、信頼性が高く汎用性のあるトランスポート層プロトコルであり、IPネットワークでの使用を目的としています。SCTPは元来、テレフォニー・シグナリングのために設計されたものですが（RFC 2960）、TCPの制限のいくつかを解決すると同時に、UDPの優れた機能も備えるという、予期せぬ利点をもたらすこととなりました。SCTPが提供する機能は、可用性と信頼性を高め、ソケット・イニシエーションのセキュリティーを向上させます（図1に、IPスタックの階層アーキテクチャーを示します）。

図1. IPスタックの階層アーキテクチャー

この記事では、Linux 2.6カーネルにおけるSCTPの概念を紹介し、優れた機能のいくつか（マルチホーミングとマルチストリーミングなど）を取り上げ、このプロトコルによるマルチストリーミングを実現するサーバーおよびクライアントのソース・コードの一部を見ます（詳しいコードへのURLも示します）。

では、IPスタックの概要から見てみましょう。

IPスタック

インターネット・プロトコル・スイートは、いくつかの層に分けられます。図1に示されているように、各層が特定の機能を提供します。

下層から順に説明します：

リンク層は、通信媒体（イーサネット・デバイスなど）への物理インターフェースを提供します。

ネットワーク層は、ネットワークでのパケットの移動を管理し、特に、パケットが宛先に確実に届くようにします（ルーティングともいいます）。

トランスポート層は、アプリケーション層の2つのホスト間のパケットの流れを規制します。また、ポートという、通信のためのアプリケーション・エンドポイントの識別を行います。

最後に、アプリケーション層は、ソケットを通じて転送されるデータに意味を与えます。このデータは、SMTP（Simple Mail Transport Protocol）を使用した電子メール・メッセージまたはHTTP（Hypertext Transport Protocol）によって表示されるWebページなどから構成されます。

アプリケーション層のプロトコルはすべて、ソケット層をトランスポート層プロトコルへのインターフェースとして使用します。ソケットAPIは、カリフォルニア大学バークレー校のBSD UNIX®オペレーティング・システムで開発されました。

では、SCTPの機能の説明に入る前に、従来のトランスポート層プロトコルを簡単に復習しておきましょう。

トランスポート層プロトコル

最も一般的な2つのトランスポート層プロトコルは、TCP（Transmission Control Protocol）とUDP（User Datagram Protocol）です。

TCPは、データが順序どおりに配送されることを保証し、ネットワーク内の輻輳を管理する信頼性の高いプロトコルです。

UDPはメッセージ指向のプロトコルであり、配送の順序も保証せず、輻輳の管理も行いません。

しかし、UDPは高速なプロトコルであり、伝送するメッセージの境界を維持します。

この記事で紹介するのは、もう1つの選択肢、SCTPです。SCTPは、TCPのように、信頼性の高い順序どおりのデータ配送を提供すると同時に、UDPのようにメッセージ指向で動作して、メッセージの境界を維持します。SCTPは、他にもいくつかの優れた特徴を備えています。

マルチホーミング

マルチストリーミング

イニシエーション保護

メッセージ・フレーミング

構成可能な順不同配送

グレースフル・シャットダウン

SCTPの主な機能

従来のトランスポート層プロトコルより優れたSCTPの最も重要な2つの機能強化は、エンドホスト・マルチホーミングおよびマルチストリーミング機能です。

マルチホーミング

マルチホーミングは、TCPを使用するよりも高い可用性をアプリケーションに提供します。マルチホーム・ホストとは、複数のネットワーク・インターフェースを持ち、アドレス指定できるIPアドレスを複数持つホストです。TCPでは、コネクションは2つのエンドポイント間のチャンネル（この場合は、2つのホストのインターフェース間のソケット）を参照します。SCTPは、2つのホスト間に存在するアソシエーションの概念を導入していますが、各ホストの複数のインターフェースとコラボレートすることが可能です。

図2に、TCPのコネクションとSCTPのアソシエーションの違いを示します。

図2. TCPコネクション対SCTPアソシエーション

上の図は、TCPコネクションです。各ホストには、1つのネットワーク・インターフェースが含まれています。コネクションは、クライアントとサーバーのそれぞれの1つのインターフェース間で作成されます。確立されたコネクションは、各インターフェースにバインドされます。

下の図のアーキテクチャーでは、ホストごとに2つのネットワーク・インターフェースが含まれています。インターフェースC0からS0とC1からS1という2つのパスが、独立したネットワークを経由して提供されています。SCTPでは、この2つのパスを1つのアソシエーションとしてまとめることができます。

SCTPは組み込みのハートビートを使用してアソシエーションのパスをモニターします。パス障害を検出すると、もう1つのパスでトラフィックを送ります。アプリケーションは、フェイルオーバー・リカバリーが行われたことを知る必要さえありません。

フェイルオーバーは、ネットワーク・アプリケーションの接続性を維持するためにも使用できます。たとえば、802.11無線インターフェースとイーサネット・インターフェースを備えたラップトップがあるとします。ラップトップがドッキング・ステーションに接続されているときには、より高速なイーサネット・インターフェースが使用されます（SCTPでは、プライマリー・アドレスと呼ばれます）。しかし、この接続が失われたときには（ドッキング・ステーションから取り外されたとき）、接続は無線インターフェースにフェイルオーバーされます。再び、ドッキング・ステーションに接続されると、イーサネット接続が検出されて、このインターフェースで通信が再開されます。これは、高可用性と高信頼性を提供するためのパワフルな仕組みです。

マルチストリーミング

いくつかの点でSCTPアソシエーションはTCPコネクションに似ていますが、異なる点はSCTPがアソシエーション内の複数のストリームをサポートするということです。アソシエーション内のすべてのストリームは独立していますが、アソシエーションに関連付けられています（図3を参照）。

図3. SCTPアソシエーションとストリームの関係

各ストリームにはストリーム番号が与えられ、ストリーム番号はアソシエーションを流れるSCTPパケット内にエンコードされます。マルチストリーミングの重要性は、ブロックされたストリーム（たとえば、パケットが消失したために再送信を待っているストリームなど）がアソシエーション内の他のストリームに影響を与えない点にあります。この問題は一般にヘッドオブライン・ブロッキングと呼ばれています。TCPは、このようなブロッキングを起こす傾向があります。

データ転送において、複数のストリームの応答性を高めるにはどうすればよいでしょうか。たとえば、HTTPプロトコルは制御とデータを同じソケットで共有しています。Webクライアントはサーバーにファイルを要求し、サーバーは同じ接続でファイルを返します。マルチストリーミングHTTPサーバーは、アソシエーション内の個別のストリームで複数の要求に応えるため、よりよい双方向性を提供します。この機能によって応答が並列化され、実際に高速になるわけではありませんが、HTMLとグラフィックス・イメージを同時にロードするので、応答性が高まったように感じます。

マルチストリーミングはSCTPの重要な機能であり、プロトコル設計における制御とデータの問題を考えるときには特に重要です。TCPでは、一般に制御とデータは同じコネクションを共有するので、制御パケットがデータ・パケットより遅れる可能性がある点が問題です。制御とデータが個別のストリームに分けられていれば、制御データをよりタイムリーに扱うことができ、使用可能なリソースをより効率的に利用することができます。

イニシエーション保護

TCPとSCTPでの新しい接続の開始（イニシエーション）は、パケットのハンドシェークによって行われます。TCPでは、これはいわゆるスリーウェイ・ハンドシェークです。クライアントはSYNパケット（Synchronizeの短縮形）を送信し、それに対してサーバーはSYN-ACKパケット（Synchronize-Acknowledge）で応答します。最後に、クライアントは受信確認としてACKパケットを送信します（図4を参照）。

図4. TCPおよびSCTPハンドシェークのパケット交換

TCPで問題となるのは、悪意あるクライアントが偽のソース・アドレスを持つIPパケットを偽造して、サーバーに大量のTCP SYNパケットを送りつけた場合です（SYNフラッド）。サーバーはSYNを受信すると、接続用のリソースを割り当てますが、SYNフラッドでは、ついにはリソース不足となり新しい要求に応えられなくなります。これをDoS（Denial of Service：サービス拒否）攻撃といいます。

SCTPは、4ウェイ・ハンドシェークとクッキーの導入によってこの種の攻撃から保護します。SCTPでは、クライアントはINITパケットによって接続を開始します。サーバーはINIT-ACKで応答し、これにはクッキー（この接続要求を識別する一意なコンテキスト）が含まれています。クライアントはCOOKIE-ECHOで応答します。これには、サーバーから送信されたクッキーが含まれています。この時点で、サーバーは接続用のリソースを割り当てて、COOKIE-ACKをクライアントに送信することによって応答します。

4ウェイ・ハンドシェークでのデータ移動の遅延問題を解決するために、SCTPではCOOKIE-ECHOおよびCOOKIE-ACKパケットにデータを含めることができます。

メッセージ・フレーミング

メッセージ・フレーミングでは、ソケットを通じて伝送されるメッセージの境界が維持されます。これは、クライアントがサーバーに100バイトを送信した後に50バイトを送信した場合、サーバーは100バイトと50バイトを2つの読み取りとしてそれぞれ読み取ることを意味します。UDPもこのように動作し、これがメッセージ指向プロトコルの利点となっています。

一方、TCPはバイト・ストリーム方式で動作します。フレーミングがない場合、ピアが受信する量は送信された量より多かったり少なかったりすることがあります（書き込みが分割されたり、複数の書き込みが1つの読み取りに集約されるため）。この動作のため、TCP上でメッセージ指向プロトコルを動作させて、アプリケーション層の中でデータ・バッファーとメッセージ・フレーミングを提供する必要があります（複雑なタスクになる可能性があります）。

SCTPは、データ伝送においてメッセージ・フレーミングを提供します。ピアがソケットへの書き込みを行うと、これと同じサイズのデータの塊がピア・エンドポイントで読み取られることが保証されます（図5を参照）。

図5. UDP/SCTP対バイト・ストリーム指向プロトコルでのメッセージ・フレーミング

オーディオやビデオなど、ストリーム指向のデータの場合は、フレーミングがなくてもかまいません。

構成可能な順不同配送

TCPは、データが順序どおりに配送されることを保証します（TCPがストリーム・プロトコルであることを考えると、これはよいことです）。UDPは、順序付けを保証しません。SCTPのメッセージは、確実に、望みどおりの順序で伝送されますが、ストリームごとに順不同配送を構成することができます。要求が個別であり、順序が重要でないメッセージ指向プロトコルにおいては、この機能は有用です。

グレースフル・シャットダウン

TCPとSCTPはコネクションベースのプロトコルですが、UDPはコネクションレス・プロトコルです。TCPとSCTPは、ピア間での接続のセットアップと破棄を必要とします。SCTPでのソケット・シャットダウンの違いは、TCPのハーフクローズの削除です。

図6に、TCPとSCTPのシャットダウン・シーケンスを示します。

図6. TCPとSCTPの接続終了シーケンス

TCPでは、ピアはソケットの自分側のエンドポイントをクローズして（FINパケットが送信されます）、なおかつデータの受信を続けることが可能です。FINは、このエンドポイントからはそれ以上のデータが送信されないが、ピアがソケットの終端をクローズするまではデータの送信を続行できることを示します。アプリケーションがこのハーフクローズ状態を使用することはまれなので、SCTPの設計者たちはこれをなくして、よりクリーンな終了シーケンスに置き換えることにしました。ピアが自分のソケットをクローズしたときには（SHUTDOWNプリミティブが発行されます）、両方のエンドポイントがクローズされる必要があり、それ以上のデータ移動は、どちらの方向でも許されません。

マルチストリーミングのデモ

SCTPの基本機能がわかったところで、サンプルのサーバーとクライアントを見てみましょう。このサーバーとクライアントはCプログラミング言語で作成されており、SCTPのマルチストリーミング機能を実現します。

このサンプルのサーバーは、一種のデイタイム・プロトコルを実装します。この伝統的なサーバーでは接続されたクライアントに現在時刻を送りますが、SCTPではストリーム0で現地時間を、ストリーム1でグリニッジ標準時（GMT）を送ります。このシンプルな例により、ストリーム通信用のAPIの用例を示します。

図7に、プロセス全体の概要を示します。この図には、ソケットAPIから見たアプリケーションの流れだけでなく、クライアントとサーバーから見た関係も示されています。

図7. マルチストリーミング・デイタイム・サーバーとクライアントで使用されるソケット関数

これらのアプリケーションは、2.6.11カーネルとLinuxカーネルSCTPプロジェクト（lksctp）を備えたGNU/Linuxオペレーティング・システムで開発されました。lksctpツール・パッケージには、非標準のソケット関数が含まれ、SourceForgeから入手可能です。「参考文献」のリンクを参照してください。

デイタイム・サーバー

マルチストリーム・デイタイム・サーバーをリスト1に示します。リスト1では、読みやすくするためにエラー・チェックがすべて省略されていますが、下記からダウンロードできるコードには、エラー・チェックやその他のSCTPソケット拡張も含まれています。

リスト1.複数のストリームを使用するSCTP用に作成されたデイタイム・サーバー

int main() { int listenSock, connSock, ret; struct sockaddr_in servaddr; char buffer[MAX_BUFFER+1]; time_t currentTime; /* Create SCTP TCP-Style Socket */ listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP ); /* Accept connections from any interface */ bzero( (void *)&servaddr, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons(MY_PORT_NUM); /* Bind to the wildcard address (all) and MY_PORT_NUM */ ret = bind( listenSock, (struct sockaddr *)&servaddr, sizeof(servaddr) ); /* Place the server socket into the listening state */ listen( listenSock, 5 ); /* Server loop... */ while( 1 ) { /* Await a new client connection */ connSock = accept( listenSock, (struct sockaddr *)NULL, (int *)NULL ); /* New client socket has connected */ /* Grab the current time */ currentTime = time(NULL); /* Send local time on stream 0 (local time stream) */ snprintf( buffer, MAX_BUFFER, "%s

", ctime(¤tTime) ); ret = sctp_sendmsg( connSock, (void *)buffer, (size_t)strlen(buffer), NULL, 0, 0, 0, LOCALTIME_STREAM, 0, 0 ); /* Send GMT on stream 1 (GMT stream) */ snprintf( buffer, MAX_BUFFER, "%s

", asctime( gmtime( ¤tTime ) ) ); ret = sctp_sendmsg( connSock, (void *)buffer, (size_t)strlen(buffer), NULL, 0, 0, 0, GMT_STREAM, 0, 0 ); /* Close the client connection */ close( connSock ); } return 0; }

リスト1のサーバーはまず、サーバー・ソケットを作成します（IPPROTO_SCTPを使用してSCTPの1対1ソケットを作成します）。次にsockaddr構造体を作成して、どのローカル・インターフェースからの接続も許されることを指定します（ワイルドカード・アドレスINADDR_ANYを使用します）。このsockaddr構造体を、bind呼び出しを使用してソケットにバインドした後、サーバー・ソケットを待ち受け状態にします。この時点で、受信接続が可能になります。

SCTPはTCPやUDPと同じソケットAPIの多くを使用することに注目してください。その他いくつかのAPI関数は、lksctp開発ツールで提供されています（「参考文献」を参照）。

サーバー・ループで、新しいクライアント接続を待ちます。accept関数からリターンすると、新しいクライアント接続がconnSockソケットによって識別されます。time関数を使用して現在時刻を取得し、snprintfで文字列に変換します。sctp_sendmsg関数（非標準のソケット呼び出し）で、特定のストリーム（LOCALTIME_STREAM）を指定して文字列をクライアントに送信することができます。現地時間文字列が送信されたら、GMTでの現在時刻を文字列としてパッケージ化して、ストリームGMT_STREAMで送信します。

この時点でデイタイム・サーバーの仕事は終わったので、ソケットをクローズして新しいクライアント接続を待ちます。簡単でしょう? では次に、デイタイム・クライアントによるマルチストリーミングの処理を見てみましょう。

デイタイム・クライアント

マルチストリーム・デイタイム・クライアントをリスト2に示します。

リスト2.複数のストリームを使用するSCTP用に作成されたデイタイム・クライアント

int main() { int connSock, in, i, flags; struct sockaddr_in servaddr; struct sctp_sndrcvinfo sndrcvinfo; struct sctp_event_subscribe events; char buffer[MAX_BUFFER+1]; /* Create an SCTP TCP-Style Socket */ connSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP ); /* Specify the peer endpoint to which we'll connect */ bzero( (void *)&servaddr, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MY_PORT_NUM); servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); /* Connect to the server */ connect( connSock, (struct sockaddr *)&servaddr, sizeof(servaddr) ); /* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */ memset( (void *)&events, 0, sizeof(events) ); events.sctp_data_io_event = 1; setsockopt( connSock, SOL_SCTP, SCTP_EVENTS, (const void *)&events, sizeof(events) ); /* Expect two messages from the peer */ for (i = 0 ; i < 2 ; i++) { in = sctp_recvmsg( connSock, (void *)buffer, sizeof(buffer), (struct sockaddr *)NULL, 0, &sndrcvinfo, &flags ); /* Null terminate the incoming string */ buffer[in] = 0; if (sndrcvinfo.sinfo_stream == LOCALTIME_STREAM) { printf("(Local) %s

", buffer); } else if (sndrcvinfo.sinfo_stream == GMT_STREAM) { printf("(GMT ) %s

", buffer); } } /* Close our socket and exit */ close(connSock); return 0; }

クライアントでは、SCTPソケットを作成した後、接続先のエンドポイントを含んだsockaddr構造体を作成します。その後、connect関数によってサーバーへの接続を確立します。メッセージのストリーム番号を取得するには、SCTPはソケットオプションsctp_data_io_eventを有効にする必要があります。

これが有効な場合、sctp_recvmsg API関数を通じてメッセージを受信するときに、ストリーム番号を含んだsctp_sndrcvinfo構造体も受信します。この番号によって、ストリーム0（現地時間）とストリーム1（GMT）のメッセージを区別することができます。

SCTPの将来

SCTPは、2000年10月にRFCになった比較的新しいプロトコルです。それ以来、GNU/Linux、BSD、Solarisなど、主要なオペレーティング・システムのすべてで利用されてきました。サードパーティの商用パッケージとして、Microsoft® Windows®オペレーティング・システムでも使用できます。

入手が容易になるにつれて、アプリケーションはSCTPを1次トランスポートとして使用するようになるでしょう。FTPやHTTPなどの従来のアプリケーションが、SCTPの機能に基づいて作成されるようになりました。SIP（Session Initiation Protocol）やSS7（Common Channel Signaling System No. 7）など、他のプロトコルもSCTPを使用しています。商用では、CiscoのIOSにSCTPが組み込まれています。

SCTPが2.6 Linuxカーネルに含まれたことにより、可用性と信頼性の高いネットワーク・アプリケーションのビルドと導入が可能になりました。SCTPは、IPベースのプロトコルとして、TCPおよびUDPのシームレスな代用となりますが、マルチホーミング、マルチストリーミング、セキュリティーの向上など、新しいサービスも提供します。この記事では、SCTPの特徴の概要を紹介しましたが、他の機能も試してみてください。LinuxカーネルSCTPプロジェクト（lksctp）には、開発を容易にするAPI拡張とドキュメントが含まれています。

ダウンロード可能なリソース

関連トピック