1. 程式人生 > >網絡協議棧(13)syn flood攻擊防範及部分重傳定時器參數分析

網絡協議棧(13)syn flood攻擊防範及部分重傳定時器參數分析

到來 rto 大於 ive 初始化 sk_buff signed ren ont

一、syn cookie攻擊防禦方法
在前一篇文章中說明了syn flood的原理,可以看到,該機制會造成服務器的DOS癱瘓而無法提供正常服務,所以在當前的Linux中提供了一種相對比較智能的方法方法,就是使用syn_cookie機制。它的實現原理就是把連接的狀態信息體現在自己提議的初始化序列號上,這個狀態信息一定需要包含當前的時間信息,這樣一方面可以保持序列號在合理長的時間內不會出現回繞,另一方面,這個時間也可以判斷連接發起方的最後一個報文是否合法。這個和原始實現的最大區別在於這種機制不會在連接被動方使用數據結構來記錄這個三次握手的前兩次交互,而是把第三次交互放在報文的序列號中,這樣當三次握手最後一個報文到來時直接進入連接建立狀態

二、當連接backlog用完時
當backlog用完時,此時又有新的連接請求到來,同樣是進入tcp_v4_conn_request函數,在該函數中進行了額外處理:
/* TW buckets are converted to open requests without
* limitations, they conserve resources and peer is
* evidently real one.
*/
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
if (sysctl_tcp_syncookies) {
want_cookie = 1;
} else
#endif
goto drop;
}
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)如果用戶來不及accept,即使配置了syn cookie,同樣丟失請求
goto drop;
……
if (want_cookie) {
#ifdef CONFIG_SYN_COOKIES
syn_flood_warning(skb);
#endif
isn = cookie_v4_init_sequence(sk, skb, &req->mss); 該步驟需要完成自己初始序列號的精心選擇
}
……

if (want_cookie) {
reqsk_free(req); 此時連接請求結構被釋放,而握手的前兩個報文信息(完成情況)被保存到精心選擇的初始序列號中,然後在收到最後一個確認報文之後還原並驗證三次握手是否完成
} else {
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
}
return 0;
三、syn+ack報文序列號選擇函數
#define COOKIEBITS 24 /* Upper bits store count */
#define COOKIEMASK (((__u32)1 << COOKIEBITS) - 1)
__u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)
{
struct tcp_sock *tp = tcp_sk(sk);
int mssind;
const __u16 mss = *mssp;


tp->last_synq_overflow = jiffies;

/* XXX sort msstab[] by probability? Binary search? */
for (mssind = 0; mss > msstab[mssind + 1]; mssind++)
;
*mssp = msstab[mssind] + 1;

NET_INC_STATS_BH(LINUX_MIB_SYNCOOKIESSENT);

return secure_tcp_syn_cookie(skb->nh.iph->saddr, skb->nh.iph->daddr,
skb->h.th->source, skb->h.th->dest,
ntohl(skb->h.th->seq),
jiffies / (HZ * 60), mssind);
}


static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
__be16 dport, __u32 sseq, __u32 count,
__u32 data)
{
/*
* Compute the secure sequence number.
* The output should be:
* HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
* + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
* Where sseq is their sequence number and count increases every
* minute by 1.
* As an extra hack, we add a small "data" value that encodes the
* MSS into the second hash value.
*/

return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + 常量數值使用不可逆hash算法,這些值在下個ack報文中保持不變
sseq + (count << COOKIEBITS) + 其中sseq 包含對方提議序列號,count表示一個由時間引入的合理鹽值,存入最高8bit
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)一些隨機值放入低24bits
& COOKIEMASK));
}
四、三次握手最後一個ack到來
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
……
#ifdef CONFIG_SYN_COOKIES
if (!th->rst && !th->syn && th->ack) 這個是三次握手最後一個報文特征ack置位,syn沒有置位,並且重要的是父套接口為listen狀態
sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt)); 此時需要用這個響應報文還原syn+ack報文信息並完成驗證
#endif
真正的檢測函數為
static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
__be16 sport, __be16 dport, __u32 sseq,
__u32 count, __u32 maxdiff)
{
__u32 diff;

/* Strip away the layers from the cookie */
cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;根據secure_tcp_syn_cookie的計算方法,該步驟之後cookie的值等於(count << COOKIEBITS) +((cookie_hash(saddr, daddr, sport, dport, count, 1) + data) & COOKIEMASK),也就是高8bits為count值,而低24位則為一個單向hash值加上一個data。而其中的count就是系統時間相關的一個變量,這樣如果這個ack報文不是在合理時間內的一個響應,同樣認為非法連接

/* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */
diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);這裏count為接收到三次握手最後一個ack是系統時間,以分鐘為單位。由於maxdiff值默認為4,所以4分鐘之內的ack認為有效
if (diff >= maxdiff)
return (__u32)-1;

return (cookie -
cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
& COOKIEMASK; /* Leaving the data behind */還原出連接請求報文提議的mss並返回給用戶
}
如果這些檢測通過,tcp_v4_hnd_req--->>>cookie_v4_check同樣會返回連接成功的socket三次握手套接字。
五、syn、synack及建鏈之後重傳
這兩個參數是三次握手中比較重要的兩個參數,其中第一個參數是連接發起方對syn發送的重試次數、後一個是連接被動方恢復synack的時間相關,當然還有一個建鏈完成之後的重試時間。一下將以系統默認TCP_TIMEOUT_INIT計算RTO,該值為
#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value */
也就是3秒鐘。最大時間為120秒
#define TCP_RTO_MAX ((unsigned)(120*HZ))
1、syn次數及時間計算
sysctl_tcp_syn_retries該值控制連接發起套接口的重試次數,其默認值為TCP_SYN_RETRIES=5。這個值在
/* A write timeout has occurred. Process the after effects. */
static int tcp_write_timeout(struct sock *sk)
{
……
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
if (icsk->icsk_retransmits)
dst_negative_advice(&sk->sk_dst_cache);
retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;
}
我們看一下,如果對方一直不給這個syn回應,這裏的syn重試時間。第一次超時之後
static void tcp_retransmit_timer(struct sock *sk)
……
if (tcp_write_timeout(sk))如果這裏判斷出時間超時,則不會重傳
goto out;
……
icsk->icsk_backoff++;
icsk->icsk_retransmits++;

out_reset_timer:
icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);不斷倍增超時時間,但是不能大於120秒
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
現在我們想一下邊界值5,到底是進行4次還是5次重傳。當第一次重傳之後icsk_retransmits的值為1,當第四次重傳之後該值為4,當第五次到來時,進入tcp_write_timeout函數後
if (icsk->icsk_retransmits >= retry_until) 還是不滿足的,所以第五次重傳依然會進行。所以總共會嘗試六次,包括第一次正常發送。
這個時間為
3+3*2+3*4+……+3*2^5=3*(2^6-1)=189
這也就是
#define TCP_SYN_RETRIES 5 /* number of times to retry active opening a
* connection: ~180sec is RFC minimum */
註釋中說的~180sec的由來。
2、synack重傳次數及時間
這個同樣是使用一個系統變量控制次數,該值在keepalive定時器中對請求隊列的修剪中觸發:
void inet_csk_reqsk_queue_prune(struct sock *parent,
const unsigned long interval,
const unsigned long timeout,
const unsigned long max_rto)
{
struct inet_connection_sock *icsk = inet_csk(parent);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
struct listen_sock *lopt = queue->listen_opt;
int max_retries = icsk->icsk_syn_retries ? : sysctl_tcp_synack_retries;
……
do {
reqp=&lopt->syn_table[i];
while ((req = *reqp) != NULL) {
if (time_after_eq(now, req->expires)) {
if ((req->retrans < thresh ||
(inet_rsk(req)->acked && req->retrans < max_retries))
&& !req->rsk_ops->rtx_syn_ack(parent, req, NULL)) {
unsigned long timeo;

if (req->retrans++ == 0)
lopt->qlen_young--;
timeo = min((timeout << req->retrans), max_rto);這裏的計算方法和之前相似,註意的一點是,前面的判斷已經遞增了retrans的值,所以第一次重傳就已經倍增超時時間
req->expires = now + timeo;
reqp = &req->dl_next;
continue;
}

/* Drop this request */
inet_csk_reqsk_queue_unlink(parent, req, reqp);
reqsk_queue_removed(queue, req);
reqsk_free(req);
continue;
}
reqp = &req->dl_next;
}

i = (i + 1) & (lopt->nr_table_entries - 1);

} while (--budget > 0);
該時間同樣是189秒,計算過程同上。
3、鏈路建立之後重傳時間
#define TCP_RETR2 15 /*
* This should take at least
* 90 minutes to time out.
* RFC1122 says that the limit is 100 sec.
* 15 is ~13-30min depending on RTO.
*/
這個時間比較長,計算方法和之前有些不同,因為第六次重傳時3*2^6=172,該值已經大於TCP_RTO_MAX120,所以其總共時間為
3*(2^6-1)+ 120*(15-5) = 189+1200=1389s=23分9秒。

網絡協議棧(13)syn flood攻擊防範及部分重傳定時器參數分析