SqlClient: parse optional Protocol part of the connection string Data Source (#1684)

* Add protocol support in connection strings

* Add comments.

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
mbakalov 2021-01-04 15:16:43 -05:00 committed by GitHub
parent 4e4f95bb53
commit 2b3e2147cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 12 deletions

View File

@ -30,13 +30,35 @@ namespace OpenTelemetry.Instrumentation.SqlClient
{
/*
* Match...
* protocol[ ]:[ ]serverName
* serverName
* serverName[ ]\\[ ]instanceName
* serverName[ ]\[ ]instanceName
* serverName[ ],[ ]port
* serverName[ ]\\[ ]instanceName[ ],[ ]port
* serverName[ ]\[ ]instanceName[ ],[ ]port
*
* [ ] can be any number of white-space, SQL allows it for some reason.
*
* Optional "protocol" can be "tcp", "lpc" (shared memory), or "np" (named pipes). See:
* https://docs.microsoft.com/troubleshoot/sql/connect/use-server-name-parameter-connection-string, and
* https://docs.microsoft.com/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring?view=dotnet-plat-ext-5.0
*
* In case of named pipes the Data Source string can take form of:
* np:serverName\instanceName, or
* np:\\serverName\pipe\pipeName, or
* np:\\serverName\pipe\MSSQL$instanceName\pipeName - in this case a separate regex (see NamedPipeRegex below)
* is used to extract instanceName
*/
private static readonly Regex DataSourceRegex = new Regex("^(.*?)\\s*(?:[\\\\,]|$)\\s*(.*?)\\s*(?:,|$)\\s*(.*)$", RegexOptions.Compiled);
private static readonly Regex DataSourceRegex = new Regex("^(.*\\s*:\\s*\\\\{0,2})?(.*?)\\s*(?:[\\\\,]|$)\\s*(.*?)\\s*(?:,|$)\\s*(.*)$", RegexOptions.Compiled);
/// <summary>
/// In a Data Source string like "np:\\serverName\pipe\MSSQL$instanceName\pipeName" match the
/// "pipe\MSSQL$instanceName" segment to extract instanceName if it is available.
/// </summary>
/// <see>
/// <a href="https://docs.microsoft.com/previous-versions/sql/sql-server-2016/ms189307(v=sql.130)"/>
/// </see>
private static readonly Regex NamedPipeRegex = new Regex("pipe\\\\MSSQL\\$(.*?)\\\\", RegexOptions.Compiled);
private static readonly ConcurrentDictionary<string, SqlConnectionDetails> ConnectionDetailCache = new ConcurrentDictionary<string, SqlConnectionDetails>(StringComparer.OrdinalIgnoreCase);
// .NET Framework implementation uses SqlEventSource from which we can't reliably distinguish
@ -121,9 +143,11 @@ namespace OpenTelemetry.Instrumentation.SqlClient
{
Match match = DataSourceRegex.Match(dataSource);
string serverHostName = match.Groups[1].Value;
string serverHostName = match.Groups[2].Value;
string serverIpAddress = null;
string instanceName;
var uriHostNameType = Uri.CheckHostName(serverHostName);
if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
{
@ -131,25 +155,56 @@ namespace OpenTelemetry.Instrumentation.SqlClient
serverHostName = null;
}
string instanceName;
string port;
if (match.Groups[3].Length > 0)
string maybeProtocol = match.Groups[1].Value;
bool isNamedPipe = maybeProtocol.Length > 0 &&
maybeProtocol.StartsWith("np", StringComparison.OrdinalIgnoreCase);
if (isNamedPipe)
{
instanceName = match.Groups[2].Value;
port = match.Groups[3].Value;
string pipeName = match.Groups[3].Value;
if (pipeName.Length > 0)
{
var namedInstancePipeMatch = NamedPipeRegex.Match(pipeName);
if (namedInstancePipeMatch.Success)
{
instanceName = namedInstancePipeMatch.Groups[1].Value;
return new SqlConnectionDetails
{
ServerHostName = serverHostName,
ServerIpAddress = serverIpAddress,
InstanceName = instanceName,
Port = null,
};
}
}
return new SqlConnectionDetails
{
ServerHostName = serverHostName,
ServerIpAddress = serverIpAddress,
InstanceName = null,
Port = null,
};
}
string port;
if (match.Groups[4].Length > 0)
{
instanceName = match.Groups[3].Value;
port = match.Groups[4].Value;
if (port == "1433")
{
port = null;
}
}
else if (int.TryParse(match.Groups[2].Value, out int parsedPort))
else if (int.TryParse(match.Groups[3].Value, out int parsedPort))
{
port = parsedPort == 1433 ? null : match.Groups[2].Value;
port = parsedPort == 1433 ? null : match.Groups[3].Value;
instanceName = null;
}
else
{
instanceName = match.Groups[2].Value;
instanceName = match.Groups[3].Value;
if (string.IsNullOrEmpty(instanceName))
{

View File

@ -43,6 +43,14 @@ namespace OpenTelemetry.Instrumentation.SqlClient.Tests
[InlineData("127.0.0.1, 1818", null, "127.0.0.1", null, "1818")]
[InlineData("127.0.0.1 \\ instanceName", null, "127.0.0.1", "instanceName", null)]
[InlineData("127.0.0.1\\instanceName, 1818", null, "127.0.0.1", "instanceName", "1818")]
[InlineData("tcp:127.0.0.1\\instanceName, 1818", null, "127.0.0.1", "instanceName", "1818")]
[InlineData("tcp:localhost", "localhost", null, null, null)]
[InlineData("tcp : localhost", "localhost", null, null, null)]
[InlineData("np : localhost", "localhost", null, null, null)]
[InlineData("lpc:localhost", "localhost", null, null, null)]
[InlineData("np:\\\\localhost\\pipe\\sql\\query", "localhost", null, null, null)]
[InlineData("np : \\\\localhost\\pipe\\sql\\query", "localhost", null, null, null)]
[InlineData("np:\\\\localhost\\pipe\\MSSQL$instanceName\\sql\\query", "localhost", null, "instanceName", null)]
public void ParseDataSourceTests(
string dataSource,
string expectedServerHostName,