linux核心中實現了syn-cookie,可以有效阻止syn-flood攻擊,syn-cookie理論上很簡單,就是在伺服器接收到用戶端的syn包時並不分配任何記憶體空間,而是巧妙的選擇伺服器的isn值傳給用戶端,isn本地也並不儲存(本地不儲存任何東西),然後用戶端發來synack的ack確認包後,從該確認包中取出確認號,然後利用確認號是上一次序號加1的特性來得到原來的伺服器發給用戶端的synack包的序號,然後用相同的源,目的地址/連接埠資訊用相同的演算法計算一個數和得到的原始序號比較,如果一樣就說明串連合法,否則不合法,在傳輸中使用逾時機制。linux的實現大體上和理論的類似,只是使用sha1演算法得到的isn中儲存了mss值,這是個額外的收穫,另外還限制了兩次握手之間的最短時限:
__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;
for (mssind = 0; mss > msstab[mssind + 1]; mssind++);
*mssp = msstab[mssind] + 1;
NET_INC_STATS_BH(LINUX_MIB_SYNCOOKIESSENT); //以下調用Function Compute摘要,最終作為伺服器端序號發送
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(__u32 saddr, __u32 daddr, __u16 sport, __u16 dport, __u32 sseq, __u32 count, __u32 data)
{
//count
return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq + (count
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data) & COOKIEMASK));
}
static inline int cookie_check(struct sk_buff *skb, __u32 cookie)
{
__u32 seq;
__u32 mssind;
seq = ntohl(skb->h.th->seq)-1;
mssind = check_tcp_syn_cookie(cookie,
skb->nh.iph->saddr, skb->nh.iph->daddr,
skb->h.th->source, skb->h.th->dest,
seq, jiffies / (HZ * 60), COUNTER_TRIES);
return mssind
}
static __u32 check_tcp_syn_cookie(__u32 cookie, __u32 saddr, __u32 daddr, __u16 sport, __u16 dport, __u32 sseq, __u32 count, __u32 axdiff)
{
__u32 diff;
cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
//經過上面計算以後,cookie剩下了高8位的那時的分鐘數和由地址,連接埠,時間算出來的一個sha摘要值以及一個附加的mss值,以下的diff計算得到目前時間和通過cookie得到的時間的差值,cookie >> COOKIEBITS得到cookie的高8位,最後的一個&操作屏掉了不相關的高位,但是有一個問題,見下面。
diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);
if (diff >= maxdiff) //這個maxdiff就是COUNTER_TRIES,該值是通過tcp的逾時機制計算出來的
return (__u32)-1;
//通過以下的減法就得到了當初的mss值,diff肯定小於maxdiff,也就是4,那麼count減去這個差值就是原來的時間,以分鐘計時。
return (cookie - cookie_hash(saddr, daddr, sport, dport, count - diff, 1)) & COOKIEMASK;
}
以上的演算法並不是十全十美的,問題出在diff的計算上,如果當前的count值和過去產生cookie時的count值差很大,比如現在的count是11111111111111111111111100000011,原來的是00000000000000000000000000000001,這樣通過diff的公式計算得到2,完全符合,然後計算下面的cookie_hash,此時如果sha又碰撞了,那麼就會得到錯誤的結果,但是即使出錯也沒有什麼大問題,出錯的幾率畢竟很小很小,因此syn-flood攻擊還是無法成功,再者說了,之所以這裡的diff檢查不嚴格,一個後面還有一個sha計算,第二是tcp本身就是逾時機制,相隔那麼長的時間逾時早就過了,因此根本就不會有那樣的包回來。另外即使不碰撞,萬一碰到由於count或者別的addr或者port等改變而引起了sha摘要的改變,但是改變的很小,被cookie減去之後仍然是合法的範圍內的值,這樣也會通過驗證,不過大可不必擔心這樣的情況,出錯率很低,摘要離得很近的機率也很低,因此這樣的驗證是可信的,設計系統時千萬不要為了萬無一失而損失效率。
並不是只要設定了syn-cookie,每次有用戶端串連都要進行驗證,而是只有在一定情況下才開啟,因為syn-cookie是很耗時並且違背tcp原則的,且看下面的程式碼片段,這是在tcp服務端進行三向交握期間回複syn包時執行的程式碼片段:
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 (want_cookie) {
isn = cookie_v4_init_sequence(sk, skb, &req->mss);
}
只有在伺服器的accept_queue滿載時才會啟用,可以看出懶惰啟用對效率提升很有效,並且還可以有效防止syn-flood攻擊,你可以隨便攻擊,這個linux不管,linux絲毫不會畏懼dos攻擊,也不會提前採取任何行動,一切就像什麼也沒有發生一樣,但是一旦攻擊開始影響系統,這裡就是接收隊列已經爆滿,linux就要應付了,啟動應急預案,這裡就是syn-cookie。
那麼linux中是怎麼實現syn-cookie的呢?其實是用sha1摘要演算法計算的,接下來就看一下這個演算法:
#define SHA_DIGEST_WORDS 5 //sha1的初始摘要一共5個32位空間
#define SHA_WORKSPACE_WORDS 80 //計算一個摘要需要80個32位的緩衝區空間
static __u32 syncookie_secret[2][16-3+SHA_DIGEST_WORDS]; //一共2*21*4位元組的空間
static u32 cookie_hash(u32 saddr, u32 daddr, u32 sport, u32 dport, u32 count, int c)
{
__u32 tmp[16 + 5 + SHA_WORKSPACE_WORDS];
memcpy(tmp + 3, syncookie_secret[c], sizeof(syncookie_secret[c]));
tmp[0] = saddr;
tmp[1] = daddr;
tmp[2] = (sport
tmp[3] = count;
//從tmp的21位移處開始填充sha的字空間,填充的內容是tmp的既有的前16個元素
//一共16+5+80個32位空間,前16個是既有資料空間,接著5個是sha輸出緩衝區,注意這5個空間已經被填充了部分syncookie_secret,因為一個syncookie_secret元素的長度是18,並且從第三個tmp空間開始填充,故而一直延展到第21個空間,從第21個空間往後就成了sha計算緩衝區了。
sha_transform(tmp + 16, (__u8 *)tmp, tmp + 16 + 5);
return tmp[17]; //對於tcp的syn-cookie,只需要挑一個就可以了
}
sha演算法核心,digest參數為摘要資訊指標,其實就需要其前5個無符號32位元據,in是需要計算摘要的資料,最後的W是計算空間緩衝區,由參數digest和in以及動態計算被填充:
void sha_transform(__u32 *digest, const char *in, __u32 *W)
{
__u32 a, b, c, d, e, t, i;
for (i = 0; i
W[i] = be32_to_cpu(((const __be32 *)in)[i]);
for (i = 0; i
W[i+16] = rol32(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 1);
a = digest[0]; //這些操作是賦值操作,將資料緩衝區賦值給臨時變數
b = digest[1]; //一共是5個32位緩衝區
c = digest[2]; //對於syn-cookie來講,這個digest就是調用函數的tmp數組的從16下標開始的,已經被覆蓋了syncookie_secret
d = digest[3];
e = digest[4]; //以下開始計算,分段計算
for (i = 0; i
t = f1(b, c, d) + K1 + rol32(a, 5) + e + W[i];
e = d; d = c; c = rol32(b, 30); b = a; a = t;
}
for (; i
t = f2(b, c, d) + K2 + rol32(a, 5) + e + W[i];
e = d; d = c; c = rol32(b, 30); b = a; a = t;
}
for (; i
t = f3(b, c, d) + K3 + rol32(a, 5) + e + W[i];
e = d; d = c; c = rol32(b, 30); b = a; a = t;
}
for (; i
t = f2(b, c, d) + K4 + rol32(a, 5) + e + W[i];
e = d; d = c; c = rol32(b, 30); b = a; a = t;
}
digest[0] += a; //輸出資料
digest[1] += b;
digest[2] += c;
digest[3] += d;
digest[4] += e;
}