/*	
 *	clipdns.c
 *
 *	DNSクライアント
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Thu Sep 22 05:58:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Sep 25 17:23:00 JST 2005 Naoyuki Sawa
 *	- dns_query()にて、否定応答の回答メッセージを受け取ったときに、すぐに処理を終了するよう修正しました。
 *	  これまでは、否定応答を受け取ったときに、処理を終了せず、タイムアウトまで待ってしまっていました。
 *	- dns_query()のタイムアウト時間の既定値を、1000[ms]から2000[ms]へ変更しました。
 *	  1000[ms]では、問い合わせへの成功/失敗が不安定になることが時々あったからです。
 *	  正しいDNSサーバが設定されていれば、間違った問い合わせ対しても、すぐに失敗回答が返ってくるはずです。
 *	  タイムアウト時間を増やしたことによって、アプリケーションの実行が遅くなるような影響は少ないはずです。
 *	  ただし、不正なDNSサーバが設定されている場合は、タイムアウト待ちによる停止時間が長くなってしまいます。
 *	* Sun Sep 25 19:34:00 JST 2005 Naoyuki Sawa
 *	- dns_query()のタイムアウト時間の既定値を、やっぱり、1000[ms]に戻しました。
 *	  問い合わせへの成功/失敗が不安定だった理由は、DNSの応答が遅いことが原因ではありませんでした。
 *	  P/ECE Ethernetドライバを起動した後、WindowsがP/ECE Ethernetデバイスを認識し、ベンダリクエストによって
 *	  MACアドレスを設定するまでに少し時間がかかりますが、その間にDNS質問メッセージを送信しようとして、
 *	  正しく送出できていなかったのが原因みたいです。また、MACアドレスが設定された後も、
 *	  しばらくはWindowsがネットワーク設定を行っているため、すぐにはTCP/UDP通信ができないみたいです。
 *	  P/ECE TCP/IPアプリケーションを作成する際、起動直後に自動的にネットワーク通信を行うようなプログラムは
 *	  この点が問題となる可能性があるので、避けて、ユーザーがボタンを押した後にネットワーク通信を行うなどの
 *	  方法を採るよう、注意してください。
 */
#include "clip.h"

/*****************************************************************************
 *	構造体、定数
 *****************************************************************************/

/* DNSヘッダ */
typedef struct _DNSHEADER {
	unsigned short id;				/* + 0,2: 識別子 */
	unsigned short rcode :4;			/* + 2,2: [ 3: 0] 回答コード */
	unsigned short z     :3;			/*        [ 6: 4] 予約 */
	unsigned short ra    :1;			/*        [    7] 再帰可能 */
	unsigned short rd    :1;			/*        [    8] 再帰要求 */
	unsigned short tc    :1;			/*        [    9] 切り詰め */
	unsigned short aa    :1;			/*        [   10] 権威ある回答 */
	unsigned short opcode:4;			/*        [14:11] 質問の種類 */
	unsigned short qr    :1;			/*        [   15] 質問or回答 */
	unsigned short qdcount;				/* + 4,2: 質問レコードの数 */
	unsigned short ancount;				/* + 6,2: 回答レコードの数 */
	unsigned short nscount;				/* + 8,2: 権威レコードの数 */
	unsigned short arcount;				/* +10,2: 追加レコードの数 */
} DNSHEADER;						/* =12 */

/* 質問レコード */
typedef struct _DNSQD {
	/* char qname[?]; */				/*   +0,?: 質問名 */
	unsigned short qtype;				/*  ?+0,2: 問い合わせ種別 */
	unsigned short qclass;				/*  ?+2,2: 問い合わせクラス */
} DNSQD;						/* =?+4 */

/* 資源レコード */
typedef struct _DNSRR {
	/* char name[?]; */				/*   + 0,?: 名前 */
	unsigned short type;				/*  ?+ 0,2: 種別 */
	unsigned short class;				/*  ?+ 2,2: クラス */
	unsigned long  ttl;				/*  ?+ 4,4: 生存時間 */
	unsigned short rdlength;			/*  ?+ 8,2: 資源データ長 */
	const unsigned char* rdata/*[rdlength]*/;	/*  ?+10,?: 資源データ */
} DNSRR;						/* =?+10+? */

/* DNSHEADER.qr */
#define DNS_QR_QUERY			  0		/* 質問 */
#define DNS_QR_RESPONSE			  1		/* 回答 */

/* DNSHEADER.opcode */
#define DNS_OPCODE_QUERY		  0		/* 標準的な質問 (質問) */
#define DNS_OPCODE_IQUERY		  1		/* 逆問合せ (逆質問) */
#define DNS_OPCODE_STATUS		  2		/* サーバ状態要求 (状態) */

/* DNSHEADER.rcode */
#define DNS_RCODE_NO_ERROR		  0		/* エラーはありあません */
#define DNS_RCODE_FORMAT_ERROR		  1		/* フォーマットエラー */
#define DNS_RCODE_SERVER_FAILURE	  2		/* サーバー失敗 */
#define DNS_RCODE_NAME_ERROR		  3		/* 名前エラー */
#define DNS_RCODE_NOT_IMPLEMENTED	  4		/* 実装されていない */
#define DNS_RCODE_REFUSED		  5		/* 拒否 */

/* DNSQD.qtype、DNSRR.type */
#define DNS_TYPE_A			  1		/* ホストアドレス */
#define DNS_TYPE_NS			  2		/* 正式なネームサーバ */
#define DNS_TYPE_MD			  3		/* メール宛先 (時代遅れ、MXを使うこと) */
#define DNS_TYPE_MF			  4		/* メール転送 (時代遅れ、MXを使うこと) */
#define DNS_TYPE_CNAME			  5		/* 別名に対する標準名 */
#define DNS_TYPE_SOA			  6		/* 正式ゾーンの開始表示 */
#define DNS_TYPE_MB			  7		/* メールボックスドメイン名 (実験的) */
#define DNS_TYPE_MG			  8		/* メールグループメンバー (実験的) */
#define DNS_TYPE_MR			  9		/* メール改名ドメイン名 (実験的) */
#define DNS_TYPE_NULL			 10		/* ヌル資源レコード (実験的) */
#define DNS_TYPE_WKS			 11		/* 周知のサービス記述 */
#define DNS_TYPE_PTR			 12		/* ドメイン名ポインタ */
#define DNS_TYPE_HINFO			 13		/* ホスト情報 */
#define DNS_TYPE_MINFO			 14		/* メールボックスあるいはメールリスト情報 */
#define DNS_TYPE_MX			 15		/* メール交換 */
#define DNS_TYPE_TXT			 16		/* テキスト文字列 */

/* DNSQD.qclass、DNSRR.class */
#define DNS_CLASS_IN			  1		/* インターネット */
#define DNS_CLASS_CS			  2		/* CSNETクラス (時代遅れ−ある時代遅れのRFCで例にだけ使われた) */
#define DNS_CLASS_CH			  3		/* カオスクラス */
#define DNS_CLASS_HS			  4		/* ヘシオド [Dyer 87] */

#define DNS_UDP_MAX			512		/* DNSメッセージのUDPデータ最大長 (UDPヘッダ含まず) */

/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/

/* DNSメッセージ圧縮を行い、名前を格納します。
 * [in]
 *	buffer		DNSメッセージ構築用バッファの先頭アドレス。(以下、構築バッファと呼ぶ)
 *			構築バッファに格納済みの、DNSヘッダの先頭アドレスを指定してください。
 *	buffer_capacity	構築バッファの許容量。
 *	offset		名前を格納する位置の、構築バッファ先頭からのオフセット。
 *			構築バッファ先頭〜オフセットまでの領域は、既に初期化されていること。
 *			(2005/09/21現在の実装では、圧縮を行わず、二回以上現れた名前もそのまま格納します。
 *			 従って現在は“バッファ先頭〜オフセット領域が初期化されている”必要はありません。
 *			 しかし将来、圧縮を行うよう変更した場合のために、上述の制限を課すことにしました。)
 *	name		格納する名前。
 * [out]
 *	戻り値		成功ならば、格納終端(+1)の位置を、構築バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_put_name(void* _buffer/*[buffer_capacity]*/, int buffer_capacity, int offset, const char* name)
{
	unsigned char* buffer = _buffer;
	//
	int label_length;

	do {
		/* '.'の連続をスキップします。 */
		label_length = strspn(name, ".");
		name += label_length;

		/* ラベル長を取得します。(名前の終端ならば0) */
		label_length = strcspn(name, ".");

		/* ラベルの最大長は、63文字です。(DNS仕様) */
		if(label_length > 63) {
			return -1; /* 名前不正 */
		}

		/* 構築バッファに、ラベル長とラベルを格納できるか、調べます。 */
		if((offset + 1 + label_length) > buffer_capacity) {
			return -1; /* 構築バッファ不足 */
		}

		/* 構築バッファに、ラベル長とラベルを格納します。 */
		buffer[offset++] = label_length;
		memcpy(&buffer[offset], name, label_length);
		offset += label_length;
		name   += label_length;

	} while(label_length); /* 名前の終端ならば抜けます。(終端の0格納済み) */

	return offset;
}

/* DNSメッセージ展開を行い、名前を取得します。
 * [in]
 *	buffer		DNSメッセージ受信バッファの先頭アドレス。(以下、受信バッファと呼ぶ)
 *			受信バッファに受信した、DNSヘッダの先頭アドレスを指定してください。
 *	buffer_length	DNSメッセージ長。
 *	offset		名前を取得する位置の、受信バッファ先頭からのオフセット。
 *	name		名前を格納するバッファの先頭アドレス。(以下、名前バッファと呼ぶ)
 *			NULLを指定すると、名前を格納せずに、捨てます。
 *	name_capacity	名前バッファの許容量。
 *			name=NULLを指定した場合も、name_capacityは有効です。
 *			任意長の名前を受け入れるには、次のように呼び出してください。
 *			retval = dns_get_name(buffer, buffer_length, offset, NULL, INT_MAX);
 * [out]
 *	戻り値		成功ならば、名前終端(+1)の位置を、受信バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_get_name(const void* _buffer, int buffer_length, int offset, char* name/*[name_capacity]*/, int name_capacity)
{
	const unsigned char* buffer = _buffer;
	//
	int next_offset = offset;
	int name_length = 0;
	//
	int label_length;

	for(;;) {
		/* ラベル長、または、圧縮ポインタの1バイト目を取得します。 */
		if((offset < 0) || (offset > buffer_length - 1)) {
			return -1; /* DNSメッセージ不正 */
		}
		label_length = buffer[offset++];
		if(offset > next_offset) {
			next_offset = offset;
		}

		/* 上位2bitによって、ラベル長、または、圧縮ポインタを判定します。 */
		switch(label_length & 0xc0) {

		case 0x00: /* 00xxxxxx = ラベル長 (0-63) */

			/* ラベル長が0ならば、名前の終端なので、抜けます。 */
			if(!label_length) {
				goto L_EXIT;
			}

			/* ラベルが受信バッファの末尾を超えていないか、調べます。 */
			if((offset + label_length) > buffer_length) {
				return -1; /* DNSメッセージ不正 */
			}

			/* 名前バッファに、ラベルと'.'を格納できるか、調べます。 */
			if((name_length + label_length + 1) > name_capacity) {
				return -1; /* 名前バッファ不足 */
			}

			/* 名前バッファに、ラベルと'.'を格納します。 */
			if(name) {
				memcpy(&name[name_length], &buffer[offset], label_length);
				name[name_length + label_length] = '.';
			}
			name_length += (label_length + 1);

			/* ラベルの分、オフセットを進めます。 */
			offset += label_length;
			if(offset > next_offset) {
				next_offset = offset;
			}

			break;

		case 0xc0: /* 11xxxxxx xxxxxxxx = 圧縮ポインタ */

			/* 圧縮ポインタを取得します。 */
			if((offset < 0) || (offset > buffer_length - 1)) {
				return -1; /* DNSメッセージ不正 */
			}
			label_length = (label_length & ~0xc0) << 8 | buffer[offset++];
			if(offset > next_offset) {
				next_offset = offset;
			}

			/* 圧縮ポインタは、走査位置を後ろへ進めることはできません。 */
			if(label_length >= offset) {
				return -1; /* DNSメッセージ不正 */
			}

			/* 走査位置を前へ戻します。 */
			offset = label_length;

			break;

		default: /* 01xxxxxx or 10xxxxxx */

			return -1; /* DNSメッセージ不正 */
		}
	}

L_EXIT:

	/* 最後に格納した'.'を、終端の'\0'に置き換えます。 */
	if(name && name_length) {
		name[name_length - 1] = '\0';
	}

	return next_offset;
}

/*---------------------------------------------------------------------------*/

/* DNSヘッダを格納します。
 * [in]
 *	buffer		DNSメッセージ構築用バッファの先頭アドレス。(以下、構築バッファと呼ぶ)
 *	buffer_capacity	構築バッファの許容量。
 *	header		格納するDNSヘッダ。
 *	戻り値		成功ならば、格納終端(+1)の位置を、構築バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_put_header(void* _buffer/*[buffer_capacity]*/, int buffer_capacity, const DNSHEADER* header)
{
	const unsigned char* src = (const unsigned char*)header;
	      unsigned char* dst =                      _buffer;
	//
	int offset;
	int hw;

	if(sizeof(DNSHEADER) != 12) {
		DIE(); /* DNSHEADERの定義が誤り */
	}
	if(buffer_capacity < 12) {
		return -1; /* 構築バッファ不足 */
	}

	for(offset = 0; offset < 12; offset += 2) {
		hw = GET_BEHALF(&src[offset]);
		     PUT_LEHALF(&dst[offset], hw);
	}

	return offset;
}

/* DNSヘッダを取得します。
 * [in]
 *	buffer		DNSメッセージ受信バッファの先頭アドレス。(以下、受信バッファと呼ぶ)
 *	buffer_length	DNSメッセージ長。
 *	header		DNSヘッダを格納する変数のアドレス。
 * [out]
 *	戻り値		成功ならば、DNSヘッダ終端(+1)の位置を、受信バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_get_header(const void* _buffer, int buffer_length, DNSHEADER* header)
{
	const unsigned char* src =                _buffer;
	      unsigned char* dst = (unsigned char*)header;
	//
	int offset;
	int hw;

	if(sizeof(DNSHEADER) != 12) {
		DIE(); /* DNSHEADERの定義が誤り */
	}
	if(buffer_length < 12) {
		return -1; /* DNSメッセージ不正 */
	}

	for(offset = 0; offset < 12; offset += 2) {
		hw = GET_BEHALF(&src[offset]);
		     PUT_LEHALF(&dst[offset], hw);
	}

	return offset;
}

/*---------------------------------------------------------------------------*/

/* 質問レコードを格納します。
 * [in]
 *	buffer		DNSメッセージ構築用バッファの先頭アドレス。(以下、構築バッファと呼ぶ)
 *			構築バッファに格納済みの、DNSヘッダの先頭アドレスを指定してください。
 *	buffer_capacity	構築バッファの許容量。
 *	offset		質問レコードを格納する位置の、構築バッファ先頭からのオフセット。
 *	qname		格納する質問名。
 *	qd		格納する質問レコード。
 * [out]
 *	戻り値		成功ならば、格納終端(+1)の位置を、構築バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_put_qd(void* _buffer/*[buffer_capacity]*/, int buffer_capacity, int offset, const char* qname, const DNSQD* qd)
{
	unsigned char* buffer = _buffer;

	/* 質問名を格納します。 */
	offset = dns_put_name(buffer, buffer_capacity, offset, qname);
	if(offset < 0) {
		return -1; /* 構築バッファ不足 */
	}

	/* 問い合わせ種別(2)、問い合わせクラス(2)を格納します。 */
	if((offset + 2 + 2) > buffer_capacity) {
		return -1; /* 構築バッファ不足 */
	}
	PUT_BEHALF(&buffer[offset + 0], qd->qtype );
	PUT_BEHALF(&buffer[offset + 2], qd->qclass);
	offset += (2 + 2);

	return offset;
}

/* 質問レコードを取得します。
 * [in]
 *	buffer		DNSメッセージ受信バッファの先頭アドレス。(以下、受信バッファと呼ぶ)
 *			受信バッファに受信した、DNSヘッダの先頭アドレスを指定してください。
 *	buffer_length	DNSメッセージ長。
 *	offset		質問レコードを取得する位置の、受信バッファ先頭からのオフセット。
 *	qname		質問名を格納するバッファの先頭アドレス。(以下、質問名バッファと呼ぶ)
 *			NULLを指定すると、質問名を格納せずに、捨てます。
 *	qname_capacity	質問名バッファの許容量。
 *			qname=NULLを指定した場合も、aname_capacityは有効です。
 *			任意長の質問名を受け入れるには、次のように呼び出してください。
 *			retval = dns_get_qd(buffer, buffer_length, offset, NULL, INT_MAX, qd);
 *	qd		質問レコードを格納する変数のアドレス。
 * [out]
 *	戻り値		成功ならば、質問レコード終端(+1)の位置を、受信バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 */
static int /* next offset */
dns_get_qd(const void* _buffer, int buffer_length, int offset, char* qname/*[qname_capacity]*/, int qname_capacity, DNSQD* qd)
{
	const unsigned char* buffer = _buffer;

	/* 質問名を取得します。 */
	offset = dns_get_name(buffer, buffer_length, offset, qname, qname_capacity);
	if(offset < 0) {
		return -1; /* DNSメッセージ不正 */
	}

	/* 問い合わせ種別(2)、問い合わせクラス(2)を取得します。 */
	if((offset + 2 + 2) > buffer_length) {
		return -1; /* DNSメッセージ不正 */
	}
	qd->qtype  = BEHALF(&buffer[offset + 0]);
	qd->qclass = BEHALF(&buffer[offset + 2]);
	offset += (2 + 2);

	return offset;
}

/*---------------------------------------------------------------------------*/

/* 資源レコードを取得します。
 * [in]
 *	buffer		DNSメッセージ受信バッファの先頭アドレス。(以下、受信バッファと呼ぶ)
 *			受信バッファに受信した、DNSヘッダの先頭アドレスを指定してください。
 *	buffer_length	DNSメッセージ長。
 *	offset		資源レコードを取得する位置の、受信バッファ先頭からのオフセット。
 *	name		名前を格納するバッファの先頭アドレス。(以下、名前バッファと呼ぶ)
 *			NULLを指定すると、名前を格納せずに、捨てます。
 *	name_capacity	名前バッファの許容量。
 *			name=NULLを指定した場合も、name_capacityは有効です。
 *			任意長の名前を受け入れるには、次のように呼び出してください。
 *			retval = dns_get_rr(buffer, buffer_length, offset, NULL, INT_MAX, rr);
 *	rr		資源レコードを格納する変数のアドレス。
 * [out]
 *	戻り値		成功ならば、資源レコード終端(+1)の位置を、受信バッファ先頭からのオフセットで返します。
 *			失敗ならば、負数を返します。
 * [note]
 *	* DNSRR.rdataフィールドは、受信バッファ内の資源データを直接ポイントしています。
 *	  DNSRR.rdataを参照する間、受信バッファの内容を維持してください。
 */
static int /* next offset */
dns_get_rr(const void* _buffer, int buffer_length, int offset, char* name/*[name_capacity]*/, int name_capacity, DNSRR* rr)
{
	const unsigned char* buffer = _buffer;

	/* 名前を取得します。 */
	offset = dns_get_name(buffer, buffer_length, offset, name, name_capacity);
	if(offset < 0) {
		return -1; /* DNSメッセージ不正 */
	}

	/* タイプ(2)、クラス(2)、生存時間(4)、資源データ長(2)を取得します。 */
	if((offset + 2 + 2 + 4 + 2) > buffer_length) {
		return -1; /* DNSメッセージ不正 */
	}
	rr->type     = BEHALF(&buffer[offset +  0]);
	rr->class    = BEHALF(&buffer[offset +  2]);
	rr->ttl      = BEWORD(&buffer[offset +  4]);
	rr->rdlength = BEHALF(&buffer[offset +  8]);
	offset += (2 + 2 + 4 + 2);

	/* 資源データを取得します。 */
	if((offset + rr->rdlength) > buffer_length) {
		return -1; /* DNSメッセージ不正 */
	}
	rr->rdata = &buffer[offset];
	offset += rr->rdlength;

	return offset;
}

/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/

static int dns_server;	/* DNSサーバのIPアドレス */

void
dns_set_server(int ip_address)
{
	dns_server = ip_address;
}

int
dns_get_server()
{
	return dns_server;
}

int
dns_query(const char* qname, int timeout)
{
	int packet_length;
	unsigned char* packet = NULL; /* 要解放 */
	//
	UDPSOCKET* socket = NULL; /* 要解放 */
	//
	int result = 0;
	int T0 = pceTimerGetCount();
	int id = (unsigned short)T0;
	//
	DNSHEADER header;
	DNSQD qd;
	DNSRR rr;
	int offset;
	int remote_ip_address;
	int remote_port;
	int a_record;
	int i;

	/* タイムアウトに負数が指定されていたら、既定値とします。 */
	if(timeout < 0) {
		timeout = 1000; /* 調整可 */
	}

	/* 構築バッファ、兼、受信バッファを確保します。 */
	packet = malloc(DNS_UDP_MAX);
	if(!packet) {
		DIE(); /* メモリ不足 */
	}

	/* 質問メッセージを構築します。 */
	memset(&header, 0, sizeof(DNSHEADER));
	header.id	= id;	/* 識別子 */
	header.rd	= 1;	/* 再帰要求 */
	header.qdcount	= 1;	/* 質問レコードの数 */
	offset = dns_put_header(packet, DNS_UDP_MAX, &header);
	if(offset < 0) {
		goto L_EXIT; /* 有り得ない */
	}
	memset(&qd, 0, sizeof(DNSQD));
	qd.qtype	= DNS_TYPE_A;	/* 問い合わせ種別 */
	qd.qclass	= DNS_CLASS_IN;	/* 問い合わせクラス */
	offset = dns_put_qd(packet, DNS_UDP_MAX, offset, qname, &qd);
	if(offset < 0) {
		goto L_EXIT; /* ドメイン名が異常に長い? */
	}

	/* UDPソケットを開きます。 */
	socket = udp_socket_open(0/*エフェメラルポート*/, DNS_UDP_MAX);

	/* 質問メッセージを送信できるか、または、タイムアウトするまで... */
	while(pceTimerGetCount() - T0 < timeout) {

		/* 質問メッセージを送信します。 */
		packet_length = udp_socket_send(socket, dns_server, 53/*DNS*/, packet, offset);
		if(packet_length >= 0) {
			break; /* 送信成功 */
		}

		/* 質問メッセージを送信前に届いたパケットは、破棄します。 */
		udp_socket_recv(socket, NULL, NULL, packet, DNS_UDP_MAX);
	}

	/* 正しい回答メッセージ(否定応答含む)を受信できるか、または、タイムアウトするまで... */
	while(pceTimerGetCount() - T0 < timeout) {

		/* このループで受信したパケットから、正しいAレコードを発見したら、ここにそのIPアドレスを格納します。 */
		a_record = 0;

		/* 回答メッセージを受信します。 */
		packet_length = udp_socket_recv(socket, &remote_ip_address, &remote_port, packet, DNS_UDP_MAX);
		if(packet_length < 0) {
			continue; /* 回答メッセージ未到着。受信を継続します */
		}
		if((remote_ip_address != dns_server) || (remote_port != 53/*DNS*/)) {
			continue; /* 送信元不正。パケットを破棄し、受信を継続します */
		}

		/* ヘッダを取得します。
		 * ヘッダが不正ならば、偶然このポートに到着したゴミパケットと考えられるので、破棄し、受信を継続します。
		 */
		offset = dns_get_header(packet, packet_length, &header);
		if(offset < 0) {
			continue; /* ヘッダ不正。パケットを破棄し、受信を継続します */
		}
	
		/* 識別子と、質問or回答を検査します。識別子が不一致、または、回答メッセージでなければ、
		 * 以前の質問に対する回答が重複して到着したか、ゴミパケットと考えられるので、破棄し、受信を継続します。
		 */
		if((header.id != id) ||	/* 識別子が不一致 */
		   (header.qr !=  1)) {	/* 回答メッセージでない */
			continue;	/* パケットを破棄し、受信を継続します */
		}

		/* ここまでの検査で、この回答メッセージは、今回の質問に対応するものであることが確定しました。
		 * これ以降でエラーを検出したら、パケットを破棄して受信を継続するのでなく、否定応答として受け入れます。
		 * レコードの取得に失敗した場合(破損？)も、これ以上待っても新しいメッセージが到着する可能性は無いので、
		 * このサーバとのやりとりでは正しい回答が得られないものと考えて、否定応答と同じ扱いとします。
		 */

		/* 切り詰めと、回答コードを検査します。 */
		if((header.tc    !=  0) ||		   /* 回答が長すぎて切り詰められた */
		   (header.rcode != DNS_RCODE_NO_ERROR)) { /* エラー有り(否定応答) */
			goto L_EXIT; /* 否定応答と見なし、失敗とします */
		}

		/* 質問レコードを取得します。(破損検査のみ) */
		for(i = 0; i < header.qdcount; i++) {
			offset = dns_get_qd(packet, packet_length, offset, NULL, INT_MAX, &qd);
			if(offset < 0) {
				goto L_EXIT; /* 質問レコード不正。否定応答と見なし、失敗とします */
			}
		}

		/* 回答レコードから、最初のAレコードを取得します。 */
		for(i = 0; i < header.ancount; i++) {
			offset = dns_get_rr(packet, packet_length, offset, NULL, INT_MAX, &rr);
			if(offset < 0) {
				goto L_EXIT; /* 回答レコード破損。否定応答と見なし、失敗とします */
			}
			if(!a_record) { /* Aレコード未発見で、正しいAレコードを発見したら... */
				if((rr.type == DNS_TYPE_A) && (rr.class == DNS_CLASS_IN) && (rr.rdlength == 4)) {
					a_record = BEWORD(rr.rdata); /* IPアドレスを取得します */
				}
			}
		}

		/* 権威レコードを取得します。(破損検査のみ) */
		for(i = 0; i < header.nscount; i++) {
			offset = dns_get_rr(packet, packet_length, offset, NULL, INT_MAX, &rr);
			if(offset < 0) {
				goto L_EXIT; /* 権威レコード破損。否定応答と見なし、失敗とします */
			}
		}

		/* 追加レコードを取得します。(破損検査のみ) */
		for(i = 0; i < header.arcount; i++) {
			offset = dns_get_rr(packet, packet_length, offset, NULL, INT_MAX, &rr);
			if(offset < 0) {
				goto L_EXIT; /* 追加レコード破損。否定応答と見なし、失敗とします */
			}
		}

		/* Aレコードを発見できていたら(a_record!=0)、その値を結果として、処理を返します。(成功)
		 * Aレコードを発見できなければ(a_record==0)、その値を結果として、処理を返します。(失敗)
		 * いずれにせよ、この回答メッセージを受け入れることになるので、ここでループを抜けます。
		 */
		result = a_record;
		break; /* ここまで */
	}

L_EXIT:	/*-------------------------------------------------------------------*/

	/* UDPソケットを閉じます。 */
	if(socket) {
		udp_socket_close(socket);
	}

	/* 構築バッファ、兼、受信バッファを開放します。 */
	free(packet);

	return result;
}

/*****************************************************************************
 *	Windowsサンプルプログラム
 *****************************************************************************/
#if 0

/* DNSクライアントモジュールの内部ルーチンは、フル機能に近いレベルで実装していますが、
 * 2005/09/22現在の実装では、それらの機能をあまり利用していません。
 * 今後、機能を利用するときのための参考として、DNS回答メッセージの内容を一覧表示する、
 * Windowsプログラムを残しておくことにします。プログラムの出力は、次のように鳴ります。
 *
 *	=== 質問レコード ===
 *	www.yahoo.co.jp     A  IN 
 *	=== 回答レコード ===
 *	www.yahoo.co.jp     A  IN 202.93.91.215 
 *	www.yahoo.co.jp     A  IN 202.93.91.216 
 *	www.yahoo.co.jp     A  IN 202.93.91.217 
 *	www.yahoo.co.jp     A  IN 202.93.91.218 
 *	www.yahoo.co.jp     A  IN 202.93.91.219 
 *	www.yahoo.co.jp     A  IN 202.229.198.216 
 *	www.yahoo.co.jp     A  IN 202.229.199.136 
 *	www.yahoo.co.jp     A  IN 202.229.199.147 
 *	www.yahoo.co.jp     A  IN 203.141.35.113 
 *	www.yahoo.co.jp     A  IN 210.80.242.236 
 *	www.yahoo.co.jp     A  IN 210.80.243.14 
 *	www.yahoo.co.jp     A  IN 210.81.3.241 
 *	www.yahoo.co.jp     A  IN 210.81.150.5 
 *	www.yahoo.co.jp     A  IN 202.93.91.214 
 *	=== 権威レコード ===
 *	yahoo.co.jp         NS IN dnsn201.yahoo.co.jp
 *	yahoo.co.jp         NS IN dnsg01.yahoo.co.jp
 *	=== 追加レコード ===
 *	dnsg01.yahoo.co.jp  A  IN 211.14.12.10 
 *	dnsn201.yahoo.co.jp A  IN 202.229.198.120 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define DebugBreak abort
#include "C:/Home/Share/Piece/clip/clipmisc.h"

/* ★★★ ここに、DNSクライアントモジュールのソースコードを含めてください ★★★ */

/* "nslookup www.yahoo.co.jp."の、回答メッセージをキャプチャしました。(UDPデータ本体) */
unsigned char sample_data[] = {
	0x00,0x05,0x81,0x80,0x00,0x01,0x00,0x0e,0x00,0x02,0x00,0x02,0x03,0x77,0x77,0x77,
	0x05,0x79,0x61,0x68,0x6f,0x6f,0x02,0x63,0x6f,0x02,0x6a,0x70,0x00,0x00,0x01,0x00,
	0x01,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xd7,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xd8,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xd9,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xda,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xdb,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0xe5,0xc6,
	0xd8,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0xe5,0xc7,
	0x88,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0xe5,0xc7,
	0x93,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xcb,0x8d,0x23,
	0x71,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xd2,0x50,0xf2,
	0xec,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xd2,0x50,0xf3,
	0x0e,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xd2,0x51,0x03,
	0xf1,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xd2,0x51,0x96,
	0x05,0xc0,0x0c,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0xca,0x5d,0x5b,
	0xd6,0xc0,0x10,0x00,0x02,0x00,0x01,0x00,0x00,0x03,0x83,0x00,0x0a,0x07,0x64,0x6e,
	0x73,0x6e,0x32,0x30,0x31,0xc0,0x10,0xc0,0x10,0x00,0x02,0x00,0x01,0x00,0x00,0x03,
	0x83,0x00,0x09,0x06,0x64,0x6e,0x73,0x67,0x30,0x31,0xc0,0x10,0xc1,0x23,0x00,0x01,
	0x00,0x01,0x00,0x00,0x03,0x83,0x00,0x04,0xd3,0x0e,0x0c,0x0a,0xc1,0x0d,0x00,0x01,
	0x00,0x01,0x00,0x00,0x02,0x9f,0x00,0x04,0xca,0xe5,0xc6,0x78,
};

void
print_type(int type)
{
	switch(type) {
	case DNS_TYPE_A:     printf("%-3s", "A"    ); break;
	case DNS_TYPE_NS:    printf("%-3s", "NS"   ); break;
	case DNS_TYPE_MD:    printf("%-3s", "MD"   ); break;
	case DNS_TYPE_MF:    printf("%-3s", "MF"   ); break;
	case DNS_TYPE_CNAME: printf("%-3s", "CNAME"); break;
	case DNS_TYPE_SOA:   printf("%-3s", "SOA"  ); break;
	case DNS_TYPE_MB:    printf("%-3s", "MB"   ); break;
	case DNS_TYPE_MG:    printf("%-3s", "MG"   ); break;
	case DNS_TYPE_MR:    printf("%-3s", "MR"   ); break;
	case DNS_TYPE_NULL:  printf("%-3s", "NULL" ); break;
	case DNS_TYPE_WKS:   printf("%-3s", "WKS"  ); break;
	case DNS_TYPE_PTR:   printf("%-3s", "PTR"  ); break;
	case DNS_TYPE_HINFO: printf("%-3s", "HINFO"); break;
	case DNS_TYPE_MINFO: printf("%-3s", "MINFO"); break;
	case DNS_TYPE_MX:    printf("%-3s", "MX"   ); break;
	case DNS_TYPE_TXT:   printf("%-3s", "TXT"  ); break;
	default:             printf("%3d" , type   ); break;
	}
}

void
print_class(int class)
{
	switch(class) {
	case DNS_CLASS_IN:   printf("%-3s", "IN" ); break;
	case DNS_CLASS_CS:   printf("%-3s", "CS" ); break;
	case DNS_CLASS_CH:   printf("%-3s", "CH" ); break;
	case DNS_CLASS_HS:   printf("%-3s", "HS" ); break;
	default:             printf("%3d" , class); break;
	}
}

int
main()
{
	char name[256];
	DNSHEADER header;
	DNSQD qd;
	DNSRR rr;
	int offset;
	int i;
	int n;

	/* ヘッダを読みます。 */
	offset = dns_get_header(sample_data, sizeof sample_data, &header);
	if(offset < 0) {
		DIE();
	}

	/* 質問レコードを表示します。 */
	printf("=== 質問レコード ===\n");
	for(i = 0; i < header.qdcount; i++) {
		offset = dns_get_qd(sample_data, sizeof sample_data, offset, name, sizeof name, &qd);
		if(offset < 0) {
			DIE();
		}
		printf("%-20s", name);
		print_type(qd.qtype);
		print_class(qd.qclass);
		printf("\n");
	}

	/* 回答レコード、権威レコード、追加レコードを表示します。 */
	for(i = 0; i < 3; i++) {
		switch(i) {
		case 0:  printf("=== 回答レコード ===\n"); n = header.ancount; break;
		case 1:  printf("=== 権威レコード ===\n"); n = header.nscount; break;
		default: printf("=== 追加レコード ===\n"); n = header.arcount; break;
		}
		while(n--) {
			offset = dns_get_rr(sample_data, sizeof sample_data, offset, name, sizeof name, &rr);
			if(offset < 0) {
				DIE();
			}
			printf("%-20s", name);
			print_type(rr.type);
			print_class(rr.class);
			switch(rr.type) {
			case DNS_TYPE_A:
				if(rr.rdlength != 4) {
					DIE();
				}
				printf("%d.%d.%d.%d ", rr.rdata[0], rr.rdata[1], rr.rdata[2], rr.rdata[3]);
				break;
			case DNS_TYPE_NS:
				if(dns_get_name(sample_data, sizeof sample_data, rr.rdata - sample_data, name, sizeof name) < 0) {
					DIE();
				}
				printf("%s", name);
				break;
			}
			printf("\n");
		}
	}

	return 0;
}

#endif
/*****************************************************************************
 *	
 *****************************************************************************/
