TeredoHelper.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / NetworkInformation / TeredoHelper.cs / 1305376 / TeredoHelper.cs

                            using System.Collections.Generic; 
using System.ComponentModel;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security; 
using System.Threading;
 
 
namespace System.Net.NetworkInformation
{ 
    // This class wraps the native API NotifyStableUnicastIpAddressTable.  The native function's behavior is:
    //
    // 1. If the address table is already stable, it returns ERROR_SUCCESS and a Mib table handle that we must free.
    //    The passed-in callback will never be called, and the cancelHandle is set to NULL. 
    //
    // 2. If the address table is not stable, it returns ERROR_IO_PENDING.  The table handle is set to NULL, 
    //    and the cancelHandle is set to a valid handle.  The callback will be called (on a native threadpool thread) 
    //    EVERY TIME the address table becomes stable until CancelMibChangeNotify2 is called on the cancelHandle
    //    (via cancelHandle.Dispose()). 
    //
    // CancelMibChangeNotify2 guarantees that, by the time it returns, all calls to the callback will be complete
    // and that no new calls to the callback will be issued.
    // 
    // The major concerns of the class are: 1) making sure none of the managed objects needed to handle a native
    // callback are GC'd before the callback, and 2) making sure no native callbacks will try to call into an unloaded 
    // AppDomain. 

    [SuppressUnmanagedCodeSecurity] 
    internal class TeredoHelper
    {
        // Holds a list of all pending calls to NotifyStableUnicastIpAddressTable.  Also used as a lock to protect its
        // contents and impendingAppDomainUnload. 
        private static List pendingNotifications;
 
        // Flag that gets set when an AppDomain unload is imminent.  When this is set, no more calls to 
        // NotifyStableUnicastIpAddressTable are allowed.
        private static bool impendingAppDomainUnload; 

        private readonly Action callback;
        private readonly object state;
 
        private bool runCallbackCalled;
 
        // We explicitly keep a copy of this to prevent it from getting GC'd. 
        private readonly StableUnicastIpAddressTableDelegate onStabilizedDelegate;
 
        // Used to cancel notification after receiving the first callback, or when the AppDomain is going down.
        private SafeCancelMibChangeNotify cancelHandle;

        static TeredoHelper() 
        {
            pendingNotifications = new List(); 
            AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnAppDomainUnload); 
        }
 
        private TeredoHelper(Action callback, object state)
        {
            this.callback = callback;
            this.state = state; 
            this.onStabilizedDelegate = new StableUnicastIpAddressTableDelegate(OnStabilized);
            this.runCallbackCalled = false; 
        } 

        // Returns true if the address table is already stable.  Otherwise, calls callback when it becomes stable. 
        // 'Unsafe' because it does not flow ExecutionContext to the callback.
        public static bool UnsafeNotifyStableUnicastIpAddressTable(Action callback, object state)
        {
            GlobalLog.Assert(callback != null, 
                "UnsafeNotifyStableUnicastIpAddressTable called without specifying callback!");
 
            TeredoHelper helper = new TeredoHelper(callback, state); 

            uint err = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS; 
            SafeFreeMibTable table = null;

            lock (pendingNotifications)
            { 
                // If OnAppDomainUnload gets the lock first, tell our caller that we'll finish async.  Their AppDomain
                // is about to go down anyways.  If we do, hold the lock until we've added helper to the 
                // pendingNotifications list (if we're going to complete asynchronously). 
                if (impendingAppDomainUnload)
                { 
                    return false;
                }

                err = UnsafeNetInfoNativeMethods.NotifyStableUnicastIpAddressTable(AddressFamily.Unspecified, 
                    out table, helper.onStabilizedDelegate, IntPtr.Zero, out helper.cancelHandle);
 
                if (table != null) 
                {
                    table.Dispose(); 
                }

                if (err == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
                { 
                    GlobalLog.Assert(!helper.cancelHandle.IsInvalid,
                        "CancelHandle invalid despite returning ERROR_IO_PENDING"); 
 
                    // Async completion: add us to the pendingNotifications list so we'll be canceled in the
                    // event of an AppDomain unload. 
                    pendingNotifications.Add(helper);
                    return false;
                }
            } 

            if (err != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 
            { 
                throw new Win32Exception((int)err);
            } 

            return true;
        }
 
        private static void OnAppDomainUnload(object sender, EventArgs args)
        { 
            // We need to ensure that we won't get any callbacks from iphlpapi after the AppDomain goes down. 
            // Set impendingAppDomainUnload to ensure that no new calls to NotifyStableUnicastIpAddressTable are
            // made, then cancel any in-progress notifications by closing their cancelHandles. 
            //
            // We intentionally leave all of the canceled TeredoHelpers in the list.  impendingAppDomainUnload
            // prevents anyone from looking at the list, and it will all be GC'd as soon as the AppDomain goes
            // down anyways. 
            lock (pendingNotifications)
            { 
                impendingAppDomainUnload = true; 

                foreach (TeredoHelper helper in pendingNotifications) 
                {
                    GlobalLog.Assert(helper.cancelHandle != null && !helper.cancelHandle.IsInvalid,
                        "Invalid cancelHandle in pendingNotifications!");
 
                    helper.cancelHandle.Dispose();
                } 
            } 
        }
 
        private void RunCallback(object o)
        {
            GlobalLog.Assert(runCallbackCalled, "RunCallback called without setting runCallbackCalled!");
 
            // If OnAppDomainUnload beats us to the lock, do nothing: the AppDomain is going down soon anyways.
            // Otherwise, wait until the call to CancelMibChangeNotify2 is done before giving it up. 
            lock (pendingNotifications) 
            {
                if (impendingAppDomainUnload) 
                {
                    return;
                }
 
#if DEBUG
                bool successfullyRemoved = pendingNotifications.Remove(this); 
                GlobalLog.Assert(successfullyRemoved, 
                    "RunCallback for a TeredoHelper which is not in pendingNotifications!");
#else 
                pendingNotifications.Remove(this);
#endif

                GlobalLog.Assert(cancelHandle != null && !cancelHandle.IsInvalid, 
                    "Invalid cancelHandle in RunCallback");
 
                cancelHandle.Dispose(); 
            }
 
            callback.Invoke(state);
        }

        // This callback gets run on a native worker thread, which we don't want to allow arbitrary user code to 
        // execute on (it will block AppDomain unload, for one).  Free the MibTable and delegate (exactly once)
        // to the managed ThreadPool for the rest of the processing. 
        // 
        // We can't use SafeHandle here for table because the marshaller doesn't support them in reverse p/invokes.
        // We won't get an AppDomain unload here anyways, because OnAppDomainUnloaded will block until all of these 
        // callbacks are done.
        private void OnStabilized(IntPtr context, IntPtr table)
        {
            UnsafeNetInfoNativeMethods.FreeMibTable(table); 

            // Lock the TeredoHelper instance to ensure that only the first call to OnStabilized will get to call 
            // RunCallback.  This is the only place that TeredoHelpers get locked, as individual instances are not 
            // exposed to higher layers, so there's no chance for deadlock.
            if (!runCallbackCalled) 
            {
                lock (this)
                {
                    if (!runCallbackCalled) 
                    {
                        runCallbackCalled = true; 
                        ThreadPool.UnsafeQueueUserWorkItem(RunCallback, null); 
                    }
                } 
            }
        }
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK