動(dòng)手學(xué)習(xí)TCP系列之服務(wù)端狀態(tài)變遷
上一篇文章介紹了TCP狀態(tài)機(jī),并且通過(guò)實(shí)驗(yàn)了解了TCP客戶端正常的狀態(tài)變遷過(guò)程。
那么,本篇文章就一起看看TCP服務(wù)端的正常狀態(tài)變遷過(guò)程。
服務(wù)端狀態(tài)變遷
根據(jù)上一篇文章中的TCP狀態(tài)變遷圖,可以得到服務(wù)器的正常狀態(tài)變遷流程如下:
CLOSED -> LISTEN -> SYN_RECV -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
服務(wù)端狀態(tài)變遷實(shí)驗(yàn)
下面就結(jié)合上面分析出來(lái)的服務(wù)端狀態(tài)變遷表,利用Pcap.Net來(lái)模擬服務(wù)端正常的狀態(tài)變遷過(guò)程。
代碼實(shí)現(xiàn)
跟前面幾次正好相反,這次我們將在宿主機(jī)運(yùn)行Pcap.Net實(shí)現(xiàn)的服務(wù)端,然后在虛擬機(jī)運(yùn)行一個(gè)客戶端。
對(duì)于服務(wù)端,主程序中設(shè)置了源和目的端的連接信息,這次宿主機(jī)中的服務(wù)端將監(jiān)聽“3333”端口。
然后,程序中設(shè)置了服務(wù)端TCP初始狀態(tài)為"LISTENING",然后就直接運(yùn)行監(jiān)聽函數(shù)了。
// 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, endPointInf)
}
這次的監(jiān)聽函數(shù)"PacketHandler"中的邏輯,跟上一次客戶端的例子還是有很大差別的。
首先是期待接收和實(shí)際發(fā)送的TCP包類型有很大的差別,其次就是狀態(tài)之間的變遷是完全不同的。但是,代碼的邏輯依然是根據(jù)上面的服務(wù)端狀態(tài)變遷表。
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); } #p# 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);
}
對(duì)于客戶端,通過(guò)Python實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Socket程序來(lái)模擬客戶端行為:
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()
運(yùn)行效果
這次,宿主機(jī)上運(yùn)行的是服務(wù)端,虛擬機(jī)運(yùn)行的是客戶端,打開Wireshark監(jiān)聽"VirtualBox Host-Only Network"網(wǎng)卡,并設(shè)置filter為"port 3333"。
運(yùn)行服務(wù)端程序,服務(wù)端將處于監(jiān)聽狀態(tài)。這是在虛擬機(jī)中運(yùn)行"client.py"。這時(shí),通過(guò)服務(wù)端console可以看到客戶端和服務(wù)端之間的包,以及服務(wù)端的狀態(tài)變遷。
Wireshark依然顯示的是TCP連接建立和終止的過(guò)程。
netstat命令
netstat是控制臺(tái)命令,是一個(gè)監(jiān)控TCP/IP網(wǎng)絡(luò)的非常有用的工具,它可以顯示路由表、實(shí)際的網(wǎng)絡(luò)連接以及每一個(gè)網(wǎng)絡(luò)接口設(shè)備的狀態(tài)信息。netstat用于顯示與IP、TCP、UDP和ICMP協(xié)議相關(guān)的統(tǒng)計(jì)數(shù)據(jù),一般用于檢驗(yàn)本機(jī)各端口的網(wǎng)絡(luò)連接情況。
實(shí)驗(yàn)中的宿主機(jī)系統(tǒng)是Win7,下面看看通過(guò) netatat /? 獲得的幫助信息:
netstat命令失效?
雖然說(shuō)上面的程序可以打印出服務(wù)端的狀態(tài)變遷過(guò)程,但是這次讓我們通過(guò)netstat命令查看一下。
為了方便查看,將"client.py"中的"time.sleep(5)"改為"time.sleep(300)",使客戶端跟服務(wù)器之間的連接保持300秒??蛻舳说亩丝谔?hào)為"1090"。
這時(shí),分別在服務(wù)端和客戶端cmd窗口中執(zhí)行 netstat -anp TCP | findstr "192.168.56" 命令,查看包含"192.168.56"字符串的TCP連接:
服務(wù)端:
客戶端:
為什么服務(wù)端看不到TCP連接?就像我們***篇介紹的那樣,Pcap.Net是不經(jīng)過(guò)操作系統(tǒng)協(xié)議棧的,所以這也就解釋了為什么"netstat"命令發(fā)現(xiàn)不了服務(wù)端的TCP連接。
等300秒結(jié)束后,客戶端會(huì)發(fā)送終止連接請(qǐng)求。當(dāng)連接終止后,可以看大客戶端的TCP連接狀態(tài)變成了"TIME_WAIT"。
客戶端:
總結(jié)
本文中根據(jù)TCP狀態(tài)變遷圖,得到了服務(wù)端的狀態(tài)變遷表。
然后使用Pcap.Net,基于服務(wù)端的狀態(tài)變遷表,構(gòu)建了一個(gè)簡(jiǎn)單的服務(wù)端,展示了服務(wù)端狀態(tài)變遷的過(guò)程。
文中還簡(jiǎn)單的介紹了"netstat"命令,通過(guò)這個(gè)命令可以查看TCP連接的狀態(tài),結(jié)合這個(gè)命令,可以更好的了解TCP狀態(tài)。