SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Webサーバのかくれんぼ

1.socketを使用しないサーバの作成

通常、TCP/IPのプログラムは、socketを使用して作成されますが、WinPcapでパケットを取得して、このパケットをそのままプログラムで扱うことで、socketを使用しないサーバプログラムを作成してみました。
TCP/IPカーネルの必要部分を一部実装するイメージです。

2.ネットワーク環境

試験のためVMwareを使用し、192.168.0.0/24のサブネット内にWindows7(192.168.0.11)及びUbuntu(192.168.0.13)の2台の端末が存在するネットワークを準備しました。


3.簡単なWebサーバの実装

とりあえず、簡単なWebサーバ(HTTP1.0相当 HideAndSeek.exe)を作成しWindows7で起動、Ubuntuからアクセスしました。

Webサーバのコード(概要)

TcpListener listener = listener = new TcpListener(_port);
listener.Start();//待ち受け開始
            
while (_life) {
    if (!listener.Pending()) {//接続が無い場合は、処理なし
        Thread.Sleep(100);
        continue;
    }
    var tcp = listener.AcceptTcpClient();
    var ns = tcp.GetStream();

    var buf = new byte[2048];//リクエストは2048バイト程度で収まるだろうとキメうちする
    var size = ns.Read(buf, 0, buf.Length);
    string request = Encoding.ASCII.GetString(buf, 0, size);
    Job(request, ns);
    ns.Close();
    tcp.Close();
    listener.Stop();
}

Ubuntu(192.168.0.13)からWindows7(192.168.0.11)のWebサーバ(HideAndSeek.exe)にアクセスしているようすです。

socketで作成したプログラムなので、「netstat -ano」とすると、80番をLISTENしているプロセスが表示されています。

$ netstat -ano | grep LISTEN | grep 80
  TCP 0.0.0.0:80      0.0.0.0:0     LISTENING   3432

Wiresharkでパケットをモニタすると、SYNパケットで始まる3WAYハンドシェイクから始まってシーケンス番号を進めながらデータをやり取りするTCPのパケットを観測することができます。


4.Webサーバの停止

Webサーバを停止すると、LISTENでの表示は消え、当然ですがブラウザからのアクセスもできなくなります。

この時のパケットをモニタすると、3WAYハンドシェイクの最初のSYNパケットに応答が無いので、ひたすらSYNを送り続けているようすが観測できます。

5.WinPcapでSYNパケットに応答

まずは、到着したパケットが処理対処かどうかを判断します。
(1)宛先MACが、自分宛か?
(2)プロトコルTCPか?
(1)ポートは80番か?
処理対象のパケットのうちSYNパケットは、新たなセッション開始として処理し、SYN以外は、既に開始しているセッション宛の後続のパケットとして処理します。


//宛先MAC確認(Etherヘッダ)
var mac = Util.Mac2Str(recvPacket.Mac[(int)Sd.Dst]);
if (mac.ToUpper() != Util.Mac2Str(_myMac)) 
    return;
//プロトコル確認(IPヘッダ)
if (recvPacket.ipHeader.protocol != 0x06) 
    return;
//ポート番号確認(TCPヘッダ)
if (recvPacket.Port[(int)Sd.Dst] != _port)
    return;
if (recvPacket.Flg == 0x02) {//SYNパケット到着
    //新しいセッションの開始
    _ar.Add(new Session(recvPacket, _log));
} else {//それ以外のパケット
    //当該セッションに追加
    for (int i = 0; i < _ar.Count; i++) {
        if (_ar[i].Append(recvPacket)) {
             break;
        }
    }
}

SYNパケットの到着に対しては、送り元・宛先を入れ替えたパケットを作成し、SYN/ACKパケットとして送信元に返信します。この際、送信パケットのシーケンス番号は新たに生成し、Ack番号は、到着したパケットのシーケンス番号+データ長+1で計算しています。

public Session(RecvPacket recvPacket,Log log) {
    //squenceは生成する
    _squence = (uint)_rnd.Next(99999);
    //相手のsquence+dataLen+(1)でackを初期化
    _ack = recvPacket.Squence + recvPacket.Len;
    if (recvPacket.Len == 0)
     _ack++;
    Send(0x12, new byte[0]);// SYN/ACKパケットの送信
}

後続のパケットは、Squence及びAckを進めながら処理します。
受信データがあった場合は、Streamに入れて上位のプログラムに渡し、上位プログラムからStreamに入れられたデータは、送信パケットに詰め込んで相手方に送信します。

パケットを直接処理して動作するWebサーバと、それにアクセスしているようすです。

パケットをモニタすると、通常のTCPパケットを確認することができます。

192.168.0.11から送信されているパケットは、すべてプログラム(HideAndSeek.exe)が作成して送信したものです。

6.ARPへの応答

TCP/IPカーネルを使用しないのであれば、結局のところ、プログラムが、どのパケットに応答するかで、動作が決定するため、特にMACやIPなどに制限がなくなります。
そこで、Webサーバの動作対象を自分のMACだけでなく、特定のMAC(00:01:02:03:04:05)にも応答するように修正し、クライアントからの192.168.0.101~105に対するARP要求(ブロードキャスト)に、この(00:01:02:03:04:05)で応答するように実装してみます。

結果として、端末のARPテーブルは、「00:01:02:03:04:05」になります。

こうなれば、192.168.0.101~105のどこへアクセスしても、先ほどと同じようにWebサーバが対応することができるようになります。

7.デフォルトゲートウエイ

デフォルトゲートウエイをホスト側のIPアドレス(192.168.0.11)に変更してみます。
[text highlight="1,6,7,8"]
# route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.254 0.0.0.0 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 eth0
192.168.0.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
# route add default gw 192.168.0.11
# route del default gw 192.168.0.254
# route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.11 0.0.0.0 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 eth0
192.168.0.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
|