Skip to content

UDP GRO forwarding:加速 Subnet Router 與 Exit Node 的 UDP 轉送

用 ethtool 開啟 rx-udp-gro-forwarding、關閉 rx-gro-list,加速 Linux 上 Subnet Router 與 Exit Node 的 UDP 轉送,並讓設定重開機後保留

Updated View as Markdown

在 Linux 上把機器設成 Exit Node(tailscale set --advertise-exit-node)或 Subnet Router,有時會看到這行警告:

Warning: UDP GRO forwarding is suboptimally configured on eth0, UDP forwarding throughput capability will increase with a configuration change.
See https://tailscale.com/s/ethtool-config-udp-gro

服務照樣能用,只是 Tailscale 認為這台機器轉送 UDP 的效率還有改善空間。這篇解釋警告的意思、官方建議的設定、兩個網卡選項的作用、為什麼需要 kernel 6.2,以及怎麼讓設定重開機後保留。

本文以 Tailscale v1.102(2026 年 10 月)與 Linux kernel 6.2 以上為準,改寫自作者的部落格文章〈Tailscale exit node 調校:為什麼要開 UDP GRO forwarding、關 rx-gro-list〉,並補上 Subnet Router 的情境。

警告是什麼意思

Linux 上廣告路由時(--advertise-routes、--advertise-exit-node),tailscale up 和 tailscale set 都會順手檢查這台機器的 UDP 轉送配置(原始碼):檢查通過就安靜地繼續;不符合最佳配置就印出上面那段警告。它只是效率建議,不影響功能。

檢查的順序值得知道:

  1. 先看 TUN 介面有沒有 tx-udp-segmentation(UDP segmentation offload)。沒有的話直接略過、不會警告——這時開不開 GRO forwarding 都用不上。
  2. TUN 支援時,再檢查預設路由那張網卡:rx-udp-gro-forwarding 要開、rx-gro-list 要關。兩個條件任一不符,就發警告。另外也有直接略過的情況:跑 userspace networking 模式時本來就沒有 TUN 可查,檢查整個跳過;預設路由網卡根本沒有 rx-udp-gro-forwarding 這個 feature(虛擬網卡常見)時也會略過。

也就是說:看到警告,代表 kernel 夠新、TUN 支援 USO,只差網卡那兩個開關;沒看到警告不代表已最佳化——可能是 kernel 太舊、跑的是 userspace networking 模式,或網卡根本不支援這些 offload、想設也沒得設。

官方建議的設定

依官方效能文件,Tailscale 1.54 以上搭配 Linux kernel 6.2 以上,就能用 transport layer offload 提升 UDP 吞吐量。機器若擔任 exit node 或 subnet router,要對預設路由那張網卡做以下設定:

NETDEV=$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")
sudo ethtool -K "$NETDEV" rx-udp-gro-forwarding on rx-gro-list off

第一行找出往外連線走的是哪張網卡,第二行開啟 rx-udp-gro-forwarding、關閉 rx-gro-list。

套用前後可以用 ethtool -k 確認(小寫 k 是查詢、大寫 K 是修改):

$ ethtool -k "$NETDEV" | grep -E "rx-udp-gro-forwarding|rx-gro-list"
rx-gro-list: off
rx-udp-gro-forwarding: off

rx-udp-gro-forwarding 的預設值通常是 off。執行設定後應該變成:

rx-gro-list: off
rx-udp-gro-forwarding: on

generic-receive-offload(一般的 GRO)通常本來就是 on,不用動。

兩個選項在做什麼

GRO 與 UDP 轉送

GRO(Generic Receive Offload)讓 kernel 在收封包時,把同一條連線連續到來的小封包合併成一個大封包,再往上層送。後續的路由、防火牆、NAT 都只處理一次,不必對每個小封包各跑一遍。

TCP 很早就有 GRO。UDP 的 GRO 則分兩種情況:封包是給本機 socket 收的,或是要轉送到別的介面去的。exit node 屬於後者:網路上回來的 UDP 封包(例如 QUIC、視訊通話)進了實體網卡,要經過 NAT 轉送到 tailscale0 這張 TUN 介面,再由 tailscaled 加密送回 tailnet 裡的裝置。Subnet Router 同樣有轉送路徑:tailnet 裝置存取區網設備的流量,從網卡進出、經過 NAT 在 TUN 之間轉送——QUIC、影音串流這類大宗 UDP 在同樣的位置受惠。

針對轉送路徑的 UDP GRO,kernel 有兩套做法:

  • rx-gro-list(NETIF_F_GRO_FRAGLIST)把封包串成 skb list,合併後的封包內部仍保留原本每個封包的邊界
  • rx-udp-gro-forwarding(NETIF_F_GRO_UDP_FWD)把封包合併成一般的 UDP GSO 封包:一個大 payload 加上「每段多大」的資訊,送出時再依這個大小切開

第二種是 Alexander Lobakin 在 2021 年送進 kernel 的patch加入的。patch 說明裡寫到,兩個旗標同時開啟時由 fraglist 優先。所以只開 rx-udp-gro-forwarding 不夠,rx-gro-list 還開著的話,走的仍是 fraglist 那條路徑——這就是為什麼要「一開一關」。

為什麼需要 kernel 6.2

合併成 UDP GSO 封包後,下一站是 TUN 介面。TUN 必須能接收這種大封包,否則 kernel 還是得在送進 TUN 前先切回一個個小封包,前面的合併就白做了。

TUN 支援 UDP segmentation offload(USO)是 Andrew Melnychenko 的 TUN/VirtioNet USO patch 系列,在 Linux 6.2 合併。有了它,tailscaled 一次 read 就能從 TUN 拿到一批 UDP 資料,加密、送出也能整批處理。Tailscale 這邊的批次處理與 UDP GSO/GRO 支援,背景可以參考官方部落格的 Surpassing 10Gb/s with Tailscale 與更早的 throughput improvements;kernel 端的總覽見 segmentation offloads 文件。

Tailscale 自己的檢查程式也是照這個順序判斷——這就是前面「檢查的順序」先看 TUN 的原因:TUN 收不了 UDP GSO 大封包時,合併了也得切回去,開不開都用不上,檢查乾脆略過。

讓設定重開機後還在

ethtool -K 改的是執行中的網卡狀態,重開機就會恢復預設。

官方做法:networkd-dispatcher

Tailscale 文件給的是 networkd-dispatcher 的寫法,在網路進入 routable 狀態時執行:

printf '#!/bin/sh\n\nethtool -K %s rx-udp-gro-forwarding on rx-gro-list off \n' \
  "$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")" \
  | sudo tee /etc/networkd-dispatcher/routable.d/50-tailscale
sudo chmod 755 /etc/networkd-dispatcher/routable.d/50-tailscale

照官方文件的建議,先手動跑一次確認沒有錯誤:

sudo /etc/networkd-dispatcher/routable.d/50-tailscale
test $? -eq 0 || echo 'An error occurred.'

這個做法的好處是網卡每次重新變成 routable(例如 networkd 重新設定介面)都會再套用一次。前提是系統有在用 systemd-networkd,而且裝了 networkd-dispatcher(systemctl is-enabled networkd-dispatcher 可以確認)。

沒有 networkd-dispatcher 時:systemd oneshot

用 ifupdown 管網路,或系統沒裝 networkd-dispatcher,可以改寫成開機時跑一次的 systemd service。

先寫一支腳本 /usr/local/sbin/udp-gro-forwarding:

#!/bin/sh
NETDEV=$(ip -o route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
exec /usr/sbin/ethtool -K "$NETDEV" rx-udp-gro-forwarding on rx-gro-list off

這裡把 cut -f 5 換成 awk 找 dev 後面的欄位。cut 的寫法假設輸出是 8.8.8.8 via <gateway> dev <iface> ...,第 5 欄剛好是網卡名稱;如果預設路由沒有 via,欄位位置就會跑掉。

再建立 /etc/systemd/system/udp-gro-forwarding.service:

[Unit]
Description=Enable UDP GRO forwarding for Tailscale subnet routers and exit nodes
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/udp-gro-forwarding

[Install]
WantedBy=multi-user.target

啟用並立即執行:

sudo chmod 755 /usr/local/sbin/udp-gro-forwarding
sudo systemctl enable --now udp-gro-forwarding.service

Type=oneshot 又沒設 RemainAfterExit=yes,所以執行完 systemctl is-active 會顯示 inactive,這是正常的。要確認有沒有成功,看執行結果:

$ systemctl show -p Result -p ExecMainStatus udp-gro-forwarding.service
Result=success
ExecMainStatus=0

最後再用 ethtool -k 看一次,rx-udp-gro-forwarding: on 就完成了。

這個做法只在開機時執行一次。如果網卡之後被重新設定或熱插拔,設定可能會被重設,到時要手動再跑一次 service,或改用 networkd-dispatcher、udev rule 這類跟著介面事件觸發的機制。

Subnet Router 與 Exit Node 都適用

這個調校的作用位置是「接收 UDP、要轉送進 TUN 的那張網卡」,所以兩種情境都適用:

  • Exit Node:tailnet 裝置的所有上網流量經由它出去,QUIC、視訊通話這類大宗 UDP 都在受惠範圍,效果最明顯。
  • Subnet Router:tailnet 裝置存取區網設備的 UDP 流量(QUIC、影音串流這類大宗 UDP)同樣經過這條轉送路徑。

官方檢查與指令針對的是預設路由的網卡。如果 Subnet Router 往區網走的是另一張網卡,把同一行 ethtool 指令用在接收該流量的網卡上也是同樣的機制——效果在「收 UDP 的那張」。

除了 kernel 端的 GRO forwarding,2026 年後續還有 Tailscale 自己資料平面的升級(小封包緩衝、multi-queue、netmap caching),其中 multi-queue 同樣針對 subnet router/exit node 的轉送,見〈2026 年的效能升級:multi-queue 與 netmap caching〉。

常見問題

  • 設定了,警告還在? 確認改的是「預設路由」那張網卡(ip -o route get 8.8.8.8 查),再用 ethtool -k 確認兩個選項的狀態。改完後再跑一次 tailscale set --advertise-exit-node(或 --advertise-routes=...)會再做一次檢查。
  • 雲端 VM 上從來沒看過警告? 虛擬網卡或 TUN 不支援這些 offload 時,檢查會直接略過、不會有警告——這時也沒有可設的東西,不用理會。
  • 需要重啟 tailscaled 嗎? 不用,ethtool 的修改即時生效;重跑 tailscale up 只是為了讓它再檢查一次。
  • 和 BBR 有關係嗎? 沒有。UDP GRO forwarding 加速的是轉送的 UDP;BBR 是 TCP 擁塞控制,只影響本機發起或終止的連線,見下一篇。
  • 想量設定前後的差異? 用 iperf3 量 UDP 吞吐,記得先確認直連/DERP 並控制 datagram 大小(MTU 1280),方法見〈用 iperf3 量 Tailscale 吞吐量的正確方法〉。

參考資料

Navigation

Type to search…

↑↓ navigate↵ selectEsc close