Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic DNS Web Service

0.00/5 (No votes)
28 Nov 2003 3  
Implements a DNS updating service via XML Web Services.

Introduction

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.

Project List

The solution is made up of multiple projects, as follows:

  • Database (t-SQL): Creates the SQL database to store the data
  • IP (C#, ASP.NET): A simple web site to glimpse data from the web service
  • IPMSCfg (C#): Allows setting of email and DNS updating options from the computer being monitored.
  • IPMCSvc (MC++): The Windows Service which monitors the IP Address of the local computer.
  • IPMXP (C++, Win32): Contains SQL Server extended procedures to drop emails and update a dynamic DNS server.
  • Store (C#, ASP.NET): The web service itself.

Database Project

The database project creates a SQL server database with three tables:

  • IPMon - keeps information about the client computers
  • Email - keeps information about email alerts
  • DNS - keeps information about DNS updating

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

IP Project

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.

IPMSCfg Project

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.

Config screenshot

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();
}

IPMCSvc Project

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;

IMPXP Project

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);

Store Project

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

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:

  1. Build the extended procedures and place them in the SQL Server's Bin directory.
  2. Build the database, by first adding a user account name IPMonWS and then using the t-SQL scripts.
  3. Deploy the web service to your web server. Note: You have to supply the ConnectionString to the SQL Server using the [IPMonWS] account and the [IPMon] database.
  4. Change all web references to your web service.
  5. Deploy the Windows Service (IPMCSvc) and fill the 2 registry values (UID and Event) in HKLM\Software\reeder.ws\IPMon. Optionally, you can also include the config utility.
  6. Optionally, you can then deploy the sample Web UI to your web server.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here