ISC DHCP Server
フリーのDHCPサーバとして利用される事もあるソフトウェア。
DNSサーバのBINDで有名なISCが開発を行っている。
近年、後継のKEA DHCPに取って代わるとアナウンスされているが、
failoverなどの未実装機能が多い為、安定するまではISC DHCPの方が良いと思われる。
0. 事前準備
ISC DHCPをビルドするには、autoconf 2.65以上が必要になる。
CentOS6でautoconfをデフォルトのまま使っていた場合、この条件を満たせないので、
事前に最新版のautoconfを導入しておく。
この時、インストール先ディレクトリによってはPATH優先度の関係で、
既存のautoconfのPATH(/usr/bin)が優先されてしまい、
追加インストールしたautoconfを参照していない状態になるので注意する
$ tar zxvf autoconf-2.69.tar.gz $ cd autoconf-2.69 $ ./configure $ make && make install $ cd /usr/bin $ mv autoconf autoconf.yum-org $ mv autoheader autoheader.yum-org $ mv autom4te autom4te.yum-org $ mv autoreconf autoreconf.yum-org $ mv autoscan autoscan.yum-org $ mv autoupdate autoupdate.yum-org $ mv ifnames ifnames.yum-org $ ln -s /usr/local/bin/autoconf $ ln -s /usr/local/bin/autoheader $ ln -s /usr/local/bin/autom4te $ ln -s /usr/local/bin/autoreconf $ ln -s /usr/local/bin/autoscan $ ln -s /usr/local/bin/autoupdate $ ln -s /usr/local/bin/ifnames
1. ソースコード改変
GCCのバージョンが8.2.0以上の場合、ISC DHCP v4.4.1以下では下記のビルドエラーが発生する。
configureスクリプトの"-Werror"を削除する事でもビルドを通す事が可能だが、
ソース改変によって根本的なエラー対策が可能なのでソース改変を行う。
discover.c: In function 'discover_interfaces': discover.c:646:4: error: 'strncpy' output may be truncated copying 15 bytes from a string of length 15 [-Werror=stringop-truncation] strncpy(tmp->name, info.name, sizeof(tmp->name) - 1); cc1: all warnings being treated as errors make[2]: *** [Makefile:519: discover.o] Error 1 make[2]: Leaving directory '/usr/local/src/dhcp-4.4.1/common' make[1]: *** [Makefile:582: all-recursive] Error 1 make[1]: Leaving directory '/usr/local/src/dhcp-4.4.1/common' make: *** [Makefile:462: all-recursive] Error 1
参考情報を元に、"dhcp-4.4.1"をオリジナル、"dhcp_4.4.1_gcc"を修正後としてディレクトリパッチを作成。
実際には3ファイルのみ修正する簡単な物なので、パッチ適用が面倒ならファイル毎に書き換える。
diff -uprN dhcp-4.4.1/common/discover.c dhcp-4.4.1_gcc/common/discover.c --- dhcp-4.4.1/common/discover.c 2018-02-21 23:30:46.000000000 +0900 +++ dhcp-4.4.1_gcc/common/discover.c 2019-08-14 15:36:23.890602209 +0900 @@ -643,7 +643,7 @@ discover_interfaces(int state) { log_fatal("Error allocating interface %s: %s", info.name, isc_result_totext(status)); } - strncpy(tmp->name, info.name, sizeof(tmp->name) - 1); + memcpy(tmp->name, info.name, sizeof(tmp->name)); interface_snorf(tmp, ir); interface_dereference(&tmp, MDL); tmp = interfaces; /* XXX */
diff -uprN dhcp-4.4.1/common/parse.c dhcp-4.4.1_gcc/common/parse.c --- dhcp-4.4.1/common/parse.c 2018-02-21 23:30:46.000000000 +0900 +++ dhcp-4.4.1_gcc/common/parse.c 2019-08-14 15:33:21.330767736 +0900 @@ -5567,7 +5567,8 @@ int parse_warn (struct parse *cfile, con va_list list; char lexbuf [256]; char mbuf [1024]; - char fbuf [1024]; + char fbuf [2048]; + char final[4096]; unsigned i, lix; do_percentm (mbuf, fmt); @@ -5578,7 +5579,7 @@ int parse_warn (struct parse *cfile, con cfile -> tlname, cfile -> lexline, mbuf); va_start (list, fmt); - vsnprintf (mbuf, sizeof mbuf, fbuf, list); + vsnprintf (final, sizeof final, fbuf, list); va_end (list); lix = 0; @@ -5594,14 +5595,14 @@ int parse_warn (struct parse *cfile, con lexbuf [lix] = 0; #ifndef DEBUG - syslog (LOG_ERR, "%s", mbuf); + syslog (LOG_ERR, "%s", final); syslog (LOG_ERR, "%s", cfile -> token_line); if (cfile -> lexchar < 81) syslog (LOG_ERR, "%s^", lexbuf); #endif if (log_perror) { - IGNORE_RET (write (STDERR_FILENO, mbuf, strlen (mbuf))); + IGNORE_RET (write (STDERR_FILENO, final, strlen (final))); IGNORE_RET (write (STDERR_FILENO, "\n", 1)); IGNORE_RET (write (STDERR_FILENO, cfile -> token_line, strlen (cfile -> token_line)));
diff -uprN dhcp-4.4.1/relay/dhcrelay.c dhcp-4.4.1_gcc/relay/dhcrelay.c --- dhcp-4.4.1/relay/dhcrelay.c 2018-02-21 23:30:46.000000000 +0900 +++ dhcp-4.4.1_gcc/relay/dhcrelay.c 2019-08-14 15:37:23.620557375 +0900 @@ -2119,7 +2119,7 @@ void request_v4_interface(const char* na (flags & INTERFACE_UPSTREAM ? 'Y' : 'N'), (flags & INTERFACE_DOWNSTREAM ? 'Y' : 'N')); - strncpy(tmp->name, name, len); + memcpy(tmp->name, name, len); interface_snorf(tmp, (INTERFACE_REQUESTED | flags)); interface_dereference(&tmp, MDL); }
2. ビルド・インストール
事前にソースコードをダウンロードしておく。
必要に応じてビルドオプションを付与させる必要があるが、
今回は稼働テストなので出来る限り多くのオプションを付与しておく。
なお、automakeのバージョンが低い場合、ISC DHCPのビルドに失敗するので、
autoreconfを実行しautomakeの参照先を書き換えておく。
$ tar zxvf dhcp-4.3.4.tar.gz $ cd dhcp-4.3.4 $ autoreconf -f -i -v $ ./configure --prefix=/usr/local/dhcp-4.3.4 \ --enable-dependency-tracking \ --enable-maintainer-mode \ --enable-early-chroot \ --enable-failover \ --enable-execute \ --enable-tracing \ --enable-ipv4-pktinfo \ --enable-dhcpv4o6 \ --enable-dhcpv6 $ make $ make install $ cd /usr/local $ ln -s /usr/local/dhcp-4.3.4 dhcp
3. 起動オプションの設定
DHCPサーバを実行するユーザ・グループの設定や、
DHCP DISCOVERを待ち受けるインターフェースを指定する。
$ vi /etc/sysconfig/dhcpd -------------------------------------------------- PROG="dhcpd" USER="dhcpd" GROUP="dhcpd" INTERFACE="eth0" CONFIG="/usr/local/dhcp/etc/dhcpd.conf" OPTION="" ================================================== PROG = バイナリ名 USER = 実行ユーザ GROUP = 実行グループ INTERFACE = パケット待ち受けインターフェース CONFIG = 設定ファイルPATH OPTION = DHCPサーバの追加起動オプション
4. 起動スクリプトの作成
ソースコードには起動スクリプトが同梱されていないので自作する必要がある。
今回は従来通りのinitスクリプトで起動を行う。
サービスの実行ユーザ設定などは、上記のsysconfigファイルで行い、
initスクリプトはプロセスの起動・停止に特化したスクリプトとする。
$ vi /etc/init.d/dhcpd -------------------------------------------------- #!/bin/sh # # dhcpd This shell script takes care of starting and stopping # dhcpd (DHCP server). # # chkconfig: 345 55 45 # description: dhcpd is a Dynamic Host Configuration Protocol Server # probe: true # Program configuration . /etc/sysconfig/dhcpd RUNDIR="/var/run/${PROG}" PIDFILE="${RUNDIR}/${PROG}.pid" LEASEFILE="${RUNDIR}/${PROG}.lease" LOCKFILE="/var/lock/subsys/${PROG}" EXEC="/usr/local/dhcp/sbin/${PROG}" # IPv4 ARGS="-user ${USER} -group ${GROUP} -cf ${CONFIG} -pf ${PIDFILE} -lf ${LEASEFILE}" # IPv6 #ARGS="-6 -user ${USER} -group ${GROUP} -cf ${CONFIG} -pf ${PIDFILE} -lf ${LEASEFILE}" # Make running directory if [ ! -e ${RUNDIR} ] ;then mkdir ${RUNDIR} chown ${USER}.${GROUP} ${RUNDIR} chmod 750 ${RUNDIR} fi # Make lease file if [ ! -e ${LEASEFILE} ] ;then touch ${LEASEFILE} chown ${USER}.${GROUP} ${LEASEFILE} fi # Source function library . /etc/rc.d/init.d/functions # Source networking configuration . /etc/sysconfig/network # Check that networking is up [ ${NETWORKING} = "no" ] && exit 0 [ -f ${EXEC} ] || exit 0 [ -f ${CONFIG} ] || exit 0 # See how we were called case "$1" in start) # Start daemon echo -n "Starting ${PROG}: " if [ -e ${LOCKFILE} ] ;then echo "${PROG} running" exit 0 else daemon ${EXEC} ${ARGS} ${OPTION} ${INTERFACE} echo touch ${LOCKFILE} fi ;; stop) # Stop daemon echo -n "Shutting down ${PROG}: " killproc -p ${PIDFILE} rm -f ${LOCKFILE} echo ;; restart) $0 stop $0 start exit $? ;; *) echo "Usage: ${PROG} {start|stop|restart}" exit 1 esac exit 0
5. 起動準備
実行ユーザの準備やiptablesのポート開放を設定する。
DHCPサーバ宛はUDP:67、DHCPクライアント宛はUDP:68を使う点に注意。
下記の内容は例なので、自環境に合わせてチューニングした上で設定を行う。
・実行ユーザ、グループ作成 ================================================== $ groupadd dhcpd $ useradd -g dhcpd -d /var/run/dhcpd -s /sbin/nologin dhcpd
・起動スクリプト登録 ================================================== $ chmod 755 /etc/init.d/dhcpd $ chkconfig --add dhcpd $ chkconfig --list dhcpd -------------------------------------------------- dhcpd 0:off 1:off 2:off 3:on 4:on 5:on 6:off
・iptables開放 ================================================== $ iptables -A INPUT -p udp --dport 67 -j ACCEPT
A. シングル構成・別セグメント待受
クライアントセグメントの外にDHCPサーバを設置し、複数セグメントのDHCPサーバを集約管理する時の設定。
DHCPサーバ設置セグメントにDHCPでIPアドレスを払い出さない場合でも、
サーバ自体が所属するサブネットについて設定を入れないと、プログラムが起動しなくなる点に注意する。
下記の設定内容では、サーバセグメントにはDHCPで払い出しを行わないが、
サーバを起動させる為に、何も払い出さない設定を追加してある。
・DHCPサーバ 構成情報
ネットワーク | 10.0.0.0/24 |
インターフェース | eth0 (1NIC構成) |
・DHCPクライアント IP払出し情報
ネットワーク | 10.10.0.0/24 |
デフォルトルート | 10.10.0.254 |
IP払い出し範囲 | 10.10.0.100~10.10.0.200 |
プライマリDNS | 10.0.0.10 |
セカンダリDNS | 10.0.0.20 |
・補足
DHCPクライアントとDHCPサーバが別セグメントになり、DHCP DISCOVERがDHCPサーバまで届かなくなるので、
クライアントのデフォルトルートとなるルータに、DHCPリレー設定を入れておく。
・設定内容
$ vi /usr/local/dhcp/etc/dhcpd.conf -------------------------------------------------- ddns-update-style interim; #DHCPリース要求時の開放動作設定 ignore client-updates; #DNSサーバエントリ自動更新時のドメイン制御指定 not authoritative; #セグメント設定情報の信頼度 log-facility local1; #ログ出力先 shared-network "DHCP" { subnet 10.0.0.0 netmask 255.255.255.0 { } subnet 10.10.0.0 netmask 255.255.255.0 { option routers 10.10.0.254; option subnet-mask 255.255.255.0; option broadcast-address 10.10.0.255; option domain-name-servers 10.0.0.10,10.0.0.20; #プライマリとセカンダリの間に、カンマを入れる range 10.10.0.100 10.10.0.200; #始点と終端の間に、スペースを入れる default-lease-time 3600; max-lease-time 86400; } }
B. 冗長構成・別セグメント待受
Aの内容でDHCPサーバを冗長構成にした場合の設定サンプル。
DHCPサーバを楽に冗長化する場合、各サーバから払い出すIPアドレス範囲を重ならないように分割し、分散配置する場合が多いのだが、
今回はDHCPサーバのFailover機能を使って、lease情報の共有を行い冗長化をする。
・DHCPサーバ 構成情報
Primary | Secondary | |
サーバIPアドレス | 10.0.0.101 | 10.0.10.101 |
ネットワーク | 10.0.0.0/24 | 10.0.10.0/24 |
インターフェース | eth0 (1NIC構成) | |
keep-alive通信 | TCP/647 |
・DHCPクライアント IP払出し情報
ネットワーク | 10.10.0.0/24 |
デフォルトルート | 10.10.0.254 |
IP払い出し範囲 | 10.10.0.100~10.10.0.200 |
プライマリDNS | 10.0.0.10 |
セカンダリDNS | 10.0.0.20 |
・補足
例の如く、DHCPリレー設定を入れる必要があるのだが、
DHCPサーバが冗長化されている場合、両サーバにDHCP DISCOVERが届くように設定する必要がある。
CISCOルータの場合、1つのインターフェースに複数のリレー先を設定出来るが、
1812J-IOSv15でテストした所、設定したリレー先全てにDISCOVERが転送される事が確認出来た。
今回の冗長化構成では、lease情報を共有しているので複数のDHCPサーバに同時転送されても問題無いが、
払い出し範囲を分割する事で冗長化している場合、もしかしたら不具合が起きるかもしれない。
・設定内容(Primary)
$ vi /usr/local/dhcp/etc/dhcpd.conf -------------------------------------------------- ddns-update-style interim; ignore client-updates; not authoritative; log-facility local1; failover peer "failover" { primary; #Primaryサーバである事を宣言 address 10.0.0.101; #自サーバIPアドレス port 647; #自サーバのFailoverプロトコル待ち受けポート。プロトコルはTCPのみ。デフォルトはTCP/647 peer address 10.0.10.101; #ピアサーバのIPアドレス peer port 647; #ピアサーバのFailoverプロトコル待ち受けポート。プロトコルはTCPのみ。デフォルトはTCP/647 load balance max seconds 3; #ピアがDHCP DISCOVERを処理出来なかった際に、強制で自サーバが処理を代行するまでの時間 max-response-delay 30; #ピアとの接続断と判断するまでの時間 max-unacked-updates 10; #Failrecovery後に1回の制御通信で同期するlease情報最大数 mclt 3600; #Primary限定。Primary障害時にlease時間を延長出来る最大時間 split 128; #Primary限定。負荷分散優先度を0~255で設定。0or255にするとActive-Standbyに(負荷分散しなく)なる } shared-network "DHCP" { subnet 10.0.0.0 netmask 255.255.255.0 { } subnet 10.0.10.0 netmask 255.255.255.0 { } subnet 10.10.0.0 netmask 255.255.255.0 { option routers 10.10.0.254; option subnet-mask 255.255.255.0; option broadcast-address 10.10.0.255; option domain-name-servers 10.0.0.10,10.0.0.20; default-lease-time 3600; max-lease-time 86400; pool { #設定を見やすくする為に、冗長化設定箇所をpoolで囲んでおく failover peer "failover"; #Failoverする際に使う設定項目を指定 range 10.10.0.100 10.10.0.200; #Primary-Secondary共通のIPアドレス払い出し範囲 deny dynamic bootp clients; #BOOTP-leaseは冗長化出来ないので拒否する } } }
・設定内容(Secondary)
$ vi /usr/local/dhcp/etc/dhcpd.conf -------------------------------------------------- ddns-update-style interim; ignore client-updates; not authoritative; log-facility daemon; failover peer "failover" { secondary; #Secondaryサーバである事を宣言 address 10.0.10.101; #自サーバIPアドレス。Primaryと逆設定になる点に注意 port 647; #自サーバのFailoverプロトコル待ち受けポート peer address 10.0.0.101; #ピアサーバのIPアドレス。Primaryと逆設定になる点に注意 peer port 647; #ピアサーバのFailoverプロトコル待ち受けポート load balance max seconds 3; max-response-delay 30; max-unacked-updates 10; } shared-network "DHCP" { subnet 10.0.0.0 netmask 255.255.255.0 { } subnet 10.0.10.0 netmask 255.255.255.0 { } subnet 10.10.0.0 netmask 255.255.255.0 { option routers 10.10.0.254; option subnet-mask 255.255.255.0; option broadcast-address 10.10.0.255; option domain-name-servers 10.0.0.10,10.0.0.20; default-lease-time 3600; max-lease-time 86400; pool { failover peer "failover"; range 10.10.0.100 10.10.0.200; deny dynamic bootp clients; } } }
C. IPv6(RA)・DNSサーバ配布
IPv6環境用のDHCPv6を構成する為の設定サンプル。
最近はRAによるEUI-64生成が多いが、DNSサーバなどの付加情報を付与するにはDHCPv6が必要となる。
なお、下記コンフィグではRAによるIPv6アドレス配布を前提としている。
dhcpdはオプション無しで起動するとIPv4動作となる為、
DHCPv6用で動かす為に、起動スクリプト内のARGS先頭に"-6"を付与してDHCPv6モードに切り替える。
・設定内容
ddns-update-style interim; ignore client-updates; not authoritative; log-facility daemon; allow leasequery; deny bootp; shared-network "DHCPv6" { subnet6 2001:aa:bb:cc::/64 { range6 2001:aa:bb:cc::/64; #エラー回避の為、仮のIPv6アドレスレンジを指定 #range6 2001:aa:bb:cc::0 2001:aa:bb:cc::ffff; #範囲指定する場合、割り当ての始点・終点を設定 option dhcp6.name-servers 2001:aa:bb:cc::a,2001:aa:bb:cc::b; #DNSサーバアドレス。間にカンマがある事に注意 option dhcp6.domain-search "local.domain.com"; #DNS検索リスト option dhcp6.info-refresh-time 21600; option dhcp-renewal-time 3600; option dhcp-rebinding-time 7200; default-lease-time 10800; preferred-lifetime 10800; max-lease-time 43200; } }
D. IPv6(EUI-64)・疑似クラスタ
IPv4はDHCPフェールオーバー(RFC2132)による、DHCPサーバ冗長構成が取れるが、
IPv6のDHCPフェールオーバー(RFC8156)は、ISC DHCPに実装されていない為、
フェールオーバーを用いたDHCPv6サーバ冗長化が出来ない。
その為、IPv6環境下でDHCPv6サーバ冗長化を行う場合、アドレスプールを細かく設定したDHCPv6サーバを複数台構築するか、
バックエンドにデータベースを利用出来るKEA DHCPでフロントエンドを増やす事が多いが、構成の複雑化がネックとなる。
シンプル構成でDHCPv6サーバを冗長化するには、、ISC DHCPでEUI-64方式によるIPv6アドレス配布を行う事により、
複数台のサーバを構築してもアドレス衝突が発生しなくなり、結果としてDHCPv6サーバの冗長化を行う事が出来る。
ただし、通常のソースビルドではEUI-64方式でのアドレッシングに対応していない為、
ヘッダーを書き換えてEUI-64オプションを有効化した後、ソースをリビルドする必要がある。
・ヘッダー書換
# vi dhcp-4.4.1/includes/site.h --- /* Enable EUI-64 Address assignment policy. Instructs the server * to use EUI-64 addressing instead of dynamic address allocation * for IA_NA pools, if the parameter use-eui-64 is true for the * pool. Can be at all scopes down to the pool level. Not * supported by the configure script. */ #define EUI_64
・設定内容
ddns-update-style interim; ignore client-updates; not authoritative; log-facility daemon; allow leasequery; deny bootp; persist-eui-64-leases true; #EUI-64で割り当てたIPv6アドレスを、 #アドレスリースファイルに書き込む shared-network "DHCPv6" { subnet6 2001:aa:bb:cc::/64 { use-eui-64 true; #EUI-64でIPv6アドレスを配布する range6 2001:aa:bb:cc::/64; #EUI-64で割り当てる為、プレフィックスは必ず "/64" を設定 option dhcp6.name-servers 2001:aa:bb:cc::a,2001:aa:bb:cc::b; option dhcp6.domain-search "local.domain.com"; option dhcp6.info-refresh-time 21600; option dhcp-renewal-time 3600; option dhcp-rebinding-time 7200; default-lease-time 10800; preferred-lifetime 10800; max-lease-time 43200; } }