コネクションの確立
コネクションの確立はクライアントがその旨をサーバーに伝えるためのTCPセグメントを送ることから始まります。
この段階をアクティブ・オープンと呼びます。
//TcpClientクラスから抜粋 /// <summary> /// TCPのコネクションを確立します。 /// </summary> /// <param name="data">送信するデータ</param> /// <param name="portNumber">指定するポート番号</param> /// <param name="sequenceNumber">シーケンス番号</param> /// <param name="nic">使用するデバイスドライバ</param> protected void Connect ( Frame data, ushort portNumber, ref uint sequenceNumber, Nic nic ) { //接続が確立されるまで繰り返し処理をする。 while ( this.state != TcpConnectionState.Open ) { switch ( this.state ) { case TcpConnectionState.Nothing: sequenceNumber = 0; TcpSegment segment = new TcpSegment ( ); segment.Syn = true; segment.SequenceNumber = sequenceNumber; segment.SourcePortNumber = portNumber; segment.DestinationPortNumber = portNumber; data.Data.UserData = segment; nic.Send ( data ); break; } } //関係のないコードは省略しています }
'TcpClientクラスから抜粋 ''' <summary> ''' TCPのコネクションを確立します。 ''' </summary> ''' <param name="data">送信するデータ</param> ''' <param name="portNumber">指定するポート番号</param> ''' <param name="sequenceNumber">シーケンス番号</param> ''' <param name="nic">使用するデバイスドライバ</param> Protected Sub Connect(ByVal data As Frame, ByVal portNumber As UShort, ByRef sequenceNumber As UInteger, ByVal nic As Nic) '接続が確立されるまで繰り返し処理をする。 While (Me.state <> TcpConnectionState.Open) Select Case Me.state Case TcpConnectionState.Null sequenceNumber = 0 Dim segment As TcpSegment = New TcpSegment() segment.Syn = True segment.SequenceNumber = sequenceNumber segment.SourcePortNumber = portNumber segment.DestinationPortNumber = portNumber data.Data.UserData = segment nic.Send(data) End Select End While '関係のないコードは省略しています End Sub
クライアントはポート番号(指定するアプリケーション層)と初期のシーケンス番号を指定し、SYNフラグを立てたTCPセグメントをサーバーへ宛に送信します。今回は分かりやすくするために初期シーケンス番号を0にしていますが、初期シーケンス番号が0もしくは推測されやすい値ですと、他者が信頼されているクライアントになりすますことが可能となってしまいますので、実際はランダムな値が指定されます。Kevin Mitnick氏が下村務氏のシステムに仕掛けたリモート攻撃は、この仕組みを利用したものだと言われています。
クライアントからこのセグメントを受け取ったサーバーは、適切なセグメントをクライアントに送り返します。
この段階のことをパッシブ・オープンと呼びます。
//TcpServerクラスから抜粋 /// <summary> /// クラアントとのやり取りを行います。 /// </summary> /// <param name="data">受信したデータ</param> /// <param name="nic">使用するドライバ</param> public void Receive ( Frame data, Nic nic ) { //現在の接続状態に基づいて適切な処理をする SocketPair info = GetConnectionInfo ( packet.SourceAddress, segment.SourcePortNumber ); if ( info == null ) { #region 初めてなので情報を登録する SocketPair pair = new SocketPair ( ); pair.ServerIPAddress = this.IP; pair.ServerPortNumber = segment.DestinationPortNumber; pair.ClientIPAddress = packet.SourceAddress; pair.ClientPortNumber = segment.SourcePortNumber; pair.State = TcpConnectionState.ActiveOpen; this.connections.Add ( pair ); #endregion #region 返答をする //TCPセグメント TcpSegment resultSegment = new TcpSegment ( ); resultSegment.SourcePortNumber = segment.DestinationPortNumber; resultSegment.DestinationPortNumber = segment.SourcePortNumber; resultSegment.Syn = true; //ここに注目 resultSegment.Ack = true; //ここに注目 resultSegment.SequenceNumber = segment.SequenceNumber; resultSegment.AcknowledgementNumber = segment.SequenceNumber + 1; //ここに注目 //IPパケットとMACフレームを作成する部分のコードを省略しました //返答を送信 nic.Send ( ( Frame ) resultMac ); #endregion return; } //関係のないコードは省略しています }
'TcpServerクラスから抜粋 ''' <summary> ''' クラアントとのやり取りを行います。 ''' </summary> ''' <param name="data">受信したデータ</param> ''' <param name="nic">使用するドライバ</param> Public Sub Receive(ByVal data As Frame, ByVal nic As Nic) '現在の接続状態に基づいて適切な処理をする Dim info As SocketPair = GetConnectionInfo(packet.SourceAddress, segment.SourcePortNumber) If info Is Nothing Then '初めてなので情報を登録する" Dim pair As SocketPair = New SocketPair() pair.ServerIPAddress = Me.IP pair.ServerPortNumber = segment.DestinationPortNumber pair.ClientIPAddress = packet.SourceAddress pair.ClientPortNumber = segment.SourcePortNumber pair.State = TcpConnectionState.ActiveOpen Me.connections.Add(pair) '--------------------返答をする-------------------- 'TCPセグメント Dim resultSegment As TcpSegment = New TcpSegment() resultSegment.SourcePortNumber = segment.DestinationPortNumber resultSegment.DestinationPortNumber = segment.SourcePortNumber resultSegment.Syn = True 'ここに注目 resultSegment.Ack = True 'ここに注目 resultSegment.SequenceNumber = segment.SequenceNumber resultSegment.AcknowledgementNumber = CUInt(segment.SequenceNumber + 1) 'ここに注目 'IPパケットとMACフレームを作成する部分のコードを省略しました '返答を送信 nic.Send(CType(resultMac, Frame)) Return End If '関係のないコードは省略しています End Sub
サーバーが送るセグメントは、SYNフラグとACKフラグをONにして、確認応答番号に初期シーケンス番号+1の値を設定したものです。
このセグメントを受け取ったクライアントは、そのポート番号を使用しているサービスが使用できることと、サーバーが稼動していることを知ることができます。
最後にクライアントはサーバーからの返答を受信したことを知らせるためにセグメントを送ります。
//TcpClientクラスから抜粋 /// <summary> /// TCPのコネクションを確立します。 /// </summary> /// <param name="data">送信するデータ</param> /// <param name="portNumber">指定するポート番号</param> /// <param name="sequenceNumber">シーケンス番号</param> /// <param name="nic">使用するデバイスドライバ</param> protected void Connect ( Frame data, ushort portNumber, ref uint sequenceNumber, Nic nic ) { //接続が確立されるまで繰り返し処理をする。 while ( this.state != TcpConnectionState.Open ) { switch ( this.state ) { case TcpConnectionState.PassiveOpen: segment = new TcpSegment ( ); segment.Ack = true; //ここがポイント segment.SequenceNumber = this.nextNumber; segment.AcknowledgementNumber = this.nextNumber; segment.SourcePortNumber = portNumber; segment.DestinationPortNumber = portNumber; data.Data.UserData = segment; nic.Send ( data ); this.state = TcpConnectionState.Open; break; } } //関係のないコードは省略しています }
'TcpClientクラスから抜粋 ''' <summary> ''' TCPのコネクションを確立します。 ''' </summary> ''' <param name="data">送信するデータ</param> ''' <param name="portNumber">指定するポート番号</param> ''' <param name="sequenceNumber">シーケンス番号</param> ''' <param name="nic">使用するデバイスドライバ</param> Protected Sub Connect(ByVal data As Frame, ByVal portNumber As UShort, ByRef sequenceNumber As UInteger, ByVal nic As Nic) '接続が確立されるまで繰り返し処理をする。 While (Me.state <> TcpConnectionState.Open) Select Case Me.state Case TcpConnectionState.PassiveOpen Dim segment As TcpSegment = New TcpSegment() segment.Ack = True 'ここに注目 segment.SequenceNumber = Me.nextNumber segment.AcknowledgementNumber = Me.nextNumber segment.SourcePortNumber = portNumber segment.DestinationPortNumber = portNumber data.Data.UserData = segment nic.Send(data) Me.state = TcpConnectionState.Open End Select End While '関係のないコードは省略しています End Sub
以上の3つのセグメントをやり取りすることによりコネクションの確立は完了します。この手順をスリーウェイ・ハンドシェイクと呼びます。
次項ではコネクションの終了について解説します。