2016-02-03

ActiveDirectory DDNS + NetworkManager

現代の開発者にとって Linux 仮想マシンは必要不可欠なものだが、IP アドレスをどうするかはいつも悩ましい問題だ。例えば下記のような状況は多いはず。

  • 社内は ActiveDirectory DNS / DHCP 環境。
  • 勝手に固定 IP アドレスを使えない。
  • 社内ネットワークから仮想マシンを参照させたい。

このような場合は DHCP を使うことになると思うが、暫く起動しないと IP アドレスが変わって接続する際に一手間かかって面倒くさい。個人的にはこの様な場合、Samba (の nmbd)を使っている。サービスを起動しておくだけで Windows PC からホスト名(NetBIOS 名)でアクセスできるので、とても便利。

しかし最近、ActiveDirectory のドメインに参加せずとも、DHCP で DNS レコードを登録できることを知った。少なくとも ActiveDirectory の DNS / DHCP にはそういう機能があり、そして私の社内ではそれが有効になっている。そうすると後は DHCP クライアント側の話。RHEL では、次の設定でそれができる。(詳細は man dhclient.conf)

RHEL6: /etc/dhcp/dhclient.conf:

send fqdn.fqdn "myhost.example.local.";
send fqdn.server-update off;

RHEL5 ではファイルの場所が /etc/dhclient.conf となる。RHEL4 以前は知らない。

ネット上には、同じく dhclient.conf の host-name や ifcfg-* の DHCP_HOSTNAME でできるよ、みたいな情報も見つかる。しかし BIND ではそれで動くのかも知れない(未検証)が、社内の ActiveDirectory では動かなかった。

なお、dhclient.conf はデバイス(インターフェイス)毎に設定が可能で、その場合はグローバルな dhclient.conf は無視されるので注意。この辺はソースコードを見た方が理解が早い。

RHEL5.11: /etc/sysconfig/network-scripts/ifup-eth:

    # allow users to use generic '/etc/dhclient.conf' (as documented in manpage!)
    # if per-device file doesn't exist or is empty
    if [ -s /etc/dhclient-${DEVICE}.conf ]; then
       DHCLIENTCONF="-cf /etc/dhclient-${DEVICE}.conf";
    else
       DHCLIENTCONF='';
    fi;

RHEL6.7: /etc/sysconfig/network-scripts/network-functions:

generate_config_file_name () {
        local ver=$1
        if [ -s /etc/dhcp/dhclient$ver-${DEVICE}.conf ]; then
                DHCLIENTCONF="-cf /etc/dhcp/dhclient$ver-${DEVICE}.conf";
        elif [ -s /etc/dhclient$ver-${DEVICE}.conf ]; then
                DHCLIENTCONF="-cf /etc/dhclient$ver-${DEVICE}.conf";
        else
                DHCLIENTCONF='';
        fi
}

例えば私の環境では、RHEL5.11 のインストール直後に /etc/dhclient-eth0.conf が存在したので、この場合は /etc/dhclient.conf に設定を書いても無視される。

以上で、私の環境でも DNS にホスト名を登録できた。ただ個人的には、Windows PC からアクセスするだけなら Samba nmbd の方を勧めたい。わざわざ DNS レコードをゴニョゴニョせずに済むなら、それに越したことはない。

と、これで話が終われば幸せだった・・・。というか、こうして本エントリを書く必要すらなかった。残念ながら、以上は RHEL6 までの話。RHEL7 では話が変わる。:-(

周知の通り、RHEL7 では NetworkManager が幅を利かせていて、NetworkManager はシェルスクリプトで書かれた従来処理は使用せずに、自前で処理を行う。(ただし従来処理自体は存在していて、それらは NetworkManager が無効の時に使用される)

まず、dhclient.conf が未設定の状態でどう動くのか確認してみる。

[rhel7]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.2 (Maipo)

[rhel7]# rpm -q NetworkManager
NetworkManager-1.0.6-27.el7.x86_64

[rhel7]# cat /etc/dhcp/dhclient.conf
cat: /etc/dhcp/dhclient.conf: No such file or directory

このときデバイスの dhclient 設定は、デバイス起動時に下記の場所に生成される。

[rhel7]# cat /var/lib/NetworkManager/dhclient-enp0s17.conf
# NetworkManager で作成されています

send host-name "rhel7"; # added by NetworkManager

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;
option wpad code 252 = string;

also request rfc3442-classless-static-routes;
also request ms-classless-static-routes;
also request static-routes;
also request wpad;
also request ntp-servers;

dhclient.conf を作ってデバイスを再起動すると、上記が再生成されるが・・・、

[rhel7]# cat /etc/dhcp/dhclient.conf
send fqdn.fqdn "rhel7.example.local.";
send fqdn.server-update off;

[rhel7]# systemctl restart network

[rhel7]# cat /var/lib/NetworkManager/dhclient-enp0s17.conf
# NetworkManager で作成されています
# /etc/dhcp/dhclient.conf からマージされています

send fqdn.server-update off;
send host-name "rhel7"; # added by NetworkManager

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;
option wpad code 252 = string;

also request rfc3442-classless-static-routes;
also request ms-classless-static-routes;
also request static-routes;
also request wpad;
also request ntp-servers;

fqdn.fqdn がマージされていない!

ソースコードを確認してみる。

#define HOSTNAME4_TAG    "send host-name"
#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager"

#define FQDN_TAG    "send fqdn.fqdn"
#define FQDN_FORMAT FQDN_TAG " \"%s\"; # added by NetworkManager"

/* ...snip... */

char *
nm_dhcp_dhclient_create_config (const char *interface,
                                gboolean is_ip6,
                                GBytes *client_id,
                                const char *anycast_addr,
                                const char *hostname,
                                const char *orig_path,
                                const char *orig_contents,
                                GBytes **out_new_client_id)
{
	/* ...snip... */

			/* Override config file hostname and use one from the connection */
			if (hostname) {
				if (strncmp (p, HOSTNAME4_TAG, strlen (HOSTNAME4_TAG)) == 0)
					continue;
				if (strncmp (p, FQDN_TAG, strlen (FQDN_TAG)) == 0)
					continue;
			}

何故か fqdn.fqdn をスキップしている。かといってfqdn.fqdn が全て抹殺される訳でもなくて、

static void
add_hostname4 (GString *str, const char *format, const char *hostname)
{
	char *plain_hostname, *dot;

	if (hostname) {
		plain_hostname = g_strdup (hostname);
		dot = strchr (plain_hostname, '.');
		/* get rid of the domain */
		if (dot)
			*dot = '\0';

		g_string_append_printf (str, format, plain_hostname);
		g_free (plain_hostname);
	}
}

/* ...snip... */

static void
add_hostname6 (GString *str, const char *hostname)
{
	/* dhclient only supports the fqdn.fqdn for DHCPv6 and requires a fully-
	 * qualified name for this option, so we must require one here too.
	 */
	if (hostname && strchr (hostname, '.')) {
		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
		g_string_append (str,
		                 "send fqdn.encoded on;\n"
		                 "send fqdn.server-update on;\n");
		g_string_append_c (str, '\n');
	}
}

何故か IPv6 の方では fqdn.fqdn を設定している。IPv6 の動作は未確認だが、これだと IPv4 で fqdn.fqdn を送る術がないように見える。

もう NetworkManager ってほんと ks だわ、と思っていたら、

どうやら NetworkManager の設定項目に ipv4.dhcp-fqdn が増える模様。果たしてこれは RHEL7.3 に入ってくれるのだろうか。


2018-09-25 追記

あれから 2 年半、現段階での私の結論を記しておく。

まず、前述の修正は RHEL7.3 に入った。しかし私の環境では、全く状況は変わらなかった。これ以上はパケットキャプチャとか更なるソースコード解読とかが必要になるし、面倒いのでずっと放置していた。

その後、今度は dhclient のフックを使う方法を知った。

これは dhclient.conf に FQDN を記述するよりスマートな方法に思える。FQDN は hostname コマンドから引っ張ってこれるので、ハードコードが不要になるからだ。

しかし、これも動かなかった。orz

NetworkManager は dhclient のフックを呼ばないらしい。あれもダメ、これもダメ、一体どうしろというのか。NetworkManager 的には、「dispatcher を使え」とのこと。

/etc/NetworkManager/dispatcher.d/90-nsupdate:

#!/bin/bash

[ -n "$DHCP4_IP_ADDRESS" ] && [ "$HOSTNAME" != "${HOSTNAME/./}" ] \
    || exit 0

nsupdate <<EOF
update delete $HOSTNAME a
update add $HOSTNAME 3600 a $DHCP4_IP_ADDRESS
send
EOF

/etc/NetworkManager/dispatcher.d/pre-down.d/90-nsupdate:

#!/bin/bash

[ "$HOSTNAME" != "${HOSTNAME/./}" ] \
    || exit 0

nsupdate <<EOF
update delete $HOSTNAME a
send
EOF

後者の pre-down 処理は、対称性の観点(up 時に登録した名前を down 時に削除する)から作ったものの、実はあってもなくても同じ。何故なら、pre-down 処理はサーバーシャットダウン時に呼ばれない から。でもネットワーク再起動(systemctl restart network)では呼ばれる。これが仕様なのかバグなのかは分からないが、ほんとこれを作った奴はタヒんでいいよ。

あと、dispatcher 内で利用できる環境変数(例: DHCP4_IP_ADDRESS)は action (例: up, pre-down)毎に異なるのだが、それらは man を見てもいまいち明確でない。なので dispatcher を自作する場合は、まず set コマンドの出力をロギングする dispatcher を作り、利用可能な環境変数を確認してみることをお勧めする。

結論: やっぱり NetworkManager は ks。