Linux 系統殘留大量 php-fpm 執行的 53 udp session

2016-12-06 CentOS

前陣子在某台伺服器發現無法正常的解析網域,導致在呼叫 API 的時候一直失敗,這是一個非常特殊的案例,實際案例解析後其實就是 udp 協定的缺陷

 

問題:

  • 該主機無法對外解析 Domain

 

釐清:

  1. 從該主機可正常交握 TCP/IP
  2. 從該主機可正常運行
  3. 該主機無其他異常
  4. 該主機指定 DNS Server 皆正常 (中華電信/Google/阿里巴巴)
  5. 本機 hosts 解析正常
  6. 使用 dig 出現錯誤訊息

 

分析:

從上述狀況了解到只有 dns 解析出問題,而 dns 是使用 udp 53 協定進行溝通,排除了 dns server 的問題,開始分析本機是否哪裡異常。

 

  • 首先確認本機是否有主動開啟 udp service 被利用漏洞攻擊。

從上述確認為正常的 LNMP 架構,並且沒有主動 Listen udp

 

  • 確認防火牆是否有正常控管 rules;除 80, 443 以外不該對外開放。
  • 確認被動的 udp session

 

從這邊看到問題的來源了,製造者是 php-fpm,而且殘留的數量很可觀

 

從程式架構上去了解發現會主動去 call API 進行溝通,而 API URL 是使用網域網址,而問題的產生點就是該主機位於香港,第一順位的 dns 是使用中華電信的 168.95.1.1,光是 dns 就選擇了跨國的 dns,這是一項錯誤的示範

 

而為什麼選擇了跨國的 dns 會造成這樣的現象?主因是 UDP 協定的缺陷,讓我們重溫一下 UDP協定

udp

UDP 本身是一個非可靠、非連線型的傳輸協定,發送端和接收端不需要建立連線,也不會進行驗證,對於發送端只需要丟出一個封包,而接收端如果收到就回應,沒收到他也不管你

 

針對 UDP 這種特性,小弟都稱他為 射後不理的協定 潮爽der,所以通常 udp 協定都拿來做為近端的溝通,並且速度優於 TCP 非常多。

 

但這個特性又和這個案例有什麼關聯?!因為 udp 適合用於近端網路,但此案例 dns server 設為跨國網路,這就不只近端了已經是非常遠並且複雜的環境。

 

 

當 php-fpm 執行 php 呼叫 API 網域,如果網路環境不佳可能出現以下狀況:

  • DNS 太遠了回應太慢,導致發送端將 udp session close
  • UDP 在傳輸的過程中因為太慢殘留太久被中繼 Route / Gateway 直接回收了,導致 DNS 根本沒有收到這個 udp 連線

 

然而 udp session 就一直留在發送端主機的一直傻傻地等待 DNS Server 回應,或是被放棄的 udp session,就造成了一個死掉的 UDP session

 

你說,不能設定 timeout 嗎!?

UDP 本身並沒有 timeout 可言,因為無法驗證連線,即使有 timeout 也不可靠,所以你看到的 udp 都是 ESTABLISHED。

 

UDP 的詳細狀態解析

好,繼續解析目前殘留在發送端的 udp session 狀態

在 Linux 中 /proc/net/udp 這裡會列出存在的 udp session,ss 工具也是從這邊蒐集資料的

  • sl:kernel hash slot 的 socket
  • local_address:本機發送的 IP:Port
  • rem_address:遠端主機的 IP:Port
  • st:socket 的狀態,不過在UDP協議中似乎沒有甚麼用處
  • tx_queue:在 kernel 中傳出的 udp 封包占用的記憶體用量
  • rx_queue:在 kernel 中進入的 udp 封包占用的記憶體用量
  • tr tm->when retrnsmt:未被UDP協定使用
  • uid:創建此 session 的 uid (497為nginx)
  • timeout:未被UDP協定使用
  • inode:此 socket 占用 inode 的位置
  • ref:計算 socket 數量
  • pointer:struct sock 在記憶體中的位址
  • drops:socket 被丟棄的數量

 

從以上可以看到殘留的 udp session 有些是傳出,有些是進入的 udp session,可能的狀況是出去時延遲了,而回來也延遲了,所以導致兩種狀況都有。

 

探討問題的解決

針對像這種 UDP 協議的缺陷,而 DNS 又是必要的服務,如果無法避免像這樣的網路環境,小弟目前有幾種的建議方式解決

  1. 在近端網路架設一台 DNS Cache Server,專門提供近端伺服器進行解析減少 udp 來往的問題。
  2. 在 Server 中使用 dnsmasq 來 cache 解析過的網址,減少 udp query。
  3. 設定 crontab 定期檢查會產生 udp session 的服務,超過即釋放 buffer 裡面的 udp session

 

針對此案例小弟是使用方案3,並且寫了一隻 udp_max_session 去檢查 php-fpm 所產生的 udp session,當到達一定的數量(100),就進行釋放

詳細可參考 github/shazi7804

 

 

當然這個案例是我個人目前的了解以及解決辦法,也希望大家如果有遇過相同的問題可以提供更好的解決方式或與我討論!

 

 

 

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

彙整

分類

open all | close all

License

訂閱 Mr. 沙先生 的文章

輸入你的 email 用於訂閱