Automating Windows (DNS) with Perl

取自 PerlChina.org - wiki

跳转到: 导航, 搜索

用 Perl 自动化 Windows DNS

翻 译:zerray 出 处:中国Perl协会 FPC(Foundation of Perlchina) 原 名:Automating Windows (DNS) with Perl 作 者:"Thomas Herchenroeder":http://www.perl.com/pub/au/Herchenroeder_Thomas 原 文:http://www.perl.com/pub/a/2005/03/24/perl_dns.html 发 表:March 24, 2005 Perlchina提醒您:请保护作者的著作权,维护作者劳动的结晶。

目录

[编辑] Driving Windows DNS Server

[编辑] 驱动 Windows DNS 服务器

If you happen to manage a DNS server running on Windows 2000 or Windows 2003 with more than just a couple of dozen resource records on it, you've probably already hit the limits of the MMC DNS plugin, the Windows administrative GUI for the DNS server implementation. Doing mass operations like creating 20 new records at once, moving a bunch of A records from one zone to another, or just searching for the next free IP in a reverse zone, can challenge your patience. To change the name part of an A record, you have to delete the entire record and re-create it from scratch using the new name. You've probably thought to yourself, "There MUST be another way to do this." There is!

如果你要管理一个运行在 Windows 2000 或 Windows 2003 带有超过几十个的资源记录( resource record )的 DNS 服务器,你可能已经碰到MMC DNS 插件,也就是Windows版本的 DNS 服务器的图形用户界面( GUI ),的极限了。每当做批量的操作,比如一次建立20个新的记录,移动一串A记录从一个区域到另一个,或仅在翻转区域搜索下一个可用的 IP ,都可能使你不胜其烦。要更改一个A记录的名字部分,你不得不删除整个记录再重建它为新的名字。你可能自己在想,“一定有别的什么方法来做这个。”有的!

Silently, almost shyly, behind the scenes and without the usual bells and whistles, Microsoft has arrived at the "power of the command shell":http://www.pragmaticprogrammer.com/ppbook/extracts/rule_list.html . For the DNS services[1], the command line utility dnscmd is available as part of the AdminPac for the server operating systems. dnscmd is a very solid command line utility, with lots of options and subcommands, that allows you to do almost every possible operation on your DNS server. These include starting and stopping the server, adding and deleting zones and resource records, and controlling a lot of its behavior.

默默的,几乎是害羞的,在幕后且没有铃声和哨声,微软推出了一个功能强大的命令行工具( "power of the command shell":http://www.pragmaticprogrammer.com/ppbook/extracts/rule_list.html )。对于 DNS 服务[1],命令行工具 dnscmd 是作为服务器操作系统的管理包( AdminPac )的一部分提供的。 dnscmd 是一个非常可靠的命令行工具,带有大量的选项和子命令,它允许你在你的 DNS 服务器上做几乎所有可能的操作。这包括了开启和停止服务器,增加和删除区域和资源记录,控制服务器的许多行为。

This article explores how to run dnscmd from Perl. In that respect, it is a classic "Perl-as-a-driver" script, invoking dnscmd with various options and working on its outputs.

这篇文章探讨了如何从 Perl 启动 dnscmd 。在这一意义上讲,它是一个经典的“驱动器 Perl ( Perl-as-a-driver )”脚本,用各种选项调用 dnscmd 并处理它的输出。

[编辑] DNSCMD

Invoke dnscmd /? to see a top-level list of available subcommands and type dnscmd /subcommand /? for more specific help for this subcommand. dnscmd /? shows that there is a subcommand RecordDelete, and dnscmd /recorddelete /? (case not significant) explains that you need a zone name (like "my.dom.ain"), a node name within this zone (like "host1"), a record type (like "A"), and the data part of the resource record to delete (like "10.20.40.5").

调用 dnscmd /? 来查看可用子命令的顶级列表,输入 dnscmd /subcommand /? 则得到对于子命令更详细的帮助。 dnscmd /? 显示有一个 RecordDelete 子命令, dnscmd /recorddelete /? (大小写不敏感)说明了你需要给出一个域名(像“my.dom.ain”),一个在域中的节点名(像“host1”),一个记录类型(像“A”),和将要删除的资源记录的数据部分(像“10.20.40.5”)。

The first argument to dnscmd, if you actually want to do something with it, is the name of the DNS server to use. A full working command looks something like:

dnscmd 的第一个参数,如果你确实想用它做点什么,是 DNS 服务器的名字。一个完整的可工作的命令像这样:

dnscmd dnssrv.my.dom.ain /RecordDelete my.dom.ain host1 A 10.20.40.5

This opens the very welcome opportunity to run dnscmd remotely—from your workstation, for example—which frees you from the need to log in to your DNS server.

这打开了一个非常好的远程运行 dnscmd 的机会——从你的工作站,例如——它使你摆脱了登陆到你的 DNS 服务器的麻烦。

[编辑] WDNS.PL (The Script)

[编辑] WDNS.PL (脚本)

The script in the current version only handles A and PTR records. There is no handling of CNAME records, for example. Within this limitation, it is also very A record-oriented: you can add or delete A records, change the IP or name of an A record, or move the A record to a different zone. It keeps PTR records in sync with these changes, creating or deleting a corresponding PTR record with its A record. This is mostly what you want.

目前版本的脚本只处理 A 和 PTR 记录。例如,没有 CNAME 记录的处理。在这个限制下,它也刚好是 A 记录定向的:你可以添加或删除 A 记录,修改 A 记录的 IP 或名字,或者移动一个 A 记录到另一个区域。它保持了 PTR 记录与这些修改,建立或删除一个与 A 记录对应的 PTR 记录的同步。这就是你想要的大部分了。

The most important thing to understand with this script is the format and the meaning of the input data it takes ("Show me your data ...")[2]. The format is simple, just:

要理解这个脚本最重要的是输入数据的格式和意义(“给我看你的数据……”)[2]。格式很简单,就这样:

            <name1>      <target1>
            <name2>      ...
            ...          ...

Separate name and target by whitespace. A name is a relative or fully-qualified domain name. A target can be one of these:

用空格分隔 nametarget 。 name 是一个相对或正式域名。 target 可以是下列的一种:

  • another domain name, meaning to rename to that name;
  • an IP, meaning to change to this IP;
  • nothing or undef, meaning to delete this name.
  • 另一个域名,代表更名为的名字;
  • 一个 IP ,代表改为的 IP ;
  • 空白或未定义,代表删掉这个名字。

This mirrors the basic functions of the script. To add some extra candy, the target parameter has two other possibilities which have proven very useful in my environment. A target can also be:

这反映了这个脚本的基本功能。要添加一些额外的东东, target 的参数有两种别的可能值在我的环境里被证明是很有用的。一个 target 也可以是:

  • a C net, given as a triple of IP buckets (like "10.20.40");
  • a net segment identifier ("v1", "v2", and so on, in my example).
  • 一个C类网络,以 IP 的三段形式给出(像“10.20.40”);
  • 一个网段标识符(“v1”,“v2”,等等,在我的例子里)。

In both cases, the script will give the name a free IP (if possible) from either the C net or the net segment specified by its identifier. I'll return to this idea soon.

两种情况中,脚本都会分给名字一个空闲的 IP (如果可能的话),来自于C类网络或者由标识符指定的网段。我一会儿会再回头说这个。

To pull all of the various possibilities together, here is a list of sample input lines, each representing one of the mentioned possibilities for the target:

为了展示所有的可能值,这里是一个输入样例的列表,每一行表示所提到的 target 可能值中的一种:

          pcthe                10.20.90.53     -- 新 ip
          pcthe.my.dom.ain     10.20.90.53     -- 正式的新 ip
          pcthe                10.20.90        -- 在范围中搜索空闲 ip
          pcthe                @v8             -- 在网段 v8 中搜索空闲 ip
          pcthe                pcthe2          -- 重命名主机
          pcthe                pcthe.oth.dom   -- 正式重命名主机
          pcthe                                -- 删除主机 (用 -r 选项)

Pass this data to the script through either an input file or STDIN[3].

通过输入文件或者标准输入[3]传递这些数据给脚本。

  • init()*

Now to the code. At startup, wdns.pl pulls in the list of primary zones from the given DNS server, both forward (names as lookup keys) and reverse (IPs as lookup keys) zones. This is handy, because it will use this list again and again.

现在来讲代码。一开始, wdns.pl 引入了所给 DNS 服务器的基本区( primary zones )的列表,包括正向(名字作为查询关键字)和逆向( IP 作为查询关键字)区域。这样很便利,因为我们会一次又一次的用到这个列表。

  • mv_ip()*

The main worker routine is the sub mv_ip. (Don't think too much about the name; it's from the time when the only function of the script was to change the IP of a given name). For any given name/target pair, it does the following: First it tries to find a FQDN for the name. If it finds a host for the given name, it uses the FQDN as a basis to construct the name part of the targeted record. If it cannot find a name, it assumes that it should create an entirely new record. If the options permit (-c), it constructs one.

主要的工作例程是子程序 mv_ip 。(不要太在意名字,它是从脚本只有修改给定名字的 IP 这一功能时来的)。对于任一 name/target ,它做如下工作:首先它试着查找名字的正式域名( FQDN )。如果找到了一个有给定名字的主机,它使用正式域名作为建造目标记录的名字部分的基础。如果找不到名字,它假定它应该创建一个全新的记录。如果选项允许了(-c),它建造一个。

Then it inspects the target. Depending on its type, the program prepares to assign a new IP to the name, rename an existing A record while retaining the IP, search for a free IP in a certain range, or just delete existing records. When everything settles, the actual changes take place, using dnscmd to delete and add A and PTR records as appropriate. (There is no UpdateRecord function in dnscmd, so updating is in fact a combination of delete and create).

接下来它检查 target 。依据它的类型,程序准备赋值一个新 IP 给名字,重命名一个存在的 A 记录并保留 IP ,在确定范围内查找一个空闲 IP ,或者仅删除存在的记录。当所有事都解决了,进行实际的改变,使用 dnscmd 适当的删除和添加 A 和 PTR 记录。(在 dnscmd 中没有 UpdateRecord 功能,所以更新实际上是删除和创建的组合)。

That's it! The rest of the code is lower-level functions that help to achieve this.

这就好了!剩下的代码是帮助实现这些的低级功能。

  • create_* and delete_**

The four subs create_A, create_PTR, delete_A, and delete_PTR are wrapper functions around the respective invocations of dnscmd. An additional issue of interest is that Windows DNS will delete a PTR record once you delete the corresponding A record, so you don't have to do so explicitly.

四个子程序 create_Acreate_PTRdelete_A,和 delete_PTR 是各自对应的 dnscmd 调用的包裹函数( wrapper functions )。另一个有趣的问题是 Windows DNS 会在你删除 A 记录的时候删除对应的 PTR 记录,所以你不必要显式的这么做。

  • get_rev_zone() and get_fwd_zone()*

One of the major issues when manipulating DNS resource records is picking the right zone to do the change in. If you have just one forward and one reverse zone, this is simple. However, if you are maintaining a lot of zones with domains and nested subdomains, while other subdomains of the same parent have their own zones, this might be tedious.

处理 DNS 资源记录时的一个主要问题是找出做修改的正确区域。如果你只有一个正向和一个逆向区域,这是简单的。但是,如果你要维护许多带有域和内嵌的子域的区域,而具有相通父亲的其它子域有着它们各自的区域,这个过程就有些乏味并且易出错了。

wdns.pl can offload this task for you. The subs get_rev_zone and get_fwd_zone use the initially retrieved list of primary zones from your server. They take an IP or a fully qualified domain name respectively, and split it into the node part and the zone part. So the IP 10.20.40.5 might split into 10.20.40 and 5 (if the proper zone of this IP is 40.20.10.in-addr.arpa) or 10 and 20.40.5 (if 10.in-addr.arpa happens to be the enclosing zone), depending on your zone settings. The same applies for domain names. Other routines use this information to add or delete resource records in their appropriate zones.

wdns.pl 可为你承担这个任务。子程序 get_rev_zoneget_fwd_zone 使用来自你的服务器的基本区初始恢复列表。它们分别接受一个 IP 或 一个 正式域名,分解成节点部分和区域部分。所以 IP 地址 10.20.40.5 会被分解成 10.20.40 和 5 (如果这个 IP 本来的区域是 40.20.10.in-addr.arpa )或者是 10 和 20.40.5 (如果 10.in_addr.arpa 刚好是封闭区域),取决于你的区域设置。对域名也提供同样的功能。其它的例程使用这一信息在它们适当的区域里添加或删除资源记录。

[编辑] IP Lookup Functions

[编辑] IP 查找功能

There is a set of subs I called "IP lookup functions". They all help to find a free IP in an appropriate range. Depending on the target specification, they will search a certain C net or a whole net segment of unused address. This searching breaks down to finding the appropriate zone, the appropriate node ("subdomain"), and then listing the already existing leaf nodes in this range. Once it has the list of used nodes, it starts scanning for gaps or unused nodes off the end of the list.

有一组被我称为“ IP 查找功能”的子程序。它们都能帮你在适当的范围里找出一个空闲的 IP 。依据指定的目标,它们会搜索某一个 C 类网或包含未使用地址的整个网段。搜索会停下来去寻找适当的区域,适当的节点(“子域”),然后列出在这一范围内已经存在的叶节点。一旦有了已用节点的列表,它会从列表末尾开始扫描缺口或未使用的节点

An additional feature of these routines is that they honor certain reservations in the ranges, either through fixed directives ("leave the first 50 addresses free at the beginning of each net segment") or through inspecting dedicated TXT records on the DNS server that contain the RESERVED keyword. (The actual format of these records is RESERVED:<range-spec>:<free text>, where range-spec is a colon-separated list of IPs or IP ranges. An example is RESERVED:1,3,5,10-20,34:IPs reserved for the VPN switches). This helps avoid re-using reserved IPs accidentally through the automatic script, and also helps avoid messing things up when time is short.

这些例程的另一特色是它们在范围内做一些预留,通过固定的指令(“在每一个网段的开始留下前50个地址空闲”)或者是通过检查 DNS 服务器上包含关键字 RESERVED 的专用 TXT 记录。(这些记录的实际格式是 RESERVED:<范围说明>:<任意文本>,其中范围说明是一个由冒号分隔的 IP 或 IP 范围列表。例如 RESERVED:1,3,5,10-20,34:IPs reserved for the VPN switches)。这会帮助避免通过自动脚本引起的意外重复使用保留 IP ,也会帮助避免当时间很短的时候把东西弄得一团糟。

In the case of these TXT records, I used dnscmd to retrieve them, not nslookup, which would have been equally possible.

对于这些 TXT 记录,我是用 dnscmd 来恢复它们,而不是 nslookup ,也许用它也是等价的吧。

[编辑] A Word About the Net Segments

[编辑] 关于网段

If your IPs reside in a segmented network, which is likely to be the case for most sites, make sure that your hosts have addresses for the segments to which they attach. For this script I have chosen a poor man's approach to represent the segments just by the list of their respective C nets in the script itself (see the hash %netsegs in the "Config section"). There might be a more clever way to do this. If you are going to run the script in your environment, edit this hash to reflect your network topology.

如果你的 IP 处于一个分段的网络,大多数站点都是这种情况,确认你的主机拥有其附属于的段的地址。在这个脚本中我选择了一个低效的方法来表示段,仅用脚本自身中的它们各自的 C 类网的列表(查看哈希表 %netsegs 中的“ Config section ”)。可能会有一个更聪明的方法来做这件事。如果你要在你的环境中运行这个脚本,编辑这个哈希表来反映你的网络拓扑结构。

The dns_lookup sub looks up the current DNS entries. It runs the extern command nslookup and parses its output. If you need more sophisticated DNS lookups (and nslookup's options just won't do), you might want to resort to dig (which has a Windows version) or "Net::DNS":http://search.cpan.org/dist/Net-DNS (which runs on Windows in any case). This simple way of doing it was just enough for my needs.

dns_lookup 子程序查找当前的 DNS 入口。它运行外部命令 nslookup 并分析它的输出。如果你需要更高级的 DNS 查找( nslookup 的选项无法做到的),你也许想要求助于 dig (它有 Windows 的版本)或者 "Net::DNS":http://search.cpan.org/dist/Net-DNS (它在任何情况下都是运行在 Windows 上的)。这一简单的方法已经可以满足我们的需要了。

[编辑] Footnotes

  1. In the Windows world, server processes are usually referred to as "services"; I tend to mix this term with "server" every now and then.
  1. "Show me your functions, and I will be confused. Show me your data, and your functions will be obvious", to re-coin a famous quote from Frederick Brooks' _The Mythical Man-month_.
  1. Depending on your Windows command shell, you might have to tinker a bit to get the STDIN input to work as desired. Cygwin's bash works like a breeze and takes Ctrl-Z<RET> as the EOF sequence.

[编辑] 脚注

  1. 在 Windows 世界中,服务器进程通常被称为“服务”;我常常倾向于将这一术语与“服务器”通用。
  1. “只给我看你的功能,我会迷惑。

当我看到你的数据,你的功能就很明显了”,改编摘自 Frederick Brooks 的 《The Mythical Man-month》中的著名引言。

  1. 依据你的 Windows 的命令行程序,你可能不得不修补一点来使得标准输入照你所希望的方式工作。在 Cygwin 上的 bash 测试以上脚本,运行非常顺利,接收 Ctrl-Z 作为文件结束符。
个人工具