はじめに
インターネットに公開しているホームページが突然閲覧できなくなりました。サーバではロードアベレージも低く、負荷はかかっていないようでした。今回の記事ではこの状況下でのボトルネックの確認&対応方法について簡単にまとめてみました。
環境
- CentOS6
- Apache
参考
https://qastack.jp/server/294209/possible-syn-flooding-in-log-despite-low-number-of-syn-recv-connections
https://github.com/hiboma/hiboma/blob/master/kernel/net/net-backlog.md
https://qiita.com/nk_yohn3301/items/515733ed244ee3f35522
https://blog.ssrf.in/posts/linux-backlog-memo/
https://christina04.hatenablog.com/entry/2016/12/31/124314
https://kazuhira-r.hatenablog.com/entry/2019/07/10/015733
http://nishitki.hatenablog.com/entry/2018/11/18/230425
https://qiita.com/smallpalace/items/14ea25ae07178f5ea6bd
https://qiita.com/sato4557/items/f1205bef8dbfe8022832
https://blog.cloudflare.com/jp/syn-packet-handling-in-the-wild-jp/
http://d.hatena.ne.jp/nyant/20111216/1324043063
http://triplesky.blogspot.jp/2014/07/centos6dmesgpossible-syn-flooding-on.html
結論
カーネルパラメータを調整する。以下対策で具体的なパラメータを記載します。
状況
OS側のSyn通信を受け付けるキューのサイズが小さく、カーネルパラメータtcp_max_syn_backlog(ACKを受け取っていない状態のコネクションのキューの保持可能数)を上回るsyn通信があるとOSは SYN Flood攻撃として認識して、SYNパケットを拒否する動作になってしまっていました。そのためインターネットからホームページアクセスができない状況となりました。
ボトルネック調査
ロードアベレージも低く、サーバに負荷がかかっていない場合、ネットワークがボトルネックになっている可能性が高いです。その場合、WAN回線の状況、LANの帯域、ネットワーク境界にあるFWなどもに以上がないか確認する必要があります。また、サーバ側ではログを調べて何かエラーなど出力されていないか確認します。今回はログを調べると/var/log/messagesにpossible SYN flooding on port 443. Sending cookies.
が出力されていました。
対策
DDOSがあるわけでもなくpossible SYN flooding on port 443. Sending cookiesがログに出た場合、以下2つのカーネルパラメータを調整する必要があります。まずはカーネルのsynを受け入れるキューサイズを大きくします。
net.ipv4.tcp_max_syn_backlog
SYN受信後のACK待ちキュー保持数( SYN flood 発生に影響あり)
※単位:キュー数
※Listen ポートでポート( =ソケット) 当たりのSYNを受け付けて、ACKを受け取っていない状態のコネクションのキューの保持可能数のことです。この値を超えたものに関しては、OSは SYN Flood攻撃として認識されて、SYNパケットを拒否することになります。
CentOS7のデフォルト値=512
# sysctl -a | grep backlog
net.ipv4.tcp_max_syn_backlog = 512
例
net.ipv4.tcp_max_syn_backlog = 512→8192
net.core.somaxconn
同時に受け入れるTCP接続のソケット上限
ListneポートでSYNからACK受信し、ESTABになる前の状態を一度に受け入れるTCPセッションのキュー数
※単位:キュー数
※netstat の Send-Q に表示されます。
※syn_backlog とsomaxconn の数字は合わせるのが推奨とされています。
CentOS7のデフォルト値=128
# sysctl -a | grep maxcon
net.core.somaxconn = 128
例
net.core.somaxconn = 128→8192
backlogのキュー長はlisten()時に指定されるbacklog引数の値に設定される。ただし、指定値がsysctlのnet.core.somaxconnより大きかった場合は、net.core.somaxconnの値に切り詰められる。
2つのパラメータについて
https://qiita.com/sion_cojp/items/c02b5b5586b48eaaa469
おそらく、TCP接続したキューと、synを受け付けるキュー。同じ値にしたほうがよさそう。
カーネルパラメータ調整後
十分な量のsynパケットを受け付けるようにすると、今度はApacheの同時接続数がボトルネックになったり、ファイルディスクリプタの数が足りなくなるかもしれません。カーネルパラメータ調整後も一定期間はログなどを定期的に見て異常がないか確認する必要があります。
図解
LinuxカーネルのTCP/IPプロトコルスタック
-
ファイルディスクリプタと、ソケットが入るキューが2つある
-
backlog
- Acceptキューには、完全に確立された接続が含まれていて、アプリケーションはいつでも利用できます。プロセスが accept() を呼び出すと、ソケットはキューから取り出され、アプリケーションに渡されます。 これはファイルディスクリプタを介してユーザープロセスにわたされて接続済みソケットが使えるようになるということ
参照:http://u-kipedia.hateblo.jp/entry/2015/01/01/001135
参照:https://wiki.bit-hive.com/linuxkernelmemo/pg/listen%20backlog%20%E3%80%903.6%E3%80%91
2つのキューが書かれている図
なのでユーザープロセスがaccept()するとこの図のbacklogから、ファイルディスクリプタに関連付けれる
- SYN を受け取ると listen queue へ格納する
- SYN/ACK を返すと同時に accept queue へ移動する
- TCP ソケットは backlog というキューを持つ
- SYN を受け取ったときに格納する Listen Queue と ACK を受け取ったときの Accept Queue がある
- キューの実装は https://elixir.bootlin.com/linux/v4.15/source/include/net/request_sock.h#L50
- SYN_RECV, ESTABLISHED な状態がキューに入っている
- accept(2) されるとキューから削除される
参考情報
TCPセッションの状態遷移
netstatのステータス状態一覧
クライアント側でSYN_SENT、FIN_WAIT_1 などは通常、一瞬しかみれないので、この状態が複数確認できる場合あは、なにかしらおかしい状態になっている。
参照:[改訂新版]プロのためのLinuxシステム構築・運用技術
参照:https://www.atmarkit.co.jp/ait/articles/0207/20/news003.html
TIME_WAIT
「CLOSING」でACKを受けた状態。アクティブ・クローズ後のタイムアウト待ち状態。同じシーケンス番号やポート番号などを再利用しないように、しばらく待ってから(ネットワーク上で遅れていたパケットがこの時間内に到着する可能性があるので、それと衝突しないように待つ)、「CLOSED」へ遷移して終了する。
コネクションの終了待ち状態。しばらく待ったあと、CLOSEDへ遷移して終了する(netstatの表示から消える)
カーネルパラメータ
上記図のキューサイズを変更するのは下記パラメータになる
パラメーター | 内容 | default値 |
---|---|---|
net.core.somaxconn = 65535 | 一度に受け入れられるTCPセッションのキューの数。 TCPのセッション数をbacklogで管理し、それを超えたものはキューに格納される。 このキューの数をここで設定する |
128 |
net.core.netdev_max_backlog = 65536 | パケット受信時にキューに繋ぐことができるパケットの最大数 | 1000 |
net.ipv4.tcp_max_syn_backlog = 65536 | ソケット当たりのSYNを受け付けてACKを受け取っていない状態のコネクションの保持可能数。 この値を超えたものに関してはOSはSYNパケットを拒否することになる。 溢れたパケットはnetstatで確認できる。 ただし、この値はnet.core.somaxconnより大きかった場合、net.core.somaxconnの値を優先する。 |
1024 |
補足
netstatコマンド
netstat -npt
「プロトコル」には、「TCP」、「UDP」といったトランスポート層のプロトコル名が入ります。
- オプションなしで「netstat」を実行した場合UDPは表示されません。
- UDPが表示されない理由は、UDPはコネクションを確立しないためです。
- netstat -a でudpも表示
- 「状態」は、TCPの状態遷移を表しています。
synのキューサイズの確認
SYN_RECVは「ESTABLISHED」になる前の状態)
なのでsynfloodがおきているときに
SYN_RECVでgrepしてwcしたら今のキューの数が見れそう
dropの確認
$ netstat -s
TcpExt:
...
1380 times the listen queue of a socket overflowed
1380 SYNs to LISTEN sockets dropped
...
キューがいっぱいの時は LINUX_MIB_LISTENOVERFLOWS と LINUX_MIB_LISTENDROPS が増加
# nstat -az TcpExtListenDrops
#kernel
TcpExtListenDrops 119 0.0
root@web2:~
# nstat -az TcpExtListenOverflows
#kernel
TcpExtListenOverflows 119 0.0
SYN_cookiesとは
tcpシーケンス番号
ssコマンドなど
httpd.confのListenBacklogディレクティブ
- backlogの指定はhttpd.confのListenBacklogディレクティブ。省略されている。
- MaxClientsいっぱいに接続があると、sommaxの値でsynが保留される。synfloodを起こしている場合、MaxClientsの値も上げる必要がある場合もある
- ListenBackLogはカーネルのtcp_max_syn_backlogの値に加算される。
ListenBackLogディレクティブ
接続待ちキューの加算数を設定します。接続待ちキューの最大数は、Linuxシステムに設定されている接続確立中キューの最大値(tcp_max_syn_backlog)に本ディレクティブの設定値を加算した値となります。加算数には、1から2147483647までを指定できます。
ListenBacklogで設定を行ってもOSによる制限の方が優先され
backlog値の確認
root@web2:~
# ss -tln | grep 443
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 :::443 :::*
listenのSend-Qはbacklogの値。デフオルトだとnet.core.somaxconn = 128の128になっている
例えばnginxではデフォルト511でそのまま511がbacklogの値となる。ミドルによって変わってくる。