🌐

【ヘアピンNATができない!】LANからだとアクセスできない問題を解決する。【ローカルDNS】

2023/11/18に公開

前置き

この記事が対象の人

  • 自宅サーバーでサービスを運用する人
  • VirtualHostを使う人
  • WAN側と同じIPやドメインでLANからアクセスする人

LANからアクセスできねぇ!

ノリで自宅サーバーを構築して、一部の人が最初の方にぶち当たる問題が 「外部からアクセスできるのにLANからアクセスできねぇ!!!」 という問題。WebサーバーでVirtualHostを使ってたりするとプライベートIPでアクセスできないので、今回はこの問題を解決していきます。
LANからアクセスできない(泣)

なぜ起きちゃう?

問題は、ルーターにあります。ここで重要になるのが ヘアピンNAT (NATループバック)。これは、LAN内の機器にグローバルIPでアクセスできるようにするスグレモノ。
これにルーターが対応していないと、LAN内からはグローバルIPで直接アクセスできないのです。
(メーカーも公表してないっぽいから気づくのさえ難しい...)
詳しい説明はこちらから。
http://www.rtpro.yamaha.co.jp/RT/docs/nat-descriptor/hairpin_nat.html

解決方法

方法は3つあります。

  1. 外部のプロキシサーバーを経由する
    (自分がVPNを使ったり、アクセスをCloudflare経由にする)(簡単だけどスピートが落ちます)

  2. hostsファイルを編集する
    (一番手軽だけど、外出時等にネット環境が変わると都度書き換える必要があります。)

  3. ローカルDNSを構築する
    (一番シームレス(だと思う)、だが初期設定がめんどくさい)

自分に合う方法を選びましょう。今回は3個目の方法でやっていきます。

実際にやっていく

環境

今回の環境です。

  • サーバーOS: Ubuntu 22.04 LTS
  • クライアントOS: Windows 11 22H2

※ ウェブサーバーとDNSサーバーは同じPCに存在する
※ サーバーのプライベートIPアドレスは192.168.3.2

サーバー側の操作

インストール

最初に、一応パッケージの更新をしましょう。

bash
sudo apt-get update
sudo apt-get upgrade

次に、DNSのソフトウェア、BIND9をインストールします。ついでにカレントディレクトリも変えちゃいましょう。

bash
sudo apt install bind9 bind9-utils
cd /etc/bind

設定ファイルの編集

LAN内からDNSが利用できるようにします。

bash
sudo nano named.conf.options
named.conf.options
//緑の部分を追加してください。

//ここでLAN内のネットワークを定義します。
+acl local-area-network {
+        192.168.3.0/24;
+};

options {
        directory "/var/cache/bind";

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        // forwarders {
        //      0.0.0.0;
        // };

//ここで、DNSの処理を受け付けるアクセス元を定義します。
+        allow-query {
+	    localhost;
+	    local-area-network;
+	};

//ここで、サーバーで定義されないドメインやIPを他のDNSに問い合わせるようにします。
+        recursion yes;

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation auto;

        listen-on-v6 { any; };
};

ドメインと設定ファイルの定義

ドメインを定義するファイルを作成します。

bash
sudo nano named.conf.my-zones

named.conf.my-zones の部分は自分に分かりやすい名前でどうぞ!

named.conf.my-zones(新規ファイル)
//一応ネームサーバーのドメインを設定します。
//my-server.dnsの部分は使いたいものにしてください。

zone "my-server.dns" IN {
        type    master; //マスタサーバーになります。
        file    "/etc/bind/my-server.dns.zone"; //DNSを設定するファイルです。
};

//ここからアクセスしたいドメインを入れます。
//example-my-service.comの部分は自分のFQDN(ドメイン)を入れてください。

zone "example-my-service.com" IN{
        type    master;
	file    "/etc/bind/example-web-service.com.zone";
}

DNSレコードの設定

先程定義したドメインのそれぞれについて、DNSレコードを設定していきます。

DNS

bash
sudo nano my-server.dns.zone
my-server.dns.zone(新規ファイル)
$TTL 86400
@       IN      SOA     ns.my-server.dns. support.my-server.dns. (

        ; INはLANを含むインターネット。
	; SOAは管理情報のこと。
	; ns.my-server.dnsの部分は使用するネームサーバー。
	; support.myserver.dnsの部分は担当者のメール。@は.になります。
	; (メールは存在する必要はない)(多分)
	
        20231118        ;Serial シリアル番号です。変更したら増やします。(日付が便利)
        3600            ;Refresh 指定した秒数ごとにDNSの情報を再取得します。
        1800            ;Retry Refreshで失敗したときにここの秒数後に再試行します。
        604800          ;Expire 情報を取得できないときにセカンダリNSの情報が使える期間
        865400          ;Minimum TTL ネガティブキャッシュを保持する期間
)

@    IN    NS   ns.my-server.dns. ;使用するネームサーバー。ns.my-server.dns とします。
ns   IN    A    192.168.3.2 ;ns.my-server.dnsのAレコード。サーバーのアドレスを入れます。

アクセスするドメイン

bash
sudo nano example-my-service.com.zone
example-my-service.com.zone(新規ファイル)
; ←このファイルはこれがコメントアウトになります
$TTL 86400
@       IN      SOA     ns.my-server.dns. support.my-server.dns. (
        20231118
        3600
        1800
        604800
        865400
)

@   IN    NS   ns.my-server.dns. ;上もだけど、最後の「.」を忘れずに!
@   IN    A    192.168.3.2 ;Webサーバーが置いてあるアドレス。

最後の仕上げ

最後に、大元の設定ファイルを編集します。

bash
sudo nano named.conf
named.conf
// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
// structure of BIND configuration files in Debian, *BEFORE* you customize
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

//これを追加してください(named.conf.my-zonesは自分のファイル名)
+include "/etc/bind/named.conf.my-zones";

確認作業

構文にミスが無いか確認しましょう。

bash
sudo named-checkzone my-server.dns /etc/bind/my-server.dns.zone
zone my-server.dns/IN: loaded serial 20231118
OK
bash
sudo named-checkzone example-my-service.com /etc/bind/example-my-service.com.zone
zone example-my-service.com/IN: loaded serial 20231118
OK

どちらとも最後にOKと出れば問題ありません。

サービスのリロード

変更内容を適用し、起動時にも働くようにします。

bash
sudo systemctl reload named
sudo systemctl enable named

LANにポート開放

ufwを利用してUDPの53番ポートを開放します。

bash
sudo ufw allow 53/udp
sudo ufw reload

DNSが働いているか確認

nslookupで確認します。

bash
nslookup google.com 192.168.3.2 //DNSサーバーのアドレス

ついでに登録したドメインも確認します。

bash
nslookup example-my-service.com 192.168.3.2 //DNSサーバーのアドレス

Name: example-web-service.com
Address: 192.168.3.2 //WebサーバーのプライベートIPアドレス

こうなれば成功です。

クライアント側の操作

DNSサーバーを設定するだけです。
設定 → ネットワークとインターネット → イーサネット → 編集

設定場所

このようにしてください。192.168.3.2の部分は自分のDNSサーバーのIPに変えましょう
設定例
(有線接続の場合です。)

あとはドメインにアクセスするだけです。
Success!

おめでとうございます。お疲れ様でした。

おわりに

注意

私はいわゆるアマチュアです。色々調べてますが、間違っていることがあると思いますので、そこだけ気をつけてください。何かサーバーに不具合が生じても責任は取れませんよ?言いましたからね?
指摘はどんどんお寄せください。

以下の記事を参考にしました。

https://zenn.dev/tochiman/articles/5434d4a1e40820
https://atmarkit.itmedia.co.jp/fnetwork/dnstips/014.html

本当にどうでもいい蛇足

先日電車に乗っていたところ、ある年配の方に話しかけられた。しばらく話してると学校の話になり、「私が小さかったころは学校なんてものは無かったんですよ」と仰られた。戦中、もしくは戦後の混乱で教育が受けたくても受けられない環境だったのかと戦争の悲惨さを感じながら、年齢を問うと、意外にも戦後約10年後の生まれであった。私の親族にその年齢に近い方が居るが、普通に学校があり、青春を謳歌していた。と考えれば、この人は別の地球から飛ばされて来たのではないかと思ってしまう。しばらくして、周辺の地理の話になった。「何処其処に川があった」など色々な話をするが、微妙に地理情報が異なっていた。なぜだろうか。最終的に同じ駅で降り、彼は赤信号を渡っていった。私が「危ないですよ!」と叫ぶと、「私はまだ若いから大丈夫」と笑顔で返してきた。(いやダメだろ)
プログラムを書くときに予想外な展開が起きることがあるが、現実でも起きるものであると認識させられてしまった...どちらも気をつけていきたいものだ。(?)

Discussion