| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- //------------------------------------------------------------
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------
- namespace System.ServiceModel.Channels
- {
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Net;
- using System.Net.NetworkInformation;
- using System.Net.Sockets;
- using System.Runtime;
- using System.ServiceModel;
- using System.Threading;
- // IP address helper class for multi-homing support
- class PeerIPHelper
- {
- public event EventHandler AddressChanged;
- bool isOpen;
- readonly IPAddress listenAddress; // To listen on a single IP address.
- IPAddress[] localAddresses;
- AddressChangeHelper addressChangeHelper;
- Socket ipv6Socket;
- object thisLock;
- const uint Six2FourPrefix = 0x220;
- const uint TeredoPrefix = 0x00000120;
- const uint IsatapIdentifier = 0xfe5e0000;
- enum AddressType
- {
- Unknown,
- Teredo,
- Isatap,
- Six2Four
- }
- public PeerIPHelper()
- {
- Initialize();
- }
- public PeerIPHelper(IPAddress listenAddress)
- {
- if (!(listenAddress != null))
- {
- throw Fx.AssertAndThrow("listenAddress expected to be non-null");
- }
- this.listenAddress = listenAddress;
- Initialize();
- }
- void Initialize()
- {
- this.localAddresses = new IPAddress[0];
- this.thisLock = new object();
- }
- // NOTE: This is just for suites -- to skip timeout usage
- internal int AddressChangeWaitTimeout
- {
- set { this.addressChangeHelper.Timeout = value; }
- }
- object ThisLock
- {
- get { return this.thisLock; }
- }
- // Compares if the specified collection matches our local cache. Return true on mismatch.
- public bool AddressesChanged(ReadOnlyCollection<IPAddress> addresses)
- {
- bool changed = false;
- lock (ThisLock)
- {
- if (addresses.Count != this.localAddresses.Length)
- {
- changed = true;
- }
- else
- {
- // If every specified addresses exist in the cache, addresses haven't changed
- foreach (IPAddress address in this.localAddresses)
- {
- if (!addresses.Contains(address))
- {
- changed = true;
- break;
- }
- }
- }
- }
- return changed;
- }
- // Since scope ID of IPAddress is mutable, you want to be able to clone an IP address
- public static IPAddress CloneAddress(IPAddress source, bool maskScopeId)
- {
- IPAddress clone = null;
- if (maskScopeId || V4Address(source))
- clone = new IPAddress(source.GetAddressBytes());
- else
- clone = new IPAddress(source.GetAddressBytes(), source.ScopeId);
- return clone;
- }
- // Since scope ID of IPAddress is mutable, you want to be able to clone IP addresses in an array or collection
- static ReadOnlyCollection<IPAddress> CloneAddresses(IPAddress[] sourceArray)
- {
- IPAddress[] cloneArray = new IPAddress[sourceArray.Length];
- for (int i = 0; i < sourceArray.Length; i++)
- {
- cloneArray[i] = CloneAddress(sourceArray[i], false);
- }
- return new ReadOnlyCollection<IPAddress>(cloneArray);
- }
- public static ReadOnlyCollection<IPAddress> CloneAddresses(ReadOnlyCollection<IPAddress> sourceCollection, bool maskScopeId)
- {
- IPAddress[] cloneArray = new IPAddress[sourceCollection.Count];
- for (int i = 0; i < sourceCollection.Count; i++)
- {
- cloneArray[i] = CloneAddress(sourceCollection[i], maskScopeId);
- }
- return new ReadOnlyCollection<IPAddress>(cloneArray);
- }
- // When listening on a specific IP address, creates an array containing just that address
- static IPAddress[] CreateAddressArray(IPAddress address)
- {
- IPAddress[] addressArray = new IPAddress[1];
- addressArray[0] = CloneAddress(address, false);
- return addressArray;
- }
- public void Close()
- {
- if (this.isOpen)
- {
- lock (ThisLock)
- {
- if (this.isOpen)
- {
- this.addressChangeHelper.Unregister();
- if (this.ipv6Socket != null)
- {
- this.ipv6Socket.Close();
- }
- this.isOpen = false;
- this.addressChangeHelper = null;
- }
- }
- }
- }
- // Retrieve the IP addresses configured on the machine.
- IPAddress[] GetAddresses()
- {
- List<IPAddress> addresses = new List<IPAddress>();
- List<IPAddress> temporaryAddresses = new List<IPAddress>();
- if (this.listenAddress != null) // single local address scenario?
- {
- // Check if the specified address is configured
- if (ValidAddress(this.listenAddress))
- {
- return (CreateAddressArray(this.listenAddress));
- }
- }
- // Walk the interfaces
- NetworkInterface[] networkIfs = NetworkInterface.GetAllNetworkInterfaces();
- foreach (NetworkInterface networkIf in networkIfs)
- {
- if (!ValidInterface(networkIf))
- {
- continue;
- }
- // Process each unicast address for the interface. Pick at most one IPv4 and one IPv6 address.
- // Add remaining eligible addresses on the list to surplus list. We can add these back to the list
- // being returned, if there is room.
- IPInterfaceProperties properties = networkIf.GetIPProperties();
- if (properties != null)
- {
- foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
- {
- if (NonTransientAddress(unicastAddress))
- {
- if (unicastAddress.SuffixOrigin == SuffixOrigin.Random)
- temporaryAddresses.Add(unicastAddress.Address);
- else
- addresses.Add(unicastAddress.Address);
- }
- }
- }
- }
- if (addresses.Count > 0)
- return ReorderAddresses(addresses);
- else
- return temporaryAddresses.ToArray();
- }
- internal static IPAddress[] ReorderAddresses(IEnumerable<IPAddress> sourceAddresses)
- {
- List<IPAddress> result = new List<IPAddress>();
- List<IPAddress> notAdded = new List<IPAddress>();
- AddressType addressType = AddressType.Unknown;
- IPAddress v4Address = null, v6Address = null, isatapAddress = null, teredoAddress = null, six2FourAddress = null;
- foreach (IPAddress address in sourceAddresses)
- {
- if (address.AddressFamily == AddressFamily.InterNetwork)
- {
- if (v4Address != null)
- notAdded.Add(address);
- else
- {
- v4Address = address;
- }
- continue;
- }
- if (address.AddressFamily != AddressFamily.InterNetworkV6)
- {
- notAdded.Add(address);
- continue;
- }
- if (address.IsIPv6LinkLocal || address.IsIPv6SiteLocal)
- {
- notAdded.Add(address);
- continue;
- }
- addressType = GetAddressType(address);
- switch (addressType)
- {
- case AddressType.Teredo:
- {
- if (teredoAddress == null)
- {
- teredoAddress = address;
- }
- else
- {
- notAdded.Add(address);
- }
- continue;
- }
- case AddressType.Six2Four:
- {
- if (six2FourAddress == null)
- {
- six2FourAddress = address;
- }
- else
- {
- notAdded.Add(address);
- }
- continue;
- }
- case AddressType.Isatap:
- {
- if (isatapAddress == null)
- {
- isatapAddress = address;
- }
- else
- {
- notAdded.Add(address);
- }
- continue;
- }
- default:
- {
- if (v6Address != null)
- notAdded.Add(address);
- else
- {
- v6Address = address;
- }
- continue;
- }
- }
- }
- if (six2FourAddress != null)
- result.Add(six2FourAddress);
- if (teredoAddress != null)
- result.Add(teredoAddress);
- if (isatapAddress != null)
- result.Add(isatapAddress);
- if (v6Address != null)
- result.Add(v6Address);
- if (v4Address != null)
- result.Add(v4Address);
- result.AddRange(notAdded);
- return result.ToArray();
- }
- static AddressType GetAddressType(IPAddress address)
- {
- AddressType result = AddressType.Unknown;
- byte[] bytes = address.GetAddressBytes();
- if (BitConverter.ToUInt16(bytes, 0) == Six2FourPrefix)
- result = AddressType.Six2Four;
- else if (BitConverter.ToUInt32(bytes, 0) == TeredoPrefix)
- result = AddressType.Teredo;
- else if (BitConverter.ToUInt32(bytes, 8) == IsatapIdentifier)
- result = AddressType.Isatap;
- return result;
- }
- // Given an EPR, replaces its URI with the specified IP address
- public static EndpointAddress GetIPEndpointAddress(EndpointAddress epr, IPAddress address)
- {
- EndpointAddressBuilder eprBuilder = new EndpointAddressBuilder(epr);
- eprBuilder.Uri = GetIPUri(epr.Uri, address);
- return eprBuilder.ToEndpointAddress();
- }
- // Given a hostName based URI, replaces hostName with the IP address
- public static Uri GetIPUri(Uri uri, IPAddress ipAddress)
- {
- UriBuilder uriBuilder = new UriBuilder(uri);
- if (V6Address(ipAddress) && (ipAddress.IsIPv6LinkLocal || ipAddress.IsIPv6SiteLocal))
- {
- // We make a copy of the IP address because scopeID will not be part of ToString() if set after IP address was created
- uriBuilder.Host = new IPAddress(ipAddress.GetAddressBytes(), ipAddress.ScopeId).ToString();
- }
- else
- {
- uriBuilder.Host = ipAddress.ToString();
- }
- return uriBuilder.Uri;
- }
- // Retrieve the currently configured addresses (cached)
- public ReadOnlyCollection<IPAddress> GetLocalAddresses()
- {
- lock (ThisLock)
- {
- // Return a clone of the address cache
- return CloneAddresses(this.localAddresses);
- }
- }
- // An address is valid if it is a non-transient addresss (if global, must be DNS-eligible as well)
- static bool NonTransientAddress(UnicastIPAddressInformation address)
- {
- return (!address.IsTransient);
- }
- public static bool V4Address(IPAddress address)
- {
- return address.AddressFamily == AddressFamily.InterNetwork;
- }
- public static bool V6Address(IPAddress address)
- {
- return address.AddressFamily == AddressFamily.InterNetworkV6;
- }
- // Returns true if the specified address is configured on the machine
- public static bool ValidAddress(IPAddress address)
- {
- // Walk the interfaces
- NetworkInterface[] networkIfs = NetworkInterface.GetAllNetworkInterfaces();
- foreach (NetworkInterface networkIf in networkIfs)
- {
- if (ValidInterface(networkIf))
- {
- IPInterfaceProperties properties = networkIf.GetIPProperties();
- if (properties != null)
- {
- foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
- {
- if (address.Equals(unicastAddress.Address))
- {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
- static bool ValidInterface(NetworkInterface networkIf)
- {
- return (networkIf.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
- networkIf.OperationalStatus == OperationalStatus.Up);
- }
- // Process the address change notification from the system and check if the addresses
- // have really changed. If so, update the local cache and notify interested parties.
- void OnAddressChanged()
- {
- bool changed = false;
- IPAddress[] newAddresses = GetAddresses();
- lock (ThisLock)
- {
- if (AddressesChanged(Array.AsReadOnly<IPAddress>(newAddresses)))
- {
- this.localAddresses = newAddresses;
- changed = true;
- }
- }
- if (changed)
- {
- EventHandler handler = AddressChanged;
- if (handler != null && this.isOpen)
- {
- handler(this, EventArgs.Empty);
- }
- }
- }
- public void Open()
- {
- lock (ThisLock)
- {
- Fx.Assert(!this.isOpen, "Helper not expected to be open");
- // Register for addr changed event and retrieve addresses from the system
- this.addressChangeHelper = new AddressChangeHelper(OnAddressChanged);
- this.localAddresses = GetAddresses();
- if (Socket.OSSupportsIPv6)
- {
- this.ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.IP);
- }
- this.isOpen = true;
- }
- }
- // Sorts the collection of addresses using sort ioctl
- public ReadOnlyCollection<IPAddress> SortAddresses(ReadOnlyCollection<IPAddress> addresses)
- {
- ReadOnlyCollection<IPAddress> sortedAddresses = SocketAddressList.SortAddresses(this.ipv6Socket, listenAddress, addresses);
- // If listening on specific address, copy the scope ID that we're listing on for the
- // link and site local addresses in the sorted list (so that the connect will work)
- if (this.listenAddress != null)
- {
- if (this.listenAddress.IsIPv6LinkLocal)
- {
- foreach (IPAddress address in sortedAddresses)
- {
- if (address.IsIPv6LinkLocal)
- {
- address.ScopeId = this.listenAddress.ScopeId;
- }
- }
- }
- else if (this.listenAddress.IsIPv6SiteLocal)
- {
- foreach (IPAddress address in sortedAddresses)
- {
- if (address.IsIPv6SiteLocal)
- {
- address.ScopeId = this.listenAddress.ScopeId;
- }
- }
- }
- }
- return sortedAddresses;
- }
- //
- // Helper class to handle system address change events. Because multiple events can be fired as a result of
- // a single significant change (such as interface being enabled/disabled), we try to handle the event just
- // once using the below mechanism:
- // Start a timer that goes off after Timeout seconds. If get another address change event from the system
- // within this timespan, reset the timer to go off after another Timeout secs. When the timer finally fires,
- // the registered handlers are notified. This should minimize (but not completely eliminate -- think wireless
- // DHCP interface being enabled, for instance -- this could take longer) reacting multiple times for
- // a single change.
- //
- class AddressChangeHelper
- {
- public delegate void AddedChangedCallback();
- // To avoid processing multiple addr change events within this time span (5 seconds)
- public int Timeout = 5000;
- IOThreadTimer timer;
- AddedChangedCallback addressChanged;
- public AddressChangeHelper(AddedChangedCallback addressChanged)
- {
- Fx.Assert(addressChanged != null, "addressChanged expected to be non-null");
- this.addressChanged = addressChanged;
- this.timer = new IOThreadTimer(new Action<object>(FireAddressChange), null, true);
- NetworkChange.NetworkAddressChanged += OnAddressChange;
- }
- public void Unregister()
- {
- NetworkChange.NetworkAddressChanged -= OnAddressChange;
- }
- void OnAddressChange(object sender, EventArgs args)
- {
- this.timer.Set(Timeout);
- }
- // Now fire address change event to the interested parties
- void FireAddressChange(object asyncState)
- {
- this.timer.Cancel();
- this.addressChanged();
- }
- }
- }
- }
|