smithvoice.com
 Y'herd thisun? 

“Learning how to do in something new what you do without thinking in something old strengthens you in both.”

from this by smith

Simple desktop ISP reconnect service

TaggedCoding, DotNet

Thanks to DSL, everyone can host a website and mailserver or two (or 15) out of their home. All it takes is buying a domain, using a DNS service to redirect requests to your static or dynamic IP, switching on IIS in your Win2k/XP Pro or Win2kServer and running IISLockdown to close all those extra ports you don't want exposed to the script kiddies

Pretty much it's a cakewalk. Until you start relying on it.

I was tired of coming home and finding that my PPPOE (Point to Point Protocol Over Ethernet) connection was dropped; squirrels swinging on my lines, the evil local phone company not liking my 3rd party DSL provider, whatever the reason it was annoying. Sometimes it would chug along for weeks and sometimes it'd die a couple of times a day. And then there were the spooky times that even though I was not connected with another computer, my web box was connected with a dynamic IP instead of my static.

Some geeks rabidly hate PPPOE, but it isn't truly a big pain in itself, except when it comes to getting automatic connections, and because it is fast becoming the dominant system for DSL customers it's better to learn to work with it than fight the losing battle moving from isp to isp to avoid it. This article shows you how to make PPPOE your slave.

We know that we could make a scheduled app to check the connection every few minutes, but Windows Task Scheduler is "end-user" &ltg&gt. We also know that if we could easily create a Windows Service then we could just tell it to check the connections and reconnect as needed. However, MS chose to never give VBClassic developers a simple way to make a Service; You either had to go non-RAD with VC, use the unsupported and apparently no longer downloadable ntsvc.ocx (which, for some reason, is easier to trust at work than at home) or buy Desaware's NTService Toolkit (great product but a bit too much for my modest needs).

Now along comes VB.net which makes creating a Windows Service a breeze!

The Spec

  • Make a Windows Service that wakes up every two minutes and checks the computer's IP Addresses
  • If the count of addresses is equal to the count of LAN connection plus 1 (I personally have only 1 nic in my machine) and the static IP is in the list, we are connected so the service goes back to sleep
  • If the count of addresses is equal to the count of LAN and, for safety, if the address is not the static then we are not connected to the internet, so force a new connection
  • If the static IP is not in the list but the count of addresses is equal to the count of LAN connection plus 1 then we are connected to the internet but not with the static. In this case force a disconnect from the internet and force a reconnect in an attempt to get hold of the static IP.
  • Write the important stuff to the EventLog so we can see where the system can be improved (gotta have bugs if you want to really learn).
  • Hold general settings (static IP address & expected number of addreses) outside of the app so nothing has to be hard-coded. While .net has far better built-in Registry support the preference is to get out of the regedit habit and just use an XML configuration file.

The great thing about a project like this is that when you have a real need but there is no full sample code to steal you learn a lot. That said, I hope that you'll follow along with the logic that 's presented in this article rather than just copy&pasting and trusting it blindly. I'm not guaranteeing that it is the end-all, and there are probably alternative techniques that will make those ultra-picky snobdevs happy (no, they're never happy) ... but it is a fine working starting point and I hope you'll use it that way too.

The Architecture

The first thing in making a service is to decide whether you should do it as a single-file monolithic deployment or as a set of component assemblies. In the days of VBClassic and ntsvc.ocx you could make a generic service app to host the form to host the ocx and have the service call out to a server, but usually since we were already hosting that darned form we shoved too much into the one exe. These days the .net service samples all copy the same ms quickstart demo and show using physically discrete assemblies which is really the smarter way to go if there is a chance that the meat of the functionality could ever be reused elsewhere. Besides, it makes testing the functionality outside of the service a matter of making a quickie console harness. So that's the way we're going here.

The pieces of the solution will be:

  • The functionality library dll (called IPConnection)
    This will be a simple class with a single public method "ConnectIfNot". The method signature will have arguments to allow passing in an IP address (string) and the max number of expected addresses on the computer (int). For my purposes there is no need of a return value.
  • The Service exe (called IPConnector)
    This will include all the code it takes to register itself as a windows service and a timer whose event will cause a IPConnection object to do it's thing

The code pt1: IPConnection.dll

Fire up the VS IDE and create a new VB Windows Service project. Overwrite the default project name value with "IPConnector" as shown below.

Go to the solution explorer and right-click >>rename the class name from the default "Service1.vb" to "IPConnector.vb"

Right-click on the solution node (the topmost node in the solution tree, not the IPConnector project node) and click to Add >> New Project.

In the option box select Class Library and set the name value to "IPConnection"

Right-click on the new project's "Class1.vb" and rename the file to "IPConnection.vb" The tab for the class will be renamed automatically but you'll have to manually change the code window's default from "Public Class Class1" to "Public Class IPConnection"

Now we get to real typing! Ok, a little cut & pasting is acceptable. Let's make the entry point method we described earlier. Click your mouse inside the IPConnection class region and enter the private variables:

Private m_sStaticAddress as string = ""
Private m_shtMaxExpectedAddresses as short = 0

On the next line, type:

Public Sub ConnectIfNot(byval StaticAddress as string, byval MaxExpectedAddresses as short)
 

This method is the entry point that our service app to set those private variables and call into some private worker functions. Type the following inside the method stub:

m_sStaticAddress = Trim(StaticAddress)
m_shtMaxExpectedAddresses = MaxExpectedAddresses
  
If Not AmConnected()  Then 
System.Diagnostics.EventLog.WriteEntry _
("IPConnection", "Computer is not connected")
MakeConnection()
End If

These lines will be used to call a method that will check the status of the static ip address (AmConnected) and if it is not alive to force it to open (MakeConnection). We'll get to those next, but first notice that we added a fully declared framework namespace call to stick some information in the Windows EventLog. You don't have to always fully type out the namespaces unless you think there will be an ambiguity issue (calling an object of one parent namespace that is named the same as another object in another parent namespace), but it doesn't hurt especially now that we're all new to this stuff to occassionally start with "System." and follow the bouncing intellisense popups; it's enlightening to see all the options that a namespace provides.

SideNote: if you're making this class in notepad you get the Trim function by Importing "Microsoft.VisualBasic.Strings" or fully qualifing the function with "Microsoft.VisualBasic.Strings.Trim([your string here])" or you could create a new System.String object and call it's Trim method. Totally up to you. I'm typing in the IDE so I get the Microsoft.VisualBasic namespace automatically.

Now let's get to the meat. AmConnected. Click below the ConnectIfNot "End Sub" (but still inside the class region) and type this function:

 

Private Function AmConnected() As Boolean
Dim oIP As System.Net.IPAddress
Dim bConnected As Boolean = False
Dim shtCountIps As Short
Dim sAddr As String
Dim Hoster As System.Net.IPHostEntry
Hoster = System.Net.Dns.Resolve(System.Net.Dns.GetHostName())
shtCountIps = Hoster.AddressList.Length
With Hoster
If shtCountIps = m_shtMaxExpectedAddresses - 1 Then
If Hoster.AddressList(0).ToString() = m_sStaticAddress Then
bConnected = True
End If
ElseIf shtCountIps = m_shtMaxExpectedAddresses Then
For Each oIP In Hoster.AddressList
If oIP.ToString() = m_sStaticAddress Then
bConnected = True
Exit For
End If
Next
End If
 End With
 'if there are m_shtMaxExpectedAddresses but neither is the static
 'disconnect the active
 If shtCountIps = m_shtMaxExpectedAddresses And Not bConnected Then
DisconnectIncorrectConnection()
 End If
 Return bConnected
End Function

That took a squiggle out of our entry point method, but it added another. We'll get to that.

This function shows one way (there are others) to use the framework to easily get the local computer name as the return of System.NetDns.GetHostName().

Once you get the computer's hostname, you pass it to the System.Net.Dns.Resolve() function to get a IPHostEntry object, which contains a "collection" of IPAddress objects that you can iterate through to look for your static IP.

A funky thing about the AddressList object is that while it allows iteration with For...Each it does not have a .Count property. Instead the equivalent property is ".Length." This is because the object is a derivation of type System.Array rather than a derivation of System.Collections.ArrayList as it's name implies. How do we know that when it's details don't come up in Wincv? For me, one-off questions like this are most quicky answered with a fast aspx page (I don't like having the IDE make all of it's support files and folders just for a quickie). Copy the following into a text file, save it as test.aspx in your wwwroot folder, then load the page with http://localhost/test.aspx:

<%@ Page Language="vb" %>
<script runat="server">
Sub page_load(s as object, e as eventargs)
lblMessage.text = Test()
end sub
Private Function Test() As string
Dim Hoster As System.Net.IPHostEntry
Hoster = System.Net.Dns.Resolve(System.Net.Dns.GetHostName()) 
return  Hoster.AddressList.GetType.BaseType.ToString()
End Function 
</script>
<html>
<body>
<asp:Label id="lblMessage" runat="server"/>
</body>
</html>

Next we move to the MakeConnection method and question: "Where in the framework is the simple "ConnectToTheInternet" method?" If you can find it, let me know. In the end I reverted to the non-COM WinInet.dll.

If you've never used a win32 dll in your .net apps, relax. It's easier than Interop. Just paste the VB6 style declaration into the top of the class. In my first test I didn't even change the old Longs to Integers and it worked. But it's probably best to changes those types ... it's like porting from VB3 to VB5, only backwards!

Now you do it. Click your mouse at the end of the class header ("Public Class IPConnection") and press enter to give yourself some room above your variables. Then type in these lines (I changed the types for you here):

Private Const INTERNET_AUTODIAL_FORCE_UNATTENDED = 2
Private Declare Function InternetAutodial Lib "wininet.dll" _
(ByVal dwFlags As Integer, ByVal dwReserved As Integer) As Integer
Private Declare Function InternetAutodialHangup Lib "wininet.dll" _
(ByVal dwReserved As Integer) As Integer

And click on some other line (not in a method) to create the wrapper:

Private Sub MakeConnection()
System.Diagnostics.EventLog.WriteEntry("IpTest", "Attempting to make connection")
Dim l As Integer
l = InternetAutodial(INTERNET_AUTODIAL_FORCE_UNATTENDED, 0)
If l = 1 Then
System.Diagnostics.EventLog.WriteEntry _
("IPConnection", "Connection Succeeded")
Else
System.Diagnostics.EventLog.WriteEntry _
("IPConnection", "Connection Failed ")
End If
  
End Sub

All this routine does is call the WinInet function InternetAutodial, passing in a 2 as the first argument which does the connection without showing the user prompt (a service cannot show gui elements) and writes the yea/nay details to the EventLog. Note: you will need to have your username and password set in IE Connection properties, they are still required even though the system is doing the dialing. This will affect the service project too, as we'll see later.

The good thing about this function is that it is synchronous, so your code will wait for it to return, either after a connection is established or the dialer times out. For that reason it's a good idea to set your IE Connection properties to limit the number of retries to just one or two and the retry wait to just a few seconds.

Just one last method to create, the one that takes care of those poltergeist connections where your server is on the internet but is not using your static IP. Again it is a simple call in to the WinInet.dll:

Private Sub DisconnectIncorrectConnection()
System.Diagnostics.EventLog.WriteEntry _
("IPConnection", "Killing non-static ip connection")
InternetAutodialHangup(0)
End Sub

Scroll through your code window and make sure that all the squigglies are gone, if any remain hold your mouse over them and use the tooltip information to figure out what lines need adjusting. Then click the Build menu and choose "Build IPConnection"

That's it! Your class is done. But before we hook it up to the service, let's give it a whirl in a simple console app.

The code pt1.5: The Test Harness

For a simple quick test harness, there's no need to have the IDE make all of it's temporary project folders. We'll just do it in a texteditor.

Open up your UltraEdit or Notepad and type:

Public Module MyMod
Public Sub Main()
Dim oTest as New IPConnection.IPConnection
oTest.ConnectIfNot("[your static ip address]", 2)
oTest = Nothing
End Sub
End Module

Make sure you replace the [your static IP address] with your own address in the format ##.##.###.##. Now save this file as TestHarness.vb in the bin of your IPConnection project (by default the IDE would have saved the IPConnection.dll to your "C:\Documents and Settings\[user name]\My Documents\Visual Studio Projects\IPConnection\bin")

To compile this into an exe, open up the Visual Studio .Net console window, navigate to that bin folder and type the following at the console prompt:

vbc.exe /t:exe /out:TestHarness.exe TestHarness.vb /r:System.dll /r:IPConnection.dll

The running of the object is going to add entries to the EventLog so open your Event Viewer (Start >> Settings >> Control Panel >> Administrative Tools >> Event Viewer) and click on the Application Log option. If there are a lot of entries already you may want to clear them or save them off to make it easier to see your app's results.

Doubleclick on your TestHarness.exe. You may see a new console pop up and go away. Bring your Event Viewer back to focus and press F5 to refresh the list. See the new entries for "IPConnection"?

Conclusion: Make the IPConnector.exe Service

who is smith    contact smith     rss feed π
Since 1997 a place for my stuff, and it if helps you too then all the better smithvoice.com