前一段時間經過Wireshark抓包,定位了一個客戶端和服務器之間數據傳輸的問題。最近就抽空看了看《TCP/IP詳解 卷1》中關於TCP的部分,書中用了不少例子展現了TCP/IP協議中的一些基本概念。安全
因此,也準備本身動手,經過一些簡單的實驗來進一步瞭解一下TCP中的一些基本概念。服務器
在開始進行實驗以前,首先看看實驗環境的搭建:網絡
在創建好實驗環境以後,還須要進行一些簡單的配置,保證宿主機和虛擬機之間的網絡是暢通的。併發
將虛擬機網絡設置爲"Host-only Adapter"模式。app
虛擬機網絡設置好以後,就能夠配置本機和虛擬機IP地址了,而後保證宿主機能夠ping通虛擬機。socket
經過上面的步驟,簡單的實驗環境就創建完成了,下面就要來實現客戶端和服務端了,試試實驗環境是否可以正常工做。tcp
首先,將虛擬機(192.168.56.102)做爲服務端,運行下面一段代碼建立一個簡單的socket server,服務端綁定192.168.56.102:8081:函數
import sys from socket import * HOST = "192.168.56.102" PORT = 8081 BUFSIZ = 1024 ADDR = (HOST, PORT) server = socket(AF_INET, SOCK_STREAM) print "Socket created" try: server.bind(ADDR) except error, msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit() server.listen(10) print 'Socket now listening' while True: conn, addr = server.accept() try: data = conn.recv(100) if data: print data except Exception, e: print e conn.close()
客戶端的實如今本機(192.168.56.101),使用一段基於Pcap.Net的代碼向服務器發送一個[SYN]包(TCP鏈接創建須要進行三次握手,[SYN]包就是第一個握手包),來請求創建TCP鏈接。工具
在客戶端代碼中,經過Pcap.Net實現了兩個工具函數,一個用來獲取本機網卡設備列表,一個用在構造不一樣類型的TPC包。ui
獲取本機網卡設備列表代碼:
public static PacketDevice GetNICDevice() { // Retrieve the device list from the local machine IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine; if (allDevices.Count == 0) { Console.WriteLine("No interfaces found! Make sure WinPcap is installed."); return null; } // Print the device list for (int i = 0; i != allDevices.Count; ++i) { LivePacketDevice device = allDevices[i]; Console.Write((i + 1) + ". " + device.Name); if (device.Description != null) Console.WriteLine(" (" + device.Description + ")"); else Console.WriteLine(" (No description available)"); } int deviceIndex = 0; do { Console.WriteLine("Enter the interface number (1-" + allDevices.Count + "):"); string deviceIndexString = Console.ReadLine(); if (!int.TryParse(deviceIndexString, out deviceIndex) || deviceIndex < 1 || deviceIndex > allDevices.Count) { deviceIndex = 0; } } while (deviceIndex == 0); return allDevices[deviceIndex - 1]; }
另外一段重要的代碼就是構造TCP包的代碼,根據OSI七層模型,下面代碼中分別建立了鏈路層、網絡層和傳輸層的部分,而後生成一個數據包:
public static Packet BuildTcpPacket(EndPointInfo endPointInfo, TcpControlBits tcpControlBits, List<TcpOption> tcpOptionList = null) { EthernetLayer ethernetLayer = new EthernetLayer { Source = new MacAddress(endPointInfo.SourceMac), Destination = new MacAddress(endPointInfo.DestinationMac), EtherType = EthernetType.None, // Will be filled automatically. }; IpV4Layer ipV4Layer = new IpV4Layer { Source = new IpV4Address(endPointInfo.SourceIp), CurrentDestination = new IpV4Address(endPointInfo.DestinationIp), Fragmentation = IpV4Fragmentation.None, HeaderChecksum = null, // Will be filled automatically. Identification = 123, Options = IpV4Options.None, Protocol = null, // Will be filled automatically. Ttl = 10, TypeOfService = 0, }; TcpLayer tcpLayer = new TcpLayer { SourcePort = endPointInfo.SourcePort, DestinationPort = endPointInfo.DestinationPort, Checksum = null, // Will be filled automatically. SequenceNumber = seqNum, AcknowledgmentNumber = ackNum, ControlBits = tcpControlBits, Window = windowSize, UrgentPointer = 0, Options = (tcpOptionList == null) ? TcpOptions.None : new TcpOptions(tcpOptionList), }; PacketBuilder builder = new PacketBuilder(ethernetLayer, ipV4Layer, tcpLayer); return builder.Build(DateTime.Now); }
主程序中,首先配置了客戶端和服務器的端口、IP/MAC地址信息,而後經過前面兩個工具函數構造一個TCP鏈接創建請求包([SYN]包),並經過"VirtualBox Host-Only Network"網卡發送給服務端。
static void Main(string[] args) { // Take the selected adapter PacketDevice selectedDevice = Utils.GetNICDevice(); // 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 = "08:00:27:70:A6:AE"; endPointInfo.SourceIp = "192.168.56.101"; endPointInfo.DestinationIp = "192.168.56.102"; endPointInfo.SourcePort = 3330; endPointInfo.DestinationPort = 8081; using (BerkeleyPacketFilter filter = communicator.CreateFilter("tcp port " + endPointInfo.DestinationPort)) { // Set the filter communicator.SetFilter(filter); } communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Synchronize, null)); PacketHandler(communicator, endPointInfo); } Console.WriteLine("Press Enter to Quit!"); Console.ReadLine(); } private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo) { Packet packet = null; do { PacketCommunicatorReceiveResult result = communicator.ReceivePacket(out packet); switch (result) { case PacketCommunicatorReceiveResult.Timeout: // Timeout elapsed continue; case PacketCommunicatorReceiveResult.Ok: Utils.PacketInfoPrinter(packet); break; default: throw new InvalidOperationException("The result " + result + " should never be reached here"); } } while (true); }
代碼完成了,下面看看運行效果,爲了直觀的看到數據包的傳輸,這是就能夠打開Wireshark了。
爲了不抓到不相關的數據包,能夠設置Wireshark中的filter,而後開始抓取。
下面運行代碼,並選擇正確的網卡。經過console和Wireshark的輸出能夠看到,咱們成功的生產了一個[SYN]包併發送到了服務器。
根據TCP鏈接創建過程能夠知道,客戶端發送[SYN]包後,期待從服務器獲得一個[SYN, ACK]包。
到這裏,說明前面搭建的環境,以及客戶端和服務端的代碼都是能夠正常工做的了。
從上面的結果中看到,客戶端在收到[SYN, ACK]包以後,發送了一個[RST]包重置這條TCP鏈接。
仔細查看了代碼發現,客戶端的代碼中並無發送[RST]包。那麼這個[RST]包是哪裏來的呢?
操做系統中有協議棧的概念,因此來自應用層的數據,都會一層層的通過操做系統協議棧處理,而後經過網卡發送出去。
當客戶端網卡收到[SYN, ACK]包後,這個包會被咱們的Pcap.Net程序捕獲,也會被傳送給客戶端操做系統。因爲經過Pcap.Net構造的[SYN]包是沒有通過操做系統協議棧的,因此操做系統會認爲[SYN, ACK]包是一個無效TCP包,並經過[RST]包重置TCP鏈接。
到這裏,多餘[RST]包就能夠解釋了。
爲了不操做系統協議棧對Pcap.Net程序的影響,經過IP安全策略(經過Run->"secpol.msc"打開設置)的設置,能夠避免操做系統從本機(192.168.56.101)向虛擬機(192.168.56.102)發送數據包。
設置完成後,再次運行程序,這是程序就正常了。
因爲客戶端沒有發送[ACK]包來確認來自服務端的[SYN, ACK]包,根據TCP工做原理,服務端會進行重傳。
本文中介紹了TCP實驗環境的搭建,經過Pcap.Net建立了一個客戶端,能夠構造不一樣類型的TCP數據包,並經過特定網卡向服務器發送。
後面繼續基於這個環境來看看TCP的一些基本概念,TCP鏈接、狀態變遷等等。