Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Internet Download Limiter

4.92/5 (39 votes)
6 Jul 2010CDDL6 min read 69.5K   1.6K  
How to set a download limit and disable a user's network connection.

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.

Capture2.PNG

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.

Capture1.PNG

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.

VB
' 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 :-(

VB
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:

VB
' 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:

VB
AddHandler myTimer.Elapsed, New ElapsedEventHandler(AddressOf timer_Elapsed)

This is where we get the update from:

VB
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:

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

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

VB
' 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.

VB
AddHandler NetworkChange.NetworkAddressChanged, AddressOf AddressChanged

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

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

VB
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:

VB
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:

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

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