structconn { /* Immutable data. */ structconn_key_nodekey_node[CT_DIRS];// 主键数组[2] structconn_keyparent_key;/* Only used for orig_tuple support. */ uint16_t nat_action; char *alg; atomic_flag reclaimed; /* False during the lifetime of the connection, * True as soon as a thread has started freeing * its memory. */ xxxx };
structtcp_peer { uint32_t seqlo; /* Max sequence number sent */ uint32_t seqhi; /* Max the other end ACKd + win */ uint16_t max_win; /* largest window (pre scaling) */ uint8_t wscale; /* window scaling factor */ enumct_dpif_tcp_statestate; };
flowchart TD
A[开始 tcp_new_conn] --> B[分配内存 newconn]
B --> C[设置源端和目的端指针]
C --> D[设置源端初始序列号 seqlo]
D --> E[计算源端序列号上限 seqhi]
E --> F{是否为 SYN 包?}
F -->|是| G[源端 seqhi++]
G --> H[获取源端窗口缩放因子]
F -->|否| I[设置双方窗口缩放状态为未知]
H --> J[设置源端最大窗口大小]
I --> J
J --> K{窗口缩放因子是否有效?}
K -->|是| L[根据缩放因子调整窗口大小]
K -->|否| M[继续处理]
L --> N{是否为 FIN 包?}
M --> N
N -->|是| O[源端 seqhi++]
N -->|否| P[继续处理]
O --> Q[初始化目的端参数]
P --> Q
Q --> R[设置连接状态]
R --> S[设置 tp_id]
S --> T[初始化超时时间]
T --> U[返回连接对象]
tcp_conn_update
注意: 能够进入 tcp_conn_update 流程的已经不是首个包了, conn 已经被创建了
tcp_conn_update 实在是其处理细节太多了…依次划分为了 4 个流程
基础检查和 SYN 包处理
序列号追踪和窗口计算
状态更新逻辑
特殊情况处理
基础检查和 SYN 包处理
flowchart TD
A[开始] --> B{检查TCP flags是否合法}
B -->|不合法| C[返回CT_UPDATE_INVALID]
B -->|合法| D{是否是SYN包}
D -->|是| E{检查连接状态}
E -->|双端>=FIN_WAIT_2| F[设置双端为CLOSED<br>返回CT_UPDATE_NEW]
E -->|src<=SYN_SENT| G[设置src为SYN_SENT<br>更新超时时间<br>返回CT_UPDATE_VALID_NEW]
D -->|否| H[继续处理]
flowchart TD
A[计算序列号和窗口] --> B{是否是首次处理该方向}
B -->|是| C[初始化序列号追踪]
C --> D[处理窗口缩放]
D --> E[计算seq_hi]
B -->|否| F[常规序列号计算]
F --> G[处理特殊情况的ACK]
G --> H[无数据包特殊处理]
H --> I[计算ackskew]
/* * Sequence tracking algorithm from Guido van Rooij's paper: * http://www.madison-gurkha.com/publications/tcp_filtering/ * tcp_filtering.ps */
orig_seq = seq = ntohl(get_16aligned_be32(&tcp->tcp_seq)); // seq bool check_ackskew = true; if (src->state < CT_DPIF_TCPS_SYN_SENT) // 比 SYN_SENT 状态还小, orign 方向第一个包, 初始化序列号追踪 { /* First packet from this end. Set its state */ ack = ntohl(get_16aligned_be32(&tcp->tcp_ack)); // ack
end = seq + p_len; // seq + n if (tcp_flags & TCP_SYN) // syn 包, 也就是首个包不是 syn 包 { end++; // seq + n + 1 if (dst->wscale & CT_WSCALE_FLAG) // 对端设置了 窗口缩放 { src->wscale = tcp_get_wscale(tcp); // tcp 可选项 提取窗口缩放 if (src->wscale & CT_WSCALE_FLAG) // orign 方向 设置窗口缩放? { /* Remove scale factor from initial window */ sws = src->wscale & CT_WSCALE_MASK; // 缩放因子 win = DIV_ROUND_UP((uint32_t)win, 1 << sws); // 窗口大小移位 dws = dst->wscale & CT_WSCALE_MASK; // 对端窗口缩放因子 } else { // 没有窗口缩放 /* fixup other window */ dst->max_win <<= dst->wscale & CT_WSCALE_MASK; /* in case of a retrans SYN|ACK */ dst->wscale = 0; // 对端窗口缩放因子清零 } } } if (tcp_flags & TCP_FIN) // fin 包 { end++; // end +1 }
src->seqlo = seq; // orign 方向最大 seq src->state = CT_DPIF_TCPS_SYN_SENT; // orign 方向状态 /* * May need to slide the window (seqhi may have been set by * the crappy stack check or if we picked up the connection * after establishment) */ // src->seqhi == 1 相当于是新连接 // SEQ_GEQ(end + MAX(1, dst->max_win << dws), src->seqhi) 当前包的结束序列号 + win > 源端最大的 seq, 需要更新 seqhi if (src->seqhi == 1 || SEQ_GEQ(end + MAX(1, dst->max_win << dws), src->seqhi)) { src->seqhi = end + MAX(1, dst->max_win << dws); // 更新 seqhi /* We are either picking up a new connection or a connection which * was already in place. We are more permissive in terms of * ackskew checking in these cases. */ // 这个注释说明了为什么这里不严格检查 ack // 新连接 或 已有连接但是需要更新 最大 seq 的; 这两种情况下 序列号尚不稳定. 因此 ackskew 检查不严格 check_ackskew = false; // 不严格检查 ack } if (win > src->max_win) { src->max_win = win; // orign 方向最大窗口 } } else { ack = ntohl(get_16aligned_be32(&tcp->tcp_ack)); // 取 ack end = seq + p_len; // 取结束序列号 if (tcp_flags & TCP_SYN) // syn 包 { end++; // seq + n + 1 } if (tcp_flags & TCP_FIN) // fin 包 { end++; // seq + n + 1 + 1 } }
if ((tcp_flags & TCP_ACK) == 0) // 没有 ack or ack = 0 { /* Let it pass through the ack skew check */ // 没有 ack , 作弊让其通过 ack 检查, 都没有检查个啥 ack = dst->seqlo; } // ack = 0. 但是设置了 ack 和 rst elseif ((ack == 0 && (tcp_flags & (TCP_ACK | TCP_RST)) == (TCP_ACK | TCP_RST)) /* broken tcp stacks do not set ack */) { /* Many stacks (ours included) will set the ACK number in an * FIN|ACK if the SYN times out -- no sequence to ACK. */ // 一些堆栈(包括我们的)在 SYN 超时时会在 FIN|ACK 中设置 ACK 号码 -- 没有序列号来 ACK // 用来处理一些现实的 tcp 协议栈实现 ack = dst->seqlo; }
if (seq == end) // pkt 内没有有效数据 { /* Ease sequencing restrictions on no data packets */ seq = src->seqlo; end = seq; }
flowchart TD
A[开始状态更新] --> B{序列号检查是否通过}
B -->|通过| C[更新窗口和序列号]
C --> D[状态转换处理]
D --> E{检查SYN标志}
E -->|有| F[更新SYN状态]
D --> G{检查FIN标志}
G -->|有| H[更新FIN状态]
D --> I{检查ACK标志}
I -->|有| J[更新ACK状态]
D --> K{检查RST标志}
K -->|有| L[设置TIME_WAIT]
D --> M[更新连接超时时间]
flowchart TD
A[特殊情况检查] --> B{是否满足特殊条件}
B -->|是| C[保守更新窗口]
C --> D[保守更新序列号]
D --> E[处理FIN标志]
E --> F[处理RST标志]
B -->|否| G[序列号检查失败]
G --> H[返回CT_UPDATE_INVALID]
C --> I[返回CT_UPDATE_VALID]
处理完毕正常就该处理异常情况了, 有 3 种特殊网络行为
非标准 TCP 实现:在接收 ACK 之前重复发送 SYN
网络设备重启:如防火墙或 NAT 设备重启后重新追踪已建立的连接
连接关闭后的异常行为:如某些系统 (Solaris) 在连接关闭后发送额外的 ACK 或 FIN 包
// 这会是一些特殊的情况了 // 对端还未发送 syn 或 对端已经处于 fin_wait_2 或更高 或 源端已经处于 fin_wait_2 或更高 且 pkt 的结束序号在窗口的 +方向还往后 elseif ((dst->state < CT_DPIF_TCPS_SYN_SENT || dst->state >= CT_DPIF_TCPS_FIN_WAIT_2 || src->state >= CT_DPIF_TCPS_FIN_WAIT_2) && SEQ_GEQ(src->seqhi + MAXACKWINDOW, end) /* Within a window forward of the originating packet */ // pkt 的结束序号在窗口 -方向还往前 && SEQ_GEQ(seq, src->seqlo - MAXACKWINDOW)) { /* Within a window backward of the originating packet */
/* * This currently handles three situations: * 1) Stupid stacks will shotgun SYNs before their peer * replies. * 2) When PF catches an already established stream (the * firewall rebooted, the state table was flushed, routes * changed...) * 3) Packets get funky immediately after the connection * closes (this should catch Solaris spurious ACK|FINs * that web servers like to spew after a close) * * This must be a little more careful than the above code * since packet floods will also be caught here. We don't * update the TTL here to mitigate the damage of a packet * flood and so the same code can handle awkward establishment * and a loosened connection close. * In the establishment case, a correct peer response will * validate the connection, go through the normal state code * and keep updating the state TTL. */ // 说了有 3 种特殊情况: // 1. 一些古怪的 tcp 协议栈实现, 会在对端回复 ack 之前,重复发送 syn // 2. 带 connect 实体(防火墙 nat表 ...)重启了, 重新对已建立的连接开始追踪 // 3. 连接关闭后的异常包, 例如 Solaris 会在关闭连接后继续发送 ack|fin 包 // 这部分代码必须非常谨慎... 因为 ddos 的包也会进入这里的处理流程 // 这里不更新 ttl 以减少 ddos 的影响, 异常连接超时后直接自行关闭了. // 保守更新状态信息, 正常连接 对端会正常回复,然后之后流程就进入到正常状态更新了. /* update max window */ if (src->max_win < win) { src->max_win = win; // 源端最大窗口 } /* synchronize sequencing */ if (SEQ_GT(end, src->seqlo)) { src->seqlo = end; // 源端最大 seq } /* slide the window of what the other end can send */ if (SEQ_GEQ(ack + (win << sws), dst->seqhi)) { dst->seqhi = ack + MAX((win << sws), 1); // 最大可接收的窗口范围更新 }
/* * Cannot set dst->seqhi here since this could be a shotgunned * SYN and not an already established connection. */ // 不能在这 设置 dst->seqhi, 因为这可能是一个重复的 syn 包, 而不是已经建立的连接 // 怪不得作者吐槽一些 tcp 协议栈实现...
if (tcp_flags & TCP_FIN && src->state < CT_DPIF_TCPS_CLOSING) // fin 包 { src->state = CT_DPIF_TCPS_CLOSING; // 源端关闭中 }