You are on page 1of 7

Internet Download Limiter

Euhemerus

Rate this: 4.92 (39 votes)

6 Jul 2010CDDL
How to set a download limit and disable a user's network connection.
 Download source code - 19.8 KB

Introduction
For those who have a capped download broadband package and have children, this article may
be a Godsend by showing a way to limit users' download ability by setting a daily download
allowance.

Background
So there I was, lecturing my children on the excess download charges our ISP had made again,
when it occured to me that what I need is someway of limiting their download capacity. After
searching the Web and asking in the CP forums, I came up with the idea for this little program.

Images
The actual program, when running, creates a permanent, very small window on the user's
desktop informing them of how much download capacity they have remaining.

Once the limit has been reached, the program informs the user that their limit has been reached
and that their Internet access is now disabled.

Using the Code


I've tried to keep the program as simple as possible, and therefore the code itself has a relatively
simple mode of operation. We use a performance counter (see http://msdn.microsoft.com/en-
us/library/system.diagnostics.performancecounter(VS.90).aspx for more information) to gather
information on the number of bytes a network adapter has received.

Hide   Copy Code

' Instantiate our performance counter that will get the information we need.
downloadCounter = New PerformanceCounter("Network Interface", "Bytes
Received/sec", nicName)

We then keep a running total, and if the running total exceeds a predetermined limit, we disable
the user's network connection until the following day. It's as simple as that!

So, how do we implement all of this? How do we disable the user's internet connection I can hear
you asking. Easy! We stop the DHCP service on the user's computer. Now, here's the rub: if you
don't use dynamically allocated IP addresses from a DHCP server, stopping the client's DHCP
service isn't going to make any difference; it won't disable their connection :-(

Hide   Copy Code

Private Sub StopService(ByVal service As String)


'http://msdn.microsoft.com/en-us/library/aa288037%28VS.71%29.aspx

Dim sc As ServiceController = New ServiceController(service)

If sc.CanStop And sc.Status <> ServiceControllerStatus.Stopped Then


sc.Stop()
sc.WaitForStatus(ServiceControllerStatus.Stopped)
End If
End Sub

To be able to gather the downloaded bytes and keep a running total, we create an event that is
triggered every second and updates the latest download total. To create the trigger, we create a
one second timer:

Hide   Copy Code

' Set the time interval for collecting the download data (milliseconds).
Dim myTimer As System.Timers.Timer = New System.Timers.Timer(1000)

We then create an Event Handler and point this to our Delegated collection Sub:

Hide   Copy Code

AddHandler myTimer.Elapsed, New ElapsedEventHandler(AddressOf timer_Elapsed)

This is where we get the update from:

Hide   Copy Code

Private Sub timer_Elapsed(ByVal sender As Object, ByVal e As ElapsedEventArgs)

downloadBytes = downloadCounter.NextSample().RawValue
regKey.SetValue("PreviouslyDownloadedBytes", downloadBytes,
RegistryValueKind.DWord)

End Sub

Note: we also save a running total to the Registry. This is explained later.

We also use the timer.tick event to see if we have reached our download limit:

Hide   Shrink     Copy Code

Private Sub TimerCounter_Tick(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles TimerCounter.Tick
If Not disabled Then
lblDownloadBytes.Text = ((tempDownloadLimit - downloadBytes) /
(1024 ^ 2)).ToString("###0.###") & " MBytes remaining"

If downloadBytes > tempDownloadLimit Then

' We've reached our download limit.


regKey.SetValue("Expired", True, RegistryValueKind.DWord)
regKey.SetValue("PreviouslyDownloadedBytes", 0,
RegistryValueKind.DWord)

disabled = True

StopService(service)
lblDownloadBytes.Text = "Download Limit Reached, Internet Access
Disabled."
End If
Else

' If we've reached the download limit, we stop the timer and remove the
event handler
' that monitors the downloaded bytes.
' No point in monitoring anymore something that isn't happening!
' We then rely on the AddressChanged sub to continue disabling the
network should the
' user restart the dhcp service.
TimerCounter.Stop()
RemoveHandler myTimer.Elapsed, AddressOf timer_Elapsed

End If
End Sub

As the program stands, as described, it would be pretty trivial to circumvent and regain network
access. So, we put in a few checks to help thwart circumvention. One of the first checks we
perform is to see if the computer has been restarted thus resetting our performance counter.

As can be seen from the previous code block, we set a couple of Registry entries. One of these
entries is 'Expired'. This is a boolean value that, if true, indicates that the download limit has been
reached. We check to see if we have 'expired' on startup, and if we have, we disable the DHCP
service straight away.
Another Registry setting we save, is the 'PreviouslyDownloadedBytes'. This is used in the event
that the user restarts their computer and hasn't exhausted their download limit for that day. We
subtract the PreviouslyDownloadedBytes figure from the limit we set, and this becomes the
new download limit for the remainder of the day.

Hide   Copy Code

Case DateComparisonResult.TheSame
' We're running again on the same day. Check to make sure we've not expired.

If downloadLimitReached Then
lblDownloadBytes.Text = "Download Limit Reached, Internet Access Disabled."
disabled = True
StopService(service)
Else
' Adjust the download limit in case the computer has been restarted.
tempDownloadLimit = maxDownloadLimit - previouslyDownloadedBytes
End If

Another check performed is to see if the user has moved the system clock forward thus resetting
the download counter. One of our Registry entries saves the date on which we last ran, we then
compare this date to the system's current date on startup to determine if we should give the user
internet access. So that we can accurately determine if the system's clock is in fact correct, we
query an NTP time server. DaveyM69 created a very detailed and useful CP article on querying
time servers; see: http://www.codeproject.com/KB/datetime/SNTPClient.aspx for more
information. I have modified Dave's original SNTPClient code to suit my own needs, and used
this code to query a time server.

Hide   Copy Code

' First check to see if the dhcp service is running, if not, start it.
' We need network access to communicate with an NTP server to check that
' the system clock hasn't been changed.
If Not IsServiceRunning(service) Then
StartService(service)
End If

' Make sure the nic is connected to the network before querying
' the NTP server.
If My.Computer.Network.IsAvailable Then
ntpServerDate = SntpClient.GetNow().ToShortDateString
End If

' Check whether the system clock has been changed.


If currentDate <> ntpServerDate Then
' System clock has been changed! Reset it.
SntpClient.UpdateLocalDateTime = True
SntpClient.GetNow()
End If

The last check we make is to monitor the state of the IP address assigned to the NIC. If we have
disabled the DHCP service and thus disabled the connection, and the NIC's IP address will be
non-existent. We set up an event handler which notifies us if the NIC's IP address changes.

Hide   Copy Code
AddHandler NetworkChange.NetworkAddressChanged, AddressOf AddressChanged

We then use our delegated sub to disable the DHCP service again if the user re-enabled it.

Hide   Copy Code

Private Sub AddressChanged(ByVal sender As Object, ByVal e As EventArgs)

' We're notified here of an IP address change. If the network should have
been
' disabled and we reach here, it means the user has restarted the dhcp
service.
' So we stop it again.
If disabled Then
StopService(service)
End If

End Sub

So, there you have it ladies and gentlemen; how to simply disable a network connection after a
predetermined number of bytes have been downloaded.

Points of Interest
Because the instantiation of the performance counter requires the name of the NIC, we use a
WMI routine to get the NIC's name.

Hide   Shrink     Copy Code

Private Function GetNetworkAdaptorName() As String

' Get the name of the nic that has an IP address associated with it.

Dim nicName As String = ""

Dim query As ManagementObjectSearcher = New ManagementObjectSearcher _


("SELECT * FROM Win32_NetworkAdapterConfiguration " +
"WHERE IPEnabled = TRUE")

Dim queryCollection As ManagementObjectCollection = query.Get()


Dim dhcpAddress() As String

For Each mo As ManagementObject In queryCollection


nicName = mo("Description").ToString.Trim
dhcpAddress = CType(mo("IPAddress"), String())

If dhcpAddress(0) <> "" Or dhcpAddress(0) <> "255.255.255.255" Then


' Replace any forward slashes to underscores.
' Nvidia adaptors often have forward slashes
' in their NIC names which totally screws things up.
If nicName.Contains("/"c) Then
nicName = nicName.Replace("/"c, "_"c)
End If
Exit For
End If
Next
Return nicName
End Function

Although we try and filter the number of NICs returned by our WMI query by using the:

Hide   Copy Code

WHERE IPEnabled = TRUE

clause, we can sometimes get back more than one NIC. In this case, we check to see if the
returned NIC has a DHCP server address associated with it.

We also use in the program a routine to start the DHCP service. One of the problems I found was
that if you start the DHCP service and then try and use any network function immediately, the
function would fail even though we wait explicitly for the service to start by using:

Hide   Copy Code

sc.WaitForStatus(ServiceControllerStatus.Running)

This is because, even though the service is running, it takes time for the DHCP server to allocate
an IP address to the NIC. To keep things simple, I just added a 5 second delay before we use any
network function.

Hide   Copy Code

Private Sub StartService(ByVal service As String)

Dim sc As ServiceController = New ServiceController(service)

If sc.Status <> ServiceControllerStatus.Running Then


Try
sc.Start()
sc.WaitForStatus(ServiceControllerStatus.Running)

' Even though we wait for the status of the dhcp service to change to
' Running, we have to pause to give the dhcp server time to allocate
' an IP address to our Nic.
Thread.Sleep(5000)

Catch ex As Exception
lblDownloadBytes.Text = "Could Not Start DHCP Service"
End Try
End If

End Sub

Running the Program


To run the program on the user's system, put the executable out of the way somewhere, say
'Windows\system32', and then create a Registry entry in
the HKLM\Software\Microsoft\Windows\CurrentVersion\Run Registry key to start the program.

Caveats
Although the program works well on Windows XP and Windows 7, the user must be a member of
the Administrators group. Additionally, on Vista, even if UAC is not disabled, UAC prompts will
appear when starting and stopping the DHCP service and trying to change the system time.

To Do
I think, to overcome the running as admin and UAC problems, everything that doesn't display
information to the user should become a Windows service with a separate GUI app that
communicates with the service to display to the user their current download usage.

One other thing: if the user stops our app in the Task Manager, obviously we can't monitor
download usage. I've been thinking about adding to the form closing event a routine that shuts
down the user's computer if they stop our app. Also, it might be an idea integrating our app in
such a way that if it's not running at startup, the NIC itself can't start; possibly by relying on our
app to, say, start the NIC's driver.

History
 4th July 2010 - Initial version.
 5th July 2010 - Bug fix.

License
This article, along with any associated source code and files, is licensed under The Common
Development and Distribution License (CDDL)

You might also like