pythonでDoIP ~⑤DoIP通信プロトコル実装~

DoIP,python

ついに最終回としてDoIP通信プロトコルを実装していきます。

設計しているうちにいろいろ見直ししてきたので、以前検討した以下GUIから若干変わっています。

通信プロトコル対応内容

以下のDoIP通信を実現できるような実装をしていきます。

コマンド対応プロトコル実装共通typelenpeyload
Generic DoIP header
negative acknowledge
非対応UDP/
TCP
サーバ/
クライアント
02FD000000000001NACKコード (1)
Vehicle identification
request message
対応UDPクライアント02FD000100000000
Vehicle identification
request message with EID
対応UDPクライアント02FD000200000006EID (6) *任意
Vehicle identification
request message with VIN
対応UDPクライアント02FD000300000011VIN (17) *任意
vehicle identification
response message
対応UDPサーバ02FD000400000021VIN (17) *任意
論理アドレス (2) *任意
EID (6) *任意
GID (6) *任意
アクションコード (1)
*0x00 固定
VIN / GID同期状態 (1)
*0x00 固定
Vehicle announcement
message
非対応UDPサーバ02FD000400000021VIN (17) *任意
論理アドレス (2) *任意
EID (6) *任意
GID (6) *0x000000000001固定
アクションコード (1) *0x00 固定
VIN / GID同期状態 (1) *0x00 固定
DoIP entity status
request
対応UDPクライアント02FD400100000000
DoIP entity status
response
対応UDPサーバ02FD400200000007ノードタイプ (1) *0x01固定
同時接続可能ソケット数 (1) *任意
同時接続中のソケット数 (1) *任意
扱える最大データ長 (4) *任意
Diagnostic power mode
information request
対応UDPクライアント02FD400300000000
Diagnostic power mode
information response
対応UDPサーバ02FD400400000001診断状態 (1) *任意
Routing activation
request
対応TCPクライアント02FD000500000007SA (2) *任意
タイプ (1) *0x00 固定
リザーブ (4) *0x00000000 固定
Routing activation
response
対応TCPサーバ02FD000600000009SA (2) *任意
TA (2) *任意
応答コード (1) *0x10固定
リザーブ (4) *0x00000000 固定
Alive check
request
対応TCPクライアント02FD000700000000
Alive check
response
対応TCPサーバ02FD000800000002SA (2) *任意
Diagnostic message対応TCPサーバ/
クライアント
02FD8001任意SA (2) *任意
TA (2) *任意
ユーザデータ *任意
Diagnostic message
positive acknowledgement
対応TCPサーバ/
クライアント
02FD800200000005SA (2) *任意
TA (2) *任意
ACKコード (1) *0x00固定
Diagnostic message
negative acknowledgement
非対応TCPサーバ/
クライアント
02FD800300000005SA (2) *任意
TA (2) *任意
NACKコード (1) *0x00固定

peyload列の任意パラメータについては、GUIから任意の値を設定できるようにします。

また、異常系については今回の実装対象外とします。

各コマンドデータの詳細については以下過去記事を確認してください。

ソース

メイン処理からの要求を受けてDoIPメッセージの作成、受信データの解析を行うようなコードを作成しました。

やっつけなので見直すべき箇所が多々あり申し訳ない。

全コードについては以下参照願います。

DoIPクライアント

import pandas as pd

# 送信コマンドテーブル
doip_cmd_tbl_send = \
[   #"cmand"                                            ,"protocol","KEY"                   ,"CMN" ,"TYPE","LEN"     ,"VALUE1"         ,"VALUE2"   ,"VALUE3"
    ["Vehicle identification request message"           ,"UDP"     ,"-CMD_UDP_VI_REQ-"      ,"02FD","0001","00000000",None             ,None       ,None           ],
    ["Vehicle identification request message with EID"  ,"UDP"     ,"-CMD_UDP_VI_REQ_EID-"  ,"02FD","0002","00000006","-TXT_EID-"      ,None       ,None           ],
    ["Vehicle identification request message with VIN"  ,"UDP"     ,"-CMD_UDP_VI_REQ_VIN-"  ,"02FD","0003","00000011","-TXT_VIN-"      ,None       ,None           ],
    ["DoIP entity status request"                       ,"UDP"     ,"-CMD_UDP_DES_REQ-"     ,"02FD","4001","00000000",None             ,None       ,None           ],
    ["Diagnostic power mode information request"        ,"UDP"     ,"-CMD_UDP_DPMI_REQ-"    ,"02FD","4003","00000000",None             ,None       ,None           ],
    ["Routing activation request"                       ,"TCP"     ,"-CMD_TCP_RA_REQ-"      ,"02FD","0005","00000007","-TXT_SA1-"      ,None       ,None           ],
    ["Alive check request"                              ,"TCP"     ,"-CMD_TCP_AC_REQ-"      ,"02FD","0007","00000000",None             ,None       ,None           ],
    ["Diagnostic message"                               ,"TCP"     ,"-CMD_TCP_DM-"          ,"02FD","8001","00000000","-TXT_SA2-"      ,"-TXT_TA-" ,"-TXT_DIAG-"   ],
    #["FREE MESSAGE"                                     ,"UDP"     ,"-CMD_UDP_FREE-"        ,None  ,None  ,None      ,"-TXT_UDP_FREE-" ,None       ,None           ],
    #["FREE MESSAGE"                                     ,"TCP"     ,"-CMD_TCP_FREE-"        ,None  ,None  ,None      ,"-TXT_TCP_FREE-" ,None       ,None           ],
    #["Diagnostic message positive acknowledgement"      ,"-CMD_TCP_DMA-"         ,"02FD","8002","00000005","-TXT_SA2-"  ,"-TXT_TA-" ,None           ],
]

# 受信コマンドテーブル
doip_cmd_tbl_recv = \
[
    ["vehicle identification response message"          ,"UDP"     ,"-CMD_UDP_VI_RES-"      ,"02FD","0004","00000021"],
    ["DoIP entity status response"                      ,"UDP"     ,"-CMD_UDP_DES_RES-"     ,"02FD","4002","00000007"],
    ["Diagnostic power mode information response"       ,"UDP"     ,"-CMD_UDP_DPMI_RES-"    ,"02FD","4004","00000001"],
    ["Routing activation response"                      ,"TCP"     ,"-CMD_TCP_RA_RES-"      ,"02FD","0006","00000009"],
    ["Alive check response"                             ,"TCP"     ,"-CMD_TCP_AC_RES-"      ,"02FD","0008","00000002"],
    ["Diagnostic message"                               ,"TCP"     ,"-CMD_TCP_DM-"          ,"02FD","8001","00000000"],
    ["Diagnostic message positive acknowledgement"      ,"TCP"     ,"-CMD_TCP_DMA-"         ,"02FD","8002","00000005"],
]

doip_send_df = pd.DataFrame((doip_cmd_tbl_send), columns=["cmand","protocol", "KEY", "CMN", "TYPE","LEN","VALUE1","VALUE2","VALUE3"])
doip_recv_df = pd.DataFrame((doip_cmd_tbl_recv), columns=["cmand","protocol", "KEY", "CMN", "TYPE","LEN"])


# 送信データ作成関数
def doip_make_msg(values,protocol):
    send_msg = None
    send_data = None
    # 送信コマンドテーブル検索
    for index, row in doip_send_df.iterrows():
        # 送信データ種別を判定
        if values[row["KEY"]] == True and protocol == row["protocol"] :
            send_msg = row["cmand"]
            payload_data = ""
            payload_len =""
            # ペイロード部分作成
            if row["cmand"] == "Routing activation request":
                payload_data += values[row["VALUE1"]]
                payload_data += "0000000000"
            else:
                if row["VALUE1"] != None:
                    payload_data += values[row["VALUE1"]]
                if row["VALUE2"] != None:
                    payload_data += values[row["VALUE2"]]
                if row["VALUE3"] != None:
                    payload_data += values[row["VALUE3"]]
            payload_len = hex(int(len(payload_data)/2))[2:]
            payload_len = payload_len.zfill(8)
            if None != payload_data:
                send_data = row["CMN"] + row["TYPE"] + payload_len + payload_data
            break
    return send_msg ,send_data


# ACK送信データ作成関数
def doip_make_msg_ack(values):
    send_msg = "02FD800200000005" + values["-TXT_SA2-"] + "00"
    return send_msg


# 受信データ判定関数
def doip_recv_msg(data,protocol):
    recv_msg = ""
    type = data[4:8]
    for index, row in doip_recv_df.iterrows():
        # 受信データ種別を判定
        if type == row["TYPE"] and protocol == row["protocol"] :
            recv_msg = row["cmand"]
    return recv_msg

DoIPサーバ

import pandas as pd
import app

# 初期設定
g_values = ""
g_sa = ""

# 応答コマンドテーブル
doip_cmd_tbl_send = \
[   #"REQUEST"                                        ,"REQTYPE"    ,"RESPONCE"                                    ,"PROTCOL","CMN" ,"TYPE","LEN"     ,"VALUE1"     ,"VALUE2"      ,"VALUE3"        ,"VALUE4"
    ["Vehicle identification request message"         ,"0001"       ,"vehicle identification response message"    ,"UDP"     ,"02FD","0004","00000021","-VIN_GW-"   ,"-LOGI_ADDR-","-EID_GW-"       ,"-GID_GW-"  ],
    ["Vehicle identification request message with EID","0002"       ,"vehicle identification response message"    ,"UDP"     ,"02FD","0004","00000021","-VIN_GW-"   ,"-LOGI_ADDR-","-EID_GW-"       ,"-GID_GW-"  ],
    ["Vehicle identification request message with VIN","0003"       ,"vehicle identification response message"    ,"UDP"     ,"02FD","0004","00000021","-VIN_GW-"   ,"-LOGI_ADDR-","-EID_GW-"       ,"-GID_GW-"  ],
    ["DoIP entity status request"                     ,"4001"       ,"DoIP entity status response"                ,"UDP"     ,"02FD","4002","00000007","-NODE_TYPE-","-CONN_NUM-" ,"-CONN_NOW_NUM-" ,"-MAX_LEN-" ],
    ["Diagnostic power mode information request"      ,"4003"       ,"Diagnostic power mode information response" ,"UDP"     ,"02FD","4004","00000001","-DIAG_ST-"  ,None         ,None             ,None        ],
    ["Routing activation request"                     ,"0005"       ,"Routing activation response"                ,"TCP"     ,"02FD","0006","00000009","-LOGI_ADDR-","-LOGI_ADDR-",None             ,None        ],
    ["Alive check request"                            ,"0007"       ,"Alive check response"                       ,"TCP"     ,"02FD","0008","00000002","-LOGI_ADDR-","-LOGI_ADDR-",None             ,None        ],
    ["Diagnostic message"                             ,"8001"       ,"Diagnostic message positive acknowledgement","TCP"     ,"02FD","8002","00000005","-LOGI_ADDR-","-LOGI_ADDR-",None             ,None        ],
    ["Diagnostic message positive acknowledgement"    ,"8002"       ,None                                         ,"TCP"     ,None  ,None   ,None     ,None         ,None         ,None             ,None        ],]

doip_recv_df = pd.DataFrame((doip_cmd_tbl_send), columns=["REQ","REQTYPE","RES","PROTCOL","CMN","TYPE","LEN","VALUE1","VALUE2","VALUE3","VALUE4"])


# 初期化関数
def doip_init(values):
    global g_values
    g_values = values
    return


# 送信データ作成関数
def doip_make_msg(row,data):
    global g_sa
    send_data = None
    send_msg = row["RES"]
    payload_data = ""
    # ペイロード部分作成
    if row["PROTCOL"] == "UDP":
        if row["VALUE1"] != None:
            payload_data += g_values[row["VALUE1"]]
        if row["VALUE2"] != None:
            payload_data += g_values[row["VALUE2"]]
        if row["VALUE3"] != None:
            payload_data += g_values[row["VALUE3"]]
        if row["VALUE4"] != None:
            payload_data += g_values[row["VALUE4"]]
        if row["RES"] == "vehicle identification response message":
            payload_data += "0000"
    else:
        if row["RES"] == "Routing activation response":
            g_sa = data[16:20]
            payload_data += g_sa + g_values[row["VALUE2"]] + "1000000000"
        if row["RES"] == "Alive check response":
            payload_data += g_values[row["VALUE1"]]
        if row["RES"] == "Diagnostic message positive acknowledgement":
            if "" != g_sa:
                payload_data += g_values[row["VALUE1"]] + g_sa + "00"
    # ぺーロード長作成
    payload_len = hex(int(len(payload_data)/2))[2:]
    payload_len = payload_len.zfill(8)
    # 送信データ作成
    if None != payload_data:
        send_data = row["CMN"] + row["TYPE"] + payload_len + payload_data
    return send_msg ,send_data


# 受信データ判定関数
def doip_recv_msg(data,protocol):
    recv_msg = ""
    send_msg = ""
    send_data = None
    type = data[4:8]
    for index, row in doip_recv_df.iterrows():
        # 受信データ種別を判定
        if type == row["REQTYPE"] and protocol == row["PROTCOL"] :
            recv_msg = row["REQ"]
            if row["REQ"] != "Diagnostic message positive acknowledgement":
                send_msg , send_data = doip_make_msg(row,data)
    return protocol,recv_msg,data,send_msg,send_data


# ACK送信データ作成関数
def doip_make_msg_ack(values):
    send_msg = "02FD800200000005" + values["-TXT_SA2-"] + "00"
    return send_msg

動作確認

UIで対応しているコマンドについては一通り通信確認が取れました。

DoIPクライアントUI

DoIPサーバUI

wiresharkによるパケット確認

ローカルホスト間の通信キャプチャについては以下参照。

以前も話しましたが、wiresharkがDoIPプロトコルに対応してくれているため、通信データの確認がとてもしやすかったです。

通信フォーマットがどこか違うとwireshark上でmarformedとして教えてくれるので、デバッグがはかどりました。

最後全コマンド実施した時のキャプチャが以下。

課題

ひとまずはDoIP通信が出来たということで、これで完了とします。

今回実装できていない内容としては下記があります。

  • 車両内ECUに対するルーティング処理
  • タイムアウト関連
  • 異常系
  • ソケット管理
  • UDS関連

余裕があればそのうち。

DoIP,pythonDoIP,python