Click here to Skip to main content
15,880,608 members
Articles / Web Development / ASP.NET
Article

Dynamic DNS Web Service

Rate me:
Please Sign up or sign in to vote.
4.17/5 (9 votes)
28 Nov 2003CPOL4 min read 110.2K   2.3K   57   7
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.

SQL
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.

C#
private reeder.IPMonWebService ws = new IPMCSCfg.reeder.IPMonWebService();
C#
//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.

C#
//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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAny chance for .NET 2.0 and VB instead of C# Pin
tmacpherson7-Jun-07 3:17
tmacpherson7-Jun-07 3:17 
GeneralRe: Any chance for .NET 2.0 and VB instead of C# Pin
Paul Reeder7-Jun-07 19:20
Paul Reeder7-Jun-07 19:20 
GeneralRe: Any chance for .NET 2.0 and VB instead of C# Pin
Paul Reeder7-Jun-07 21:23
Paul Reeder7-Jun-07 21:23 
GeneralRe: Any chance for .NET 2.0 and VB instead of C# Pin
tmacpherson8-Jun-07 4:44
tmacpherson8-Jun-07 4:44 
Looks like everything has gotten better since the updates from .Net 1.1 and SQL2005. I have started a project to do this but had to stop because of a lack of knowledge about creating services. The detection of NATd addresses would be a good change! Looks like a great project and tutorial for you to do Smile | :) hint hint wink wink!!!

http://troysmusic.com

Generalyou are my god. Pin
littlorry7-Aug-05 6:11
littlorry7-Aug-05 6:11 
GeneralLove it... Pin
funny thing30-Nov-03 13:55
funny thing30-Nov-03 13:55 
GeneralRe: Love it... Pin
Paul Reeder30-Nov-03 20:24
Paul Reeder30-Nov-03 20:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.