3. DoIP路由激活
任务说明
在DoIP协议中,客户端(测试仪)与服务器(ECU)建立TCP连接后,必须通过路由激活流程使ECU进入就绪状态,之后才能进行诊断会话控制或UDS请求。本案例实现一个最小可运行的DoIP路由激活流程,支持两种激活类型(默认0x00、带安全认证0x01),并处理关键响应码。
协议要点
- 请求报文:
0x02 0xFD 0x00 0x05 + 激活类型(1字节) + 保留字节(4字节,填0x00),共8字节payload - 响应报文:
0x02 0xFD 0x00 0x06 + 激活类型 + 源地址(2字节) + 诊断地址(2字节) + 响应码(1字节),共12字节payload - 响应码:
0x00成功;0x01拒绝;0x02收到不支持的激活类型;0x03需要安全激活但请求为默认类型
代码实现
import socket
import struct
import threading
# DoIP协议常量
PROTOCOL_VERSION = 0x02
INVOKE_ID = 0xFD
ROUTINE_ACTIVATION_REQ = 0x0005
ROUTINE_ACTIVATION_RES = 0x0006
PAYLOAD_TYPE_REQ = 0x8000 # 路由激活请求
PAYLOAD_TYPE_RES = 0x8001 # 路由激活响应
# 响应码
CODE_SUCCESS = 0x00
CODE_REJECT = 0x01
CODE_UNSUPPORTED_ACT_TYPE = 0x02
CODE_NEED_SECURE = 0x03
# 激活类型
ACT_DEFAULT = 0x00
ACT_SECURE = 0x01
# 逻辑地址
CLIENT_ADDR = 0x0E80
ECU_ADDR = 0x0001
def build_activation_request(act_type):
"""构建路由激活请求payload"""
payload = struct.pack('!BBHBBBBB',
PROTOCOL_VERSION, INVOKE_ID,
ROUTINE_ACTIVATION_REQ,
act_type, 0x00, 0x00, 0x00, 0x00)
return payload
def build_doip_message(payload_type, payload):
"""构建完整DoIP消息(含12字节头)"""
header = struct.pack('!BBH I', PROTOCOL_VERSION, INVOKE_ID,
payload_type, len(payload))
return header + payload
def parse_activation_response(payload):
"""解析路由激活响应,返回(激活类型, 客户端地址, ECU地址, 响应码)"""
if len(payload) != 12:
return None
ver, inv, ptype, _ = struct.unpack('!BBHI', payload[:8])
if ptype != ROUTINE_ACTIVATION_RES:
return None
act_type, client, ecu, code = struct.unpack('!BHHB', payload[8:])
return act_type, client, ecu, code
def handle_activation(sock, client_addr):
"""处理单个客户端的路由激活请求"""
# 接收DoIP头(8字节)+ payload
data = sock.recv(1024)
if len(data) < 8:
return
ver, inv, ptype, plen = struct.unpack('!BBHI', data[:8])
if ptype != PAYLOAD_TYPE_REQ:
return
# 接收完整payload
while len(data) < 8 + plen:
data += sock.recv(1024)
payload = data[8:8 + plen]
if len(payload) != 8:
return
# 解析请求
req_ver, req_inv, req_type, act_type = struct.unpack('!BBHB', payload[:5])
# 处理激活类型
if act_type == ACT_DEFAULT:
resp_code = CODE_SUCCESS
elif act_type == ACT_SECURE:
# 本案例不实现完整安全认证,直接拒绝
resp_code = CODE_REJECT
else:
resp_code = CODE_UNSUPPORTED_ACT_TYPE
# 构建响应payload
resp_payload = struct.pack('!BBH BHHB',
PROTOCOL_VERSION, INVOKE_ID,
ROUTINE_ACTIVATION_RES,
act_type, client_addr, ECU_ADDR, resp_code)
msg = build_doip_message(PAYLOAD_TYPE_RES, resp_payload)
sock.sendall(msg)
def run_server(port=13400):
"""启动DoIP服务器"""
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', port))
srv.listen(1)
print(f'DoIP服务器监听 {port}')
while True:
conn, _ = srv.accept()
threading.Thread(target=handle_activation,
args=(conn, CLIENT_ADDR), daemon=True).start()
def client_activate(act_type, port=13400):
"""客户端发起路由激活"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', port))
req_payload = build_activation_request(act_type)
msg = build_doip_message(PAYLOAD_TYPE_REQ, req_payload)
sock.sendall(msg)
# 接收响应
resp = sock.recv(1024)
while len(resp) < 20: # 8字节头 + 12字节payload
resp += sock.recv(1024)
result = parse_activation_response(resp[8:])
sock.close()
return result
if __name__ == '__main__':
import sys
# 启动服务器线程
threading.Thread(target=run_server, daemon=True).start()
import time
time.sleep(0.5)
# 测试三种激活类型
for act in [ACT_DEFAULT, ACT_SECURE, 0x05]:
res = client_activate(act)
if res:
act_type, client, ecu, code = res
print(f'激活类型0x{act:02X} → 响应码0x{code:02X} '
f'(客户端0x{client:04X}, ECU 0x{ecu:04X})')
else:
print(f'激活类型0x{act:02X} → 解析失败')
运行与验证
- 运行:直接执行
python doip_activation.py,脚本自动启动服务器并依次测试三种激活类型。 - 预期输出(由代码逻辑直接推出):
DoIP服务器监听 13400 激活类型0x00 → 响应码0x00 (客户端0x0E80, ECU 0x0001) 激活类型0x01 → 响应码0x01 (客户端0x0E80, ECU 0x0001) 激活类型0x05 → 响应码0x02 (客户端0x0E80, ECU 0x0001) - 判定标准:默认激活返回成功;安全激活因未实现认证返回拒绝;未知类型返回不支持。若需验证真实网络交互,可去掉
if __name__中的自动测试,单独运行服务器后用Wireshark抓包观察TCP 13400端口上的DoIP报文。
关键说明
- 本案例聚焦路由激活流程本身,安全激活(0x01)仅返回拒绝码,不实现完整TLS认证逻辑。
- DoIP头中
payload type使用0x8000/0x8001(带ISO 13400-2保留位),实际车载网络中可能使用0x0005/0x0006,需按具体实现调整。 - 逻辑地址(0x0E80/0x0001)为示例值,实际ECU地址由整车网络定义。