Nagiosのcheck_httpプラグインのリダイレクトの動作について

2020/09/27に公開

はじめに

Webサービスにおいて正常にサイトが見れているかは重要です。そのため正常に動作しているかのリアルタイム監視が必要です。しかし、すべてを人力で監視するのは現実的ではありません。世の中にはNagiosやZabbix、最近ではSensu, Mackerelなど様々な監視ソフトウェアやSaaSが存在します。監視の中でも外形監視、つまりユーザから見た時に問題がないかの監視は欠かせません。Webサービスはブラウザ(ここではHTTPを喋るクライアントという意味の広義のブラウザ)でみることを前提に設計されていますのでHTTPを使っての監視は必須になります。またWebサイトでは様々な事情によりリダイレクトがされています。そのため監視側でのリダイレクトのフォローが必須です。今回はその中でもNagiosのHTTP監視プラグインのひとつであるcheck_httpのリダイレクトの挙動について、特に解説があまりみあたらない-f stickyオプションについて調査しましたので紹介します。

リダイレクトについて

check_httpのリダイレクトの詳細に移る前に軽くリダイレクトについて解説しておきます。HTTPプロトコルについては書くとそれだけで記事になってしまうため触れません。
 リダイレクト[1]とはあるURLからあるURLへ転送する技術です。実現のためにWebサーバ側はレスポンスコードとして300番台を返します。また転送先のURLを伝えるためLocationヘッダに転送先のURLをセットします。HTTPクライアント側では300番台のリクエストが来た場合、(リダイレクトをする場合は)Locationヘッダから転送先URLを取得し、再度その転送先URLへアクセスする必要があります。

バーチャルホスト

念の為、バーチャルホストについても解説しておきます。本来はリダイレクトに関係ないのですが、バーチャルホストについて理解していないと-f stickyオプションの旨味がわかりません。
 本来、1つのドメインに対して1つのIPアドレスが原則です。しかし、1つのサーバで複数のドメインを運用したい、逆に1つのドメインで複数サーバに分散したいなどで、1つのIPに対して1ドメインというのがもはや現代では成り立たなくなっています。1つめの複数のドメインを1つのIPに割り当てて、どのドメインに来たのかを特定する技術がバーチャルホストです。具体的にはHTTPリクエストヘッダのHostにドメインを指定してWebサーバがHostヘッダを見て判断することで1つのIPで複数のドメインを運用することができます。この場合はドメインを指定すれば1つのサーバを指定したことになりますので監視をする上でも特に問題はおこりません。問題はさらに複数ドメインを複数のサーバで運用している際に監視している場合です。全体としての監視の他に1つのサーバが動作しているか確認したいことがあります。そういったときにcheck_httpでは-Hでドメインを指定するほかに、-IオプションでIPアドレスを指定することができます。そうすることでcheck_httpは-Iオプションで指定したIPアドレスにつなぎに行きつつ、Hostヘッダは-Hで指定した値を使います[2][3]
check_httpで-fオプションをつけたとき(リダイレクトさせるとき)はこの-Iオプションの扱いが変わります。

ソースコードを読んで解説

以上の前提の元、いよいよcheck_httpの解説に入ります。公式の説明では下記のようになっています。こちらは動作を理解して読んでみると理解できますが、"specified IP address"とは何を指すのか、「stick」をどういう意味で使っているのか明らかではなく不明瞭です(ネイティブならわかるのかもしれませんが...)。

 -f, --onredirect=<ok|warning|critical|follow|sticky|stickyport>
     How to handle redirected pages. sticky is like follow but stick to the
     specified IP address. stickyport also ensures port stays the same.

そこでソースコードを読んでみることにします。参考にしたのは諸事情により下記の2.2.1のソースコードです。

https://github.com/nagios-plugins/nagios-plugins/blob/release-2.2.1/plugins/check_http.c

重要な関数として int check_http (void) および void redir (char *pos, char *status_line)int process_arguments (int, char **) の3つがあります。
 check_http関数は名前の通りcheck_http本体の処理です。引数がありませんが、グローバル変数で他の関数とやり取りをしています(どうしてこうなった)。
 redir関数はリダイレクト処理を担当しています。おそらくredirectの略かと思います。-fオプションの挙動もこのredir関数で制御しています。詳細は後述しますが、redir関数の引数で重要なものはposです。このposにはcheck_httpからHTTPヘッダ先頭を指しているポインタが渡されます。そしてredir関数内では現在読んでいる位置(position)を管理するポインタとして使われます。
process_argumentsはコマンドライン引数を処理する関数です。この関数でcheck_http関数で使う各種グローバル変数に値を代入しています。

次に重要なグローバル変数をあげます。char *server_address;followstickyの2つです。server_addressは-Iで指定するアドレスがprocess_argumentsによって入れられます。またfollowstickyはsticky関連のフラグ管理用の変数でこちらもprocess_argumentsで-fオプションを解析した際に値が入れられます。

最後の準備としてredir関数内の重要なローカル変数addrについてです。これはリダイレクト先FQDNやIPアドレスを入れている一時変数となります。

以上を前提に実際にソースコードを見ていきましょう。まずはprocess_argumentsでの-fオプションの処理(下記)です。stickyが指定されるとfollowstickyにSTICKY_HOSTフラグを立てます。単にfollowでも形式的にSTICKY_NONEフラグを"立てて"いますが、これは0でその後、取り出すことはできないので実質的には無意味な処理[4]となります。

#define STICKY_NONE 0
#define STICKY_HOST 1
#define STICKY_PORT 2
//(中略)
    case 'f': /* onredirect */
      if (!strcmp (optarg, "stickyport"))
        onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST|STICKY_PORT;
      else if (!strcmp (optarg, "sticky"))
        onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST;
      else if (!strcmp (optarg, "follow"))
        onredirect = STATE_DEPENDENT, followsticky = STICKY_NONE;

つぎにcheck_http関数を見ていきます。レスポンスコードが300番台かつリダイレクトする設定だったらredir関数にheaderとstatus_lineを渡して呼び出しています。

  /* check redirected page if specified */
  else if (http_status >= 300) {

    if (onredirect == STATE_DEPENDENT)
      redir (header, status_line);

次にredir関数を見ていきます。redir関数のうちリダイレクト先の解析と-fオプションの制御部分について見ます。

まずリダイレクト先の解析は下記です。sscanfわかりにくいですがLocationヘッダ(大文字小文字区別しない)の位置を取得しposの位置を変えています。また、addrにはFQDNあるいはIPアドレス(URI_HOST)を入れています。

 #define URI_HTTP "%5[HTPShtps]"
 #define URI_HOST "%255[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
 #define URI_PORT "%6d" /* MAX_PORT's width is 5 chars, 6 to detect overflow */
 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
 #define HD1 URI_HTTP "://" URI_HOST ":" URI_PORT "/" URI_PATH
 #define HD2 URI_HTTP "://" URI_HOST "/" URI_PATH
 #define HD3 URI_HTTP "://" URI_HOST ":" URI_PORT
 #define HD4 URI_HTTP "://" URI_HOST
 /* HD5 - relative reference redirect like //www.site.org/test https://tools.ietf.org/html/rfc3986 */
 #define HD5 URI_HTTP "//" URI_HOST "/" URI_PATH
 #define HD6 URI_PATH
 //(中略)
 //ここの周辺の処理でposのポインタをLocationヘッダがでてくるとこまで進めてる
    sscanf (pos, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx, &i);
 //(中略)
 //HD1-6まであるがいずれにせよaddrにはURI_HOSTが入る
     /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
     if (sscanf (pos, HD1, type, addr, &i, url) == 4) {
       url = prepend_slash (url);
       use_ssl = server_type_check (type);
     }
 //(後略)

次に-fオプションの制御部分について見ます。ソースコードは下記です。これが本日の一番重要な部分です。STICKY_HOSTフラグが立っていなかったらaddrの内容でserver_addressを上書きしています。つまり、STICKY_HOSTフラグが立っていたらserver_addressを-Iで指定したIPのまま変えません。

   if (!(followsticky & STICKY_HOST)) {
     free (server_address);
     server_address = strndup (addr, MAX_IPV4_HOSTLENGTH);
   }

まとめると、-f stickyが指定された場合は下記動作をします。

  1. -Iで指定したIPアドレスのままリダイレクトする
  2. Hostヘッダはリダイレクト先に書き換わる

ここまでくると-fの説明がわかります。訳すと「stickyは単純なfollowのようにみえるものの、(-Iオプションで)指定したIPアドレスで固定したままにする」ということです。

以上から-f stickyは-Iで指定したサーバを指定したままリダイレクトが成功することを確認することができます。

まとめ

今回はcheck_httpの-fオプションの挙動についてソースコードを読み解説をしました。結果、-f stickyを指定した場合は(-Iオプションで)指定したIPアドレスで固定したままリダイレクトができることがわかりました。これは-Iで指定したサーバを指定したままリダイレクトをさせたい場合に有効です。

脚注
  1. 正確にはURLリダイレクト。ただ誤解が生じないためここではリダイレクトとしています。 ↩︎

  2. ここでhttps接続した時どうなるのかと気になった人がいるかと思います。check_httpではSSL証明書エラーを無視します ↩︎

  3. -Iオプションを指定しない場合は-Hの値を-Iの値として利用します ↩︎

  4. もしちゃんと取り出せるようにする場合はSTICKY_NONEに1,STICKY_HOSTに2,STICKY_PORTに4を割り当てるなどすれば良いです。 ↩︎

Discussion