3. DoIP路由激活

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} → 解析失败')

运行与验证

  1. 运行:直接执行 python doip_activation.py,脚本自动启动服务器并依次测试三种激活类型。
  2. 预期输出(由代码逻辑直接推出):
    
    DoIP服务器监听 13400
    激活类型0x00 → 响应码0x00 (客户端0x0E80, ECU 0x0001)
    激活类型0x01 → 响应码0x01 (客户端0x0E80, ECU 0x0001)
    激活类型0x05 → 响应码0x02 (客户端0x0E80, ECU 0x0001)
    
  3. 判定标准:默认激活返回成功;安全激活因未实现认证返回拒绝;未知类型返回不支持。若需验证真实网络交互,可去掉if __name__中的自动测试,单独运行服务器后用Wireshark抓包观察TCP 13400端口上的DoIP报文。

关键说明

  • 本案例聚焦路由激活流程本身,安全激活(0x01)仅返回拒绝码,不实现完整TLS认证逻辑。
  • DoIP头中payload type使用0x8000/0x8001(带ISO 13400-2保留位),实际车载网络中可能使用0x0005/0x0006,需按具体实现调整。
  • 逻辑地址(0x0E80/0x0001)为示例值,实际ECU地址由整车网络定义。