动态 DNS Web 服务






4.17/5 (9投票s)
通过 XML Web 服务实现 DNS 更新服务。
引言
此服务允许远程客户端上的服务监控公共 DHCP 地址。当 IP 地址更改时,它会被报告给 Web 服务,然后根据账户的选项,可能会发送关于更改的管理电子邮件,并且可能会更新 DNS 条目。
项目列表
该解决方案包含多个项目,如下所示:
- 数据库 (t-SQL): 创建用于存储数据的 SQL 数据库
- IP (C#, ASP.NET): 一个简单的网站,用于查看 Web 服务的数据
- IPMSCfg (C#): 允许从被监控的计算机设置电子邮件和 DNS 更新选项。
- IPMCSvc (MC++): 监控本地计算机 IP 地址的 Windows 服务。
- IPMXP (C++, Win32): 包含 SQL Server 扩展存储过程,用于发送电子邮件和更新动态 DNS 服务器。
- Store (C#, ASP.NET): Web 服务本身。
数据库项目
数据库项目创建了一个包含三个表的 SQL Server 数据库。
- IPMon - 存储有关客户端计算机的信息
- Email - 存储有关电子邮件警报的信息
- DNS - 存储有关 DNS 更新的信息
它还在 [master] 数据库中注册了来自 IMPXP 项目的两个扩展存储过程 xp_AppendDNSHostEntry
和 xp_SendIPMonMailNotification
。
此功能由 IPMon 表上的 t-SQL AFTER UPDATE
触发器整合。此脚本在必要时更新 Email 和 DNS 表,并调用扩展存储过程来执行外部电子邮件和 DNS 工作。
CREATE TRIGGER trigSendMail ON [dbo].[IPMon] AFTER UPDATE
AS
IF ((COLUMNS_UPDATED() & 8 = 8) AND (@@ROWCOUNT = 1))
--IP Address updated
BEGIN
--get ip address
DECLARE @UID uniqueidentifier
DECLARE @NewIPAddress varchar(15)
DECLARE ip cursor local for
SELECT UID, IPAddress FROM inserted
OPEN ip
FETCH NEXT FROM ip INTO @UID, @NewIPAddress
--start of email check
DECLARE @EmailAddress varchar(50)
DECLARE @LastUpdateIP varchar(15)
DECLARE email cursor local keyset for
SELECT TOP 1 EmailAddress, LastUpdateIP FROM Email WHERE UID = @UID
for read only
OPEN email
FETCH NEXT FROM email INTO @EmailAddress, @LastUpdateIP
IF (@@FETCH_STATUS = 0)
BEGIN
--entry exists
IF (@LastUpdateIP <> @NewIPAddress)
BEGIN
--IP address has changed
UPDATE Email
SET LastUpdateIP = @NewIPAddress
WHERE UID = @UID
SET @LastUpdateIP = @NewIPAddress;
EXEC master.dbo.xp_SendIPMonMailNotification
@emailaddress = @EmailAddress,
@ipaddress = @NewIPAddress
END
END
CLOSE email
DEALLOCATE email
--start dns check
DECLARE @HostName varchar(50)
DECLARE dnsc cursor local keyset for
SELECT TOP 1 HostName, LastUpdateIP FROM DNS WHERE UID = @UID
for read only
OPEN dnsc
FETCH NEXT FROM dnsc INTO @HostName, @LastUpdateIP
IF (@@FETCH_STATUS = 0)
BEGIN
--dns entry exists
IF (@LastUpdateIP <> @NewIPAddress)
BEGIN
--IP Address has changed
UPDATE DNS
SET LastUpdateIP = @NewIPAddress
WHERE UID = @UID
SET @LastUpdateIP = @NewIPAddress;
EXEC master.dbo.xp_AppendDNSHostEntry
@hostname = @HostName,
@ipaddress = @NewIPAddress
END
END
CLOSE dnsc
DEALLOCATE dnsc
CLOSE ip
DEALLOCATE ip
END
IP 项目
IP 项目是 Web 服务数据的非常基础的前端。它非常自明。我设想任何实现都会将此用作一般示例并构建自己的自定义界面。
IPMSCfg 项目
IPMSCfg 项目提供了一个简单的用户界面来更改数据库上的 DNS 和电子邮件选项。它依赖于服务的安装和注册表项 \HKLM\Software\reeder.ws\IPMon
的完整性。
程序从注册表中检索服务 GUID,然后使用 Web 服务来检索和存储信息。由于 Web 服务使用空字符串来定义未设置的电子邮件和 DNS 设置选项,因此在 GUI 中必须将这些考虑在内。
private reeder.IPMonWebService ws = new IPMCSCfg.reeder.IPMonWebService();
//retrieve UID from registry
Microsoft.Win32.RegistryKey reg =
Microsoft.Win32.Registry.LocalMachine.OpenSubKey
("Software").OpenSubKey("reeder.ws").OpenSubKey("IPMon");
this.uid = (System.Guid)
(System.ComponentModel.TypeDescriptor.GetConverter
(this.uid).ConvertFrom(reg.GetValue("UID")));
//try/catch block for web service
{
//get email settings
this.Email = ws.GetEmailAddress(this.uid);
if (this.Email == string.Empty)
this.SendEmail = false;
else
this.SendEmail = true;
//get dns settings
this.DNS = ws.GetDNSInfo(this.uid);
if (this.DNS == string.Empty)
this.SendDNS = false;
else
this.SendDNS = true;
}
catch{
System.Windows.Forms.MessageBox.Show(this,
"Error retrieving information from the IPMon Web Service." +
" Please ensure that you have an active Internet" +
" connection, then restart this program",
"IPMon Web Service Error",
System.Windows.Forms.MessageBoxButtons.OK);
this.Close();
return;
}
//fill data
if (this.SendEmail){
this.ckEmail.Checked = true;
this.txtEmail.Enabled = true;
this.txtEmail.Text = this.Email;
}
if (this.SendDNS){
this.ckDNS.Checked = true;
this.txtDNS.Enabled = true;
this.txtDNS.Text = this.DNS;
}
在服务配置完成后,程序将使用 System.ServiceProcess.ServiceController
类的实例重新启动 Windows 服务。
//service functions
System.ServiceProcess.ServiceController sc =
new System.ServiceProcess.ServiceController("IP Monitor");
if (changed && (sc.Status ==
System.ServiceProcess.ServiceControllerStatus.Running)){
//restart to pick up changes
sc.Stop();
sc.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped);
sc.Start();
sc.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Running);
}
if (sc.Status == System.ServiceProcess.ServiceControllerStatus.Stopped){
if (System.Windows.Forms.MessageBox.Show(this,
"The IP Monitor service is not currently running." +
" Would you like to start it now?", "Service Start",
System.Windows.Forms.MessageBoxButtons.YesNo)
== System.Windows.Forms.DialogResult.Yes)
sc.Start();
}
IPMCSvc 项目
IP Monitor Client 服务监控客户端计算机的 IP 地址,搜索第一个可用的公共 IP 地址。当 IP 地址更改时,或者在闲置一小时后,客户端会将 IP 地址发布到 Web 服务。
为了监控本地计算机的 IP 地址列表,该服务大量使用了 Win32 IP Helper API。它使用两个工作线程,一个用于在 IP 地址更改时更新,另一个用于通过计时器更新。
void IPMCSvc::IPMCSvcWinService::IPChangeThreadStart(){ while (true){ DWORD ret = NotifyAddrChange(NULL, NULL); if (ret == NO_ERROR){ if (this->EventLevel & 0x04) this->EventLog->WriteEntry("Win32 IP Address changed trigger hit", System::Diagnostics::EventLogEntryType::Information, EVENT_IP_W32_CHANGE_EVENT); this->CheckIPAddress(); } } } void IPMCSvc::IPMCSvcWinService::IPTimerThreadStart(){ while (true){ System::Threading::Thread::Sleep(TIMER_SLEEP_SECONDS); if (this->EventLevel & 0x04) this->EventLog->WriteEntry("Timed check triggered", System::Diagnostics::EventLogEntryType::Information, EVENT_IP_TIMER); this->CheckIPAddress(); }
检查公共 IP 地址的过程相当直接。您检索 IP 地址列表,然后逐个检查,直到找到一个非私有地址,如下面的代码片段所示。
static DWORD RegIPAddr = 0; //keeps the currently registered IP Address //size of the buffer for the IP Address table ULONG ipasize = IPADDRESS_BUFFER_SIZE; //get win32 IP Address information PMIB_IPADDRTABLE ipa = reinterpret_cast<PMIB_IPADDRTABLE>(malloc(IPADDRESS_BUFFER_SIZE)); GetIpAddrTable(ipa, &ipasize, FALSE); //find suitable address bool AddressFound = false; for (DWORD i = 0; i < ipa->dwNumEntries; ++i){ bool usable = true; unsigned char* ipb = reinterpret_cast<unsigned char*>(&(ipa->table[i].dwAddr)); if ((*ipb == 127) && (!ipb[1]) && (!ipb[2]) && (ipb[3] == 1)) usable = false; if (*ipb == 0x0A) usable = false; if ((*ipb == 172) && ((ipb[1] & 0xF0) == 0x10)) usable = false; if ((*ipb == 192) && (ipb[1] == 168)) usable = false;
IMPXP 项目
IMPXP 项目包含电子邮件通知和 DNS 更新的扩展存储过程。当然,电子邮件也可以使用 SQL Mail 完成,但通常不推荐使用 SQL Mail,因为它强制来自 SQL Service 实例的所有承载电子邮件的应用程序都必须从单一的 MAPI 电子邮件服务运行。
为 SQL Server 构建扩展存储过程最痛苦的事情可能是从 t-SQL EXEC
命令中检索参数。这远非简单,希望下一版 SQL Server(据说支持在 t-SQL 脚本中嵌入 C# 语句)会使其更容易。下面是一个从 EXEC
命令中检索电子邮件地址的示例。
//check first parameter if (srv_paraminfo(proc, 1, &type, &mlen, &clen, NULL, &fNull) == FAIL) return (FAIL); if ((type != SRVCHAR) && (type != SRVVARCHAR) && (type != SRVBIGVARCHAR)) return (FAIL); emailaddress = reinterpret_cast<BYTE*>(malloc(clen + 1)); emailaddress[clen] = '\0'; srv_paraminfo(proc, 1, &type, &mlen, &clen, emailaddress, &fNull);
这绝对不是一件容易的事。但一旦解决了这个问题,其余的一切都相当直接。我应该注意的是,电子邮件发送功能依赖于 MS SMTP 服务,并期望 drop 目录位于 C:\Inetpub\mailroot\pickup。可以通过更改 ROOT_DROP_DIRECTORY
的值并重新编译来更改位置。
对于 DNS 功能,使用了 Win32 DNS API。这取决于默认 DNS 服务器是否接受动态更新。将尝试安全的和不安全的更新。
为了允许第一次条目成功,会尝试检索主机记录,然后提交取决于该检索状态。
//get old dns recordset PDNS_RECORD dnsoldlist; DNS_STATUS oldlookupret = DnsQuery_A(reinterpret_cast<PSTR>(hostname), DNS_TYPE_A, DNS_QUERY_BYPASS_CACHE | DNS_QUERY_TREAT_AS_FQDN | DNS_QUERY_DONT_RESET_TTL_VALUES, NULL, &dnsoldlist, NULL); //build new dns record DNS_RECORD rec; rec.pNext = NULL; rec.pName = reinterpret_cast<PSTR>(hostname); rec.wType = DNS_TYPE_A; rec.wDataLength = sizeof(DNS_A_DATA); rec.Flags.DW = 0; rec.dwTtl = 480; ec.dwReserved = 0; rec.Data.A.IpAddress = inet_addr(reinterpret_cast<char*>(ipaddress)); //post dns record DNS_STATUS dnsret; if (!oldlookupret){ dnsret = DnsModifyRecordsInSet_A(&rec, dnsoldlist, DNS_UPDATE_SECURITY_ON, NULL, NULL, NULL); DnsRecordListFree(dnsoldlist, DnsFreeFlat); } else dnsret = DnsModifyRecordsInSet_A(&rec, NULL, DNS_UPDATE_SECURITY_ON, NULL, NULL, NULL);
Store 项目
Store 项目包含 Web 服务,该服务提供数据库和客户端 Windows 服务之间的网关功能。它包含以下 WebMethod
s。
bool CheckUserName(string UserName)
System.Guid CreateNewUser(string UserName, string Password)
bool GetCurrentInfo(string UserName, string Password,
out string IPAddress, out System.DateTime UpdateTime)
bool StoreIPAddress(System.Guid Id, string IPAddress)
bool GetGUID(string UserName, string Password, out string Uid)
bool StoreEmailInfo(System.Guid UID, string EmailAddress)
bool DropEmailInfo(System.Guid UID)
string GetEmailAddress(System.Guid UID)
bool StoreDNSInfo(System.Guid UID, string HostName)
bool DropDNSInfo(System.Guid UID)
string GetDNSInfo(System.Guid UID)
这些都相当直接。
实施
实现这一点并不像仅仅部署可执行文件那样简单(这就是为什么没有包含可执行文件)。要进行无需更改的实现,您应该执行以下操作:
- 构建扩展存储过程并将它们放在 SQL Server 的 Bin 目录中。
- 构建数据库,首先添加一个名为
IPMonWS
的用户帐户,然后使用 t-SQL 脚本。 - 将 Web 服务部署到您的 Web 服务器。注意:您必须使用 [IPMonWS] 帐户和 [IPMon] 数据库提供 SQL Server 的 ConnectionString。
- 更改所有指向您 Web 服务的 Web 引用。
- 部署 Windows 服务 (IPMCSvc) 并在
HKLM\Software\reeder.ws\IPMon
中填写 2 个注册表值 (UID
和Event
)。可选地,您还可以包含配置实用程序。 - 可选地,然后您可以将示例 Web UI 部署到您的 Web 服务器。