2024/06/12

Unboundでご家庭DDNSサーバを構築する

 

DDNSとは

DDNSはDynamic Domain Name Sysytemの略で、DNSレコードをDynamicに書き換えられるシステムです。

IPアドレスが固定ではないFLETS回線のアドレスをFQDNとして登録し、アドレスが変更された際に自動で書き換えてくれます。

WEBサービスとしてはDynDNSやi.open.ad.jpなどがあります。

対応してる端末は一般的なLinuxサーバのほか、ルータなどにも機能がついていることが多いです。

例えばNEC製ルータのIXシリーズでは

https://jpn.nec.com/univerge/ix/faq/ddns.html

のように設定できます。

i.open.ad.jpは特にフレッツのIPv6空間でのDDNS用途に開発されており、特定のアドレスへPingを打つだけでレコード更新をする機能もあります。

今回作るご家庭DDNSについて

上で例に上げたDDNSサービスはグローバルIPアドレスをFQDNと紐づけるもので、プライベートアドレスの登録はできません。

今回自分は自宅内にDDNSサーバを立ち上げて、DHCPのアドレスアサインが変更されてもFQDNでアクセスできるように本サーバを構築しました。

IPv6にも対応しているので、覚えることがほぼ不可能なIPv6アドレスを任意の文字列に変換できます。

サーバを構築するたびにDNSレコードを手動で書き換えるのも面倒なので、リモートからコマンド一発でレコードを書き換えられるシステムです。

超注意点

本サーバはあくまで家庭内で稼働させることを前提としているため、脆弱性どころじゃないセキュリティリスクがあります。

認証機能も付けていないため、外部からアクセスできてしまうと好き勝手にキャッシュDNSレコードを書き換えられてしまいます。

必ず適切なアクセスコントロールを行ってください。

また、Unboundの機能上レコードを更新するたびにプロセスのRestartが走るので処理速度はかなり遅く、効率は悪いです。いやマジで。

前提

Unboundを単なる家庭内DNSサーバとして使うための設定については省略します。

「Unbound 設定」等で調べると普通に出てきます。

既にUnboundで名前が引けることを前提に説明します。

また、OSはRHEL系であるAlma linux9を使用します。

設計

クライアントからのDDNS要求はCurl等HTTPポストアクセスで行います。

"domain"パラメータにFQDNを格納し、DDNSサーバへアクセスします。

DDNSサーバはアクセスを受けると、アクセス元IP(v4/v6)とdomainパラーメータをもとにDNSレコードを更新します。

レコード更新後、UnboundをRestartすると名前解決が可能となります。

なお、今回は家庭内のメインDNSサーバに仕込むので逆引きも登録します。

→これが不幸の始まりになるとも知らずに……

実装

nginx,phpのインストール

こだわりがなければyum(dnf)で適当にインストールしましょう。

# sudo yum install nginx php php-fpm

Unboundの設定

DDNS要求のあったパラメータを格納するため、設定を変更します。

configに一行追加してください。

# sudo vi /etc/unbound/unbound.conf

include: /etc/unbound/local-zone.conf

次にDDNSレコードを格納するためのファイルを作成します。

# sudo touch /etc/unbound/local-zone.conf
# sudo chown unbound:unbound /etc/unbound/local-zone.conf

nginxの設定

次にWEBサーバであるnginxの設定を行います。

portが80でよければデフォルトの「listen 80;」の設定を下記のように書き換えてください。

server {
    listen 80;
    listen [::]:80;
    server_name ddns-api;

    root /usr/share/nginx/html;

    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include fastcgi.conf;
        fastcgi_pass unix:/run/php-fpm/www.sock;
    }
}

スクリプトの配置

次にHTTPアクセス用のスクリプトを作成します。

今回はChatGPT産です。

コードは以下の通り

# vi /usr/share/nginx/html/update.php


<?php
// アクセス元のIPアドレスを取得
$ip = $_SERVER['REMOTE_ADDR'];
$domain = $_POST['domain'];

// 入力の検証
if ((filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) && preg_match('/^[a-zA-Z0-9.-]+$/', $domain)) {
    // Unbound設定ファイルのパス
    $configFile = '/etc/unbound/local-zone.conf';

    // 新しい正引きレコードを作成
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        $newForwardRecord = "local-data: \"$domain. A $ip\"";
        $newReverseRecord = "local-data-ptr: \"$ip $domain.\"";
    } else {
        $newForwardRecord = "local-data: \"$domain. AAAA $ip\"";
        $ip = inet_pton($ip); // Convert IPv6 to binary format
        $newReverseRecord = "local-data-ptr: \"$ip $domain.\"";
    }

    // 現在の設定ファイルを読み込む
    $config = file_get_contents($configFile);

    // 正引きレコードが既に存在するかチェックして置換
    if (strpos($config, "local-data: \"$domain. A ") !== false) {
        // Aレコードを置換
        $config = preg_replace("/local-data: \"$domain\. A [^\"]+\"/", $newForwardRecord, $config);
    } elseif (strpos($config, "local-data: \"$domain. AAAA ") !== false) {
        // AAAAレコードを置換
        $config = preg_replace("/local-data: \"$domain\. AAAA [^\"]+\"/", $newForwardRecord, $config);
    } else {
        // 新しい正引きレコードを追加
        $config .= "\n" . $newForwardRecord;
    }

    // 既存の逆引きレコードを削除
    $config = preg_replace("/local-data-ptr: \"[^\"]+ $domain\.\"/", '', $config);

    // 新しい逆引きレコードを追加
    $config .= "\n" . $newReverseRecord;

    // 設定ファイルを更新
    file_put_contents($configFile, $config);

    // Unboundを再起動
    exec('sudo systemctl restart unbound');

    echo "DNS record for $domain updated to $ip and reverse DNS set.";
} else {
    echo "Invalid input";
}
?>

次に権限周りを設定を

# sudo chown nginx:nginx /etc/unbound/local-zone.conf

nginxユーザからunboundのリスタートを実行できるようにします。

# sudo visudo

nginx ALL=(ALL) NOPASSWD: /bin/systemctl restart unbound

nginxとphp-fpmをリスタートします。

# sudo systemctl restart nginx
# sudo systemctl restart php-fpm

完成!

テスト

クライアント想定の端末からDDNSを試してみます。

# curl -X POST -d "domain=[登録したいFQDN]" http://[DDNSサーバのアドレス]/update.ph

「DNS record for $domain updated to $ip and reverse DNS set.」と表示されたら、実際にレコードが登録されているか確認してみましょう。

# nslookup [登録したいFQDN] [DDNSサーバのアドレス]

無事名前を引ければ問題なしです。

使用感

ゴミです。

そもそもUnboundをDDNS用途に使うのが間違っていました。

上でも書いていますがUnboundはレコードの更新にプロセスのリスタートを伴います。

自宅のメインDNSサーバにしていると、DDNSスクリプトが走った際にプロセスのリスタートが発生し、その間名前解決が出来なくなります。

一度完成してしまったので記事としては残しますが、速攻レコード更新のいらないDNSミドルウェアに変更します。

多分Unbound以外のミドルウェア全部リスタート必要ないんじゃないかと思います。

>knot DNS,PowerDNS,Bind,NSCなど

権威DNSサーバとしてだけ使うのであればUnboundでもよいかと思いますが、それでもリアルタイム性に欠けるので次回にでもほかのミドルウェアを使って書き換えようと思います。

0 件のコメント:

コメントを投稿