Skip to content

BBR:擁塞控制在 Tailscale 節點上的實際效果

在 Tailscale 節點上啟用 Linux 的 BBR 擁塞控制前,先弄清楚它只影響本機發起或終止的 TCP 連線,對被 Subnet Router 與 Exit Node 轉送的流量沒有影響

Updated View as Markdown

網路上不少文章會叫你在 Linux 的 Exit Node 上開 BBR,說「出口會變快」。先把結論寫在前面:TCP 擁塞控制只作用在由本機發起或終止的 TCP 連線上。kernel 模式下的 Subnet Router 與 Exit Node 只是轉送封包,被轉送連線的擁塞控制在兩端裝置上——在 exit node 上開 BBR,並不會讓經過它的流量變快。例外是走 DERP 中繼的隧道與 userspace networking 模式:這兩種情況下機器的 kernel 會親自經手 TCP 連線,後面會說明。

本文以 Tailscale v1.102(2026 年 10 月)為準;netstack 的行為以 v1.102.5 的原始碼查證。Linux 端的事實以 kernel 原始碼與文件為準。

BBR 是什麼

BBR 是 Google 開發的 Linux TCP 擁塞控制演算法,2016 年進入 kernel(Linux 4.9 起可用)。大多數發行版的預設是 CUBIC。

兩者的差異,照 kernel 原始碼註解的說法:BBR 根據 ACK 估計的交付速率(delivery rate)計算送出速率,核心演算法不直接對丟包或延遲做反應;CUBIC 則是以丟包為主要訊號調整擁塞視窗的 loss-based 演算法。在高延遲、會丟包的路徑上,BBR 通常不會像 CUBIC 一樣把速率砍到很低——這是它常被推薦的原因。

先弄清楚:BBR 影響哪些連線

兩個原則先記住:

  1. 擁塞控制是 TCP 端點的行為——只有「發起」或「終止」這條 TCP 連線的機器,它的擁塞控制才有作用,轉送封包的機器不算端點。
  2. 每個方向各看送出方——同一條連線,下載方向的速度由伺服器那端的演算法決定,上傳方向由用戶端那端決定。所以在某台機器開 BBR,影響的是這台機器送出的資料:從它下載才受惠,上傳給它則看對方的演算法。

把這兩個原則套到 Tailscale 節點上,逐種流量看:

  • kernel 模式(預設)的 Subnet Router/Exit Node 只是轉送 IP 封包:經過它們的 TCP 連線,端點是 tailnet 裝置與目的地伺服器,擁塞控制在兩端裝置上,跟這台機器無關。假設筆電透過家裡的 exit node 上 YouTube,這條 TCP 連線的速度由筆電和 YouTube 伺服器的擁塞控制決定。
  • 節點之間直連時走 WireGuard(UDP):隧道本身沒有 TCP 擁塞控制這回事。
  • 經 DERP 中繼時:tailscaled 和 DERP 伺服器之間是一條 kernel 的 TCP 連線(DERP 走 HTTPS、通常是 443)。這條連線上節點送出的方向——相當於 tailnet 裝置上傳出去的隧道流量——會受本機 kernel 擁塞控制影響;下載方向則由 DERP 伺服器決定。節點在 CGNAT/嚴格 NAT 後面打不通直連時就會走這條路,這是 BBR 在 exit node 上可能真的有感的情境。用 tailscale status/tailscale ping 檢查,輸出出現 relay "xxx" 或 via DERP(xxx) 就是走中繼。
  • 由 tailscaled 的 netstack 終止的連線(Tailscale Serve、Funnel、Tailscale SSH 的 tailnet 側):TCP 在 tailscaled 內部的 netstack 終止,kernel 的 sysctl 影響不到它。而且 tailscaled 在原始碼裡明確把 netstack 的擁塞控制寫死成 reno(註解說明是為了避開 gVisor cubic 在擁塞視窗計算上的整數溢位問題,見 gvisor#11632)——所以這一側連「改擁塞控制」的選項都沒有。
  • userspace networking 模式下轉送:連線被拆成兩段——tailnet 裝置↔節點這段由 netstack 終止(reno,sysctl 管不到);節點↔目的地這段是 tailscaled 用一般 kernel socket 撥出去的 TCP(原始碼裡的 forwardTCP),節點送出的方向受 kernel 擁塞控制影響。Serve 反向代理到非 loopback 的後端時,後端那段同理。

整理成表(「有」都指這台機器送出的方向):

流量 kernel BBR 有沒有影響
節點上由 kernel 終止的服務(sshd、nginx 等) 有(別人從節點下載時)
節點自己對外發起的 TCP 有(節點上傳的方向)
經 Subnet Router/Exit Node 轉送的連線 沒有(端點在兩端裝置)
節點間隧道,直連(WireGuard UDP) 沒有(走 UDP)
節點間隧道,經 DERP 中繼 有(DERP 走 kernel TCP)
Serve/Funnel/Tailscale SSH 的 tailnet 側 沒有(netstack 固定 reno)
userspace 模式轉送的對外側 有(tailscaled 用 kernel TCP)

所以「在 exit node 上開 BBR」真正的效果是:這台機器送出的 TCP 變得抗丟包——機器自己的對外上傳(系統更新、推備份、拉容器映像檔)、別人從機器上直接跑的服務下載;直連轉送的流量不變;走 DERP 中繼時,上傳方向的隧道流量受惠。如果你的 exit node 直連通暢,開 BBR 對 tailnet 裝置多半沒有可感知的差異。

啟用方式

先確認 kernel 支援。BBR 從 Linux 4.9 就有(現在的發行版都遠超過),但可能編成模組還沒載入:

sudo modprobe tcp_bbr
sysctl net.ipv4.tcp_available_congestion_control

依 kernel 文件,tcp_available_congestion_control 列出已註冊的演算法,有些以模組形式存在但尚未載入——清單裡沒有 bbr 的話,就是模組沒載入(或核心沒編)。

之後在 /etc/sysctl.d/ 設 tcp_congestion_control=bbr 時不必煩惱模組:kernel 找不到指定的演算法時會自動以 request_module 載入對應模組(原始碼裡的 tcp_ca_find_autoload),所以通常不需要另外寫進 /etc/modules-load.d/;上面的 modprobe 只是當下先確認模組存在、方便檢查而已。

把設定寫進 /etc/sysctl.d/99-bbr.conf:

net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

套用並確認:

sudo sysctl -p /etc/sysctl.d/99-bbr.conf
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.default_qdisc

net.ipv4.tcp_congestion_control 顯示 bbr 就生效了。

為什麼要一起設 fq:照 kernel 原始碼註解的說法,BBR 建議搭配 fq 佇列管理(man tc-fq)——這樣 pacing 的工作交給 qdisc;沒有 fq 時 TCP 會退回每個 socket 一個高解析度計時器的內建 pacing,比較耗資源。所以 fq 不是硬性必要,而是讓 BBR 跑得省力的標準搭配。

兩個小提醒:

  • 擁塞控制是新連線套用:已建立的連線不受影響,而且既有服務的 listener 是在舊設定下建立的,繼續收連線會沿用舊演算法——長跑的服務(nginx、sshd 等)要重啟才會真正用上 bbr。
  • 只想給特定服務用的話,也可以不動全系統預設,用 setsockopt(TCP_CONGESTION) 在應用程式層指定(iperf3 就有 -C bbr 這個參數),對照測試時很方便。

用 iperf3 驗證

以下提供兩個測法,實際數據請自行量測——效能結論跟硬體、路徑、對端有關,本文不附任何特定數字。通用的量測方法(先確認直連/DERP、MTU 1280、CPU 瓶頸判讀、參數速查)見〈用 iperf3 量 Tailscale 吞吐量的正確方法〉。

測法一:轉送的流量(預期沒有差異)

  1. 在節點 A 上啟用 BBR 之前,從 tailnet 裝置 B 啟用 A 當 exit node,對外部的 iperf3 伺服器量一次:iperf3 -c <server> -t 30(iperf3 預設是 client 上傳、由 B 送出資料)
  2. 在 A 上啟用 BBR,B 重複測量幾輪

這條 TCP 連線的擁塞控制在 B 和外部伺服器上,A 只是轉送——前後數字應該在同一個區間內浮動。想在這個測試裡看到 BBR 的效果,要開 BBR 的是送出資料的那台(B),不是 exit node。

測法二:節點自己的服務(預期有差異)

要量到 A 的擁塞控制,資料必須由 A 送出,所以要用 -R(reverse,改由 server 送):

  1. 在節點 A 上跑 iperf3 -s
  2. 從 tailnet 裝置 B 直連 A 的 Tailscale IP:iperf3 -c <A 的 Tailscale IP> -R -t 30——-R 讓 server(A)送出資料,A 的擁塞控制作用在送出方向
  3. 給路徑加一點丟包,讓擁塞控制的差異顯現:丟包要加在送出方 A 的實體網卡上(sudo tc qdisc add dev "$NETDEV" root netem loss 1%);加在 B 上只會丟到 ACK,效果不同
  4. 對照 iperf3 -c <A 的 Tailscale IP> -R -t 30 -C cubic 與 -C bbr 各量一輪:-C 指定這次連線的擁塞控制,而且 client 的設定會一併傳給 server 套用,所以不必改 sysctl,也不受 A 上既有 listener 繼承舊演算法的影響
  5. 測完記得清掉:sudo tc qdisc del dev "$NETDEV" root

順帶一提,轉送 UDP 的效能(exit node、subnet router 的大宗流量)要靠 UDP GRO forwarding 那篇的調校,跟 BBR 是兩件獨立的事。2026 年後續還有一批資料平面的升級(小封包緩衝、multi-queue、netmap caching),見〈2026 年的效能升級:multi-queue 與 netmap caching〉。

常見問題

  • 開了 BBR,經過 exit node 的速度變快了? 先確認三件事:是不是同時做了別的變動——最常見的是一起設了 UDP GRO forwarding(真的會加速轉送的 UDP);是不是量測誤差;以及當時是否走 DERP 中繼(用 tailscale ping 看得到,走中繼時上傳方向的隧道流量確實會受 BBR 影響)。直連轉送的 TCP 不受本機擁塞控制影響。
  • tcp_available_congestion_control 裡沒有 bbr? sudo modprobe tcp_bbr 後再看;模組載入後就會出現。還是沒有就是核心沒編進 BBR(少見)。
  • 不用 fq 可以嗎? 可以,不是硬性必要;BBR 會退回內建 pacing,只是每個 socket 一個高解析度計時器、比較耗 CPU。
  • Serve 的流量開 BBR 有幫助嗎? 沒有——Serve(和 Funnel、Tailscale SSH)的 TCP 連線由 tailscaled 的 netstack 終止,擁塞控制固定是 reno,kernel 的 sysctl 影響不到。

參考資料

Navigation

Type to search…

↑↓ navigate↵ selectEsc close