日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

想必大家已經知道我的niao性,搞個標題,就是不喜歡立馬回答。

就是要搞一大堆原理性的東西,再回答標題的問題。

說這個是因為我這次會把問題的答案就放到開頭嗎?

不!

我就不!

但是大家可以直接根據目錄看自己感興趣的部分。

之所以要先鋪墊一些原理,還是希望大家能先看些基礎的,再慢慢循序漸進,這樣有利于建立知識體系。多一點上下文,少一點gap。

好了,進入正題。

下面是這篇文章的目錄。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

收到RST就一定會斷開連接嗎

 

什么是RST

我們都知道TCP正常情況下斷開連接是用四次揮手,那是正常時候的優雅做法。

異常情況下,收發雙方都不一定正常,連揮手這件事本身都可能做不到,所以就需要一個機制去強行關閉連接。

RST 就是用于這種情況,一般用來異常地關閉一個連接。它是一個TCP包頭中的標志位

正常情況下,不管是發出,還是收到置了這個標志位的數據包,相應的內存、端口等連接資源都會被釋放。從效果上來看就是TCP連接被關閉了。

而接收到 RST的一方,一般會看到一個 connection reset 或 connection refused 的報錯。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

TCP報頭RST位

 

怎么知道收到RST了?

我們知道內核應用層是分開的兩層,網絡通信功能在內核,我們的客戶端或服務端屬于應用層。應用層只能通過 send/recv 與內核交互,才能感知到內核是不是收到了RST。

當本端收到遠端發來的RST后,內核已經認為此鏈接已經關閉。

此時如果本端應用層嘗試去執行 讀數據操作,比如recv,應用層就會收到 Connection reset by peer 的報錯,意思是遠端已經關閉連接

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

ResetByPeer

如果本端應用層嘗試去執行寫數據操作,比如send,那么應用層就會收到 Broken pipe 的報錯,意思是發送通道已經壞了。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

BrokenPipe

這兩個是開發過程中很經常遇到的報錯,感覺大家可以把這篇文章放進收藏夾吃灰了,等遇到這個問題了,再打開來擦擦灰,說不定對你會有幫助。

 

出現RST的場景有哪些

RST一般出現于異常情況,歸類為 對端的端口不可用 和 socket提前關閉

 

端口不可用

端口不可用分為兩種情況。要么是這個端口從來就沒有"可用"過,比如根本就沒監聽(listen)過;要么就是曾經"可用",但現在"不可用"了,比如服務突然崩了。

端口未監聽

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

TCP連接未監聽的端口

服務端listen 方法會創建一個sock放入到全局的哈希表中。

此時客戶端發起一個connect請求到服務端。服務端在收到數據包之后,第一時間會根據IP和端口從哈希表里去獲取sock。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

全局hash表

如果服務端執行過listen,就能從全局哈希表里拿到sock。

但如果服務端沒有執行過listen,那哈希表里也就不會有對應的sock,結果當然是拿不到。此時,正常情況下服務端會發RST給客戶端。

 

端口未監聽就一定會發RST嗎?

不一定。上面提到,發RST的前提是正常情況下,我們看下源碼。

// net/ipv4/tcp_ipv4.c  
// 代碼經過刪減
int tcp_v4_rcv(struct sk_buff *skb)
{
    // 根據ip、端口等信息 獲取sock。
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk)
        goto no_tcp_socket;

no_tcp_socket:
    // 檢查數據包有沒有出錯
    if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
        // 錯誤記錄
    } else {
        // 發送RST
        tcp_v4_send_reset(NULL, skb);
    }
}

內核在收到數據后會從物理層、數據鏈路層、網絡層、傳輸層、應用層,一層一層往上傳遞。到傳輸層的時候,根據當前數據包的協議是TCP還是UDP走不一樣的函數方法。可以簡單認為,TCP數據包都會走到 tcp_v4_rcv()。這個方法會從全局哈希表里獲取 sock,如果此時服務端沒有listen()過 , 那肯定獲取不了sock,會跳轉到no_tcp_socket的邏輯。

注意這里會先走一個 tcp_checksum_complete(),目的是看看數據包的校驗和(Checksum)是否合法。

校驗和可以驗證數據從端到端的傳輸中是否出現異常。由發送端計算,然后由接收端驗證。計算范圍覆蓋數據包里的TCP首部和TCP數據。

如果在發送端到接收端傳輸過程中,數據發生任何改動,比如被第三方篡改,那么接收方能檢測到校驗和有差錯,此時TCP段會被直接丟棄。如果校驗和沒問題,那才會發RST。

所以,只有在數據包沒問題的情況下,比如校驗和沒問題,才會發RST包給對端。

 

為什么數據包異常的情況下,不發RST?

一個數據包連校驗都不能通過,那這個包,多半有問題

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

有可能是在發送的過程中被篡改了,又或者,可能只是一個胡亂偽造的數據包。

五層網絡,不管是哪一層,只要遇到了這種數據,推薦的做法都是默默扔掉而不是去回復一個消息告訴對方數據有問題。

如果對方用的是TCP,是可靠傳輸協議,發現很久沒有ACK響應,自己就會重傳。

如果對方用的是UDP,說明發送端已經接受了“不可靠會丟包”的事實,那丟了就丟了。

因此,數據包異常的情況下,默默扔掉,不發RST,非常合理。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

還是不能理解?那我再舉個例子

正常人噴你,他說話條理清晰,主謂賓分明。此時你噴回去,那你是個充滿熱情,正直,富有判斷力的好人。

而此時一個憨憨也想噴你,但他思維混亂,連話都說不清楚,一直阿巴阿巴的,你雖然聽不懂,但大受震撼,此時你會?

  • A:跟他激情互噴
  • B:不跟他一般見識,就當沒聽過

一般來說最優選擇是B,畢竟你理他,他反而來勁。

這下,應該就懂了。

 

程序啟動了但是崩了

端口不可用的場景里,除了端口未監聽以外,還有可能是從前監聽了,但服務端機器上做監聽操作的應用程序突然崩了,此時客戶端還像往常一樣正常發送消息,服務器內核協議棧收到消息后,則會回一個RST。在開發過程中,這種情況是最常見的

比如你的服務端應用程序里,弄了個空指針,或者數組越界啥的,程序立馬就崩了。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

TCP監聽了但崩了

這種情況跟端口未監聽本質上類似,在服務端的應用程序崩潰后,原來監聽的端口資源就被釋放了,從效果上來看,類似于處于CLOSED狀態。

此時服務端又收到了客戶端發來的消息,內核協議棧會根據IP端口,從全局哈希表里查找sock,結果當然是拿不到對應的sock數據,于是走了跟上面"端口未監聽"時一樣的邏輯,回了個RST。客戶端在收到RST后也釋放了sock資源,從效果上來看,就是連接斷了

RST和502的關系

上面這張圖,服務端程序崩潰后,如果客戶端再有數據發送,會出現RST。但如果在客戶端和服務端中間再加一個Nginx,就像下圖一樣。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

RST與502

nginx會作為客戶端和服務端之間的"中間人角色",負責轉發請求和響應結果。但當服務端程序崩潰,比如出現野指針或者OOM的問題,那轉發到服務器的請求,必然得不到響應,后端服務端還會返回一個RST給nginx。nginx在收到這個RST后會斷開與服務端的連接,同時返回客戶端一個502錯誤碼。

所以,出現502問題,一般情況下都是因為后端程序崩了,基于這一點假設,去看看監控是不是發生了OOM或者日志是否有空指針等報錯信息。

 

socket提前關閉

這種情況分為本端提前關閉,和遠端提前關閉。

本端提前關閉

如果本端socket接收緩沖區還有數據未讀,此時提前close() socket。那么本端會先把接收緩沖區的數據清空,然后給遠端發一個RST。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

recvbuf非空

 

遠端提前關閉

遠端已經close()了socket,此時本端還嘗試發數據給遠端。那么遠端就會回一個RST。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

close()觸發TCP四次揮手

大家知道,TCP是全雙工通信,意思是發送數據的同時,還可以接收數據。

Close()的含義是,此時要同時關閉發送和接收消息的功能。

客戶端執行close(), 正常情況下,會發出第一次揮手FIN,然后服務端回第二次揮手ACK。如果在第二次和第三次揮手之間,如果服務方還嘗試傳數據給客戶端,那么客戶端不僅不收這個消息,還會發一個RST消息到服務端。直接結束掉這次連接。

 

對方沒收到RST,會怎么樣?

我們知道TCP是可靠傳輸,意味著本端發一個數據,遠端在收到這個數據后就會回一個ACK,意思是"我收到這個包了"。

而RST,不需要ACK確認包

因為RST本來就是設計來處理異常情況的,既然都已經在異常情況下了,還指望對方能正常回你一個ACK嗎?可以幻想,不要妄想。

問題又來了,網絡環境這么復雜,丟包也是分分鐘的事情,既然RST包不需要ACK來確認,那萬一對方就是沒收到RST,會怎么樣?

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

RST丟失

RST丟了,問題不大。比方說上圖服務端,發了RST之后,服務端就認為連接不可用了。

如果客戶端之前發送了數據,一直沒等到這個數據的確認ACK,就會重發,重發的時候,自然就會觸發一個新的RST包。

而如果客戶端之前沒有發數據,但服務端的RST丟了,TCP有個keepalive機制,會定期發送探活包,這種數據包到了服務端,也會重新觸發一個RST。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

RST丟失后keepalive

 

收到RST就一定會斷開連接嗎?

先說結論,不一定會斷開。我們看下源碼。

// net/ipv4/tcp_input.c
static bool tcp_validate_incoming()
{
    // 獲取sock
    struct tcp_sock *tp = tcp_sk(sk);

    // step 1:先判斷seq是否合法(是否在合法接收窗口范圍內)
    if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
        goto discard;
    }

    // step 2:執行收到 RST 后該干的事情
    if (th->rst) {
        if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt)
            tcp_reset(sk);
        else
            tcp_send_challenge_ack(sk);
        goto discard;
    }
}

收到RST包,第一步會通過tcp_sequence先看下這個seq是否合法,其實主要是看下這個seq是否在合法接收窗口范圍內。如果不在范圍內,這個RST包就會被丟棄。

至于接收窗口是個啥,我們先看下面這個圖。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

接收窗口

這里黃色的部分,就是指接收窗口,只要RST包的seq不在這個窗口范圍內,那就會被丟棄。

 

為什么要校驗是否在窗口范圍內

正常情況下客戶端服務端雙方可以通過RST來斷開連接。假設不做seq校驗,如果這時候有不懷好意的第三方介入,構造了一個RST包,且在TCP和IP等報頭都填上客戶端的信息,發到服務端,那么服務端就會斷開這個連接。同理也可以偽造服務端的包發給客戶端。這就叫RST攻擊

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

RST攻擊

受到RST攻擊時,從現象上看,客戶端老感覺服務端崩了,這非常影響用戶體驗。

如果這是個游戲,我相信多崩幾次,第二天大家就不來玩了。

實際消息發送過程中,接收窗口是不斷移動的,seq也是在飛快的變動中,此時第三方是比較難構造出合法seq的RST包的,那么通過這個seq校驗,就可以攔下了很多不合法的消息。

 

加了窗口校驗就不能用RST攻擊了嗎

不是,只是增加了攻擊的成本。但如果想搞,還是可搞的。

以下是面向監獄編程的環節。

希望大家只了解原理就好了,不建議使用

相信大家都不喜歡穿著藍白條紋的衣服,拍純獄風的照片。

從上面可以知道,不是每一個RST包都會導致連接重置的,要求是這個RST包的seq要在窗口范圍內,所以,問題就變成了,我們怎么樣才能構造出合法的seq

 

盲猜seq

窗口數值seq本質上只是個uint32類型。

struct tcp_skb_cb {
    __u32       seq;        /* Starting sequence number */
}

如果在這個范圍內瘋狂猜測seq數值,并構造對應的包,發到目的機器,雖然概率低,但是總是能被試出來,從而實現RST攻擊。這種亂棍打死老師傅的方式,就是所謂的合法窗口盲打(blind in-window attacks)

覺得這種方式比較?那有沒有聰明點的方式,還真有,但是在這之前需要先看下面的這個問題。

 

已連接狀態下收到第一次握手包會怎么樣?

我們需要了解一個問題,比如服務端在已連接(ESTABLISHED)狀態下,如果收到客戶端發來的第一次握手包(SYN),會怎么樣?

以前我以為服務單會認為客戶端憨憨了,直接RST連接。

但實際,并不是

static bool tcp_validate_incoming()
{
    struct tcp_sock *tp = tcp_sk(sk);

    /* 判斷seq是否在合法窗口內 */
    if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
        if (!th->rst) {
            // 收到一個不在合法窗口內的SYN包
            if (th->syn)
                goto syn_challenge;
        }
    }

    /* 
     * RFC 5691 4.2 : 發送 challenge ack
     */
    if (th->syn) {
syn_challenge:
        tcp_send_challenge_ack(sk);
    }
}

當客戶端發出一個不在合法窗口內的SYN包的時候,服務端會發一個帶有正確的seq數據ACK包出來,這個ACK包叫 challenge ack。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

challenge ack抓包

上圖是抓包的結果,用scapy隨便偽造一個seq=5的包發到服務端(端口9090),服務端回復一個帶有正確seq值的challenge ack包給客戶端(端口8888)。

 

利用challenge ack獲取seq

上面提到的這個challenge ack ,仿佛為盲猜seq的老哥們打開了一個新世界。

在獲得這個challenge ack后,攻擊程序就可以以ack值為基礎,在一定范圍內設置seq,這樣造成RST攻擊的幾率就大大增加了。

動圖圖解!收到RST,就一定會斷開TCP連接嗎?

 

利用ChallengeACK的RST攻擊

 

總結

  • RST其實是TCP包頭里的一個標志位,目的是為了在異常情況下關閉連接。
  • 內核收到RST后,應用層只能通過調用讀/寫操作來感知,此時會對應獲得 Connection reset by peer 和Broken pipe 報錯。
  • 發出RST后不需要得到對方的ACK確認包,因此RST丟失后對方不能立刻感知,但是通過下一次重傳數據或keepalive心跳包可以導致RST重傳。
  • 收到RST包,不一定會斷開連接,seq不在合法窗口范圍內的數據包會被默默丟棄。通過構造合法窗口范圍內seq,可以造成RST攻擊,這一點大家了解就好,千萬別學!

來源:
https://mp.weixin.qq.com/s/Fr6o6gRiIUIspV9-jR9snw

分享到:
標簽:連接 TCP
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定