昨天我們已經成功解析 Ethernet Header,能夠讀取:
然而,真實網卡並不會接收所有經過網路的封包。
今天我們要讓自製的網路堆疊具備真正網卡的第一項核心能力:
Receive Filter
也就是:
完成以下內容:
在真實區域網路中,每秒可能有大量封包在網路上流動。
例如:
PC-A
PC-B
PC-C
PC-D
如果所有封包都交給作業系統處理:
Ethernet Frame
↓
Linux Kernel
↓
CPU 解析
即使封包根本不是發送給自己,也會浪費大量 CPU 資源。
因此網卡會先在 Layer 2 建立第一道防線:
Ethernet Frame
↓
Receive Filter
↓
決定要不要往上層送
真實網卡在收到 Ethernet Frame 時,並不會無條件把所有封包都交給上層處理,而是會先根據目的 MAC 做一次接收判斷。

這張流程圖對應到本日程式中的核心邏輯:Broadcast 會被接受,目的 MAC 等於本機 MAC 的 Unicast 也會被接受,其餘封包則會被丟棄。
廣播 MAC:
ff:ff:ff:ff:ff:ff
代表:
網段內所有設備都必須接收這個封包。
例如:
ARP Request
就是典型的 Broadcast。
單播代表封包只發送給某一台設備。
例如:
02:00:00:00:00:01
若目的 MAC 與本機一致:
Destination MAC
==
Local MAC
則接受。
否則直接丟棄。
今天新增一個設定檔:
Networking_Fundamentals
│
├── include
│ ├── config.h
│ └── ethernet.h
│
└── src
├── ethernet.c
└── tap.c
檔案:
include/config.h
今天先將我們的虛擬網卡 MAC 固定設定為:
02:00:00:00:00:01
#ifndef CONFIG_H
#define CONFIG_H
#include <stdint.h>
static const uint8_t LOCAL_MAC[6] = {
0x02,
0x00,
0x00,
0x00,
0x00,
0x01
};
#endif
MAC Address 第一個位元組的第二低位元(Locally Administered Bit)為 1 時:
02:xx:xx:xx:xx:xx
代表:
Locally Administered Address
也就是軟體自行指定的 MAC。
非常適合作為實驗用途。
檔案:
include/ethernet.h
新增接收過濾器相關函式:
int ethernet_is_broadcast(const uint8_t *mac);
int ethernet_is_for_me(const uint8_t *mac);
int ethernet_accept_frame(const struct ethernet_hdr *hdr);
| 函式 | 功能 |
|---|---|
| ethernet_is_broadcast() | 判斷是否為廣播 |
| ethernet_is_for_me() | 判斷是否為本機 |
| ethernet_accept_frame() | 接收過濾器入口 |
檔案:
src/ethernet.c
int ethernet_is_broadcast(const uint8_t *mac)
{
for (int i = 0; i < ETH_ADDR_LEN; i++) {
if (mac[i] != 0xff)
return 0;
}
return 1;
}
邏輯:
ff ff ff ff ff ff
全部都是:
0xff
才算廣播。
int ethernet_is_for_me(const uint8_t *mac)
{
return memcmp(
mac,
LOCAL_MAC,
ETH_ADDR_LEN
) == 0;
}
邏輯:
Destination MAC
==
LOCAL_MAC
則接受。
int ethernet_accept_frame(
const struct ethernet_hdr *hdr)
{
if (ethernet_is_broadcast(hdr->dst)) {
return 1;
}
if (ethernet_is_for_me(hdr->dst)) {
return 1;
}
return 0;
}
流程:
Broadcast ?
│
├─ YES → ACCEPT
│
▼
For Me ?
│
├─ YES → ACCEPT
│
▼
DROP
檔案:
src/tap.c
在解析 Ethernet Header 後:
struct ethernet_hdr *eth =
(struct ethernet_hdr *)buffer;
加入:
if (!ethernet_accept_frame(eth)) {
printf("[DROP] Not for me\n");
continue;
}
printf("[ACCEPT]\n");
ethernet_print_header(eth);
此時資料流變成:
Ethernet Frame
↓
Ethernet Parser
↓
Receive Filter
↓
ACCEPT / DROP
gcc src/tap.c src/ethernet.c \
-Iinclude \
-o ethernet_receiver
sudo ./ethernet_receiver
前面已經完成接收判斷邏輯,接下來用三種典型封包情境來驗證程式是否能正確接受或丟棄 Frame。

這三個測試會分別覆蓋 Broadcast、目的 MAC 是自己的 Unicast,以及目的 MAC 不是自己的 Unicast,剛好對應到網卡接收決策的三種結果。
執行:
ping -I tap0 10.0.0.99
Linux 會先送出:
ARP Request
其目的 MAC 為:
ff:ff:ff:ff:ff:ff
程式輸出:
[ACCEPT]
Ethernet Frame
-------------------------
Destination : ff:ff:ff:ff:ff:ff
Source : c6:bf:60:33:9d:73
EtherType : 0x0806
Frame length: 42 bytes
結果:
Broadcast
↓
ACCEPT
建立靜態 ARP:
sudo ip neigh add \
10.0.0.2 \
lladdr 02:00:00:00:00:01 \
dev tap0
測試:
ping -I tap0 10.0.0.2
程式輸出:
[ACCEPT]
Ethernet Frame
-------------------------
Destination : 02:00:00:00:00:01
Source : c6:bf:60:33:9d:73
EtherType : 0x0800
Frame length: 98 bytes
結果:
Destination MAC
==
LOCAL_MAC
成功接受。
建立另一個 MAC:
sudo ip neigh add \
10.0.0.3 \
lladdr 02:00:00:00:00:99 \
dev tap0
測試:
ping -I tap0 10.0.0.3
程式輸出:
[DROP] Not for me
結果:
Destination MAC
!=
LOCAL_MAC
因此直接丟棄。
因為許多重要協定都依賴 Broadcast。
例如:
ARP Request
DHCP Discover
若把 Broadcast 全部丟掉:
找不到 MAC
拿不到 IP
後續網路功能都無法運作。
因為接收端關心的是:
這個封包是不是送給我?
因此必須檢查:
Destination MAC
而不是 Source MAC。
今天我們讓自製網路堆疊具備了真正網卡的重要能力:
Receive Filter
資料流正式進化為:
Ethernet Frame
↓
Ethernet Parser
↓
Receive Filter
↓
ACCEPT / DROP
目前網路堆疊進度:
Ethernet Frame
↓
Ethernet Parser
↓
Receive Filter ← 今天完成
↓
ACCEPT / DROP
我們已經能夠像真實網卡一樣,決定哪些封包應該被處理,哪些封包應該被直接忽略。
明天將正式進入:
ARP Protocol
(Address Resolution Protocol)
我們會第一次主動構造並發送網路協定封包:
Who has 10.0.0.1 ?
Tell 10.0.0.2
也就是詢問:
誰的 IP 是 10.0.0.1?
請告訴 10.0.0.2。
Day 04 會完成:
write() 從 TAP Device 發送出去tcpdump / Wireshark 驗證封包內容屆時我們的處理流程將從:
Ethernet Frame
↓
Ethernet Parser
↓
Receive Filter
↓
ACCEPT / DROP
進一步發展為:
Ethernet Frame
↓
Ethernet Parser
↓
Receive Filter
↓
EtherType = 0x0806
↓
ARP Request
這代表我們不只會被動接收 Ethernet Frame,也會開始主動產生並送出第一個真正的網路協定封包。