Commit 42f95161 authored by Nick Craver's avatar Nick Craver Committed by Marc Gravell

Initial IPv6 connection support (#893)

* Initial IPv6 connection support

TL;DR: We previously did IP:Port parsing based on splitting at the semicolon...that's pretty hostile for IPv6 and breaks everything. This adopts better parsing from Microsoft.AspNetCore since it doesn't exist in the BCL. The BCL/API conversation is started here: https://github.com/dotnet/corefx/issues/23463

A quick way to test this across all tests is via TestConfig, for example:
{
  "MasterServer": "[::1]",
  "SlaveServer": "[::1]",
  "SecureServer": "[::1]"
}

Note: there's still an Unspecified == AddressFamily.InterNetwork in SocketManager which may be affecting DNS endpoints that are v6...need to look at that next. This change allows actually connecting to an IPv6 endpoint via IP though, that's step 1.

* Move BasicStringGetPerf to non-parallel

* Fix IPv4 assumptions

The socket world has changed - unspecified and dual mode sockets now handle IPv6 correctly. We can just straight up remove the assumption of IPv4 anywhere in code now.
parent 610c0c46
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace StackExchange.Redis.Tests
{
public class FormatTests : TestBase
{
public FormatTests(ITestOutputHelper output) : base(output) { }
public static IEnumerable<object[]> EndpointData()
{
// DNS
yield return new object[] { "localhost", new DnsEndPoint("localhost", 0) };
yield return new object[] { "localhost:6390", new DnsEndPoint("localhost", 6390) };
yield return new object[] { "bob.the.builder.com", new DnsEndPoint("bob.the.builder.com", 0) };
yield return new object[] { "bob.the.builder.com:6390", new DnsEndPoint("bob.the.builder.com", 6390) };
// IPv4
yield return new object[] { "0.0.0.0", new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0) };
yield return new object[] { "127.0.0.1", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0) };
yield return new object[] { "127.1", new IPEndPoint(IPAddress.Parse("127.1"), 0) };
yield return new object[] { "127.1:6389", new IPEndPoint(IPAddress.Parse("127.1"), 6389) };
yield return new object[] { "127.0.0.1:6389", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6389) };
yield return new object[] { "127.0.0.1:1", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1) };
yield return new object[] { "127.0.0.1:2", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2) };
yield return new object[] { "10.10.9.18:2", new IPEndPoint(IPAddress.Parse("10.10.9.18"), 2) };
// IPv6
yield return new object[] { "::1", new IPEndPoint(IPAddress.Parse("::1"), 0) };
yield return new object[] { "::1:6379", new IPEndPoint(IPAddress.Parse("::0.1.99.121"), 0) }; // remember your brackets!
yield return new object[] { "[::1]:6379", new IPEndPoint(IPAddress.Parse("::1"), 6379) };
yield return new object[] { "[::1]", new IPEndPoint(IPAddress.Parse("::1"), 0) };
yield return new object[] { "[::1]:1000", new IPEndPoint(IPAddress.Parse("::1"), 1000) };
yield return new object[] { "[2001:db7:85a3:8d2:1319:8a2e:370:7348]", new IPEndPoint(IPAddress.Parse("2001:db7:85a3:8d2:1319:8a2e:370:7348"), 0) };
yield return new object[] { "[2001:db7:85a3:8d2:1319:8a2e:370:7348]:1000", new IPEndPoint(IPAddress.Parse("2001:db7:85a3:8d2:1319:8a2e:370:7348"), 1000) };
}
[Theory]
[MemberData(nameof(EndpointData))]
public void ParseEndPoint(string data, EndPoint expected)
{
var result = Format.TryParseEndPoint(data);
Assert.Equal(expected, result);
}
}
}
......@@ -6,6 +6,7 @@
namespace StackExchange.Redis.Tests
{
[Collection(NonParallelCollection.Name)]
public class Performance : TestBase
{
public Performance(ITestOutputHelper output) : base(output) { }
......
......@@ -193,27 +193,73 @@ private static bool CaseInsensitiveASCIIEqual(string xLowerCase, ReadOnlySpan<by
return true;
}
internal static EndPoint TryParseEndPoint(string endpoint)
internal static EndPoint TryParseEndPoint(string addressWithPort)
{
if (string.IsNullOrWhiteSpace(endpoint)) return null;
string host;
int port;
int i = endpoint.IndexOf(':');
if (i < 0)
// Adapted from IPEndPointParser in Microsoft.AspNetCore
// Link: https://github.com/aspnet/BasicMiddleware/blob/f320511b63da35571e890d53f3906c7761cd00a1/src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs#L8
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
string addressPart = null;
string portPart = null;
if (string.IsNullOrEmpty(addressWithPort)) return null;
var lastColonIndex = addressWithPort.LastIndexOf(':');
if (lastColonIndex > 0)
{
host = endpoint;
port = 0;
// IPv4 with port or IPv6
var closingIndex = addressWithPort.LastIndexOf(']');
if (closingIndex > 0)
{
// IPv6 with brackets
addressPart = addressWithPort.Substring(1, closingIndex - 1);
if (closingIndex < lastColonIndex)
{
// IPv6 with port [::1]:80
portPart = addressWithPort.Substring(lastColonIndex + 1);
}
}
else
{
// IPv6 without port or IPv4
var firstColonIndex = addressWithPort.IndexOf(':');
if (firstColonIndex != lastColonIndex)
{
// IPv6 ::1
addressPart = addressWithPort;
}
else
{
// IPv4 with port 127.0.0.1:123
addressPart = addressWithPort.Substring(0, firstColonIndex);
portPart = addressWithPort.Substring(firstColonIndex + 1);
}
}
}
else
{
host = endpoint.Substring(0, i);
var portAsString = endpoint.Substring(i + 1);
if (string.IsNullOrEmpty(portAsString)) return null;
if (!TryParseInt32(portAsString, out port)) return null;
// IPv4 without port
addressPart = addressWithPort;
}
int? port = 0;
if (portPart != null)
{
if (int.TryParse(portPart, out var portVal))
{
port = portVal;
}
else
{
// Invalid port, return
return null;
}
}
if (string.IsNullOrWhiteSpace(host)) return null;
return ParseEndPoint(host, port);
if (IPAddress.TryParse(addressPart, out IPAddress address))
{
return new IPEndPoint(address, port ?? 0);
}
return new DnsEndPoint(addressPart, port ?? 0);
}
}
}
......@@ -124,9 +124,8 @@ private void Dispose(bool disposing)
internal static Socket CreateSocket(EndPoint endpoint)
{
var addressFamily = endpoint.AddressFamily == AddressFamily.Unspecified ? AddressFamily.InterNetwork : endpoint.AddressFamily;
var protocolType = addressFamily == AddressFamily.Unix ? ProtocolType.Unspecified : ProtocolType.Tcp;
var socket = new Socket(addressFamily, SocketType.Stream, protocolType);
var protocolType = endpoint.AddressFamily == AddressFamily.Unix ? ProtocolType.Unspecified : ProtocolType.Tcp;
var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, protocolType);
SocketConnection.SetRecommendedClientOptions(socket);
return socket;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment