gethostbyaddr 探検

前回の大河小説を見ていたら、こんなのが

以前から気になっていることがあるのですが、この連載の主題の「探検」。パソコンで
漢字変換した際に、木へんの「検」とこざとへんの「険」が出てきます。
で、ググってみたところ

        探検: 調査
        探険: 危険を伴う探検

全く気にもとめてませんでした。上手に使い分けろよ。まあ、オイラーがやる事は、観光なんで 探検ですな。

行ってQと言う番組、どんどん過激になるな。受けを狙って無謀な事をさせてる。タレントさんは、きっと拒否出来ないんだろうね。代わりはいくらでもいますからって。ブラック番組と思うぞ。そのうちに大事故が起きないか心配。とか言いながら、見ちゃうんだな。見てる分には、安全ですから。

dig 探検

原典にあたります。

ドメイン名 - 実装と仕様

前回、逆引きして、12人に1人は、名無しの権兵衛ってのを見つけ、これは諜報機関に違いないって結論を出しちゃった。原典では、逆引きはオプションってしっかり書いてあったぞ。 こういうのをdigすると、ちゃんと未対応って回答が有るのかな。後でやってみよう。

安全・安心なソース観光コースを用意しましょ。勿論安全・安心と言ったらOpenBSD。 ソースの在処は、bindの中のbin中にあった。これって、bindの成果物でユーザーに提供する コマンド類の元ソース置き場だな。それにしては、有名なnamedが無いぞ。どーなってんの?

慌てて、NetBSDを参照してみる。

/usr/src/external/bsd/bind
nb8$ ls bin
CVS/            check/          dig/            html/           nsupdate/
Makefile        confgen/        dnssec/         named/          rndc/
Makefile.inc    delv/           host/           nslookup/       tools/

こちらは、bindのフルセットが採用されてました。ちゃんと namedも居るしね。

ふと、man3の原稿置き場で、ls get* したら、面白いものを発見。寄り道は3文の得だね。

nb8$ getaddrinfo www.NetBSD.org
dgram inet6 udp 2001:470:a085:999::80 0
dgram inet udp 199.233.217.205 0
stream inet6 tcp 2001:470:a085:999::80 0
stream inet tcp 199.233.217.205 0

それはいいんだけど、OpenBSDで消えてしまっているnamedはどうなった? リリースノートを見ていたら、そっと書いてあったぞ。

DESCRIPTION
       NSD is a complete implementation of an authoritative DNS nameserver.
       Upon startup, NSD will read the database specified with -f database
       argument and put itself into background and answers queries on port 53
        :
AUTHORS
       NSD was written by NLnet Labs and RIPE NCC joint team. Please see
       CREDITS file in the distribution for further details.

大事な部分だから、別の船に乗り換えたんか。これで、毒入りパケットを防ごうって魂胆なんだな。何かとbind方面は騒々しいですから。

原点復帰。digをgdbにかけられるように独自にコンパイルしたい。 bindのdir下に、configureが有るんで、そこでごちゃごちゃしろって事だな。/tmpにそっくりコピーして、configしてからmakeしたら、無事にdigが出来上がった。

けど、惜しいかな、-g -O2 なんだよな。Makefileを書き換えちゃえ。

ob64$ for f in `find . -name Makefile`
> do
> sed -e 's/-g -O2/-ggdb3 -O0/' < $f  > $$
> mv $$ $f
> done

無事にdigが出来たので、yahoo.co.jpのIPを逆引きしてみる。

(gdb) b dig.c:1345
Breakpoint 1 at 0x4c12: file dig.c, line 1345.
(gdb) r -x 183.79.135.206
Starting program: /tmp/bind/bin/dig/dig -x 183.79.135.206

Breakpoint 1, dash_option (option=0xcf7d79bf "x", next=0xcf7d79c1 "183.79.135.2\
06", lookup=0xcf7d735c, open_type_class=0xcf7d7358, need_clone=0xcf7d733c, conf\
ig_only=isc_boolean_false, argc=3, argv=0xcf7d7904, firstarg=0xcf7d7360) at dig\
.c:1345
1345                    if (get_reverse(textname, sizeof(textname), value,

ちょいと進めると、問い合わせ内容が作成されてた。

(gdb) p *(*lookup)
$7 = {
  pending = isc_boolean_true,
  waiting_connect = (unknown: 3200171710),
   :
  textname = "206.135.79.183.in-addr.arpa.\000", '\276' <repeats 995 times>,
  cmdline = "\000", '\276' <repeats 1023 times>,
  rdtype = 12,
  qrdtype = 1,
  rdclass = 1,

先へ進めて

(gdb)
1783            check_result(result, "isc_app_onrun");
(gdb)
1784            isc_app_run();

isc_app_run()を実行した時、下記の結果が得られたよ。

;; ANSWER SECTION:
206.135.79.183.in-addr.arpa. 900 IN     PTR     f1.top.vip.kks.yahoo.co.jp.

やっぱり、ソースだけを目視してたら、こういう風に現場を押さえられなかっただろね。

続いて、isc_app_runに舞台を移して、トレースしたけど、

(gdb)
475             result = handle_signal(SIGHUP, reload_action);
(gdb)
476             if (result != ISC_R_SUCCESS)
(gdb)
547             (void)isc__taskmgr_dispatch();
(gdb)
549             result = evloop();
(gdb)

Program received signal SIGTERM, Terminated.
kill () at -:3
3       -: No such file or directory.

こういう難解な部分が出てきたので、これはもう撤収だな。ただ想像するに、DNSサーバーとのやり取りになるんで、別タスクにそれを任せて、結果が返ってきたらSIGTERMで、お知らせしてくれるような作りになってるぽい。

gethostbyaddr

挫折したんで、別の手を考える。逆引きってどうやってるのを知りたい。これがdig観光の大目玉なんだ。(最初に目的書かなかったけどね)

前回golangで逆引きする手続きを書いた。いや、書いたと言うより、正引きのコードの一行を変更しただけ。軽やかに逆引きできた。

よくそんな逆引き関数を見つけたな? コードをじっと眺めていたら、netパッケージを見ろと訴えかけていたんで、その通りに Package net のLookupHostを探したのさ。そしたら、

type Resolver
    func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error)
    func (r *Resolver) LookupCNAME(ctx context.Context, host string) (cname string, err error)
    func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error)
    func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error)
    func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error)
    func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error)
    func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error)
    func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error)
    func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error)

こんな群れが見つかった。で、ピンときたんだ。C語のライブラリィーも、群れていないかと。

手元に有った、ネット本で、正引き関数を調べそいつをman gethostbyname。

NAME
     gethostbyname, gethostbyname2, gethostbyaddr, gethostent, sethostent,
     endhostent, hstrerror, herror - get network host entry

SYNOPSIS
     #include <netdb.h>

やっぱり群れてたね。名前で引くか、アドレスで引くかって事だ。惜しいかな、使い方の例は載っていなかった。

大先輩のお知恵を借りよう。誰もが最初は初心者。考えて出て来るような代物ではないからね。

IPアドレスからホスト名への変換

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main(int argc, char *argv[]) {
  char *whichadr;
  struct hostent *host;
  struct in_addr addr;

  whichadr = "127.0.0.1";
  if (argc > 1)
    whichadr = argv[1];

  addr.s_addr = inet_addr(whichadr);

  host = gethostbyaddr((const char *)&addr.s_addr,
                       sizeof(addr.s_addr), AF_INET);
  if (host == NULL) {
    /* gethostbyaddrのエラーはperrorでは無いので注意 */
    herror("gethostbyaddr");
    return 1;
  }

  printf("%s\n", host->h_name);

  return 0;
}

先輩のコードをちょっと変更して、引数でIPアドレスを受け付けるようにしてみた。

折角なので、少し逆引きしてみる。

ob6$ ./a.out xxx.xxx.xxx.xxx  # 名無しの権兵衛さん(代表)
gethostbyaddr: Unknown host
ob6$ ./a.out 10.0.2.15        # vbox のNAT環境で配られるやつ
gethostbyaddr: Unknown host
ob6$ ./a.out 192.168.10.10    # ローカル代表
gethostbyaddr: Unknown host
ob6$ ./a.out 172.217.25.195   # www.google.co.jp
nrt12s13-in-f3.1e100.net

digと答え合わせしたら、あってたよ。よかったね。

エラー時の検出はNULLでやってるけど、他のエラーは無いのかなと、FreeBSDのmanを見ていたら、何とgethostbyaddrの例が載ってた。例が載ってるって事は、使い方が難解な部類に入るのだろうか?

一つのOSに固執しないで、色々と見比べてみると利得が大きくなるって例だな。

EXAMPLES
     Print out the hostname associated with a specific IP address:

           const char *ipstr = "127.0.0.1";
           struct in_addr ip;
           struct hostent *hp;

           if (!inet_aton(ipstr, &ip))
                   errx(1, "can't parse IP address %s", ipstr);

           if ((hp = gethostbyaddr((const void *)&ip,
               sizeof ip, AF_INET)) == NULL)
                   errx(1, "no name associated with %s", ipstr);

           printf("name associated with %s is %s\n", ipstr, hp->h_name);

gethostbyaddr 観光

折角C語の見通しの良いコードが有るので、探検してみる。

(gdb) p whichadr
$1 = 0x1c4385b00b30 "127.0.0.1"
(gdb) p addr.s_addr
$2 = 16777343
(gdb) p/x addr.s_addr
$3 = 0x100007f

逆引き直前の値。文字列表現のIPアドレスが、整数に変換された。意味不な数値になったけど、 16進数表記にしてみると、一目瞭然。7fってのは1バイトで数値に直すと127になる。色々なIPを指定して、楽しむのさ。

(gdb) s
gethostbyaddr (addr=0x7f7ffffc2c14, len=4, af=2) at /usr/src/lib/libc/asr/gethostnamadr.c:175
(gdb) info macro AF_INET
Defined at /usr/include/sys/socket.h:164
  included at /tmp/rev.c:3
#define AF_INET 2

OpenBSDの真骨頂。libcの生まで潜って行けますが、取り合えず先に進めます。

main (argc=1, argv=0x7f7ffffc2c68) at rev.c:20
20        if (host == NULL) {
(gdb) set print pretty
(gdb) p *host
$4 = {
  h_name = 0x1c459f4d84b8 <_entbuf+24> "localhost",
  h_aliases = 0x1c459f4d84a0 <_entbuf>,
  h_addrtype = 2,
  h_length = 4,
  h_addr_list = 0x1c459f4d84a8 <_entbuf+8>
}

ちゃんと答えが得られました。

そんじゃ、先ほどすっ飛ばしたlibcのソースにご対面しようと思って行ってみたら、asr_run.3なんてのがCのソースに混じって置いてあった。これはもう、 man asr_runする鹿ないな。

そして、 gethostnamadr_async.c:hostent_from_packet() この関数あたりかな。

(gdb) b hostent_from_packet
Breakpoint 1 at 0xeb1e2d5f746: file /usr/src/lib/libc/asr/gethostnamadr_async.c, line 521.
(gdb) r 172.217.25.195
Starting program: /tmp/a.out 172.217.25.195

Breakpoint 1, gethostnamadr_async_run (as=0x1bb2f506fb00, ar=0x7f7ffffc02c8)
    at /usr/src/lib/libc/asr/gethostnamadr_async.c:299
299                     h = hostent_from_packet(as->as_type,
(gdb) bt
#0  gethostnamadr_async_run (as=0x1bb2f506fb00, ar=0x7f7ffffc02c8)
    at /usr/src/lib/libc/asr/gethostnamadr_async.c:299
#1  0x00001bb2a50fe971 in _libc_asr_run (as=<optimized out>, ar=0x7f7ffffc02c8)
    at /usr/src/lib/libc/asr/asr.c:148
#2  _libc_asr_run_sync (as=0x1bb2f506fb00, ar=0x7f7ffffc02c8)
    at /usr/src/lib/libc/asr/asr.c:195
#3  0x00001bb2a510d59e in gethostbyaddr (addr=<optimized out>,
    len=<optimized out>, af=<optimized out>)
    at /usr/src/lib/libc/asr/gethostnamadr.c:183
#4  0x00001bb0074005c8 in main (argc=2, argv=0x7f7ffffc04d8) at rev.c:18

man resolverも参考になるな。

port 53

原典にあたった時、53番さんって言ってたので、DNSサーバーへ問い合わせしてみる。

ob64$ ./a.out 172.217.25.195
nrt12s13-in-f195.1e100.net

こんな問い合わせをした時に、どんなパケットが行くかモニターしてみる。貧乏人なので、wiresharkなんて飼えないのさ。伝統的なtcpdumpです。

ob64$ doas tcpdump -i em0 -t -n -s 200 port 53
tcpdump: listening on em0, link-type EN10MB
10.0.2.15.24174 > 8.8.8.8.53: [bad udp cksum fecd! -> 8172] 54155+ PTR? 195.25.217.172.in-addr.arpa.(45) (ttl 64, id 1529, len 73)
8.8.8.8.53 > 10.0.2.15.24174: [udp sum ok] 54155 q: PTR? 195.25.217.172.in-addr.arpa. 4/0/0 195.25.217.172.in-addr.arpa. PTR nrt12s13-in-f195.1e100.net., 195.25.217.172.in-addr.arpa. PTR nrt12s13-in-f3.1e100.net., 195.25.217.172.in-addr.arpa. PTR nrt12s13-in-f195.1e100.net., 195.25.217.172.in-addr.arpa. PTR nrt12s13-in-f3.1e100.net.(142) (ttl 64, id 945, len 170)

tcpdumpの引数の説明

-i em0   NIC em0のパケットをキャプチャ
-t       時刻はいらないよ
-n       わざわざサーバーの名前は調べなくてもいいよ。
-s 200   200Byteまでパケットを拾ってね。(デフォでは、64Byteだったかな)
         普通は -X して、bin/asciiもdumpさせる。少しはwiresharkに近づくぞ。
port 53  DNSとのやり取りだけキャプチャしてね

DNSサーバーは下記の有名なものを使ってみた。

ob6$ ./a.out 8.8.8.8
google-public-dns-a.google.com
ob6$ ./a.out 1.1.1.1
one.one.one.one

ワン ワン ワン ワンとふざけた名前が返ってきたけど、ちゃんとしたDNSサーバーだ。

ob6$ dig -x 8.8.8.8  @1.1.1.1
  :
;; ANSWER SECTION:
8.8.8.8.in-addr.arpa.   1038    IN      PTR     google-public-dns-a.google.com.

;; Query time: 17 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)

パケットの出入り確認

UDPってんで、前回の大河ドラマでも取り上げられてて好都合。例示されてたコードを盗み見した所、サーバー側では、sendto/recvfromを使ってて、クライアント側は、send/recv関数を使ってた。今回は、クライアントだな。

Breakpoint 1, _libc_send (s=3, msg=0xdfa3b75b600, len=44, flags=0)
    at /usr/src/lib/libc/net/send.c:39
39              return (sendto(s, msg, len, flags, NULL, 0));
(gdb) bt
#0  _libc_send (s=3, msg=0xdfa3b75b600, len=44, flags=0)
    at /usr/src/lib/libc/net/send.c:39
#1  0x00000df9fda183a6 in udp_send (as=0xdfa2a9a6400)
    at /usr/src/lib/libc/asr/res_send_async.c:434
#2  res_send_async_run (as=0xdfa2a9a6400, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/res_send_async.c:180
#3  0x00000df9fd9f67f2 in _libc_asr_run (as=0xdfa2a9a6400, ar=0xdfa3b75b600)
    at /usr/src/lib/libc/asr/asr.c:148
#4  0x00000df9fda0b708 in gethostnamadr_async_run (as=0xdfa2a9a5200,
    ar=0x7f7ffffd1818) at /usr/src/lib/libc/asr/gethostnamadr_async.c:278
#5  0x00000df9fd9f685d in _libc_asr_run (as=0xdfa2a9a5200, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/asr.c:148
#6  _libc_asr_run_sync (as=0xdfa2a9a5200, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/asr.c:195
#7  0x00000df9fda0559e in gethostbyaddr (addr=<optimized out>,
    len=<optimized out>, af=<optimized out>)
    at /usr/src/lib/libc/asr/gethostnamadr.c:183
#8  0x00000df7e5e005c8 in main (argc=2, argv=0x7f7ffffd1a28) at rev.c:18
(gdb) c
Continuing.

Breakpoint 2, _libc_recv (s=3, buf=0xdfa3b75e000, len=4096, flags=0)
    at /usr/src/lib/libc/net/recv.c:39
39              return (recvfrom(s, buf, len, flags, NULL, 0));
(gdb) bt
#0  _libc_recv (s=3, buf=0xdfa3b75e000, len=4096, flags=0)
    at /usr/src/lib/libc/net/recv.c:39
#1  0x00000df9fda1801a in udp_recv (as=<optimized out>)
    at /usr/src/lib/libc/asr/res_send_async.c:465
#2  res_send_async_run (as=0xdfa2a9a6400, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/res_send_async.c:193
#3  0x00000df9fd9f67f2 in _libc_asr_run (as=0xdfa2a9a6400, ar=0xdfa3b75e000)
    at /usr/src/lib/libc/asr/asr.c:148
#4  0x00000df9fda0b708 in gethostnamadr_async_run (as=0xdfa2a9a5200,
    ar=0x7f7ffffd1818) at /usr/src/lib/libc/asr/gethostnamadr_async.c:278
#5  0x00000df9fd9f6971 in _libc_asr_run (as=<optimized out>, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/asr.c:148
#6  _libc_asr_run_sync (as=0xdfa2a9a5200, ar=0x7f7ffffd1818)
    at /usr/src/lib/libc/asr/asr.c:195
#7  0x00000df9fda0559e in gethostbyaddr (addr=<optimized out>,
    len=<optimized out>, af=<optimized out>)
    at /usr/src/lib/libc/asr/gethostnamadr.c:183
#8  0x00000df7e5e005c8 in main (argc=2, argv=0x7f7ffffd1a28) at rev.c:18

man gethostbyaddr on Ubuntu

先輩の説明では、この関数で逆引き出来るのは、IPv4までだよってなってた。 遠い未来に使えるようになるであろうIPv6は、どうすんねん?

先輩が無料で公開されてるIPv6本をくまなく耕せば、その答えが出てきて、オイラーもIPv6対応になるのかな。ちょっと手抜きで、ウブに答えがないか探してみたよ。

       The  gethostbyaddr()  function  returns a structure of type hostent for
       the given host address addr of length len and address type type.  Valid
       address types are AF_INET and AF_INET6.  The host address argument is a
       pointer to a struct of a type depending on the address type, for  exam‐
       ple  a  struct in_addr * (probably obtained via a call to inet_addr(3))
       for address type AF_INET.

ああ、GNU extensions で、 gethostbyaddr_r を使えばってのを見落としていた。便利はいいんだけど、何となくロックインが気になる。_r は、再入可能って印か。

これを頭に置いて、BSD系のmanを見直してみるか。見直すような発見が待っててくれるかな?