☰
DoIP+UDS+OTA:车载以太网诊断协议栈与64MB固件升级实战拆解
2026/9/28 5:53:13 网站建设 项目流程

1. 项目概述:为什么要把DoIP、UDS和OTA放在一起拆

先说说背景。车载诊断这条线,过去十几年基本被CAN总线统治,OBD口出来一根CAN线,连上诊断仪就能读故障码、读数据流、刷写ECU。但这两年情况明显变了,智能座舱、辅助驾驶、域控制器这些上车之后,单ECU的软件体量从几百KB直接涨到几十MB甚至上百MB,一台车的控制器加起来固件包几百MB很常见。CAN总线最高也就1Mbps的带宽,刷一个几十MB的包要几个小时,工厂产线根本等不起,售后OTA更是想都别想。所以DoIP(Diagnostic over IP,基于IP网络的诊断协议,规范定义在ISO 13400)就是在这个节骨眼上被大规模推向量产。

这篇内容我打算用一台实际项目的网关设备做例子,完整走一遍“UDS诊断 -> 建立DoIP连接 -> 64MB固件包OTA升级”的全流程,把每一段报文都拆开来看。适合正在做车载以太网、UDS诊断栈、OTA刷写方案的朋友参考,也适合刚转到汽车电子、想搞清楚DoIP到底和CAN诊断有什么区别的人。

为什么要强调“64MB”?因为这是当前OTA升级里一个很有代表性的分界点。小于2MB的固件,传统CAN FD勉强能刷,但体验已经很差;到了64MB这个量级,就必须用DoIP走以太网。我实测下来,百兆车载以太网做64MB传输,纯数据阶段大约5到6秒,加上诊断流程和擦写时间,整个升级事务控制在3分钟以内是可行的。这个体验只有DoIP能给。

这次抓包我用的是PC + USB网卡直连车载以太网交换机镜像口,Wireshark抓包,中间串了一个自研的DoIP诊断模拟器做网关转发。下面所有报文分析全部来自这份抓包文件。

2. DoIP协议核心拆解:连接、路由与报文结构

2.1 连接建立:从物理层到TCP连接

DoIP在OSI模型里位于传输层之上,底层走TCP/IP,所以DoIP的连接流程本质上和“设备上网”没什么区别。测试设备(Tester)要找到车辆侧的DoIP实体(Vehicle Gateway),第一步是IP地址获取。实车上一般通过DHCP拿地址,诊断仪也可以配置静态IP。需要注意的是,车载以太网和办公室以太网不一样,很多ECU支持Auto IP(IPv4LL),当DHCP不可用时自动退到169.254.x.x网段。开发阶段我建议直接用静态IP,少一层依赖,排查问题也方便。

拿到IP之后,DoIP有两个关键端口:13400/TCP用于控制类报文和诊断报文,13400/UDP用于车辆发现和公告。TCP端口承载的是可靠传输的控制流程,UDP端口则承担了“车辆在网络上广播自己”的角色,也就是DoIP的车辆发现机制(Vehicle Announcement)。诊断仪上线后向组播地址发一条Vehicle Identification Request,车端网关就会回Vehicle Identification Response,里面带VIN、逻辑地址、EID(实体标识)、GID(组标识)这些硬件身份信息。

这一段流程结束后,Tester会向网关发起TCP连接。这里有个经验点:DoIP的TCP连接不限制只能建一条,但诊断会话资源是有限的。ISO 13400里定义了一个节点最多支持多少个并发TCP连接,超过会被拒绝(返回0x02的NACK码)。我们遇到过一个奇怪问题:Tester重连时端口还挂在TIME_WAIT状态,网关侧没做SO_REUSEADDR导致bind失败,诊断会话一直起不来。后来在网关侧加了TCP参数调整,这个问题才消失。

2.2 DoIP报文头:8个字节定一生

DoIP报文没有“像UDS那样复杂的寻址结构”,它的协议层非常简洁,核心就是8字节的通用报文头,定义了版本号、报文类型、负载长度。我们抓到的第一条DoIP报文是这么一段:

00 02 00 00 00 00 00 03

拆开看:

  • 00 02:协议版本号。ISO 13400-2:2012对应的版本值是0x02,0x03是2019版本。
  • 00 00:报文类型。0x0000是Generic DoIP header NACK,0x0001是Vehicle Identification Request,等等,这个是0x0000,即通用NACK。
  • 00 00 00 03:负载长度,后面紧跟着3个字节的负载。

DoIP报文类型有很多,但日常开发中核心关注这几种:0x0001/0x0002(车辆识别请求/响应)、0x0003/0x0004(路由激活请求/响应)、0x0005/0x0006(诊断消息请求/响应)、0x8001/0x8002(车辆公告请求/响应)、0x4001(Alive Check Request)和0x4002(Alive Check Response)。整理成速查表更直观:

报文类型值名称方向用途
0x0000Generic DoIP header NACKTester->Vehicle/Vehicle->Tester协议头错误,比如版本不支持、负载过长
0x0001Vehicle Identification RequestTester->Vehicle单播/组播查询车辆身份信息
0x0002Vehicle Identification ResponseVehicle->Tester返回VIN、逻辑地址、EID、GID
0x0003Routing Activation RequestTester->Vehicle路由激活,申请诊断通道
0x0004Routing Activation ResponseVehicle->Tester返回激活状态(0x10成功)
0x0005Diagnostic MessageTester<->Vehicle负载为完整的UDS诊断报文
0x0006Diagnostic Message ACK/NACKVehicle->Tester确认诊断消息是否被路由
0x4001Alive Check RequestVehicle->Tester检测激活路由是否还存活
0x4002Alive Check ResponseTester->Vehicle响应Alive Check

2.3 路由激活:拿到诊断通行证

TCP连接建立后,Tester想发UDS诊断消息,必须先做路由激活。这一步相当于“登录网关”,告诉网关“我要发诊断,我的逻辑地址是0x0E80”,网关校验通过后分配一个源地址,后续的诊断报文就带着这个地址在DoIP层内网路由。

我们抓包里的路由激活请求:

00 02 00 03 00 00 00 07 0E 80 00 00 00 00 00 00 00 00 00 00
  • DoIP头:版本0x02,类型0x0003,负载长度7。
  • 负载:0E 80是Tester的逻辑地址,后面的激活类型和保留位默认填0。

响应报文:

00 02 00 04 00 00 00 0B 0E 80 00 00 00 00 00 10 00 00 00 00 00 00

注意倒数第5个字节是0x10,这个就是激活响应码,表示Routing Activation成功。其它常见的激活失败码有:0x00(拒绝确认)、0x01(拒绝,不支持的激活类型)、0x02(拒绝,Tester地址与已激活路由冲突)、0x03(拒绝,节点忙)、0x04(拒绝,未知目标地址)、0x05(拒绝,VIN不匹配)、0x06(拒绝,EID不匹配)等。如果激活失败了,大概率是这几个问题之一:Tester逻辑地址和别的设备冲突、网关同时在线连接数达到上限、VIN没烧录(很多网关在没VIN时不允许激活诊断)。

2.4 诊断消息的DoIP封装与寻址

路由激活完成之后,DoIP层就进入“透传UDS”模式。诊断消息通过0x0005类型报文传输,负载结构如下:

00 02 00 05 00 00 00 0B 0E 80 00 00 00 00 00 00 00 00 03 22 F1 90

拆开看:

  • DoIP头:类型0x0005,负载长度0x0B(11字节)。
  • 前4个字节是源地址(Source Address):Tester逻辑地址0x0E80。
  • 中间4个字节是目标地址(Target Address):ECU逻辑地址0x0000(网关自身)或具体ECU地址。
  • 后面的字节是真正的UDS报文:03 22 F1 90,含义是:UDS长度3字节,SID=0x22(按标识符读取数据),DID=0xF190(一个自定义软件版本标识符)。

这里有个特别需要注意的点:DoIP封装UDS时,UDS报文前面的那个长度字节(比如这里的0x03)是UDS消息自身长度,不是DoIP负载长度。DoIP的payload length包含源地址、目标地址、UDS消息长度字节、UDS消息本身的总和。初学的人在这个地方特别容易算错。我们写测试脚本时用Python的struct打包时也踩过这个坑,算错了Wireshark里直接标记为Malformed,排查了半天才发现是长度写错了。

UDS on DoIP的寻址方式有物理寻址和功能寻址两种。物理寻址就是一对一,目标地址填具体ECU的逻辑地址,比如0x1070;功能寻址的目标地址是所有ECU共享的功能地址,比如0x1A00,网关会把这条诊断消息广播到网段内所有ECU。OTA刷写过程一般用物理寻址,因为擦写动作必须精确到单个ECU,不能多个ECU同时执行;某些读取操作可以用功能寻址做“一次问多个ECU”的效率优化。

3. UDS诊断协议栈:OTA升级的“地基”操作拆解

3.1 诊断会话切换:先进入扩展会话

UDS(ISO 14229)定义了几种诊断会话,最基础的是默认会话(0x01),但真正干活儿基本都要切到扩展会话(0x03)或编程会话(0x02)。编程会话限制访问,防止行车过程中误刷软件;扩展会话允许执行写入、例程控制等操作。

OTA升级的第一步就是切换会话。抓包报文:

Tester -> ECU: 02 10 03 ECU -> Tester: 02 50 03
  • 请求02 10 03:SID=0x10(DiagnosticSessionControl),子功能=0x03(扩展会话)。
  • 响应02 50 03:0x50是0x10 | 0x40的肯定响应,意思是“会话已切换,当前是扩展会话”。

如果ECU拒绝切换,会返回NRC(否定响应码)。常见的NRC里,0x22(条件不满足)出现得最多——ECU当前车速不为0、电源模式不对、未解锁安全等级,都会拒掉会话切换。我们实测遇到过一个案例:某个ECU在整车CAN网络上收到持续的动力CAN报文,判定车辆处于“行驶状态”,直接拒绝进入编程会话。解决办法是在诊断仪侧模拟整车环境,通过CANoe或网关把车速信号置0,再发会话切换。

3.2 安全访问:解不开锁什么都做不了

从扩展会话进入编程会话后,UDS会要求安全访问(SecurityAccess,SID 0x27)。这一步是OTA链路里最“敏感”也最容易出问题的地方。机理是:Tester先发27 01请求种子(Seed),ECU返回一段随机数;Tester用这段随机数按厂商算法计算出密钥(Key),发27 02提交;ECU校验通过后返回67 02,之后一段时间内允许执行写入操作。

抓到的典型报文序列:

Tester -> ECU: 02 27 01 ECU -> Tester: 04 67 01 1A B2 C3 D4 Tester -> ECU: 06 27 02 A1 B2 C3 D4 E5 F6 ECU -> Tester: 02 67 02

注意几个点:

  • 种子一般是4字节,但有的ECU用8字节甚至更长,完全看厂商的算法。
  • 安全访问有次数限制。ISO 14229定义尝试次数的上限是厂商自定义,一般是3到5次。连续失败会锁定一段时间,我们调试时曾经因为算法写错,把ECU锁了10分钟,非常痛苦。
  • 种子和密钥的计算算法一般由Tester端的dll提供,OEM不会公开。如果你是自己做Tester,得先和OEM确认这个算法。

这里有很强的安全考量在里面——正是有0x27服务这道门槛,OTA刷写才能防止“随便一个设备连上OBD口就篡改固件”。在网关层面,DoIP路由激活之后还要再做一次安全访问,等于双层锁。在实际攻击面分析中,我们也验证过:如果TP层没做重放攻击防护,录一段合法会话的报文再回放,有一定概率能绕过安全锁,所以正规实现都要加时间戳、计数器防重放。

3.3 读取数据:升级前先确认版本和环境

安全访问通过后,标准流程是先读ECU当前软件版本号、硬件版本号、序列号、配置字这些关键标识。这既是做升级前“资格检查”,也是防止刷错零件号的保险。这里用的是0x22服务(ReadDataByIdentifier)。

Tester -> ECU: 03 22 F1 90 ECU -> Tester: 06 62 F1 90 01 02 03 04

0xF190这个DID是软件版本号,返回的01 02 03 04转换成ASCII就是类似“V1.2.3.4”的版本字符串。实际工程中建议在升级前把以下DID都读一遍:软件版本、硬件版本、序列号、供应商代码、Bootloader版本、ECU装配日期。这些信息在刷写失败做根因分析时特别有用,能快速判断是软件包传错还是ECU硬件不支持。

3.4 例程控制:升级的“开关”和“保险丝”

例程控制(RoutineControl,SID 0x31)在OTA里承担了大量“不是传输数据但必须做的事”。典型场景是刷写前的“预编程”和刷写后的“后编程”:

  • 预编程:关闭DTC记录(0x31 01 02 02)、禁用通信、停掉应用层程序、做一个内存擦除前的准备。
  • 后编程:重新启用DTC记录、复位ECU、检查软件完整性、验证应用激活。

举个实际报文例子,关闭DTC记录:

Tester -> ECU: 05 31 01 02 02 ECU -> Tester: 02 71 01

意思是:执行(01)例程ID为0x0202的“停止记录故障码”例程,执行成功返回71 01。我们的抓包里还看到一个很有意思的例程:擦除Flash前的“存储器检查”,ECU收到后先做一遍Flash坏块扫描,返回擦除所需预估时间(以秒为单位),Tester依据这个返回值动态计算刷写超时阈值。

0x31服务的坑主要在于:例程ID完全是厂商自定义的,同一个APID在不同OEM平台里可能对应不同功能,所以调试一定要拿OEM提供的诊断调查表(Diagnostic Descriptor)对照着来。在我过往的项目里,因为例程ID理解错位导致的刷写失败,占了OTA问题里相当的比例。

3.5 写入数据与传输数据:64MB怎么塞进诊断协议

OTA的重头戏就是固件包的传输。UDS里有两套传输方式:0x2E(WriteDataByIdentifier)适合小数据量,比如写配置字;0x34/0x36/0x37(RequestDownload/TransferData/RequestTransferExit)才是大文件传输的正路。

抓包里可以看到完整的0x34 -> 多个0x36 -> 0x37序列:

Tester -> ECU: 0B 34 00 44 00 00 00 00 04 00 00 00 ECU -> Tester: 07 74 00 44 01 F4 01 00 00

请求的含义:SID=0x34,dataFormatIdentifier=0x00(不压缩不加密),addressAndLengthFormatIdentifier=0x44表示后面的地址占4字节、长度占4字节,然后跟4字节的Flash起始地址和4字节的数据总长度(0x04000000 = 64MB)。响应的含义:0x74是肯定响应,maxNumberOfBlockLength=0x01F4(500字节),表示“每个block最多传500字节”。

这里我实测统计过,64MB固件包如果blockLength设成500字节,需要发送大约131072个0x36请求。假设每个block传输时间是0.5ms(百兆网下纯链路时间只要0.04ms,但ECU擦写Flash耗时是主要瓶颈),总耗时也要65秒,加上擦除和校验时间,整体在一两分钟量级。如果想优化,可以把maxNumberOfBlockLength增大到4KB或更大。但FLASH驱动的buffer size决定了实际能接受多大blockLength,强行调大可能导致ECU内存溢出崩溃,所以这个值不是越大越好,得跟ECU开发对齐。

0x36传输的报文:

Tester -> ECU: 04 36 01 xx xx xx ECU -> Tester: 03 76 01 xx xx

请求SID=0x36,blockSequenceCounter=0x01,后面是最多500字节的数据块。响应SID=0x76,返回当前已成功接收的块序号。注意一个问题:如果请求里的块序号突然跳变(比如从0x01直接跳到0x05),ECU会判定为传输错误返回NRC 0x73(wrongBlockSequenceCounter),整个下载流程中止。

传输结束发0x37:

Tester -> ECU: 01 37 ECU -> Tester: 02 77

3.6 编程后处理:复位、验证与再次连接

0x37之后,ECU拿到了完整的固件镜像,但此时它还只是在RAM缓冲区或固定下载区里。接下来要执行真正的Flash写入——一般通过0x31调用“编程”例程来完成,同时做完整性校验(CRC/签名)。校验通过后,用0x11服务(ECUReset)复位ECU,ECU重新上电自检,应用软件启动。

Tester -> ECU: 02 11 01 ECU -> Tester: 02 51 01

代码0x01是hardReset,0x03是powerDown。执行复位后TCP连接会断开,需要重新做DoIP的路由激活和UDS会话切换,然后再次读取软件版本号,确认升级后的版本信息正确。整个OTA流程到这一步才算闭环。

4. 64MB OTA升级抓包实战:现场报文时间线复盘

4.1 抓包环境怎么搭

如果你也想复现整个流程,环境大概是这样的:

  • 一个支持DoIP的以太网网关(我们用的是自研的,代码基于开源移植),上面跑着UDS诊断栈。
  • 一个16端口车载以太网交换机,网关和Tester PC都接在上面,PC的网卡做端口镜像或者在交换机上做span port。
  • Wireshark抓包,过滤条件doip,如果装了UDS解析插件,Wireshark还能自动把doip的payload解析成SID和参数。

实测有一个很实用的技巧:在Tester PC上除了抓包,还可以用tshark -i eth0 -f "udp port 13400 or tcp port 13400"做实时日志输出,把报文直接滚到命令行终端里当成流水线日志看。调试的时候比打开Wireshark GUI方便很多,尤其适合挂在后台跑一整晚的稳定性测试。

4.2 全流程时间线与报文摘录

下面这个时间线是从64MB OTA抓包文件里按时间戳整理出来的,每个阶段都标了耗时。我们用的ECU Flash擦除大约耗时18秒,数据块大小500字节,00到结束总计152秒。

阶段起始时间结束时间耗时关键报文
DoIP连接与路由激活0.000s0.142s142ms0x0003路由激活
读取软件版本0.152s0.214s62ms22 F1 90 读版本
切换编程会话0.226s0.261s35ms10 02 切编程会话
安全访问0.290s0.425s135ms27 01/27 02
预编程例程0.442s1.253s811ms31 01 02 02
Flash擦除1.280s19.865s18.585s31 01 FF 00
请求下载19.902s19.945s43ms34 00 44 …
传输64MB数据19.962s91.337s71.375s0x36 x 130k次
请求传输退出91.354s91.386s32ms37
编程+校验91.400s142.110s50.710s31 01 02 01
复位142.133s142.152s19ms11 01
重新连接DoIP143.360s143.522s162ms路由激活
读版本确认143.533s143.596s63ms22 F1 90

这份抓包里我发现两个非常有意思的点。第一,Flash擦除花了18.6秒,但这段过程里Tester几乎“无事可做”,只能等。很多DoIP实现会把擦除动作和“RoutineControl”合并成一条长超时请求,Tester的超时时间必须设得够长(我们设的是30秒),否则Tester会先报超时,然后ECU过一会再回响应,时序就乱了。第二,64MB数据传输本身只要71秒,平均吞吐量约0.9MB/s,并没有跑到百兆网络的极限。瓶颈在ECU侧接收Flash写入的速率,链路带宽根本不是问题。这也解释了为什么DoIP方案能撑起更大体量的OTA——瓶颈在ECU端的存储写入而不在协议本身。

4.3 Wireshark过滤规则与高效分析技巧

抓包文件动辄几十万条报文,靠肉眼翻肯定不行。我整理了几条最实用的过滤规则:

  • 只看DoIP协议:doip
  • 只看路由激活相关:doip.msg_type == 0x0003 || doip.msg_type == 0x0004
  • 只看诊断消息(0x0005):doip.msg_type == 0x0005
  • 只看UDS肯定响应:doip.msg_type == 0x0005 && uds.sid >= 0x50 && uds.sid != 0x7F(当Wireshark启用了UDS解析时)
  • 只看NRC否定响应:uds.nrc != 0x00
  • 只看特定源地址的请求:doip.src_address == 0x0e80
  • 统计每个DoIP报文类型的数量:doip.msg_type

还有一个很推荐的功能:Wireshark的“Telephony -> VoIP Calls”不适用,但“Statistics -> IO Graph”很好用,把过滤器设成doip.msg_type == 0x0005 && uds.sid == 0x36,就能画出一根“0x36传输带宽随时间变化”的曲线。如果看到曲线中间有塌陷或者停止,说明传输过程中出现了等待或重传,可以顺藤摸瓜找到瓶颈。我们之前定位一个“OTA卡在50%不动”的bug,就是用IO Graph发现0x36的速率突然掉到接近0,再对照时间戳看到TCP层发生了大量重传,最后定位到是车载交换机端口协商成了半双工模式导致丢包。

5. 常见问题与排障实录:踩过的坑都在这里

5.1 UDS常见否定响应码速查

OTA调试中NRC是最高频的“对话内容”,很多问题其实是一眼就能看出来的。我把最常见的NRC整理成了表:

NRC (Hex)名称含义常见原因
0x10General Reject一般拒绝请求格式不对、服务不支持
0x11Service Not Supported服务不支持SID拼写错误或该ECU未实现此服务
0x12Sub-Function Not Supported子功能不支持例如0x10服务里子功能0x04不存在
0x13Incorrect Message Length Or Invalid Format报文长度错误请求少了参数或多填了字节,长度字段算错
0x22Conditions Not Correct条件不满足会话不对、车速不为0、未解锁、电源模式不对
0x31Request Out Of Range请求超出范围DID/例程ID不存在、Flash地址越界
0x33Security Access Denied安全访问被拒绝种子/密钥错误、访问次数超限
0x35Invalid Key密钥无效密钥算错或算法不一致
0x36Exceed Number Of Attempts尝试次数超限安全访问连续失败被锁定
0x72General Programming Failure编程失败Flash写入失败、校验失败
0x73Wrong Block Sequence Counter块序号错误0x36序号跳变
0x78Request Correctly Received, Response Pending响应待处理ECU正在忙,需要Tester继续等待

0x78这个响应特别常见也特别容易被误解。ECU处理长任务时会先回一个0x78,表示“我已经收到了,但还没干完,你继续等”。Tester收到0x78之后必须保持当前请求的上下文,不能发新请求,直到ECU发来最终响应。有些自研Tester没有实现0x78的循环处理逻辑,导致刷写流程直接崩掉,这是初学者必踩的坑。

5.2 DoIP连接与传输层疑难杂症

DoIP本质上是TCP/IP上的应用协议,所以经典的网络问题它一个不落都会遇到。我列几个亲身踩过的高频问题:

  • 路由激活失败,返回0x02(Tester地址冲突):多台Tester同时连到同一个网关,且共用了相同的逻辑地址。生产环境里多台诊断仪的地址配置需要统一规划,不能随便填0x0E80。

  • 诊断请求发出去了,ECU没任何响应:先用Wireshark在Tester侧看报文有没有发到网卡上;再看网关侧日志确认有没有收到;再在ECU侧抓一次,确认ECU有没有回。逐段断开排查,基本能定位。我们遇到过一例,ECU的UDS接收线程死锁,TCP连接还活着,但应用层就是不响应。

  • TCP粘包/拆包导致DoIP报文被截断:DoIP报文头8字节里的payload length字段是唯一的拆分依据,实现时必须按“读够8字节头 -> 解析payload length -> 再读够payload length”的方式循环读取。很多初学协议栈的人直接用recv一次读固定长度,遇到大包被TCP拆成多段就解析失败了。解决方案是维护一个接收缓冲区和状态机。

  • 64MB传输约5分钟后TCP连接被断开:我们在测试中发现,有的ECU TCP keep-alive机制没实现好,长时间大流量传输后连接被中间设备认为空闲而杀掉。解决办法是应用层定期发Alive Check Request,或者干脆在ECU侧把TCP keepalive参数调短。

  • OTA过程中出现0x33安全访问拒绝,但明明密钥是对的:ECU内部的安全访问“已解锁状态”是有超时的,通常在几秒到几十秒内有效。如果前面读版本号、切会话花的时间太长,解锁状态已经过期了。刷写脚本里要把“安全访问”放在真正写入Flash的紧前面,中间不要插入耗时长的大流量操作。

5.3 安全性探讨:OTA链路面临的实际威胁与防御

既然热词里有“威胁及防御”,这块我也说几句实操层面的体会。DoIP + OTA的最大暴露面在于:只要拿到了车载以太网的物理访问权(比如OBD口、服务接口、甚至被攻破的车机做跳板),任何人都可以尝试对ECU做路由激活、UDS诊断和固件刷写。具体威胁有这几类:

  • 未授权诊断:直接连上DoIP端口,用标准UDS服务读取车内数据。防御手段是路由激活环节加认证,比如要求Tester提供证书或预共享密钥,网关校验通过后才允许激活。
  • 重放攻击:录一段合法的诊断会话报文,原封不动地重放。如果ECU没做时间戳、随机数、序列号绑定,重放有可能绕过安全访问。防御方式是安全访问的种子要带随机性,并且对每次请求做防重放校验。
  • DoS攻击:向网关发起大量TCP连接或大量诊断请求,耗尽会话资源。ISO 13400里定义的并发连接上限就是一道防线,网关侧还需要做IP白名单、速率限制、会话超时回收。我们实测用脚本每秒发200次路由激活请求,不做限流的网关很快就无法响应正常诊断了。
  • 固件包篡改:OTA升级包在传输过程中被篡改,或者被替换成恶意固件。防御手段是固件包做数字签名(RSA/ECDSA)和完整性校验(CRC/SHA256),ECU写入前必须验证签名。这一点在我们做的安全测评里是强制项,没有签名验证的OTA链路基本可以直接判不合格。

给正在做DoIP/OTA开发的人一句实在话:DoIP带来的便利是双向的,能让你快速刷写,也能让别人快速攻击。不要把安全只当成“上线前加个证书”,要从架构层面把“身份认证、权限分级、防重放、完整性校验”嵌入到整个诊断会话生命周期里。

6. 我的实操心得与后续扩展方向

项目做下来,一个深刻的感受是:DoIP本身不难,真正难的是UDS状态机和多ECU协同的复杂性。

如果你是从CAN诊断转过来,你会发现DoIP的报文结构比CAN诊断简单太多——没有CAN ID的P2/P3仲裁,没有复杂的多帧传输层协议,一切都有清晰的IP和TCP语义。但恰恰因为太“简单”,隐藏了很多需要自己处理的细节:TCP连接管理、粘包处理、长超时场景、多客户端并发、会话资源回收,等等。

后续扩展方向我觉得有几个很值得做:

  • 把检测脚本自动化:上面所有报文分析流程可以封装成Python脚本,基于python-can/scapy或自研的DoIP库实现自动化OAT升级回归测试,每晚跑一轮,输出测试报告。
  • 引入ISO 13400-2:2019的新特性:2019版本增加了一些安全和扩展相关的内容,自动化测试和工具链需要跟上新版本。
  • 做UDS协议栈源码级别的裁剪与优化:我们用的自研栈在收到大量0x36时CPU占用偏高,后来做了批量接收重排、队列深度限制、Flash写入异步化,才把刷写吞吐拉上去。
  • 尝试不依赖OEM的刷写链路线:如果你也想自己搭一套完整的DoIP刷写工具链,建议从Wireshark抓包分析开始,然后基于开源的DoIP协议栈做一个Tester脚本,在仿真环境里反复验证每个SID的时序关系,踩过一轮坑之后再做量产方案,成功率会高很多。

最后分享一个其实很小但救过我好几次的习惯:每次抓包前,先把Tester、网关、ECU的时间基准先对齐(NTP或者手动设定),并且在Tester侧记录本机日志。这样万一OTA出问题,我能把Wireshark的时间戳、Tester日志、ECU日志三条时间线拉到一起做交叉分析,定位效率翻倍。现在每次做OTA项目,我都会先花十分钟把这个基础打牢,后面能省出好几个小时的排障时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询