於是回頭重新好好讀了一下 TFO 的原理,發現自己對 TFO 的理解是有問題的 - 原先我以為在 SYN 裡是可以直接帶上請求資料的 - 而這很容易被攻擊。實際上的流程應該是:
- 用戶端發送 SYN 包,包尾加的是一個 FOC 請求,只有 4 位元組。
- 伺服器端收到 FOC 請求,驗證後根據來源 IP 位址產生 COOKIE(8 位元組),將這個 COOKIE 載入 SYN+ACK 包的末尾發送回去。
- 用戶端緩衝住擷取到的 COOKIE 可以給下一次使用。
- 下一次請求開始,用戶端發送 SYN 包,這時候包後面帶上緩衝的 COOKIE,然後就是要正式發送的資料。
- 伺服器端驗證 COOKIE 正確,將資料交給上層應用處理得到響應結果,然後在發送 SYN+ACK 時,不再等待用戶端的 ACK 確認,即開始發送響應資料。
示圖如下:
所以可以總結兩點:
第一次請求是不會有時間節約的效果的,測試至少要 httping -F -c 2。
從第二次開始節約的時間可以認為是第一個來回,httping 本身是個 HEAD 請求,可以認為是 50% 的節約。
但是用 -c 2 運行依然沒有看到 RTT 變化。這時候用stap 'probe kernel.function("tcp_fastopen_cookie_gen") {printf("%d ", $foc->len)}' 命令發現這個最重要的產生 COOKIE 的函數(net/ipv4/tcp_fastopen.c裡)居然一直沒有被觸發!
認真閱讀了一下調用這個函數的 tcp_fastopen_check 函數(net/ipv4/tcp_ipv4.c裡),原來前面首先有一步檢查 sysctl 的邏輯:
代碼如下 |
複製代碼 |
if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) == 0 || fastopenq == NULL || fastopenq->max_qlen == 0) return false; |
這個 TFO_SERVER_ENABLE 常量是 2。而我電腦預設的 net.ipv4.tcp_fastopen 值是 1。1 只開啟用戶端支援 TFO,所以這裡要改成 2(或者 3,如果你不打算把用戶端搬到別的主機上測試的話)。
重新開始 httping 測試,RTT 依然沒有縮短。這時候的 stap 命令發現 tcp_fastopen_cookie_gen 函數雖然觸發了,但是函數裡真正幹活的這段邏輯依然沒有觸發(即 crypto_cipher_encrypt_one):
代碼如下 |
複製代碼 |
void tcp_fastopen_cookie_gen(__be32 addr, struct tcp_fastopen_cookie *foc) { __be32 peer_addr[4] = { addr, 0, 0, 0 }; struct tcp_fastopen_context *ctx; rcu_read_lock(); ctx = rcu_dereference(tcp_fastopen_ctx); if (ctx) { crypto_cipher_encrypt_one(ctx->tfm, foc->val, (__u8 *)peer_addr); foc->len = TCP_FASTOPEN_COOKIE_SIZE; } rcu_read_unlock(); } |
我試圖通過 stap 'probe kernel.function("tcp_fastopen_cookie_gen"){printf("%s ", $$locals$$)}' 來查看這個 ctx 是什麼內容。輸出顯示 ctx 結構裡的元素值都是問號。
目前就卡在這裡。
為了驗證除了這步沒有其他問題,我”野蠻”的通過 systemtap 修改了一下 tcp_fastopen_cookie_gen 裡的變數。命令如下:
代碼如下 |
複製代碼 |
stap 'probe kernel.function("tcp_fastopen_cookie_gen") { $foc->len = 8 }' |
賦值為 8,就是 TCP_FASTOPEN_COOKIE_SIZE 常量的值。
然後再運行測試,就發現 httping 的第二次啟動並執行 RTT 時間減半了(最後那個 F 應該就是標記為 Fastopen 的意思吧)!可見目前問題就出在這裡。
代碼如下 |
複製代碼 |
$ httping -F -g http://192.168.0.100 -c 2 PING 192.168.0.100:80 (/url): connected to 192.168.0.100:80 (154 bytes), seq=0 time= 45.60 ms connected to 192.168.0.100:80 (154 bytes), seq=1 time= 23.43 ms F --- http://192.168.0.100/url ping statistics --- 2 connects, 2 ok, 0.00% failed, time 2069ms round-trip min/avg/max = 23.4/34.5/45.6 ms |
註:上面這個強制賦值 foc->len 沒有改變其實 foc->val 是空的事實,所以只能是測實驗證一下想法,真用的話多用戶端之間會亂套的。