ORA-12545エラーは本当に名前解決の問題であるかをNetトレースで検証する

# はじめに

Oracle Databaseへ外部から接続する際には、いくつかの典型的なエラーが発生します。特に、ホスト名やポート、サービス名などの設定が不適切な場合に起こるエラーは多く、以下のように分類できます。

```
ORA-12154: TNS:could not resolve the connect identifier specified
    → 接続先ホストが見つからない
```
```
ORA-12541: TNS:no listener
    → ホストには到達したが、指定ポートにリスナーがいない
```
```
ORA-12514: TNS:listener does not currently know of service requested in connect
    → ホストおよびポートは正しく、それによりリスナーへは到達できた。
      しかし指定したサービスをそのリスナーが認識していない
```

このように、Oracle Databaseへの接続で基本的かつ重要であるのは「**接続先ホスト**」「**接続先ポート**」「**接続先サービス**」の3要素が正しく設定されているかどうかと言えます。

## ORA-12545について

しかしこれら3つの条件をすべて満たしていても、まれに以下のようなエラーが発生する場合があります。

```text
ORA-12545: Connect failed because target host or object does not exist
```

* 日本語の場合: 
  ```
  ORA-12545: ターゲット・ホストまたはオブジェクトが存在しないため、接続に失敗しました
  ```

本記事では主にこの**ORA-12545**エラーについてを主題とします。


## 「ターゲット・ホストまたはオブジェクトが存在しない」？

「**ターゲット・ホストまたはオブジェクトが存在しない**」

‥ このメッセージを受け取ったあなたは、今何が起こっているか・そして何をすべきかを即座に想像できるでしょうか？

ORA-12545はネットを検索するとすぐに情報が出ますが、原因は「**名前解決に失敗**」したことです。とはいえ、このエラーメッセージの言っていることが具体的ではないので、このままでは本当にそうなのか納得が難しいところがあります。

## 名前解決なら一見できているように見えるが？

名前解決に失敗というけれど、RAC環境の場合、状況はさらに複雑になります。以下のようなコマンドでSCANに向けて接続した際に発生するORA-12545について考えてみます。

```bash
[oracle@client19c ~]$ sqlplus system/********@test-scan.localdomain:1625/db_testdb

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Mar 5 16:13:02 2025
Version 19.23.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12545: Connect failed because target host or object does not exist


Enter user-name:
```

SCANでの名前解決には下記の通り成功しています。そもそも、これができていないとRACのインストールの時に警告が発生しますね。では、一体どこの名前解決に失敗しているのか？本当に名前解決の問題なの？という疑問も出てきます。

```bash
[oracle@client19c ~]$ nslookup test-scan.localdomain
Server:		192.168.56.2
Address:	192.168.56.2#53

Name:	test-scan.localdomain
Address: 192.168.56.95
Name:	test-scan.localdomain
Address: 192.168.56.93
Name:	test-scan.localdomain
Address: 192.168.56.94
```

# Netトレースを使用して何が起こっているのか確認する

と、このように疑問が募ったところで、実際何がどうなってORA-12545に至っているのか？本当に名前解決の失敗なのか？名前解決の失敗であるとしてどのドメインのアドレスへの解決に失敗しているのか？を確認してみます。

この場合は、Netトレースによる情報取得が有効です。


## クライアント環境でNetトレースを有効にする

クライアント環境で以下のようにコマンドを実行すると、/tmp/nettraceディレクトリが新たに作成され、その中にNetトレースの設定が含まれるsqlnet.oraが作成されます。そして、クライアントが新規に接続を行う際にこのsqlnet.oraを参照するようになります。
また、このディレクトリにNetトレースによるトレースファイルが作成できるようになります。

```bash
export TNS_ADMIN=/tmp/nettrace
mkdir $TNS_ADMIN
cat <<EOF > $TNS_ADMIN/sqlnet.ora
TRACE_LEVEL_CLIENT=USER
TRACE_DIRECTORY_CLIENT=$TNS_ADMIN
TRACE_UNIQUE_CLIENT=OFF
TRACE_TIMESTAMP_CLIENT=ON
DIAG_ADR_ENABLED=OFF
EOF
```
いくつかパラメータの設定を行っていますが、簡単な説明を以下にまとめました。
| パラメータ | 説明 |
| --- | --- |
| TRACE_LEVEL_CLIENT | 情報の出力の詳細度。OFF → USER → ADMIN → SUPPORTの順に出力詳細度が上がりますが今回は一番軽いUSERに設定 |
| TRACE_DIRECTORY_CLIENT | トレースファイルの出力先を指定 |
| TRACE_UNIQUE_CLIENT | セッションごとに出力トレースファイルを一意に分けるかどうか。 |
| TRACE_TIMESTAMP_CLIENT | トレースファイルの出力にタイムスタンプを付与するかどうか。 |
| DIAG_ADR_ENABLED | ADR機能を有効にするかどうか。デフォルトのONの場合、TRACE_DIRECTORY_CLIENTの設定をしていても無視されADR内に出力される場合があるのでOFFに設定 |

上記のコマンドを有効にしたセッションからそのままsqlplusでORA-12545を再現させます。

## トレースファイルを取得する

その後TRACE_DIRECTORY_CLIENTに指定したディレクトリを確認すると、以下のようにcli.trcというファイルが生成されている状況を確認できます。

```bash
[oracle@client19c ~]$ cd /tmp/nettrace/
[oracle@client19c nettrace]$ ls -l
total 20
-rw-r-----. 1 oracle oinstall 13307 Mar  5 16:03 cli.trc  <--- ★このファイル★
-rw-r--r--. 1 oracle oinstall   132 Mar  5 16:02 sqlnet.ora
[oracle@client19c nettrace]$
```
このファイルを確認してみます。

```
(193006080) [05-MAR-2025 17:02:16:062] nsopen: global context check-in (to slot 0) complete
(193006080) [05-MAR-2025 17:02:16:062] nscon: doing connect handshake...
(193006080) [05-MAR-2025 17:02:16:062] nscon: connect id = 0x6944
(193006080) [05-MAR-2025 17:02:16:062] nscon: sending NSPTCN packet
(193006080) [05-MAR-2025 17:02:16:062] nscon: got NSPTRD packet
(193006080) [05-MAR-2025 17:02:16:062] nscon: recving connect data
(193006080) [05-MAR-2025 17:02:16:062] nsdo: 249 bytes from NS buffer
(193006080) [05-MAR-2025 17:02:16:062] nscall: redirected
(193006080) [05-MAR-2025 17:02:16:062] nstimarmed: no timer allocated
(193006080) [05-MAR-2025 17:02:16:062] nsclose: closing transport
(193006080) [05-MAR-2025 17:02:16:062] nsclose: global context check-out (from slot 0) complete
(193006080) [05-MAR-2025 17:02:16:062] nscall: connecting...
(193006080) [05-MAR-2025 17:02:16:062] nsc2addr: (ADDRESS=(PROTOCOL=TCP)(HOST=node1-vip)(PORT=1521))
(193006080) [05-MAR-2025 17:02:16:062] snlinGetAddrInfo: getaddrinfo() failed with error -2
(193006080) [05-MAR-2025 17:02:16:062] nttbnd2addr: looking up IP addr for host: node1-vip
(193006080) [05-MAR-2025 17:02:16:063] snlinGetAddrInfo: getaddrinfo() failed with error -2
(193006080) [05-MAR-2025 17:02:16:063] nttbnd2addr:  *** hostname lookup failure! ***
(193006080) [05-MAR-2025 17:02:16:063] nserror: nsres: id=0, op=77, ns=12545, ns2=12560; nt[0]=515, nt[1]=11, nt[2]=0; ora[0]=0, ora[1]=0, ora[2]=0
(193006080) [05-MAR-2025 17:02:16:063] nscall: connecting...
(193006080) [05-MAR-2025 17:02:16:063] nioqper:  error from nscall
(193006080) [05-MAR-2025 17:02:16:063] nioqper:    ns main err code: 12545
(193006080) [05-MAR-2025 17:02:16:063] nioqper:    ns (2)  err code: 12560
(193006080) [05-MAR-2025 17:02:16:063] nioqper:    nt main err code: 515
(193006080) [05-MAR-2025 17:02:16:063] nioqper:    nt (2)  err code: 11
(193006080) [05-MAR-2025 17:02:16:063] nioqper:    nt OS   err code: 0
(193006080) [05-MAR-2025 17:02:16:063] niqme: reporting NS-12545 error as ORA-12545
(193006080) [05-MAR-2025 17:02:16:063] niotns: Couldn't connect, returning 12545
(193006080) [05-MAR-2025 17:02:17:494] niotns: Not trying to enable dead connection detection.
```
内容をすべて理解する必要はないので、12545の文字列を検索してヒットする箇所の周辺を見てみます。


特に重要なところは以下のところで、`node1-vip`に対応するIPアドレスを探そうとして失敗(つまり名前解決に失敗)し、ORA-12545として報告しようとしていることが確認できます。

```
(193006080) [05-MAR-2025 17:02:16:062] nttbnd2addr: looking up IP addr for host: node1-vip
(193006080) [05-MAR-2025 17:02:16:063] snlinGetAddrInfo: getaddrinfo() failed with error -2
(193006080) [05-MAR-2025 17:02:16:063] nttbnd2addr:  *** hostname lookup failure! ***
:
(193006080) [05-MAR-2025 17:02:16:063] niqme: reporting NS-12545 error as ORA-12545
(193006080) [05-MAR-2025 17:02:16:063] niotns: Couldn't connect, returning 12545
```

なるほど。以下に試した通りなのですが、確かに`node1-vip`に対してはこのクライアントからはpingもnslookupも通りません。


```bash
[oracle@client19c ~]$ ping -c 1 node1-vip
ping: node1-vip: Name or service not known
[oracle@client19c ~]$ nslookup node1-vip
Server:		192.168.56.2
Address:	192.168.56.2#53

** server can't find node1-vip: REFUSED

[oracle@client19c ~]$
```

これにより、ORA-12545が名前解決を原因とするものであること、そして何に対して名前解決ができないのかが明らかになりました。


# この例でのORA-12545のそもそもの原因は

名前解決されずORA-12545の原因となった`node1-vip`は、実際のところ2ノード構成のRACのうちノード1側のローカルVIPでした。ノード1では、初期化パラメータlocal_listenerの設定を`LSNR_TESTDB`と設定し、tnsnames.oraに以下のように`LSNR_TESTDB`のエイリアスを設定していました。

* リスナーの設定
  ```
  SQL> show parameter listener
  
  NAME                                 TYPE        VALUE
  ------------------------------------ ----------- ------------------------------
  forward_listener                     string
  listener_networks                    string
  local_listener                       string      LSNR_TESTDB
  remote_listener                      string      test-scan.localdomain:1625
  ```
* tnsnames.oraの設定
  ```
  # for listener alias
  LSNR_TESTDB =
  (ADDRESS=(PROTOCOL=TCP)
    (HOST=node1-vip)(PORT=1521)
  )
  ```

今回の例については、以下のような流れで接続への手続きが進んでいたと考えられます。

1. SCANリスナーに接続 
2. リダイレクト接続先として指定されたローカルリスナーの情報を渡される
3. 渡されたローカルリスナーへの接続情報は、local_listenerの設定を元にした `(ADDRESS=(PROTOCOL=TCP)(HOST=node1-vip)(PORT=1521))`だった
4. クライアントは、この情報を元にローカルリスナーへの接続を試みた

しかし、クライアントにとっては`node1-vip`って何？という状態となり、ローカルリスナーへ接続できずORA-12545に至ったということとなります。

ローカルVIPは、SCAN VIPに比べてDNSへの登録がおろそかになりがちです。しかし、サーバーの/etc/hostsにローカルVIPの名前解決情報を記載している場合（例：インストール時の要件チェックを通すためなど）、ローカルでの接続テストではその/etc/hostsの情報が使われるため、問題が表面化しません。結果として、外部のクライアントが接続に失敗するまでこの問題に気づかない可能性があります。

ところで、local_listenerはデフォルトの場合は以下のようにローカルVIPの直IPが設定されます。この場合、特に何も設定しなければ今回のような問題は発生しないと言えます。

* デフォルトの設定例
  ```
  SQL> show parameter listener
  
  NAME				                   TYPE        VALUE
  ------------------------------------ ----------- ------------------------------
  forward_listener                     string
  listener_networks                    string
  local_listener                       string      (ADDRESS=(PROTOCOL=TCP)(HOST=
                                                   192.168.56.91)(PORT=1521))
  remote_listener                      string      test-scan.localdomain:1625
  ```

# 終わりに
今回はORA-12545について見ていきました。

Netトレースからの確認により、ORA-12545はネット上の情報等で言われている通り名前解決の問題であると検証できました。

Oracle Databaseは巨大なソフトウェアのため、非常に多数のエラーメッセージの種類の中からまれに表示されているエラーメッセージの内容と真の原因がかけ離れた(あるいは非常に遠い)表現となっているエラーに出くわすことがありますが、これはその一つと言ってよいでしょう。

「ターゲット・ホストまたはオブジェクトが存在しない」という表現はあながち間違いとまでは言えないのですが、名前解決に失敗したことがよりはっきり分かる表現であること、また、どれに対して名前解決が失敗したのか引数などで示してくれると良いのに、と今更ながら願っています。