iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Security

30 天實戰車聯網資安系列 第 17

Day 17|第一個 V2X 模擬:讓兩台車互相傳送訊息

  • 分享至 

  • xImage
  •  

Day 16 已經讓 SUMO 裡的 Vehicle 變成會在 OMNeT++ 中建立、移動與離開的動態 Node。

不過,看見兩個 Node 在 Qtenv 裡靠近,仍然不能證明它們真的交換過訊息。

要完成第一個 V2X 模擬,我們至少要把以下事件串起來:

SUMO 建立 carA 與 carB
  → Veins 建立兩個 Vehicle Node
  → Application 讀取各自的 Mobility
  → carA 產生並廣播 V2X Message
  → carB 的 Radio/MAC 收到 Frame
  → carB 的 Application 執行接收邏輯
  → carB 再送出自己的訊息給 carA
  → 結果檔留下傳送、接收與延遲證據

今天會沿用 Day 14 的雙路口路網與 Day 16 的相容環境,指定 carAcarB 從相反方向出發。

兩台車會各自廣播研究用狀態訊息,內容包含 SUMO Vehicle ID、序號、宣告位置、速度與傳送時間。

最後不只看動畫,還要用 Log、Scalar 與 Vector 證明訊息確實從一台車流到另一台車。

今天的學習目標

讀完這篇文章,你應該能夠:

  1. 說明一個 Veins Vehicle Node 裡 Application、MAC、PHY 與 Mobility 的分工
  2. 分辨 SUMO Vehicle ID、OMNeT++ Module Path 與 V2X Message Sender ID
  3. 使用 .msg 定義研究用 V2X Message
  4. 使用 Self-message 週期產生封包
  5. 使用 populateWSM()sendDown() 廣播訊息
  6. 使用 onWSM() 接收訊息並計算模擬延遲
  7. 從 Qtenv、Log、Scalar 與 Vector 驗證 Packet Flow
  8. 說明「封包收到」和「訊息內容可信」為什麼是兩件事

今天的完成標準是什麼?

先把「看到兩台車」與「完成 V2X Message Exchange」分開。

觀察 可以證明什麼 還不能證明什麼
SUMO GUI 有 carAcarB 交通需求成功載入 OMNeT++ 已建立對應 Node
Qtenv 有兩個動態 Vehicle Node Veins 已完成 Vehicle 映射 Application 已經送出封包
Log 出現 SEND Application 已把 Message 交給下層 另一台車一定收到
Log 出現 RECV 接收端 Application 已執行 onWSM() 訊息內容一定真實可信
Scalar 的接收數大於 0 這次 Run 留下接收統計 所有傳送訊息都成功抵達
Vector 有 Reception Delay 可以分析接收延遲分布 延遲數值代表所有真實道路環境

因此今天的合格成果不是一張「兩台車旁邊有無線圈」的截圖。

我們要留下至少一筆由 carA 送出、carB 收到的紀錄,也要留下反方向的證據。

Vehicle Node 裡有哪些元件?

Veins 5.3.1 的 Car 是一個 Compound Module,裡面至少包含三個和今天直接相關的 Submodule:

Submodule 今天負責的工作
appl 建立研究用狀態訊息,處理 Timer 與接收事件
nic 執行 IEEE 802.11p/WAVE 教學模型中的 MAC 與 PHY 行為
veinsmobility 保存由 SUMO 經 TraCI 同步而來的位置、速度及道路狀態

訊息從 appl 送到 nic,再經無線通道抵達其他 Node 的 nic,最後往上交給接收端 appl

carA 從 Mobility 建立研究用狀態訊息,經無線通道送到 carB Application

圖 1:SUMO 提供兩台車的 Ground Truth Mobility,Veins Vehicle Node 再由 Application、MAC 與 PHY 完成研究用訊息的廣播及接收

這張圖有兩條不同的資料流:

Mobility Flow
  SUMO → TraCI → TraCIMobility → Application

Packet Flow
  Sender Application → MAC → PHY → Wireless Channel
  → Receiver PHY → MAC → Receiver Application

Mobility 同步成功,不代表 Packet Flow 已經成功。

相反地,固定位置的 Node 能互傳封包,也不代表交通移動已經正確接入。

今天要同時驗證兩條路徑。

今天傳的是 BSM 或 CAM 嗎?

不是。

我們會建立一個名為 Day17StatusMessage 的研究用訊息,欄位概念和 Day 12 的車輛狀態訊息相似,但它不是 SAE J2735 BSM,也不是 ETSI CAM 的 ASN.1 Payload。

欄位 今天的用途 不代表什麼
senderId 保存 SUMO Vehicle ID,例如 carA 憑證身分或真實車牌
sequenceNumber 協助對照同一來源的訊息順序 完整的防重放機制
senderPosition 保存傳送時 Application 看到的位置 經過 GNSS 誤差模型的真實座標
senderSpeed 保存傳送時 SUMO 提供的速度 經過獨立感測驗證的速度
sentAt 保存 OMNeT++ Simulation Time 具備可信時間來源的安全時間戳記

這樣設計有兩個目的。

第一,先把「產生、傳送、接收與記錄」的 Packet Flow 跑通,不讓 ASN.1、憑證與完整標準 Profile 同時增加實作複雜度。

第二,預先保留 Ground Truth 與宣告內容的邊界。

今天的正常節點會把目前位置寫進 Message。

到了 Day 21 與 Day 30,惡意節點可以只修改 senderPosition,SUMO 中真正的車輛位置仍保持不變,這樣才能量測假位置造成的差異。

Day17StatusMessage 只用於隔離模擬環境,不是可直接部署到道路設備的 V2X 協定。

Broadcast 不是指定傳給另一台車

今天的 populateWSM() 沒有傳入特定接收位址,因此會使用 L2 Broadcast Address。

這代表 carA 不是先知道 carB 的位址,再建立一條點對點連線。

它會把 Message 交給無線下層,位於可接收條件內的節點都有機會處理這筆廣播。

今天的場景只有兩台車,因此我們可以很容易看見互傳效果。

若之後加入十台背景車,同一筆廣播可能被多個 Node 收到,接收數就不再等於傳送數。

還要注意,Broadcast 不應被當成「可靠群播」:

  • 接收端不一定都在可通訊範圍內
  • Radio Propagation 與 Interference 可能讓封包無法解碼
  • MAC Contention 會影響實際傳送時間
  • Broadcast 不會因為應用程式需要,就自動提供端到端確認
  • Application 收到封包,也不代表內容通過來源與合理性驗證

如果應用需要知道特定對象是否收到,還要另外設計回覆、逾時與重試機制。

今天先讓兩台車各自週期廣播,因此反方向訊息是另一筆狀態更新,不是針對前一筆封包的 ACK。

三種 ID 不要混在一起

Day 16 已經提醒,一台概念車輛可能同時有多種身分。

今天會實際看到:

ID 範例 主要用途
SUMO Vehicle ID carA 對應 Route、Position、Speed 與 TraCI Vehicle
OMNeT++ Module Path Day17Scenario.node[0] 找到 Runtime 動態 Module
L2 Address Veins 在 Runtime 指派的 MAC Address 無線資料鏈結層的來源與接收處理
Message senderId carA 今天為了實驗對照放入 Payload 的字串

node[0] 不應被寫死成 carA

動態建立順序可能隨出發時間與場景改變,重新執行後也不應把 Module Index 當成長期 V2X 身分。

今天的 Log 會同時列出接收端 SUMO ID 與 Message 裡的 senderId,用來確認映射,而不是只看 node[0]node[1] 猜測。

一筆 Message 會經過哪些事件?

假設 carA 的第一個 Timer 在第 1 秒到期,概念流程如下:

t = 1.000 s
  → sendStatusTimer 到達 carA Application
  → Application 讀取 carA 的 Position 與 Speed
  → 建立 Day17StatusMessage,sequenceNumber = 0
  → populateWSM() 填入 WAVE 教學模型需要的欄位
  → sendDown() 把 Message 交給 MAC

t > 1.000 s
  → MAC 取得傳送機會
  → PHY 建立無線傳輸
  → carB PHY 嘗試解碼
  → carB MAC 將成功收到的 Frame 往上交付
  → carB Application 執行 onWSM()
  → 記錄 senderId、sequenceNumber 與 receptionDelay

實際接收時間不應先寫死。

它會受到 Radio、MAC、Channel 與事件排程影響,應從這次 Run 的結果讀取。

今天讓兩台車錯開第一個傳送 Timer,避免它們在同一個模擬時間刻意同步產生第一筆封包:

兩台車從建立、第一次廣播到重複傳送與結果輸出的時間軸

圖 2:carAcarB 分別在建立後 1.0 秒及 1.5 秒送出第一筆訊息,之後每秒各自更新;接收時間仍由無線模型與事件排程決定

sendDown() 成功,為什麼不等於對方收到?

Application 呼叫 sendDown() 時,只是把 Message Ownership 交給較低層 Module。

接下來仍要經過:

  1. MAC 排隊與媒體存取
  2. PHY 傳輸及無線傳播
  3. 接收端的訊號與干擾判定
  4. 接收端 MAC 處理
  5. Message 向上交給 Application

只有接收端的 onWSM() 被呼叫,才能說這筆 Message 已到達接收端 Application。

這和 Day 03、Day 04 對 CAN ACK 的提醒相似:不同層次的「成功」不能混為一談。

事件 所在層次 可以判斷的事
sendDown() Sender Application Message 已交給下層處理
PHY Transmission Sender Radio 無線傳輸事件已建立
Receiver 解碼成功 Receiver PHY/MAC Frame 通過模型中的接收判定
onWSM() Receiver Application Application 已取得 Message
Application 採用內容 應用與安全邏輯 還要驗證來源、新鮮度與合理性

今天只做到第四層,並把第五層保留為後續攻擊與防禦篇章的研究問題。

今日實作:讓 carAcarB 互傳研究用狀態訊息

本次實作沿用 Day 16 已驗證的版本組合:

元件 本篇基準版本
Veins 5.3.1
OMNeT++ 6.1.0
SUMO 1.22.0

如果你的版本不同,先回到 Day 16 跑通官方 Example,再移植今天的 Application。

不要在未驗證的版本組合上同時除錯 Build、TraCI 與 Packet Flow。

步驟 1:建立獨立 Project

在 Day 16 使用的 OMNeT++ Workspace 建立 Empty Project:

File
  → New
  → OMNeT++ Project
  → Project Name:day17-v2x
  → Empty Project

接著開啟:

Project
  → Properties
  → Project References
  → 勾選 veins

Project Reference 讓今天的程式可以引用 Veins 的 NED Type、Header 與 Library。

完成後在 Project Root 建立:

Day17Scenario.ned
Day17V2XApp.ned
Day17V2XApp.cc
Day17StatusMessage.msg
omnetpp.ini
day17.rou.xml
day17.sumocfg
day17.launchd.xml
day17.net.xml
config.xml
antenna.xml

day17.net.xml 來自 Day 14,config.xmlantenna.xml 則沿用 Veins 5.3.1 官方 Example。

步驟 2:準備 Day 14 路網與 Veins Radio 設定

如果 Day 14 的工作目錄仍在 ~/sumo-day14,可以複製路網:

cp ~/sumo-day14/day14.net.xml \
  ~/omnetpp-workspace/day17-v2x/day17.net.xml

再從 Veins 5.3.1 官方 Example 複製兩份 Radio 設定:

cp ~/src/veins-5.3.1/examples/veins/config.xml \
  ~/omnetpp-workspace/day17-v2x/config.xml
cp ~/src/veins-5.3.1/examples/veins/antenna.xml \
  ~/omnetpp-workspace/day17-v2x/antenna.xml

上面的路徑沿用 Day 15 與 Day 16 的目錄安排。

如果你的 Workspace 放在其他位置,先以 IDE 的 Project Properties 確認實際 Project Location,再調整目的路徑。

這一步沿用 Day 14 的道路幾何,但不沿用原本六股 Flow。

今天只建立兩台可辨識的測試車,讓 Packet Flow 容易對照。

步驟 3:建立兩台相向行駛的 SUMO Vehicle

day17.rou.xml 填入:

<?xml version="1.0" encoding="UTF-8"?>
<routes>
    <vType id="passenger"
           vClass="passenger"
           accel="2.6"
           decel="4.5"
           sigma="0"
           length="5"
           maxSpeed="13.89"/>

    <route id="west_to_east"
           edges="west_j1 j1_j2 j2_east"/>
    <route id="east_to_west"
           edges="east_j2 j2_j1 j1_west"/>

    <vehicle id="carA"
             type="passenger"
             route="west_to_east"
             depart="0"
             departSpeed="max"
             color="1,0,0"/>

    <vehicle id="carB"
             type="passenger"
             route="east_to_west"
             depart="0"
             departSpeed="max"
             color="0,0,1"/>
</routes>

carA 從西向東行駛,carB 則從東向西行駛。

sigma="0" 只用來減少本篇教學場景中的跟車隨機性,不代表正式研究永遠應關閉 Driver Imperfection。

步驟 4:建立 SUMO Configuration

day17.sumocfg 填入:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <input>
        <net-file value="day17.net.xml"/>
        <route-files value="day17.rou.xml"/>
    </input>

    <time>
        <begin value="0"/>
        <end value="120"/>
        <step-length value="0.1"/>
    </time>

    <report>
        <xml-validation value="never"/>
        <xml-validation.net value="never"/>
        <no-step-log value="true"/>
    </report>
</configuration>

SUMO Step Length 設為 100 ms,後面的 Veins updateInterval 也會使用 100 ms。

兩者採用相同數值讓今天的時間關係容易理解,但這不是所有研究都必須遵守的固定規則。

正式實驗仍要依車速、Radio Model 與量測問題評估同步粒度。

先單獨確認場景:

cd ~/omnetpp-workspace/day17-v2x
sumo -c day17.sumocfg

正常結束只能證明 SUMO 場景可載入,還不能證明 Veins Packet Flow 已接通。

步驟 5:建立 Launch Configuration

day17.launchd.xml 填入:

<?xml version="1.0"?>
<launch>
    <copy file="day17.net.xml"/>
    <copy file="day17.rou.xml"/>
    <copy file="day17.sumocfg" type="config"/>
</launch>

veins_launchd 會把三份檔案放進本次 Run 的暫存目錄,再啟動獨立 SUMO Instance。

步驟 6:定義研究用 Message

Day17StatusMessage.msg 填入:

import veins.base.utils.Coord;
import veins.modules.messages.BaseFrame1609_4;

packet Day17StatusMessage extends BaseFrame1609_4
{
    string senderId;
    int sequenceNumber = 0;
    Coord senderPosition;
    double senderSpeed = 0;
    simtime_t sentAt;
}

OMNeT++ Message Compiler 會依這份 .msg 產生 Day17StatusMessage_m.h 與對應實作,讓 C++ 程式可以使用 setSenderId()getSenderPosition() 等方法。

BaseFrame1609_4 提供 Veins WAVE 教學模型需要的 Channel、User Priority、PSID 與 Recipient Address 等欄位。

今天新增的欄位則屬於研究用 Application Payload。

步驟 7:宣告 Application Module

Day17V2XApp.ned 填入:

package day17;

import org.car2x.veins.modules.application.ieee80211p.DemoBaseApplLayer;

simple Day17V2XApp extends DemoBaseApplLayer
{
    parameters:
        @class(Day17V2XApp);
        double messageInterval @unit(s) = default(1s);
}

這個 Module 繼承 DemoBaseApplLayer,因此可以使用:

  • mobility 讀取 TraCI Mobility
  • populateWSM() 填入下層需要的欄位
  • sendDown() 把 Message 交給 MAC
  • onWSM() 處理收到的研究用 Frame

步驟 8:實作傳送、接收與結果紀錄

Day17V2XApp.cc 填入:

#include <omnetpp.h>

#include "Day17StatusMessage_m.h"
#include "veins/modules/application/ieee80211p/DemoBaseApplLayer.h"

using namespace omnetpp;
using namespace veins;

class Day17V2XApp : public DemoBaseApplLayer
{
  private:
    cMessage* sendTimer = nullptr;
    int nextSequenceNumber = 0;
    int sentCount = 0;
    int receivedCount = 0;
    cOutVector receptionDelayVector;

  protected:
    void initialize(int stage) override;
    void handleSelfMsg(cMessage* msg) override;
    void onWSM(BaseFrame1609_4* frame) override;
    void finish() override;

  public:
    ~Day17V2XApp() override;
};

Define_Module(Day17V2XApp);

void Day17V2XApp::initialize(int stage)
{
    DemoBaseApplLayer::initialize(stage);

    if (stage == 0) {
        sendTimer = new cMessage("sendStatusTimer");
        receptionDelayVector.setName("receptionDelay");
    }
    else if (stage == 1) {
        const std::string vehicleId = mobility->getExternalId();

        if (vehicleId == "carA") {
            scheduleAt(simTime() + SimTime(1, SIMTIME_S), sendTimer);
        }
        else if (vehicleId == "carB") {
            scheduleAt(simTime() + SimTime(1500, SIMTIME_MS), sendTimer);
        }
    }
}

void Day17V2XApp::handleSelfMsg(cMessage* msg)
{
    if (msg != sendTimer) {
        DemoBaseApplLayer::handleSelfMsg(msg);
        return;
    }

    auto* status = new Day17StatusMessage("day17Status");
    populateWSM(status);

    status->setSenderId(mobility->getExternalId().c_str());
    status->setSequenceNumber(nextSequenceNumber++);
    status->setSenderPosition(curPosition);
    status->setSenderSpeed(mobility->getSpeed());
    status->setSentAt(simTime());

    sentCount++;

    EV_INFO << "SEND sender=" << status->getSenderId()
            << " seq=" << status->getSequenceNumber()
            << " position=" << status->getSenderPosition()
            << " speed=" << status->getSenderSpeed()
            << " time=" << status->getSentAt() << "\n";

    sendDown(status);
    scheduleAt(simTime() + par("messageInterval"), sendTimer);
}

void Day17V2XApp::onWSM(BaseFrame1609_4* frame)
{
    auto* status = check_and_cast<Day17StatusMessage*>(frame);
    const simtime_t delay = simTime() - status->getSentAt();

    receivedCount++;
    receptionDelayVector.record(delay);

    EV_INFO << "RECV receiver=" << mobility->getExternalId()
            << " sender=" << status->getSenderId()
            << " seq=" << status->getSequenceNumber()
            << " declaredPosition=" << status->getSenderPosition()
            << " receiverPosition=" << curPosition
            << " delay=" << delay << "\n";

    findHost()->getDisplayString().setTagArg("i", 1, "green");
}

void Day17V2XApp::finish()
{
    recordScalar("day17Sent", sentCount);
    recordScalar("day17Received", receivedCount);
    DemoBaseApplLayer::finish();
}

Day17V2XApp::~Day17V2XApp()
{
    cancelAndDelete(sendTimer);
}

這段程式有五個重要細節。

1. 先呼叫 Parent initialize()

DemoBaseApplLayer::initialize(stage) 會準備 Mobility、MAC 與統計欄位。

如果跳過 Parent Initialization,mobilitymac 可能尚未可用。

2. 依 SUMO Vehicle ID 錯開第一個 Timer

carA 在建立後 1 秒送出第一筆,carB 則在 1.5 秒送出。

後續都使用 messageInterval = 1s

這些時間是教學設定,不是 BSM 或 CAM 的標準傳送週期。

3. populateWSM() 預設建立 Broadcast

沒有提供特定 Recipient 時,populateWSM() 會使用 L2 Broadcast Address,並依 dataOnSch 等參數設定 Channel 與 User Priority。

4. sendDown() 後不再操作 Message

呼叫 sendDown(status) 後,Message Ownership 已交給 OMNeT++/下層 Module。

目前 Application 不應再讀寫或刪除 status

5. onWSM() 不刪除收到的 Frame

Veins 的 DemoBaseApplLayer::handleLowerMsg() 會在 onWSM() 返回後刪除收到的 Message。

因此這個 Callback 只讀取欄位與記錄結果,不自行 delete frame,避免 Double Free。

步驟 9:建立頂層 Scenario

Day17Scenario.ned 填入:

package day17;

import org.car2x.veins.nodes.Scenario;

network Day17Scenario extends Scenario
{
}

Scenario 已經包含:

  • TraCIScenarioManagerLaunchd
  • ConnectionManager
  • ObstacleControl
  • AnnotationManager
  • Runtime 動態建立的 Car Module

今天不加入 RSU,讓場景只保留兩台 Vehicle Node。

步驟 10:設定耦合、Radio 與 Application

omnetpp.ini 填入:

[General]
network = day17.Day17Scenario
sim-time-limit = 35s
debug-on-errors = true
print-undisposed = true

**.scalar-recording = true
**.vector-recording = true

*.playgroundSizeX = 1200m
*.playgroundSizeY = 800m
*.playgroundSizeZ = 50m

*.obstacles.obstacles = xmldoc("config.xml", "//AnalogueModel[@type='SimpleObstacleShadowing']/obstacles")

*.manager.updateInterval = 0.1s
*.manager.host = "localhost"
*.manager.port = 9999
*.manager.autoShutdown = true
*.manager.launchConfig = xmldoc("day17.launchd.xml")

*.connectionManager.sendDirect = true
*.connectionManager.maxInterfDist = 1000m
*.connectionManager.drawMaxIntfDist = false

*.**.nic.mac1609_4.useServiceChannel = false
*.**.nic.mac1609_4.txPower = 20mW
*.**.nic.mac1609_4.bitrate = 6Mbps

*.**.nic.phy80211p.minPowerLevel = -110dBm
*.**.nic.phy80211p.useNoiseFloor = true
*.**.nic.phy80211p.noiseFloor = -98dBm
*.**.nic.phy80211p.decider = xmldoc("config.xml")
*.**.nic.phy80211p.analogueModels = xmldoc("config.xml")
*.**.nic.phy80211p.usePropagationDelay = true
*.**.nic.phy80211p.antenna = xmldoc("antenna.xml", "/root/Antenna[@id='monopole']")

*.node[*].applType = "day17.Day17V2XApp"
*.node[*].appl.headerLength = 80bit
*.node[*].appl.sendBeacons = false
*.node[*].appl.dataOnSch = false
*.node[*].appl.dataLengthBits = 320bit
*.node[*].appl.dataUserPriority = 5
*.node[*].appl.messageInterval = 1s

*.node[*].veinsmobility.x = 0
*.node[*].veinsmobility.y = 0
*.node[*].veinsmobility.z = 0
*.node[*].veinsmobility.setHostSpeed = false

上面的 PHY、MAC 與 Antenna 設定沿用 Veins 5.3.1 官方 Example 作為教學基準。

它們不是經過特定城市、車款或天線量測校正的參數。

正式研究必須說明模型選擇、參數來源、敏感度分析與限制,不能因為官方 Example 能執行,就直接把結果當成道路實測效能。

sendBeacons = false 也很重要。

今天不用 Veins 內建的週期 DemoSafetyMessage,而是由 sendStatusTimer 建立自訂 Day17StatusMessage,避免兩種週期訊息同時出現而混淆結果。

步驟 11:Build Project

在 IDE 執行:

Project
  → Clean
  → 選擇 day17-v2x

Project
  → Build Project

Build 過程會先由 Message Compiler 產生 Day17StatusMessage_m.*,再編譯 Day17V2XApp.cc

若出現找不到 Veins Header 或 NED Type,依序確認:

  1. Project References 是否勾選 veins
  2. Veins Project 是否已在同一個 Workspace Build 成功
  3. 目前 IDE 是否從 Day 16 的 OMNeT++ 6.1.0 環境啟動
  4. Day17StatusMessage.msg 的 Import 路徑是否和 Veins 5.3.1 一致
  5. Build Console 最早出現的 Error 是什麼

不要只修最後一行 Linker Error,最前面的 Message Compiler 或 Header Error 通常才是根因。

步驟 12:啟動 veins_launchd

在另一個 Terminal 進入 Veins Root:

cd ~/src/veins-5.3.1
./bin/veins_launchd -vv -c "$(command -v sumo)"

確認畫面出現:

Listening on port 9999

保持 Terminal 開啟。

今天仍使用 localhost,不把 veins_launchd 暴露到外部網路。

步驟 13:用 Qtenv 逐步觀察 Packet Flow

在 IDE 選取 omnetpp.ini,執行:

Run As
  → OMNeT++ Simulation
  → Qtenv

模擬開始後先觀察:

  1. node[0]node[1] 是否由 Manager 動態建立
  2. 兩個 Node 的 veinsmobility Position 是否每 100 ms 更新
  3. Module Log 是否在約第 1 秒出現 SEND sender=carA
  4. 是否在稍後出現 RECV receiver=carB sender=carA
  5. 約第 1.5 秒是否出現 SEND sender=carB
  6. 是否出現 RECV receiver=carA sender=carB

Log 的實際 Module Path 與小數時間會依 Runtime Event 決定,不需要和某一張教學畫面完全相同。

核心關係應該是:

SEND sender=carA seq=0
  → RECV receiver=carB sender=carA seq=0

SEND sender=carB seq=0
  → RECV receiver=carA sender=carB seq=0

如果只看到 SEND 而沒有 RECV,先檢查兩台車的距離、Radio Parameter、config.xml、Antenna 設定與 PHY Log。

不要因為 Coupling 正常就把問題歸咎於 TraCI。

步驟 14:用 Cmdenv 留下可重現結果

Qtenv 適合理解事件,但正式保存結果應再用 Cmdenv 執行一次。

在 Project Run Configuration 選擇 Cmdenv,或從已設定好 Project Library 與 NED Path 的環境執行對應 Run Command。

模擬正常到達第 35 秒後,results/ 應包含 .sca.vec 等結果檔。

在 OMNeT++ Result Analysis 中檢查:

Result 預期觀察
day17Sent carAcarB 都大於 0
day17Received 兩台車都至少收到對方一筆訊息
generatedWSMs Veins Base Application 記錄的研究用 WSM 傳送數
receivedWSMs Veins Base Application 記錄的研究用 WSM 接收數
receptionDelay 每筆到達 Application 的模擬延遲 Vector

不要先寫死精確收件數。

兩台車在不同時間位置不同,無線接收也受到模型參數影響。

今天真正要驗證的是:兩個方向都有接收事件,傳送與接收序號能對上,結果檔也保存了延遲資料。

如何從結果回答三個問題?

問題一:兩台車真的都送過訊息嗎?

查看兩個 Application Module 的 day17SentgeneratedWSMs

如果兩者都大於 0,並且 Log 分別出現 sender=carAsender=carB,可以確認兩台車都執行過傳送邏輯。

問題二:訊息真的到達另一台車的 Application 嗎?

查看 day17ReceivedreceivedWSMsRECV Log。

RECV receiver=carB sender=carA 表示 carA 的 Message 已走到 carB Application。

反方向紀錄則證明 carB 的 Message 到達 carA

問題三:訊息產生時的位置和接收時的位置有何不同?

declaredPosition 是傳送端建立 Message 時寫入的座標,receiverPosition 則是接收端處理 Message 時自己的目前座標。

這兩個欄位本來就不應相等。

可以進一步計算兩車距離,也能比較相鄰序號的宣告位置是否形成連續軌跡。

但今天的延遲通常遠小於 100 ms Mobility Update Interval,因此不要從單一短 Run 過度推論真實車輛動態與無線延遲關係。

常見失敗現象怎麼判斷?

現象 優先檢查 不要立刻下的結論
SUMO 無法載入 Route Day 14 Edge ID、day17.net.xml 與 Route 連續性 C++ Application 寫錯
Qtenv 沒有動態 Node veins_launchd、Launch Config 與 Vehicle depart Radio Range 太小
Build 找不到 _m.h .msg 是否成功編譯與 Makefile 是否更新 TraCI 版本不相容
有 Node、沒有 SEND applType、Vehicle ID 與 Timer Scheduling PHY 無法解碼
SEND、沒有 RECV 兩車距離、MAC/PHY、Decider 與 Antenna 設定 SUMO Mobility 沒同步
RECV、沒有 Vector receptionDelayVector.setName() 與 Vector Recording Message 沒有到達
node[0] 突然消失 SUMO Vehicle 是否已抵達或離開 Application 一定 Crash

除錯時先判斷問題位於:

Traffic
  → Coupling
  → Dynamic Node
  → Application Timer
  → MAC/PHY
  → Receiver Application
  → Result Recording

一次只往下一層,會比同時修改 Route、Radio 與 C++ 更容易找到根因。

這個模型還不能代表什麼?

完成 Message Exchange 後,仍要主動列出限制:

  • Day17StatusMessage 不是標準 BSM/CAM 編碼
  • 沒有憑證、數位簽章與安全標頭
  • 沒有驗證 Sender 是否有權宣告這個身分
  • sequenceNumber 還沒有形成完整防重放視窗
  • 宣告位置直接取自 SUMO Ground Truth,尚未加入感測誤差
  • Radio Parameter 沿用官方 Example,沒有完成場域校正
  • 場景只有兩台車,沒有高密度頻道競爭
  • 沒有建築物或其他車輛遮蔽模型的場景驗證
  • 沒有 Application ACK、重試與遺失處理
  • 沒有讓收到的訊息改變車速或 Route

因此今天的結論只能是:

在這組版本、路網與模型設定下,兩個由 SUMO 驅動的 Veins Vehicle Node 完成了雙向研究用 Message Exchange,並留下 Application 接收與延遲結果。

不能把它改寫成「真實 V2X 在道路上保證可靠」,也不能說「訊息收到,所以位置一定正確」。

模擬器任務關卡:留下第一份 Packet Flow 證據

項目 本篇任務
主線概念 找出 Sender Timer、MAC/PHY 傳輸與 Receiver onWSM() 的事件鏈
隔離工具 SUMO、OMNeT++、Veins 與本機 veins_launchd
測試節點 carAcarB,不連接真實車輛或 RSU
輸出證據 Version Tuple、場景檔、雙向 SEND/RECV Log、Scalar 與 Delay Vector

完成後保存:

Veins/OMNeT++/SUMO Version Tuple
day17.net.xml/day17.rou.xml/day17.sumocfg
day17.launchd.xml/omnetpp.ini
Day17StatusMessage.msg
Day17V2XApp.ned/Day17V2XApp.cc
Random Seed 與 sim-time-limit
Scalar/Vector Result

這些檔案會成為後續 Baseline。

Day 21 可以只修改 Payload 的宣告位置,Day 22 再加入合理性檢查,Day 30 則比較 Baseline、Attack 與 Mitigation。

安全與法律提醒: 本篇所有訊息只在本機模擬環境中傳送,不會對道路車輛、公共 RSU、行動網路或他人的設備發送資料。veins_launchd 維持在 localhost 或受控隔離網路,不任意暴露到公開網路。

從資安角度看第一筆 V2X Message

今天的 Message 沒有任何安全驗證,因此接收端只是把 senderId 當成 Payload 字串顯示。

它不能證明:

  • 發送者真的就是 carA
  • 同一個 Physical Node 沒有宣告其他身分
  • senderPosition 和 SUMO Ground Truth 相同
  • sequenceNumber 沒有被重設或重送
  • Message 在更高層具有授權與可信時間

這也正是後續 V2X 攻擊面會從哪裡開始。

可以先把今天的信任鏈拆成:

SUMO Ground Truth
  → Sender Application 讀取位置
  → Payload 宣告 senderId/position/speed/time
  → 無線模型傳送
  → Receiver Application 收到 Payload
  → 尚未進行安全驗證與合理性檢查

未來加入簽章後,也只能提高來源與受保護內容完整性的信心。

接收端仍要比較時間、位置、速度、道路與其他觀察,判斷內容是否合理。

今天留下的 Ground Truth 與 Payload 欄位,正是進行這項比較的基礎。

今日重點

  • SUMO Vehicle、OMNeT++ Vehicle Node 與 V2X Message 位於不同層次,需要透過 TraCI、Mobility 與 Application 串起來
  • Car Compound Module 由 Application、NIC 與 TraCIMobility 共同完成移動節點通訊
  • Day17StatusMessage 是研究用 Payload,不是標準 BSM 或 CAM
  • Self-message 用來安排週期傳送,populateWSM() 準備下層欄位,sendDown() 將 Message 交給 MAC
  • onWSM() 被呼叫,才代表 Message 已到達接收端 Application
  • L2 Broadcast 不保證所有節點收到,也不提供應用層確認
  • SUMO Vehicle ID、OMNeT++ Module Path、L2 Address 與 Payload Sender ID 不能混為一談
  • sendDown() 後 Message Ownership 已交給下層,接收 Callback 也要遵守 Veins 的刪除流程
  • Qtenv 用來理解事件,Log、Scalar 與 Vector 才是可比較的實驗證據
  • 封包收到不代表內容可信,來源、新鮮度與位置合理性仍要另外驗證

明日預告

今天完成了第一筆雙向 V2X Message Exchange,也知道成功接收不代表訊息內容可信。

Day 18 將把視野從這兩台模擬車拉回整個聯網車輛,盤點 CAN、OBD、Bluetooth、Wi-Fi、Cellular、V2X 與 OTA 等常見攻擊面,並分清楚入口、弱點與攻擊路徑。

參考資料


上一篇
Day 16|SUMO + OMNeT++:把交通與無線網路模擬接起來
下一篇
Day 18|汽車真的安全嗎?盤點車聯網常見攻擊面
系列文
30 天實戰車聯網資安21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言