PeerIPHelper.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. //------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //------------------------------------------------------------
  4. namespace System.ServiceModel.Channels
  5. {
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.Net;
  9. using System.Net.NetworkInformation;
  10. using System.Net.Sockets;
  11. using System.Runtime;
  12. using System.ServiceModel;
  13. using System.Threading;
  14. // IP address helper class for multi-homing support
  15. class PeerIPHelper
  16. {
  17. public event EventHandler AddressChanged;
  18. bool isOpen;
  19. readonly IPAddress listenAddress; // To listen on a single IP address.
  20. IPAddress[] localAddresses;
  21. AddressChangeHelper addressChangeHelper;
  22. Socket ipv6Socket;
  23. object thisLock;
  24. const uint Six2FourPrefix = 0x220;
  25. const uint TeredoPrefix = 0x00000120;
  26. const uint IsatapIdentifier = 0xfe5e0000;
  27. enum AddressType
  28. {
  29. Unknown,
  30. Teredo,
  31. Isatap,
  32. Six2Four
  33. }
  34. public PeerIPHelper()
  35. {
  36. Initialize();
  37. }
  38. public PeerIPHelper(IPAddress listenAddress)
  39. {
  40. if (!(listenAddress != null))
  41. {
  42. throw Fx.AssertAndThrow("listenAddress expected to be non-null");
  43. }
  44. this.listenAddress = listenAddress;
  45. Initialize();
  46. }
  47. void Initialize()
  48. {
  49. this.localAddresses = new IPAddress[0];
  50. this.thisLock = new object();
  51. }
  52. // NOTE: This is just for suites -- to skip timeout usage
  53. internal int AddressChangeWaitTimeout
  54. {
  55. set { this.addressChangeHelper.Timeout = value; }
  56. }
  57. object ThisLock
  58. {
  59. get { return this.thisLock; }
  60. }
  61. // Compares if the specified collection matches our local cache. Return true on mismatch.
  62. public bool AddressesChanged(ReadOnlyCollection<IPAddress> addresses)
  63. {
  64. bool changed = false;
  65. lock (ThisLock)
  66. {
  67. if (addresses.Count != this.localAddresses.Length)
  68. {
  69. changed = true;
  70. }
  71. else
  72. {
  73. // If every specified addresses exist in the cache, addresses haven't changed
  74. foreach (IPAddress address in this.localAddresses)
  75. {
  76. if (!addresses.Contains(address))
  77. {
  78. changed = true;
  79. break;
  80. }
  81. }
  82. }
  83. }
  84. return changed;
  85. }
  86. // Since scope ID of IPAddress is mutable, you want to be able to clone an IP address
  87. public static IPAddress CloneAddress(IPAddress source, bool maskScopeId)
  88. {
  89. IPAddress clone = null;
  90. if (maskScopeId || V4Address(source))
  91. clone = new IPAddress(source.GetAddressBytes());
  92. else
  93. clone = new IPAddress(source.GetAddressBytes(), source.ScopeId);
  94. return clone;
  95. }
  96. // Since scope ID of IPAddress is mutable, you want to be able to clone IP addresses in an array or collection
  97. static ReadOnlyCollection<IPAddress> CloneAddresses(IPAddress[] sourceArray)
  98. {
  99. IPAddress[] cloneArray = new IPAddress[sourceArray.Length];
  100. for (int i = 0; i < sourceArray.Length; i++)
  101. {
  102. cloneArray[i] = CloneAddress(sourceArray[i], false);
  103. }
  104. return new ReadOnlyCollection<IPAddress>(cloneArray);
  105. }
  106. public static ReadOnlyCollection<IPAddress> CloneAddresses(ReadOnlyCollection<IPAddress> sourceCollection, bool maskScopeId)
  107. {
  108. IPAddress[] cloneArray = new IPAddress[sourceCollection.Count];
  109. for (int i = 0; i < sourceCollection.Count; i++)
  110. {
  111. cloneArray[i] = CloneAddress(sourceCollection[i], maskScopeId);
  112. }
  113. return new ReadOnlyCollection<IPAddress>(cloneArray);
  114. }
  115. // When listening on a specific IP address, creates an array containing just that address
  116. static IPAddress[] CreateAddressArray(IPAddress address)
  117. {
  118. IPAddress[] addressArray = new IPAddress[1];
  119. addressArray[0] = CloneAddress(address, false);
  120. return addressArray;
  121. }
  122. public void Close()
  123. {
  124. if (this.isOpen)
  125. {
  126. lock (ThisLock)
  127. {
  128. if (this.isOpen)
  129. {
  130. this.addressChangeHelper.Unregister();
  131. if (this.ipv6Socket != null)
  132. {
  133. this.ipv6Socket.Close();
  134. }
  135. this.isOpen = false;
  136. this.addressChangeHelper = null;
  137. }
  138. }
  139. }
  140. }
  141. // Retrieve the IP addresses configured on the machine.
  142. IPAddress[] GetAddresses()
  143. {
  144. List<IPAddress> addresses = new List<IPAddress>();
  145. List<IPAddress> temporaryAddresses = new List<IPAddress>();
  146. if (this.listenAddress != null) // single local address scenario?
  147. {
  148. // Check if the specified address is configured
  149. if (ValidAddress(this.listenAddress))
  150. {
  151. return (CreateAddressArray(this.listenAddress));
  152. }
  153. }
  154. // Walk the interfaces
  155. NetworkInterface[] networkIfs = NetworkInterface.GetAllNetworkInterfaces();
  156. foreach (NetworkInterface networkIf in networkIfs)
  157. {
  158. if (!ValidInterface(networkIf))
  159. {
  160. continue;
  161. }
  162. // Process each unicast address for the interface. Pick at most one IPv4 and one IPv6 address.
  163. // Add remaining eligible addresses on the list to surplus list. We can add these back to the list
  164. // being returned, if there is room.
  165. IPInterfaceProperties properties = networkIf.GetIPProperties();
  166. if (properties != null)
  167. {
  168. foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
  169. {
  170. if (NonTransientAddress(unicastAddress))
  171. {
  172. if (unicastAddress.SuffixOrigin == SuffixOrigin.Random)
  173. temporaryAddresses.Add(unicastAddress.Address);
  174. else
  175. addresses.Add(unicastAddress.Address);
  176. }
  177. }
  178. }
  179. }
  180. if (addresses.Count > 0)
  181. return ReorderAddresses(addresses);
  182. else
  183. return temporaryAddresses.ToArray();
  184. }
  185. internal static IPAddress[] ReorderAddresses(IEnumerable<IPAddress> sourceAddresses)
  186. {
  187. List<IPAddress> result = new List<IPAddress>();
  188. List<IPAddress> notAdded = new List<IPAddress>();
  189. AddressType addressType = AddressType.Unknown;
  190. IPAddress v4Address = null, v6Address = null, isatapAddress = null, teredoAddress = null, six2FourAddress = null;
  191. foreach (IPAddress address in sourceAddresses)
  192. {
  193. if (address.AddressFamily == AddressFamily.InterNetwork)
  194. {
  195. if (v4Address != null)
  196. notAdded.Add(address);
  197. else
  198. {
  199. v4Address = address;
  200. }
  201. continue;
  202. }
  203. if (address.AddressFamily != AddressFamily.InterNetworkV6)
  204. {
  205. notAdded.Add(address);
  206. continue;
  207. }
  208. if (address.IsIPv6LinkLocal || address.IsIPv6SiteLocal)
  209. {
  210. notAdded.Add(address);
  211. continue;
  212. }
  213. addressType = GetAddressType(address);
  214. switch (addressType)
  215. {
  216. case AddressType.Teredo:
  217. {
  218. if (teredoAddress == null)
  219. {
  220. teredoAddress = address;
  221. }
  222. else
  223. {
  224. notAdded.Add(address);
  225. }
  226. continue;
  227. }
  228. case AddressType.Six2Four:
  229. {
  230. if (six2FourAddress == null)
  231. {
  232. six2FourAddress = address;
  233. }
  234. else
  235. {
  236. notAdded.Add(address);
  237. }
  238. continue;
  239. }
  240. case AddressType.Isatap:
  241. {
  242. if (isatapAddress == null)
  243. {
  244. isatapAddress = address;
  245. }
  246. else
  247. {
  248. notAdded.Add(address);
  249. }
  250. continue;
  251. }
  252. default:
  253. {
  254. if (v6Address != null)
  255. notAdded.Add(address);
  256. else
  257. {
  258. v6Address = address;
  259. }
  260. continue;
  261. }
  262. }
  263. }
  264. if (six2FourAddress != null)
  265. result.Add(six2FourAddress);
  266. if (teredoAddress != null)
  267. result.Add(teredoAddress);
  268. if (isatapAddress != null)
  269. result.Add(isatapAddress);
  270. if (v6Address != null)
  271. result.Add(v6Address);
  272. if (v4Address != null)
  273. result.Add(v4Address);
  274. result.AddRange(notAdded);
  275. return result.ToArray();
  276. }
  277. static AddressType GetAddressType(IPAddress address)
  278. {
  279. AddressType result = AddressType.Unknown;
  280. byte[] bytes = address.GetAddressBytes();
  281. if (BitConverter.ToUInt16(bytes, 0) == Six2FourPrefix)
  282. result = AddressType.Six2Four;
  283. else if (BitConverter.ToUInt32(bytes, 0) == TeredoPrefix)
  284. result = AddressType.Teredo;
  285. else if (BitConverter.ToUInt32(bytes, 8) == IsatapIdentifier)
  286. result = AddressType.Isatap;
  287. return result;
  288. }
  289. // Given an EPR, replaces its URI with the specified IP address
  290. public static EndpointAddress GetIPEndpointAddress(EndpointAddress epr, IPAddress address)
  291. {
  292. EndpointAddressBuilder eprBuilder = new EndpointAddressBuilder(epr);
  293. eprBuilder.Uri = GetIPUri(epr.Uri, address);
  294. return eprBuilder.ToEndpointAddress();
  295. }
  296. // Given a hostName based URI, replaces hostName with the IP address
  297. public static Uri GetIPUri(Uri uri, IPAddress ipAddress)
  298. {
  299. UriBuilder uriBuilder = new UriBuilder(uri);
  300. if (V6Address(ipAddress) && (ipAddress.IsIPv6LinkLocal || ipAddress.IsIPv6SiteLocal))
  301. {
  302. // We make a copy of the IP address because scopeID will not be part of ToString() if set after IP address was created
  303. uriBuilder.Host = new IPAddress(ipAddress.GetAddressBytes(), ipAddress.ScopeId).ToString();
  304. }
  305. else
  306. {
  307. uriBuilder.Host = ipAddress.ToString();
  308. }
  309. return uriBuilder.Uri;
  310. }
  311. // Retrieve the currently configured addresses (cached)
  312. public ReadOnlyCollection<IPAddress> GetLocalAddresses()
  313. {
  314. lock (ThisLock)
  315. {
  316. // Return a clone of the address cache
  317. return CloneAddresses(this.localAddresses);
  318. }
  319. }
  320. // An address is valid if it is a non-transient addresss (if global, must be DNS-eligible as well)
  321. static bool NonTransientAddress(UnicastIPAddressInformation address)
  322. {
  323. return (!address.IsTransient);
  324. }
  325. public static bool V4Address(IPAddress address)
  326. {
  327. return address.AddressFamily == AddressFamily.InterNetwork;
  328. }
  329. public static bool V6Address(IPAddress address)
  330. {
  331. return address.AddressFamily == AddressFamily.InterNetworkV6;
  332. }
  333. // Returns true if the specified address is configured on the machine
  334. public static bool ValidAddress(IPAddress address)
  335. {
  336. // Walk the interfaces
  337. NetworkInterface[] networkIfs = NetworkInterface.GetAllNetworkInterfaces();
  338. foreach (NetworkInterface networkIf in networkIfs)
  339. {
  340. if (ValidInterface(networkIf))
  341. {
  342. IPInterfaceProperties properties = networkIf.GetIPProperties();
  343. if (properties != null)
  344. {
  345. foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
  346. {
  347. if (address.Equals(unicastAddress.Address))
  348. {
  349. return true;
  350. }
  351. }
  352. }
  353. }
  354. }
  355. return false;
  356. }
  357. static bool ValidInterface(NetworkInterface networkIf)
  358. {
  359. return (networkIf.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
  360. networkIf.OperationalStatus == OperationalStatus.Up);
  361. }
  362. // Process the address change notification from the system and check if the addresses
  363. // have really changed. If so, update the local cache and notify interested parties.
  364. void OnAddressChanged()
  365. {
  366. bool changed = false;
  367. IPAddress[] newAddresses = GetAddresses();
  368. lock (ThisLock)
  369. {
  370. if (AddressesChanged(Array.AsReadOnly<IPAddress>(newAddresses)))
  371. {
  372. this.localAddresses = newAddresses;
  373. changed = true;
  374. }
  375. }
  376. if (changed)
  377. {
  378. EventHandler handler = AddressChanged;
  379. if (handler != null && this.isOpen)
  380. {
  381. handler(this, EventArgs.Empty);
  382. }
  383. }
  384. }
  385. public void Open()
  386. {
  387. lock (ThisLock)
  388. {
  389. Fx.Assert(!this.isOpen, "Helper not expected to be open");
  390. // Register for addr changed event and retrieve addresses from the system
  391. this.addressChangeHelper = new AddressChangeHelper(OnAddressChanged);
  392. this.localAddresses = GetAddresses();
  393. if (Socket.OSSupportsIPv6)
  394. {
  395. this.ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.IP);
  396. }
  397. this.isOpen = true;
  398. }
  399. }
  400. // Sorts the collection of addresses using sort ioctl
  401. public ReadOnlyCollection<IPAddress> SortAddresses(ReadOnlyCollection<IPAddress> addresses)
  402. {
  403. ReadOnlyCollection<IPAddress> sortedAddresses = SocketAddressList.SortAddresses(this.ipv6Socket, listenAddress, addresses);
  404. // If listening on specific address, copy the scope ID that we're listing on for the
  405. // link and site local addresses in the sorted list (so that the connect will work)
  406. if (this.listenAddress != null)
  407. {
  408. if (this.listenAddress.IsIPv6LinkLocal)
  409. {
  410. foreach (IPAddress address in sortedAddresses)
  411. {
  412. if (address.IsIPv6LinkLocal)
  413. {
  414. address.ScopeId = this.listenAddress.ScopeId;
  415. }
  416. }
  417. }
  418. else if (this.listenAddress.IsIPv6SiteLocal)
  419. {
  420. foreach (IPAddress address in sortedAddresses)
  421. {
  422. if (address.IsIPv6SiteLocal)
  423. {
  424. address.ScopeId = this.listenAddress.ScopeId;
  425. }
  426. }
  427. }
  428. }
  429. return sortedAddresses;
  430. }
  431. //
  432. // Helper class to handle system address change events. Because multiple events can be fired as a result of
  433. // a single significant change (such as interface being enabled/disabled), we try to handle the event just
  434. // once using the below mechanism:
  435. // Start a timer that goes off after Timeout seconds. If get another address change event from the system
  436. // within this timespan, reset the timer to go off after another Timeout secs. When the timer finally fires,
  437. // the registered handlers are notified. This should minimize (but not completely eliminate -- think wireless
  438. // DHCP interface being enabled, for instance -- this could take longer) reacting multiple times for
  439. // a single change.
  440. //
  441. class AddressChangeHelper
  442. {
  443. public delegate void AddedChangedCallback();
  444. // To avoid processing multiple addr change events within this time span (5 seconds)
  445. public int Timeout = 5000;
  446. IOThreadTimer timer;
  447. AddedChangedCallback addressChanged;
  448. public AddressChangeHelper(AddedChangedCallback addressChanged)
  449. {
  450. Fx.Assert(addressChanged != null, "addressChanged expected to be non-null");
  451. this.addressChanged = addressChanged;
  452. this.timer = new IOThreadTimer(new Action<object>(FireAddressChange), null, true);
  453. NetworkChange.NetworkAddressChanged += OnAddressChange;
  454. }
  455. public void Unregister()
  456. {
  457. NetworkChange.NetworkAddressChanged -= OnAddressChange;
  458. }
  459. void OnAddressChange(object sender, EventArgs args)
  460. {
  461. this.timer.Set(Timeout);
  462. }
  463. // Now fire address change event to the interested parties
  464. void FireAddressChange(object asyncState)
  465. {
  466. this.timer.Cancel();
  467. this.addressChanged();
  468. }
  469. }
  470. }
  471. }