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 出現錯誤訊息
$ dig www.google.com.tw
dig isc_socket_bind

 

分析:

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

 

  • 首先確認本機是否有主動開啟 udp service 被利用漏洞攻擊。
$ ss -tunl
tcp    LISTEN     0      128     127.0.0.1:9000    *:*
tcp    LISTEN     0      50      *:3306            *:*
tcp    LISTEN     0      128     *:80              *:*
tcp    LISTEN     0      128     *:22              *:*
tcp    LISTEN     0      128     *:443             *:*

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

 

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

udp        0      0 1x9.8x.2x7.1x6:53678        168.95.1.1:53               ESTABLISHED 29519/php-fpm
udp        0      0 1x9.8x.2x7.1x6:34094        168.95.1.1:53               ESTABLISHED 29520/php-fpm
udp        0      0 1x9.8x.2x7.1x6:41134        168.95.1.1:53               ESTABLISHED 29518/php-fpm
udp        0      0 1x9.8x.2x7.1x6:37038        168.95.1.1:53               ESTABLISHED 7725/php-fpm
udp        0      0 1x9.8x.2x7.1x6:58542        168.95.1.1:53               ESTABLISHED 7736/php-fpm
udp        0      0 1x9.8x.2x7.1x6:56622        168.95.1.1:53               ESTABLISHED 29516/php-fpm
udp        0      0 1x9.8x.2x7.1x6:49838        168.95.1.1:53               ESTABLISHED 29528/php-fpm
udp        0      0 1x9.8x.2x7.1x6:43438        168.95.1.1:53               ESTABLISHED 1619/php-fpm
udp        0      0 1x9.8x.2x7.1x6:34222        168.95.1.1:53               ESTABLISHED 29531/php-fpm
udp        0      0 1x9.8x.2x7.1x6:56878        168.95.1.1:53               ESTABLISHED 1556/php-fpm
udp        0      0 1x9.8x.2x7.1x6:51502        168.95.1.1:53               ESTABLISHED 29526/php-fpm
udp      552      0 1x9.8x.2x7.1x6:32942        8.8.8.8:53                  ESTABLISHED 7721/php-fpm
udp        0      0 1x9.8x.2x7.1x6:42414        168.95.1.1:53               ESTABLISHED 29523/php-fpm
udp        0      0 1x9.8x.2x7.1x6:35886        168.95.1.1:53               ESTABLISHED 7731/php-fpm
udp        0      0 1x9.8x.2x7.1x6:42030        168.95.1.1:53               ESTABLISHED 7740/php-fpm
udp        0      0 1x9.8x.2x7.1x6:45998        168.95.1.1:53               ESTABLISHED 29531/php-fpm
udp        0      0 1x9.8x.2x7.1x6:34734        168.95.1.1:53               ESTABLISHED 7723/php-fpm
udp        0      0 1x9.8x.2x7.1x6:45102        168.95.1.1:53               ESTABLISHED 29523/php-fpm
udp        0      0 1x9.8x.2x7.1x6:59694        168.95.1.1:53               ESTABLISHED 7735/php-fpm
udp        0      0 1x9.8x.2x7.1x6:53294        168.95.1.1:53               ESTABLISHED 7723/php-fpm
udp        0      0 1x9.8x.2x7.1x6:57134        168.95.1.1:53               ESTABLISHED 29531/php-fpm
udp      552      0 1x9.8x.2x7.1x6:56366        168.95.1.1:53               ESTABLISHED 29517/php-fpm
udp        0      0 1x9.8x.2x7.1x6:58926        168.95.1.1:53               ESTABLISHED 29523/php-fpm
udp        0      0 1x9.8x.2x7.1x6:39854        168.95.1.1:53               ESTABLISHED 4915/php-fpm
udp      552      0 1x9.8x.2x7.1x6:39086        8.8.8.8:53                  ESTABLISHED 4917/php-fpm
udp      552      0 1x9.8x.2x7.1x6:52910        168.95.1.1:53               ESTABLISHED 29533/php-fpm
udp      552      0 1x9.8x.2x7.1x6:43694        168.95.1.1:53               ESTABLISHED 7729/php-fpm
udp      552      0 1x9.8x.2x7.1x6:55214        168.95.1.1:53               ESTABLISHED 7733/php-fpm
udp      552      0 1x9.8x.2x7.1x6:41902        8.8.8.8:53                  ESTABLISHED 1555/php-fpm
udp        0      0 1x9.8x.2x7.1x6:48686        168.95.1.1:53               ESTABLISHED 29523/php-fpm
udp        0      0 1x9.8x.2x7.1x6:42798        168.95.1.1:53               ESTABLISHED 1618/php-fpm
udp        0      0 1x9.8x.2x7.1x6:37678        168.95.1.1:53               ESTABLISHED 7730/php-fpm

 

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

$ ss -tun | grep udp | grep php-fpm | wc -l
6312

 

從程式架構上去了解發現會主動去 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 工具也是從這邊蒐集資料的

$ cat /proc/net/udp

sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops
0: 3A226F1A:C8DC 0B50100A:0035 01 00000000:00000228 00:00000000 00000000   497        0 160696905 2 ffff880134b3f040 0
0: 3A226F1A:D6DC 0B50100A:0035 01 00000000:00000000 00:00000000 00000000   497        0 156340394 2 ffff8800816db140 0
0: 3A226F1A:BE5C 0B50100A:0035 01 00000000:00000000 00:00000000 00000000   497        0 156073421 2 ffff88020bb0b500 0
1: 3A226F1A:E1DD 0C50100A:0035 01 00000000:00000228 00:00000000 00000000   497        0 165299140 2 ffff8800d42363c0 0
2: 3A226F1A:B75E 0B50100A:0035 01 00000000:00000228 00:00000000 00000000   497        0 152373920 2 ffff880216be7740 0
3: 3A226F1A:E6DF 0B50100A:0035 01 00000000:00000000 00:00000000 00000000   497        0 159659260 2 ffff88014df260c0 0
  • 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

 

 

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

 

 

 

One Reply to “Linux 系統殘留大量 php-fpm 執行的 53 udp session”

  1. pizida表示:

    感谢博主的分享!这几天一直遇到这个问题。
    虽然可以通过crontab重启php-fpm解决,但没有找到问题根源。

給 Mr. 沙先生一點建議

彙整

分類

展開全部 | 收合全部

License

訂閱 Mr. 沙先生 的文章

輸入你的 email 用於訂閱