質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.87%

apacheのエラーログに"DNS lookup failure" が出力される

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 2,754

Liastea

score 10

リバースプロキシとして使用しているapacheに出力されるDNS lookupエラーについて質問です。

 前提

  • SSLのためにリバースプロキシを使用

  • リバースプロキシサーバーは CentOS 7.1 + apache(2.4.33)で構成

  • 1日あたり約70万/日のアクセスがある

  • 構成は下記のようなイメージです。
    クライアント <---> インターネット <---> ファイアウォール <---> リバースプロキシ(apache)[今回の質問対象] <---> ロードバランサー <---> コンテンツサーバー(apache)

 エラー発生時の状況

  • HTTP/2.0に対応させるため、apacheの設定を下記のとおり変更し、restartした。(apache event + http/2)
主なapache設定変更点

LoadModule mpm_event_module modules/mod_mpm_event.so  ←追加した
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so  ←コメントアウトした

~~~~

Protocols               h2 http/1.1  ←追加した
#Protocols               http/1.1  ←コメントアウトした

~~~~
下記設定を追加

<IfModule mpm_event_module>
MaxRequestWorkers        400
ServerLimit              8
ThreadLimit              50
ThreadsPerChild          50
StartServers             8
MinSpareThreads          100
MaxSpareThreads          400
MaxConnectionsPerChild   0
MaxMemFree               2048
</IfModule>
  • 設定変更後、1万~10万アクセスに一度、下記のようなapacheエラーログが出力されるようになった。
[Tue Oct 16 12:43:25.460055 2018] [proxy:error] [pid 12345:tid 140458525089774] "example.jp" [client 192.168.X.X:45678] AH00898: DNS lookup failure for: www.hogehoge.example.jp returned by /example/hogehoge/
  • その時、アクセスログでは502エラーが返っている
  • 99.99%以上のアクセスは問題なし
  • 現在は切り戻しており、再現の条件ははっきりと分かっていない。(大量のアクセスが条件?)

 確認したこと

  • CPU、メモリ、I/O → 負荷はみられない
  • /var/log/messages、dmesg等のシステムログ → 特に変わったところはない
  • apcheのソース(後述)

 質問内容

  • "DNS lookup failure" が出力される原因は何なのでしょうか
  • 本当に名前解決できていないのでしょうか(apacheが誤解している?)

これまで apache prefork + http/1.1 で運用しており、"DNS lookup failure" が出力されたことはありません。  
ネットで色々調べたものの、類似の事象が見つけられず困り果ております。  


 追加情報

 確認したapacheのソース(コメント一部割愛)

apacheエラーログのエラーコード(AH00898)より下記ソースの動作であることは確認しました。  

err変数が途中で書き換わるとDNS lookup failureが出力される様子  

proxy_util.c 2189行目

    int server_port;
    apr_status_t err = APR_SUCCESS;
    apr_status_t uerr = APR_SUCCESS;
    const char *uds_path;
proxy_util.c 2356行目

    if (err != APR_SUCCESS) {
        return ap_proxyerror(r, HTTP_BAD_GATEWAY,
                             apr_pstrcat(p, "DNS lookup failure for: ",
                                         conn->hostname, NULL));
    }

現状の設定だとerr変数が書き換わるのはどうもここっぽい

proxy_util.c 2306行目

            if (!will_reuse) {
                err = apr_sockaddr_info_get(&(conn->addr),
                                            conn->hostname, APR_UNSPEC,
                                            conn->port, 0,
                                            conn->pool);
            }


apr_sockaddr_info_get関数のソース
httpd-2.4.XX/srclib/apr/network_io/unix/sockaddr.c にあった

sockaddr.c 605行目

APR_DECLARE(apr_status_t) apr_sockaddr_info_get(apr_sockaddr_t **sa,
                                                const char *hostname,
                                                apr_int32_t family, apr_port_t port,
                                                apr_int32_t flags, apr_pool_t *p)
{
    apr_int32_t masked;
    *sa = NULL;

    if ((masked = flags & (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK))) {
        if (!hostname ||
            family != APR_UNSPEC ||
            masked == (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK)) {
            return APR_EINVAL;
        }
        if (flags & APR_IPV6_ADDR_OK) {
            return APR_ENOTIMPL;
        }
    }
    if (family == APR_UNSPEC) {
        family = APR_INET;
    }
    return find_addresses(sa, hostname, family, port, flags, p);
}

returnのfind_addressesのソースを読むとcall_resolverが呼び出されているので
call_resolverのソースを見てみる

sockaddr.c 307行目

static apr_status_t call_resolver(apr_sockaddr_t **sa,
                                  const char *hostname, apr_int32_t family,
                                  apr_port_t port, apr_int32_t flags,
                                  apr_pool_t *p)
{
    struct addrinfo hints, *ai, *ai_list;
    apr_sockaddr_t *prev_sa;
    int error;
    char *servname = NULL;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;
    if (family == APR_UNSPEC) {
        hints.ai_flags = AI_ADDRCONFIG;
    }
~~~略~~~

    error = getaddrinfo(hostname, servname, &hints, &ai_list);

~~~略~~~

getaddrinfoは、アドレス情報を決定するためのヒントを与えることでsockaddr型構造体を得ることができる関数らしい。
ヒントとなる情報を格納した不完全なaddrinfo型構造体を与えると、必要なメンバが全てそろったaddrinfo型構造体を返すとのこと。
第1~3引数で情報を与え、第4引数に結果(メモリアドレス)が返る。ここでは&ai_listに結果が返る

sockaddr.c 403行目

    prev_sa = NULL;
    ai = ai_list;
    while (ai) { /* while more addresses to report */
        apr_sockaddr_t *new_sa;

        if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
        if (ai->ai_family != AF_INET) {
            ai = ai->ai_next;
            continue;
        }

        new_sa = apr_pcalloc(p, sizeof(apr_sockaddr_t));

        new_sa->pool = p;
        memcpy(&new_sa->sa, ai->ai_addr, ai->ai_addrlen);
        apr_sockaddr_vars_set(new_sa, ai->ai_family, port);

        if (!prev_sa) { /* first element in new list */
            if (hostname) {
                new_sa->hostname = apr_pstrdup(p, hostname);
            }
            *sa = new_sa;
        }
        else {
            new_sa->hostname = prev_sa->hostname;
            prev_sa->next = new_sa;
        }

        prev_sa = new_sa;
        ai = ai->ai_next;
    }
    freeaddrinfo(ai_list);

    if (prev_sa == NULL) {
        return APR_EGENERAL;
    }

    return APR_SUCCESS;
}

上記でやってること?  
getaddrinfoから得た情報(ai)のメモリ情報をコピーして新しいソケットアドレス(new_sa)を作成  
最初に見つけたソケットアドレスをsaをセット(saは後にコネクション確立する用?)  
最終的にprev_sa(new_sa)に値が入っているとAPR_SUCCESSが返る  

要するに新しいソケットアドレスが作成できなかったときに"DNS lookup failure"が発生する?(自信なし)  

 リバースプロキシのhttpd.conf (一部省略しています)

# Server Base
ServerRoot              "/usr/local/apache2"
PidFile                 "/var/run/httpd.pid"
DefaultRuntimeDir       "/var/run/apache2"
Listen                  80
Listen                  443
UseCanonicalName        Off
HostnameLookups         On
ServerTokens            Prod
ServerSignature         Off
Timeout                 120
KeepAlive               On
MaxKeepAliveRequests    50
KeepAliveTimeout        2
TraceEnable             Off
FileETag                MTime Size

ServerAdmin             root@localhost
ServerName              localhost
User                    www
Group                   www
DocumentRoot            "/var/www"

# Load Module
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule socache_dbm_module modules/mod_socache_dbm.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule http2_module modules/mod_http2.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule mpm_event_module modules/mod_mpm_event.so
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule dir_module modules/mod_dir.so

## prefork MPM
<IfModule mpm_prefork_module>
        ServerLimit          500
        StartServers          50
        MinSpareServers       50
        MaxSpareServers       50
        MaxClients           400
        MaxRequestsPerChild 1000
</IfModule>

# Event MPM
<IfModule mpm_event_module>
MaxRequestWorkers        400
ServerLimit              8
ThreadLimit              50
ThreadsPerChild          50
StartServers             8
MinSpareThreads          100
MaxSpareThreads          400
MaxConnectionsPerChild   0
MaxMemFree               2048
</IfModule>

# Proxy
ProxyRequests     Off
ProxyVia          Off
ProxyPreserveHost On

# HTTP2
<IfModule http2_module>
ProtocolsHonorOrder     On
Protocols               h2 http/1.1
#Protocols               http/1.1
LogLevel                http2:info
H2MaxWorkerIdleSeconds  120
H2ModernTLSOnly         On
H2Push                  On
H2MaxSessionStreams     30
H2SessionExtraFiles     30
H2TLSWarmUpSize         65535
</IfModule>

# SSL
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
SSLProtocol             -All +TLSv1.2 +TLSv1.1 +TLSv1
SSLCipherSuite          (割愛)

SSLHonorCipherOrder     On
SSLCompression          Off

SSLSessionCache         dbm:/hoge/hoge
Mutex                   file:/var/tmp
SSLPassPhraseDialog     exec:/hoge/hoge
SSLSessionCacheTimeout  300

SSLSessionTickets       Off

SSLUseStapling                      On
SSLStaplingResponderTimeout         5
SSLStaplingReturnResponderErrors    Off
SSLStaplingCache                    shmcb:/var/run/apache2/ocsp(128000)

# configure
<VirtualHost 192.168.1.10>
    SSLEngine On
    ServerName sslproxy.example.jp
    SSLCertificateKeyFile   /hoge/hoge.key
    SSLCertificateFile      /hoge/hoge.crt
</VirtualHost>

<VirtualHost 192.168.1.11>
    SSLEngine On
    ServerName hoge.example.jp
    SSLCertificateKeyFile  /hoge/hoge.key
    SSLCertificateFile     /hoge/hoge.crt

    <IfDefine !AP22>
        ProxyPass / http://www.hogehoge.example.jp/
    </IfDefine>
    <IfDefine AP22>
        ProxyPass / http://www.hogehoge.example.jp/ disablereuse=On
    </IfDefine>
</VirtualHost>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • TaichiYanagiya

    2018/10/21 23:50

    /var/log/messages には何かエラーは出ていませんでしょうか。ファイルディスクリプターが足りないとか。

    キャンセル

  • Liastea

    2018/10/22 11:24

    コメントありがとうございます。 該当時間の/var/log/messagesには何も出力されていませんでした。 ulimitやnf_conntrackの上限に達しているわけではなさそうです。

    キャンセル

回答 1

check解決した方法

+2

答えに辿り着くまでに相当時間がかかりましたが、自己解決しました。
結論から言うとglibcのバグでした。

検証中、テストプログラムをコンパイルするためにgccをyumでインストールした後、
不具合が発生しなくなったのをきっかけに気づくことができました。

問題のバージョンは glibc-2.17-78 で、
glibc-2.17-105 以上にアップデートすることによって修正できることを確認しました。

対象のバグは、おそらくですが下記と予想しています。

https://access.redhat.com/security/cve/cve-2013-7423

It was discovered that, under certain circumstances, glibc's getaddrinfo() function would send DNS queries to random file descriptors.

https://jvndb.jvn.jp/ja/contents/2013/JVNDB-2013-006731.html

第三者により、大量のリクエストを介して、getaddrinfo関数の呼び出しを誘発されることで、DNS クエリを意図しない場所に送信される可能性があります。

上記不具合は glibc-2.17-90 で修正されています。
http://rpm.pbone.net/index.php3/stat/22/idpl/33992424/com/changelog.html

apacheのsockaddr.cでも使用されていたgetaddrinfo関連の修正なのでほぼ間違いないかなと思っています。

以上、この記録が誰かの役に立つことを祈っています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる