iptablesで各種DDoSから身を守る

本日幼稚園児以来の食あたりになりました。
コドラでもなんともないのに、お酒の飲み過ぎ以外でキラキラしたのはものすごい久しぶりです。

さて技術のなさを客に責任転嫁して某ホスティング会社が話題になっていますね。

これ一番の問題は自分のホストがDDoSによって直接ダウンやフラッピングしてしまうことでしょう。
共用サーバなら打つ手なしですが、VPSや専用サーバの場合はiptablesである程度自衛することができます。

各社フィルタリングをしていると発表していますが、内容を明かせないのはセキュリティ的しかたないですよね。
自社ではDDoS対策にiptablesを使っているということもあり、可能な範囲で手法を公開しようと思います。

方法としてはiptablesのhashtablesを使います。
iptablesはフラグやステータス、srcIPやdstIPなど条件を細かくしてハッシュテーブルを作ることができます。
これに対してバーストや最大値などの条件を上回った場合に、例えばDROPなどの動作が指定できます。

つまり特定のIPからSYNのステータスが一定数以上増えた場合、または1秒で指定した回数以上リクエストが来た場合はDROPするという処理ができます。
通常であればSYNをDROPした場合再送までアタック元は待つはずなので、素直にSYN,ACKを返すより相手の手が止まるという効果も期待できるかもしれません。

というように実に簡単なロジックなのですが、こんなこともできないんですかね。

さて具体的な内容です。
なお俺の考えた最強のを参考にさせてもらっています。

iptables -N SYN_FLOOD
iptables -i $IF_EXT -A SYN_FLOOD -p tcp --syn \ # 対象はExternalのIF
         -m hashlimit \
         --hashlimit 100/s \
         --hashlimit-burst 25 \
         --hashlimit-htable-expire 30000 \
         --hashlimit-mode srcip \
         --hashlimit-name t_SYN_FLOOD \
         --hashlimit-htable-max 32768 \
         -j RETURN

# 制限を超えたらSYNはDROP
iptables -i $IF_EXT -A SYN_FLOOD -j LOG --log-prefix "[ATTACK]SYN_FLOOD:" --log-level=debug
iptables -i $IF_EXT -A SYN_FLOOD -j DROP

# SYNフラグはSYN_FLOODチェーンを通す
iptables -i $IF_EXT -A INPUT -p tcp --syn -j SYN_FLOOD

・バーストの上限を制限すること
・合計値の上限を制限すること
・さらにこれらの情報をタイムアウトまで蓄積すること、タイムアウトをもってリセットすること

一般的なウェブサービスではアプライアンスのFWで十分でしょうが、広告配信となるとDDoSと区別がつかないので、手で調整できるiptablesのほうが融通がきくかもしれません。

またフラグやプロトコルを変えれば、例えばHTTP DoSとかにも有効です。

一つ注意しなければいけないのはhtable-maxです。
いずれかに定義した上限値を超えたら問答無用でDROPという条件なので、htable-maxも超えたらDROPされてしまいます。
かといって上限がなければ不測の事態にメモリを食いつぶしてしまいます。
接続元クライアントが多い場合はかなり多めに定義しておくといいでしょう。

ここまで書いておいてiptablesでゴリゴリ処理するのはメモリがすくないVPSだと荷が重いなと思いました。
エンドユーザが頑張るところっじゃなですね。

LAGを過信しちゃいけないよ

なんかちょっと昭和臭いですが、ウェブ広告連載シリーズのインターバルとして息抜きで書こうと思いつきました。

どの現場も冗長性と帯域確保のためにリンクアグリゲーションを使っていると思いますが、LAGとしての合計帯域は余裕なはずなのにポートフルで障害が起きたことがあります。

LAGの概念としては1G+1G=2Gとか1G+1G+1G+1G=4Gと考えがちですが、実際はMACアドレスやIPアドレスなどを元にしたハッシュテーブルからスイッチがポートを分散しているだけであって、特定の条件では使用されるポートがかたより、一つのインターフェイスが上限値となってしまう場合があります。
多数のホスト同士の通信ではほとんど問題はないのですが、数の少ない特定のホスト同士で大容量の通信が発生した場合に問題が出ます。

うまく分散する場合

一方条件が悪いときのLAG
このように大容量のトラフィックが特定のホスト同士で発生し、なおかつ分散が偏るとポートフルとなり通信が正常に行えず、障害になってしまいます。
LAGで束ねた本数を実効帯域として考えるのは間違いでした。

やはり確実なのは1Gのスイッチなら10G、10Gなら40Gのアップリンクを使って太い線でつなげることです。
ただし、その際注意しなければいけないのはスイッチ内部のバックプレーンの容量です。

スイッチのポートは複数ポートごとにASICで束ねられており、さらASIC同士がバスで接続されています。
表面上の帯域は問題なくても、バックプレーンが溢れることがよくあります。
ASICの性能やバックプレーン能力、構造などは自信をもってスイッチを作っているメーカーなら仕様がすべて公開されていますし、代理店やSEに聞いても教えてもらえます。
すべて材料を集めればどの程度の処理能力があるか、どう対策すべきが見えてくるはずです。

スイッチも装置であり生ものなので、ちゃんと考えて使ってあげる必要がありますね。

ウェブ広告のインフラについて(第3回) vyattaのチューニング

梅雨入り前の微妙な天気が続きますね。

今回は前回の基本構成で紹介した、vyattaに実際どういったチューニングを施しているのかと愚痴を書いていきます。

自社でのサービス系のトラフィックのほとんどはLVSから入りvyattaから出て行きます。
vyattaはLVSを通った戻りのトラフィックだけでなく、多数あるDSPへのビッドリクエストも処理しています。
また、グローバルIP節約のためにIPマスカレードをしています。

実は自社のネットワークではここがコアスイッチに次いで重いのですが、CPUが i7 870 メモリ16G NIC 82574L 2口のサーバでピーク時に上り 500Mbps 200K/pps 程度を処理しています。
ただbufferbloatやマイクロバーストの影響と思われる送信キューの再キューが発生してしまっています。これは今度の課題です。

現状使っているハードウェアがかなりショボイので、箱を変えるだけで10GクラスのIPマスカレードもvyattaでいけると確信しています。
そもそもIntelに言わせると、82574LなんてNICじゃなくてPHYだ!だそうで…。

では具体的なチューニングを紹介していきます。

リングバッファを増やす

82574Lに限らずIntelのNICはTx/Rx共に最大値が4096ですが、デフォルトだと256固定になっています。
これにより、受信の場合だとTCPスタックのキューに入る前にリングバッファが溢れてパケットが破棄されてしまいます。

ただバッファも多ければいいというわけではなく、bufferbloatやバッファが増えることによりレイテンシーが低下します。
パフォーマンスを求めるなら、NAPIのチューニングも必要になります。
難しいことはいつもの次回以降に。

デフォルトの状態です。

ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:		4096
RX Mini:	0
RX Jumbo:	0
TX:		4096
Current hardware settings:
RX:		256
RX Mini:	0
RX Jumbo:	0
TX:		256

増やしてみます。
この際リンクダウンが発生するので注意が必要です。

ethtool -G eth0 rx 4096 tx 4096

なお現状に適正なリングバッファの値を調べることはかなり面倒です。
闇雲に増やしてもマイクロバーストの原因となってしまいます。
リングバッファが常に溢れていないか、またはフルになった形跡を確認する必要があります。
これについては確証は得ていないのですが、それっぽい方法は調べてあるので次回以降です。

RFS/RPS

82574LはとてもいいNICなのですが、NICのインタラプトが以下のようになっています。

 50:   81480180          0          0          0   PCI-MSI-edge      eth0-rx-0
 51:    7064255          0          0          0   PCI-MSI-edge      eth0-tx-0
 52:       4786          0          0          0   PCI-MSI-edge      eth0
 53:          2   77843270          0          0   PCI-MSI-edge      eth1-rx-0
 54:          1    1888030          0          0   PCI-MSI-edge      eth1-tx-0
 55:          8          0          0          0   PCI-MSI-edge      eth1

一応マルチキューになってはいますが、Rx/Txが単体で分散していないため、どうして特定のコアに負荷が集中します。
これを分散する為にRFS/RPSを利用しています。

なお評価中のi350ではRx/Txをモジュールに与える値で変更できます。
以下は一例です。

  65:          7          0          0          0          0          0  IR-PCI-MSI-edge      eth2
  66:   47472804          0          0          0         21         27  IR-PCI-MSI-edge      eth2-rx-0
  67:   49140306          0          0          0          0          0  IR-PCI-MSI-edge      eth2-rx-1
  68:         31          0         29   68618984          0          0  IR-PCI-MSI-edge      eth2-rx-2
  69:    9835040          0          0          0          0          0  IR-PCI-MSI-edge      eth2-tx-0
  70:         26          0          0          0    9837578          0  IR-PCI-MSI-edge      eth2-tx-1
  71:         24          0          0   10782440          0          0  IR-PCI-MSI-edge      eth2-tx-2
  72:   17034513          0          0          0          0          0  IR-PCI-MSI-edge      ahci
  73:          1          0          0          0          0          0  IR-PCI-MSI-edge      isci-msix
  74:          0          0          0          0          0          0  IR-PCI-MSI-edge      isci-msix
  75:          7          0          0          0          0          0  IR-PCI-MSI-edge      eth3
  76:         25          0          0         11         35   27880083  IR-PCI-MSI-edge      eth3-rx-0
  77:    9845213          0          0          0          0          0  IR-PCI-MSI-edge      eth3-rx-1
  78:         22    9845071          0          0          0          0  IR-PCI-MSI-edge      eth3-rx-2
  79:    9825399          0          0          0          0          0  IR-PCI-MSI-edge      eth3-tx-0
  80:         23          0    9825377          0          0          0  IR-PCI-MSI-edge      eth3-tx-1
  81:    9825424          0          0          0          0          0  IR-PCI-MSI-edge      eth3-tx-2
  82:          3          0          0          0          0          0  IR-PCI-MSI-edge      eth0
  83: 1383727826          0          0          0          0          0  IR-PCI-MSI-edge      eth0-rx-0
  84:         55          0         71        155 1302104857          0  IR-PCI-MSI-edge      eth0-rx-1
  85: 1324395997          0          0          0          0          0  IR-PCI-MSI-edge      eth0-rx-2
  86: 1830013044        164          0          0          0          0  IR-PCI-MSI-edge      eth0-tx-0
  87:        262         75          0          0          0 1928280549  IR-PCI-MSI-edge      eth0-tx-1
  88: 1883840698          0          0          0          0          0  IR-PCI-MSI-edge      eth0-tx-2
  89:          3          0          0          0          0          0  IR-PCI-MSI-edge      eth1
  90:         44          0   62434654          0          0          0  IR-PCI-MSI-edge      eth1-rx-0
  91:    9853611          0          0          0          0          0  IR-PCI-MSI-edge      eth1-rx-1
  92:    9840908          0          0          0          0          0  IR-PCI-MSI-edge      eth1-rx-2
  93:         21    9825401          0          0          0          0  IR-PCI-MSI-edge      eth1-tx-0
  94:    9825431          0          0          0          0          0  IR-PCI-MSI-edge      eth1-tx-1
  95:    9825440          0          0          0          0          0  IR-PCI-MSI-edge      eth1-tx-2

上記の場合パラメータはこんな感じです。CPUはXeon E5-1650v2なので6コアを意識しています。

options igb IntMode=2,2,2,2 RSS=3,3,3,3 QueuePairs=0,0,0,0

さて脱線しましたが、ついでにeth0とeth1でflow entryを使い切るように設定します。
国内で有名なコピペ設定でもいいと思いますが、ここは海外含めて事例を調べてこう設定してみました。
この部分に関してはまだカーネルの実装までは追っていませんので確証がもてません。

echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 16384 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
echo "f" > /sys/class/net/eth1/queues/rx-0/rps_cpus
echo 16384 > /sys/class/net/eth1/queues/rx-0/rps_flow_cnt
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

カーネルをアップグレード

公式のコミュニティ版だとカーネルが古いためVyatta4People.Org版(?)にアップグレードして使っています。
設定はそのままでコマンド一発でアップグレードすることができます

vyatta@rt4:~$ add system image http://ftp.het.net/iso/vyatta/vc6.7/images/vyatta-livecd-redding-20130712-amd64.iso

 CoDel等、最近になって野心的に実装されたおいしい機能を試すためです。

conntrack tableを正確に設定する

ここがIPマスカレードの一番の肝です。
まとめエントリーはこちらになります。

メモリ16Gで以下の設定で運用しています。

 expect-table-size 196608
 hash-size 23576
 table-size 196608

実メモリから逆算することにより限界値が明確になります。
expect-table-sizeは自社の使い方だとほぼ使われることがありませんが、メモリも余裕があるので保険で同じ値をいれてあります。

conntrackタイムアウトを短くする

conntrack tableは実メモリの制限を受けるため、不要なものはさっさとconntrack tableから削除してしまします。

 timeout {
     icmp 3
     other 600
     tcp {
         close 10
         close-wait 1
         established 10
         fin-wait 10
         last-ack 30
         syn-recv 60
         syn-sent 5
         time-wait 3
     }
     udp {
         other 30
         stream 10
     }
 }

vyattaに限らずconntrack tableは特定の環境でかなりの頻度で溢れます。
少ないデフォルト値を増やして対処できればいいですが、消えないのにタイムアウトが長めになっている場合もあります。
どう対処したかは結構前にかいたこの辺のテキストにまとまっています。

愚痴

分裂したようなんですがどうなんでしょうか…。

次回はたぶんLVSのチューニングです。

 

ウェブ広告のインフラについて(第2回) 基本構成

まだ二回なのであまり深く突っ込んだ話はしません。

今回は自社DCのNW構成と、その際の注意していることなどを書いていこうと思います。

下の図をご覧ください。

実際はもう少し複雑ですが、LVS-DR構成を利用したシンプルな作りになっています。
入社直後チューニングノウハウがない時期にLVS-NATで運用していましたが、いきなり負荷でLVSがつぶされた経緯があり、現在と比べてかなり小さい規模でしたが思い切ってLVS-DRに切り替えました。
今でもその流れを継いでいます。

このように外から入ってくるギガ線のトラフィックをすべてLVSで受けて、内部のLVSでnginxから各APサーバに分散し、DRでデフォルトゲートウェイのVyattaから投げ返しています。

LVS-DRを表のLBに使ってるとかマジキチと自分でも思うんですが、使い始めると案外なんとかなってしまうもので、高い箱物をアテにしていないので、予算とか読めない事業予測にとらわれないスケーラブル()なインフラが結果的にできてしまいました。
CPUはいい仕事してくれます。

FWどうしてんだよ!とかは次回以降で!

しかしLVS-DR構成もデメリットとまでは言いませんが、運用に工夫が必要です。

スイッチの配置と挙動を考慮する

何も考えずに配線すると簡単にUnicastがFloodingします。
こちらのエントリーを参考にしてください。
2ラックでサーバを両ラックに分割して配置など特に要注意です。
結局スイッチまたぎの問題は解決できず、pingを定期的に実行するサーバを運用しています。

また以下の図のようにスイッチ間のトラフィックも偏りがでないように、実際はL2コアスイッチ的なものを一台はさんで配置しています。

ブロードキャストドメインの制限がある

LVS-DRはブロードキャストドメインの制限のために、スケールに限界があります。
現在は1ブロードキャストドメインにおとなしく納まる台数ですが、今後はもう1セットLVSを用意してDNSで分散するか、LVS-DRを多段にするか、そもそもLVS-DRを捨てるか等考えている最中です。
L2 over L3。。。おっと誰かきたようだ。

リアルサーバでVIPを受けるためにループバックインターフェイスを使っている

リアルサーバでVIPの通信でするためにiptablesを使っている場合が多いと思いますが、数多くある配信サーバでconntrack tableの管理をしたくないため、ループバックインターフェイスを使っています。
うっかりarp_ignore等重要な設定を忘れたりしないために、puppetで管理をしています。
manifestはgithubで公開しているので参考にしてください。

目次の前に書いてある内容程度ですが今日はこの辺で。
細々とやるのが長く続ける秘訣ですね。

次回はLVSとVyattaのチューニングについて書こうと思います。