Skip to content

Commit

Permalink
sre: managing-load finish pokemon go
Browse files Browse the repository at this point in the history
  • Loading branch information
evan361425 committed Jul 23, 2024
1 parent 8e0341f commit ef12932
Show file tree
Hide file tree
Showing 2 changed files with 449 additions and 22 deletions.
369 changes: 368 additions & 1 deletion src/essay/web/tcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ Kernel options 可以參考 [sysctl-explorer](https://sysctl-explorer.net/net/)

### Congestion Control

[BBR](https://github.com/evan361425/evan361425.github.io/issues/34), [Queue-Discipline](https://sysctl-explorer.net/net/core/default_qdisc/)
避免封包壅塞,TCP 提供幾種演算法:

- [BBR](https://github.com/evan361425/evan361425.github.io/issues/34)
- [Queue-Discipline](https://sysctl-explorer.net/net/core/default_qdisc/)

### 範例

Expand Down Expand Up @@ -145,6 +148,370 @@ TBD

TBD

## 指標

指標名稱都有前綴:`node_netstat_TcpExt_`

> [!Note]
>
> 本列表示使用 Node Exporter 提供的指標。
>
> 你可以透過以下指令來取得各個指標的上升幅度:
>
> ```text
> increase(label_replace({__name__=~"node_netstat_Tcp.*"}, "na", "$1", "__name__", "node_netstat_Tcp(.+)")[5m:1m])
> ```
| 名稱 | 原理 | 說明 |
| - | - | - |
| ArpFilter | Basic | 接收到 ARP 封包時,拿到未知的 entry 時 |
| EmbryonicRsts | Basic | `SYN_RECV` 時收到不合理的封包,並回傳 `RST` |
| ListenDrops | Basic | `LISTEN` 因任何原因建立連線失敗,大於等於 ListenOverflows |
| TCPTimeouts | Basic | 第一次發生 RTO 或 SYN-ACK 超時 |
| TW | Basic | `TIME_WAIT` 數量 |
| TCPTimeWaitOverflow | Basic | `TIME_WAIT` 數量太多了 |
| TWKilled | Basic | `TIME_WAIT` 在未達時限時,強制回收的數量 |
| TWRecycled | Basic | `TIME_WAIT` 被回收的數量 |
| TCPAbortFailed | Basic | 程式發生錯誤時,強制關閉連線 |
| TCPAbortOnClose | Basic | 在進入 `FIN_WAIT_1``FIN_WAIT_2` 時,強制關閉連線 |
| TCPAbortOnData | Basic | 在連線還有資料時,強制關閉連線 |
| TCPAbortOnMemory | Basic | 資源超過限制時,強制關閉連線 |
| TCPAbortOnTimeout | Basic | RTO/probe/keepalive timer 達到最高上限時,強制關閉連線 |
| TCPSynRetrans | Basic | 重新嘗試送出 `SYN` |
| TCPRetransFail | Basic | 重新嘗試送出 `SYN` 卻仍失敗? |
| TCPKeepAlive | Basic | Keep-Alive 次數 |
| TCPFastRetrans | [Fast Retransmission] | 收到新的 `ACK`,舊的還沒收到就直接 retrans |
| TCPSlowStartRetrans | [Delayed ACK] | Delayed ACK 開始啟用 |
| DelayedACKLocked | [Delayed ACK] | Timer 被本機 lock 住而無法正確發送 `ACK` |
| DelayedACKLost | [Delayed ACK] | 送出的 `ACK` 被認為不合法 |
| DelayedACKs | [Delayed ACK] | 送出 Delayed ACK 次數 |
| ListenOverflows | [Backlog] | `LISTEN` 成功完成握手後,準備移交連線時發現 queue 滿了 |
| TCPBacklogCoalesce | [Backlog] | |
| TCPBacklogDrop | [Backlog] | 進入佇列時失敗 |
| PAWSActive | [PAWS] | `SYN_SENT` 後的 `ACK` 檢查 PAWS 失敗 |
| PAWSPassive | [PAWS] | `SYN_RECV` 後的 `ACK` 檢查 PAWS 失敗 |
| PAWSEstab | [PAWS] | 連線建立後 PAWS 檢查失敗 |
| TCPTSReorder | [PAWS] | Timestamp 亂序了 |
| TCPDSACKIgnoredNoUndo | [Selective ACK] | 收到非法 D-SACK 時,話句話說,SACK 中的序號太舊 |
| TCPDSACKIgnoredOld | [Selective ACK] | 收到非法 D-SACK 時,話句話說,SACK 中的序號太舊 |
| TCPDSACKOfoRecv | [Selective ACK] | 本機發送的舊封包還沒送到,新的封包已經送去了 |
| TCPDSACKOfoSent | [Selective ACK] | 本機舊的封包還沒收到,新的封包已經來了 |
| TCPDSACKOldSent | [Selective ACK] | 對方傳本機已經看過的封包,換句話說,對方收不到本機的 `ACK` |
| TCPDSACKRecv | [Selective ACK] | 對方收到本機的封包重傳,換句話說,本機收不到對方的 `ACK` |
| TCPDSACKUndo | [Selective ACK] | |
| TCPLostRetransmit | [Selective ACK] | 送過去的封包沒有得到相應的 SACK |
| TCPSACKDiscard | [Selective ACK] | 收到非法 SACK 時,話句話說,SACK 中的序號太舊 |
| TCPSACKReneging | [Selective ACK] | 本機已經丟掉了之前接收的數據,本機食言了 |
| TCPSACKReorder | [Selective ACK] | 本機收不到對方封包,開始等待舊資料 |
| TCPSackRecovery | [Selective ACK] | 本機送不出去封包,開始整理封包順序 |
| TCPSackFailures | [Selective ACK] | RTO 發生,不再重傳對方沒接收到的封包 |
| TCPSackRecoveryFail | [Selective ACK] | RTO 發生,不再重整接收的封包順序 |
| TCPSackShifted | [Selective ACK] | Linux 成功整理順序 |
| TCPSackShiftFallback | [Selective ACK] | Linux 整理順序失敗 |
| TCPSackMerged | [Selective ACK] | 得到亂序資料後,進行整併成功 |
| TCPRenoFailures | | 在不啟用 SACK 的狀態 |
| TCPRenoRecovery | | 在不啟用 SACK 的狀態 |
| TCPRenoRecoveryFail | | 在不啟用 SACK 的狀態 |
| TCPRenoReorder | | 在不啟用 SACK 的狀態 |
| TCPDeferAcceptDrop | [Defer Accept] | Defer accept 的連線被丟棄 |
| TCPFastOpenActive | [Fast Open] | 發送方成功建立 |
| TCPFastOpenActiveFail | [Fast Open] | 發送方建立失敗 |
| TCPFastOpenBlackhole | [Fast Open] | 避免 firewall 阻擋導致失能的機制被觸發 |
| TCPFastOpenCookieReqd | [Fast Open] | Server 回應 Cookie Required |
| TCPFastOpenListenOverflow | [Fast Open] | 接收方連線佇列滿溢 |
| TCPFastOpenPassive | [Fast Open] | 接收方獲得新連線 |
| TCPFastOpenPassiveAltKey | [Fast Open] | 接收方獲得新連線且 cookie 有效 |
| TCPFastOpenPassiveFail | [Fast Open] | 接收方嘗試建立連線失敗 |
| TCPFromZeroWindowAdv | [Zero Window] | |
| TCPToZeroWindowAdv | [Zero Window] | |
| TCPWantZeroWindowAdv | [Zero Window] | |
| TCPZeroWindowDrop | [Zero Window] | |
| SyncookiesFailed | [SYN Cookies] | 驗證失敗 |
| SyncookiesRecv | [SYN Cookies] | 收到有 cookie 的 SYN |
| SyncookiesSent | [SYN Cookies] | 送出有 cookie 的 SYN |
| TCPAutoCorking | [CORK] | 滿了或時間到了,直接送出 |
| TCPRcvCoalesce | [Coalescing] | 收到整併的封包 |
| TCPAbortOnLinger | [Linger] | 進入 `FIN_WAIT_2` 時,強制關閉連線 |
| TCPChallengeACK | | |
| TCPSYNChallenge | | |
| TCPACKSkippedChallenge | | |
| TCPOFODrop | | |
| TCPOFOMerge | | |
| TCPOFOQueue | | |
| TCPMTUPFail | | |
| TCPMTUPSuccess | | |
| PruneCalled | | |
| OfoPruned | | |
| RcvPruned | | |
| BusyPollRxPackets | | |
| IPReversePathFilter | | |
| LockDroppedIcmps | ICMP | ICMP 封包本應被丟棄,但因為是本機的請求,所以被 handle 了 |
| OutOfWindowIcmps | ICMP | 在已有的連線中偵測出錯誤狀態 |
| PFMemallocDrop | | |
| TCPACKSkippedFinWait2 | | |
| TCPACKSkippedPAWS | | |
| TCPACKSkippedSeq | | |
| TCPACKSkippedSynRecv | | |
| TCPACKSkippedTimeWait | | |
| TCPAckCompressed | | |
| TCPPureAcks | | |
| TCPFullUndo | | |
| TCPPartialUndo | | |
| TCPLossUndo | | |
| TCPDelivered | | |
| TCPDeliveredCE | | |
| TCPHPAcks | | |
| TCPHPHits | | |
| TCPHystartDelayCwnd | | |
| TCPHystartDelayDetect | | |
| TCPHystartTrainCwnd | | |
| TCPHystartTrainDetect | | |
| TCPLossFailures | | |
| TCPLossProbeRecovery | | |
| TCPLossProbes | | |
| TCPMemoryPressures | | |
| TCPMemoryPressuresChrono | | |
| TCPMinTTLDrop | Basic | 封包的 TTL 過小,換句話說,傳了太久或太遠 |
| TCPOrigDataSent | | |
| TCPRcvCollapsed | | |
| TCPRcvQDrop | | |
| TCPReqQFullDoCookies | | |
| TCPReqQFullDrop | | |
| TCPSpuriousRTOs | | |
| TCPSpuriousRtxHostQueues | | |
| TCPWinProbe | | |
| TCPWqueueTooBig | | |
| TCPMD5Failure | | |
| TCPMD5NotFound | | |
| TCPMD5Unexpected | | |

相關說明可以參考:

- [Linux Network Statistics Reference](https://loicpefferkorn.net/2018/09/linux-network-statistics-reference/#tcpext)
- [TCP netstat -s 各项参数意义](https://www.cnblogs.com/lovemyspring/articles/5087895.html)

### Backlog

連線建立後會被儲存在一個佇列中,這個佇列稱為 backlog。
Linux 的實作是把 *三次握手階段**連線建立完成* 分別放在不同的佇列。

詳見 Linux man page [listen](https://linux.die.net/man/2/listen)
以及 [How TCP backlog works in Linux](https://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html)

### Linger

- [參考](https://breezetemple.github.io/2019/07/04/tcp-option-SO-LINGER/)

Linux 透過 `SO_LINGER` 來啟用,透過 `RST` 來關閉連線。
這時可能會遇到緩衝區還有資料時,被迫中斷。

有兩種階段,這邊不贅述。

### Defer Accept

Linux 透過 `TCP_DEFER_ACCEPT` 來啟用,只在收到資料後才把該連線認定為成功。

### Fast Retransmission

本來如果送出的封包在 `RTO` 內沒有收到 ACK,則會重新發送(retransmission),
但是如果發送方收到大於最低的 ACK 號碼時,就會直接回傳舊的封包,一種快速回傳的機制。

### Zero Window

- [參考](https://blog.51cto.com/u_15060507/3641387)

當接受方的緩存滿溢了,代表接受方實在過於忙碌,消化不完寄過來的封包。

Full window 則代表發送方需要等待對方的 `ACK` 以接著發送更多資訊,換句話說,對方一直沒有回 `ACK` 給我,
我不敢送更多封包給他。

### Coalescing

- [RFC-896](https://datatracker.ietf.org/doc/html/rfc896)
- 相關:[Delayed ACK][Cork]

用來減少網路負荷的,等到時機純熟後一次性送出多個封包,時機包括:

- 累積的封包超過 MSS(Maximum Segment Size, `MSS=MTU – 40`
- 所有送出的封包都收到相應的 `ACK`

透過 `TCP_NODELAY=1` 來單獨為特定 socket 關閉本功能。

#### 問題

如果搭配 [Delayed ACK] 可能會造成我在等你的 `ACK` 再繼續送封包,而你在等我的 `ACK` 才會繼續送封包。

### CORK

- [參考](https://baus.net/on-tcp_cork/)
- 相關:[Coalescing]

[Coalescing] 相似,等到緩衝填滿了才把資料送出,
這個功能適用於有多個分段的區塊要送出,例如 HTTP Header 和 HTTP Body。

[Coalescing] 的差異主要在於:

- 無視上段封包送回的 `ACK`
- 200 ms 過後就會強制送出封包

透過 `TCP_CORK=1` 來單獨為特定 socket 開啟本功能。

### Delayed ACK

- [RFC-1122](https://datatracker.ietf.org/doc/html/rfc1122)
- 相關:[Coalescing]

用來減少網路負荷的,不用每次收到封包都回 ACK,而是:

- 整合達到一定數目後再送一個 ACK;
- 超過時限,500ms,仍沒收到資料就直接回傳 ACK。

Linux 透過 `TCP_QUICKACK=1` 來關閉本功能。

#### 問題

如果搭配 [Coalescing] 可能會造成我在等你的 `ACK` 再繼續送封包,而你在等我的 `ACK` 才會繼續送封包。

### Selective ACK

- [RFC-2018](https://datatracker.ietf.org/doc/html/rfc2018)
- [參考](https://blog.csdn.net/wdscq1234/article/details/52503315)

用來避免重複傳送的機制,可以指定特定封包重傳,而不用一直等待最舊的那個重傳。

#### Duplicated

- [RFC-2883](https://datatracker.ietf.org/doc/html/rfc2883)
- 相關:[Forward RTO]

透過 SACK 來告知對方我收到了重複的封包,例如:

![告知重複接收了舊封包](https://i.imgur.com/KzOierA.png)

或者告知舊的封包還沒收到,新的封包已經來了(Out-of-order, OFO),例如:

![舊的封包還沒收到,新的封包已經來了](https://i.imgur.com/997qP23.png)

### SYN Cookies

- [RFC-4987](https://datatracker.ietf.org/doc/html/rfc4987)
- [參考](https://cs.pynote.net/net/tcp/202205052/)

接收方在收到 `SYN` 時,回傳一個 cookie,
並等到下次收到相應 cookie 的 `ACK` 後才開始分配記憶體給這個連線。
這樣可以避免大量 `SYN` 傳入時,每個都分配相應連線的記憶體時,導致的服務忙碌。

![SYN Cookies 流程](https://i.imgur.com/rxS0uva.png)

#### 問題

一開始小封包的延遲,可能會造成封包順序的誤判,詳見
[SYN cookies ate my dog](https://wpbolt.com/syn-cookies-ate-my-dog-breaking-tcp-on-linux/)

<!-- ### TLP
https://perthcharles.github.io/2015/10/31/wiki-network-tcp-tlp/ -->

### PAWS

- [RFC-1323](https://datatracker.ietf.org/doc/html/rfc1323)
- [參考](https://blog.csdn.net/mrpre/article/details/124633999)

PAWS(Protection Against Wrapping Sequence),是用來避免 `SEQ` 溢位造成的判斷錯誤。
實作上會使用時間戳記來驗證之。

每個 TCP 封包都會有個 `SEQ` 號碼來代表之,如果收到舊的 `SEQ` 號碼就代表這是之前重傳的封包,需要捨棄之,如下圖所示。

```mermaid
---
title: TCP Sequence Number
---
sequenceDiagram
Client-->Server: Initialize SEQ as 100
Client->>+Server: Length 33, SEQ-100
Client-->>Network: Length 33, SEQ-100
Server->>-Client: ACK-133
Client->>+Server: Length 44, SEQ 133
Server->>-Client: ACK-177
Network->>Server: Length 33, SEQ-100
Note right of Server: ⚠️Drop!
```

> [!Note]
>
> 初始化的 `SEQ` 是隨機產生的。
SEQ 是一個 $2^32$ 的值,最大的值約為 43 億,換句話說一條連線如果傳送了 4GB 的資料,就會遇到溢位,然後就會丟棄該封包。
不管這個連線是長連線還是短時間大量資料傳遞。

```mermaid
---
title: TCP Sequence Number Overflow
---
sequenceDiagram
Client-->Server: Initialize SEQ as 1G
Client->>+Server: Length 1G, SEQ-1G
Client->>+Server: Length 1G, SEQ-2G
Client->>+Server: Length 1G, SEQ-3G
Client->>+Server: Length 1G, SEQ-4G
Client->>+Server: Length 1G, SEQ-1G(Overflow)
Note right of Server: ⚠️Drop!
```

於是就開始使用時間戳記(Timestamp, TS)在每個封包上,這樣即時傳大量的資料,仍不會因為溢位而拒絕封包。

```mermaid
---
title: TCP Sequence Number Overflow
---
sequenceDiagram
Client-->Server: Initialize SEQ as 1G
Client->>+Server: Length 1G, SEQ-1G, TS-1
Client->>+Server: Length 1G, SEQ-2G, TS-2
Client->>+Server: Length 1G, SEQ-3G, TS-3
Client->>+Server: Length 1G, SEQ-4G, TS-4
Client->>+Server: Length 1G, SEQ-1G(Overflow), TS-5
Note right of Server: OK, TS-5 > TS-4
```

要注意的是時間戳記的初始值也是隨機產生的。

#### 問題

Client-A 的請求通過 SNAT 被轉發到 Server,並以時間戳記 TS-A 訪問並結束連線。
Client-B 的請求在送出請求時如果也因為 SNAT 被使用到相同的 Port,此時 Client-B 可能會因為時間戳記不同而被拒絕。

### Forward RTO

- [RFC-5682](https://datatracker.ietf.org/doc/html/rfc5682)
- 相關:[Selective ACK]

![網路延遲,導致錯誤的重新發送,並進而導致 Duplicated ACK](https://i.imgur.com/sVyxmKX.png)

透過在一段時間內等待當下兩個 `ACK` 封包,並發現大於重傳的封包,這代表這是虛假重傳。
從而達到避免單純網路延遲導致的錯誤重送。

![當下兩個 ACK 大於重傳的封包,代表這是虛假重傳](https://i.imgur.com/1GmOIcc.png)

### Fast Open

- [RFC-7413](https://datatracker.ietf.org/doc/html/rfc7413)
- [參考](https://kb.nssurge.com/surge-knowledge-base/technotes/tfo)

![Fast Open 可以在第二次連線後省略握手階段](https://i.imgur.com/SKvBAHH.png)

接收方和發送方同時紀錄 cookie,並在下次連線時忽略握手階段。

### 特殊

一些在指標上看不到的功能。

#### ECN

- [RFC-3168](https://datatracker.ietf.org/doc/html/rfc3168)

使用顯式擁塞通知(Explicit Congestion Notification, ECN)可以測量擁塞的程度。

## 有用指令

查看為什麼 kernel reject 封包(段):
Expand Down
Loading

0 comments on commit ef12932

Please sign in to comment.