Tcpdump一次抓包記錄(Postgresql通訊)
資料庫版本:postgresql 9.2.1
作者:高銘傑
郵箱:[email protected]
日期:2016年7月14日
1 背景
最近在除錯一個gsql灌資料時的卡死問題,問題的跟因還沒有分析清楚,和社群上的一個問題十分類似:
BUG #6342: libpq blocks forever in “poll” function
Hi,
I have a big and strange problem. Sometimes, libpq remains blocked in “poll”
function even if the server has already answered to the query. If I attach
to the process using kdbg I found this stack:__kernel_vsyscall()
poll() from /lib/libc.so.6
pqSocketCheck() from /home/pg/pgsql/lib-32/libpq.so.5
pqWaitTimed() from /home/pg/pgsql/lib-32/libpq.so.5
pqWait() from /home/pg/pgsql/lib-32/libpq.so.5
PQgetResult() from /home/pg/pgsql/lib-32/libpq.so.5
PQexecFinish() from /home/pg/pgsql/lib-32/libpq.so.5
為了分析清楚這類問題,這裡記錄一下使用tcpdump的方式去分析pg的通訊過程。
2 Tcpdump安裝
- 下載tcpdump-4.6.2.tar.gz libpcap-1.6.2.tar.gz
- 編譯安裝libpcap
- 編譯安裝tcpdump
或
sudo yum search tcpdump
sudo yum install tcpdump.xxx
3 Tcpdump 引數
NAME
tcpdump - dump traffic on a network
SYNOPSIS
tcpdump [ -AbdDefhHIJKlLnNOpqRStuUvxX ][ -B buffer_size ][ -c count ][ -C file_size ][ -G rotate_seconds ][ -F file ][ -i interface ][ -j tstamp_type ][ -m module ][ -M secret ][ -P in|out|inout ][ -r file ][ -V file ][ -s snaplen ][ -T type ][ -w file ][ -W filecount ][ -E [email protected] algo:secret,... ][ -y datalinktype ][ -z postrotate-command ][ -Z user ][ expression ]
4 使用Tcpdump除錯PostgreSQL
連線資料庫,注意使用gsql -h的方式,否則直接連線會建立程序通訊用的本地socket,這樣是無法使用Tcpdump抓包的:
連線資料庫後,確認TCP連線已經建立,我的資料庫port=8473:
現在就可以使用tcpdump進行抓包了:
sudo tcpdump tcp port 8473 -i lo -s0 -nnX
執行後,在資料庫中執行SQL:drop table TEST;
這裡看到Tcpdump共抓到了3個TCP包,連線沒有加密,所以部分指令以明文顯示。
1、psql傳送drop table TEST。
23:44:08.563717 IP 127.0.0.1.53757 > 127.0.0.1.8473
2、資料庫後端返回結果。
23:44:08.570911 IP 127.0.0.1.8473 > 127.0.0.1.53757
3、psql向後端發包。
23:44:08.570942 IP 127.0.0.1.53757 > 127.0.0.1.8473
(20160714以上)
5 分析資料(非連線池)
5.1 執行SQL
select * from test;
5.2 抓包情況
抓到了三個包,這裡只引出分析方法,不同的SQL有不同的通訊策略。、
方向:
內容:
5.2.1 第一個包[PSH,ACK][Q]
51開頭的高亮部分是資料庫填充的資料,前四行和第五行的60 94是IP和TCP報文。(TCP/IP報頭分析的一篇部落格)
資料庫填充的資料為:
|5100這裡開始為資料庫填充的資料
0x0030: 0002 6094 5100 0000 1873 656c 6563 7420 ..`.Q....select.
0x0040: 2a20 6672 6f6d 2074 6573 743b 00 *.from.test;.
訊息的第一個位元組標識訊息型別,然後後面跟著的四個位元組宣告訊息剩下部分的長度(這個長度包括長度域自身,但是不包括訊息型別位元組)。請根據訊息型別去手冊中查詢對應的包,這裡第一個位元組為Q決定了訊息的格式:
Query (F)
Byte1(’Q’)
標識訊息是一個簡單查詢。
Int32
以位元組計的訊息內容長度,包括長度本身。
String
查詢字串自身
5.2.1 第二個包[PSH,ACK][T]
SQL執行結果:
postgres=# select * from test;
i
---
1
(1 row)
Byte1(’T’)
標識訊息是一個行描述
Int32
以位元組計的訊息內容長度,包括長度本身。
Int16
宣告在一個行裡面的欄位數目(可以為零)
然後對於每個欄位,有下面的東西:
String
欄位名字
Int32
如果欄位可以標識為一個特定表的欄位,那麼就是表的物件ID;否則就是零。
Int16
如果該欄位可以標識為一個特定表的欄位,那麼就是該表字段的屬性號;否則就是零。
Int32
欄位資料型別的物件ID
Int16
資料型別尺寸(參閱pg_type.typlen)。請注意負數表示變寬型別。
Int32
型別修飾詞(參閱pg_attribute.atttypmod)。修飾詞的含義是型別相關的。
Int16
用於該欄位的格式碼。目前會是0(文字)或者1(二進位制)。
從語句的Describe 返回的RowDescription裡,格式碼還是未知的,因此總是零。
5.2.1 第三個包[ACK]
ps. 抓包分析過程
1、sudo tcpdump -i lo -s0 -X port 8473 -w select.cap
生成select.cap
2、使用wireshark開啟select.cap