ISC DHCP serverでIPアドレスのリース時に外部コマンドを実行する
ISC DHCP serverでDHCPサーバを運用するとき,IPアドレスをリースするタイミングで任意のコマンドを実行したいことがある.例えば,クライアントのMACアドレスとリースしたIPアドレスのペアをデータベースに入れておきたい,といった場合.
こういった場合,on commit
/on release
/on expiry
が使える.dhcpd.conf
に次のような設定を書いておくと,それぞれのタイミングで外部のコマンドが実行される.
on commit { execute("/path/to/script", "arg1", "arg2", "arg3"); } on release { execute("/path/to/script", "arg1", "arg2", "arg3"); } on expiry { execute("/path/to/script", "arg1", "arg2", "arg3"); }
各コマンドはそれぞれ次のタイミングで実行される:
on commit
- サーバがクライアントにIPアドレスをリースしたタイミング
on release
- クライアントがIPアドレスをリリースしたタイミング
on expiry
- サーバがクライアントにリースしたIPアドレスのリース期限が切れたタイミング
実行する外部コマンドにIPアドレス・MACアドレスを渡す
クライアントのMACアドレスおよびIPアドレスは,それぞれhardware
変数およびleased-address
変数に格納されている.ただしこれらの変数の値は整数で表現されているので,通常そのままでは扱い辛い.そこで文字列に変換した上で外部コマンドに渡す.
on commit { set clip = binary-to-ascii(10, 8, ".", leased-address); set clhw = concat ( suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,1,1))),2), ":", suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,2,1))),2), ":", suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,3,1))),2), ":", suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,4,1))),2), ":", suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,5,1))),2), ":", suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,6,1))),2) ); execute("/path/to/script", clip, clhw); }
たまにMACアドレスの取得方法として次のような方法を紹介している記事がある.この方法だと"02:01:23:0a:bc:de"が"2:1:23:a:bc:de"と変換されてしまう.
set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
外部コマンド側の実装
on commit
時にIPアドレスとMACアドレスのペアをRedisに突っ込み,on expiry
で対応するエントリを削除するスクリプトだと,下のような感じになる.
#!/usr/bin/env python """Manage bindings between an IP address and a MAC address""" import redis import sys import argparse kvs = redis.Redis(host="127.0.0.1", port=6379, db=1) def on_commit(ip_address, mac_address): """Insert a newly-leased IP address and its corresponding MAC address to the redis server""" kvs.set(ip_address, mac_address) return def on_expiry(ip_address): """Remove the expired IP address from the redis server""" kvs.delete(ip_address) return def main(): """Parse command-line arguments and call a corresponding function""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() subparser_commit = subparsers.add_parser("commit") subparser_commit.set_defaults( func=lambda command_args: on_commit(command_args.ip_address, command_args.mac_address)) subparser_commit.add_argument("ip_address") subparser_commit.add_argument("mac_address") subparser_expiry = subparsers.add_parser("expiry") subparser_expiry.set_defaults( func=lambda command_args: on_expiry(command_args.ip_address)) subparser_expiry.add_argument("ip_address") args = parser.parse_args() try: subcommand_func = args.func except AttributeError: parser.print_usage() sys.exit(2) subcommand_func(args) if __name__ == '__main__': main()
以前,この仕組みを使ってCaptive Portalのようなものを書いて運用していた.WebページにアクセスしてきたクライアントのIPアドレスからMACアドレスが引けるようになるので,アカウント(人)とMACアドレス(デバイス)の紐付けが簡単に実現できる.