//****************************************************************************************************** // Transport.cs - Gbtc // // Copyright © 2010, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 06/01/2006 - Pinal C. Patel // Original version of source created. // 09/29/2008 - J. Ritchie Carroll // Converted to C#. // 08/22/2009 - Pinal C. Patel // Modified CreateEndPoint() to try parsing IP address first before doing a DNS lookup. // 09/08/2009 - Pinal C. Patel // Modified CreateSocket() to create a socket for the AddressFamily of the endpoint. // Modified CreateEndPoint() to use IPv6 if supported when no IP address is specified. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 10/30/2009 - Pinal C. Patel // Added IsIPv6IP() and IsMulticastIP() methods. // Fixed bug in CreateSocket() that was breaking one-way communication support in UDP components. // 04/29/2010 - Pinal C. Patel // Added EndpointFormatRegex constant to be used for parsing endpoint strings. // 08/18/2011 - J. Ritchie Carroll // Multiple additions and updates to accomodate easier IPv6 or IPv4 selection as well as // dual-mode socket support. // 09/21/2011 - J. Ritchie Carroll // Added Mono implementation exception regions. // 10/8/2012 - Danyelle Gilliam // Modified Header // //****************************************************************************************************** #region [ Contributor License Agreements ] //****************************************************************************************************** // // Copyright © 2011, Grid Protection Alliance. All Rights Reserved. // // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // //****************************************************************************************************** #endregion using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; namespace GSF.Communication { #region [ Enumerations ] /// /// IP stack enumeration. /// public enum IPStack { /// /// IPv6 stack. /// /// /// Requests to use IPv6 stack if possible. /// IPv6, /// /// IPv4 stack. /// /// /// Requests to use IPv4 stack if possible. /// IPv4, /// /// Default stack. /// /// /// Requests to use the default OS IP stack. /// Default } #endregion /// /// Defines helper methods related to IP socket based communications. /// public static class Transport { /// /// Specifies the lowest valid port number for a . /// public const int PortRangeLow = 0; /// /// Specifies the highest valid port number for a . /// public const int PortRangeHigh = 65535; /// /// Regular expression used to validate the format for an endpoint. /// /// /// /// Matches the following valid input:
/// - localhost:80
/// - 127.0.0.1:80
/// - [::1]:80
/// - [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80 ///
///
public const string EndpointFormatRegex = @"(?.+)\:(?\d+$)"; /// /// Creates an for the specified host name and port number. /// /// The host name or IP address to resolve. /// The port number to be associated with the address. /// Desired IP stack to use. /// An object. public static IPEndPoint CreateEndPoint(string hostNameOrAddress, int port, IPStack stack) { // Determine system's default IP stack if the default stack was requested if (stack == IPStack.Default) stack = GetDefaultIPStack(); // Make sure system can support specified stack if (stack == IPStack.IPv6 && !Socket.OSSupportsIPv6) throw new NotSupportedException(string.Format("IPv6 stack is not available for socket creation on {0}:{1}", hostNameOrAddress.ToNonNullNorWhiteSpace("localhost"), port)); #if !MONO else if (stack == IPStack.IPv4 && !Socket.OSSupportsIPv4) throw new NotSupportedException(string.Format("IPv4 stack is not available for socket creation on {0}:{1}", hostNameOrAddress.ToNonNullNorWhiteSpace("localhost"), port)); #endif if (hostNameOrAddress.IsNullOrWhiteSpace()) { // No host name or IP address was specified, use local IPs if (stack == IPStack.IPv6) return new IPEndPoint(IPAddress.IPv6Any, port); return new IPEndPoint(IPAddress.Any, port); } else { IPAddress address; bool ipStackMismatch = false; // Attempt to parse provided address name as a literal IP address if (IPAddress.TryParse(hostNameOrAddress, out address)) { // As long as desired IP stack matches format of specified IP address, return end point for address if ((stack == IPStack.IPv6 && address.AddressFamily == AddressFamily.InterNetworkV6) || (stack == IPStack.IPv4 && address.AddressFamily == AddressFamily.InterNetwork)) return new IPEndPoint(address, port); // User specified an IP address that is mismatch with the desired IP stack. If the DNS server // responds to this IP, we can attempt to see if an IP is defined for the desired IP stack, // otherwise this is an exception ipStackMismatch = true; } try { // Handle "localhost" as a special case, returning proper loopback address for the desired IP stack if (string.Compare(hostNameOrAddress, "localhost", true) == 0) return new IPEndPoint(stack == IPStack.IPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, port); // Failed to parse an IP address for the deisred stack - this may simply be that a host name was provided // so we attempt a DNS lookup. Note that exceptions will occur if DNS lookup fails. IPAddress[] dnsAddressList = Dns.GetHostEntry(hostNameOrAddress).AddressList; if (dnsAddressList.Length > 0) { // Traverse address list looking for first match on desired IP stack foreach (IPAddress dnsAddress in dnsAddressList) { if ((stack == IPStack.IPv6 && dnsAddress.AddressFamily == AddressFamily.InterNetworkV6) || (stack == IPStack.IPv4 && dnsAddress.AddressFamily == AddressFamily.InterNetwork)) return new IPEndPoint(dnsAddress, port); } // If no available matching address was found for desired IP stack, this is an IP stack mismatch ipStackMismatch = true; } throw new InvalidOperationException(string.Format("No valid {0} addresses could be found for \"{1}\"", stack, hostNameOrAddress)); } catch { // Spell out a specific error message for ip stack mismatches if (ipStackMismatch) throw new InvalidOperationException(string.Format("IP address mismatch: unable to find an {0} address for \"{1}\"", stack, hostNameOrAddress)); // Otherwise report original exception throw; } } } public static Socket CreateSocket(string address, int port, ProtocolType protocol, IPStack stack) { return CreateSocket(address, port, protocol, stack, true); } /// /// Creates a for the specified and . /// /// The local address where the will be bound. /// The port number at which the will be bound. /// One of the values. /// Desired IP stack to use. /// Determines if dual-mode socket is allowed when endpoint address is IPv6. /// An object. public static Socket CreateSocket(string address, int port, ProtocolType protocol, IPStack stack, bool allowDualStackSocket) { Socket socket = null; IPEndPoint endpoint = null; switch (protocol) { case ProtocolType.Tcp: endpoint = CreateEndPoint(address, port, stack); socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); //// If allowDualModeSocket is true and the enpoint is IPv6, we setup a dual-mode socket //// by setting the IPv6Only socket option to false //if (allowDualStackSocket && endpoint.AddressFamily == AddressFamily.InterNetworkV6 && Environment.OSVersion.Version.Major > 5) // socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); // Associate the socket with the local endpoint socket.Bind(endpoint); break; case ProtocolType.Udp: // Allow negative port number to be specified for unbound socket. if (port >= 0) { endpoint = CreateEndPoint(address, port, stack); socket = new Socket(endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); //// If allowDualModeSocket is true and the endpoint is IPv6, we setup a dual-mode socket //// by setting the IPv6Only socket option to false //if (allowDualStackSocket && endpoint.AddressFamily == AddressFamily.InterNetworkV6 && Environment.OSVersion.Version.Major > 5) // socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); socket.Bind(endpoint); } else { // Create a socket with no binding when -1 is used for port number endpoint = CreateEndPoint(address, 0, stack); socket = new Socket(endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); } break; default: throw new NotSupportedException("Communications library does not support socket creation for protocol " + protocol); } return socket; } /// /// Gets the default IP stack for this system. /// /// /// System's assumed default IP stack. /// public static IPStack GetDefaultIPStack() { try { IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName()); // IP's are normally ordered with default IP stack first if (hostEntry.AddressList.Length > 0) return (hostEntry.AddressList[0].AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6 ? IPStack.IPv6 : IPStack.IPv4); } catch { } // If default stack cannot be determined, assume IPv4 return IPStack.IPv4; } /// /// Derives the desired from the "interface" setting in the connection string key/value pairs. /// /// Connection string key/value pairs. /// Desired based on "interface" setting. /// /// The "interface" setting will be added to the if it /// doesn't exist, in this case return value will be . /// public static IPStack GetInterfaceIPStack(Dictionary connectionStringKVPairs) { string ipAddress = null; if (connectionStringKVPairs.TryGetValue("interface", out ipAddress)) return IsIPv6IP(ipAddress) ? IPStack.IPv6 : IPStack.IPv4; connectionStringKVPairs.Add("interface", string.Empty); return IPStack.Default; } /// /// Determines if the specified is an IPv6 IP. /// /// IP address to check. /// true if the is IPv6 IP; otherwise false. public static bool IsIPv6IP(string ipAddress) { if (ipAddress.IsNullOrWhiteSpace()) throw new ArgumentNullException("ipAddress"); IPAddress address; if (IPAddress.TryParse(ipAddress, out address)) return address.AddressFamily == AddressFamily.InterNetworkV6; return false; } /// /// Determines if the specified is a multicast IP. /// /// IP address to check. /// true if the is multicast IP; otherwise false. public static bool IsMulticastIP(IPAddress ipAddress) { if (ipAddress == null) throw new ArgumentNullException("ipAddress"); if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { // IP is IPv6 return ipAddress.IsIPv6Multicast; } else { // IP is IPv4 int firstOctet = int.Parse(ipAddress.ToString().Split('.')[0]); // Check first octet to see if IP is a Class D multicast IP if (firstOctet >= 224 && firstOctet <= 247) return true; return false; } } /// /// Determines whether the specified port is valid. /// /// The port number to be validated. /// True if the port number is valid. public static bool IsPortNumberValid(string port) { int portNumber; // Check to see if the specified port is a valid integer value if (int.TryParse(port, out portNumber)) { // Check to see if the port number is within the valid range if (portNumber >= PortRangeLow && portNumber <= PortRangeHigh) return true; return false; } throw new ArgumentException("Port number is not a valid number"); } /// /// Determines if the specified UDP destination is listening for data. /// /// The for the UDP destination to be checked. /// true if the UDP destination is listening for data; otherwise false. public static bool IsDestinationReachable(IPEndPoint targetIPEndPoint) { try { // Check if the target endpoint exists by sending empty data to it and waiting for a response, // if the endpoint doesn't exist then we'll receive a ConnectionReset socket exception EndPoint targetEndPoint = (EndPoint)targetIPEndPoint; using (Socket targetChecker = new Socket(targetIPEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)) { targetChecker.ReceiveTimeout = 1; targetChecker.SendTo(new byte[] { }, targetEndPoint); targetChecker.ReceiveFrom(new byte[] { }, ref targetEndPoint); } } catch (SocketException ex) { // Connection reset means that the target endpoint is unreachable if (ex.SocketErrorCode == SocketError.ConnectionReset) return false; } catch { // We'll ignore any other exceptions we might encounter and assume destination is reachable } return true; } } }