philippica: proxy lab從入門到放棄

好久沒寫東西了,寫點沒啥乾貨的。

知乎好像有吹csapp的傳統,好吧這是本非常棒的書,可到了網路這章講真,覺得講的不是太好,正好第三版出來了,順著縷一縷。天天盯著那些底層協議解析各種二進位協議各種tlv的和http這種字元型的協議的感覺還是很不一樣的T T 特別是用cc++這種字元串處理非常不友好的語言

操作網路和操作文件從上層調用者來看真的差不多,但好像網路篇幅比較大的樣子,其實冗繁消盡,整個代碼的主幹是很明顯的。

數據的流向是明確的,忽略各種許可權、限制的檢查,比如當write的時候數據從user space移動到一個特定的地址,然後dma送到網卡上,而寫磁碟只是從網卡變成磁碟而已。從程序員角 度,我們只要關注數據從User space到特定的地址之間的移動,dma之後的可以看成黑盒。

當我們需要訪問遠程主機時,一個connect函數就搞定了。connect函數就是三次握手的過程,當選擇block模式時,connect發送三次握手的第一個SYN為1的包,然後block住,當伺服器應答第二個syn/ack包時返回並發送第三個包完成三次握手,至此就可以和目標主機通信了。

既然網路操作和磁碟操作差不多,為啥csapp書中open_clientfd寫得那麼又臭又長?這是因為這裡他使用的是domain name而不是ip進行通信,因此還要加上dns這茬。linux的系統函數getaddrinfo()封裝了dns查詢,然而DNS查詢並不是一對一的,只要你的dns提供者支持,你完全可以設置多條a record,dns的answer欄位可以儘管塞東西。畢竟有 DNS Round Robin這種號稱窮人的負載均衡的技術存在TUT

所以getaddrinfo()得到的不是一個ip,而是一個domain name對應的一坨ip,你會得到一個鏈表,然後嘗試連接鏈表中的每個ip直到鏈接成功,好吧,加上DNS查詢,你的程序的長度是不是又長了那麼一點點了,好為了讓自己的程序robust一點,對各個函數返回錯誤的情況逐一判斷下,用完自己關,是不是你發現自己建立鏈接的程序也和書上的差不多又臭又長了呢?

好吧,反對csapp一句原理不說直接扔代碼的惡習,而且代碼里還是帶warning的,這是直接向國內三流教科書看齊啊。

下面說下這個proxy lab,雖然只是個proxy玩具,但玩具也要跑起來的好吧,再不濟pornhub看看小電影啥的總可以吧(逃。說下我做的時候主要的痛點。

linux下推薦用firefox調試,chrome的proxy配置是linux的全局proxy,比較麻煩,相比起來ff直接就可以配。

proxy就是個二道販子,上游來的報文吃了直接原封不動吐給下游就可以了,好第一個問題,對於http request報文我吐給誰?原本這都是瀏覽器搞的,解析域名之後直接在IP頭的欄位里填上目標伺服器的地址即可,現在到好,IP頭裡目標的ip、埠都是proxy的,所以我們不得不解析這個http包來找到我們要鏈接的伺服器的地址。

很開心的是,當是proxy的時候,瀏覽器會把Request-Line中的Request-URI變成絕對地址。例如

「GET w3.org/pub/WWW/TheProje HTTP/1.1」

我們可以根據這個找到我們要到的伺服器。當然你也可以parse出host欄位的內容,但是這個欄位是從http1.1開始出現的,並不兼容HTTP1.0。

另外,測試這段代碼的時候一開始用了學校的老舊網站來測發現跑不起來,telnet到其80埠直接輸入發現

「GET xxx.edu.cn/yyyy HTTP/1.1rnHost:」 就會返回一個帶BAD request的網頁,連request頭都沒有,也就是一旦Request-URI是絕對地址就返回錯誤,其他網站都沒有發現類似的,內心也是很糟逼的。。。。

第二個問題:連接什麼時候關?

http協議是短連接大家都知道,tcp像套套一樣,用一個關一個,可http1.1都出現那麼久,咱大清都滅了,keep-alive省了tcp建立鏈接的握手包,也使得我這個線程一旦和伺服器建了鏈接就不能關,你得hold on,直到伺服器或者用戶把鏈接關了你這個線程才能撤。這裡由於我用的是block的socket,所以如果只是無腦收發,我們不知道伺服器在這一刻已經發完報了,接下來該用戶發包了,可我們還在等待read讀伺服器的包就會掛住,所以我們必須知道此刻服務端或者用戶有沒有發完,這就要求我們必須parse http,這就引出了下面的問題

第三個問題,怎麼知道http包發完了?

最好的辦法就是按著rfc2616把http協議全部parse一遍,本著不要重複造輪子的原則可以去github上找http parser,但找庫實在不像做Lab自己動手的樣子,而且僅僅這個問題我們並不需要手敲完整的http協議,首先對於type是304,401 402 403這種的只要讀個頭,到最後一個rn就結束了,對於type是200的這種讀完頭分兩種情況:chunk和非chunk

對於非chunk的情況,會有content-length這個欄位,http1.1規定這個參數在非chunk情況下是必填的,這就是下面要讀的長度,對於chunk的沒有content-length這個欄位,經過觀察發現大部分是經過gzip壓縮的數據,會是這樣。這種情況下先是有個字元型十六進位的該chunk的長度加上rn,接下來是相應的payload,重複,直到讀到下面chunk的長度是0為止,這個http包才結束。我們發現僅僅做轉發,並不需要解析所有的欄位。

做到這裡一個能轉發http的proxy就能用了。下面一個議題,https。

(未完,也許坑)

reference: rfc 2616 : ietf.org/rfc/rfc2616.tx


推薦閱讀:

鼓吹矽谷 Palo Alto 學區房便宜的,不是壞就是蠢
趣話安全啟動:迷思與啟示
學python非常困難怎麼辦?

TAG:计算机科学 | 计算机网络 | CSAPP |