以太網(wǎng)診斷路由:DoIP到DoCAN轉(zhuǎn)發(fā)規(guī)則與實(shí)戰(zhàn)避坑指南)
1. 診斷路由到底在解決什么問(wèn)題1.1 從一次典型的診斷超時(shí)說(shuō)起如果你在整車(chē)廠或者Tier1做過(guò)診斷開(kāi)發(fā)大概率遇到過(guò)這樣的場(chǎng)景產(chǎn)線EOL工位用診斷儀通過(guò)以太網(wǎng)口給整車(chē)刷寫(xiě)結(jié)果刷到一半超時(shí)日志里顯示DoIP連接正常但UDS的響應(yīng)就是回不來(lái)。抓包一看診斷請(qǐng)求發(fā)到了網(wǎng)關(guān)網(wǎng)關(guān)也收到了但轉(zhuǎn)發(fā)到目標(biāo)ECU的CAN總線上之后響應(yīng)遲遲不上來(lái)或者上來(lái)了但網(wǎng)關(guān)沒(méi)有正確路由回以太網(wǎng)側(cè)。這個(gè)問(wèn)題十有八九出在診斷路由上。所謂診斷路由說(shuō)白了就是網(wǎng)關(guān)這個(gè)中間人要把來(lái)自不同物理鏈路以太網(wǎng)、CAN、CAN FD、LIN的診斷報(bào)文按照規(guī)則準(zhǔn)確地轉(zhuǎn)發(fā)到目標(biāo)ECU再把ECU的響應(yīng)原路送回給診斷儀。聽(tīng)起來(lái)簡(jiǎn)單但實(shí)際做起來(lái)協(xié)議轉(zhuǎn)換、尋址映射、超時(shí)管理、并發(fā)處理每一個(gè)環(huán)節(jié)都能讓你調(diào)到頭禿。這篇文章我打算把汽車(chē)以太網(wǎng)診斷路由這件事從頭到尾講清楚核心聚焦在DoIP到DoCAN的轉(zhuǎn)發(fā)規(guī)則上。不管你是剛接觸診斷協(xié)議棧的新人還是已經(jīng)做過(guò)幾個(gè)項(xiàng)目但總覺(jué)得理解不夠系統(tǒng)的老手應(yīng)該都能從里面找到有用的東西。我會(huì)盡量把每個(gè)設(shè)計(jì)決策背后的為什么講透而不是只丟一堆配置參數(shù)給你。1.2 為什么是DoIP到DoCAN先解釋一下這兩個(gè)詞。DoIP全稱(chēng)Diagnostics over IP是基于ISO 13400標(biāo)準(zhǔn)、跑在以太網(wǎng)上的診斷傳輸協(xié)議。DoCAN就是傳統(tǒng)的基于CAN總線的診斷傳輸遵循ISO 15765-2也就是常說(shuō)的ISO-TP。UDS統(tǒng)一診斷服務(wù)ISO 14229則是跑在這兩種傳輸層之上的應(yīng)用層協(xié)議定義了0x10、0x22、0x27、0x31、0x34-0x37等一系列診斷服務(wù)。現(xiàn)在的整車(chē)架構(gòu)通常是這樣的外部診斷儀通過(guò)OBD口的以太網(wǎng)引腳接入走DoIP協(xié)議跟網(wǎng)關(guān)通信網(wǎng)關(guān)內(nèi)部再把診斷請(qǐng)求轉(zhuǎn)換成DoCAN格式發(fā)到對(duì)應(yīng)的CAN網(wǎng)段上目標(biāo)ECU收到后處理并響應(yīng)。整個(gè)鏈路里網(wǎng)關(guān)承擔(dān)了協(xié)議轉(zhuǎn)換和路由的核心角色。為什么不讓診斷儀直接走CAN因?yàn)橐蕴W(wǎng)的帶寬優(yōu)勢(shì)太明顯了。刷寫(xiě)一個(gè)幾MB的固件走CAN可能要幾十分鐘走以太網(wǎng)幾分鐘就搞定。但問(wèn)題是不是所有ECU都支持以太網(wǎng)大量車(chē)身控制器、執(zhí)行器還是掛在CAN上。所以網(wǎng)關(guān)的DoIP到DoCAN路由就成了剛需。1.3 本文的適用范圍和讀者定位這篇文章主要面向做診斷協(xié)議棧開(kāi)發(fā)、網(wǎng)關(guān)路由配置、整車(chē)診斷測(cè)試的工程師。如果你在做以下工作內(nèi)容會(huì)直接對(duì)你有幫助網(wǎng)關(guān)診斷路由模塊的開(kāi)發(fā)和調(diào)試DoIP協(xié)議棧的集成和測(cè)試UDS診斷服務(wù)的端到端驗(yàn)證產(chǎn)線EOL診斷方案的設(shè)計(jì)需要的基礎(chǔ)知識(shí)包括了解CAN總線基本概念、知道UDS大概有哪些服務(wù)、對(duì)TCP/IP有基本認(rèn)知。如果這些你還不熟也沒(méi)關(guān)系我會(huì)在關(guān)鍵地方補(bǔ)充必要的背景。2. 核心概念拆解DoIP、DoCAN和UDS的關(guān)系2.1 三層協(xié)議棧的分工要理解診斷路由先把協(xié)議棧的層次理清楚。從下往上說(shuō)物理層和鏈路層以太網(wǎng)側(cè)就是標(biāo)準(zhǔn)的100BASE-TX或1000BASE-TCAN側(cè)就是CAN或CAN FD。這一層不用太操心硬件搞定。傳輸層這是路由的核心戰(zhàn)場(chǎng)。以太網(wǎng)側(cè)是DoIPISO 13400CAN側(cè)是ISO-TPISO 15765-2。兩者做的事情類(lèi)似——把上層來(lái)的大數(shù)據(jù)分包傳輸、重組——但機(jī)制完全不同。應(yīng)用層UDSISO 14229統(tǒng)一跑在兩種傳輸層之上。也就是說(shuō)一個(gè)0x22讀數(shù)據(jù)的請(qǐng)求不管走DoIP還是DoCANUDS層的報(bào)文內(nèi)容是一樣的區(qū)別只在于外面的封裝。這個(gè)分層設(shè)計(jì)是路由能實(shí)現(xiàn)的基礎(chǔ)。網(wǎng)關(guān)要做的就是把DoIP的信封拆掉把里面的UDS數(shù)據(jù)拿出來(lái)再套上DoCAN的信封發(fā)出去。反過(guò)來(lái)收到DoCAN的響應(yīng)拆掉ISO-TP的封裝取出UDS數(shù)據(jù)再套上DoIP的封裝送回診斷儀。2.2 DoIP協(xié)議的關(guān)鍵機(jī)制DoIP不是簡(jiǎn)單地把UDS數(shù)據(jù)塞進(jìn)TCP包就完事了。它有一套自己的協(xié)議機(jī)制幾個(gè)關(guān)鍵點(diǎn)必須搞清楚DoIP報(bào)文頭每個(gè)DoIP報(bào)文都有一個(gè)8字節(jié)的頭部包含協(xié)議版本、版本反碼、載荷類(lèi)型和載荷長(zhǎng)度。載荷類(lèi)型決定了這個(gè)報(bào)文是做什么的比如0x8001是診斷消息0x0001是車(chē)輛識(shí)別請(qǐng)求0x0005是路由激活請(qǐng)求。TCP和UDP的分工DoIP同時(shí)使用TCP和UDP。UDP用于車(chē)輛發(fā)現(xiàn)和基本的車(chē)輛信息查詢(xún)廣播場(chǎng)景TCP用于實(shí)際的診斷通信。診斷數(shù)據(jù)走TCP因?yàn)樾枰煽總鬏?。路由激活診斷儀在發(fā)送診斷請(qǐng)求之前必須先通過(guò)TCP發(fā)送路由激活請(qǐng)求0x0005網(wǎng)關(guān)確認(rèn)后才能開(kāi)始診斷通信。這一步很關(guān)鍵很多新手調(diào)試時(shí)忘了做路由激活結(jié)果診斷請(qǐng)求發(fā)出去石沉大海。邏輯地址DoIP用邏輯地址來(lái)標(biāo)識(shí)診斷儀和目標(biāo)ECU。診斷儀有源地址目標(biāo)ECU有目標(biāo)地址。網(wǎng)關(guān)根據(jù)目標(biāo)地址來(lái)判斷這個(gè)請(qǐng)求應(yīng)該路由到哪條CAN總線上的哪個(gè)ECU。2.3 DoCAN/ISO-TP的核心要點(diǎn)CAN總線一幀最多8字節(jié)數(shù)據(jù)CAN FD可以到64字節(jié)而UDS的診斷請(qǐng)求動(dòng)輒幾十上百字節(jié)所以必須分包。ISO-TP就是干這個(gè)的首幀F(xiàn)F數(shù)據(jù)長(zhǎng)度超過(guò)單幀容量時(shí)第一幀叫首幀包含總數(shù)據(jù)長(zhǎng)度信息和前6字節(jié)數(shù)據(jù)經(jīng)典CAN。連續(xù)幀CF后續(xù)的數(shù)據(jù)幀每幀帶一個(gè)序列號(hào)從1開(kāi)始遞增到15后回繞到0。流控幀F(xiàn)C接收方收到首幀后回復(fù)流控幀告訴發(fā)送方可以繼續(xù)發(fā)多少幀、幀間隔是多少。流控幀有三個(gè)關(guān)鍵參數(shù)FS流控狀態(tài)、BS塊大小、STmin最小間隔時(shí)間。單幀SF數(shù)據(jù)長(zhǎng)度不超過(guò)6字節(jié)經(jīng)典CAN時(shí)直接用單幀傳輸不需要流控。這些機(jī)制在路由時(shí)必須正確處理。網(wǎng)關(guān)收到DoIP的診斷請(qǐng)求后如果數(shù)據(jù)長(zhǎng)度超過(guò)6字節(jié)就要按照ISO-TP的規(guī)則分包發(fā)到CAN上收到CAN上的多幀響應(yīng)也要正確重組后再通過(guò)DoIP發(fā)回去。2.4 UDS服務(wù)在路由中的透明性從路由的角度看UDS層應(yīng)該是透明的。也就是說(shuō)網(wǎng)關(guān)不需要理解0x22是讀數(shù)據(jù)、0x2E是寫(xiě)數(shù)據(jù)、0x31是例程控制它只需要把UDS數(shù)據(jù)原封不動(dòng)地轉(zhuǎn)發(fā)就行。但實(shí)際項(xiàng)目中有些網(wǎng)關(guān)會(huì)做一些聰明的事情比如攔截某些服務(wù)做特殊處理、修改某些響應(yīng)內(nèi)容。這種做法要非常小心因?yàn)橐坏┚W(wǎng)關(guān)對(duì)UDS層做了干預(yù)就可能破壞診斷儀和ECU之間的會(huì)話狀態(tài)一致性導(dǎo)致一些隱蔽的bug。我的建議是除非有明確的業(yè)務(wù)需求否則網(wǎng)關(guān)對(duì)UDS層保持透明轉(zhuǎn)發(fā)。需要做特殊處理的地方一定要有清晰的文檔記錄和充分的測(cè)試覆蓋。3. 診斷路由的完整轉(zhuǎn)發(fā)規(guī)則設(shè)計(jì)3.1 地址映射表路由的導(dǎo)航地圖診斷路由最核心的東西就是地址映射表。這張表定義了哪個(gè)邏輯地址對(duì)應(yīng)哪條CAN總線、哪個(gè)CAN ID、哪個(gè)ECU。一個(gè)典型的映射表長(zhǎng)這樣DoIP目標(biāo)地址CAN通道發(fā)送CAN ID接收CAN IDECU名稱(chēng)0x1001CAN10x7A00x7A8發(fā)動(dòng)機(jī)控制器0x1002CAN10x7A10x7A9變速箱控制器0x2001CAN20x7B00x7B8車(chē)身控制器0x2002CAN20x7B10x7B9空調(diào)控制器這張表的設(shè)計(jì)有幾個(gè)關(guān)鍵決策點(diǎn)邏輯地址的分配規(guī)則通常按網(wǎng)段和ECU類(lèi)型來(lái)分配。比如0x10xx段給動(dòng)力總成0x20xx段給車(chē)身0x30xx段給信息娛樂(lè)。這樣一看地址就能大致知道目標(biāo)在哪個(gè)域。CAN ID的映射規(guī)則診斷CAN ID通常遵循一定的規(guī)律比如物理尋址的請(qǐng)求ID是0x7A0-0x7A7響應(yīng)ID是0x7A8-0x7AF。功能尋址用0x7DF。這些規(guī)則在ISO 15765-4里有定義但具體項(xiàng)目可能會(huì)有調(diào)整。一對(duì)多的情況功能尋址比如0x7DF廣播需要網(wǎng)關(guān)把請(qǐng)求同時(shí)發(fā)到多條CAN總線上然后收集所有ECU的響應(yīng)。這比物理尋址復(fù)雜得多后面會(huì)單獨(dú)講。3.2 物理尋址的轉(zhuǎn)發(fā)流程物理尋址是最常見(jiàn)的場(chǎng)景診斷儀明確知道要跟哪個(gè)ECU通信。完整流程如下第一步DoIP路由激活。診斷儀通過(guò)TCP連接網(wǎng)關(guān)的DoIP端口通常是13400發(fā)送路由激活請(qǐng)求。網(wǎng)關(guān)驗(yàn)證后回復(fù)激活響應(yīng)分配一個(gè)內(nèi)部的路由激活句柄。第二步接收DoIP診斷請(qǐng)求。診斷儀發(fā)送DoIP診斷消息載荷類(lèi)型0x8001里面包含源地址、目標(biāo)地址和UDS數(shù)據(jù)。網(wǎng)關(guān)解析報(bào)文頭提取目標(biāo)地址。第三步查表路由。網(wǎng)關(guān)用目標(biāo)地址查地址映射表找到對(duì)應(yīng)的CAN通道和CAN ID。如果找不到回復(fù)一個(gè)否定響應(yīng)。第四步協(xié)議轉(zhuǎn)換和發(fā)送。網(wǎng)關(guān)把UDS數(shù)據(jù)按照ISO-TP規(guī)則封裝通過(guò)對(duì)應(yīng)的CAN通道發(fā)送。如果數(shù)據(jù)超過(guò)6字節(jié)先發(fā)首幀等收到流控幀后再發(fā)連續(xù)幀。第五步接收CAN響應(yīng)。目標(biāo)ECU處理完請(qǐng)求后通過(guò)CAN發(fā)送響應(yīng)。網(wǎng)關(guān)監(jiān)聽(tīng)對(duì)應(yīng)的接收CAN ID接收響應(yīng)數(shù)據(jù)。第六步重組和回傳。如果響應(yīng)是多幀的網(wǎng)關(guān)先按照ISO-TP規(guī)則重組完整數(shù)據(jù)然后封裝成DoIP診斷消息通過(guò)TCP連接發(fā)回給診斷儀。這個(gè)流程看起來(lái)直白但每一步都有坑。比如第三步查表失敗時(shí)否定響應(yīng)的格式和時(shí)機(jī)就有講究第四步發(fā)送多幀時(shí)流控幀的超時(shí)處理如果不當(dāng)會(huì)導(dǎo)致整個(gè)診斷流程卡死。3.3 功能尋址的轉(zhuǎn)發(fā)規(guī)則功能尋址是診斷儀向一組ECU廣播請(qǐng)求比如0x7DF就是經(jīng)典CAN上的功能尋址ID。在DoIP場(chǎng)景下診斷儀發(fā)送目標(biāo)地址為0xE000或其他約定的功能地址的請(qǐng)求網(wǎng)關(guān)需要把這個(gè)請(qǐng)求轉(zhuǎn)發(fā)到所有相關(guān)的CAN總線。功能尋址的復(fù)雜性在于多通道并發(fā)發(fā)送網(wǎng)關(guān)需要同時(shí)向多條CAN總線發(fā)送請(qǐng)求。這里要注意發(fā)送的時(shí)序如果所有通道同時(shí)發(fā)可能導(dǎo)致網(wǎng)關(guān)CPU負(fù)載瞬間飆升。實(shí)際實(shí)現(xiàn)中通常會(huì)做一定的錯(cuò)峰處理。多響應(yīng)收集多個(gè)ECU會(huì)同時(shí)響應(yīng)網(wǎng)關(guān)需要收集所有響應(yīng)并逐一通過(guò)DoIP回傳。這里的問(wèn)題是響應(yīng)可能在不同時(shí)間到達(dá)網(wǎng)關(guān)需要維護(hù)一個(gè)響應(yīng)收集窗口窗口結(jié)束后才能認(rèn)為功能尋址完成。響應(yīng)去重和排序有些ECU可能不支持某個(gè)功能尋址的服務(wù)會(huì)回復(fù)否定響應(yīng)。網(wǎng)關(guān)需要決定是否把所有響應(yīng)都回傳還是只回傳肯定響應(yīng)。這取決于具體的診斷規(guī)范要求。超時(shí)管理功能尋址的總超時(shí)時(shí)間通常比物理尋址長(zhǎng)因?yàn)橐人蠩CU響應(yīng)。但也不能無(wú)限等需要設(shè)置合理的超時(shí)上限。3.4 路由激活與會(huì)話管理路由激活是DoIP通信的前置條件但它的作用不只是打個(gè)招呼。網(wǎng)關(guān)在路由激活時(shí)通常會(huì)做幾件事資源分配為這個(gè)診斷儀連接分配一個(gè)會(huì)話上下文記錄源地址、連接句柄、激活時(shí)間等信息。權(quán)限檢查有些網(wǎng)關(guān)會(huì)檢查診斷儀的源地址是否在允許列表中防止未授權(quán)的診斷接入。并發(fā)控制限制同時(shí)激活的診斷儀數(shù)量。產(chǎn)線上可能有多臺(tái)診斷儀同時(shí)工作網(wǎng)關(guān)需要確保資源不會(huì)耗盡。超時(shí)監(jiān)控如果診斷儀長(zhǎng)時(shí)間沒(méi)有發(fā)送診斷請(qǐng)求網(wǎng)關(guān)應(yīng)該主動(dòng)釋放會(huì)話資源。這個(gè)超時(shí)時(shí)間通??膳渲媚J(rèn)可能是幾分鐘。會(huì)話管理還有一個(gè)容易忽略的點(diǎn)當(dāng)診斷儀斷開(kāi)TCP連接時(shí)網(wǎng)關(guān)必須清理對(duì)應(yīng)的會(huì)話上下文包括正在進(jìn)行的診斷請(qǐng)求。如果清理不干凈下次連接時(shí)可能會(huì)出現(xiàn)資源沖突。4. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)4.1 環(huán)境搭建與工具準(zhǔn)備要復(fù)現(xiàn)和驗(yàn)證診斷路由你需要以下環(huán)境硬件一臺(tái)支持DoIP的網(wǎng)關(guān)或者用PC模擬一個(gè)CAN分析儀比如常見(jiàn)的USB-CAN工具一臺(tái)診斷儀或者PC上的診斷軟件目標(biāo)ECU或者ECU模擬器軟件Wireshark抓以太網(wǎng)包分析DoIP協(xié)議CANoe/CANalyzer分析CAN總線上的ISO-TP和UDS診斷協(xié)議??梢杂瞄_(kāi)源的也可以自己寫(xiě)一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)配置診斷儀和網(wǎng)關(guān)的以太網(wǎng)接口要在同一網(wǎng)段確保防火墻沒(méi)有攔截13400端口如果網(wǎng)關(guān)有多個(gè)網(wǎng)口確認(rèn)診斷口配置正確我個(gè)人的習(xí)慣是先用Wireshark確認(rèn)DoIP層的通信正常再用CAN工具確認(rèn)CAN側(cè)的收發(fā)正常最后再聯(lián)調(diào)。這樣出問(wèn)題時(shí)能快速定位是哪一側(cè)的問(wèn)題。4.2 DoIP診斷請(qǐng)求的構(gòu)造與發(fā)送用Python寫(xiě)一個(gè)最簡(jiǎn)單的DoIP診斷請(qǐng)求發(fā)送腳本核心代碼如下import socket import struct # DoIP頭部版本2反碼0xFD載荷類(lèi)型0x8001載荷長(zhǎng)度 def build_doip_header(payload_type, payload_length): version 0x02 inverse_version 0xFF - version header struct.pack(BBHI, version, inverse_version, payload_type, payload_length) return header # 診斷消息載荷源地址、目標(biāo)地址、UDS數(shù)據(jù) def build_diagnostic_payload(source_addr, target_addr, uds_data): payload struct.pack(HH, source_addr, target_addr) uds_data return payload # 發(fā)送0x22服務(wù)讀取F186數(shù)據(jù)標(biāo)識(shí) source_addr 0x0E00 target_addr 0x1001 uds_data bytes([0x22, 0xF1, 0x86]) payload build_diagnostic_payload(source_addr, target_addr, uds_data) header build_doip_header(0x8001, len(payload)) sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((192.168.1.100, 13400)) # 先發(fā)送路由激活請(qǐng)求 activation struct.pack(BBHI, 0x02, 0xFD, 0x0005, 7) activation struct.pack(HBB, 0x0E00, 0x00, 0x00) sock.send(activation) response sock.recv(1024) print(路由激活響應(yīng):, response.hex()) # 發(fā)送診斷請(qǐng)求 sock.send(header payload) response sock.recv(4096) print(診斷響應(yīng):, response.hex()) sock.close()這段代碼的關(guān)鍵點(diǎn)DoIP頭部的版本反碼是版本號(hào)取反用于校驗(yàn)路由激活請(qǐng)求的載荷是源地址加一個(gè)激活類(lèi)型字節(jié)診斷請(qǐng)求的載荷是源地址、目標(biāo)地址加UDS數(shù)據(jù)接收響應(yīng)時(shí)要留足夠的緩沖區(qū)因?yàn)槎鄮憫?yīng)可能比較大4.3 網(wǎng)關(guān)側(cè)的路由轉(zhuǎn)發(fā)實(shí)現(xiàn)網(wǎng)關(guān)側(cè)的路由邏輯用偽代碼描述核心流程void handle_doip_diagnostic(uint16_t source, uint16_t target, uint8_t *uds_data, uint16_t len) { // 查找路由表 route_entry_t *entry lookup_route_table(target); if (entry NULL) { send_doip_nack(source, target, NACK_UNKNOWN_TARGET); return; } // 檢查目標(biāo)CAN通道是否空閑 if (is_channel_busy(entry-can_channel)) { send_doip_nack(source, target, NACK_BUSY); return; } // 構(gòu)造ISO-TP幀并發(fā)送 isotp_send(entry-can_channel, entry-tx_can_id, uds_data, len); // 啟動(dòng)響應(yīng)超時(shí)定時(shí)器 start_response_timer(source, target, entry); } void on_can_response(uint8_t channel, uint32_t can_id, uint8_t *data, uint8_t len) { // 查找對(duì)應(yīng)的路由會(huì)話 session_t *session find_session_by_can_id(channel, can_id); if (session NULL) { return; } // 重組ISO-TP數(shù)據(jù) uint8_t uds_response[MAX_UDS_LEN]; uint16_t uds_len isotp_reassemble(channel, can_id, data, len, uds_response); if (uds_len 0) { // 通過(guò)DoIP回傳響應(yīng) send_doip_diagnostic(session-source, session-target, uds_response, uds_len); stop_response_timer(session); } }這里有幾個(gè)實(shí)現(xiàn)細(xì)節(jié)值得展開(kāi)路由表查找的效率如果路由表很大幾百個(gè)ECU線性查找會(huì)很慢。實(shí)際項(xiàng)目中通常用哈希表或者二分查找。地址分配有規(guī)律的話直接用數(shù)組索引最快。通道忙判斷一條CAN總線上同時(shí)只能有一個(gè)診斷會(huì)話在進(jìn)行物理尋址場(chǎng)景。如果已經(jīng)有診斷在跑新的請(qǐng)求要么排隊(duì)要么直接拒絕。我傾向于直接拒絕并返回忙狀態(tài)讓診斷儀自己決定重試策略。響應(yīng)超時(shí)每個(gè)診斷請(qǐng)求都要有超時(shí)保護(hù)。超時(shí)時(shí)間通常設(shè)為P2時(shí)間UDS默認(rèn)50ms加上一些余量實(shí)際項(xiàng)目中可能設(shè)到幾秒。超時(shí)后要清理會(huì)話并給診斷儀返回超時(shí)否定響應(yīng)。4.4 ISO-TP分包與重組的關(guān)鍵參數(shù)ISO-TP的流控參數(shù)直接影響路由的性能和可靠性幾個(gè)關(guān)鍵參數(shù)BSBlock Size發(fā)送方連續(xù)發(fā)送多少幀后要等下一個(gè)流控幀。設(shè)為0表示不需要再等流控幀可以一直發(fā)。實(shí)際項(xiàng)目中通常設(shè)一個(gè)非零值比如8或16給接收方喘息的機(jī)會(huì)。STminSeparation Time Minimum連續(xù)幀之間的最小間隔時(shí)間。經(jīng)典CAN上通常設(shè)1-5msCAN FD可以更小。設(shè)太小可能導(dǎo)致接收方緩沖區(qū)溢出設(shè)太大又影響傳輸效率。N_BSTimeout for Block Size等待流控幀的超時(shí)時(shí)間通常1000ms。N_CRTimeout for Consecutive Frame等待連續(xù)幀的超時(shí)時(shí)間通常1000ms。這些參數(shù)在網(wǎng)關(guān)的CAN側(cè)和ECU側(cè)必須匹配。我遇到過(guò)因?yàn)榫W(wǎng)關(guān)側(cè)STmin設(shè)了0而ECU側(cè)處理不過(guò)來(lái)導(dǎo)致丟幀的情況后來(lái)把STmin調(diào)到2ms就穩(wěn)定了。4.5 多幀響應(yīng)的處理流程多幀響應(yīng)的處理是路由中最容易出問(wèn)題的地方。完整流程網(wǎng)關(guān)收到CAN上的首幀解析出總數(shù)據(jù)長(zhǎng)度網(wǎng)關(guān)發(fā)送流控幀告訴ECU可以發(fā)送的塊大小和最小間隔網(wǎng)關(guān)接收連續(xù)幀按序列號(hào)重組數(shù)據(jù)如果數(shù)據(jù)沒(méi)收完但塊大小到了再發(fā)一個(gè)流控幀數(shù)據(jù)收完后封裝成DoIP消息通過(guò)TCP發(fā)送這里的關(guān)鍵是序列號(hào)的處理。ISO-TP的序列號(hào)從1到15循環(huán)如果數(shù)據(jù)特別長(zhǎng)比如刷寫(xiě)時(shí)的幾MB數(shù)據(jù)序列號(hào)會(huì)回繞很多次。網(wǎng)關(guān)必須嚴(yán)格按序列號(hào)校驗(yàn)發(fā)現(xiàn)序列號(hào)不對(duì)要立即中止并報(bào)錯(cuò)。還有一個(gè)坑是流控幀的發(fā)送時(shí)機(jī)。有些實(shí)現(xiàn)是收到首幀后立即發(fā)流控幀有些是等應(yīng)用層準(zhǔn)備好緩沖區(qū)再發(fā)。如果應(yīng)用層處理慢立即發(fā)流控幀可能導(dǎo)致數(shù)據(jù)到了但沒(méi)地方放。我的做法是先在驅(qū)動(dòng)層緩沖應(yīng)用層從緩沖區(qū)取數(shù)據(jù)這樣流控幀可以及時(shí)發(fā)。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 診斷請(qǐng)求發(fā)出后無(wú)響應(yīng)這是最常見(jiàn)的問(wèn)題排查思路按以下順序第一步確認(rèn)DoIP層通信正常。用Wireshark抓包看路由激活是否成功診斷請(qǐng)求是否發(fā)到了網(wǎng)關(guān)。如果路由激活就失敗了檢查源地址是否在允許列表、網(wǎng)關(guān)的DoIP服務(wù)是否啟動(dòng)。第二步確認(rèn)網(wǎng)關(guān)是否轉(zhuǎn)發(fā)了。在CAN總線上抓包看有沒(méi)有對(duì)應(yīng)的診斷請(qǐng)求幀。如果沒(méi)有說(shuō)明網(wǎng)關(guān)的路由表配置有問(wèn)題或者目標(biāo)地址查不到。第三步確認(rèn)ECU是否響應(yīng)了。如果CAN上有請(qǐng)求但沒(méi)有響應(yīng)檢查ECU的診斷功能是否使能、CAN ID是否匹配、ECU是否處于可診斷狀態(tài)。第四步確認(rèn)響應(yīng)是否被網(wǎng)關(guān)回傳了。如果CAN上有響應(yīng)但診斷儀沒(méi)收到檢查網(wǎng)關(guān)的響應(yīng)重組和DoIP回傳邏輯。這個(gè)排查順序的核心邏輯是從外到內(nèi)逐層確認(rèn)。不要一上來(lái)就懷疑最復(fù)雜的部分先把簡(jiǎn)單的可能性排除掉。5.2 多幀傳輸中斷或數(shù)據(jù)不完整多幀傳輸出問(wèn)題通常有這幾個(gè)原因現(xiàn)象可能原因排查方法首幀后無(wú)連續(xù)幀流控幀未發(fā)出或參數(shù)不對(duì)抓CAN包確認(rèn)流控幀連續(xù)幀序列號(hào)跳變丟幀或發(fā)送方bug檢查CAN錯(cuò)誤計(jì)數(shù)數(shù)據(jù)長(zhǎng)度不匹配首幀長(zhǎng)度字段錯(cuò)誤對(duì)比實(shí)際數(shù)據(jù)長(zhǎng)度傳輸中途停止超時(shí)或緩沖區(qū)溢出檢查超時(shí)參數(shù)和緩沖區(qū)大小我遇到過(guò)一個(gè)典型案例網(wǎng)關(guān)的ISO-TP緩沖區(qū)只有512字節(jié)但ECU返回的響應(yīng)有800多字節(jié)結(jié)果傳輸?shù)揭话刖彌_區(qū)滿(mǎn)了后續(xù)數(shù)據(jù)被丟棄。后來(lái)把緩沖區(qū)擴(kuò)到4KB就解決了。這個(gè)問(wèn)題的隱蔽性在于小數(shù)據(jù)量的診斷都正常只有大數(shù)據(jù)量的才出問(wèn)題很容易漏測(cè)。5.3 功能尋址響應(yīng)收集不全功能尋址時(shí)網(wǎng)關(guān)需要收集多個(gè)ECU的響應(yīng)。常見(jiàn)問(wèn)題是有些ECU的響應(yīng)沒(méi)被收集到響應(yīng)窗口太短有些ECU處理慢響應(yīng)來(lái)得晚如果網(wǎng)關(guān)的收集窗口設(shè)得太短就會(huì)漏掉。建議窗口時(shí)間至少設(shè)為最長(zhǎng)ECU響應(yīng)時(shí)間的1.5倍。CAN ID過(guò)濾太嚴(yán)功能尋址的響應(yīng)CAN ID范圍要配置正確。如果只配了部分ID就會(huì)漏掉一些ECU。并發(fā)處理能力不足多個(gè)響應(yīng)同時(shí)到達(dá)時(shí)如果網(wǎng)關(guān)的處理能力不夠可能會(huì)丟響應(yīng)。需要優(yōu)化網(wǎng)關(guān)的CAN接收中斷處理邏輯。5.4 路由激活失敗的原因分析路由激活失敗通常有這幾個(gè)原因源地址不合法診斷儀的源地址不在網(wǎng)關(guān)的允許列表中網(wǎng)關(guān)資源耗盡同時(shí)激活的診斷儀數(shù)量達(dá)到上限TCP連接問(wèn)題網(wǎng)絡(luò)不通或者端口被占用版本不匹配DoIP協(xié)議版本不一致排查時(shí)先看網(wǎng)關(guān)的日志通常會(huì)有明確的拒絕原因。如果沒(méi)有日志用Wireshark看網(wǎng)關(guān)返回的激活響應(yīng)碼ISO 13400定義了各種拒絕碼的含義。5.5 實(shí)操避坑清單最后整理一份避坑清單都是實(shí)際項(xiàng)目中踩過(guò)的注意網(wǎng)關(guān)的ISO-TP緩沖區(qū)一定要留足余量建議至少4KB刷寫(xiě)場(chǎng)景可能需要更大。注意STmin參數(shù)不要設(shè)0除非你確認(rèn)ECU能處理連續(xù)無(wú)間隔的幀。設(shè)1-2ms是比較穩(wěn)妥的選擇。注意功能尋址的響應(yīng)收集窗口要可配置不同項(xiàng)目的ECU響應(yīng)速度差異很大。注意路由表變更后一定要做全量回歸測(cè)試一個(gè)地址配錯(cuò)可能導(dǎo)致整個(gè)網(wǎng)段的診斷不可用。注意診斷會(huì)話的超時(shí)清理邏輯要健壯異常斷開(kāi)時(shí)資源必須能正確釋放。注意DoIP的TCP連接可能同時(shí)有多個(gè)網(wǎng)關(guān)要能正確處理并發(fā)連接。注意刷寫(xiě)場(chǎng)景下數(shù)據(jù)傳輸量很大要特別關(guān)注網(wǎng)關(guān)的內(nèi)存和CPU占用。注意測(cè)試時(shí)不要只測(cè)正常流程異常場(chǎng)景超時(shí)、斷開(kāi)、錯(cuò)誤數(shù)據(jù)的測(cè)試同樣重要。5.6 性能優(yōu)化的幾個(gè)方向如果網(wǎng)關(guān)的診斷路由性能不達(dá)標(biāo)可以從這幾個(gè)方向優(yōu)化路由表查找優(yōu)化用哈希表替代線性查找O(1)的查找速度對(duì)高頻診斷場(chǎng)景很重要。零拷貝轉(zhuǎn)發(fā)如果UDS數(shù)據(jù)不需要修改可以直接從DoIP緩沖區(qū)轉(zhuǎn)發(fā)到CAN發(fā)送緩沖區(qū)避免內(nèi)存拷貝。中斷合并CAN接收中斷太頻繁會(huì)消耗大量CPU可以用中斷合并或者DMA來(lái)降低負(fù)載。并發(fā)會(huì)話管理用連接池管理DoIP會(huì)話避免頻繁創(chuàng)建銷(xiāo)毀。超時(shí)定時(shí)器優(yōu)化用時(shí)間輪或者最小堆管理大量定時(shí)器比每個(gè)會(huì)話一個(gè)定時(shí)器效率高得多。這些優(yōu)化不是每個(gè)項(xiàng)目都需要但如果你的網(wǎng)關(guān)要支持產(chǎn)線多工位同時(shí)診斷性能優(yōu)化就是必須的。6. 診斷路由的安全考量6.1 訪問(wèn)控制與認(rèn)證診斷接口是車(chē)輛的一個(gè)潛在攻擊入口。網(wǎng)關(guān)在路由層面可以做的基本防護(hù)包括源地址白名單只允許已知的診斷儀源地址接入。這在產(chǎn)線場(chǎng)景下很有效但在售后場(chǎng)景下可能不夠靈活。路由激活認(rèn)證有些項(xiàng)目會(huì)在路由激活時(shí)要求診斷儀提供認(rèn)證信息比如挑戰(zhàn)響應(yīng)或者證書(shū)。服務(wù)級(jí)過(guò)濾網(wǎng)關(guān)可以配置哪些UDS服務(wù)允許通過(guò)哪些禁止。比如刷寫(xiě)相關(guān)的0x34-0x37服務(wù)在非產(chǎn)線環(huán)境下可以禁止。這些防護(hù)措施各有優(yōu)劣需要根據(jù)實(shí)際的安全需求來(lái)選擇和組合。6.2 異常流量檢測(cè)網(wǎng)關(guān)還可以做一些基本的異常檢測(cè)單位時(shí)間內(nèi)診斷請(qǐng)求數(shù)量超過(guò)閾值時(shí)告警目標(biāo)地址不在路由表中的請(qǐng)求記錄并告警非法的UDS服務(wù)請(qǐng)求記錄并告警異常的診斷會(huì)話持續(xù)時(shí)間告警這些檢測(cè)不能替代專(zhuān)業(yè)的安全防護(hù)但可以作為第一道防線及時(shí)發(fā)現(xiàn)異常行為。6.3 刷寫(xiě)場(chǎng)景的特殊考慮刷寫(xiě)是診斷路由中負(fù)載最重的場(chǎng)景也是安全風(fēng)險(xiǎn)最高的場(chǎng)景。幾個(gè)關(guān)鍵點(diǎn)刷寫(xiě)權(quán)限控制刷寫(xiě)服務(wù)應(yīng)該只在特定條件下開(kāi)放比如車(chē)輛處于產(chǎn)線模式或者售后授權(quán)模式。數(shù)據(jù)完整性校驗(yàn)網(wǎng)關(guān)雖然不參與刷寫(xiě)數(shù)據(jù)的校驗(yàn)但要確保傳輸過(guò)程中數(shù)據(jù)不被篡改。DoIP和ISO-TP本身有校驗(yàn)機(jī)制但應(yīng)用層的校驗(yàn)比如CRC也很重要。刷寫(xiě)過(guò)程監(jiān)控刷寫(xiě)過(guò)程中如果出現(xiàn)異常中斷網(wǎng)關(guān)要能正確清理狀態(tài)避免車(chē)輛停留在不可用的刷寫(xiě)模式。刷寫(xiě)日志記錄記錄刷寫(xiě)的開(kāi)始時(shí)間、目標(biāo)ECU、數(shù)據(jù)大小、結(jié)果等信息便于事后審計(jì)。我在實(shí)際項(xiàng)目中遇到過(guò)刷寫(xiě)過(guò)程中網(wǎng)關(guān)重啟導(dǎo)致ECU停留在Bootloader的情況后來(lái)加了刷寫(xiě)狀態(tài)持久化網(wǎng)關(guān)重啟后能恢復(fù)刷寫(xiě)會(huì)話問(wèn)題才解決。這個(gè)經(jīng)驗(yàn)說(shuō)明刷寫(xiě)場(chǎng)景的異常處理必須考慮得非常周全。6.4 診斷路由的測(cè)試策略最后說(shuō)一下測(cè)試。診斷路由的測(cè)試不能只測(cè)正常流程要覆蓋以下幾類(lèi)功能測(cè)試各種UDS服務(wù)通過(guò)路由是否正常物理尋址和功能尋址都要測(cè)。邊界測(cè)試最大數(shù)據(jù)長(zhǎng)度、最大并發(fā)會(huì)話數(shù)、最小超時(shí)時(shí)間等邊界條件。異常測(cè)試網(wǎng)絡(luò)斷開(kāi)、ECU無(wú)響應(yīng)、數(shù)據(jù)錯(cuò)誤、電源中斷等異常場(chǎng)景。性能測(cè)試高并發(fā)下的響應(yīng)時(shí)間、吞吐量、資源占用。安全測(cè)試未授權(quán)訪問(wèn)、異常流量、刷寫(xiě)權(quán)限等安全相關(guān)場(chǎng)景。測(cè)試用例的設(shè)計(jì)要基于實(shí)際的使用場(chǎng)景而不是憑空想象。產(chǎn)線場(chǎng)景和售后場(chǎng)景的測(cè)試重點(diǎn)完全不同要分別設(shè)計(jì)。我個(gè)人在實(shí)際操作中的體會(huì)是診斷路由的問(wèn)題往往不是出在單個(gè)模塊的功能上而是出在模塊之間的交互和異常處理上。比如DoIP層正常、ISO-TP層正常、UDS層正常但三者組合起來(lái)在特定時(shí)序下就出問(wèn)題。所以聯(lián)調(diào)和異常場(chǎng)景測(cè)試的時(shí)間要留夠不要等到項(xiàng)目后期才發(fā)現(xiàn)問(wèn)題。另外分享一個(gè)小技巧在網(wǎng)關(guān)里加一個(gè)診斷路由的調(diào)試日志開(kāi)關(guān)記錄每個(gè)診斷請(qǐng)求的路由決策、轉(zhuǎn)發(fā)時(shí)間、響應(yīng)時(shí)間等信息。平時(shí)關(guān)閉不影響性能出問(wèn)題時(shí)打開(kāi)就能快速定位。這個(gè)日志在排查偶發(fā)性問(wèn)題時(shí)特別有用因?yàn)楹芏嗦酚蓡?wèn)題是時(shí)序相關(guān)的不抓現(xiàn)場(chǎng)很難復(fù)現(xiàn)。