4、DoIP协议(ISO 13400):TCP/UDP端口分配、车辆发现与路由激活、诊断报文封装与传输

好,咱们进入第四讲。DoIP协议,全称是Diagnostic over Internet Protocol。说白了,就是让诊断数据跑在以太网上。以前咱们诊断用CAN,一根线慢慢传,现在车上的数据量越来越大,OTA升级、远程诊断这些需求一上来,CAN那点带宽就不够看了。DoIP就是干这个的。

我个人习惯把DoIP理解成「汽车界的HTTP」。它定义了诊断仪怎么找到车、怎么建立连接、怎么收发诊断报文。ISO 13400这个标准,就是它的完整规范。嗯,咱们今天不念标准,只讲实战中你必须搞清楚的几个核心点。

4.1 TCP/UDP端口分配:谁用TCP,谁用UDP?

DoIP协议里,端口分配是有明确分工的。我刚开始接触时也犯过糊涂,以为所有数据都走TCP。其实不是。

  • UDP 端口 13400:用于车辆发现和车辆声明。说白了,就是广播和组播用的。诊断仪发个「谁在?」的广播,车机收到后回复「我在这,这是我的VIN」。这个过程不需要建立连接,一发一收,UDP最合适。
  • TCP 端口 13400:用于路由激活、诊断报文传输。一旦诊断仪找到了车,后续所有正经的诊断通信(比如读故障码、刷写数据)都走TCP。因为TCP可靠,能保证数据不丢、不乱序。

重要:UDP和TCP用的是同一个端口号13400,但协议不同。你抓包时一定要区分清楚。我曾经在项目里看到有人把UDP的车辆发现报文当成TCP报文去解析,结果死活连不上车。

你想想看,为什么车辆发现要用UDP?因为诊断仪刚上电时,根本不知道车在哪。它只能发广播。如果广播也用TCP,那得先建立连接,但连谁都不知道,这就成了死循环。所以UDP在这里是必须的。

4.2 车辆发现与路由激活:怎么找到车?怎么连上车?

车辆发现,DoIP里叫Vehicle Discovery。流程很简单:

  1. 诊断仪发送一个UDP广播报文(Vehicle Identification Request),目标地址是255.255.255.255:13400。
  2. 车上的DoIP模块收到后,回复一个Vehicle Identification Response,里面包含VIN、逻辑地址、EID(实体ID)等信息。
  3. 诊断仪拿到这些信息,就知道车在哪了。

这里有个坑。我曾经遇到过,车机在休眠状态下不会响应UDP广播。你得先唤醒它。唤醒方式可能是CAN报文,也可能是特定的网络管理报文。嗯,这个后面章节会细讲。

找到车之后,下一步是路由激活(Routing Activation)。这一步是为了建立诊断仪和车机之间的逻辑通道。诊断仪发一个Routing Activation Request到TCP 13400端口,车机回复一个Routing Activation Response。只有激活成功后,才能开始传诊断报文。

个人经验:路由激活时,诊断仪需要携带自己的逻辑地址。这个地址不能乱填,必须和OEM定义的地址范围一致。我见过有人随便填了个0x0001,结果车机直接拒绝激活。后来查标准才发现,0x0000到0x00FF是保留地址,不能用于诊断仪。

4.3 诊断报文封装与传输:数据怎么打包?

诊断报文本身是UDS(ISO 14229)的格式。DoIP只是负责把它封装成以太网帧。封装格式如下:

字段 长度 说明
协议版本 1字节 当前固定为0x02(ISO 13400-2:2012)
反向协议版本 1字节 一般也是0x02
负载类型 2字节 0x8001表示诊断报文(UDS)
负载长度 4字节 后面跟着的UDS数据长度
源地址 2字节 发送方的逻辑地址
目标地址 2字节 接收方的逻辑地址
UDS数据 可变 实际的诊断请求/响应

举个例子,你要发送一个读取VIN的UDS请求(22 F1 90),封装后的DoIP报文大概长这样:

02 02 80 01 00 00 00 03 0E 80 00 01 22 F1 90
|--|--|------|-----------|-----|-----|-------|
版本 反向 负载类型  负载长度  源地址 目标地址 UDS数据

这里要注意,负载长度是3,因为UDS数据只有3个字节(22 F1 90)。源地址0x0E80是诊断仪的逻辑地址,目标地址0x0001是车机的逻辑地址。这些地址值每个OEM可能不同,但结构是一样的。

避坑指南:我曾经在调试时发现,车机回复的报文里,源地址和目标地址是反过来的。这是对的,因为响应报文里,车机是源,诊断仪是目标。但有人写代码时忘了交换地址,结果解析出来的数据全乱了。记住:请求和响应,地址要互换。

4.4 传输过程中的注意事项

DoIP报文走TCP,但TCP是流式协议,没有消息边界。你发一个DoIP报文,TCP可能把它拆成多个段发送,也可能把多个报文合并成一个段。所以接收方必须自己组包。

怎么组包?靠负载长度字段。你先读8个字节的头部(版本、反向版本、负载类型、负载长度),然后根据负载长度字段的值,再读那么多字节的UDS数据。这样就能完整还原一个DoIP报文。

我建议你在代码里维护一个状态机:

  • 状态0:等待头部(8字节)
  • 状态1:等待UDS数据(根据头部中的长度字段)
  • 状态2:报文完整,交给上层处理

嗯,这个状态机看起来简单,但实际项目中很容易出bug。比如TCP粘包时,你可能在一个recv()调用里收到了两个完整的DoIP报文。这时候你的状态机必须能连续处理,不能只处理一个就退出。

好了,这一章的内容就这些。DoIP的核心就是三件事:UDP找车、TCP激活、封装传输。搞清楚了这三步,你就能在以太网上跑诊断了。下一章咱们聊聊实际抓包分析,看看报文长什么样。