■ 浙江 盛建平
編者按: 筆者遇到某單位進行網絡改造后,由于網絡變動觸發應用的隱性Bug,最后由開發部門與網絡部門合作解決,該故障在日常非常罕見,因此進行了總結。
某單位外聯網絡為二層架構,外部用戶訪問內部通迅機,使用防火墻進行端口級保護。但二層架構安全性不高,內部通迅機直接暴露給外部用戶,若通迅機被攻破,則可能直接進入內網,對該單位信息系統安全構成威脅。
為有效保障網絡安全,該單位對外聯網絡進行改造,新增DMZ區,將通迅機前移到原防火墻外部的DMZ區,并在通迅機與外部用戶之間新增其他廠商的防火墻實現異構。改造前后拓撲如圖1所示。
外聯網絡從二層架構切換到三層架構后,外部用戶訪問DMZ區通迅機辦理業務,發現約有30%的交易不成功,有時重新辦理又會成功,極大的影響了用戶體驗。回退到二層架構,業務又都正常。

圖1 改造前后的拓撲圖
因通迅機雖改變了部署位置,但通迅程序未做任何變動,而本次又是新增了不同廠商的防火墻,因此排查重點定位在新增防火墻上。
檢查外層防火墻配置,未發現異常。使用抓包工具對外層防火墻內、外口進行抓包分析,看是否存在異常的數據包。經分析,發現防火墻內、外口數據完全一致,并未發現丟包等異常現象。對業務出現異常時的數據包進行分析,發現數據收發均正常,見圖2第610-613號數據包,客戶端A.100.27發送了兩個數據包(第 610和 612號),服務端B.231.2回復了兩個ACK進行 確 認(第 611和613號)。但奇怪的是,服務端突然發送RST報文中斷了TCP連接,如圖2所示。
在網絡人員進行故障分析的同時,應用開發人員對應用日志進行了分析。對通迅機上提取到的日志分析后發現,當某筆業務不成功時,服務程序僅接收到1460字節的數據,而非第610和612號數據包發送的1584字節(注:第610號數據包長度為1514,減去以太網頭部14字節,IP頭部20字節,TCP頭部20字節,實際有效數據載荷為1460字節,同樣第612號數據包有效數據載荷為124字節,兩個數據包共發送1584字節)。開發人員認為,服務程序從網絡上接收數據不完整,導致程序處理異常,應用連接中斷,從而引起交易失敗。
問題陷入僵局,從網絡層面分析,客戶端交易數據均已正確發往服務端,并收到服務端的確認;從應用日志分析,服務端數據接收不完整,從而引起交易中斷。就像一起網絡訂單,前后共發兩個快遞,快遞公司均已送達并經門衛簽收(ACK報文),但最終用戶從收發室(緩沖區)只收到第一個快遞(1460字節)。最奇怪的是那個RST報文,明明數據完整送達了,服務方卻中止了TCP連接。
重新回顧RST報文產生原因,主要有如下幾種情況:
一是端口未打開。如A機向B機發送一個SYN請求,表示想連接B機的3000端口,但B機上并沒有打開3000端口,于是向A機發送了一個RST。
二是請求超時。如A機的程序建立了Socket之后,用setsockopt的SO_RCVTIMEO選項設置了recv的超時時間為100ms,若此時A機發送SYN后到從B機接收到SYN的時間超過100ms,則A機上的程序認為接收超時,會發送RST拒絕進一步發送數據。

圖2 服務端突然發送RST報文并中斷了TCP連接
三是提前關閉。若客戶端向服務端發送了2000字節,而服務端程序僅接收前1800字節就關閉了連接,此時TCP層發現“Close Socket時Recv Buff不為空”,TCP認為數據沒有正確提交到應用,使用RST關閉連接。表現在抓包信息上就是客戶機向服務器發送了2000個字節的數據,然后服務器端發送ACK進行確認,緊接著服務器向客戶機發送了一個RST斷開連接,該現象和本案的故障非常相像。
最后一種是在一個已關閉的Socket上收到數據。如客戶端在服務端已經關閉掉Socket之后,仍然在發送數據,這時服務端會產生RST。
根據RST報文產生的第三個原因,我們提出第一個解決方案,即服務端在收到1460字節后,對報文接收完整性進行校驗,若未全部接收,則繼續接收剩余的數據。據此開發人員修改程序后,業務回歸正常。(該解決方案由胡瑛提出并實現,在此表示感謝)
根據網絡訂單二個快遞的說法,我們猜想若最終用戶稍等片刻再取快遞,是否就能一次性取到完整的二個快遞呢?據此開發人員修改程序,從緩沖區接收數據前,先等待2毫秒,之后業務也回歸正常。
本次故障是網絡變動觸發應用的隱性Bug,實屬罕見。該問題的解決得益于網絡與開發相互配合,通力合作。建議在使用Socket接口接收變長報文體時,采用尾標識法(通過尋找接收的通迅報文中的尾標識字符串,確認報文是否完整)或負載長度法(通過通迅報文頭添加報文長度字段,確定有效報文的長度)等來提高報文接收的可靠性和完整性。