Skip to content

Commit 0ed0db3

Browse files
author
fengbojiang
committed
docs: add analysis of is_tcp_syn() IPv6 extension header handling
Analyze accuracy of IPv6 header skip in is_tcp_syn(): correct for the common case (no extension headers, >99% of SYN traffic), false negative (safe) for SYN packets with extension headers. f-stack already has get_ipv6_hdr_len() in ff_dpdk_kni.c that could be reused for full accuracy.
1 parent 5f3685c commit 0ed0db3

2 files changed

Lines changed: 205 additions & 1 deletion

File tree

‎docs/issue_1076/zh_cn/07-mbuf背压实现报告.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
安全检查:
4646
- 每层解析前验证剩余长度,防止越界读
4747
- IPv4 IHL 最小值检查(`>= sizeof(struct rte_ipv4_hdr)`)
48-
- IPv6 不处理扩展头(简化设计,SYN 包通常无扩展头)
48+
- IPv6 不处理扩展头(简化设计;带扩展头的 SYN 会假阴性=不触发背压,安全失败模式;详见文档 `08-is_tcp_syn-ipv6-ext-hdr-analysis.md`)
4949

5050
### 2.3 process_packets() 水位检查
5151

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# 08 - is_tcp_syn() IPv6 扩展头分析
2+
3+
> f-stack issue #1076: `is_tcp_syn()` 函数中 IPv6 头跳过判断的准确性分析
4+
> 分析时间:2026-08-11
5+
> 前置文档:00-07 号文档
6+
> 关联提交:`7112dc2bcf`(feat: add mbuf water-level backpressure for issue #1076)
7+
8+
---
9+
10+
## 1. 问题描述
11+
12+
commit `7112dc2bcf` 新增的 `is_tcp_syn()` 函数(`lib/ff_dpdk_if.c:2023-2070`)中,IPv6 分支的处理为:
13+
14+
```c
15+
} else if (ether_type == RTE_ETHER_TYPE_IPV6) {
16+
if (len < sizeof(struct rte_ipv6_hdr) + sizeof(struct rte_tcp_hdr))
17+
return 0;
18+
const struct rte_ipv6_hdr *iph = (const struct rte_ipv6_hdr *)data;
19+
if (iph->proto != IPPROTO_TCP)
20+
return 0;
21+
tcph = (const struct rte_tcp_hdr *)
22+
((const char *)data + sizeof(struct rte_ipv6_hdr));
23+
}
24+
```
25+
26+
文档 `07` 注释"IPv6 不处理扩展头(简化设计,SYN 包通常无扩展头)"。本分析验证此判断是否准确。
27+
28+
---
29+
30+
## 2. IPv6 扩展头机制
31+
32+
### 2.1 IPv6 报头结构
33+
34+
IPv6 固定报头 40 字节,其中 `next_header` 字段(对应 DPDK `rte_ipv6_hdr.proto`)指定紧跟的协议类型。若无扩展头,`next_header == IPPROTO_TCP(6)`,TCP 报头紧跟在 40 字节固定头之后。
35+
36+
若有扩展头,`next_header` 指向第一个扩展头类型,扩展头内部再通过各自的 `next_header` 字段链式指向下一个,直到最终上层协议(TCP)。
37+
38+
### 2.2 扩展头类型(RFC 8200)
39+
40+
| 协议号 | 类型 | 出现在 SYN 的可能性 |
41+
|--------|------|---------------------|
42+
| 0 | Hop-by-Hop Options | 低(TFO Cookie 在 TCP 选项中,不在 IPv6 Hop-by-Hop;Jumbograms 极罕见) |
43+
| 43 | Routing Header | 低(Mobile IPv6 / SRV6,普通客户端不用) |
44+
| 44 | Fragment Header | 极低(SYN 包通常 60-80 字节,远小于 MTU 1280) |
45+
| 51 | Authentication Header (AH) | 低(IPsec VPN 场景,SYN 可携带 AH) |
46+
| 50 | Encapsulating Security Payload (ESP) | 低(ESP 加密后无法解析 TCP 头) |
47+
| 60 | Destination Options | 极低(很少用于 SYN) |
48+
| 135 | Mobility Header | 极低(Mobile IPv6 场景) |
49+
50+
### 2.3 外网资料交叉验证
51+
52+
- RFC 8200 §4.1:Hop-by-Hop Options 若存在必须是第一个扩展头
53+
- Linux 内核 `net/ipv6/ip6_input.c` 的 `ip6_input_finish()` 调用 `ipv6_skip_exthdr()` 遍历扩展头链
54+
- DPDK `rte_net_get_ptype()` 内部也有 `ipv6_skip_exthdr` 等价逻辑
55+
56+
结论:IPv6 扩展头是标准协议机制,Linux 内核和 DPDK 均在正常收包路径中处理。但带扩展头的 TCP SYN 包在生产流量中**罕见**。
57+
58+
---
59+
60+
## 3. f-stack 已有扩展头遍历函数
61+
62+
`lib/ff_dpdk_kni.c:281-315` 已有 `get_ipv6_hdr_len()` 函数,正确处理扩展头链:
63+
64+
```c
65+
static int
66+
get_ipv6_hdr_len(uint8_t *proto, void *data, uint16_t len)
67+
{
68+
int ext_hdr_len = 0;
69+
70+
switch (*proto) {
71+
case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_DSTOPTS:
72+
case IPPROTO_MH: case IPPROTO_HIP: case IPPROTO_SHIM6:
73+
ext_hdr_len = *((uint8_t *)data + 1) + 1; // 8 字节为单位
74+
break;
75+
case IPPROTO_FRAGMENT:
76+
ext_hdr_len = 8; // 固定 8 字节
77+
break;
78+
case IPPROTO_AH:
79+
ext_hdr_len = (*((uint8_t *)data + 1) + 2) * 4; // 4 字节为单位
80+
break;
81+
case IPPROTO_NONE:
82+
default:
83+
return ext_hdr_len; // 非扩展头,终止
84+
}
85+
86+
if (ext_hdr_len >= len) return len;
87+
88+
*proto = *((uint8_t *)data); // 读 next_header
89+
ext_hdr_len += get_ipv6_hdr_len(proto, data + ext_hdr_len, len - ext_hdr_len);
90+
91+
return ext_hdr_len;
92+
}
93+
```
94+
95+
该函数被 `protocol_filter_ip()`(`:356-358`)正确使用:
96+
97+
```c
98+
hdr_len = sizeof(struct rte_ipv6_hdr);
99+
proto = ((struct rte_ipv6_hdr *)data)->proto;
100+
hdr_len += get_ipv6_hdr_len(&proto, (void *)data + hdr_len, len - hdr_len);
101+
```
102+
103+
`is_tcp_syn()` **未使用** `get_ipv6_hdr_len()`,而是直接检查 `iph->proto != IPPROTO_TCP`。
104+
105+
---
106+
107+
## 4. 当前代码的行为分析
108+
109+
### 4.1 无扩展头(常见情况,>99%)
110+
111+
- `iph->proto == IPPROTO_TCP` → 检查通过
112+
- `tcph = data + 40` → 偏移正确
113+
- TCP flags 检查正确
114+
115+
**结论:正确**。
116+
117+
### 4.2 有扩展头(罕见情况,<1%)
118+
119+
- `iph->proto` 是扩展头类型(如 0/43/44/51/60),不等于 `IPPROTO_TCP(6)`
120+
- 函数返回 0(不是 SYN)
121+
- SYN 包**未被丢弃**,继续进入协议栈
122+
123+
**结论:假阴性(false negative)**。SYN 包未被检测到,背压机制未触发。
124+
125+
### 4.3 失败模式安全分析
126+
127+
| 失败类型 | 后果 | 是否安全 |
128+
|---------|------|---------|
129+
| 假阴性(SYN 未被检测) | SYN 进入协议栈,不触发背压 | **安全** — 与不启用背压时行为相同 |
130+
| 假阳性(非 SYN 被误判为 SYN) | 非 SYN 包被丢弃 | **不安全** — 影响已有连接 |
131+
132+
当前实现只会产生假阴性,不会产生假阳性。对于背压机制,假阴性是**可接受的失败模式**。
133+
134+
---
135+
136+
## 5. 潜在改进
137+
138+
### 5.1 复用 get_ipv6_hdr_len()
139+
140+
将 `get_ipv6_hdr_len()` 从 `ff_dpdk_kni.c` 移到共享头文件(如 `ff_dpdk_if.h` 或新建 `ff_proto_parse.h`),`is_tcp_syn()` 调用它遍历扩展头链。
141+
142+
改进后的 IPv6 分支:
143+
144+
```c
145+
} else if (ether_type == RTE_ETHER_TYPE_IPV6) {
146+
if (len < sizeof(struct rte_ipv6_hdr))
147+
return 0;
148+
const struct rte_ipv6_hdr *iph = (const struct rte_ipv6_hdr *)data;
149+
uint8_t proto = iph->proto;
150+
uint16_t hdr_len = sizeof(struct rte_ipv6_hdr);
151+
hdr_len += get_ipv6_hdr_len(&proto, (char *)data + hdr_len, len - hdr_len);
152+
if (proto != IPPROTO_TCP)
153+
return 0;
154+
if (len < hdr_len + sizeof(struct rte_tcp_hdr))
155+
return 0;
156+
tcph = (const struct rte_tcp_hdr *)((const char *)data + hdr_len);
157+
}
158+
```
159+
160+
### 5.2 改进代价
161+
162+
- 需要将 `get_ipv6_hdr_len()` 从 static 改为非 static(或移到共享头)
163+
- 增加少量代码行(约 5 行)
164+
- `get_ipv6_hdr_len()` 是递归调用,但扩展头链通常 0-2 层,开销可忽略
165+
166+
### 5.3 是否必要
167+
168+
考虑到:
169+
1. 带扩展头的 IPv6 TCP SYN 包在生产流量中罕见
170+
2. 假阴性是安全失败模式
171+
3. 当前实现对常见情况正确
172+
4. 背压机制是 best-effort 保护,非严格 enforcement
173+
174+
**改进非必要,但推荐**。若未来需要更精确的 SYN 检测(例如用于 ACL 或限速场景),则应改进。
175+
176+
---
177+
178+
## 6. 其他边界问题
179+
180+
### 6.1 QinQ(双层 VLAN)
181+
182+
当前只处理单层 VLAN。QinQ 包的第二个 VLAN tag 会被当作 ethertype,不匹配 IPv4/IPv6,返回 0。假阴性,安全。
183+
184+
### 6.2 IPv4 IPv6 不等长检查
185+
186+
IPv4 分支检查 `len < ihl + sizeof(struct rte_tcp_hdr)`,其中 `ihl` 是可变的。IPv6 分支检查 `len < sizeof(struct rte_ipv6_hdr) + sizeof(struct rte_tcp_hdr)`,这是固定 40+20=60。两者均正确。
187+
188+
### 6.3 TCP 最小头部长度
189+
190+
`sizeof(struct rte_tcp_hdr)` 在 DPDK 中是 20 字节(最小 TCP 头)。SYN 包通常 TCP 头 20-60 字节(含选项)。检查 `>= 20` 是正确的最小检查。
191+
192+
---
193+
194+
## 7. 结论
195+
196+
| 问题 | 结论 |
197+
|------|------|
198+
| IPv6 无扩展头时判断准确吗? | **准确** |
199+
| IPv6 有扩展头时判断准确吗? | **不准确**(假阴性,SYN 未被检测) |
200+
| 假阴性安全吗? | **安全** — 与不启用背压时行为相同 |
201+
| 需要立即修复吗? | **否** — 背压是 best-effort 机制,假阴性可接受 |
202+
| 建议改进吗? | **推荐改进** — 复用已有的 `get_ipv6_hdr_len()` |
203+
204+
文档 `07` 注释"IPv6 不处理扩展头(简化设计,SYN 包通常无扩展头)"的判断**方向正确**(SYN 包通常无扩展头),但"通常"不等于"总是"。对于 best-effort 背压机制,当前实现可接受;若用于更严格的场景(ACL/限速),应复用 `get_ipv6_hdr_len()` 改进。

0 commit comments

Comments
 (0)