//-----------------------------------------------------------------------------
//
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
using Amazon.Runtime.Internal.Util;
namespace Amazon.XRay.Recorder.Core.Internal.Utils
{
///
/// Provides extension function to .
///
public static class IPEndPointExtension
{
private const string Ipv4Address = @"^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}:\d{1,5}$";
private static readonly Logger _logger = Logger.GetLogger(typeof(IPEndPointExtension));
private const char _addressDelimiter = ' '; // UDP and TCP address
private const char _addressPortDelimiter = ':';
private const string _udpKey = "udp";
private const string _tcpKey = "tcp";
///
/// Validates that is an IP.
///
/// Sting to be validated.
/// true if is an IP, false otherwise.
public static bool IsIPAddress(string input)
{
try
{
// Validate basic format of IPv4 address
return Regex.IsMatch(input, Ipv4Address, RegexOptions.None, TimeSpan.FromMinutes(1));
}
catch (RegexMatchTimeoutException e)
{
_logger.Error(e, "Failed to determine if IP because of match timeout. ({0})", input);
return false;
}
}
///
/// Tries to parse a string to .
///
/// The input string. Must be able to be validated by .
/// The parsed IPEndPoint
/// true if converted successfully; otherwise, false.
public static bool TryParse(string input, out IPEndPoint endPoint)
{
endPoint = null;
string[] ep = input.Split(':');
if (ep.Length != 2)
{
_logger.InfoFormat("Failed to parse IPEndpoint because input has not exactly two parts splitting by ':'. ({0})", input);
return false;
}
// Validate IP address is in valid range
if (!IPAddress.TryParse(ep[0], out IPAddress ip))
{
_logger.InfoFormat("Failed to parse IPEndPoint because ip address is invalid. ({0})", input);
return false;
}
if (!int.TryParse(ep[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out int port))
{
_logger.InfoFormat("Failed to parse IPEndPoint because port is invalid. ({0})", input);
return false;
}
try
{
// Validate port number is in valid range
endPoint = new IPEndPoint(ip, port);
return true;
}
catch (ArgumentOutOfRangeException e)
{
_logger.Error(e, "Failed to parse IPEndPoint because argument to IPEndPoint is invalid. ({0}", input);
return false;
}
}
///
/// Tries to parse a string to
///
/// The input string.
/// The parsed HostEndPoint
/// true if converted successfully; otherwise, false.
public static bool TryParse(string input, out HostEndPoint hostEndpoint)
{
var entries = input.Split(':');
if (entries.Length != 2)
{
_logger.InfoFormat("Failed to parse HostEndPoint because input has not exactly two parts splitting by ':'. ({0})", input);
hostEndpoint = null;
return false;
}
if (!int.TryParse(entries[1], out var port))
{
_logger.InfoFormat("Failed to parse HostEndPoint because port is invalid. ({0})", input);
hostEndpoint = null;
return false;
}
if (port < 0 || 65535 < port)
{
_logger.InfoFormat("Failed to parse HostEndPoint because port is out of range. ({0})", input);
hostEndpoint = null;
return false;
}
/*
* Almost anything can be a hostname which makes further validation here hard.
* Accept any string in entries[0] and let it fail in the DNS lookup instead.
*/
hostEndpoint = new HostEndPoint(entries[0], port);
_logger.InfoFormat("Using custom daemon address: {0}:{1}", hostEndpoint.Host, hostEndpoint.Port);
return true;
}
///
/// Tries to parse a string to
///
/// The input string.
/// The parsed EndPoint
/// true if converted successfully; otherwise, false.
public static bool TryParse(string input, out EndPoint endpoint)
{
endpoint = null;
if (IsIPAddress(input))
{
_logger.DebugFormat("Determined that {0} is an IP.", input);
if (TryParse(input, out IPEndPoint ipEndPoint))
{
endpoint = EndPoint.Of(ipEndPoint);
return true;
}
}
else
{
_logger.DebugFormat("Determined that {0} is not an IP address, will try to parse it as a hostname.", input);
if (TryParse(input, out HostEndPoint hostEndPoint))
{
endpoint = EndPoint.Of(hostEndPoint);
return true;
}
}
return false;
}
///
/// Tries to parse a string to .
///
/// The input string.
/// The parsed instance.
///
public static bool TryParse(string daemonAddress, out DaemonConfig daemonEndPoint)
{
daemonEndPoint = null;
if (string.IsNullOrEmpty(daemonAddress))
{
return false;
}
try
{
string[] ep = daemonAddress.Split(_addressDelimiter);
return TryParseDaemonAddress(ep, out daemonEndPoint);
}
catch (Exception e)
{
_logger.Error(e, "Invalid daemon address. ({0})", daemonAddress);
return false;
}
}
private static bool TryParseDaemonAddress(string[] daemonAddress, out DaemonConfig endPoint)
{
endPoint = null;
if (daemonAddress.Length == 1)
{
return ParseSingleForm(daemonAddress, out endPoint);
}
else if (daemonAddress.Length == 2)
{
return ParseDoubleForm(daemonAddress, out endPoint);
}
return false;
}
private static bool ParseSingleForm(string[] daemonAddress, out DaemonConfig endPoint)
{
endPoint = new DaemonConfig();
if (TryParse(daemonAddress[0], out EndPoint udpEndpoint))
{
endPoint._udpEndpoint = udpEndpoint;
endPoint._tcpEndpoint = udpEndpoint;
_logger.InfoFormat("Using custom daemon address for UDP and TCP: {0}:{1}", endPoint.UDPEndpoint.Address.ToString(), endPoint.UDPEndpoint.Port);
return true;
}
else
{
return false;
}
}
private static bool ParseDoubleForm(string[] daemonAddress, out DaemonConfig endPoint)
{
endPoint = new DaemonConfig();
IDictionary addressMap = new Dictionary();
string[] address1 = daemonAddress[0].Split(_addressPortDelimiter); // tcp::2000 udp::2001
string[] address2 = daemonAddress[1].Split(_addressPortDelimiter);
addressMap[address1[0]] = address1[1] + _addressPortDelimiter + address1[2];
addressMap[address2[0]] = address2[1] + _addressPortDelimiter + address2[2];
string udpAddress = addressMap[_udpKey];
string tcpAddress = addressMap[_tcpKey];
if (TryParse(udpAddress, out EndPoint udpEndpoint) && TryParse(tcpAddress, out EndPoint tcpEndpoint))
{
endPoint._udpEndpoint = udpEndpoint;
endPoint._tcpEndpoint = tcpEndpoint;
_logger.InfoFormat("Using custom daemon address for UDP {0}:{1} and TCP {2}:{3}", endPoint.UDPEndpoint.Address.ToString(), endPoint.UDPEndpoint.Port, endPoint.TCPEndpoint.Address.ToString(), endPoint.TCPEndpoint.Port);
return true;
}
return false;
}
}
}