rndc status の recursive clients

rndc status 打つと以下のような表示が出るが、

number of zones: 14
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is ON
recursive clients: 194/1900/2000
tcp clients: 0/100
server is up and running

recursive clients: 194/1900/2000
これの真ん中の 1900 ってナニ?
という質問をされました。
気分転換に調べてみたのでメモ。

まずBINDのソースをダウンロードしてきます。
それを展開して以下のファイルを見ます。

bin/named/server.c

        n = snprintf((char *)isc_buffer_used(text),
                     isc_buffer_availablelength(text),
                     "version: %s%s%s%s\n"
#ifdef ISC_PLATFORM_USETHREADS
                     "CPUs found: %u\n"
                     "worker threads: %u\n"
#endif
                     "number of zones: %u\n"
                     "debug level: %d\n"
                     "xfers running: %u\n"
                     "xfers deferred: %u\n"
                     "soa queries in progress: %u\n"
                     "query logging is %s\n"
                     "recursive clients: %d/%d/%d\n"
                     "tcp clients: %d/%d\n"
                     "server is up and running",
                     ns_g_version, ob, alt, cb,
#ifdef ISC_PLATFORM_USETHREADS
                     ns_g_cpus_detected, ns_g_cpus,
#endif
                     zonecount, ns_g_debuglevel, xferrunning, xferdeferred,
                     soaqueries, server->log_queries ? "ON" : "OFF",
                     server->recursionquota.used, server->recursionquota.soft,
                     server->recursionquota.max,
                     server->tcpquota.used, server->tcpquota.max);

ここに以下のような記述があります。

"recursive clients: %d/%d/%d\n"

実際に recursive clients: 194/1900/2000 と表示される部分ね。
ここの %d に当てはまるのがそれぞれ、

server->recursionquota.used,
server->recursionquota.soft,
server->recursionquota.max,

のようです。
usedとmaxはいいとして、softが何なのか謎です。
もうちょい調べてみる。

bin/named/server.c をもう一度良く見て、以下の記述を発見

        /*
         * Configure various server options.
         */
        configure_server_quota(maps, "transfers-out", &server->xfroutquota);
        configure_server_quota(maps, "tcp-clients", &server->tcpquota);
        configure_server_quota(maps, "recursive-clients",
                               &server->recursionquota);
        if (server->recursionquota.max > 1000)
                isc_quota_soft(&server->recursionquota,
                               server->recursionquota.max - 100);
        else
                isc_quota_soft(&server->recursionquota, 0);

ここでサーバパラメータを設定している。
MAXが1000より大きな値ならば、Soft Quotaに MAX - 100 の値を設定している。
たとえば
recursive clients: 194/1900/2000
のように 2000 にしたとしたら、 Soft Quotaは 1900 になる。

で、isc_quota_soft は何かというと以下のCファイルに関数が定義されていて、

lib/isc/quota.c

void
isc_quota_soft(isc_quota_t *quota, int soft) {
        LOCK(&quota->lock);
        quota->soft = soft;
        UNLOCK(&quota->lock);
}

isc_quota_soft(&server->recursionquota, server->recursionquota.max - 100);
のように呼び出して MAX - 100 の値を入れているだけのようだ。

設定されたSoft Quota がどのように利用されるかというと、
まず、lib/isc/quota.c の中の以下の関数で現在のクライアント数をカウントしていて、

lib/isc/quota.c

isc_result_t
isc_quota_reserve(isc_quota_t *quota) {
        isc_result_t result;
        LOCK(&quota->lock);
        if (quota->max == 0 || quota->used < quota->max) {
                if (quota->soft == 0 || quota->used < quota->soft)
                        result = ISC_R_SUCCESS;
                else
                        result = ISC_R_SOFTQUOTA;
                quota->used++;
        } else
                result = ISC_R_QUOTA;
        UNLOCK(&quota->lock);
        return (result);
}

通常は ISC_R_SUCCESS が返るが、Soft Quotaに達したら ISC_R_SOFTQUOTA を返す。
MAXに達したら ISC_R_QUOTA を返す。
その返り値を見て、

bin/named/query.c の

        if (client->recursionquota == NULL) {
                result = isc_quota_attach(&ns_g_server->recursionquota,
                                          &client->recursionquota);
                if  (result == ISC_R_SOFTQUOTA) {
                        static isc_stdtime_t last = 0;
                        isc_stdtime_t now;
                        isc_stdtime_get(&now);
                        if (now != last) {
                                last = now;
                                ns_client_log(client, NS_LOGCATEGORY_CLIENT,
                                              NS_LOGMODULE_QUERY,
                                              ISC_LOG_WARNING,
                                              "recursive-clients soft limit "
                                              "exceeded, aborting oldest query");
                        }
                        ns_client_killoldestquery(client);
                        result = ISC_R_SUCCESS;
                } else if (result == ISC_R_QUOTA) {
                        static isc_stdtime_t last = 0;
                        isc_stdtime_t now;
                        isc_stdtime_get(&now);
                        if (now != last) {
                                last = now;
                                ns_client_log(client, NS_LOGCATEGORY_CLIENT,
                                              NS_LOGMODULE_QUERY,
                                              ISC_LOG_WARNING,
                                              "no more recursive clients: %s",
                                              isc_result_totext(result));
                        }
                        ns_client_killoldestquery(client);
                }
                if (result == ISC_R_SUCCESS && !client->mortal &&
                    (client->attributes & NS_CLIENTATTR_TCP) == 0) {
                        result = ns_client_replace(client);
                        if (result != ISC_R_SUCCESS) {
                                ns_client_log(client, NS_LOGCATEGORY_CLIENT,
                                              NS_LOGMODULE_QUERY,
                                              ISC_LOG_WARNING,
                                              "ns_client_replace() failed: %s",
                                              isc_result_totext(result));
                                isc_quota_detach(&client->recursionquota);
                        }
                }
                if (result != ISC_R_SUCCESS)
                        return (result);
                ns_client_recursing(client);
        }

if (result == ISC_R_SOFTQUOTA) でクライアント数がSoft Quotaに達したら
"recursive-clients soft limit "
"exceeded, aborting oldest query"
のようなログを吐いて、
ns_client_killoldestquery(client); 関数を呼んで
古いクエリを殺し、ISC_R_SUCCESS を返す。

if (result == ISC_R_QUOTA) でクライアント数がHard Quota (MAX)に達したら、
no more recursive clients:
のようなログを吐いて、
ns_client_killoldestquery(client); 関数を呼んで
古いクエリを殺すが ISC_R_SUCCESSは返さない。


このns_client_killoldestquery(client)関数の中身までは追ってない。

ソース読むの苦手だけど、BINDのソースってずいぶん丁寧に書かれていてわかりやすいな……すごい。