<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 8338字,需瀏覽 17分鐘

           ·

          2021-09-23 17:03

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

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

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

          不!

          我就不!

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

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

          好了,進入正題。

          下面是這篇文章的目錄。

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


          什么是RST

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

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

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

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

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

          TCP報頭RST位


          怎么知道收到RST了?

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

          當本端收到遠端發(fā)來的RST后,內(nèi)核已經(jīng)認為此鏈接已經(jīng)關閉。

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

          ResetByPeer

          如果本端應用層嘗試去執(zhí)行寫數(shù)據(jù)操作,比如send,那么應用層就會收到 Broken pipe 的報錯,意思是發(fā)送通道已經(jīng)壞了。

          BrokenPipe

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


          出現(xiàn)RST的場景有哪些

          RST一般出現(xiàn)于異常情況,歸類為 對端的端口不可用socket提前關閉。


          端口不可用

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

          端口未監(jiān)聽
          TCP連接未監(jiān)聽的端口

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

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

          全局hash表

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

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


          端口未監(jiān)聽就一定會發(fā)RST嗎?

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

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

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

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

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

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

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

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


          為什么數(shù)據(jù)包異常的情況下,不發(fā)RST?

          一個數(shù)據(jù)包連校驗都不能通過,那這個包,多半有問題。

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

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

          如果對方用的是TCP,是可靠傳輸協(xié)議,發(fā)現(xiàn)很久沒有ACK響應,自己就會重傳。

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

          因此,數(shù)據(jù)包異常的情況下,默默扔掉,不發(fā)RST,非常合理。


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

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

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

          • A:跟他激情互噴

          • B:不跟他一般見識,就當沒聽過

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

          這下,應該就懂了。


          程序啟動了但是崩了

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

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

          TCP監(jiān)聽了但崩了

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

          此時服務端又收到了客戶端發(fā)來的消息,內(nèi)核協(xié)議棧會根據(jù)IP端口,從全局哈希表里查找sock,結果當然是拿不到對應的sock數(shù)據(jù),于是走了跟上面"端口未監(jiān)聽"時一樣的邏輯,回了個RST??蛻舳嗽谑盏絉ST后也釋放了sock資源,從效果上來看,就是連接斷了

          RST和502的關系

          上面這張圖,服務端程序崩潰后,如果客戶端再有數(shù)據(jù)發(fā)送,會出現(xiàn)RST。但如果在客戶端和服務端中間再加一個nginx,就像下圖一樣。

          RST與502

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

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


          socket提前關閉

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

          本端提前關閉

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

          recvbuf非空


          遠端提前關閉

          遠端已經(jīng)close()socket,此時本端還嘗試發(fā)數(shù)據(jù)給遠端。那么遠端就會回一個RST。

          close()觸發(fā)TCP四次揮手

          大家知道,TCP是全雙工通信,意思是發(fā)送數(shù)據(jù)的同時,還可以接收數(shù)據(jù)。

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

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


          對方?jīng)]收到RST,會怎么樣?

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

          而RST,不需要ACK確認包。

          因為RST本來就是設計來處理異常情況的,既然都已經(jīng)在異常情況下了,還指望對方能正?;啬阋粋€ACK嗎?可以幻想,不要妄想。

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

          RST丟失

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

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

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

          RST丟失后keepalive


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

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

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

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

              // step 2:執(zhí)行收到 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是否在合法接收窗口范圍內(nèi)。如果不在范圍內(nèi),這個RST包就會被丟棄。

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

          接收窗口

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


          為什么要校驗是否在窗口范圍內(nèi)

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

          RST攻擊

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

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

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


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

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

          以下是面向監(jiān)獄編程的環(huán)節(jié)。

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

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

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


          盲猜seq

          窗口數(shù)值seq本質(zhì)上只是個uint32類型。

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

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

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


          已連接狀態(tài)下收到第一次握手包會怎么樣?

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

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

          但實際,并不是。

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

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

              /* 
               * RFC 5691 4.2 : 發(fā)送 challenge ack
               */

              if (th->syn) {
          syn_challenge:
                  tcp_send_challenge_ack(sk);
              }
          }

          當客戶端發(fā)出一個不在合法窗口內(nèi)的SYN包的時候,服務端會發(fā)一個帶有正確的seq數(shù)據(jù)ACK包出來,這個ACK包叫 challenge ack。

          challenge ack抓包

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


          利用challenge ack獲取seq

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

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

          利用ChallengeACK的RST攻擊


          總結

          • RST其實是TCP包頭里的一個標志位,目的是為了在異常情況下關閉連接。

          • 內(nèi)核收到RST后,應用層只能通過調(diào)用讀/寫操作來感知,此時會對應獲得 Connection reset by peerBroken pipe 報錯。

          • 發(fā)出RST后不需要得到對方的ACK確認包,因此RST丟失后對方不能立刻感知,但是通過下一次重傳數(shù)據(jù)或keepalive心跳包可以導致RST重傳。

          • 收到RST包,不一定會斷開連接,seq不在合法窗口范圍內(nèi)的數(shù)據(jù)包會被默默丟棄。通過構造合法窗口范圍內(nèi)seq,可以造成RST攻擊,這一點大家了解就好,千萬別學!


          參考資料

          TCP旁路攻擊分析與重現(xiàn) - https://www.cxyzjd.com/article/qq_27446553/52416369


          瀏覽 132
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲色诱| 懂色av蜜臀av粉嫩av | 天天干天天噜天天操 | 四虎久久影院 | 大香蕉操B |