CDN 絕對不只是 Cache — 從 OSI 7 層解析 Cloudflare 架構
BGP & anycast & unimog & QUIC & pingora 技術棧解析
Cloudflare 作為 CDN 霸主不只提供 cache,還兼顧 WAF & 防 DDos ,甚至 Cloudflare Workers 能讓工程師在 edge proxy 中執行 js 代碼,已然成為網路最重要的基建之一。
如此重要基建其架構是什麼呢?一起透過下面問題來探討:
如何將單一 Domain 匹配到不同地理位置的 edge proxy?
CDN 首要功能是依照用戶地理位置,將相同 domain 的請求送到離他近的 edge proxy。
通常一個 Domain 對應一個 public IP,而 Geo DNS 支援相同 Domain 依照地理位置匹配不同 public IP,在 DNS 分層查詢下,會找到 Geo DNS 並用 client IP 解析地理位置後查到 public IP。
但該方法有許多缺點:
1. 假設 Geo DNS 坐落在美國機房,為了找到 public IP 可能先繞到較遠的地方
2. DNS 有快取且 TTL 可能很長,若要替換 public IP 會需要一段時間才生效,發生故障遷移無法即時生效
那有沒有更快,可用性更好的方式?
與其將一個 Domain 綁定不同 public IP,Cloudflare 用 anycast 技術將一個 public IP 映射到不同位置的機器上。
image source: https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/
anycast 如何將一個 public IP 映射到不同位置的機器上?
網路分成 Local Area Network (LAN) & Wide Area Network (WAN),相同區域會用相同網域,例如中華電信 (ISP) 是 36.224.0.0/13,而相同網域下,網線由一個 Autonomous System (AS) 建置與管理,傳輸很快,甚至不用 CDN。
當封包進入 WAN 後,距離拉長,要跨 AS 之間傳輸,不同 AS 管理不同網域,彼此需要 protocol 交換路由資訊 (aka IP Prefix),該 protocol 為 Border Gateway Protocol (BGP)。
image source: https://www.cloudns.net/blog/understanding-bgp-a-comprehensive-guide-for-beginners/
例如中華電信用 BGP 告訴日本 ISP 36.224.0.0/13 是我負責的,當日本 ISP 送 request 到 36.224.1.1 時,會將 request forward 到中華電信的 border router,由該 router 將 request 送到 server 上。
而 anycast 可將一組 IP Prefix 透過 BGP 與不同 AS 註冊在不同的 border router 上,不同 border router 紀錄不同的 routing table,相同的 public IP 會因為來源 AS 不同,forward 到不同 border router,最終到不同 server 上。
Cloudflare 作為獨立 AS,可跟不同 AS 註冊 IP Prefix (e.g 103.21.244.0/22),不同 AS 用當地的 border router 和 edge proxy 負責該網域所有 IP,例如從台灣送 request 到 103.21.244.1,透過 BGP 找到台灣當地的 border router ,最終請求送到台灣當地 edge proxy。
BGP 是 layer 3 protocol,forward request 不需要層層解析,效能好,且可用性更高,例如 103.21.244.1 在日本的 edge proxy 因為機房失火出問題,不用將 domain 換成另一個 public IP,日本 ISP 透過 BGP 會發現台灣有路可通往 103.21.244.1,並 forward 過去,雖然距離變遠,但確保可用性。
anycast + BGP 是完美 Solution 嗎?
anycast 基於 BGP 路由規則將相同 public IP 送往靠近用戶的 server,但 BGP 並不依照地理位置或者封包 RTT 決定路由,而是 AS Path 的成本,而成本主要靠 Admin 配置 (policy based)。
例如 A 國與 B 國網線對接時,A 國若跟很多其他 AS 對接,B 國就會受惠,因此 A 國可能會像 B 國收錢,若封包從 B 國出發,可經由 A 或 C 到 D,假設 C 沒收錢,就會先走 C,即便目的地離 A 更近。
而 Cloudflare 會與 AS 對接 Local AS Path 確保優先權。
note: 詳細 BGP route 規則可參考這裡。
另外 BGP 有劫持問題,駭客可用相同 IP Prefix 與其他 AS 註冊,並設法讓到達他的 AS Path 成本變低,就可能把原本會走到正確 Cloudflare 流量轉到駭客服務上。
image source: https://blog.cloudflare.com/bgp-hijack-detection/
解法是註冊 IP Prefix 時要驗證 AS 身份,類似 HTTPS 的 domain 憑證驗證,但非所有 AS 都做這件事,所以仍有風險,可用 https://isbgpsafeyet.com/ 查你的 ISP 有沒有驗證流程。
另外 anycast 特性讓相同 public IP 在同區域也可有多個 proxy server,因此 TCP 封包可能送往不同 server,導致以下問題:
當 client 與 server 建立 TCP 連線後,為了確保封包的順序性,server kernel 會在記憶體中記錄 TCP 連線 ID 和對應狀態,例如收到哪些封包,下一個封包編號等,若相同 TCP 連線封包送往不同 server 且該 server 沒有對應連線狀態會關閉連線。
如何解決 anycast 單一 public IP 有多台 server 導致 TCP 斷線問題?
一個跨區封包網路路徑為:
1. client 從 ISP gateway 出發,透過 BGP 選到 border gateway
2. border gateway 傳給 NAT,NAT 將 dest ip 從 public ip 轉成 private ip
3. NAT 傳給 private network 的 edge proxy
當 border gateway 發現相同 dest ip 有多條路徑時,會用 ECMP (Equal-Cost Multi-Path) 算法,hash(source ip, source port, dest ip, dest port) % path_cnt 方式 forward,確保相同連線封包往同個 NAT & edge proxy。
但增加或減少 edge proxy,ECMP 算法會不一致,且無法依照 proxy workload 分發。
因此 Cloudflare 使用 XDP (ebpf ) 開發了 unimog Layer 4 Load balancer,讓每個 proxy server 有轉發封包能力,ebpf 能在 user space process 實作 control panel,彈性高,例如用 meglev hashing 跟監控 proxy workload 來調整路由。
且 proxy 有 forward 能力就不需要 NAT 轉發,proxy 可用 Tunneling 將封包封裝一層,修改 dest ip 成 private IP 送出。
image source: https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/
除了 Layer 4 Load balancer,Cloudflare 還在 Layer 4 使用其他技術,也就是 QUIC!
什麼是 QUIC?QUIC 解決什麼問題?
QUIC 是基於 UDP 開發的 Layer 4 新協定,主要搭配 HTTP/3 ,用來解決 HTTP/2 & TCP 的效能問題:
Head-of-Line Blocking
HTTP/2 解決 HTTP/1 Layer 7 Head-of-Line Blocking,不用等當前 request 的 response 就能送下一個 request,可用一個 TCP connection 同時送出多個 request,應用層收到不同 request 對應 response 封包後,會用 stream id 組回完整 payload。
image source: https://juejin.cn/post/6844903984524705800
但 Layer 4 也有 Head-of-Line Blocking ,TCP 視角無法分辨封包屬於不同 request,會將封包視為相同連線且要有序地接收,當 A response 少了一個封包,即便 B response 的 payload 已經完整抵達,只要遺失封包沒到,kernel 層就不會把 B response payload 給 user space process。
QUIC 解法是將 Layer 7 的 stream id 搬移到 Layer 4 解析,每個 stream 封包要有序,跨 stream 不管,此外由於 HTTP/3 在應用層 (i.e 瀏覽器) 可解析封包重組 payload,QUIC 不用在 kernel 維護多個 stream 的封包排序,降低 kernel 記憶體管理複雜度。
image source: https://www.researchgate.net/figure/The-head-of-line-blocking-problem-and-the-stream-based-solution_fig1_344551663
連線建立要握手
TCP 傳資料前,要先 Client SYN => Server SYN-ACK => Client Client-ACK 後才開始傳送資料,而 QUIC 是 client 在發送第一個 SYN 時,連帶把 data 發出去,等於 0-RTT 就可傳輸資料。
不過支援 TLS 會是 1-RTT,例如透過 Diffie–Hellman 算法 :
1. Client => public key => Server
2. Server 用 server’s private key + client’s public key 算出一個 share secret
3. Server => session ticket (i.e 加密的 share secret) + server’s public key => Client
Client 用 client’s private key + server’s public key 算出一樣 share secret,用該 secret 加密 request,連同 session ticket 傳給 server。
image source: https://vocal.com/voip/quic/
此外 session ticket 可儲存在 client 硬碟中,斷線後仍可續用,重連不用交換 public key 算 share secret, 可直接傳資料是真 0-RTT,但風險就是重放攻擊,因此 server side 會定期換產生 session ticket 的加密 key 以及設定 session ticket TTL,並只允許 GET request 可 0-RTT。
除了優化效能,QUIC 也實現重傳跟流量控制機制 ,例如當 server memory buffer 滿了,會叫 client 先不要傳,或者 RTT time 太長也會叫 client 先不要傳新的,等遺漏封包都收到。
傳統 TCP 沒有 stream 概念,相同封包,即便重傳序號也相同,導致收到 ACK 時不知道是第一個封包還是重傳封包的 ACK,算 RTT 會不準。
而 QUIC 順序是 stream 決定,stream 內有獨立 seq,封包 id 可全域 (跨 stream) 遞增,可分辨ACK 是重傳的還是第一個的,RTT 更精準,流量控制更不會誤判。
此外 QUIC 重傳跟流量控制是在 user space 實現的,kernel 就是 UDP,彈性更大,可針對業務需求調整,例如串流情境中,重傳要求不高,但流量要控制好。
image source: https://datatracker.ietf.org/meeting/98/materials/slides-98-edu-sessf-quic-tutorial-00.pdf
除了效能優化,Cloudflare 使用 QUIC 還有什麼好處?
Cloudflare 用 rust 實作了 QUIC 功能 (https://github.com/cloudflare/quiche) 並在實作中發現好處跟挑戰:
好處 — Connection Migration
TCP 連線 ID 是 (src_ip, src_port, dest_ip, dest_port) 一旦用戶切換網路並改變 src ip (e.g 5G => 4G),連線就要重建立,但 QUIC 連線 ID 是隨機編號,儲存在 Client 端,用戶切換網路 CID 一樣就可辨識狀態。
在上面 anycast 架構提到,一般 router 在 Layer 3 會用 ECMP hash(src_ip, src_port, dest_ip, dest_port) 決定路由,當用戶 src ip 改變,相同 CID 仍會 forward 到不同 server,而 Cloudflare 只要在 unimog layer 4 load balancer 加上 CID forward,就能實現用戶換網路不重連的功能。
image source: https://zhuanlan.zhihu.com/p/311221111
挑戰 — Reflection Attacks
QUIC 建構在 UDP 上,而 reflection attacks 是 UDP 常見攻擊,駭客偽造用戶 IP,用該 IP 不斷向 server 請求大量資料,導致用戶網路塞爆,例如 QUIC 的 TLS 要傳 cert + public key 給 client,是一大坨資料,駭客可不斷發送 TLS 請求讓用戶不斷收到資料。
image source: https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/udp-reflection-attacks.html
TCP 用少量資料 hand shake 避免該問題,收到 Client ACK 後才傳 cert + public key,若駭客偽造 IP 是收不到 SYN-ACK 也無法回傳 Client ACK,因此根解仍是 hand shake,server 送一個加密 token 給 client 並要求他回傳,但該方法犧牲 0-RTT 特性,因此 Cloudflare 在嘗試 ECDSA cert 或壓縮 cert 資料。
Cloudflare 除了用 rust 實作 QUIC 之外,還實作了 proxy server — pingora 用來取代 nginx 。
為什麼 Cloudflare 不用 nginx 要自己開發 pingora?
nginx 是 process based 架構,用 socket SO_REUSEPORT system call 讓多個 process bind 相同 port,kernel 依照 hash(src_id, src_port, dest_ip, dest_port) 分配 socket 給 process 處理。
process based 缺點是 share memory 實作複雜且有限制,例如 proxy => original server 的連線不能共用,且 nginx 是 c 寫的,有 memory leak 風險。
Cloudflare 用 rust 寫了一個 thread based 的 proxy — pingora,沿用 nginx 的架構,多個 kernel thread bind 相同 port,每個 thread 有獨立 epoll instance,共享 original server tcp 連線資訊,而在 payload 特大或檔案傳輸情境用 zero copy 避免大量 bytes 從 kernel space copy 到 user space,會吃 CPU。
image source: https://www.linkedin.com/posts/pal-arnab_what-is-pingora-pingora-is-a-new-http-proxy-activity-7106102391397113856-B8rJ/
此外 nginx 有許多開發包袱,例如用 lua script 擴充功能,但 lua 弱型別不好維護,效能也較差,nginx 模組化疊很多功能,有些功能用不到卻會影響效能,benchmark 結果 pingora 比 nginx 吞吐量大,CPU 用量少也較穩定。
Cloudflare 如何防範 DDos?
anycast 本身比 Geo DNS 更能適應 DDos,當駭客用 DNS 解析到 IP 後攻擊,請求會被 BGP 分流到不同地方,屬於 layer 3 分流。
而 layer 3 防 DDos 機制還有 反向路由檢查,DDos 中,為避免收到 server response 會偽造 src ip,因此 layer 3 可檢查 routing table 中是否存在可回去路徑,若沒代表 src ip 是偽造,丟棄封包。
layer 4 的 DDos 攻擊常見的有 SYN flood,TCP 交握 client 送出第一個 SYN,server 收到會在 kernel 裡紀錄 tcp 狀態 (src ip, src port, init seq number etc) 並回傳 SYN-ACK,SYN flood 是傳送大量 TCP SYN 讓 kernel 記憶體塞滿大量狀態。
SYN flood 解法之一是用 SYN cookies,將狀態編碼成一個數字當作 init seq number,放在 ACK 裡面不儲存在記憶體,當 client 收到後要回傳 CLIENT-ACK (seq=init seq number+1),server 收到確定連線建立成功在解碼狀態儲存到記憶體。
此外也會用 eBPF 在 kernel 層插入 hook ,監控 PPS (Packets Per Second) & BPS (Bit Per Second) 調整 layer 4 rate limit 策略。
最終 layer 7 rate limit 不是單純 rule based,是透過 Bot Management 共享全球 proxy 被攻擊經驗,分享分析行為模式結果,當美國被攻擊時,相關數據可同步給日本,相同 bot 就無法攻擊到日本。
image source: https://blog.cloudflare.com/cloudflare-bot-management-machine-learning-and-more/
結論
當從 CDN Server 理解 Cloudflare 可能會認為是一個有 Cache 功能的 edge proxy,但從 OSI 網路架構角度,會發現他做了很多事情:
相信大家看完這篇後,在去看 Cloudflare 的故障報告文章能更好地理解他們提到的技術問題。
References:
https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/
https://blog.cloudflare.com/the-road-to-quic/
https://blog.cloudflare.com/how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet/https://www.cloudflare.com/ru-ru/learning/dns/what-is-anycast-dns/
https://blog.cloudflare.com/async-quic-and-http-3-made-easy-tokio-quiche-is-now-open-source/
https://medium.com/codavel-blog/quic-vs-tcp-tls-and-why-quic-is-not-the-next-big-thing-d4ef59143efd















