要實現乙個mysql proxy,首先需要做的就是理解並實現mysql通訊協議。這樣才能通過proxy架起client到server之間的橋梁。
mixer的mysql協議實現主要參考mysql官方的internal manual,並用wireshark同時進行驗證。在實現的過程中,當然踩了很多坑,這裡記錄一下,算是對協議分析的乙個總結。
需要注意的是,mixer並沒有支援所有的mysql協議,譬如備份,儲存過程等,主要在於精力有限,同時也為了實現簡單。
mysql協議只有兩種基本的資料型別,integer和string。
integer包括fixed length integer和length encoded integer兩種,對於length encoded integer,用的地方比較多,這裡詳細說明一下。
對於乙個integer,我們按照如下的方式將其轉成length encoded integer:
相應的,對於乙個length encoded integer,我們可以通過判斷第乙個byte的值來轉成相應的integer。
string包括:
在mysql中,如果client或server要傳送資料,它需要將資料按照(2 ** 24 - 1)拆分成packet,給每乙個packet新增header,然後再以此傳送。
對於乙個packet,格式如下:
3 payload length
1 sequence id
string[len] payload
前面3個位元組表明的是該packet的長度,每個packet最大不超過16mb。第4個位元組表明的是該packet的序列號,從0開始,對於多個packet依次遞增,等到下乙個新的命令傳送資料的時候才重置為0。前面4個位元組組成了乙個packet的header,後面就是該packet實際的資料。
因為乙個packet最大能傳送的資料位16mb,所以如果需要傳送大於16mb的資料,就需要拆分成多個packet進行傳送。
通常,server會回給client三種型別的packet
要實現proxy,首先需要解決的就是登陸問題,包括proxy模擬server處理client的登陸,proxy模擬client登陸server。
為了簡單,mixer只支援username + password的方式進行登陸,這應該也是最通用的登陸方式。同時不支援ssl以及compression。
乙個完整的登陸流程如下:
這裡,不得不說實現登陸協議的時候踩過的乙個很大的坑,因為我使用的是handshakev10協議,在文件裡面,協議有這樣的規定:
if capabilities & client_secure_connection
如果根據文件的說明,算出來auth-plugin-data-part-2的長度是13,因為auth-plugin-data的長度是20。但是,實際情況是,auth-plugin-data-part-2的長度應該為12,第13位一直為0。只有這樣,我們才能根據salt算出正確的加密密碼。這一點,在mysql-proxy官方的文件,以及多個msyql client driver上面,wireshark的分析中都是如此,在go-sql-driver中,作者都直接寫了如下的注釋:
// second part of the password cipher [12? bytes]
// the documentation is ambiguous about the length.
// the official python library uses the fixed length 12
// which is not documented but seems to work.
可想而知,這個坑有多坑爹。至少我開始是栽在上面了。加密老是不對。
搞定了登陸,剩下的就是mysql的命令支援,mixer只實現了基本的命令。主要集中在text protocol以及prepared statment裡面。
最基本的ping實現,用來檢查mysql是否存活。
雖然叫init db,其實壓根幹的事情就跟use db一樣,用來切換使用db的。
可以算是最重要的乙個命令,我們在命令列使用的多數mysql語句,都是通過該命令傳送的。
在com_query中,mixer主要支援了select,update,insert,delete,replace等基本的操作語句,同時支援begin,commit,rollback事物操作,還支援set names和set autocommit。
com_query有4中返回packet
這裡重點說明一下text resultset,因為它包含的就是我們最常用的select的結果集。
乙個text resultset,包括如下幾個包:
對於乙個row packet的裡面的資料,我們通過如下方式獲取:
com_stmt_族協議就是通常的prepared statement,當我在atlas群裡面說支援prepared statement的時候,很多人以為我支援的是在com_query中使用的prepare,execute和deallocate prepare這組語句。其實這兩個還是很有區別的。
為什麼我不現在不想支援com_query的prepare,主要在於這種prepare需要進行變數設定,mixer在後端跟server是維護的乙個連線池,所以對於client設定的變數,proxy維護起來特別麻煩,並且每次跟server使用新的連線的時候,還需要將所有的變數重設,這增大了複雜度。所以我不支援變數的設定,這點看cobar也是如此。既然不支援變數,所以com_query的prepare我也不會支援了。
com_stmt_*這組命令,主要用在各個語言的client driver中,所以我覺得只支援這種的prepare就夠了。
對於com_stmt_execute的返回結果,因為prepare的語句可能是select,所以會返回binary resultset,binary resultset組成跟前面text resultset差不多,唯一需要注意的就是row packet採用的是binary row packet。
對於每乙個binary row packet,第乙個byte為0,後面緊跟著乙個null bitmap,然後才是實際的資料。
在binary row packet中,使用null bitmap來表明該行某一列的資料為null。null bitmap長度通過 (column-count + 7 + 2) / 8計算得到,而對於每列資料,如果為null,那麼它在null bitmap中的位置通過如下方式計算:
null-bitmap-byte = ((field-pos + offset) / 8)
null-bitmap-bit = ((field-pos + offset) % 8)
offset在binary resultset中為2,field-pos為該列的位置。
對於實際非null資料,則是根據每列定義的資料型別來獲取,譬如如果type為mysql_type_longlong,那麼該資料值的長度就是8位元組,如果type為mysql_type_string,那麼該資料值就是乙個length encoded string。
我通過wireshark分析了一些mysql protocol,主要在這裡,這裡不得不強烈推薦wireshark,它讓我在學習mysql protocol過程中事半功倍。
mixer的**在這裡歡迎反饋。
網路協議分析 VRRP協議分析
2 rip 2的報文格式 3 rip 2的驗證報文 三 例項化 rip報文由頭部 header 和多個路由表項 route entries 部分組成。乙個rip表項中最多可以有25個路由表項。rip是基於udp協議的,所以rip報文的資料報不能超過512個位元組。1 command 長度8bit,報...
網路協議分析 生成樹協議(STP)分析
lan資料鏈路層 例項化二層通訊協議,基本應用是防止交換機冗餘鏈路產生的環路.用於確保乙太網中無環路的邏輯拓撲結構.從而避免了廣播風暴,大量占用交換機的資源。網橋協議資料單元 bridge protocol data unit 是一種生成樹協議問候資料報,它可以配置的間隔發出,用來在網路的網橋間進行...
應用層協議分析 HTTPS協議分析
例項化secure socket layer ssl 俗稱安全套接層,是由netscape communitcation於1990年開發,用於保障word wide web www 通訊的安全。主要任務是提供私密性,資訊完整性和身份認證。1994年改版為sslv2,1995年改版為sslv3。tra...