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

ついに最終回としてDoIP通信プロトコルを実装していきます。
設計しているうちにいろいろ見直ししてきたので、以前検討した以下GUIから若干変わっています。
Contents
通信プロトコル対応内容
以下のDoIP通信を実現できるような実装をしていきます。
| コマンド | 対応 | プロトコル | 実装 | 共通 | type | len | peyload | 
|---|---|---|---|---|---|---|---|
| Generic DoIP header negative acknowledge | 非対応 | UDP/ TCP | サーバ/ クライアント | 02FD | 0000 | 00000001 | NACKコード (1) | 
| Vehicle identification request message | 対応 | UDP | クライアント | 02FD | 0001 | 00000000 | – | 
| Vehicle identification request message with EID | 対応 | UDP | クライアント | 02FD | 0002 | 00000006 | EID (6) *任意 | 
| Vehicle identification request message with VIN | 対応 | UDP | クライアント | 02FD | 0003 | 00000011 | VIN (17) *任意 | 
| vehicle identification response message | 対応 | UDP | サーバ | 02FD | 0004 | 00000021 | VIN (17) *任意 論理アドレス (2) *任意 EID (6) *任意 GID (6) *任意 アクションコード (1) *0x00 固定 VIN / GID同期状態 (1) *0x00 固定 | 
| Vehicle announcement message | 非対応 | UDP | サーバ | 02FD | 0004 | 00000021 | VIN (17) *任意 論理アドレス (2) *任意 EID (6) *任意 GID (6) *0x000000000001固定 アクションコード (1) *0x00 固定 VIN / GID同期状態 (1) *0x00 固定 | 
| DoIP entity status request | 対応 | UDP | クライアント | 02FD | 4001 | 00000000 | – | 
| DoIP entity status response | 対応 | UDP | サーバ | 02FD | 4002 | 00000007 | ノードタイプ (1) *0x01固定 同時接続可能ソケット数 (1) *任意 同時接続中のソケット数 (1) *任意 扱える最大データ長 (4) *任意 | 
| Diagnostic power mode information request | 対応 | UDP | クライアント | 02FD | 4003 | 00000000 | – | 
| Diagnostic power mode information response | 対応 | UDP | サーバ | 02FD | 4004 | 00000001 | 診断状態 (1) *任意 | 
| Routing activation request | 対応 | TCP | クライアント | 02FD | 0005 | 00000007 | SA (2) *任意 タイプ (1) *0x00 固定 リザーブ (4) *0x00000000 固定 | 
| Routing activation response | 対応 | TCP | サーバ | 02FD | 0006 | 00000009 | SA (2) *任意 TA (2) *任意 応答コード (1) *0x10固定 リザーブ (4) *0x00000000 固定 | 
| Alive check request | 対応 | TCP | クライアント | 02FD | 0007 | 00000000 | – | 
| Alive check response | 対応 | TCP | サーバ | 02FD | 0008 | 00000002 | SA (2) *任意 | 
| Diagnostic message | 対応 | TCP | サーバ/ クライアント | 02FD | 8001 | 任意 | SA (2) *任意 TA (2) *任意 ユーザデータ *任意 | 
| Diagnostic message positive acknowledgement | 対応 | TCP | サーバ/ クライアント | 02FD | 8002 | 00000005 | SA (2) *任意 TA (2) *任意 ACKコード (1) *0x00固定 | 
| Diagnostic message negative acknowledgement | 非対応 | TCP | サーバ/ クライアント | 02FD | 8003 | 00000005 | SA (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_msgDoIPサーバ
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関連
余裕があればそのうち。

 https://telecom-engineer.blog/python-doip-gui/
 https://telecom-engineer.blog/python-doip-gui/
 https://telecom-engineer.blog/python-doip-3/
 https://telecom-engineer.blog/python-doip-3/
 https://github.com/hiro-telecom-engineer/python-doip/tree/ma...
 https://github.com/hiro-telecom-engineer/python-doip/tree/ma...
 https://troushoo.blog.fc2.com/blog-entry-388.html
 https://troushoo.blog.fc2.com/blog-entry-388.html