//******************************************************************************************************
// 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;
}
}
}