497 lines
14 KiB
C#
Raw Normal View History

//
// System.Net.NetworkInformation.NetworkChange
//
// Authors:
// Gonzalo Paniagua Javier (LinuxNetworkChange) (gonzalo@novell.com)
// Aaron Bockover (MacNetworkChange) (abock@xamarin.com)
//
// Copyright (c) 2006,2011 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2013 Xamarin, Inc. (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
#if NETWORK_CHANGE_STANDALONE
namespace NetworkInformation {
public class NetworkAvailabilityEventArgs : EventArgs
{
public bool IsAvailable { get; set; }
public NetworkAvailabilityEventArgs (bool available)
{
IsAvailable = available;
}
}
public delegate void NetworkAddressChangedEventHandler (object sender, EventArgs args);
public delegate void NetworkAvailabilityChangedEventHandler (object sender, NetworkAvailabilityEventArgs args);
#else
namespace System.Net.NetworkInformation {
#endif
internal interface INetworkChange : IDisposable {
event NetworkAddressChangedEventHandler NetworkAddressChanged;
event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
bool HasRegisteredEvents { get; }
}
public sealed class NetworkChange {
static INetworkChange networkChange;
public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
add {
lock (typeof (INetworkChange)) {
MaybeCreate ();
if (networkChange != null)
networkChange.NetworkAddressChanged += value;
}
}
remove {
lock (typeof (INetworkChange)) {
if (networkChange != null) {
networkChange.NetworkAddressChanged -= value;
MaybeDispose ();
}
}
}
}
public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
add {
lock (typeof (INetworkChange)) {
MaybeCreate ();
if (networkChange != null)
networkChange.NetworkAvailabilityChanged += value;
}
}
remove {
lock (typeof (INetworkChange)) {
if (networkChange != null) {
networkChange.NetworkAvailabilityChanged -= value;
MaybeDispose ();
}
}
}
}
static void MaybeCreate ()
{
if (networkChange != null)
return;
try {
networkChange = new MacNetworkChange ();
} catch {
#if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
networkChange = new LinuxNetworkChange ();
#endif
}
}
static void MaybeDispose ()
{
if (networkChange != null && networkChange.HasRegisteredEvents) {
networkChange.Dispose ();
networkChange = null;
}
}
}
internal sealed class MacNetworkChange : INetworkChange
{
const string DL_LIB = "/usr/lib/libSystem.dylib";
const string CORE_SERVICES_LIB = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
const string CORE_FOUNDATION_LIB = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
[UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
[DllImport (DL_LIB)]
static extern IntPtr dlopen (string path, int mode);
[DllImport (DL_LIB)]
static extern IntPtr dlsym (IntPtr handle, string symbol);
[DllImport (DL_LIB)]
static extern int dlclose (IntPtr handle);
[DllImport (CORE_FOUNDATION_LIB)]
static extern void CFRelease (IntPtr handle);
[DllImport (CORE_FOUNDATION_LIB)]
static extern IntPtr CFRunLoopGetMain ();
[DllImport (CORE_SERVICES_LIB)]
static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
[DllImport (CORE_SERVICES_LIB)]
static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
[DllImport (CORE_SERVICES_LIB)]
static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
[DllImport (CORE_SERVICES_LIB)]
static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
[DllImport (CORE_SERVICES_LIB)]
static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
[StructLayout (LayoutKind.Explicit, Size = 28)]
struct sockaddr_in {
[FieldOffset (0)] public byte sin_len;
[FieldOffset (1)] public byte sin_family;
public static sockaddr_in Create ()
{
return new sockaddr_in {
sin_len = 28,
sin_family = 2 // AF_INET
};
}
}
[StructLayout (LayoutKind.Sequential)]
struct SCNetworkReachabilityContext {
public IntPtr version;
public IntPtr info;
public IntPtr retain;
public IntPtr release;
public IntPtr copyDescription;
}
[Flags]
enum NetworkReachabilityFlags {
None = 0,
TransientConnection = 1 << 0,
Reachable = 1 << 1,
ConnectionRequired = 1 << 2,
ConnectionOnTraffic = 1 << 3,
InterventionRequired = 1 << 4,
ConnectionOnDemand = 1 << 5,
IsLocalAddress = 1 << 16,
IsDirect = 1 << 17,
IsWWAN = 1 << 18,
ConnectionAutomatic = ConnectionOnTraffic
}
IntPtr handle;
IntPtr runLoopMode;
SCNetworkReachabilityCallback callback;
bool scheduledWithRunLoop;
NetworkReachabilityFlags flags;
event NetworkAddressChangedEventHandler networkAddressChanged;
event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
public event NetworkAddressChangedEventHandler NetworkAddressChanged {
add {
value (null, EventArgs.Empty);
networkAddressChanged += value;
}
remove { networkAddressChanged -= value; }
}
public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
add {
value (null, new NetworkAvailabilityEventArgs (IsAvailable));
networkAvailabilityChanged += value;
}
remove { networkAvailabilityChanged -= value; }
}
bool IsAvailable {
get {
return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
(flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
}
}
public bool HasRegisteredEvents {
get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
}
public MacNetworkChange ()
{
var sockaddr = sockaddr_in.Create ();
handle = SCNetworkReachabilityCreateWithAddress (IntPtr.Zero, ref sockaddr);
if (handle == IntPtr.Zero)
throw new Exception ("SCNetworkReachabilityCreateWithAddress returned NULL");
callback = new SCNetworkReachabilityCallback (HandleCallback);
var info = new SCNetworkReachabilityContext {
info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
};
SCNetworkReachabilitySetCallback (handle, callback, ref info);
scheduledWithRunLoop =
LoadRunLoopMode () &&
SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
SCNetworkReachabilityGetFlags (handle, out flags);
}
bool LoadRunLoopMode ()
{
var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
if (cfLibHandle == IntPtr.Zero)
return false;
try {
runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
if (runLoopMode != IntPtr.Zero) {
runLoopMode = Marshal.ReadIntPtr (runLoopMode);
return runLoopMode != IntPtr.Zero;
}
} finally {
dlclose (cfLibHandle);
}
return false;
}
public void Dispose ()
{
lock (this) {
if (handle == IntPtr.Zero)
return;
if (scheduledWithRunLoop)
SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
CFRelease (handle);
handle = IntPtr.Zero;
callback = null;
flags = NetworkReachabilityFlags.None;
scheduledWithRunLoop = false;
}
}
#if MONOTOUCH
[MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
#endif
static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
{
if (info == IntPtr.Zero)
return;
var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
if (instance == null || instance.flags == flags)
return;
instance.flags = flags;
var addressChanged = instance.networkAddressChanged;
if (addressChanged != null)
addressChanged (null, EventArgs.Empty);
var availabilityChanged = instance.networkAvailabilityChanged;
if (availabilityChanged != null)
availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
}
}
#if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
internal sealed class LinuxNetworkChange : INetworkChange {
[Flags]
enum EventType {
Availability = 1 << 0,
Address = 1 << 1,
}
object _lock = new object ();
Socket nl_sock;
SocketAsyncEventArgs nl_args;
EventType pending_events;
Timer timer;
NetworkAddressChangedEventHandler AddressChanged;
NetworkAvailabilityChangedEventHandler AvailabilityChanged;
public event NetworkAddressChangedEventHandler NetworkAddressChanged {
add { Register (value); }
remove { Unregister (value); }
}
public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
add { Register (value); }
remove { Unregister (value); }
}
public bool HasRegisteredEvents {
get { return AddressChanged != null || AvailabilityChanged != null; }
}
public void Dispose ()
{
}
//internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
bool EnsureSocket ()
{
lock (_lock) {
if (nl_sock != null)
return true;
IntPtr fd = CreateNLSocket ();
if (fd.ToInt64 () == -1)
return false;
nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, fd);
nl_args = new SocketAsyncEventArgs ();
nl_args.SetBuffer (new byte [8192], 0, 8192);
nl_args.Completed += OnDataAvailable;
nl_sock.ReceiveAsync (nl_args);
}
return true;
}
// _lock is held by the caller
void MaybeCloseSocket ()
{
if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
return;
CloseNLSocket (nl_sock.Handle);
GC.SuppressFinalize (nl_sock);
nl_sock = null;
nl_args = null;
}
bool GetAvailability ()
{
NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
foreach (NetworkInterface n in adapters) {
// TODO: also check for a default route present?
if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
continue;
if (n.OperationalStatus == OperationalStatus.Up)
return true;
}
return false;
}
void OnAvailabilityChanged (object unused)
{
NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
}
void OnAddressChanged (object unused)
{
NetworkAddressChangedEventHandler d = AddressChanged;
d (null, EventArgs.Empty);
}
void OnEventDue (object unused)
{
EventType evts;
lock (_lock) {
evts = pending_events;
pending_events = 0;
timer.Change (-1, -1);
}
if ((evts & EventType.Availability) != 0)
ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
if ((evts & EventType.Address) != 0)
ThreadPool.QueueUserWorkItem (OnAddressChanged);
}
void QueueEvent (EventType type)
{
lock (_lock) {
if (timer == null)
timer = new Timer (OnEventDue);
if (pending_events == 0)
timer.Change (150, -1);
pending_events |= type;
}
}
unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
{
EventType type;
fixed (byte *ptr = args.Buffer) {
type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
}
nl_sock.ReceiveAsync (nl_args);
if (type != 0)
QueueEvent (type);
}
void Register (NetworkAddressChangedEventHandler d)
{
EnsureSocket ();
AddressChanged += d;
}
void Register (NetworkAvailabilityChangedEventHandler d)
{
EnsureSocket ();
AvailabilityChanged += d;
}
void Unregister (NetworkAddressChangedEventHandler d)
{
lock (_lock) {
AddressChanged -= d;
MaybeCloseSocket ();
}
}
void Unregister (NetworkAvailabilityChangedEventHandler d)
{
lock (_lock) {
AvailabilityChanged -= d;
MaybeCloseSocket ();
}
}
#if MONOTOUCH || MONODROID
const string LIBNAME = "__Internal";
#else
const string LIBNAME = "MonoPosixHelper";
#endif
[DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr CreateNLSocket ();
[DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
[DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr CloseNLSocket (IntPtr sock);
}
#endif
}