Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

dnsdist のパフォーマンスを引き出すネットワーク設定

YAPC::Kyoto 2023、JANOG51 MeetingではDNSへの水責めの攻撃とその対策について話をさせていただきました。その中で DNS攻撃をフィルタリングするために利用している dnsdist についてチューニングにより大きくパフォーマンス向上できることがわかってきたので紹介します。

YAPCの記事 kazeburo.hatenablog.com

JANOG51 Meetingについての記事 knowledge.sakura.ad.jp

Linuxのネットワークパラメータのチューニング

Linuxのチューニングでよくある設定ですが、sysctl.conf で以下のカーネルパラメータをチューニングをします。

net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728

ここでは rmem_maxwmem_max だけを設定しています。 net.core.rmem_defaultnet.core.wmem_default については後述します。

dnsdistのチューニング

listenするスレッド数を増やす

dnsdistでは addLocal を増やすことでlistenerスレッドの数を増やせます。

addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})
addLocal("0.0.0.0:53", {reusePort=true})

この際に reusePort を有効にするとそれぞれのスレッドにListen Queueが分散されるので高速化が見込めます。

また、CPU affinityを固定するのも良いでしょう

addLocal("0.0.0.0:53", {reusePort=true,cpus={0}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={1}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={2}})
addLocal("0.0.0.0:53", {reusePort=true,cpus={3}})

さらにTCPの接続が多い場合、キューのサイズを増やしておくと良いかもしれません

addLocal("0.0.0.0:53", {reusePort=true,cpus={0},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={1},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={2},tcpListenQueueSize=4096})
addLocal("0.0.0.0:53", {reusePort=true,cpus={3},tcpListenQueueSize=4096})

UDPのバッファサイズの調整

UDPの読み書きのバッファのサイズを大きくします。デフォルトはカーネルパラメータの net.core.rmem_defaultnet.core.wmem_default になります。なのでsysctlで設定することもできます。

setUDPSocketBufferSizes(8388608,8388608)

カーネルパラメータの net.core.rmem_maxnet.core.wmem_maxが設定できる最大値となります。

複数のUDPによるクエリを1度のシステムコールで読み込む

recvmmsg(2) を使うことで1度のシステムコールで複数のUDPによるクエリを読み込むことができます。 setUDPMultipleMessagesVectorSize はその最大個数を指定します。

setUDPMultipleMessagesVectorSize(100)

デフォルトは1で、通常の recvmsg(2) を使います。

ベンチマークの効果

さくらのクラウドの8コアの仮想サーバにて自作のDNS水責めベンチマークツールを使い検証を行いました。

dnsdistのみの検証のため、設定に

addAction(
  AllRule(),
  RCodeAction(DNSRCode.REFUSED)
)

を設定し、すべてのクエリに即時 REFUSED を返すように設定してあります。

初期状態

Listenerスレッドが1個の状態です。

# GOGC=500 ./prsd-bench4 -P 53 -H 192.168.10.50 --max-workers 1000 --max-length 8 --label 1 --zone example.com 2> /dev/null
2023-05-24 16:09:52.842209747 +0900 JST m=+10.002875416 resolved: 0.000000 query/sec, refused 231067.300000 query/sec, failed 72.200000 query/sec
2023-05-24 16:10:02.842144106 +0900 JST m=+20.002809775 resolved: 0.000000 query/sec, refused 220423.400000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:12.842143685 +0900 JST m=+30.002809354 resolved: 0.000000 query/sec, refused 230264.600000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:22.84215706 +0900 JST m=+40.002822729 resolved: 0.000000 query/sec, refused 220053.900000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:32.842160142 +0900 JST m=+50.002825810 resolved: 0.000000 query/sec, refused 227706.300000 query/sec, failed 144.400000 query/sec
2023-05-24 16:10:42.840047568 +0900 JST m=+60.000713237 resolved: 0.000000 query/sec, refused 232174.600000 query/sec, failed 144.400000 query/sec

20万qps以上は出ていますが、エラーもちらほらあります。

チューニング後

listenerスレッドを仮想コア数と同じ8個として、紹介したチューニングを全て入れている状態です。

# GOGC=500 ./prsd-bench4 -P 53 -H 192.168.10.50 --max-workers 1000 --max-length 8 --label 1 --zone example.com 2> /dev/null
2023-05-24 16:21:29.396835468 +0900 JST m=+10.001673734 resolved: 0.000000 query/sec, refused 417461.100000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:39.396746836 +0900 JST m=+20.001585106 resolved: 0.000000 query/sec, refused 445043.800000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:49.396666734 +0900 JST m=+30.001505003 resolved: 0.000000 query/sec, refused 431644.600000 query/sec, failed 0.000000 query/sec
2023-05-24 16:21:59.396818157 +0900 JST m=+40.001656423 resolved: 0.000000 query/sec, refused 438897.200000 query/sec, failed 0.000000 query/sec
2023-05-24 16:22:09.396665403 +0900 JST m=+50.001503669 resolved: 0.000000 query/sec, refused 440108.300000 query/sec, failed 0.000000 query/sec
2023-05-24 16:22:19.395664578 +0900 JST m=+60.000502848 resolved: 0.000000 query/sec, refused 425201.300000 query/sec, failed 0.000000 query/sec

エラーなく40万qps以上処理できるようになりました。

まとめ

権威DNSサーバであるNSDでは、UDPのバッファサイズ、recvmmsgの利用などはデフォルトで行われており、dnsdistパフォーマンスの調査をするにあたり参考にしました。

www.nlnetlabs.nl

この記事がDNSサーバのパフォーマンスで困った際にお役に立てれば幸いです。