Added some IP address improvements
This commit is contained in:
@@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `ContainsAny()` and `ContainsAll()` for strings
|
||||
- `ASPNETCORE_APPL_PROXY` environment variable can be used on proxy configuration
|
||||
- Support for .NET 10.0 LTS
|
||||
- `ToCleanString()` for `IPAddresses` mapped IPv4 to IPv6
|
||||
- `ToSubnetMask()` to generated IPv4 subnet masks from CIDR notation
|
||||
- `ObfuscateIpAddress()` to mask sensidive parts of an IP address (e.g. for listing in logs)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace System.Net
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IPAddress"/>es.
|
||||
@@ -48,5 +51,90 @@
|
||||
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="IPAddress"/> to a clean string representation.
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">The <see cref="IPAddress"/> to convert.</param>
|
||||
/// <returns>A clean string representation of the <see cref="IPAddress"/>.</returns>
|
||||
public static string ToCleanString(this IPAddress ipAddress)
|
||||
{
|
||||
string address = ipAddress.ToString();
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
address = ipAddress.MapToIPv4().ToString();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a prefix length to a subnet mask (IPv4 only).
|
||||
/// </summary>
|
||||
/// <param name="prefixLength">The prefix length.</param>
|
||||
public static IPAddress ToSubnetMask(this int prefixLength)
|
||||
{
|
||||
if (prefixLength < 0 || prefixLength > 32)
|
||||
throw new ArgumentOutOfRangeException(nameof(prefixLength));
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
for (int i = 0; i < prefixLength; i++)
|
||||
{
|
||||
int byteIndex = i / 8;
|
||||
int bitIndex = 7 - (i % 8);
|
||||
bytes[byteIndex] |= (byte)(1 << bitIndex);
|
||||
}
|
||||
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obfuscates an IP address by masking portions of it to conceal sensitive information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method replaces parts of the IP address with a masking character to help prevent exposure of
|
||||
/// full address details in logs or user interfaces. For IPv4-mapped IPv6 addresses, the address is first converted to
|
||||
/// its IPv4 equivalent before obfuscation. Loopback addresses (<c>127.0.0.1</c> and <c>::1</c>) are not obfuscated.
|
||||
/// </remarks>
|
||||
/// <param name="address">The IP address to obfuscate.</param>
|
||||
/// <param name="obfuscation">The char (or string) to obfuscate with.</param>
|
||||
/// <returns>
|
||||
/// A string representation of the obfuscated IP address. For loopback addresses, the original address is returned unmodified.
|
||||
/// </returns>
|
||||
public static string ObfuscateIpAddress(this IPAddress address, string obfuscation = "•")
|
||||
{
|
||||
string[] addressParts;
|
||||
string delimiter;
|
||||
switch (address.AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
if (address.ToString() == "127.0.0.1")
|
||||
return address.ToString();
|
||||
|
||||
delimiter = ".";
|
||||
addressParts = address.ToString().Split(delimiter.First());
|
||||
break;
|
||||
|
||||
case AddressFamily.InterNetworkV6:
|
||||
if (address.IsIPv4MappedToIPv6)
|
||||
return address.MapToIPv4().ObfuscateIpAddress(obfuscation);
|
||||
|
||||
if (address.ToString() == "::1")
|
||||
return address.ToString();
|
||||
|
||||
delimiter = ":";
|
||||
addressParts = address.ToString().Split(delimiter.First());
|
||||
break;
|
||||
|
||||
default:
|
||||
return address.ToString();
|
||||
}
|
||||
|
||||
for (int i = 0; i < addressParts.Length; i++)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
addressParts[i] = obfuscation;
|
||||
}
|
||||
|
||||
return string.Join(delimiter, addressParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace AMWD.Common.Tests.Extensions
|
||||
{
|
||||
@@ -82,5 +83,135 @@ namespace AMWD.Common.Tests.Extensions
|
||||
// assert
|
||||
Assert.AreEqual("255.255.255.255", decremented.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCleanMappedIPAddressString()
|
||||
{
|
||||
// arrange
|
||||
var mapped = IPAddress.Parse("::ffff:192.168.0.1");
|
||||
|
||||
// act
|
||||
string cleaned = mapped.ToCleanString();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("192.168.0.1", cleaned);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnCleanIPv6String()
|
||||
{
|
||||
// arrange
|
||||
var ipv6 = IPAddress.Parse("2001:db8::1");
|
||||
|
||||
// act / assert
|
||||
Assert.AreEqual("2001:db8::1", ipv6.ToCleanString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(0, "0.0.0.0")]
|
||||
[DataRow(32, "255.255.255.255")]
|
||||
[DataRow(8, "255.0.0.0")]
|
||||
[DataRow(12, "255.240.0.0")]
|
||||
[DataRow(24, "255.255.255.0")]
|
||||
[DataRow(23, "255.255.254.0")]
|
||||
public void ShouldReturnSubnetMask(int cidr, string expected)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
string mask = cidr.ToSubnetMask().ToString();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(expected, mask);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(-1)]
|
||||
[DataRow(33)]
|
||||
public void ShouldThrowArgumentOutOfRangeExceptionForInvalidSubnetMask(int cidr)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act & assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => cidr.ToSubnetMask());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldObfuscateIPv4()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("192.168.0.100");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("•.168.•.100", ob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldObfuscateIpAddressWithCustomString()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("10.0.0.5");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress("X");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("X.0.X.5", ob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotObfuscateLocalhostIPv4()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("127.0.0.1");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("127.0.0.1", ob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldObfuscatedMappedIP()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("::ffff:10.0.0.5");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress("X");
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("X.0.X.5", ob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldObfuscateIPv6()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("•:db8:•::•:370:•", ob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotObfuscateLocalhostIPv6()
|
||||
{
|
||||
// arrange
|
||||
var ip = IPAddress.Parse("::1");
|
||||
|
||||
// act
|
||||
string ob = ip.ObfuscateIpAddress();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual("::1", ob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user