來源:http://yfydz.cublog.cn1. 前言在2.6.1*以上的Linux核心中,關於TCP串連跟蹤處理有了比較大的修改,增加了TCP可能標誌位組合的檢查;增加了通過序號、確認號和視窗值來判斷資料包合法性的功能,支援SACK選項;狀態轉換數組也進行了一些修改和完善,相應程式碼量增加不少。以下2.6核心代碼版本為2.6.17.11。2. 通過確認號、序號和視窗判斷資料包合法性該思路提出比較早,最初是在“Real Statefule TCP Packet Filtering in IP FIlter”中提出的( http://www.nluug.nl/events/sane2000/papers.html
),用在FreeBSD,OpenBSD,NetBSD等作業系統的防火牆IP Filter中。原理:TCP串連開始時進行3次握手,交換MSS等資訊,同時在window欄位中告訴對方本方的資料接收緩衝區大小,另一方發送資料時一次不能發送
超過該大小的資料,也就是一方的序號變化值不能超過對方提供的window大小,確認號的變化值是不能超過己方提供的window大小,這是正常TCP
實現都會遵守的,如果不遵守這條件,說明該資料包非法。使用該功能要注意兩個TCP選項,第一,TCP的SACK(選擇性確認)選項,RFC1323,2018,2883,在資料包丟失的情況下,使發送方只重新發送丟失的包而不是全部發送;第二,擴充window選項,該選項可將window值從16位最大擴充到30位。為描述此功能新增加了一個資料結構:/* include/linux/netfilter/nf_conntrack_tcp.h */struct ip_ct_tcp_state {
u_int32_t td_end; /* max of seq + len */
u_int32_t td_maxend; /* max of ack + max(win, 1) */
u_int32_t td_maxwin; /* max(win) */
u_int8_t td_scale; /* window scale factor */
u_int8_t loose; /* used when connection picked up from the middle */
u_int8_t flags; /* per direction options */
};判斷一個TCP包序號和確認號是否在給定window範圍內的函數是tcp_in_window:static int tcp_in_window(struct ip_ct_tcp *state,
enum ip_conntrack_dir dir,
unsigned int index,
const struct sk_buff *skb,
struct iphdr *iph,
struct tcphdr *tcph)
{
struct ip_ct_tcp_state *sender = &state->seen[dir];
struct ip_ct_tcp_state *receiver = &state->seen[!dir];
__u32 seq, ack, sack, end, win, swin;
int res;
// 用戶端發的第一個SYN包是到不了這個函數的,直接就接受了,
// 是從串連的第2個包以後才進入本函數處理
/*
* Get the required data from the packet.
*/
// 序號
seq = ntohl(tcph->seq);
// 確認號
ack = sack = ntohl(tcph->ack_seq);
// 本方視窗
win = ntohs(tcph->window);
// 本資料包結束序號
end = segment_seq_plus_len(seq, skb->len, iph, tcph);
// 接收方支援SACK的話檢查是否在TCP選項中有SACK
if (receiver->flags & IP_CT_TCP_FLAG_SACK_PERM)
tcp_sack(skb, iph, tcph, &sack);// 省略符號部分是一些調試列印資訊,忽略下同
...
if (sender->td_end == 0) {
// 串連初始情況
/*
* Initialize sender data.
*/
if (tcph->syn && tcph->ack) {
// 伺服器端
/*
* Outgoing SYN-ACK in reply to a SYN.
*/
sender->td_end =
sender->td_maxend = end;
sender->td_maxwin = (win == 0 ? 1 : win);
// 檢查TCP選項,判斷接收方是否支援SACK和視窗擴大
tcp_options(skb, iph, tcph, sender);
/*
* RFC 1323:
* Both sides must send the Window Scale option
* to enable window scaling in either direction.
*/
if (!(sender->flags & IP_CT_TCP_FLAG_WINDOW_SCALE
&& receiver->flags & IP_CT_TCP_FLAG_WINDOW_SCALE))
// 不支援視窗擴大
sender->td_scale =
receiver->td_scale = 0;
} else {
/*
* We are in the middle of a connection,
* its history is lost for us.
* Let's try to use the data from the packet.
*/
sender->td_end = end;
sender->td_maxwin = (win == 0 ? 1 : win);
sender->td_maxend = end + sender->td_maxwin;
}
} else if (((state->state == TCP_CONNTRACK_SYN_SENT
&& dir == IP_CT_DIR_ORIGINAL)
|| (state->state == TCP_CONNTRACK_SYN_RECV
&& dir == IP_CT_DIR_REPLY))
&& after(end, sender->td_end)) {
// 發送方重新發包
/*
* RFC 793: "if a TCP is reinitialized ... then it need
* not wait at all; it must only be sure to use sequence
* numbers larger than those recently used."
*/
sender->td_end =
sender->td_maxend = end;
sender->td_maxwin = (win == 0 ? 1 : win); tcp_options(skb, iph, tcph, sender);
}
// 非ACK包和RST包,將確認號置為接收方的結束序號
if (!(tcph->ack)) {
/*
* If there is no ACK, just pretend it was set and OK.
*/
ack = sack = receiver->td_end;
} else if (((tcp_flag_word(tcph) & (TCP_FLAG_ACK|TCP_FLAG_RST)) ==
(TCP_FLAG_ACK|TCP_FLAG_RST))
&& (ack == 0)) {
/*
* Broken TCP stacks, that set ACK in RST packets as well
* with zero ack value.
*/
ack = sack = receiver->td_end;
}// 無資料包或起始包
if (seq == end
&& (!tcph->rst
|| (seq == 0 && state->state == TCP_CONNTRACK_SYN_SENT)))
/*
* Packets contains no data: we assume it is valid
* and check the ack value only.
* However RST segments are always validated by their
* SEQ number, except when seq == 0 (reset sent answering
* SYN.
*/
seq = end = sender->td_end;