NSD + ISC-DHCPの環境でDynamicDNSの仕組みを作ってみる


寒くなってきまして、そろそろクリスマスのシーズンも近づく中、社内でも風邪をひいている方をチラホラ見かけるようになりました。
。。。という私も風邪をひいてしまいました。「体調には気を付けましょう!」と言いたかったのですが、説得力がないですね。

先日、能登半島へ旅行してまいりました。
能登半島は金沢から輪島で一泊 – 珠洲と行って、その後、富山県経由で五箇山 – 下呂温泉で一泊、郡上八幡 – 白川郷という具合です。
下呂温泉は昔、私が現在とは全く異なる業界で働いていた時に大変お世話になった方(呼び名も”げろさん”だったりする)の旦那さんの田舎で、よく話を聞いていたので一度行ってみたいなと思っていました。
下呂温泉ってカエル神社とかあるんですよね。至る所にカエル。
そして名物の下呂プリン。なんと素敵な街!!

旅行中、車の中で童謡とか唱歌(結構好きなんですねこういうのも)を大きな声で歌っておりましたところ

  トンボのメガネは水色メガネ~♪

の歌が頭から離れず、早速、出社して社用ストラトキャスターでアレンジして歌ってみたところをsys-solのY.M女史、営業のS.Y女史にも笑われるという結果に至ったのでした。

写真は、石川県の羽咋市にある日本で唯一車で走れる砂浜の千里浜(ちりはま)なぎさドライブウェイ(全長8Km)でドローンから撮ったものです。

いきさつ

そろそろ本題に。。 私はこの1年ほどネットワーク可視化関連のお仕事をさせていただいておりまして、おかげさまでその辺のノウハウがかなり溜まってまいりました。

SNMPとKibanaのVEGAを使ってユーザがどの無線APに接続しているかの可視化をしてみよう!、、、、かな、、、と思ったのですが(脳内設計有り〼)、ブログの〆切が明日に迫った現在PM8:37。。。

なんも書いてない。これから実装するのか? こりゃ無理だ、、、と。

ですので、半年ほど前に作った仕組みをネタとさせていただきます。

トラフィックを可視化しようとすると、必ずと言っていいほど送信元IPごとの流量Top10なんかのグラフを作ったりするケースがあるわけですが、ここで疑問に思うのがDHCPで動的に割り振られているIPを見せてどうすんだ、、、と。セキュリティインシデントやトラブルなんかが起こった場合、どの端末なのかが追えないんですよね、IPだけだと。

DHCPの仕組みで、ある程度同じ端末には同じIPが割り当たるようにはなっていますが、これは端末とIPの紐づけを保証できないし、インシデント当時のログからせっせと頑張って端末を特定するのか、、という運用面でも大変面倒なわけです。

で、「そーだ、DynamicDNSにしちゃえばいいんだ」という単純な発想に行き着くわけです。

早速、当社の実運用しているDNSを実験台に(いいのか?)取り掛かってみたわけなんですが、当社のDNSはスーパーサーバーエンジニアと自他ともに認める(ホントか?)I.Y氏が「今更BINDは古い、、レスポンスが、、脆弱性が、、もっとシンプルに、、、」とコダワリぬいた実装(ホントか?)で、NSD(権威)+ UNBOUD(キャッシュ)なんですね。
調べてみると、BINDはDynamicDNSの仕組みを持っているらしいんですが、NSDはそういうの無いみたい。。。
そうかい、ここは腕の見せ所だと、当時何の根拠もなく強気で自信満々のワタクシは思ってしまったわけです。

アーキテクチャ


  • 上図で記載された”DDNSD”というデーモンプログラムをを自作し、ISC-DHCPが動作するサーバに常駐させます。
  • DDNSDをデーモンとして動作させるため、systemdに組み込みます。
  • DDNSDは設定された実行インターバルに基づき定期的に動作します。
  • 上図ですとISC-DHCPはFailover Peerを構成しており、IP払い出しステータスが同期されていますが、DDNSDは非同期で動作するため(※1)、ゾーンファイルアップデート時の排他制御の仕組み(※2)を持たせます。
  • ※1 スタンドアロン構成やFailover Peerを構成しない冗長構成、例えば192.168.1.0/24のネットワークにおけるIPの払い出いを分担し、#a系が192.168.1.1~127、#b系が192.168.1.128~254といった構成も考慮した結果、非同期の方が良いと判断ました。
    ※2 アップデート実行の衝突を検知した場合、実行インターバルをクラスタを構成するノード数で割った時間分待機してから再実行します。
  • DDNSDは”dhcpd.lease”ファイルにおいてMACアドレスをキーとし、”binding state”が”active”である最終エントリのみをチェックし、Aレコード/PTRレコードの仮テーブルをそれぞれ作成します。
  • Aレコードは<ホスト名>.<MACアドレス>のフォーマットと、利用しやすさのため<ホスト名>のみのフォーマットの2種類を登録します(CNAMEは使用せず両方Aレコード)。
  • PTRレコードは一意性を持たせるため、<ホスト名>.<MACアドレス>のフォーマットのみとします。
  • 権威DNS(NSD)サーバ上の現在のゾーンファイルの内容と作成した仮テーブルを比較し、差分があった場合のみ、内容のアップデートおよび、シリアルを書き換え”nsd-control reload”コマンドを投入することでゾーン転送します。
  • ゾーンファイルには固定エントリも当然存在することから、DDNSDによるゾーンファイルのアップデート箇所はアップデート元ホスト名(上図の場合dhcp01/dhcp02)ごとのBOF~EOFで区切られた部分に限られます。
  • DDNSD -> NSD方向の情報の取得および、アップデート実行のための接続は、事前に作成された専用のユーザ(ddnsd)による公開鍵認証を用いたSSH接続によって行われます。
  • 実行ステータス、エラーをログに出力します。
  • loggerを用いてtagに”ddnsd”を付与していますので、(※faciltyは”local7″固定)必要に応じてrsyslog等でルールマッチングさせることができます。
  • 実装

    ※両系すべて同一です。

  • DDNSD設定ファイル
  • /usr/local/sbin/ddnsd/ddnsd.conf

  • INTERVAL_SEC
  • 処理を実行する周期を設定します。
  • SSH_TIMEOUT_SEC
  • SSHによる接続試行時のタイムアウト(秒)を設定します。
  • TMP_DIR
  • テンポラリファイルを配置するディレクトリを設定します。
  • INFO_DIR
  • 情報ファイルを配置するディレクトリを設定します(”dhcpd.lease”ファイルを読み込み、仮テーブルファイル作成処理途中のファイルが保存されます。※デバッグ用)。
  • DHCPD_REDUNDANT_CNT
  • ISC-DHCPサーバのクラスタを構成するノード数を設定します。
  • DHCPD_LEASE_PATH
  • “dhcpd.lease”のフルパスを設定します。
  • NSD_SSH_USER
  • 権威DNSサーバへログインするためのユーザを設定します。
  • NSD_SRV
  • 権威DNSサーバのログイン先を設定します。
  • NSD_ZONE_DIR
  • 権威DNSサーバ上のZONEファイル配置ディレクトリを設定します。
  • NSD_CTL_PATH
  • 権威DNSサーバ上における”nsd-control”コマンドのフルパスを設定します。
  • NSD_LOCKFILE_DIR
  • 排他制御用に権威DNSサーバ上に作成するロックファイルの配置ディレクトリを設定します。
  • ZONE_A
  • Aレコードのサフィックスを設定します(1つのみ可)。
  • ZONE_PTRS
  • PTRレコードのドメイン名を設定します(空白もしくはタブ区切りで複数可)。
  • ZONE_TTL_SEC
  • レコードのTTLを設定します。
  • ZONE_SERIAL_LINE_REGEXP
  • ゾーンファイルにおけるシリアルが記載された行に合致する正規表現を設定します。

  • DDNSDスクリプトファイル
  • /usr/local/sbin/ddnsd/ddnsd.sh

  • systemdファイル
  • /usr/lib/systemd/system/ddnsd.service
    ※要”systemctl daemon-reload”

    使い方

    起動:
    systemctl start ddnsd
    停止:
    systemctl stop ddnsd
    リロード(設定ファイル再読込):
    systemctl reload ddnsd

    結果

  • ゾーンファイル(Aレコード “test.glodia.jp.zone”)
  • ゾーンファイル(PTRレコード “16.17.172.in-addr.arpa.zone”)
  • ゾーンファイル(PTRレコード “2.168.192.in-addr.arpa.zone”)
  • ログ出力
  • PINGしてみる
  • おわりに

    なんだかんだで結構な量を書いてしまいました。。ワタクシの性格上どうしようもないんですね、これ。

    今回の実装は当社のような小規模でシンプルな環境でしたら問題なく動作すると思います(現在も問題なく動作してます)。
    大規模な環境ではやったことないのでどうなるかはわかりませんが、TTLやインターバルを負荷を回避するようにウマイことチューニングすれば案外イケるのかも、、(適当)

    また、自分なりに面白いこと(思いついたこと)をやってみようと思いますので、興味持たれた方は是非また読んでくださいね!

    ※記事の内容は保証はしておりません(執筆時期や実施環境により挙動が変わるものがある為)。
    別途検証してご利用いただくことをおすすめいたします。