昨天(Day 17)我們完成了 TCP Socket Table 與狀態機,能夠在收到 SYN 時動態建立 TCP_SYN_RECEIVED 的連線槽位。但當時伺服器就像是個「已讀不回」的冷漠客服 —— 雖然在內部做好了登記,卻一句話都沒回覆對方。
今天(Day 18),我們正式讓自製的網路協定棧開口說話,邁出 TCP 三向交握中最關鍵的第二步:
由伺服端組裝並發送標準的 SYN + ACK 封包!

conn->ack = client_seq + 1。tcp_send_syn_ack(),組出 SYN-ACK 封包。tcpdump 驗證 Flags [S.] 已送出。為什麼 TCP 不能只交握兩次?一定要三次?
想像兩個人在山頭用無線電對話,在講正事(傳資料)前必須確認通道暢通:
1. Client 呼叫 (SYN, SEQ=1000):
「喂喂喂,Server,你聽得到我說話嗎?我講話的序號從 1000 號開始算。」
Server 知道:Client 能發話、Server 能收聽。
2. Server 回覆 (SYN-ACK, SEQ=5000, ACK=1001):
「聽得很清楚!你下次從 1001 開始講。那我發話你聽得到嗎?我講話的序號從 5000 號開始算。」
Client 收到後知道:Server 能發話、Client 能收聽!而且剛才自己的話 Server 有收到!
3. Client 最後確認 (ACK, ACK=5001):
「我也聽得很清楚!你下次從 5001 開始講。」
Server 收到後知道:原來 Client 也能順利聽到我剛剛說的話!
經過這三步:
從 Server 回覆 SYN-ACK 時,所有「來源」與「目的」必須嚴格反轉:
| 欄位 | Client 送進來的 SYN | Server 回覆的 SYN-ACK | 說明 |
|---|---|---|---|
| Ethernet Dst MAC | Server MAC (02:00:00:00:00:01) |
Client MAC (52:54:00:12:34:56) |
從 ARP 表中查得 |
| Ethernet Src MAC | Client MAC (52:54:00:12:34:56) |
Server MAC (LOCAL_MAC) |
本機網卡 MAC |
| IPv4 Dst IP | 10.0.0.2 |
10.0.0.1 |
送回給 Client |
| IPv4 Src IP | 10.0.0.1 |
10.0.0.2 |
本機 IP |
| TCP Dst Port | 8080 |
52144 |
送回 Client 的臨時埠 |
| TCP Src Port | 52144 |
8080 |
本機監聽埠 |
| TCP Flags | 0x02 (SYN) |
0x12 (`SYN |
ACK`) |
| TCP SEQ | 1000 |
5000 |
Server 自己的起始序號 |
| TCP ACK | 0 |
1001 |
$\text{Client SEQ} + 1$ |
Checksum 補充:IPv4 Header Checksum 與 TCP Checksum 是兩件事。本文程式碼已重算 IPv4 Header Checksum,但 TCP Header 內的
checksum仍暫填0。這是 Day18~Day21 測試環境的教學簡化;真實 TCP 封包必須計算包含 Pseudo Header、TCP Header 與 Payload 的 TCP Checksum,否則一般 OS TCP Stack 可能會丟棄封包。
include/tcp.h)在 struct tcp_socket 中新增 seq 與 ack,用以追蹤雙方的序列號進度:
struct tcp_socket
{
uint32_t src_ip;
uint32_t dst_ip;
uint16_t src_port;
uint16_t dst_port;
uint32_t seq; // Server 自己的序列號
uint32_t ack; // 期待收到的對方序列號
enum tcp_state state;
};
int tcp_send_syn_ack(int fd, struct tcp_socket *conn);
src/tcp.c)int tcp_send_syn_ack(int fd, struct tcp_socket *conn)
{
// 1. 準備 Buffer 與切割各層指標 (14 + 20 + 20 = 54 Bytes)
uint8_t buffer[ETH_HEADER_LEN + sizeof(struct ipv4_hdr) + sizeof(struct tcp_hdr)];
memset(buffer, 0, sizeof(buffer));
struct ethernet_hdr *eth = (struct ethernet_hdr *)buffer;
struct ipv4_hdr *ip = (struct ipv4_hdr *)(buffer + ETH_HEADER_LEN);
struct tcp_hdr *tcp = (struct tcp_hdr *)(buffer + ETH_HEADER_LEN + sizeof(struct ipv4_hdr));
// 2. 封裝 Layer 4: TCP Header
tcp->src_port = htons(conn->dst_port); // 8080
tcp->dst_port = htons(conn->src_port); // 52144
tcp->seq = htonl(conn->seq); // 5000
tcp->ack = htonl(conn->ack); // 1001
tcp->data_offset = (5 << 4); // 20 Bytes (5 words)
tcp->window = htons(4096); // 初始視窗大小
tcp->flags = (TCP_ACK | TCP_SYN); // 0x12: SYN + ACK
tcp->checksum = 0; // 暫設為 0
tcp->urgent_ptr = 0;
// 3. 封裝 Layer 3: IPv4 Header
ip->version_ihl = (4 << 4) | 5; // Version 4, IHL 5
ip->tos = 0;
ip->total_length = htons(sizeof(struct ipv4_hdr) + sizeof(struct tcp_hdr)); // 40 Bytes
ip->identification = htons(2001);
ip->flags_fragment = 0;
ip->ttl = 64;
ip->protocol = IPPROTO_TCP; // Protocol 6
ip->src_ip = conn->dst_ip; // 來源:10.0.0.2
ip->dst_ip = conn->src_ip; // 目的:10.0.0.1
ip->checksum = 0;
ip->checksum = ipv4_checksum(ip, sizeof(struct ipv4_hdr));
// 4. 封裝 Layer 2: Ethernet Header
struct arp_entry *entry = arp_table_lookup((const uint8_t *)&conn->src_ip);
if (!entry || !entry->valid) {
printf("[TCP] Send SYN-ACK failed: MAC not in ARP table\n");
return -1;
}
memcpy(eth->dst, entry->mac, ETH_ADDR_LEN); // Client MAC
memcpy(eth->src, LOCAL_MAC, ETH_ADDR_LEN); // 本機 MAC
eth->ethertype = htons(ETHERTYPE_IPV4);
// 5. 寫出到 TAP 虛擬網卡
ssize_t sent = write(fd, buffer, sizeof(buffer));
if (sent < 0) {
perror("[TCP] write SYN-ACK failed");
return -1;
}
printf("[TCP] Sent SYN-ACK: SEQ=%u, ACK=%u\n", conn->seq, conn->ack);
return 0;
}
tcp_receive)void tcp_receive(int fd, const uint8_t *buffer, size_t len)
{
struct ipv4_hdr *ip = (struct ipv4_hdr *)(buffer + ETH_HEADER_LEN);
size_t ip_hdr_len = (ip->version_ihl & 0x0F) * 4;
if (len < ETH_HEADER_LEN + ip_hdr_len + sizeof(struct tcp_hdr)) {
printf("[TCP] Packet too short\n");
return;
}
// ⭐ 動態 ARP 學習:收到封包時立刻記下 Client 的 IP 與 MAC
struct ethernet_hdr *rx_eth = (struct ethernet_hdr *)buffer;
arp_table_insert((const uint8_t *)&ip->src_ip, rx_eth->src);
struct tcp_hdr *tcp = (struct tcp_hdr *)(buffer + ETH_HEADER_LEN + ip_hdr_len);
tcp_print_header(tcp);
if (tcp->flags & TCP_SYN) {
uint16_t dst_port = ntohs(tcp->dst_port);
uint16_t src_port = ntohs(tcp->src_port);
struct tcp_socket *listener = tcp_find_listener(dst_port);
if (listener == NULL) {
printf("[TCP] Port %u not listening -> DROP\n", dst_port);
return;
}
printf("[TCP] Incoming SYN on Port %u\n", dst_port);
struct tcp_socket *conn = tcp_create_connection(ip->src_ip, ip->dst_ip, src_port, dst_port);
if (conn == NULL) {
printf("[TCP] Connection table full!\n");
return;
}
// 計算雙方序號
uint32_t client_seq = ntohl(tcp->seq);
conn->ack = client_seq + 1; // 消耗 1 個序號
conn->seq = 5000; // 本機初始序號
tcp_dump_table();
// 觸發回覆 SYN-ACK!
tcp_send_syn_ack(fd, conn);
return;
}
}
./network)[ACCEPT]
Ethernet Frame
-------------------------
Destination : 02:00:00:00:00:01
Source : 52:54:00:12:34:56
EtherType : 0x0800
IPv4 Packet
------------------
Version : 4
Header Length: 20 bytes
TTL : 64
Protocol : 6
Checksum : 0x62e5 (OK)
Source IP : 10.0.0.1
Destination IP : 10.0.0.2
[IPv4] Local Delivery (for me)
TCP Packet
----------------------------------------
Source Port : 52144
Destination Port : 8080
SEQ : 1000
ACK : 0
Data Offset : 5 (Header Length: 20 bytes)
Flags : 0x02 [ SYN ]
SYN = 1, ACK = 0, FIN = 0, RST = 0
Window : 4096
Checksum : 0x0000
Urgent Pointer : 0
----------------------------------------
[TCP] Incoming SYN on Port 8080
=== TCP SOCKET TABLE ===
[00] State: LISTEN | Local Port: 8080 (*:* -> :8080)
[01] State: SYN_RECEIVED | 10.0.0.1:52144 -> 10.0.0.2:8080
========================
[TCP] Sent SYN-ACK: SEQ=5000, ACK=1001
Frame length: 54 bytes
tcpdump 真實網路封包抓包user@MSI:/mnt/c/Users/zenboen/Tutorial/Networking_Fundamentals$ sudo tcpdump -i tap0 -nn tcp
listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
16:50:48.558938 IP 10.0.0.1.52144 > 10.0.0.2.8080: Flags [S], seq 1000, win 4096, length 0
16:50:48.559301 IP 10.0.0.2.8080 > 10.0.0.1.52144: Flags [S.], seq 5000, ack 1001, win 4096, length 0
Flags [S] 代表 SYN 旗標。seq 1000,長度為 0。Flags [S.] 代表 SYN + ACK 雙旗標!seq 5000 精準對應我們設定的 ISN。ack 1001 精準確認了 Client 的第 1000 號連線契約! Ethernet (L2)
│
┌─────────────┴─────────────┐
│ │
ARP (0x0806) IPv4 (0x0800)
│ │
ARP Cache Table ┌────────────┼────────────┐
▲ │ │ │
│ ICMP (1) UDP (17) TCP (6)
(動態學習對方的 MAC) │ │ │
Echo Reply DNS / App Header Parser
↓
Socket Table Lookup
↓
TCP State Machine
┌───────────┴───────────┐
│ │
LISTEN SYN_RECEIVED
(服務接待處) │
▼
tcp_send_syn_ack() <-- 【Day 18】
(回覆 Flags: [S.])
今天我們完成了「SYN $\rightarrow$ SYN-ACK」這前兩步。
明天,我們將收下三向交握的最後一塊拼圖:
Client Server
SYN ----------------------> (State: SYN_RECEIVED)
<---------------- SYN-ACK
ACK ----------------------> (State: ESTABLISHED 🎉)
SYN_RECEIVED 狀態的連線 Socket。seq + 1(5001)。TCP_ESTABLISHED!