はじめに
自宅サーバが古くなってきたので、以下のような構成の新しい自宅サーバを構築していきます。
この自宅サーバを構築するために、全6回に分けて記事を掲載予定であり、今回は5回目の記事となります。今回はHomeサーバに家内のDHCPサーバとDNSサーバを構築していきます。
第1回:Ubuntuインストール編
第2回:KVMとCockpitで仮想サーバ構築編
第3回:ApacheとWordpressでWebサーバ構築編
第4回:PostfixとRainloopでメールサーバ構築編
第5回:DNS・DHCPサーバ構築編 ←この記事
第6回:NoderedとMQTTでホームサーバ構築編
番外編:KVM上でのWindows11のインストール編
番外編:KVM上でのUbuntu Serverのインストール編
第5回:DHCP・DNSサーバ構築編
第3回と第4回では、インターネット向けに公開するWeb系やMail系サーバの設定を行いました。第5回では、家内のPC・スマホ・タブレットの利用に欠かせないDHCPやDNSの設定を行なっていきます。なお、IPv4だけでなくIPv6に関しても設定していきます。さらに、端末が追加になった時のDHCPやDNSへの追加は手間がかかる運用作業ですので、これを効率化するためのCockpitの拡張機能を開発していきます。
DHCPサーバの設定
Ubuntsu24.4にはデフォルトではDHCPサーバはインストールされていないので、DHCPサーバをインストールし、設定していきます。
①DHCPサーバのインストール
Ubuntsu24.4に簡単にインストールできるDHCPサーバは「isc-dhcp-server」です。早速「apt-get install」コマンドでインストールしていきます。
1 2 |
# apt-get install isc-dhcp-server ・・・省略・・・ |
②dhcpd.confの修正
DHCPサーバのメインの設定である「dhcpd.conf」の設定を行なっていきます。
まずは、オリジナルのファイルをバックアップしてテキストエディタで開きます。
1 2 3 |
# cd /etc/dhcp # cp dhcpd.conf dhcpd.conf.org # vi dhcpd.conf |
「dhcpd.conf」の修正内容は以下の通りです。
1 2 3 4 5 6 7 8 9 |
# option definitions common to all supported networks... #option domain-name "example.org"; option domain-name "home-net"; #option domain-name-servers ns1.example.org, ns2.example.org; option domain-name-servers 192.168.1.2; # Use this to send dhcp log messages to a different log file (you also # have to hack syslog.conf to complete the redirection). log-facility local7; |
3行目と5行目の「option domain-name」と「option domain-name-servers」は、ドメイン名とDNSサーバのIPアドレスの設定です。私の場合にはドメインを「home-net」、DNSサーバを「192.168.1.2」に設定しています。また、9行目はSyslogで利用するログファシリティの設定で、ここを「local7」に設定し、Syslog側の設定で「local7」に対応するログファイル名を設定することで、DHCPに関するログを「/var/log/syslog」とは別のファイルに出力する事ができます。DHCPのログは端末が接続される度に大量に出力されるので、別ファイルにすることをお勧めします。
次に「dhcpd.conf」の一番下に、以下のサブレットの設定を追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
subnet 192.168.1.0 netmask 255.255.255.0 { range 192.168.1.200 192.168.1.250; authoritative; option domain-name-servers 192.168.1.2; option domain-name "home-net"; option subnet-mask 255.255.255.0; option routers 192.168.1.2; default-lease-time 600; max-lease-time 7200; include "/etc/dhcp/dhcpd.hosts"; } |
1行目で192.168.1.0(ネットマスク255.255.255.0)のサブネットに関する設定であることを宣言し、2行目でDHCPが自動的に割り当てるIPアドレスの範囲として192.168.1.200〜192.168.1.250を設定します。なお、後でMACアドレスに紐づく固定IPアドレスの割り当てを設定するので、ここでの設定はMACアドレスが登録されていない端末に自動的に割り当てる範囲となります。3行目は、このDHCPサーバが信頼できるものであることを宣言。5行目と5行目は、上でも設定したDNSサーバとドメイン名の設定、8行目はサブネットマスクの設定。9行目はデフォルトゲートウェイの設定。12行目と13行目は、IPアドレスのデオフォルトと最大のリリースタイムの設定です。また、通常であれば15行目以降に固定IPアドレスの設定を記載しますが、この部分は端末の追加に応じて随時更新する必要があるので、別ファイル「dhcpd.hosts」に定義することにします。このため、15行目では「dhcpd.hosts」ファイルをインクルードすることを記載します。
③dhcpd.hostsの設定
dhcpd.hostsには、端末のMACアドレスに紐づく固定IPアドレスの設定を記載します。ここに家内の端末のMACアドレスを登録しておくことで、端末がネットワークに接続された時に毎回同じIPアドレスがDHCPにより設定されます。これにより、DNSによる名前解決やIPTablesによる端末ごとのネットワーク通信制限などが可能になります。
1 |
# vi dhcpd.hosts |
1 2 3 4 5 6 7 8 |
host router { hardware ethernet 00:00:00:00:00:00; fixed-address 192.168.1.1; } host iPad { hardware ethernet 00:00:00:00:00:00; fixed-address 192.168.1.2; } |
④DHCPサービスの提供元インタフェースの設定
Homeサーバは、2つのネットワークインタフェースを持つので、何も設定しないとLAN側だけでなくDMZ側にもDHCPサービスを提供してしまいます。これを防止するためにDHCPサービスを提供するネットワークインタフェースの設定を「/etc/default/isc-dhcp-server」に記載します。
1 2 |
# cp isc-dhcp-server isc-dhcp-server.org # vi isc-dhcp-server |
「isc-dhcp-server」の修正内容は以下の通りです。
1 2 |
INTERFACESv4="ens1" INTERFACESv6="ens1" |
「INTERFACESv4」と「INTERFACESv6」の値にDHCPサービスを提供するネットワークインタフェース名を記載します。ネットワークインタフェース名は「ip a」コマンドで調べられるので、分からない人は「ip a」コマンドでLAN側のネットワークインタフェース名を調べましょう。また、この後IPv6の設定も行うので「INTERFACESv6」もついでに設定しておきます。
⑤ログ出力先の設定
DHCPに関するログの出力先を変更するためにSyslogの設定ファイルである「/etc/rsyslog.d/50-default.conf」を編集します。
1 2 3 |
# cd /etc/rsyslog.d # cp 50-default.conf 50-default.conf.org # vi 50-default.conf |
「50-default.conf」を以下のように変更します。
1 2 3 4 5 6 7 8 9 |
auth,authpriv.* /var/log/auth.log *.*;auth,authpriv,local7.none -/var/log/syslog #cron.* /var/log/cron.log #daemon.* -/var/log/daemon.log kern.* -/var/log/kern.log #lpr.* -/var/log/lpr.log mail.* -/var/log/mail.log #user.* -/var/log/user.log local7.* -/var/log/dhcpd.log |
まず、9行目の設定によりDHCPサーバが「local7」のログファシリティを用いて記載したログが、Syslogにより「/var/log/dhcpd.log」に記載されるようになります。次に2行目を「*.*;auth,authpriv,local7.none」に変更することで、「auth,authpriv,local7.none」を除く全ログを/var/log/syslogに記入するというが意味になります。これで、DHCP関係のログが「/var/log/syslog」からは除外され「/var/log/dhcpd.log」に記載されるようになります。
⑥Systemdへの登録と起動
最後にDHCPサーバをSystemdへ登録して、DHCPサーバを起動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# systemctl enable isc-dhcp-server # systemctl start isc-dhcp-server # systemctl status isc-dhcp-server ● isc-dhcp-server.service - ISC DHCP IPv4 server Loaded: loaded (/usr/lib/systemd/system/isc-dhcp-server.service; enabled; preset: enabled) Active: active (running) since Sat 2024-08-10 10:58:26 JST; 18s ago Docs: man:dhcpd(8) Main PID: 10345 (dhcpd) Tasks: 1 (limit: 9446) Memory: 3.7M (peak: 4.2M) CPU: 12ms CGroup: /system.slice/isc-dhcp-server.service └─10345 dhcpd -user dhcpd -group dhcpd -f -4 -pf /run/dhcp-server/dhcpd.pid -cf /etc/dhcp/d> |
3行目の「systemctl status isc-dhcp-server」コマンドを実行して「Active」欄が「active」になっていれば起動成功です。
⑦テスト
それでは、LANにPCなどの端末を接続してみましょう。上で設定したネットワークのIPアドレスが割り当てられていれば設定成功です‼️
DHCPサーバ(IPv6)の設定
ここからはIPv6のDHCPサーバの設定を行なっていきます。IPv6のIPアドレスの配布の仕組みはIPv4とは大きく異なっており、RA(ルータ)を使ってアドレスの割り当てを行う方法とDHCPサーバを利用する方法があります。
ここではIPv6でも固定のIPアドレスを割り当てたいのと、DNSを利用したいのでDHCPを用いる方法で設定していきます。いくらFWがあるからといって、全機器のIPアドレスをインターネットに晒すのは怖いなとIPv4時代からの慣れが邪魔するので、IPv6についてもプライベートアドレス+NAPTという構成にしています。ただし、IPv6のDHCP仕様にはデフォルトゲートウェイのIPアドレスを配布する仕様がないので、デフォルトゲートウェイはRAから配布する必要があります。この辺り、どうにかしてほしいものだと思います。
なお、IPv6用のDHCPサーバパッケージはIPv4用と同時にインストールされているのでパッケージのインストールは不要です。
①dhcpd6.confの修正
IPv6用のDHCPサーバの設定ファイルである「dhcpd6.conf」の設定を行なっていきます。
1 2 3 |
# cd /etc/dhcp # cp dhcpd6.conf dhcpd6.conf.org # vi dhcpd6.conf |
「dhcpd6.conf」の修正内容は以下の通りです。
1 2 3 4 5 6 7 |
# Global definitions for name server address(es) and domain search list #option dhcp6.name-servers 3ffe:501:ffff:100:200:ff:fe00:3f3e; option dhcp6.name-servers fc00:cafe::2; #option dhcp6.domain-search "test.example.com","example.com"; option dhcp6.domain-search "home-net"; log-facility local7; |
3行目と5行目の「option dhcp6.name-servers」と「option dhcp6.domain-search」は、IPv4の場合と同様にDNSサーバのIPアドレスとドメイン名の設定です。私の場合にはDNSサーバを「fc00:cafe::2」、ドメインを「home-net」に設定しています。7行目は、オリジナルのdhcpd6.confには記載がないのですが「log-facility local7;」を設定し、IPv4と同様に「/var/log/dhcpd.log」にログが出力されるようにします。ちなみに、これを記載しないと「/var/log/syslog」にもIPv6に関するDHCPのログは出力されません💦
次に「dhcpd6.conf」の一番下に、以下のサブレットの設定を追記します。
1 2 3 4 5 6 7 |
subnet6 fc00:cafe::/64 { option dhcp6.name-servers fc00:cafe::2; range6 fc00:cafe::100 fc00:cafe::200; include "/etc/dhcp/dhcpd6.hosts"; } |
家内のサブネットは「fc00:cafe::」のアドレス体系を使います。「cafe」にしているのは、なんかオシャレな気がするからです😊そして、3行目の「dhcp6.name-servers」で、DNSサーバのIPアドレスを「fc00:cafe::2」に設定し、4行目の「range6」でDHCPがIPアドレスを割り当てる範囲を「fc00:cafe::100」から「fc00:cafe::200」の間に設定します。IPv6は16進数なので、100や200と書くと10進数で記入するIPv4のアドレス範囲と一致しませんが、分かりやすさを優先しました。そして6行目で固定IPアドレスを割り当てるための別ファイル「dhcpd6.hosts」をインクルードします。
②dhcpd6.hostsの修正
IPv4と同様にMACアドレスに基づき固定IPアドレスを割り当てる「dhcpd6.hosts」を設定していきます。
1 |
# vi dhcpd6.hosts |
1 2 3 4 5 6 7 8 |
host router { hardware ethernet 00:00:00:00:00:00; fixed-address6 fc00:cafe:0000:0000:0000:0000:0000:0001; } host iPad { hardware ethernet 00:00:00:00:00:00; fixed-address6 fc00:cafe:0000:0000:0000:0000:0000:0002; } |
③Systemdへの登録と起動
最後にSystemdへ登録して、DHCPサーバを起動します。ここで、DHCPサーバのデーモンはIPv4とIPv6で分かれているのでIPv6用の「isc-dhcp-server6」を追加で登録する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# systemctl enable isc-dhcp-server6 # systemctl start isc-dhcp-server6 # systemctl status isc-dhcp-server6 ● isc-dhcp-server6.service - ISC DHCP IPv6 server Loaded: loaded (/usr/lib/systemd/system/isc-dhcp-server6.service; enabled; preset: enabled) Active: active (running) since Sat 2024-08-10 17:49:38 JST; 4s ago Docs: man:dhcpd(8) Main PID: 14138 (dhcpd) Tasks: 1 (limit: 9446) Memory: 1.6M (peak: 2.2M) CPU: 13ms CGroup: /system.slice/isc-dhcp-server6.service └─14138 dhcpd -user dhcpd -group dhcpd -f -6 -pf /run/dhcp-server6/dhcpd6.pid -cf /etc/dhcp> |
3行目の「systemctl status isc-dhcp-server6」コマンドを実行して「Active」欄が「active」になっていれば起動成功です。
(参考)「dhcpd6.pid」の書き込みエラー
私の環境では「Can’t create PID file /run/dhcp-server6/dhcpd6.pid」と「dhcpd6.pid」の書き込みに失敗する事象が発生しました。実害はありませんが、気になる方は以下で解決できます。
1 2 3 |
# echo "/run/dhcp-server6/dhcpd6.pid rw," >> /etc/apparmor.d/local/usr.sbin.dhcpd # apparmor_parser -r /etc/apparmor.d/usr.sbin.dhcpd # systemctl restart isc-dhcp-server6 |
④テスト
それでは、LANにPCなどの端末を接続してみましょう。ちなみにWindows11でIPv6のIPアドレスを取得しなおす時は「ipconfig /renew6」と最後に「6」をつけないといけないので注意してください。上で設定したIPv6のネットワークのIPアドレスが割り当てられていれば設定成功です‼️
DNSサーバの設定
ここからは家内で利用するDNSサーバの設定を行なっていきます。DHCPと同様にIPv4だけでなくIPv6の両方に対応します。
①DNSサーバのインストール
Ubuntsu24.4に簡単にインストールできるDNSサーバは「bind9」ですので「apt-get install」コマンドでインストールしていきます。
1 2 |
# apt-get install bind9 ・・・省略・・・ |
②named.confの修正
まずはbind9のメインの設定ファイルである「named.conf」の設定を行なっていきます。
1 2 3 |
# cd /etc/bind # cp named.conf named.conf.org # vi named.conf |
named.confへの修正は、最終行の追加です。
1 2 3 4 |
include "/etc/bind/named.conf.options"; include "/etc/bind/named.conf.local"; include "/etc/bind/named.conf.default-zones"; include "/etc/bind/named.conf.my-zones"; |
named.confでは、各種設定を行うファイルをIncludeしているので、家の中のゾーンを設定するためのファイルとして「include “/etc/bind/named.conf.my-zones”;」を追加します。
③named.conf.optionsの修正
次にbind9の基本設定を行う「named.conf.options」を修正していきます。
1 2 3 |
# cd /etc/bind # cp named.conf.options named.conf.options.org # vi named.conf.options |
「named.conf.options」の修正箇所は以下の通りです。
1 2 3 4 5 6 7 8 9 |
dnssec-validation no; auth-nxdomain no; # conform to RFC1035 listen-on port 53 { 127.0.0.1; 192.168.1.2;}; listen-on-v6 port 53 { ::1; fc00:cafe::2; }; allow-query { localhost; 192.168.1.0/24; fc00:cafe::/64; }; forwarders { 8.8.8.8; 2001:4860:4860:0:0:0:0:8888; }; forward only; allow-transfer { none; }; |
まず、1行目で、今回は家の中のDNSサーバなのでDNS SECは不要なので、無効化します。次に2行目でNXDOMAINに関する権威設定を「No」にします。
4行目で、このDNSサーバがサービスを提供するポート番号を53とIPアドレスを「127.0.0.1; 192.168.1.2;」に設定し、インターネット側からの問い合わせを制限します。同様に5行目は、IPv6に関するサービス提供するポート番号とIPアドレスの制限です。6行目は、このDNSサーバが応答を返す依頼元ホストのネットワークアドレスを制限し、家の中からの応答にのみ答えるように設定します。
7行目は、このDNSサーバが回答を持っていない時に依頼を転送するDNSサーバのIPアドレスを設定しています。ここではIPv4とIPv6共にGoogleのDNSサーバを指定しています。最後の8行目はforwardersに問い合わせても回答が得られないときに反復的な問い合わせをするかどうかの指定で「only」を指定して反復的問い合わせをしないように設定します。
9行目の「allow-transfer」はこのDNSサーバの情報を転送できるセカンダリDNSサーバの設定ですが、今回はセカンダリDNSサーバは用意しないので「none」を指定します。
④named.conf.my-zonesの作成
次にこのDNSサーバで名前解決を行うゾーンの設定を行う「named.conf.my-zones」を作成します。
1 |
# vi named.conf.my-zones |
「named.conf.my-zones」の中には3つのゾーン(実際には4つ)を記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
zone "home-net" IN { type master; file "/etc/bind/db.home-net"; allow-transfer { none; }; }; zone "1.168.192.in-addr.arpa" IN { type master; file "/etc/bind/db.192.168.1"; allow-update { none; }; }; zone "0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f.ip6.arpa" IN { type master; file "/etc/bind/db.fc00:cafe"; allow-update { none; }; }; zone "0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f.ip6.int" IN { type master; file "/etc/bind/db.fc00:cafe"; allow-update { none; }; }; |
まず、1〜5行目で、家の中のプライベートドメインである「home-net」の正引き(ホスト名からIPアドレスを調べる)の設定を行います。このDNSサーバがマスターサーバであることを示す「type master;」と設定ファイルのパス名、そして他のDNSサーバにゾーン転送を行わないようにするための「allow-transfer { none; };」を設定します。また、7〜11行目は、IPv4用の「192.186.1.0」の逆引き(IPアドレスからホスト名を調べる)の設定となります。
13〜22行目でIPv6用の「fc00:cafe::/64」の設定を行います。まず、13〜17行目と18〜22行目の二つがあるのは、IPv6の仕様が「ip6.int」から「ip6.arpa」に変更されたことを受けた互換性の確保のためです。DNSサーバを利用するホストのOSやバージョンによっては、片方だとうまく引けないことがあるようなので2つを設定しています。次に「0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f」の部分が、少し厄介です。IPアドレス「fc00:cafe::」の省略されている「0(ゼロ)」を全て展開し、ネットワーク部分の64ビット部分を切り取り、通常16ビットごとに区切る「:(コロン)」の代わりに1ビットづつ「.(ドット)」で区切り、さらにそれを左右逆転させます。つまり以下となります。
1.省略されている0を全て展開
→fc00:cafe:0000:0000:0000:0000:0000:0000
2.ネットワーク部分の先頭64ビットを切り取る
→fc00:cafe:0000:0000
3.コロン区切りをドット区切りへ
→f.c.0.0.c.a.f.e.0.0.0.0.0.0.0.0
4.左右を入れ替える
→0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f
⑤正引き用ゾーンファイルの作成
「named.conf.my-zones」で指定したゾーンファイルのうち正引き用の「db.home-net」を作成します。
1 |
# vi db.home-net |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
home-net. 86400 IN SOA ns.home-net. admin.home-net. ( 2024090707 ; serial 3600 ; refresh (1 hour) 300 ; retry (5 minutes) 4294962816 ; expire (7101 weeks 3 days 5 hours 13 minutes 36 seconds) 20864 ; minimum (5 hours 47 minutes 44 seconds) ) 10800 IN NS router.home-net. 86400 IN MX 10 mx.home-net. router.home-net. 10800 IN A 192.168.1.1 router.home-net. 10800 IN AAAA fc00:cafe:0000:0000:0000:0000:0000:0001 iPad.home-net. 10800 IN A 192.168.1.2 iPad.home-net. 10800 IN AAAA fc00:cafe:0000:0000:0000:0000:0000:0002 |
ゾーンファイルの書き方は、多くの情報がネットにあるので詳しく書きませんが、10行目と12行目のIPv6アドレスの部分だけ触れてたいと思います。正引きのゾーンファイルでは、IPv4とIPv6のアドレスを一つのファイルに書く事ができ、IPv4のホストはAレコードで、IPv6のホストはAAAAレコードで記載します。
⑥逆引き用ゾーンファイル(IPv4)の作成
次に「named.conf.my-zones」で指定したゾーンファイルのうちIPv4用の逆き用の「db.192.168.1」を作成します。
1 |
# vi db.192.168.1 |
1 2 3 4 5 6 7 8 9 10 |
1.168.192.in-addr.arpa. 10800 IN SOA 1.168.192.in-addr.arpa. admin.1.168.192.in-addr.arpa. ( 2024090707 ; serial 86400 ; refresh (1 day) 3600 ; retry (1 hour) 604800 ; expire (1 week) 345600 ; minimum (4 days) ) 10800 IN NS router.home-net. 1 10800 IN PTR router.home-net. 2 10800 IN PTR iPad.home-net. |
このファイルはIPv4用の設定なので、特筆すべきことはありません。他のネットの情報を参考にしながら設定してください。
⑦逆引き用ゾーンファイル(IPv6)の作成
最後に「named.conf.my-zones」で指定したゾーンファイルのうちIPv6用の逆き用の「db.fc00:cafe」を作成します。
1 |
# vi db.fc00:cafe |
1 2 3 4 5 6 7 8 9 10 |
@ 10800 IN SOA 0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f.ipv6.arpa. admin.fc00cafe.in-addr.arpa. ( 2024090707 ; serial 86400 ; refresh (1 day) 3600 ; retry (1 hour) 604800 ; expire (1 week) 345600 ; minimum (4 days) ) 10800 IN NS router.home-net. 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 10800 IN PTR router.home-net. 2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 10800 IN PTR iPad.home-net. |
ここでもホスト部分のIPアドレスは、左右を逆にして設定する必要があることが必要です。
⑧namedの起動
上記の設定ファイルを作成し終えたら、DNSサーバであるnamedを起動させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# systemctl enable named # systemctl restart named # systemctl status named Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; preset: enabled) Active: active (running) since Wed 2024-09-07 09:01:07 JST; 1s ago Docs: man:named(8) Main PID: 48790 (named) Status: "running" Tasks: 10 (limit: 9446) Memory: 6.0M (peak: 6.7M) CPU: 41ms CGroup: /system.slice/named.service └─48790 /usr/sbin/named -f -u bind |
⑨テスト
それでは、DNSで名前解決ができるかテストしてみましょう。テストにはnslookupコマンドを利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# nslookup > server 127.0.0.1 Default server: 127.0.0.1 Address: 127.0.0.1#53 > router.home-net Server: 127.0.0.1 Address: 127.0.0.1#53 Name: router.home-net Address: 192.168.1.1 Name: router.home-net Address: fc00:cafe::1 > 192.168.1.1 1.1.168.192.in-addr.arpa name = router.home-net. > fc00:cafe::1 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.e.f.a.c.0.0.c.f.ip6.arpa name = router.home-net. > exit |
まず、1行目でnslookupコマンドを引数なしで実行し対話モードに入ります。次に2行目で「server 127.0.0.1」と入力し、問い合わせ先DNSサーバを自ホスト(ホームサーバ)に設定します。5行目で「router.home-net」と入力し、正引きのテストを行います。この例では10行目と12行目でIPv4とIPv6のIPアドレスが引けていることが分かります。次に13行目で「192.168.1.1」と入力し、IPv4アドレスの逆引きのテストを行います。この例では14行目で「router.home-net」が引けていることがわかります。さらに15行目で「fc00:cafe::1」と入力し、IPv6アドレスの逆引きのテストを行います。この例では16行目で「router.home-net」が引けていることがわかります。
Cockpit拡張機能の構築
ここまででDHCPサーバとDNSさーばの設定は完了ですが、新規に機器が増えた際には「dhcpd.hosts」「dhcpd6.hosts」「db.home-net」「db.192.168.1」「db.fc00:cafe」の5つのファイルにホスト名・MACアドレス・IPアドレスをそれぞれ追記する必要があり運用が大変です。このためCockpitを用いて家内の機器の登録・変更・削除ができるようにしていきます。
同じような悩みを持っている人も多いのでは?とCokpitのアドオンを探したのですが、DNSやDHCPに関するアドオンはなかったのでオリジナルで構築することにしました。今回構築した仕組みは以下の通りです。
①Cokcpitの画面から新しい機器のMACアドレス、ホスト名、IPアドレスを入力
②Cockpitからサーバ上の機器一覧情報を記載した「hostlist.txt」ファイルを更新する
③Cockpitからサーバ上のdhcp_dns.shを実行する
④dhcp_dns.shでhostlist.txtを読み込みDHCPやDNSに必要な5つのファイルを作成する
構築するCockpitの入力画面は以下の通りです。
なおCockpitアドオンの使い方についても日本語の情報はほとんどない状態なので、別途記事にしたいと考えています。
①Cockpitのインストール
まずはCockpitをインストールが必要です。Cockpitのインストールは何度もやっているのでもう日常作業化しています。
1 |
# apt-get install cokpit |
②Systemdへの登録
CockpitをインストールしたらSystemdに登録して起動します。
1 2 3 |
# systemctl enable cockpit # systemctl start cockpit # systemctl status cockpit |
③Cockpitの起動確認
Webブラウザで「http://<IPアドレス>:9090」にアクセスしてCockpitの画面が表示されるか確認します。
④Cockpitアドオンフォルダの作成
Cockpitのアドオンは「/usr/share/cockpit/」にフォルダを作成することで作れます。今回は「DHCPandDNS」というフォルダを作成します。
1 2 |
# cd /usr/share/cockpit # mkdir DHCPandDNS |
⑤manifest.jsonの作成
Cockpitにアドオンとして認識させるために、以下の「manifest.json」を作成します。
1 2 |
# cd DHCPandDNS # vi manifest.json |
「manifest.json」の内容は以下の通りです。
1 2 3 4 5 6 7 8 9 10 |
{ "version": 0, "tools": { "DHCPandDNS": { "label": "DHCP・DNS登録", "path": "dhcp_dns.html" } } } |
内容は非常にシンプルですね。labelの部分でCockpitの画面に表示するメニュー名を、pathの部分で画面表示するためのhtmlファイル名を指定しています。
⑥dhcp_dns.htmlの作成
次にCockpitの画面に表示するための「dhcp_dns.html」を作成していきます。
1 |
# vi dhcp_dns.html |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
<!DOCTYPE html> <html id="networkmanager-page" lang="ja" dir="ltr"> <head> <title>DHCP・DNS登録</title> <meta charset="utf-8"> <link href="../network/networkmanager.css" type="text/css" rel="stylesheet"> <link href="dhcp_dns.css" type="text/css" rel="stylesheet"> <script src="../base1/cockpit.js"></script> </head> <body class="pf-v5-m-tabular-nums"> <div id="network-page" class="ct-page-fill network-page"> <div id="networking" class="pf-v5-c-page"> <main class="pf-v5-c-page__main" tabindex="-1"> <section class="pf-v5-c-page__main-section"> <div class="pf-v5-l-gallery pf-m-gutter"> <!--ホストの追加・編集--> <div id="networking-interfaces" class="pf-v5-c-card" data-ouia-component-type="PF5/Card" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Card-1"> <div class="pf-v5-c-card__header"> <div class="pf-v5-c-card__actions"></div> <div class="pf-v5-c-card__header-main"> <div class="pf-v5-c-card__title"> <h2 class="pf-v5-c-card__title-text" id="networking-interfaces-title">ホストの登録・編集</h2> </div> </div> <table aria-label="ホストの登録・編集" role="grid" class="ct-table pf-v5-c-table pf-m-grid-md pf-m-compact" data-ouia-component-type="PF5/Table" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Table-1"> <tbody role="rowgroup" class="pf-v5-c-table__tbody"> <tr class="pf-v5-c-table__tr" data-ouia-component-type="PF5/TableRow" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TableRow-3" data-interface="ens1" data-sample-id="ens1" data-row-id="ens1"> <td tabindex="-1" data-label="IPv4アドレス" scope="col" class="pf-v5-c-table__th pf-m-width-10"> <input id="input_ipv4" minlength="1" type="text" aria-invalid="false" placeholder="IPv4アドレス" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TextInputBase-2" size="10" value=""> </td> <td tabindex="-1" data-label="IPv6アドレス" scope="col" class="pf-v5-c-table__td pf-m-width-20"> <input id="input_ipv6" minlength="1" type="text" aria-invalid="false" placeholder="IPv6アドレス" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TextInputBase-2" size="35" value=""> </td> <td tabindex="-1" data-label="ホスト名" class="pf-v5-c-table__td pf-m-width-10"> <input id="input_name" minlength="1" type="text" aria-invalid="false" placeholder="ホスト名" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TextInputBase-2" value=""> </td> <td tabindex="-1" data-label="MACアドレス" class="pf-v5-c-table__td pf-m-width-10"> <input id="input_mac" minlength="1" type="text" aria-invalid="false" placeholder="MACアドレス" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TextInputBase-2" value=""> </td> <td tabindex="-1" data-label="備考" class="pf-v5-c-table__td pf-m-width-15"> <input id="input_remark" minlength="1" type="text" aria-invalid="false" placeholder="備考" data-ouia-component-type="PF5/TextInput" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TextInputBase-2" value=""> </td> <td tabindex="-1" data-label="" class="pf-v5-c-table__td pf-m-width-10"> <button id="button_update" aria-disabled="false" class="pf-v5-c-button pf-m-secondary" type="button" data-ouia-component-type="PF5/Button" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Button-secondary-11">登録・更新</button> </td> </tr> </tbody> </table> <div class="pf-v5-c-card__actions"></div> <div class="pf-v5-c-card__header-main"> <div class="pf-v5-c-card__title"> <h2 class="pf-v5-c-card__title-text" id="result"></h2> </div> </div> </div> </div> <!--ホスト一覧--> <div id="networking-interfaces" class="pf-v5-c-card" data-ouia-component-type="PF5/Card" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Card-1"> <div class="pf-v5-c-card__header"> <div class="pf-v5-c-card__actions"></div> <div class="pf-v5-c-card__header-main"> <div class="pf-v5-c-card__title"> <h2 class="pf-v5-c-card__title-text" id="networking-interfaces-title">登録ホスト一覧</h2> </div> </div> <table aria-label="登録ホスト一覧" role="grid" class="ct-table pf-v5-c-table pf-m-grid-md pf-m-compact" data-ouia-component-type="PF5/Table" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Table-1"> <thead class="pf-v5-c-table__thead"> <tr class="pf-v5-c-table__tr" data-ouia-component-type="PF5/TableRow" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TableRow-1"> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-10">IPv4アドレス</th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-20">IPv6アドレス</th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-10">ホスト名</th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-10">MACアドレス</th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-15">備考</th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-10"> </th> <th tabindex="-1" scope="col" class="pf-v5-c-table__th pf-m-width-10"> </th> </tr> </thead> <tbody role="rowgroup" class="pf-v5-c-table__tbody"> </tbody> <tbody id="hostlist" role="rowgroup" class="pf-v5-c-table__tbody"> <tr class="pf-v5-c-table__tr" data-ouia-component-type="PF5/TableRow" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-TableRow-3" data-interface="ens1" data-sample-id="ens1" data-row-id="ens1"> <td tabindex="-1" data-label="IPv4アドレス" scope="col" class="pf-v5-c-table__th"></td> <td tabindex="-1" data-label="IPv6アドレス" scope="col" class="pf-v5-c-table__td"></td> <td tabindex="-1" data-label="ホスト名" class="pf-v5-c-table__td"></td> <td tabindex="-1" data-label="MACアドレス" class="pf-v5-c-table__td"></td> <td tabindex="-1" data-label="備考" class="pf-v5-c-table__td"></td> <td tabindex="-1" data-label="" class="pf-v5-c-table__td"> <button id="button_edit" aria-disabled="false" class="pf-v5-c-button pf-m-secondary" type="button" data-ouia-component-type="PF5/Button" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Button-secondary-11">編集</button> </td> <td tabindex="-1" data-label="" class="pf-v5-c-table__td"> <button id="button_delete" aria-disabled="false" class="pf-v5-c-button pf-m-secondary" type="button" data-ouia-component-type="PF5/Button" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Button-secondary-11">削除</button> </td> </tr> </tbody> </table> </div> </div> <!--ログ一覧--> <div id="networking-interfaces" class="pf-v5-c-card" data-ouia-component-type="PF5/Card" data-ouia-safe="true" data-ouia-component-id="OUIA-Generated-Card-1"> <div class="pf-v5-c-card__header"> <div class="pf-v5-c-card__actions"></div> <div class="pf-v5-c-card__header-main"> <div class="pf-v5-c-card__title"> <h2 class="pf-v5-c-card__title-text" id="networking-interfaces-title">DHCPログ(/var/log/dhcp.log)</h2> </div> </div> </div> <div class="pf-v5-c-card__header" > <pre id="logout"></pre> </div> </div> </div> </div> </main> </div> </div> <script src="dhcp_dns.js"></script> </body> </html> |
内容は、他画面に合わせるCSS設定が面倒ですが基本的にはHTMLなので特筆すべきところはないと思います。あえて上げるなら8行目で「cockpit.js」を122行目で後述の「dhcp_dns.js」のJavaScriptを読み込んでいるところぐらいでしょうか。
⑦dhcp_dns.cssの作成
この画面のCSSは基本的にはNetworkManagerのCSSを利用していますが、一部オリジナルのCSSを利用するために「dhcp_dns.css」を作成します。
1 |
# vi dhcp_dns.css |
1 2 3 4 |
pre#logout { width: 1300px; white-space: pre-wrap; } |
⑧dhcp_dns.jsの作成
Cockpitを用いたDHCP&DNS登録機能の肝である「dhcp_dns.js」を作成していきます。
1 |
# vi dhcp_dns.js |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
/************************** * 設定項目 **************************/ const filename_hostlist="/usr/share/cockpit/DHCPandDNS/hostlist.txt"; const filename_logs="/var/log/dhcpd.log"; const filename_script="/usr/share/cockpit/DHCPandDNS/dhcp_dns.sh"; /************************** * HTMLのノードを取得 **************************/ //登録フォーム const input_ipv4 = document.getElementById("input_ipv4"); const input_ipv6 = document.getElementById("input_ipv6"); const input_name = document.getElementById("input_name"); const input_mac = document.getElementById("input_mac"); const input_remark = document.getElementById("input_remark"); const result = document.getElementById("result"); const button_update = document.getElementById("button_update"); //ホスト一覧表 const html_hostlist = document.getElementById("hostlist"); //DHCPログ一覧 const logout = document.getElementById("logout"); //テスト用 const button = document.getElementById("button_edit"); /************************** * グローバル変数 **************************/ //グローバル変数 const hostsArray = []; //ホスト一覧を格納する配列を用意 const isDebug=false; /************************** * サーバからのホスト情報の読み込み **************************/ //サーバからホストリストファイルを読み込み function read_run() { cockpit.file(filename_hostlist).read() .then(read_success) .catch(error => { result.style.color = "red"; result.textContent = "サーバファイルの読み込み失敗"; }); result.textContent = ""; } //読み込み成功時の処理 function read_success(data,tag) { //表示を変える if(data != null){ result.style.color = "green"; result.textContent = ""; }else{ result.style.color = "red"; result.textContent = "サーバファイルの読み込み失敗"; return; } //ホスト一覧を初期化 hostsArray.splice(0); const dataString = data.split('\n'); //改行で分割 for (let i = 0; i < dataString.length; i++) { //あるだけループ hostsArray[i] = dataString[i].split(','); } //出力処理 update_hostlist(); } /************************** * ホスト情報の追加・更新・削除処理 **************************/ //HTMLにホスト一覧を出力する function update_hostlist(){ //配列の要素が1以下だったら何もしない if(hostsArray.length<1)return; //表の最初の子ノードを取得 const line=html_hostlist.childNodes[1]; //表の全ての子ノードを一旦削除 while (html_hostlist.firstChild) {html_hostlist.removeChild(html_hostlist.firstChild);} //Hostlistの数だけ表の行を作成 for (let i = 0; i < hostsArray.length; i++) { if(hostsArray[i].length>=4){ //配列の要素が4つ以上あるノードのみ const l=line.cloneNode(true); l.childNodes[1].textContent=hostsArray[i][0]; l.childNodes[3].textContent=hostsArray[i][1]; l.childNodes[5].textContent=hostsArray[i][2]; l.childNodes[7].textContent=hostsArray[i][3]; l.childNodes[9].textContent=hostsArray[i][4]; l.childNodes[11].childNodes[1].addEventListener("click",function(){ edit_host(i)}); l.childNodes[13].childNodes[1].addEventListener("click",function(){ delete_host(i)}); //console.log(l.childNodes[11].childNodes[1]); html_hostlist.append(l); } } } //編集ボタンが押された時 function edit_host(index){ console.log("Editボタンが押されました。index="+index); //編集入力ボックスに値をセット input_ipv4.value=hostsArray[index][0]; input_ipv6.value=hostsArray[index][1]; input_name.value=hostsArray[index][2]; input_mac.value=hostsArray[index][3]; input_remark.value=hostsArray[index][4]; //画面表示をクリア result.textContent = ""; } //削除ボタンが押された時 function delete_host(index){ if(isDebug){console.log("Deleteボタンが押されました。index="+index);} //確認ダイアログを表示 if(window.confirm(hostsArray[index][0]+"のホストを削除します。よろしいですか?")==false){return;} //要素を削除 hostsArray.splice(index,1); //サーバ上のファイルを更新 upload_hostlist(); //表示を更新 update_hostlist(); } //登録・更新ボタンが押された時 function update_host(){ if(isDebug){console.log("Updateボタンが押されました。");} //入力チェック if(input_ipv4.value==""){alert("IPv4アドレスが未入力です。");return;} if(input_ipv6.value==""){alert("IPv6アドレスが未入力です。");return;} if(input_name.value==""){alert("ホスト名が未入力です。");return;} if(input_mac.value==""){alert("MACアドレスが未入力です。");return;} //正規表現チェック if(input_ipv4.value.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)==null){alert("IPv4アドレスの形式が間違っています");return;} if(input_ipv6.value.match(/^([0-9a-fA-F]{4}:){7}[0-9a-fA-F]{4}$/)==null){alert("IPv6アドレスの形式が間違っています");return;} if(input_name.value.match(/^[0-9a-zA-Z][0-9a-zA-Z]*$/)==null){alert("ホスト名は英数字で入力してください");return;} if(input_mac.value.match(/^[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}$/)==null){alert("MACアドレスの形式が間違っています");return;} //IPv4アドレスが同じものを探す=更新の場合 var flag=false; for (let i = 0; i < hostsArray.length; i++) { if(input_ipv4.value==hostsArray[i][0]){ //重複チェック for (let j = 0; j < hostsArray.length; j++) { if(input_ipv6.value==hostsArray[j][1] && i!=j){alert("IPv6アドレスが重複しています");return;} if(input_name.value==hostsArray[j][2] && i!=j){alert("ホスト名が重複しています");return;} if(input_mac.value==hostsArray[j][3] && i!=j){alert("MACアドレスが重複しています");return;} } //フォームデータを配列に転記 hostsArray[i][1]=input_ipv6.value hostsArray[i][2]=input_name.value hostsArray[i][3]=input_mac.value hostsArray[i][4]=input_remark.value flag=true; } } //追加の場合 if(flag==false){ //重複チェック for (let i = 0; i < hostsArray.length; i++) { if(input_ipv6.value==hostsArray[i][1]){alert("IPv6アドレスが重複しています");return;} if(input_name.value==hostsArray[i][2]){alert("ホスト名が重複しています");return;} if(input_mac.value==hostsArray[i][3]){alert("MACアドレスが重複しています");return;} } hostsArray[hostsArray.length]=[input_ipv4.value,input_ipv6.value,input_name.value,input_mac.value,input_remark.value]; } //並び替え hostsArray.sort(function(first, second) { const f = first[0].split('.'); //.で分割 const s = second[0].split('.'); //.で分割 if(Number(f[3]) > Number(s[3])){ return 1; }else{return -1;} return 0; }); //サーバ上のファイルを更新 upload_hostlist(); //表示を更新 input_ipv4.value=""; input_ipv6.value=""; input_name.value=""; input_mac.value=""; update_hostlist(); } /************************** * サーバ上のファイルを更新 **************************/ //サーバ上のファイルを更新 function upload_hostlist(){ //サーバのファイルのバックアップ cockpit.spawn(["cp", filename_hostlist, filename_hostlist+"."+dateStr()]) .then(mv_success) .catch(error => { result.style.color = "red"; result.textContent = "サーバ上のファイルのバックアップに失敗しました"; }); } //ファイルのバックアップが成功した function mv_success(){ if(isDebug){console.log("サーバ上のファイルのバックアップが成功しました");} //配列をCSVへ変換 var csvData=""; for (let i = 0; i < hostsArray.length; i++) { if(hostsArray[i].length>=4){ //配列の要素が4つ以上あるノードのみ csvData+=hostsArray[i].join(',')+ '\r\n'; } } //CSVファイルを表示(テスト用) if(isDebug){console.log(csvData);} //オリジナルのファイルに書き込み cockpit.file(filename_hostlist).replace(csvData) .then(tag => { //サーバ側の設定ファイル作成スクリプトを実行 cockpit.spawn(["/bin/bash", filename_script, filename_hostlist],{ superuser: "try" }) .then(tag => { result.style.color = "green"; result.textContent = "ホスト一覧の更新とDHCP・DNSサーバの再起動が完了しました"; }) .catch(error => { result.style.color = "red"; result.textContent = "サーバ上のスクリプトファイルの実行に失敗しました。"; }); }) .catch(error => { result.style.color = "red"; result.textContent = "サーバ上のファイルの書き込みに失敗しました"; }); } //日付時間の文字列を生成 function dateStr(){ var d = new Date(); return d.toLocaleDateString('sv-SE')+'_'+ [d.getHours(),d.getMinutes(), d.getSeconds()].join(''); } /************************** * ログファイルの読み込み **************************/ function readLog(){ cockpit.spawn(["tail", "-n", "30", filename_logs]) .stream(data => { logout.append(document.createTextNode(data)); }) .then(() => { //何もしない }) .catch(error => { result.style.color = "red"; result.textContent = "ログファイルの読み込みに失敗しました"; }); } //ボタンへのイベントリスナ登録 button_update.addEventListener("click", update_host); //button.addEventListener("click", readLog); //テスト用 // Send a 'init' message. This tells integration tests that we are ready to go cockpit.transport.wait(function() { }); //サーバファイルの読み込み read_run(); //ログファイルの読み込み readLog(); |
1〜7行目はサーバ上のファイルパスの記載であり、8〜24行目は上で作成したHTML上の各要素の取得部分です。26〜31行目で、グローバル変数を宣言します。ここのisDebugをtrueにすると、各種ログを出力するようにできます。
次に33〜70行目は、サーバ上の「hostlist.txt」の読み込み処理となります。38行目で「cockpit.file()」のメソッドを用いることでCockpitの機能を用いてサーバ上のファイルを読み込むことができます。読み込んだCSV形式のファイルは、63〜66行目でカンマ区切りで処理され、hostsArray配列に読み込まれます。
次の72〜100行目は、hostsArray配列の内容をHTMLに出力する処理となります。103〜113行目は、画面上の「編集」ボタンが押された時の処理であり、画面の一番上の登録・編集欄に対象ホストのIPアドレスやMACアドレスなどを転記します。115〜127行目は、「削除」ボタンが押された時の処理であり、確認ダイアログを出した後に、hostsArray配列から対象の一行を削除し、後述する「upload_hostlist()」を呼び出してサーバ上の「hostlist.txt」を更新します。
次の130〜191行目は、画面上の「登録・更新」ボタンが押された時の処理であり、未入力チェック、正規表現によるIPアドレスやMACアドレスのチェック、重複確認チェックを行った上でhostsArray配列を更新します。また、174〜183行目でIPv4アドレスの第4オクテットの昇順で並べ替えを行なっています。
次の193〜247行目が、サーバ上のファイル処理になります。まず、197〜205行目で、「cockpit.spawn()」メソッドを用いて、サーバ上で「CP」コマンドを実行し「hostliext.txt」のバックアップを作成します。212〜217行目で、hostsAarry配列の内容をCSVファイル形式に変換し、222行目で「cockpit.file()」メソッドを用いて、サーバ上の「hostlist.txt」ファイルを上書きします。そして、ファイル書き込みに成功した場合には、後述の「dhcp_dns.sh」シェルスクリプトをroot権限で実行(「superuser: “try”」を指定)します。
最後に252〜264行目は、サーバ上の「/var/log/dhcp.log」を読み込んで画面に表示する処理となります。上の設定でDHCP用のログを別ファイルに分けておいたのもこの理由です。
以上のように「cockpit.spawn()」メソッドを用いてコマンドを叩けたり、「cockpit.file()」を用いてファイルアクセスが簡単にできるところがCockpitを用いて開発するメリットとなると思います。
⑧dhcp_dns.shの作成
ここからはCockpitから実行され「hostlist.txt」を読み込んで「dhcp.hosts」や「db.home-net」などのファイルを作成するシェルスクリプトである「dhcp_dns.sh」を作成します。
1 |
# vi dhcp_dns.sh |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
#!/bin/bash PATH_DHCP4="/etc/dhcp/dhcpd.hosts" PATH_DHCP6="/etc/dhcp/dhcpd6.hosts" PATH_DNS="/etc/bind/db.home-net" PATH_DNS_TEMP="/etc/bind/db.home-net.temp" PATH_DNSIP="/etc/bind/db.192.168.1" PATH_DNSIP_TEMP="/etc/bind/db.192.168.1.temp" PATH_DNSIP6="/etc/bind/db.fc00:cafe" PATH_DNSIP6_TEMP="/etc/bind/db.fc00:cafe.temp" DOMAIN_NAME="home-net" #ファイルのバックアップ cp $PATH_DHCP4 $PATH_DHCP4'.'`date '+%Y-%m-%d_%H%M%S'` cp $PATH_DHCP6 $PATH_DHCP6'.'`date '+%Y-%m-%d_%H%M%S'` cp $PATH_DNS $PATH_DNS'.'`date '+%Y-%m-%d_%H%M%S'` cp $PATH_DNSIP $PATH_DNSIP'.'`date '+%Y-%m-%d_%H%M%S'` cp $PATH_DNSIP6 $PATH_DNSIP6'.'`date '+%Y-%m-%d_%H%M%S'` #対象ファイルをクリア echo "" > $PATH_DHCP4 echo "" > $PATH_DHCP6 #テンプレートファイルをコピー cp $PATH_DNS_TEMP $PATH_DNS cp $PATH_DNSIP_TEMP $PATH_DNSIP cp $PATH_DNSIP6_TEMP $PATH_DNSIP6 #一行ずつ処理する while read line do # $lineに読み込んだCSVファイルの一行のテキストが格納され、それをcutコマンドでカラムに分割し変数に格納 ipv4=$(echo ${line} | cut -d , -f 1) ipv6=$(echo ${line} | cut -d , -f 2) name=$(echo ${line} | cut -d , -f 3) mac=$(echo ${line} | cut -d , -f 4) remark=$(echo ${line} | cut -d , -f 5) #------------------------------------------------- #ipv4アドレスの処理(最終オクテットのみとる) #------------------------------------------------- ipv4adder=`echo $ipv4|awk -F'[.]' '{print $4}'` #echo "ipv4adder="${ipv4adder} #------------------------------------------------- #ipv6アドレスの処理(:をとって一文字ずつに.で区切り左右逆転) #------------------------------------------------- ipv6adder="" i=0 while read -N1 c; do list[i]=${c} i=`expr $i + 1` done <<EOF `echo $ipv6|sed -e "s/://g"` EOF #デバック用 #echo "i="$i #echo "list="${list[@]} i=`expr $i - 2` #なぜか2大きくなるのでマイナス for j in `seq $i -1 28` do if [ $j -eq 28 ]; then ipv6adder+="${list[${j}]}" else ipv6adder+="${list[${j}]}." fi done #echo $ipv6adder #------------------------------------------------- #DHCP、DCP6、DNS正引き、DNS逆引きipv4、DNS逆引きipv6ファイルの作成 #------------------------------------------------- # DHCPipv4用のファイルを作成 cat <<EOF >>$PATH_DHCP4 host ${name} { hardware ethernet ${mac}; fixed-address ${ipv4}; } EOF # DHCPipv6用のファイルを作成 cat <<EOF >>$PATH_DHCP6 host ${name} { hardware ethernet ${mac}; fixed-address6 ${ipv6}; } EOF # DNS正引き用のファイルを作成 cat <<EOF >>$PATH_DNS ${name}.$DOMAIN_NAME. 10800 IN A ${ipv4} ${name}.$DOMAIN_NAME. 10800 IN AAAA ${ipv6} EOF # DNS逆引きipv4のファイルを作成 cat <<EOF >>$PATH_DNSIP ${ipv4adder} 10800 IN PTR ${name}.home-net. EOF # DNSIP6のファイルを作成 cat <<EOF >>$PATH_DNSIP6 ${ipv6adder} 10800 IN PTR ${name}.home-net. EOF # $colX で読み込んだCSVファイルのテキストを参照 echo "$ipv4,$ipv6,$name,$mac,$remark" done < $1 #------------------------------------------------- #DHCPサーバとDNSサーバの再起動 #------------------------------------------------- systemctl restart isc-dhcp-server systemctl restart isc-dhcp-server6 systemctl restart bind9 |
まず、3〜11行目でサーバ上のファイルパスの設定を行い、14〜19行目でファイルのバックアップを作成し、22〜27行目でDHCP系の2ファイルは初期化、DNS系の3ファイルはテンプレートファイルのコピーを行います。
次の30行目以降の部分でhostlist.txt(第1引数に指定のファイル)の1行づつを読み取って処理を行なっていきます。まず、32〜37行目でカンマで分割し各変数に値を格納します。次に、42行目でIPv4アドレスをドットで区切って、最終オクテット部分(ホスト部)を求めます。
48〜69行目は、IPv6アドレスのホスト部を求める処理ですが、こちらは少々大変です。50〜55行目でコロンを削除(「sed -e “s/://g”」の部分)したIPv6アドレスの一文字づつを「list」配列に読み込みます。次に60〜68行目でlist配列の大きい方から逆順に一文字つづを読み取って「ipa6dder」に追記していきます。この処理により、IPv6アドレスのホスト部分の逆順のドット区切りの文字列が得られます。
ここまできたら71〜100行目で、それぞれの形式に従ってIPアドレスやMACアドレスの情報を各ファイルに書き込んでいきます。最後に109〜122行目で1、DHCP4サーバ、DHCP6サーバ、DNSサーバの再起動を行なって完了です。
⑨テンプレートファイルの作成
ここまで来たらあと一息です。「dhcp_dns.sh」に読み込ませるテンプレートファイルを作成していきます。
1 |
# vi /etc/bind/db.home-net.temp |
1 2 3 4 5 6 7 8 9 |
home-net. 86400 IN SOA ns.home-net. admin.home-net. ( 2024090707 ; serial 3600 ; refresh (1 hour) 300 ; retry (5 minutes) 4294962816 ; expire (7101 weeks 3 days 5 hours 13 minutes 36 seconds) 20864 ; minimum (5 hours 47 minutes 44 seconds) ) 10800 IN NS router.home-net. 86400 IN MX 10 mx.home-net. |
ファイルの内容は見ていただくと分かる通り、各ゾーンファイルの冒頭の定義部分となります。同様にして「/etc/bind/db.192.168.1.temp」「db.fc00:cafe.temp」も作成します。
⑩テスト
ここまでファイルを作成したら「http://<IPアドレス>:9090」にアクセスしてCockpitの画面を表示させましょう。すると左側のメニューに「DHCP・DNS投獄」が追加されている事が確認できると思います。ホストの追加や更新削除など、正しく動作するか確認してみましょう。
(参考)ファイル一式のダウンロード
上記の実際のファイルはここに置いておきますので、参考までにダンロードして使ってください。
おわりに
今回は、家内のPC・スマホ・タブレットの利用に欠かせないDHCPやDNSの設定を行いました。さらに、端末が追加になった時の運用作業を効率化するためのCockpitの拡張機能を開発していきました。全ての端末がMACアドレスに基づき固定のIPアドレスとなることから、ファイヤウォールでの帯域制限、ログ監視などが可能になります。
次回は、6回目として「NoderedとMQTTでホームサーバ構築編」を書いていきたいと思います。
第1回:Ubuntuインストール編
第2回:KVMとCockpitで仮想サーバ構築編
第3回:ApacheとWordpressでWebサーバ構築編
第4回:PostfixとRainloopでメールサーバ構築編
第5回:DNS・DHCPサーバ構築編
第6回:NoderedとMQTTでホームサーバ構築編 ←次回
番外編:KVM上でのWindows11のインストール編
番外編:KVM上でのUbuntu Serverのインストール編