Linux Install Memo

サーバー管理者によるLinux関連ソフトのインストールメモ

Home » □PowerDNSのBackendをつくってみる

□PowerDNSのBackendをつくってみる

□PowerDNSのBackendをつくってみる

PowerDNSといえば、シェアこそ少ないものの、かなり大規模なシステムで使われているDNSです。(MyDNS.JPでも使っています)
さまざまなSQLデータベースと直結しているので、SQLができる人ならDNS情報の管理がしやすいという利点があります。
また、データベースと連携して地理的ロードバランス(Global Server Load Balancing:GSLB、Geo-IPとかGeoLocation-IPとかいったりしますね。)ができる、ということでも有名かもしれません。

GSLBの実装としてGeoBackendというバックエンド処理があるのですが、これ自体は期待するほど使えません。
(期待してがっかりした本人が言うのですから間違いない!?)

GeoBackendは特定のというか一つのドメインでのみGSLBができ、実際にIPv4アドレスの地理情報を提供しているところの情報を使い、あらかじめ指定した国のみ、そのIPからの問い合わせには、指定した国別コード(アルファベット2文字)をつけたCNAMEを返す、というものです。

例:

emacs /etc/powerdns/pdns.conf

----------
launch=geo,gpgsql


#
# GeoBackend Setting
#
query-cache-ttl=0
cache-ttl=0
geo-zone=geo.domain.com
geo-soa-values=ns0.domain.com,hostmaster@domain.com
geo-ns-records=ns0.domain.com,ns1.domain.com
geo-ip-map-zonefile=/usr/src/zz.countries.nerd.dk.rbldnsd
geo-maps=/etc/powerdns/geo-maps
----------

emacs /etc/powerdns/geo-maps/geo.domain.com
----------
$RECORD www
$ORIGIN geo.domain.com.

# default
0 other
# 127.0.1.136 -> 392
392 japan
# 127.0.2.190 -> 702
702 singapore
# 127.0.2.16 -> 528
528 netherlands
# 127.0.3.72 -> 840
840 usa
----------

とすると、この設定をしたPowerDNSに「www.geo.domain.com」を問い合わせると、問い合わせをしたマシンのIPv4アドレスの所在地により、日本から(と/usr/src/zz.countries.nerd.dk.rbldnsdに定義されている)なら

japan.www.geo.domain.com

がCNAMEとして返るので、別途本来のPowerDNSに「japan.www.geo.domain.com」のAレコードを指定しておけば、それが問い合わせ元に返る、ということになります。

上記の場合には「launch=geo,gpgsql」としているので、このgeobackendでCNAMEまで判明したら、gpgsqlbackend:つまりPostgreSQLの中の情報を検索する、ということになります。

これだけなら「おおっ、すごい」と思うのですが、「geo-zone=geo.domain.com」と書いてしまっているので、このドメインでしかGSLBができません。また「/usr/src/zz.countries.nerd.dk.rbldnsd」自体が国別でしか情報を返してくれないので、そこまで細かい設定はしなくていいんだけど、ということにもなります。
だってMyDNS.JPは日本とシンガポールとオランダとアメリカにしかサーバーがないんだもん。

ではPowerDNSをつかって複数のドメインでGSLBを実現するにはどうするか?
これはもうGeoBackendを改造するか、GeoBackendとは別のバックエンドを作るしかありません。

で、そのバックエンドのサンプルをPHPで作ってしまいました。

最初はC++でバックエンドそのものを作るか?それともCでソケット通信でもしてやり取りするようにするか?とか思っていたのですが、あれこれしながらドキュメントを読んでいると「pipebackendで納得いかない場合には自分でバックエンドを作れ」みたいな事が書いてありました。
http://doc.powerdns.com/html/backend-writers-guide.html

で、サンプルのPerlスクリプトを見ながら、PHPで同じようなものを書いてみました。

「PerlからPHPにしただけかい!!」といわれると…まぁこれはそうなんですが、実際にはこれにさらに味付けをすることで、MyDNS.JPで大陸別のGSLBを標準実装する予定ですので、もうちょっとお待ちください。

#!/usr/bin/php
<?
// ------------------------------------------------------------
// 
// GeoLocBackend
// 
// Ver.01.00	2014.06.20 思い立って少し作ってみる
// 
// Program:
//     Takeshi Kaburagi/JS1FVG    disco-v8 @ 4x4.jp
// 
// Usage:
// 
//   Edit /etc/powerdns/pdns.conf
//   
//   ----------
//   launch=pipe,gpgsql
//   pipe-command=/etc/powerdns/pipebackend.php
//   pipebackend-abi-version=1
//   ----------
//   
//   /etc/init.d/pdns-server restart
//   
//   http://doc.powerdns.com/html/backends-detail.html#pipebackend
//   
//   1) HELO 1が飛んできたらOKを返すとPIPEBackend処理が開始
//   2) PowerDNSから標準入力で上記URLにあるクエリが飛んでくる。
//    →デバッグモードをON(=1)にして出力するとわかるが、しつこくバラバラにしていろいろなタイプのクエリが飛んでくる
//   3) 必要に応じて処理をして返したいデータを標準出力に出して、ENDを最後に送信して終わり
//   
//   ※launchに書いた順番でDNS情報が検索される、pipe(この処理)で情報が見つからないと、gpgsqlに探しに行く、ということ
//   ※pipebackend-abi-versionによってHELO 1の数字が変わる、と同時に送られてくるクエリのフォーマットも増えるし、返し方も変わるので注意
// ------------------------------------------------------------
?>
<?
// ----------------------------------------------------------------------
// Init Routine
// ----------------------------------------------------------------------
// 環境変数を設定する
// タイムゾーン
//date_default_timezone_set('Asia/Tokyo');
date_default_timezone_set(@date_default_timezone_get());

// 内部文字コード
mb_internal_encoding('UTF-8');

// 出力文字コード
mb_http_output('UTF-8');

// デバッグ時のログファイル名
$LOG_FILE = '/var/tmp/pipebackend.log';

// 動作モード
$MODE = 0;

// デバッグモード
$DEBUG_MODE = 1;

?>
<?
// ----------------------------------------------------------------------
// Sub Routine
// ----------------------------------------------------------------------
?>
<?
// --------------------
// LOGGING
// --------------------
function log_write($LOG_STR)
{
	global $LOG;
	global $LOG_FILE;

	// ログファイルポインタが開かれていないなら
	if (!$LOG)
	{
		$LOG = fopen($LOG_FILE, 'a');
	}
	// ログファイルポインタが開かれているなら
	if ($LOG)
	{
		// ログファイルを排他的ロック
		if (flock($LOG, LOCK_EX))
		{
			// ログファイルの一番最後までシーク
			fseek($LOG, 0, SEEK_END);
			// ログファイルに書き出し
			fprintf($LOG, $LOG_STR);
			// ログファイルをアンロック
			flock($LOG, LOCK_UN);
		}
	}
}
?>
<?
// --------------------
// メイン処理
// --------------------

while(($QUERY = fgetcsv(STDIN, 2048, "\t")) !== FALSE)
{
	// デバッグモードがON(=1)なら
	if ($DEBUG_MODE == 1)
	{
		log_write("----------------"."\n".'['.date("Y/m/d H:i:s").']'."\n"."PARAM COUNT = ".count($QUERY)."\n");
		// クエリを全て書き出し
		foreach ($QUERY as $VALUE)
		{
			log_write($VALUE."\n");
		}
	}
	// 接続状態なら
	if ($MODE == 1)
	{
		// パラメータが6個ないなら
		if (count($QUERY) < 6)
		{
			// ネゴシエーション完了、通常動作モード(=1)に
			$MODE = 1;
			log_write("MODE = ".$MODE."\n");
			fprintf(STDOUT, "LOG\tPowerDNS sent unparseable line!?"."\n");
			fprintf(STDOUT, "FAIL"."\n");
			continue;
		}
		
		// クエリがAかCNAMEかANYの場合にのみ、CNAMEが返せる
		if (count($QUERY) == 6 && $QUERY[0] == "Q" && ($QUERY[3] == "A" || $QUERY[3] == "CNAME" || $QUERY[3] == "ANY") && $QUERY[1] == "www.example.com")
		{
			log_write("DATA = ".$QUERY[1]." -> "."geolocip.".$QUERY[1]."\n");
			fprintf(STDOUT, "DATA"."\t".$QUERY[1]."\t".$QUERY[2]."\t"."CNAME"."\t"."300"."\t"."-1"."\t"."geolocip.".$QUERY[1]."\n");
		}
		
		// ここに、受けたクエリ別にPowerDNSに返したいデータを作る処理を書いていけばよい
		
		
		// このBackendでできることはこれで終わり
		fprintf(STDOUT, "END"."\n");
		
	}
	// 接続状態でないなら
	else if ($MODE == 0)
	{
		// ネゴシエーション文字列なら
		if (count($QUERY) == 2 && $QUERY[0] == "HELO" && $QUERY[1] == "1")
		{
			// ネゴシエーション完了、通常動作モード(=1)に
			$MODE = 1;
			log_write("MODE = ".$MODE."\n");
			fprintf(STDOUT, "OK\tPIPE backend firing up"."\n");
		}
	}
}
fclose($LOG);
?>

 

Name of author

Name: admin

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です