![]() |
Web Development »
Web Services »
General
Advanced
License: The Code Project Open License (CPOL)
Dynamic DNS Web ServiceBy Paul ReederImplements a DNS updating service via XML Web Services. |
C++/CLI, C#, VC7.1.NET 1.1, Win2K, WinXP, Win2003, ASP.NET, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
This service allows public DHCP address monitoring by a service on remote clients. When the IP address changes, it is reported to a web service, and then depending on the options for the account, may send an administrative email about the change, and may update a DNS entry.
The solution is made up of multiple projects, as follows:
The database project creates a SQL server database with three tables:
It also registers the two extended stored procedures xp_AppendDNSHostEntry and xp_SendIPMonMailNotification from the IMPXP project in the [master] database.
This functionality is brought together by the t-SQL AFTER UPDATE trigger on the IPMon table. This script updates the Email and DNS tables when necessary and also calls the extended procedures to perform the external email and DNS work.
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
The IP project is a very rudimentary front end for the data from the web service. It is fairly self explanatory. I imagine that any implementation would use this as a general example and build its own custom interface.
The IPMSCfg project allows a simple user interface to change the DNS and email options on the database. It is dependent on the service being installed and the registry key \HKLM\Software\reeder.ws\IPMon intact.

The program retrieves the service GUID from the registry, then uses the web service to retrieve and store information. Since the web service uses an empty string to define options not set for email and DNS settings, these must be taken into account for the 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;
}
After the service has been configured, the program restarts the Windows service by using an instance of the System.ServiceProcess.ServiceController class.
//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();
}
The IP Monitor Client service monitors the IP addresses of the client computer, searching for the first available public IP address. When the IP address changes, or after an hour of dormancy, the client publishes the IP address to the web service.
To monitor the local computer's IP address list, the service makes heavy use of the Win32 IP Helper API. It uses two worker threads, one for updating on IP address changes, and one for updating via a timer.
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(); }
The actual check for a public IP address is fairly straightforward. You retrieve the IP address list, then check each one until a non-private address is found, as shown in the following snippet:
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;
The IMPXP project contains the extended stored procedures for email notification and DNS updating. Email, of course, could have been done using SQL Mail, but often, SQL Mail is not preferred because it forces all mail bearing applications from the SQL Service instance to operate from a single MAPI-based email service.
Probably the most painful thing in building an extended stored procedure for SQL Server is retrieving the parameters from the t-SQL EXEC command. This is no near as simple as it could be, and hopefully the next version of SQL Server (which supposedly supports embedded C# statements inside t-SQL scripts) will make this much easier. Here is an example of retrieving the email address from the EXEC command.
//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);
Definitely not a trivial thing. But once this is traversed, everything else is fairly straightforward. I should note that the email sending functionality is dependent on the MS SMTP Service and expects the drop directory to be at C:\Inetpub\mailroot\pickup. The location can be changed by changing the value for ROOT_DROP_DIRECTORY and recompiling.
For DNS functionality, the Win32 DNS API is used. This is dependent on the default DNS server accepting dynamic updates. Both secure and unsecure updates are attempted.
In order to allow a first time entry to succeed, an attempt to retrieve a host record is done, then the submission is dependent on the status of that retrieval.
//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);
The Store project contains the Web service which provides gating functionality between the database and the clients' Windows services. It consists of the following WebMethods:
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)
These are all fairly straightforward.
Implementing this isn't as simple as just deploying the executables (which is why the executables aren't included here). To implement without changes, you should do the following:
IPMonWS and then using the t-SQL scripts.
UID and Event) in HKLM\Software\reeder.ws\IPMon. Optionally, you can also include the config utility.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 28 Nov 2003 Editor: Smitha Vijayan |
Copyright 2003 by Paul Reeder Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |