DotRas dailer call hangs on second start of application

Jun 6, 2014 at 9:40 AM
Edited Jun 7, 2014 at 8:46 AM
I'm using DotRas in a small VPN dailer application. The user starts the application clicks on connect, the connection is created (if not there), dialed and a network drive is mounted. It works ok on Windows XP/7, however on Windows 8.1 on the first start of the app everything works fine, the user can connect and disconnect several times. When he then closes and reopens the application and clicks on connect the application hangs on dialing. At this stage I can also sometimes not go to see the network connection page (it hangs too). The PC hangs also on restart (I have to hit the power button).

I know that this is related to disposing the dialer (DotRas.RasDialer) component. Howerver I really tried to call dispose on all component that support this method. I do this also on application close (MainForm_FormClosing). I confirmed in the debugger, that the component is really disposed.

I'm using the Win8 version of DotRas. At the moment I do not have an idea what else I could to to prevent the app from hanging on the connection. Does anyone have a suggestion?

In the MainForm.Designer.cs:
private.DotRas.RasDialer Dialer;
In the Main Form.cs:
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if ((IsConnected) || (this.Dialer.IsBusy)){
        Disconnect();
    }
    if (this.Dialer != null) {this.Dialer.Dispose()}
}
I'm using .Net 2.0, Visual Studio 2013. I also tried with the example provided with the SDK, just changeing the VPN connection properties and using the local user phone book (no admin rights are needed to create an entry there).
private void CreateEntryButton_Click(object sender, EventArgs e)
{
    string UserPhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.User);
    UserPhoneBook = new RasPhoneBook();
    UserPhoneBook.Open(UserPhoneBookPath);

    RasEntry entry = RasEntry.CreateVpnEntry(EntryName, VPNServer, 
        RasVpnStrategy.L2tpOnly,  RasDevice.GetDeviceByName("(PPTP)", 
        RasDeviceType.Vpn));
    entry.Options.UserPreShardKey = true;
    entry.Options.UserLogOnCredentials = false;
    userPhoneBook.Entries.Add(entry);
    entry.UpdateCredentials(RasPreSharedKey.Client, SecretePW);
    entry.UpdateCredentials(new NetworkCredential(VPNUser, InputVPNPassword));
    entry.Update();
}
Even with this small change to the example application, on a second or third start of the application the application hangs on dialing (state: ConnectDevice is shown twice). So same behaviour as my extended version.

An suggestion or help would be highly appreciated.
Philipp
Coordinator
Jun 6, 2014 at 8:46 PM
You are correct that the problem is most likely surrounding what happens when Windows loses the ability to notify the callback method that state has changed during a connection attempt.

The Dispose method on the RasDialer is simply to ensure that any necessary cleanup of the component is done and to attempt cancellation of the connection if there is one pending. However, since you're disposing of the component right before the application terminates (by releasing the main form), and not ensuring that you don't have a connection attempt in progress before allowing termination, more than likely Windows is trying to signal a callback that the connection was cancelled (since Dispose is not blocking), and that's where it's corrupting the state machine in Windows.

Once that happens, depending on how the RAS state machine is being corrupted, it may or may not cause other issues with Windows, and thus requiring you to hard-reset your machine to fix it.

My suggestion to fix it: don't blindly terminate your app if a connection attempt is in progress. Cancel the connection attempt yourself first, and wait for it to complete cancellation before allowing the app to terminate.
Jun 7, 2014 at 8:44 AM
Edited Jun 7, 2014 at 8:50 AM
Thanks for your reply. I do not dispose of the dialer without disconnecting. The above snipped does not show the full code. I extended it. The problem also shows up, when I first disconnect and then close and reopen the application. Which is actually the normal usage. What is also strange: I do not see the same problem on Windows XP/7. I still have to test on plain Windows 8.

If the problem only would happen, when the application is closed while there is a connection or a the application is dialing, then I would agree. But it als happens when I start the app, dial a connection, hang up and then close the application.

Regards
Philipp
Coordinator
Jun 7, 2014 at 4:04 PM
Edited Jun 7, 2014 at 4:04 PM
Q: What exactly is disconnect doing?

I'd imagine the problem is around how you're disconnecting the connections. Microsoft tends to change how this stuff operates under the hood in between major releases, so it wouldn't surprise me at all that Windows XP - 7 are fine, but Windows 8/8.1 isn't.

Q: If you added a disconnect button (just for the sake of testing) can you repeatedly dial your connection within the application without closing it?

You might need to play around with how you're disconnecting in relation to when the application terminates to make it compliant with later versions of Windows. When dialing connections asynchronously (via DialAsync) a callback is passed to Windows that gets notified of event changes. Typically if the in-memory location of the callback is removed during a connection attempt, the RAS state machine gets corrupted and causes what you're seeing.
Jun 8, 2014 at 10:28 AM
Edited Jun 8, 2014 at 10:34 AM
This is the code for the disconnect method. Just to add: All the dispose parts were added because I had the problem on Windows 8.1. The problem was also there without all the dispose parts.
        private void Disconnect()
        {
            KeepVPNOpen.Stop();
            if (this.Dialer.IsBusy)
            {

                // The connection attempt has not been completed, cancel the attempt.
                this.Dialer.DialAsyncCancel();
            }
            else
            {
                // First disconnect mapped drive
                UnMapDrive();

                // The connection attempt has completed, attempt to find the connection in the active connections.
                RasConnection connection = RasConnection.GetActiveConnectionByHandle(this.handle);
                if (connection != null)
                {
                    // The connection has been found, disconnect it.
                    connection.HangUp();
                }
                        DisposeDialer();
            }
            this.StatusTextBox.AppendText("Die Verbindung wurde getrennt." + "\r\n");
            IsConnected = false;
        }

        private void DisposeDialer() {
            if (this.handle != null)
            {
                this.handle.Dispose();
                this.handle = null;
            }

            if (this.watcher != null)
            {
                this.watcher.Dispose();
                this.watcher = null;
            }

            if (this.UserPhoneBook != null) {
                this.UserPhoneBook.Dispose();
                this.UserPhoneBook = null;
            }
        }

        private void ConnectButton_Click(object sender, EventArgs e)
        {
            if (!KeepVPNOpen.DoPing(PingSServer))
            {
                this.StatusTextBox.AppendText("Internet Verbindungstest war nicht erfolgreich." + "\r\n");
                this.StatusTextBox.AppendText("Ist dieser PC mit dem Internet verbunden?" + "\r\n");
            }
            else
            {
                this.StatusTextBox.AppendText("Internet Verbindungstest war erfolgreich." + "\r\n");
            }

            string UserPhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.User);
            UserPhoneBook = new RasPhoneBook();
            UserPhoneBook.Open(UserPhoneBookPath);

            if (!UserPhoneBook.Entries.Contains(EntryName))
            {
                try
                {
                    // Create the entry that will be used by the dialer to dial the connection. Entries can be created manually, however the static methods on
                    // the RasEntry class shown below contain default information matching that what is set by Windows for each platform.
                    RasEntry entry = RasEntry.CreateVpnEntry(EntryName, VPNServer, RasVpnStrategy.L2tpOnly,RasDevice.GetDeviceByName("(PPTP)", RasDeviceType.Vpn));
                    entry.Options.UsePreSharedKey = true;
                    entry.Options.UseLogOnCredentials = false;
                    // To use split tunneling we would need to add a route by code - todo
                    //entry.Options.RemoteDefaultGateway = false;

                    // Add the new entry to the phone book.
                    UserPhoneBook.Entries.Add(entry);
                    entry.Options.UsePreSharedKey = true;
                    entry.UpdateCredentials(RasPreSharedKey.Client, SecretePW);
                    entry.UpdateCredentials(new NetworkCredential(VPNUser, this.InputVPNPassword.Text.Trim()));
                    entry.Update();
                    this.StatusTextBox.AppendText("VPN Verbindung erstellt." + "\r\n");
                }
                catch (Exception ex)
                {
                    this.StatusTextBox.AppendText(ex.ToString());
                }
            }
            else
            {
                this.StatusTextBox.AppendText("VPN Eintrag besteht bereits." + "\r\n");
            }
            
            this.Dialer.EntryName = EntryName;
            this.Dialer.PhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.User);

            try
            {
                // Set the credentials the dialer should use.
                this.Dialer.Credentials = new NetworkCredential(VPNUser, this.InputVPNPassword.Text.Trim());

                // NOTE: The entry MUST be in the phone book before the connection can be dialed.
                // Begin dialing the connection; this will raise events from the dialer instance.
                this.StatusTextBox.AppendText("VPN Verbindung wird hergestellt." + "\r\n");
                if (this.handle != null) { DisposeDialer(); }
                this.handle = this.Dialer.DialAsync();

                // Enable the disconnect button for use later.
                this.ConnectButton.Enabled = false;
                this.DisconnectButton.Enabled = true;

                // Monitor connection loss
                if (this.watcher != null) { this.watcher.Dispose(); }
                this.watcher = new RasConnectionWatcher();
                watcher.Disconnected += new EventHandler<RasConnectionEventArgs>(this.Connection_StateChanged);
                watcher.Handle = this.handle;
                watcher.EnableRaisingEvents = true;
            }
            catch (Exception ex)
            {
                this.StatusTextBox.AppendText(ex.ToString());
            }
        }
Regarding this callback that is passed to Windows: Where is this happending and it there a way to remove this callback manually, when I know that the application is closeing?

Regards
Philipp
Coordinator
Jun 8, 2014 at 2:40 PM
The callback gets passed to Windows when you call DialAsync under the hood, that's how the RasDialer gets notifications that the state of the connection has changed. The only thing that stands out to me is that you're calling DialAsyncCancel and not blocking until the connection attempt actually finishes.

Aside from that, nothing stands out to me, looks like you're going to have to play around with it until you make Windows happy.
Jun 13, 2014 at 3:09 PM
Edited Jun 14, 2014 at 3:02 PM
To be honest, I'm at a loss. I do not seem to find a way to "make Windows happy". What I have done/found out:
  • I can dial endless number of times, and the applications works as expected. However once dialed, closing and reopening the application and then dial again, the dialer will hang after "Connect device". Sometimes I can dial once or twice after reopening the application, but then the application hangs and I have to restart the PC to make it work again.
  • I had a look at the source code of DotRas and then increased the pollingInterval when calling HangUp. No luck.
  • I rewrote my application, to use the none-async version of Diailer.Dial(). No luck.
  • I created a very basic application based on the awailable example with really not a lot of logic. Same problem.
  • I tried using the WinXP, Win7, Win8 version of the DotRas library (they seem all to work on Windows 8.1). Same problem (Why are there different versions anyway?).
I can try using the .Net 4.0 version, but I suppose I will see the same problem.
So for me, the current version of DotRas does not work on Windows 8.1 (I do not know on Windows 8). Has anyone a working example on Windows 8.1?

If you have any additional suggestions in what direction I could "play around to make Windows happy", I would be very gracefull for suggestions. It is a littel bit frustrating: I have a usefull application which works perfect on Windows XP/Vista/7 but I do not seem to be able to make it work on Windows 8.1.

By the way: When the dialer blocks I have to hard reset Windows because it will hang on "Restarting". So not really something I can deliver to my users.

Philipp

Update: I did a test on Windows 8 and IT WORKED AS EXPECTED!!!! So it seems to be a problem with Windows 8.1.
Update: Same problem with the .Net 4.0 version.
Coordinator
Jun 16, 2014 at 3:58 AM
Sounds like you might have uncovered a problem with Windows 8.1. Is it Windows 8.1 Update, or just Windows 8.1?

Q: Are you disconnecting the connection using Windows manually once the application has been closed? (For example, terminating your application in Task Manager and being able to safely recover later)

There are different versions of DotRas because the structures that get passed to Windows change between operating systems. They typically add features (which change the underlying structs) between major versions. If I simply had one gigantic assembly that had all of the features available I'd be inundated with support questions asking why features won't work. You can see more about the features supported by each flavor by checking the "Choosing your Build" section of the provided CHM file included with the SDK.
Jun 16, 2014 at 5:57 AM
Thank you for the clarification. I just use a very limited set of functionality (creating a VPN, dialing it and disconnecting). Therefore it seems that in this case the version for XP also works for Windows Vista/7/8. However I also tried the one for Windows 8 on Windows 8.1 and it did hang on applicaiton close.

Regarding your question: The user opens my application, the can click on connect, which results in a test if the VPN connection is there, if not it is created, then the VPN is established and a network drive is mapped. I'm establishing a VPN to a Zyxle Firewall, which has a 3 minute max. timeout for inactive connections. Therefore I impletemted a small ping class which pings the server an keeps he connection open. As described on Windows 8.1 (it is an update from Windows 8) I can connect/disconnect several times. Everything works fine as long as I do not close the application. Once closed it than hangs on one of the next connection attemts. It blocks in a way, that makes Windows instable. Once blocked I can no longer go the the Network pannel to check the adapter settings. Even this pannel hangs. I cannot restart, because Windows hangs on "restarting".

On more thing I tried is to delete the created connection on exit of the application. On Windows 8.1 this gave me some more goes before the connection hanged again. It also is not really handy, because Windows will then ask on every dialing what type of network (private, public, work) the connection is.

The test you suggest is opening the application, connect/disconnect several times and then killing the applicaiton in the task manager, to see if this also leads to the same problem on the next start? I can try this and will report back.

Philipp
Coordinator
Jun 17, 2014 at 4:10 AM
Edited Jun 17, 2014 at 4:11 AM
Not sure if you're aware, but there's 3 versions of Windows 8 currently available.

1) Windows 8 - released October 26, 2012
2) Windows 8.1 - released October 17, 2013
3) Windows 8.1 Update - released April 8, 2014

Aside from how atrocious Microsoft's naming conventions are, any of the 3 editions could exhibit the issue. It would seem you've ruled out the first item from being the problem at least, so that's good.
Jun 17, 2014 at 7:03 AM
What I'm testing on is surely version 3) (Windows 8 updated to Windows 8.1). I also tested on version 1). As stated on plane Windows 8 my application works. I do currently not have a PC with a plain Windows 8.1 (version 2) installed.

Any idea on where I could look or what I could tweek to make it work?

Philipp
Coordinator
Jun 18, 2014 at 1:07 AM
Unfortunately with the bug within Windows you might be forced to wait until they patch it. Or come up with a workaround yourself, but that would also require you to figure out a potential workaround.

The only thing I can think of would be to move it into a Windows service and use IPC communication between a user accessible front end, and the Windows service over WCF and named pipes. If the problem is due to your client being closed, that might help. Aside from that, I wouldn't know what to tell you.

Wish I had an answer, but it sounds to me like it's a problem Microsoft is going to have to fix (if they even know about it).