动手学习TCP:服务端状态变迁

上一篇文章介绍了TCP状态机,并且通过实验了解了TCP客户端正常的状态变迁过程。

那么,本篇文章就一起看看TCP服务端的正常状态变迁过程

服务端状态变迁

根据上一篇文章中的TCP状态变迁图,可以得到服务器的正常状态变迁流程如下:

CLOSED -> LISTEN -> SYN_RECV -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED

具体的将状态跟TCP包关联起来就如下表示:

From State

To State

Recv Packet

Send Packet

CLOSED

LISTEN

-

-

LISTEN

SYN_RCVD

[SYN]

[SYN, ACK]

SYN_RCVD

ESTABLISHED

[ACK]

-

ESTABLISHED

CLOSE_WAIT

[FIN, ACK]

[ACK]

CLOSE_WAIT

LAST_ACK

-

[FIN, ACK]

LAST_ACK

CLOSED

[ACK]

-

 

服务端状态变迁实验

下面就结合上面分析出来的服务端状态变迁表,利用Pcap.Net来模拟服务端正常的状态变迁过程。

代码实现

跟前面几次正好相反,这次我们将在宿主机运行Pcap.Net实现的服务端,然后在虚拟机运行一个客户端。

对于服务端,主程序中设置了源和目的端的连接信息,这次宿主机中的服务端将监听“3333”端口。

然后,程序中设置了服务端TCP初始状态为"LISTENING",然后就直接运行监听函数了。

// Open the output device
using (PacketCommunicator communicator = selectedDevice.Open(System.Int32.MaxValue, // name of the device
                                                             PacketDeviceOpenAttributes.Promiscuous, // promiscuous mode
                                                             1)) // read timeout
{
    EndPointInfo endPointInfo = new EndPointInfo();
    endPointInfo.SourceMac = "08:00:27:00:C0:D5";
    endPointInfo.DestinationMac = "";
    endPointInfo.SourceIp = "192.168.56.101";
    endPointInfo.DestinationIp = "";
    endPointInfo.SourcePort = 3333;
    endPointInfo.DestinationPort = 0;

    using (BerkeleyPacketFilter filter = communicator.CreateFilter("tcp port " + endPointInfo.SourcePort))
    {
        // Set the filter
        communicator.SetFilter(filter);
    }

    tcpStatus = TCPStatus.LISTENING;
    PacketHandler(communicator, endPointInfo);
}

这次的监听函数"PacketHandler"中的逻辑,跟上一次客户端的例子还是有很大差别的。

首先是期待接收和实际发送的TCP包类型有很大的差别,其次就是状态之间的变迁是完全不同的。但是,代码的逻辑依然是根据上面的服务端状态变迁表。

private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo)
{
    Packet packet = null;
    bool running = true;

    do
    {
        PacketCommunicatorReceiveResult result = communicator.ReceivePacket(out packet);

        switch (result)
        {
            case PacketCommunicatorReceiveResult.Timeout:
                // Timeout elapsed
                continue;
            case PacketCommunicatorReceiveResult.Ok:
                bool isRecvedPacket = (packet.Ethernet.IpV4.Destination.ToString() == endPointInfo.SourceIp) ? true : false;

                if (isRecvedPacket)
                {
                    switch (packet.Ethernet.IpV4.Tcp.ControlBits)
                    {
                        case TcpControlBits.Synchronize:
                            if (tcpStatus == TCPStatus.LISTENING)
                            {
                                endPointInfo.DestinationMac = packet.Ethernet.Source.ToString();
                                endPointInfo.DestinationIp = packet.Ethernet.IpV4.Source.ToString();
                                endPointInfo.DestinationPort = packet.Ethernet.IpV4.Tcp.SourcePort;

                                Utils.PacketInfoPrinter(packet);
                                Packet synAck = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Synchronize | TcpControlBits.Acknowledgment);
                                communicator.SendPacket(synAck);
                                tcpStatus = TCPStatus.SYN_RECEIVED;
                            }
                            break;
                        case TcpControlBits.Acknowledgment:
                            if (tcpStatus == TCPStatus.SYN_RECEIVED)
                            {
                                tcpStatus = TCPStatus.ESTABLISHED;
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                            }
                            else if (tcpStatus == TCPStatus.LAST_ACK)
                            {
                                tcpStatus = TCPStatus.CLOSED;
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                                tcpStatus = TCPStatus.LISTENING;
                            }
                            else if (tcpStatus == TCPStatus.FIN_WAIT_1)
                            {
                                tcpStatus = TCPStatus.FIN_WAIT_2;
                                Utils.PacketInfoPrinter(packet);
                            }
                            break;
                        case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
                            if (tcpStatus == TCPStatus.FIN_WAIT_2)
                            {
                                Utils.PacketInfoPrinter(packet);
                                Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
                                communicator.SendPacket(ack);
                                tcpStatus = TCPStatus.TIME_WAIT;
                            }
                            else if (tcpStatus == TCPStatus.ESTABLISHED)
                            {
                                Utils.PacketInfoPrinter(packet);
                                Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
                                communicator.SendPacket(ack);
                                tcpStatus = TCPStatus.CLOSE_WAIT;
                            }
                            break;
                        default:
                            Utils.PacketInfoPrinter(packet);
                            break;
                    }
                }
                else
                {
                    switch (packet.Ethernet.IpV4.Tcp.ControlBits)
                    {
                        case (TcpControlBits.Synchronize | TcpControlBits.Acknowledgment):
                            if (tcpStatus == TCPStatus.SYN_RECEIVED)
                            {
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                            }
                            break;
                        case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
                            if (tcpStatus == TCPStatus.FIN_WAIT_1)
                            {
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                            }
                            else if (tcpStatus == TCPStatus.LAST_ACK)
                            {
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                            }
                            break;
                        case TcpControlBits.Acknowledgment:
                            if (tcpStatus == TCPStatus.TIME_WAIT)
                            {
                                Utils.PacketInfoPrinter(packet, tcpStatus);
                            }
                            else if (tcpStatus == TCPStatus.CLOSE_WAIT)
                            {
                                
                                Utils.PacketInfoPrinter(packet, tcpStatus);

                                Packet fin = Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment);
                                communicator.SendPacket(fin);
                                tcpStatus = TCPStatus.LAST_ACK;
                            }
                            break;
                        default:
                            Utils.PacketInfoPrinter(packet);
                            break;
                    }
                }
                break;
            default:
                throw new InvalidOperationException("The result " + result + " should never be reached here");
        }
    } while (running);

}

对于客户端,通过Python实现了一个简单的Socket程序来模拟客户端行为:

from socket import *
import time

HOST = "192.168.56.101"
PORT = 3333
BUFSIZ = 1024
ADDR = (HOST, PORT)

client = socket(AF_INET, SOCK_STREAM)
client.connect(ADDR)

time.sleep(5)

client.close()

运行效果

这次,宿主机上运行的是服务端,虚拟机运行的是客户端,打开Wireshark监听"VirtualBox Host-Only Network"网卡,并设置filter为"port 3333"。

运行服务端程序,服务端将处于监听状态。这是在虚拟机中运行"client.py"。这时,通过服务端console可以看到客户端和服务端之间的包,以及服务端的状态变迁。

Wireshark依然显示的是TCP连接建立和终止的过程。

netstat命令

netstat是控制台命令,是一个监控TCP/IP网络的非常有用的工具,它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。

实验中的宿主机系统是Win7,下面看看通过 netatat /? 获得的帮助信息:

netstat命令失效?

虽然说上面的程序可以打印出服务端的状态变迁过程,但是这次让我们通过netstat命令查看一下。

为了方便查看,将"client.py"中的"time.sleep(5)"改为"time.sleep(300)",使客户端跟服务器之间的连接保持300秒。客户端的端口号为"1090"。

这时,分别在服务端和客户端cmd窗口中执行 netstat -anp TCP | findstr "192.168.56" 命令,查看包含"192.168.56"字符串的TCP连接:

服务端:

客户端:

为什么服务端看不到TCP连接?就像我们第一篇介绍的那样,Pcap.Net是不经过操作系统协议栈的,所以这也就解释了为什么"netstat"命令发现不了服务端的TCP连接。

等300秒结束后,客户端会发送终止连接请求。当连接终止后,可以看大客户端的TCP连接状态变成了"TIME_WAIT"。

客户端:

总结

本文中根据TCP状态变迁图,得到了服务端的状态变迁表。

然后使用Pcap.Net,基于服务端的状态变迁表,构建了一个简单的服务端,展示了服务端状态变迁的过程。

文中还简单的介绍了"netstat"命令,通过这个命令可以查看TCP连接的状态,结合这个命令,可以更好的了解TCP状态。

 

posted @ 2015-10-08 13:01  田小计划  阅读(1465)  评论(0编辑  收藏
Fork me on GitHub