えんでぃの技術ブログ

えんでぃの技術ブログ

ネットワークエンジニアの視点で、IT系のお役立ち情報を提供する技術ブログです。

/etc/nsswitch.conf を少し深く理解する (systemd-resolved対応)

multiple_switch

お伝えしたいこと

前回の記事で、Fedora33の変更点を紹介しました。
その中で、デフォルトのDNSクライアントがnss-dns (/etc/resolv.confベースで動作) からsystemd-resolved (systemdの一部。より高度かつ幅広い機能を持つ) に変わると紹介しました。

endy-tech.hatenablog.jp

それに伴い、/etc/nsswitch.confの中身もFedora33以降で変わっています。

本記事では、/etc/nsswitch.confを細かく読み込んでみます。
個々の名前解決サービスについては、「そもそもどんな機能を持っていて、どのように動くのか」の概要のみ紹介します。
systemd-resolvedの基本動作については、後続の記事で扱います。

サマリ

先に要点だけ紹介します。
詳細は後半のセクションで紹介しますが、ややマニアックなので興味のある方だけ見ていただくのが良いかもしれません。

Fedora33以降の動作

多くのプログラムは、最初に/etc/nsswitch.confを参照します。
nss-files (/etc/hosts)で名前解決できない場合は、nss-resolve (systemd-resolved)に処理が移ります。
そしてsystemd-resolved/etc/systemd/resolved.confの設定に従って、外部DNSサーバへの問い合わせなど処理を継続します。

↓こちらが、Fedora33の/etc/nsswitch.confです。

hosts:      files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] myhostname dns

dighostなど一部のプログラムは、/etc/nsswitch.confを読まず、/etc/resolv.confのみを参照します (情報元:man digman host)。

systemd-resolvedは、digのようなプログラムとも互換性を保つために/etc/resolv.confシンボリックリンク越しに自動更新します。
デフォルトでは/etc/resolv.conf/run/systemd/resolve/stub-resolv.confへのシンボリックリンクとなっており、システムのDNSサーバは127.0.0.53に設定されています。
127.0.0.53:53 (TCP/UDP)は、systemd-resolvedがリッスンしているソケットであり、結果的にdigsystemd-resolvedを参照することになります。

Fedora32以前の動作

多くのプログラムは、最初に/etc/nsswitch.confを参照します。
nss-files (/etc/hosts)で名前解決できない場合は、nss-dnsの制御を受けます。
そしてnss-dns/etc/resolv.confを参照します。

/etc/resolv.confは、シンボリックリンクではなくテキストファイルです。
/etc/resolv.confがテキストファイルの場合、原則としてNetworkManagerがこのファイルを自動更新します。
NetworkManagerを無効化したり、/etc/resolv.conf/run/NetworkManager/resolv.conf以外を宛先とするシンボリックリンクの場合はNetworkManagerの制御対象外となり、/etc/resolv.confを手動更新できるようになります (RHEL6時代からの流れでそういった構成の現場も一部あると思います)。

ちなみに比較的新しいCentOS8, RHEL8においてはsystemd-resolved.serviceはデフォルトで有効化されていますが、/etc/nsswitch.confresolveが書かれていないので実質ほぼ使われません。

↓こちらが、Fedora32以前の/etc/nsswitch.confです。RHEL8も同様だと思います。

hosts:      files mdns4_minimal [NOTFOUND=return] dns myhostname

digなど一部のプログラムは、/etc/nsswitch.confを読まず、/etc/resolv.confのみを参照します (情報元:man dig)。
/etc/resolv.confは上述の通り、基本的にはNetworkManagerが自動更新します。
DNSの設定を変えたい場合は、nmcliコマンドなどでNetworkManagerから制御します。

/etc/nsswitch.conf の読み込み

概要

Fedora33の/etc/nsswitch.confは以下のようになっていました。

hosts:      files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] myhostname dns

# Fedora32以前はresolveが存在せず、代わりにdnsが上位にいた (RHELやCentOSもこちらがデフォルトのはず)
# hosts:      files mdns4_minimal [NOTFOUND=return] dns myhostname

man nsswitch.confを参考に、上行の意味を読み取ります。

左側に書いてある hosts がデータベース名、右側に書いてあるfiles, mdns4_minimal, resolve, myhostname, dnsがサービス名です。
データベースごとに、どのサービスから情報を引いてくるかの優先順位を決めているのが/etc/nsswitch.confであり、NSS (Name System Switch) の役割です。
名前解決をしたい場合は、hostsデータベースを参照します。

サービスは左に書いてあるものほど優先順位が高く、Fedora33以降では従来使われていたdnsよりもresolve (systemd-resolved) の方が優先順位が高い構成となっています。
サービスの実体はnss-<サービス名>という共有ライブラリです。
例えばresolveの実体はnss-resolveであり、/lib64/libnss_resolve.so.2に存在します。

[NOTFOUND=return]!UNAVAIL=returnは、[STATE=ACTION]の形式で動作を一部変更します。
[STATE=ACTION]は、その左に書いてあるサービスに作用します。
上記例ではmdns4_minimal [NOTFOUND=return]で1セット になります。
詳細な動作は後述します。

以降のセクションで、filesから順に各サービスの意味を読み取っていきます。

files

filesでは、/etc/hostsから名前を検索します。
検索にヒットしなければ次のnss (mdns4_minimal) に進みます。

files/etc/hostsと対応するという情報は、man nsswitch.confに書いてありました。

mdns4_minimal [NOTFOUND=return]

mdns4_minimalの名前解決は基本的に行われず、そのままスキップされて次へ進みます。
...以降は参考情報です。

mdns4_minimalが名前解決をする対象は、名前が.localで終わる場合か、IPアドレス169.254.x.xの場合のみです (つまりリンクローカル専用のサービスです)。
従ってこの部分の処理は大抵「対象外」としてスキップされ、次のresolveへ進みます。

NOTFOUND=returnは、実際にmdns4_minimalのサービスでクエリが行われた場合のみ関係します。
言い換えると、名前が*.localか、IPアドレス169.254.x.xの場合しか関係しません。
実際にクエリを行って、「該当するレコードが見つからない (NOTFOUND)」という結果だった場合、/etc/nsswitch.confの走査はこの時点で終了して結果を返します (return)。
つまり、resolve以降には進まず、この場で「その名前は存在しない」という応答を返すことになります。

余談ですが、テスト用のドメイン.localとしてDNSサーバに登録した時、この仕様に引っかかってDNSサーバに問い合わせが行かないと思われます。
/etc/nsswitch.confにてresolvednsよりも先にmdns4_minimalの評価対象となり、mdns4_minimalで名前解決しようとするも見つからず、[NOTFOUND=return]によって名前解決を終了してしまうためです。

.localは思わぬ挙動をすることがあるので、mDNSを使う意図がなければ不用意に使わないようにしましょう。

(参考) mDNSの概要

気になる方向けにリンクを貼ります。
パケットキャプチャも含めてわかりやすい解説が書いてあり、とてもありがたい記事でした。

john-rama01.hatenablog.com

概要としては、以下のとおりです。

resolve [!UNAVAIL=return]

resolveは、systemd-resolved.serviceを指します。
例えば、systemctl status systemd-resolvedで状態を確認できます。

systemd-resolved.serviceの動作については次の記事で紹介しますが、基本的にはこのサービスによって問い合わせ先のリモートDNSサーバが決まり、実際に問い合わせが行われます。

[!UNAVAIL=return]により、systemd-resolved.serviceが正しく動作していれば、以降のmyhostnamednsのクエリは実施されない設定になっています。
!UNAVAILは「Unavailableではない」、つまり「正しく動作している限りは」という意味になります。

(参考) myhostname

そうそう使うことはないと思いますが、折角の機会なのでmyhostnameについても書きます。

基本的にはresolve/etc/nsswitch.confの走査が完了するので、nss-myhostnameが発動することは基本ありません。
ただman nss-myhostnameman nss-resolveを見比べるとわかりますが、nss-resolvenss-myhostname相当の機能も内包しています。
従って、myhostname相当の名前解決は、systemd-resolvedメインの構成になったとしても引き続き可能です。
具体的にはman systemd-resolvedの「SYNTHETIC RECORDS」を見ればわかりますが、以下に示す名前が解決されます。

  • 自身のホスト名 (私の環境では、pc)。
    • NICに設定されたIPアドレスを一通り返す
    • scope 順にソートされる
      • scope の意味はman ip-addressを参照
      • 平たく言うと、通常のIPより localhost の方が下に表示される
    • IPが未設定だった場合は、127.0.0.2 (ローカルループバックアドレス) と ::1 (local host) を返す
  • localhostlocalhost.localdomain*.localhost*.localhost.localdomain
    • 127.0.0.1::1を返す
  • _gateway

上記リストを見たら同じことを考えると思うのですが、私達がシェル上でコマンドを実行してこれらの名前解決を利用することはそうそうありません。
「一部のプログラムが機能を果たすため、内部的に使われることがある」機能だと思います。

ただせっかくなので、実験として実際に名前解決してみました。

(参考) myhostname の名前解決を試してみる

getent ahosts <name>で調べると、/etc/nsswitch.confに従って名前を表示してくれます。
今回の構成では、systemd-resolvedが応答を返してくれます。

[stopendy@pc ~]$ getent ahosts pc
fe80::1e69:7aff:fe6a:24f0%2 STREAM pc
fe80::1e69:7aff:fe6a:24f0%2 DGRAM  
fe80::1e69:7aff:fe6a:24f0%2 RAW    
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 STREAM 
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 DGRAM  
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 RAW    
192.168.1.110   STREAM 
192.168.1.110   DGRAM  
192.168.1.110   RAW    
192.168.122.1   STREAM 
192.168.122.1   DGRAM  
192.168.122.1   RAW  

[stopendy@pc ~]$ getent ahosts TEKITO.localhost
::1             STREAM localhost
::1             DGRAM  
::1             RAW    
127.0.0.1       STREAM 
127.0.0.1       DGRAM  
127.0.0.1       RAW

[stopendy@pc ~]$ getent ahosts _gateway
fe80::1%2     STREAM _gateway
fe80::1%2     DGRAM  
fe80::1%2     RAW    
192.168.1.1     STREAM 
192.168.1.1     DGRAM  
192.168.1.1     RAW 

digの場合は、/etc/resolv.confを参照します (man digより)。
/etc/nsswitch.confは見てくれません。
host コマンドも同様の結果になります

今回digで名前解決できているのは、/etc/resolv.conf127.0.0.53がServerとして指定されており、systemd-resolvedが名前解決しているためです。
digのようなプログラムも互換性を保つため、基本的には/etc/resolv.conf../run/systemd/resolve/stub-resolv.confなどへのシンボリンクリンクになっています。
そして、/run/systemd/resolve/stub-resolv.confsystemd-resolvedが自動更新するという作りになっています。

[stopendy@pc ~]$ dig pc | sed -ne '/ANSWER SECTION/,/^$/p'
;; ANSWER SECTION:
pc.         0   IN  A   192.168.1.110
pc.         0   IN  A   192.168.122.1

[stopendy@pc ~]$ dig TEKITO.localhost | sed -ne '/ANSWER SECTION/,/^$/p'
;; ANSWER SECTION:
TEKITO.localhost.   0   IN  A   127.0.0.1

[stopendy@pc ~]$ dig _gateway | sed -ne '/ANSWER SECTION/,/^$/p'
;; ANSWER SECTION:
_gateway.       0   IN  A   192.168.1.1

[stopendy@pc ~]$ 

もう一つ実験として、以下のコマンドでsystemd-resolvedを停止してみます。
これにより、myhostnameにも出番が回ってくるようになります。

sudo systemctl stop systemd-resolved

もう一度同じコマンドを試してみます。
getent ahosts は、変わらず応答を返します。
しかし応答を返しているのはmyhostnameです。

※ちなみに、getent ahosts pc --service myhostname と、明確にmyhostnameを指定することも可能です

[stopendy@pc ~]$ getent ahosts pc
fe80::1e69:7aff:fe6a:24f0%2 STREAM pc
fe80::1e69:7aff:fe6a:24f0%2 DGRAM  
fe80::1e69:7aff:fe6a:24f0%2 RAW    
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 STREAM 
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 DGRAM  
240d:1a:75d:1200:1e69:7aff:fe6a:24f0 RAW    
192.168.1.110   STREAM 
192.168.1.110   DGRAM  
192.168.1.110   RAW    
192.168.122.1   STREAM 
192.168.122.1   DGRAM  
192.168.122.1   RAW    

一方で、digコマンドは応答を返しません。
これはdigが常に/etc/resolv.confに従って127.0.0.53、つまりsystemd-resolvedにクエリを投げているためです。
systemd-resolvedを停止している間は、digがまともに動作しなくなってしまいます。

[stopendy@pc ~]$ dig pc
^C[stopendy@pc ~]$ 

dns

nss-dns は、従来通りのDNSサービスです。
/etc/resolv.confに従ってDNSサーバを決定します。

/etc/resolv.confの中身を見ればコメントで書かれているのでわかりますが、/etc/resolv.confは大抵他のサービスによって自動更新される構成になっています。
どのサービスが自動更新するかはディストリビューションのバージョンによって変わりますが、いずれにしても手動で/etc/resolv.confを編集することは原則推奨されていません。

Fedora32以前のnss-dnsがメインだった頃は、NetworkManagerが/etc/resolv.confを直接自動更新していました。
nmcli connection modify ens192 ipv4.dns '8.8.8.8, 8.8.4.4' といったコマンドで挙動を制御できます。

Fedora33以降のnss-resolveメインになった後は、/etc/resolv.confsystemd-resolved管理の別ファイルへのシンボリックリンクに変わっています。
そしてsystemd-resolvedシンボリックリンク先を自動で書き換えることで、/etc/resolv.confが自動更新されています。
なお、デフォルトの構成ではsystemd-resolvedの設定ファイルである/etc/systemd/resolved.confには外部DNSサーバのIPアドレスが指定されていません。
/etc/systemd/resolved.confにシステム全体のデフォルトのDNSサーバを設定することができますが、ここが空白でも per-link DNS を設定していれば名前解決が可能です。
このあたりの動きについては、次の記事で詳細を紹介したいと思います。

(参考) /etc/nsswitch.conf の順序は書き換えるべき?

未検証ではありますが、man nss-resolveに興味深いことが書いてありました。

  • nss-resolve単体で外部DNSへの問い合わせだけでなく、/etc/hostsの読み込みと自身のホスト名の名前解決なども可能
    • 言い換えると、nss-resolveは、nss-dnsだけでなく、nss-filesnss-myhostname相当の機能も内包している
  • systemd-resolved内部では、/etc/hostsや自身のホスト名は、外部DNSクエリよりも先に評価される (man systemd-resolvedのPROTOCOLS AND ROUTINGを参照)
  • systemd-resolvedによるnss-filesnss-myhostname相当の名前解決は、結果がキャッシュされる
  • /etc/nsswitch.confにおいて、resolvefiles, nss-dns, myhostname よりも優先されるように左に書くべきだ (処理効率や推奨動作の観点で)
  • 逆に言うと、resolveの右にfilesnss-dnsmyhostnameを残しておくべき。systemd-resolvedがDownしているときも名前解決を継続できるようにするため
  • /etc/nsswitch.confにおいてmymachines (VMやコンテナの名前解決) を利用する場合は、resolveよりも手前に配置する。nss-resolveはこの機能を内包していないため
  • また、man systemd-resolvedman resolved.confによると、resolvemdns相当の機能も有効化できる
    • resolvectl mdnsコマンドの出力によると、デフォルトではsystemd-resolvedのmdns機能は無効化されていた
    • 有効化するためには、/etc/systemd/resolved.confMulticastDNS=を変更する必要がある

将来的にsystemd-resolvedをメインで扱うようになったら、上述の設定チューニングも視野に入れても良いのかもしれませんね。
上記を踏まえて/etc/nsswitch.confを書き換えるとしたら、このようになります。

# files をresolveの後に持ってくる
hosts:      mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns

# resolved.conf でMulticastDNS=true, またはMulticastDNS=resolveに設定した場合は、mdns4_minimal も右に移動
# hosts:      resolve [!UNAVAIL=return] files mdns4_minimal [NOTFOUND=return] myhostname dns

まとめ

/etc/nsswitch.confの中身をガッツリ見てみました。

Fedora33以降になって、最近普及し始めてきたresolve (systemd-resolved.service) が上位に移動してきました。
systemd-resolved/etc/hosts読み取り機能やDNSサーバ問い合わせ機能を持っており、基本的に従来のDNSクライアント機能全てを置き換え可能です。
ただし、systemd-resolved.serviceがダウンしてしまうと上記の機能が全て使えなくなってしまうので、従来のサービスも/etc/nsswitch.confの下位には残しておくことが推奨されています。

次の記事

systemd-resolved.serviceの基本機能と設定方法について、より踏み込んで紹介します。

systemd-resolved.serviceはper-link DNSに対応しており、より柔軟なDNSサーバ選択が可能です。
特にVPN配下の環境においては、物理リンクの先にあるインターネットDNSサーバとVPNの先にある社内向けDNSサーバをFQDNによって使い分けることができます。
Cisco Anyconnect VPN Client のSplit DNSと同様な機能だと思いますが、システム構成によってはこの機能が重要になってくると思います。

endy-tech.hatenablog.jp