當前位置:首頁 » 操作系統 » 報文解析源碼

報文解析源碼

發布時間: 2023-05-19 23:45:15

❶ Android socket源碼解析(三)socket的connect源碼解析

上一篇文章著重的聊了socket服務端的bind,listen,accpet的邏輯。本文來著重聊聊connect都做了什麼?

如果遇到什麼問題,可以來本文 https://www.jianshu.com/p/da6089fdcfe1 下討論

當服務端一切都准備好了。客戶端就會嘗試的通過 connect 系統調用,嘗試的和服務端建立遠程連接。

首先校驗當前socket中是否有正確的目標地址。然後獲取IP地址和埠調用 connectToAddress 。

在這個方法中,能看到有一個 NetHooks 跟蹤socket的調用,也能看到 BlockGuard 跟蹤了socket的connect調用。因此可以hook這兩個地方跟蹤socket,不過很少用就是了。

核心方法是 socketConnect 方法,這個方法就是調用 IoBridge.connect 方法。同理也會調用到jni中。

能看到也是調用了 connect 系統調用。

文件:/ net / ipv4 / af_inet.c

在這個方法中做的事情如下:

注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .

文件:/ net / ipv4 / tcp_ipv4.c

本質上核心任務有三件:

想要能夠理解下文內容,先要明白什麼是路由表。

路由表分為兩大類:

每個路由器都有一個路由表(RIB)和轉發表 (fib表),路由表用於決策路由,轉發表決策轉發分組。下文會接觸到這兩種表。

這兩個表有什麼區別呢?

網上雖然給了如下的定義:

但實際上在Linux 3.8.1中並沒有明確的區分。整個路由相關的邏輯都是使用了fib轉發表承擔的。

先來看看幾個和FIB轉發表相關的核心結構體:

熟悉Linux命令朋友一定就能認出這裡面大部分的欄位都可以通過route命令查找到。

命令執行結果如下:

在這route命令結果的欄位實際上都對應上了結構體中的欄位含義:

知道路由表的的內容後。再來FIB轉發表的內容。實際上從下面的源碼其實可以得知,路由表的獲取,實際上是先從fib轉發表的路由字典樹獲取到後在同感加工獲得路由表對象。

轉發表的內容就更加簡單

還記得在之前總結的ip地址的結構嗎?

需要進行一次tcp的通信,意味著需要把ip報文准備好。因此需要決定源ip地址和目標IP地址。目標ip地址在之前通過netd查詢到了,此時需要得到本地發送的源ip地址。

然而在實際情況下,往往是面對如下這么情況:公網一個對外的ip地址,而內網會被映射成多個不同內網的ip地址。而這個過程就是通過DDNS動態的在內存中進行更新。

因此 ip_route_connect 實際上就是選擇一個緩存好的,通過DDNS設置好的內網ip地址並找到作為結果返回,將會在之後發送包的時候填入這些存在結果信息。而查詢內網ip地址的過程,可以成為RTNetLink。

在Linux中有一個常用的命令 ifconfig 也可以實現類似增加一個內網ip地址的功能:

比如說為網卡eth0增加一個IPV6的地址。而這個過程實際上就是調用了devinet內核模塊設定好的添加新ip地址方式,並在回調中把該ip地址刷新到內存中。

注意 devinet 和 RTNetLink 嚴格來說不是一個存在同一個模塊。雖然都是使用 rtnl_register 注冊方法到rtnl模塊中:

文件:/ net / ipv4 / devinet.c

文件:/ net / ipv4 / route.c

實際上整個route模塊,是跟著ipv4 內核模塊一起初始化好的。能看到其中就根據不同的rtnl操作符號注冊了對應不同的方法。

整個DDNS的工作流程大體如下:

當然,在tcp三次握手執行之前,需要得到當前的源地址,那麼就需要通過rtnl進行查詢內存中分配的ip。

文件:/ include / net / route.h

這個方法核心就是 __ip_route_output_key .當目的地址或者源地址有其一為空,則會調用 __ip_route_output_key 填充ip地址。目的地址為空說明可能是在回環鏈路中通信,如果源地址為空,那個說明可能往目的地址通信需要填充本地被DDNS分配好的內網地址。

在這個方法中核心還是調用了 flowi4_init_output 進行flowi4結構體的初始化。

文件:/ include / net / flow.h

能看到這個過程把數據中的源地址,目的地址,源地址埠和目的地址埠,協議類型等數據給記錄下來,之後內網ip地址的查詢與更新就會頻繁的和這個結構體進行交互。

能看到實際上 flowi4 是一個用於承載數據的臨時結構體,包含了本次路由操作需要的數據。

執行的事務如下:

想要弄清楚ip路由表的核心邏輯,必須明白路由表的幾個核心的數據結構。當然網上搜索到的和本文很可能大為不同。本文是基於LInux 內核3.1.8.之後的設計幾乎都沿用這一套。

而內核將路由表進行大規模的重新設計,很大一部分的原因是網路環境日益龐大且復雜。需要全新的方式進行優化管理系統中的路由表。

下面是fib_table 路由表所涉及的數據結構:

依次從最外層的結構體介紹:

能看到路由表的存儲實際上通過字典樹的數據結構壓縮實現的。但是和常見的字典樹有點區別,這種特殊的字典樹稱為LC-trie 快速路由查找演算法

這一篇文章對於快速路由查找演算法的理解寫的很不錯: https://blog.csdn.net/dog250/article/details/6596046

首先理解字典樹:字典樹簡單的來說,就是把一串數據化為二進制格式,根據左0,右1的方式構成的。

如圖下所示:

這個過程用圖來展示,就是沿著字典樹路徑不斷向下讀,比如依次讀取abd節點就能得到00這個數字。依次讀取abeh就能得到010這個數字。

說到底這種方式只是存儲數據的一種方式。而使用數的好處就能很輕易的找到公共前綴,在字典樹中找到公共最大子樹,也就找到了公共前綴。

而LC-trie 則是在這之上做了壓縮優化處理,想要理解這個演算法,必須要明白在 tnode 中存在兩個十分核心的數據:

這負責什麼事情呢?下面就簡單說說整個lc-trie的演算法就能明白了。

當然先來看看方法 __ip_dev_find 是如何查找

文件:/ net / ipv4 / fib_trie.c

整個方法就是通過 tkey_extract_bits 生成tnode中對應的葉子節點所在index,從而通過 tnode_get_child_rcu 拿到tnode節點中index所對應的數組中獲取葉下一級別的tnode或者葉子結點。

其中查找index最為核心方法如上,這個過程,先通過key左移動pos個位,再向右邊移動(32 - bits)演算法找到對應index。

在這里能對路由壓縮演算法有一定的理解即可,本文重點不在這里。當從路由樹中找到了結果就返回 fib_result 結構體。

查詢的結果最為核心的就是 fib_table 路由表,存儲了真正的路由轉發信息

文件:/ net / ipv4 / route.c

這個方法做的事情很簡單,本質上就是想要找到這個路由的下一跳是哪裡?

在這裡面有一個核心的結構體名為 fib_nh_exception 。這個是指fib表中去往目的地址情況下最理想的下一跳的地址。

而這個結構體在上一個方法通過 find_exception 獲得.遍歷從 fib_result 獲取到 fib_nh 結構體中的 nh_exceptions 鏈表。從這鏈表中找到一模一樣的目的地址並返回得到的。

文件:/ net / ipv4 / tcp_output.c

❷ OkHttp源碼分析:五大攔截器詳解

主要完成兩件事: 重試與重定向

重試與重定向攔截器主要處理Response,可以看到RouteException和IOException都是調用了recover,返回true表示允許重試。允許重試—>continue—> while (true)—>realChain.proceed,這就完成了重試的過程。

接著看重定向

重定向總結

另附HTTP響應狀態碼分類:

小結: RetryAndFollowUpInterceptor是整個責任鏈中的第一個,首次接觸到Request和最後接收Response的角色,它的主要功能是判斷是否需要重試與重定向。

重試的前提是出現了RouteException或IOException,會通過recover方法進行判斷是否進行重試。

重定向是發生在重試判定後,不滿足重試的條件,會進一步調用followUpRequest根據Response的響應碼進行重定向操作。

補全請求頭:

小結: BridgeInterceptor是連接應用程序和伺服器的橋梁,它為我們補全請求頭,將請求轉化為符合網路規范的Request。得到響應後:1.保存Cookie,在下次請求會讀取對應的cookie數據設置進請求頭,默認cookieJar不提供的實現 2.如果使用gzip返回的數據,則使用 GzipSource 包裝便於解析。

緩存攔截器顧名思義處理緩存的,但是要建立在get請求的基礎上,我們可以去通過okHttpClient.cache(cache)去設置。緩存攔截器的處理流程:

1.從緩存中取出對應請求的響應緩存

2.通過CacheStrategy判斷使用緩存或發起網路請求,此對象中的networkRequest代表需要發起網路請求,cacheResponse表示直接使用緩存。

即: networkRequest存在則優先發起網路請求,否則使用cacheResponse緩存,若都不存在則請求失敗。

如果最終判定不能使用緩存,需要發起網路請求,則來到下一個攔截器ConnectInterceptor

StreamAllocation對象是在第一個攔截器RetryAndFollowUpInterceptor中初始化完成的(設置了連接池、url路徑等),當一個請求發出,需要建立連接,建立連接之後需要使用流來讀取數據,這個StreamAllocation就是協調請求、連接與數據流三者之前的關系,它負責為一次請求尋找連接,然後獲得流來實現網路通信。

StreamAllocation對象有兩個關鍵角色:

真正的連接是在RealConnection中實現的,連接由ConnectionPool管理。

接著我們看下RealConnection的創建和連接的建立:

streamAllocation.newStream—>findHealthyConnection—>findConnection

findConnection:

①StreamAllocation的connection如果可以復用則復用

②如果connection不能復用,則從連接池中獲取RealConnection對象,獲取成功則返回

③如果連接池裡沒有,則new一個RealConnection對象

④調用RealConnection的connect()方法發起請求

⑤將RealConnection對象存進連接池中,以便下次復用

⑥返回RealConnection對象

小結:

ConnectInterceptor攔截器從攔截器鏈中獲取StreamAllocation對象,這個對象在第一個攔截器中創建,在ConnectInterceptor中才用到。

執行StreamAllocation對象的newStream方法創建HttpCodec對象,用來編碼HTTP request和解碼HTTP response。

newStream方法裡面通過findConnection方法返回了一個RealConnection對象。

StreamAllocation對象的connect方法拿到上面返回的RealConnection對象,這個RealConnection對象是用來進行實際的網路IO傳輸的。

writeRequestHeaders和readResponseHeaders(以Http2Codec為例)

小結: CallServerInterceptor完成HTTP協議報文的封裝和解析。

①獲取攔截器鏈中的HttpCodec、StreamAllocation、RealConnection對象

②調用httpCodec.writeRequestHeaders(request)將請求頭寫入緩存

③判斷是否有請求體,如果有,請求頭通過攜帶特殊欄位 Expect:100-continue來詢問伺服器是否願意接受請求體。(一般用於上傳大容量請求體或者需要驗證)

④通過httpCodec.finishRequest()結束請求

⑤通過responseBuilder構建Response

⑥返回Response

❸ heapbuffer報文java怎麼解析

heap buffer 和 direct buffer區別

在Java的NIO中,我們一般採用ByteBuffer緩沖區來傳輸數據,一般情況下我們創建Buffer對象是通過ByteBuffer的兩個靜態方法:

ByteBuffer.allocate(int capacity);
ByteBuffer.wrap(byte[] array);

查看JDK的NIO的源代碼關於這兩個部分:

/**allocate()函數的源碼**/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}

/**wrap()函數的源碼**/
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
//
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}

我們可以很清楚的發現,這兩個方法都是實例化HeapByteBuffer來創建的ByteBuffer對象,也就是heap buffer. 其實除了heap buffer以外還有一種buffer,叫做direct buffer。我們也可以創建這一種buffer,通過ByteBuffer.allocateDirect(int capacity)方法,查看JDK源碼如下:

public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

我們發現該函數調用的是DirectByteBuffer(capacity)這個類,這個類就是創建了direct buffer。

❹ OkHttp源碼解析 (三)——代理和路由

初看OkHttp源碼,由於對Address、Route、Proxy、ProxySelector、RouteSelector等理解不夠,讀源碼非常吃力,看了幾遍依然對於尋找復用連接、創建連接、連接伺服器、連接代理伺服器、創建隧道連接等邏輯似懂非懂,本篇決定梳理一遍相關的概念及基本原理。

● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY

一個http請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,http請求用80;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後,發送http報文數據。

一個https請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,https請求用443;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後進行TLS握手,可通過java標准款提供的SSLSocket完成;
5、握手成功後,發送https報文數據。

1、分類
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5

2、HTTP代理分類及說明
普通代理
HTTP/1.1 協議的第一部分。其代理過程為:
● client 請求 proxy
● proxy 解析請求獲取 origin server 地址
● proxy 向 origin server 轉發請求
● proxy 接收 origin server 的響應
● proxy 向 client 轉發響應
其中proxy獲取目的伺服器地址的標准方法是解析 request line 里的 request-URL。因為proxy需要解析報文,因此普通代理無法適用於https,因為報文都是加密的。

隧道代理
通過 Web 代理伺服器用隧道方式傳輸基於 TCP 的協議。
請求包括兩個階段,一是連接(隧道)建立階段,二是數據通信(請求響應)階段,數據通信是基於 TCP packet ,代理伺服器不會對請求及響應的報文作任何的處理,都是原封不動的轉發,因此可以代理 HTTPS請求和響應。
代理過程為:
● client 向 proxy 發送 CONNET 請求(包含了 origin server 的地址)
● proxy 與 origin server 建立 TCP 連接
● proxy 向 client 發送響應
● client 向 proxy 發送請求,proxy 原封不動向 origin server 轉發請求,請求數據不做任何封裝,為原生 TCP packet.

3、SOCKS代理分類及說明
● SOCKS4:只支持TCP協議(即傳輸控制協議)
● SOCKS5: 既支持TCP協議又支持UDP協議(即用戶數據包協議),還支持各種身份驗證機制、伺服器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反過來卻不行,比如我們常用的聊天工具QQ在使用代理時就要求用SOCKS5代理,因為它需要使用UDP協議來傳輸數據。

有了上面的基礎知識,下面分析結合源碼分析OkHttp路由相關的邏輯。OkHttp用Address來描述與目標伺服器建立連接的配置信息,但請求輸入的可能是域名,一個域名可能對於多個ip,真正建立連接是其中一個ip,另外,如果設置了代理,客戶端是與代理伺服器建立直接連接,而不是目標伺服器,代理又可能是域名,可能對應多個ip。因此,這里用Route來描述最終選擇的路由,即客戶端與哪個ip建立連接,是代理還是直連。下面對比下Address及Route的屬性,及路由選擇器RouteSelector。

描述與目標伺服器建立連接所需要的配置信息,包括目標主機名、埠、dns,SocketFactory,如果是https請求,包括TLS相關的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理伺服器信息Proxy 、ProxySelector 。

Route提供了真正連接伺服器所需要的動態信息,明確需要連接的伺服器IP地址及代理伺服器,一個Address可能會有很多個路由Route供選擇(一個DNS對應對個IP)。

Address和Route都是數據對象,沒有提供操作方法,OkHttp另外定義了RouteSelector來完成選擇的路由的操作。

1、讀取代理配置信息:resetNextProxy()

讀取代理配置:
● 如果有指定代理(不讀取系統配置,在OkHttpClient實例中指定),則只用1個該指定代理;
● 如果沒有指定,則讀取系統配置的,可能有多個。

2、獲取需要嘗試的socket地址(目標伺服器或者代理伺服器):resetNextInetSocketAddress()

結合Address的host和代理,解析要嘗試的套接字地址(ip+埠)列表:
● 直連或者SOCK代理, 則用目標伺服器的主機名和埠,如果是HTTP代理,則用代理伺服器的主機名和埠;
● 如果是SOCK代理,根據目標伺服器主機名和埠號創建未解析的套接字地址,列表只有1個地址;
● 如果是直連或HTTP代理,先DNS解析,得到InetAddress列表(沒有埠),再創建InetSocketAddress列表(帶上埠),InetSocketAddress與InetAddress的區別是前者帶埠信息。

3、獲取路由列表:next()

選擇路由的流程解析:
● 遍歷每個代理對象,可能多個,直連的代理對象為Proxy.DIRECT(實際是沒有中間代理的);
● 對每個代理獲取套接字地址列表;
● 遍歷地址列表,創建Route,判斷Route如果在路由黑名單中,則添加到失敗路由列表,不在黑名單中則添加到待返回的Route列表;
● 如果最後待返回的Route列表為空,即可能所有路由都在黑名單中,實在沒有新路由了,則將失敗的路由集合返回;
● 傳入Route列表創建Selection對象,對象比較簡單,就是一個目標路由集合,及讀取方法。

為了避免不必要的嘗試,OkHttp會把連接失敗的路由加入到黑名單中,由RouteDatabase管理,該類比較簡單,就是一個失敗路由集合。

1、創建Address
Address的創建在RetryAndFollowUpInteceptor里,每次請求會聲明一個新的Address及StreamAllocation對象,而StreamAllocation使用Address創建RouteSelector對象,在連接時RouteSelector確定請求的路由。

每個Requst都會構造一個Address對象,構造好了Address對象只是有了與伺服器連接的配置信息,但沒有確定最終伺服器的ip,也沒有確定連接的路由。

2、創建RouteSelector
在StreamAllocation聲明的同時會聲明路由選擇器RouteSelector,為一次請求尋找路由。

3、選擇可用的路由Route

下面在測試過程跟蹤實例對象來理解,分別測試直連和HTTP代理HTTP2請求路由的選擇過程:
● 直連請求流程
● HTTP代理HTTPS流程
請求url: https://www.jianshu.com/p/63ba15d8877a

1、構造address對象

2、讀取代理配置:resetNextProxy

3、解析目標伺服器套接字地址:resetNextInetSocketAddress

4、選擇Route創建RealConnection

5、確定協議

測試方法:
● 在PC端打開Charles,設置埠,如何設置代理,網上有教程,比較簡單;
● 手機打開WIFI,選擇連接的WIFI修改網路,在高級選項中設置中指定了代理伺服器,ip為PC的ip,埠是Charles剛設置的埠;
● OkHttpClient不指定代理,發起請求。

1、構造address對象

2、讀取代理配置:resetNextProxy

3、解析目標伺服器套接字地址:resetNextInetSocketAddress

4、選擇Route創建RealConnection

5、創建隧道
由於是代理https請求,需要用到隧道代理。

從圖可以看出,建立隧道其實是發送CONNECT請求,header包括欄位Proxy-Connection,目標主機名,請求內容類似:

6、確定協議,SSL握手

1、代理可分為HTTP代理和SOCK代理;
2、HTTP代理又分為普通代理和隧道代理;普通代理適合明文傳輸,即http請求;隧道代理僅轉發TCP包,適合加密傳輸,即https/http2;
3、SOCK代理又分為SOCK4和SOCK5,區別是後者支持UDP傳輸,適合代理聊天工具如QQ;
4、沒有設置代理(OkHttpClient沒有指定同時系統也沒有設置),客戶端直接與目標伺服器建立TCP連接;
5、設置了代理,代理http請求時,客戶端與代理伺服器建立TCP連接,如果代理伺服器是域名,則解釋代理伺服器域名,而目標伺服器的域名由代理伺服器解析;
6、設置了代理,代理https/http2請求時,客戶端與代理伺服器建立TCP連接,發送CONNECT請求與代理伺服器建立隧道,並進行SSL握手,代理伺服器不解析數據,僅轉發TCP數據包。

如何正確使用 HTTP proxy
OkHttp3中的代理與路由
HTTP 代理原理及實現(一)

❺ 求C# socket 封包拆包源碼

看這個題目就知道你對「協議」的概念還不了解。
所謂的封包和拆包,值得是在指定協議下,將若干個不同數據類型的值整合到一個報文幀裡面。
拆包是封包的反向過程,將報文幀還原為原始數據。
不論是封包也好拆包也好,都必然是在指定協議下進行的。
脫離協議約定,這兩個動作是沒有意義的,無法獨立存在。

❻ http協議解析 請求行的信息怎麼提取 c語言源碼

實現步驟:
1)用Wireshark軟體抓包得到test.pcap文件
2)程序:分析pcap文件頭 -> 分析pcap_pkt頭 -> 分析幀頭 -> 分析ip頭 -> 分析tcp頭 -> 分析http信息
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<time.h>
#define BUFSIZE 10240
#define STRSIZE 1024
typedef long bpf_int32;
typedef unsigned long bpf_u_int32;
typedef unsigned short u_short;
typedef unsigned long u_int32;
typedef unsigned short u_int16;
typedef unsigned char u_int8;
//pacp文件頭結構體
struct pcap_file_header
{
bpf_u_int32 magic; /* 0xa1b2c3d4 */
u_short version_major; /* magjor Version 2 */
u_short version_minor; /* magjor Version 4 */
bpf_int32 thiszone; /* gmt to local correction */
bpf_u_int32 sigfigs; /* accuracy of timestamps */
bpf_u_int32 snaplen; /* max length saved portion of each pkt */
bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */
};
//時間戳
struct time_val
{
long tv_sec; /* seconds 含義同 time_t 對象的值 */
long tv_usec; /* and microseconds */
};
//pcap數據包頭結構體
struct pcap_pkthdr
{
struct time_val ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
//數據幀頭
typedef struct FramHeader_t
{ //Pcap捕獲的數據幀頭
u_int8 DstMAC[6]; //目的MAC地址
u_int8 SrcMAC[6]; //源MAC地址
u_short FrameType; //幀類型
} FramHeader_t;
//IP數據報頭
typedef struct IPHeader_t
{ //IP數據報頭
u_int8 Ver_HLen; //版本+報頭長度
u_int8 TOS; //服務類型
u_int16 TotalLen; //總長度
u_int16 ID; //標識
u_int16 Flag_Segment; //標志+片偏移
u_int8 TTL; //生存周期
u_int8 Protocol; //協議類型
u_int16 Checksum; //頭部校驗和
u_int32 SrcIP; //源IP地址
u_int32 DstIP; //目的IP地址
} IPHeader_t;
//TCP數據報頭
typedef struct TCPHeader_t
{ //TCP數據報頭
u_int16 SrcPort; //源埠
u_int16 DstPort; //目的埠
u_int32 SeqNO; //序號
u_int32 AckNO; //確認號
u_int8 HeaderLen; //數據報頭的長度(4 bit) + 保留(4 bit)
u_int8 Flags; //標識TCP不同的控制消息
u_int16 Window; //窗口大小
u_int16 Checksum; //校驗和
u_int16 UrgentPointer; //緊急指針
}TCPHeader_t;
//
void match_http(FILE *fp, char *head_str, char *tail_str, char *buf, int total_len); //查找 http 信息函數
//
int main()
{
struct pcap_file_header *file_header;
struct pcap_pkthdr *ptk_header;
IPHeader_t *ip_header;
TCPHeader_t *tcp_header;
FILE *fp, *output;
int pkt_offset, i=0;
int ip_len, http_len, ip_proto;
int src_port, dst_port, tcp_flags;
char buf[BUFSIZE], my_time[STRSIZE];
char src_ip[STRSIZE], dst_ip[STRSIZE];
char host[STRSIZE], uri[BUFSIZE];
//初始化
file_header = (struct pcap_file_header *)malloc(sizeof(struct pcap_file_header));
ptk_header = (struct pcap_pkthdr *)malloc(sizeof(struct pcap_pkthdr));
ip_header = (IPHeader_t *)malloc(sizeof(IPHeader_t));
tcp_header = (TCPHeader_t *)malloc(sizeof(TCPHeader_t));
memset(buf, 0, sizeof(buf));
//
if((fp = fopen(「test.pcap」,」r」)) == NULL)
{
printf(「error: can not open pcap file\n」);
exit(0);
}
if((output = fopen(「output.txt」,」w+」)) == NULL)
{
printf(「error: can not open output file\n」);
exit(0);
}
//開始讀數據包
pkt_offset = 24; //pcap文件頭結構 24個位元組
while(fseek(fp, pkt_offset, SEEK_SET) == 0) //遍歷數據包
{
i++;
//pcap_pkt_header 16 byte
if(fread(ptk_header, 16, 1, fp) != 1) //讀pcap數據包頭結構
{
printf(「\nread end of pcap file\n」);
break;
}
pkt_offset += 16 + ptk_header->caplen; //下一個數據包的偏移值
strftime(my_time, sizeof(my_time), 「%Y-%m-%d %T」, localtime(&(ptk_header->ts.tv_sec))); //獲取時間
// printf(「%d: %s\n」, i, my_time);
//數據幀頭 14位元組
fseek(fp, 14, SEEK_CUR); //忽略數據幀頭
//IP數據報頭 20位元組
if(fread(ip_header, sizeof(IPHeader_t), 1, fp) != 1)
{
printf(「%d: can not read ip_header\n」, i);
break;
}
inet_ntop(AF_INET, (void *)&(ip_header->SrcIP), src_ip, 16);
inet_ntop(AF_INET, (void *)&(ip_header->DstIP), dst_ip, 16);
ip_proto = ip_header->Protocol;
ip_len = ip_header->TotalLen; //IP數據報總長度
// printf(「%d: src=%s\n」, i, src_ip);
if(ip_proto != 0×06) //判斷是否是 TCP 協議
{
continue;
}
//TCP頭 20位元組
if(fread(tcp_header, sizeof(TCPHeader_t), 1, fp) != 1)
{
printf(「%d: can not read ip_header\n」, i);
break;
}
src_port = ntohs(tcp_header->SrcPort);
dst_port = ntohs(tcp_header->DstPort);
tcp_flags = tcp_header->Flags;
// printf(「%d: src=%x\n」, i, tcp_flags);
if(tcp_flags == 0×18) // (PSH, ACK) 3路握手成功後
{
if(dst_port == 80) // HTTP GET請求
{
http_len = ip_len – 40; //http 報文長度
match_http(fp, 「Host: 「, 「\r\n」, host, http_len); //查找 host 值
match_http(fp, 「GET 「, 「HTTP」, uri, http_len); //查找 uri 值
sprintf(buf, 「%d: %s src=%s:%d dst=%s:%d %s%s\r\n」, i, my_time, src_ip, src_port, dst_ip, dst_port, host, uri);
//printf(「%s」, buf);
if(fwrite(buf, strlen(buf), 1, output) != 1)
{
printf(「output file can not write」);
break;
}
}
}
} // end while
fclose(fp);
fclose(output);
return 0;
}
//查找 HTTP 信息
void match_http(FILE *fp, char *head_str, char *tail_str, char *buf, int total_len)
{
int i;
int http_offset;
int head_len, tail_len, val_len;
char head_tmp[STRSIZE], tail_tmp[STRSIZE];
//初始化
memset(head_tmp, 0, sizeof(head_tmp));
memset(tail_tmp, 0, sizeof(tail_tmp));
head_len = strlen(head_str);
tail_len = strlen(tail_str);
//查找 head_str
http_offset = ftell(fp); //記錄下HTTP報文初始文件偏移
while((head_tmp[0] = fgetc(fp)) != EOF) //逐個位元組遍歷
{
if((ftell(fp) – http_offset) > total_len) //遍歷完成
{
sprintf(buf, 「can not find %s \r\n」, head_str);
exit(0);
}
if(head_tmp[0] == *head_str) //匹配到第一個字元
{
for(i=1; i<head_len; i++) //匹配 head_str 的其他字元
{
head_tmp[i]=fgetc(fp);
if(head_tmp[i] != *(head_str+i))
break;
}
if(i == head_len) //匹配 head_str 成功,停止遍歷
break;
}
}
// printf(「head_tmp=%s \n」, head_tmp);
//查找 tail_str
val_len = 0;
while((tail_tmp[0] = fgetc(fp)) != EOF) //遍歷
{
if((ftell(fp) – http_offset) > total_len) //遍歷完成
{
sprintf(buf, 「can not find %s \r\n」, tail_str);
exit(0);
}
buf[val_len++] = tail_tmp[0]; //用buf 存儲 value 直到查找到 tail_str
if(tail_tmp[0] == *tail_str) //匹配到第一個字元
{
for(i=1; i<tail_len; i++) //匹配 head_str 的其他字元
{
tail_tmp[i]=fgetc(fp);
if(tail_tmp[i] != *(tail_str+i))
break;
}
if(i == tail_len) //匹配 head_str 成功,停止遍歷
{
buf[val_len-1] = 0; //清除多餘的一個字元
break;
}
}
}
// printf(「val=%s\n」, buf);
fseek(fp, http_offset, SEEK_SET); //將文件指針 回到初始偏移
}

熱點內容
liststringjava 發布:2025-04-23 02:56:18 瀏覽:406
asi源碼 發布:2025-04-23 02:46:45 瀏覽:576
小候編程 發布:2025-04-23 02:46:41 瀏覽:559
網路工程師使用哪些軟體寫腳本 發布:2025-04-23 02:28:43 瀏覽:458
c語言短路現象 發布:2025-04-23 02:23:54 瀏覽:302
可運行腳本怎麼寫 發布:2025-04-23 02:23:09 瀏覽:324
安卓死亡空間怎麼飛行 發布:2025-04-23 02:17:21 瀏覽:545
安卓機怎麼設置語音開機 發布:2025-04-23 02:08:01 瀏覽:485
mysql存儲過程事務控制 發布:2025-04-23 02:02:04 瀏覽:652
伺服器ip承載量 發布:2025-04-23 01:53:37 瀏覽:595