Skip to content

Add support for resolving IPv4-mapped IPv6 addresses #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CancellationToken cancellationToken
if (!FilterAddressTableEntryForAddressResolution(entry))
return false;

if (!entry.Equals(ipAddress))
if (!entry.Equals(ipAddress, shouldConsiderIPv4MappedIPv6Address: ShouldResolveIPv4MappedIPv6Address))
return false;

// ignore the entry that is marked as invalidated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ timeStampForFullScan is null || // not performed yet
private const int DefaultParallelCountForRefreshInvalidatedAddresses = 3;
private SemaphoreSlim partialScanSemaphore;

/// <summary>
/// Gets or sets a value indicating whether the address resolution to be aware or not to be aware that
/// the IP address is an IPv4-mapped IPv6 address when resolving IP address to MAC address.
/// </summary>
/// <seealso cref="MacAddressResolverBase.ResolveIPAddressToMacAddressAsync(IPAddress, CancellationToken)" />
/// <seealso cref="AddressTableEntry.Equals(IPAddress?, bool)" />
public bool ShouldResolveIPv4MappedIPv6Address { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="MacAddressResolver"/> class.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,31 @@ public bool Equals(AddressTableEntry other)
=> DefaultEqualityComparer.Equals(this, other);

public bool Equals(IPAddress? other)
=> Equals(other, shouldConsiderIPv4MappedIPv6Address: false);

/// <summary>
/// Indicates whether the member <see cref="AddressTableEntry.IPAddress"/> is equal to the IP address passed to the parameter.
/// </summary>
/// <param name="other">The <see cref="System.Net.IPAddress"/> to be compared with the <see cref="AddressTableEntry.IPAddress"/>.</param>
/// <param name="shouldConsiderIPv4MappedIPv6Address">
/// Specifies whether or not to be aware that the IP address to be an IPv4-mapped IPv6 address or not when comparing IP addresses.
/// </param>
/// <returns>
/// <see langword="true"/> if the <see cref="AddressTableEntry.IPAddress"/> is equal to the <paramref name="other"/> parameter; otherwise, <see langword="false"/>.
/// </returns>
public bool Equals(IPAddress? other, bool shouldConsiderIPv4MappedIPv6Address)
{
if (IPAddress is null)
return other is null;
if (other is null)
return IPAddress is null;

if (shouldConsiderIPv4MappedIPv6Address) {
if (other.IsIPv4MappedToIPv6 && other.MapToIPv4().Equals(IPAddress))
return true;
if (IPAddress is not null && IPAddress.IsIPv4MappedToIPv6 && other.Equals(IPAddress.MapToIPv4()))
return true;
}

return IPAddress.Equals(other);
return other.Equals(IPAddress);
}

public bool Equals(PhysicalAddress? other)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -628,4 +628,33 @@ await resolver.ResolveMacAddressToIPAddressAsync(entryToResolve.PhysicalAddress!
Is.EqualTo(expectedEntry.IPAddress)
);
}

[Test]
public async Task ResolveIPAddressToMacAddressAsync_ShouldResolveIPv4MappedIPv6Address(
[Values("127.0.0.1", "192.0.2.0")] string ipv4AddressString,
[Values] bool shouldResolveIPv4MappedIPv6Address
)
{
using var resolver = CreateNullNetworkScannerMacAddressResolver(
new StaticAddressTable([
new(IPAddress.Parse(ipv4AddressString), PhysicalAddress.Parse("00-00-5E-00-53-00"), false, AddressTableEntryState.Reachable, "wlan0"),
])
);

resolver.ShouldResolveIPv4MappedIPv6Address = shouldResolveIPv4MappedIPv6Address;

Assert.That(
await resolver.ResolveIPAddressToMacAddressAsync(IPAddress.Parse(ipv4AddressString)),
Is.Not.Null
);

var ipv4MappedIPv6AddressString = $"::ffff:{ipv4AddressString}";

Assert.That(
await resolver.ResolveIPAddressToMacAddressAsync(IPAddress.Parse(ipv4MappedIPv6AddressString)),
shouldResolveIPv4MappedIPv6Address
? Is.Not.Null
: Is.Null
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,94 @@ private static System.Collections.IEnumerable YieldTestCases_Equals_OfIPAddress(
public void Equals_OfIPAddress(AddressTableEntry entry, IPAddress? ipAddress, bool expected)
=> Assert.That(entry.Equals(ipAddress), Is.EqualTo(expected));

private static System.Collections.IEnumerable YieldTestCases_Equals_OfIPAddress_ShouldConsiderIPv4MappedIPv6Address()
{
var ipv4AddressEntry = new AddressTableEntry(
ipAddress: TestIPAddress,
physicalAddress: null,
isPermanent: true,
state: AddressTableEntryState.None,
interfaceId: null
);
var ipv4MappedIPv6AddressEntry = new AddressTableEntry(
ipAddress: TestIPAddress.MapToIPv6(),
physicalAddress: null,
isPermanent: true,
state: AddressTableEntryState.None,
interfaceId: null
);
IPAddress? nullIPAddress = null;

foreach (var shouldConsiderIPv4MappedIPv6Address in new[] { true, false }) {
yield return new object?[] {
ipv4AddressEntry,
nullIPAddress,
shouldConsiderIPv4MappedIPv6Address,
false
};
yield return new object?[] {
ipv4AddressEntry,
ipv4AddressEntry.IPAddress,
shouldConsiderIPv4MappedIPv6Address,
true
};
yield return new object?[] {
ipv4AddressEntry,
ipv4AddressEntry.IPAddress!.MapToIPv6(), // compare with IPv4-mapped IPv6 address
shouldConsiderIPv4MappedIPv6Address,
shouldConsiderIPv4MappedIPv6Address
};
}

foreach (var shouldConsiderIPv4MappedIPv6Address in new[] { true, false }) {
yield return new object?[] {
ipv4MappedIPv6AddressEntry,
nullIPAddress,
shouldConsiderIPv4MappedIPv6Address,
false
};
yield return new object?[] {
ipv4MappedIPv6AddressEntry,
ipv4MappedIPv6AddressEntry.IPAddress,
shouldConsiderIPv4MappedIPv6Address,
true
};
yield return new object?[] {
ipv4MappedIPv6AddressEntry,
ipv4MappedIPv6AddressEntry.IPAddress!.MapToIPv4(), // compare with IPv4-mapped IPv6 address
shouldConsiderIPv4MappedIPv6Address,
shouldConsiderIPv4MappedIPv6Address
};
}

var nullIPAddressEntry = default(AddressTableEntry);

foreach (var shouldConsiderIPv4MappedIPv6Address in new[] { true, false }) {
yield return new object?[] {
nullIPAddressEntry,
nullIPAddress,
shouldConsiderIPv4MappedIPv6Address,
true
};
yield return new object?[] {
nullIPAddressEntry,
TestIPAddress,
shouldConsiderIPv4MappedIPv6Address,
false
};
yield return new object?[] {
nullIPAddressEntry,
TestIPAddress.MapToIPv6(),
shouldConsiderIPv4MappedIPv6Address,
false
};
}
}

[TestCaseSource(nameof(YieldTestCases_Equals_OfIPAddress_ShouldConsiderIPv4MappedIPv6Address))]
public void Equals_OfIPAddress_ShouldConsiderIPv4MappedIPv6Address(AddressTableEntry entry, IPAddress? ipAddress, bool shouldConsiderIPv4MappedIPv6Address, bool expected)
=> Assert.That(entry.Equals(ipAddress, shouldConsiderIPv4MappedIPv6Address), Is.EqualTo(expected));

[Test]
public void Equals_PhysicalAddressNull()
{
Expand Down