-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[API Proposal]: Add a type to represent IP networks based on CIDR notation #79946
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationWe've been using a custom IPAddressRange in our project, but this seems like a general-purpose concern. Looking across internal code bases at Microsoft, we've found dozens of similar implementations of this type, reinforcing the notion that this is general-purpose and should be in the framework. API Proposalnamespace System.Net;
/// <summary>
/// Represents an IP address range using CIDR notation.
/// </summary>
/// <remarks>The range contains addresses starting with <see cref="BaseAddress"/>,
/// and including every combination of bit values for the rightmost variable part after the subnet mask.
/// I.e. "13.73.248.16/29" will contain 2^3 addresses. 32(IPv4 bits) - 29(subnet mask length) = 3(remaining variable bits).
/// </remarks>
public readonly struct IPAddressRange : IEquatable<IPAddressRange>
{
/// <summary>
/// Gets the network prefix in CIDR notation.
/// </summary>
/// <remarks><see href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation"/>.</remarks>
public IPAddress BaseAddress { get; }
/// <summary>
/// Gets the subnet mask length in bits.
/// </summary>
/// <remarks><see href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation"/>.</remarks>
public byte MaskLength { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IPAddressRange"/> struct.
/// </summary>
/// <param name="baseAddress">Network prefix in CIDR notation.</param>
/// <param name="maskLength">Subnet mask length in bits.</param>
public IPAddressRange(IPAddress baseAddress, byte maskLength);
/// <summary>
/// Parses string IP range to <see cref="IPAddressRange"/> object.
/// </summary>
/// <param name="rangeString">IP range in CIDR notation.</param>
/// <returns>Parsed IP range.</returns>
/// <exception cref="FormatException">Thrown if passed value is not in correct format.</exception>
public static IPAddressRange Parse(string rangeString);
/// <summary>
/// Parses string IP range to <see cref="IPAddressRange"/> object.
/// </summary>
/// <param name="rangeSpan">IP range in CIDR notation.</param>
/// <returns>Parsed IP range.</returns>
/// <exception cref="FormatException">Thrown if passed value is not in correct format.</exception>
public static IPAddressRange Parse(ReadOnlySpan<char> rangeSpan, IFormatProvider? provider);
/// <summary>
/// Parses string IP range to <see cref="IPAddressRange"/> object.
/// </summary>
/// <param name="rangeString">IP range in CIDR notation.</param>
/// <param name="range">Parsed IP range.</param>
/// <returns><see langword="true"/> if parsing was successful, otherwise <see langword="false"/>.</returns>
public static bool TryParse(string rangeString, IFormatProvider? provider, out IPAddressRange range);
/// <summary>
/// Parses string IP range to <see cref="IPAddressRange"/> object.
/// </summary>
/// <param name="rangeSpan">IP range in CIDR notation.</param>
/// <param name="range">Parsed IP range.</param>
/// <returns><see langword="true"/> if parsing was successful, otherwise <see langword="false"/>.</returns>
public static bool TryParse(ReadOnlySpan<char> rangeSpan, out IPAddressRange range);
/// <summary>
/// Tests whether a particular address falls within the current range.
/// </summary>
/// <param name="ip">The IP address to test.</param>
/// <returns><see langword="true"> if the given address is covered by the range; otherwise <see langword="false" />.</returns>
public bool Contains(IPAddress ip);
/// <summary>
/// Checks if objects are equal.
/// </summary>
/// <param name="obj">Object to check.</param>
/// <returns>True if equal, false otherwise.</returns>
public override bool Equals(object? obj);
/// <summary>
/// Checks if objects are equal.
/// </summary>
/// <param name="other">Object to check.</param>
/// <returns>True if equal, false otherwise.</returns>
public bool Equals(IPAddressRange other);
/// <summary>
/// Gets hash code.
/// </summary>
/// <returns>Hash code.</returns>
public override int GetHashCode();
/// <summary>
/// Produces a CIDR representation of this range.
/// </summary>
public override string ToString();
/// <summary>
/// Compares two ranges.
/// </summary>
/// <param name="left">Left element to compare.</param>
/// <param name="right">Right element to compare.</param>
/// <returns><see langword="true" /> when equal, <see langword="false" /> otherwise.</returns>
public static bool operator ==(IPAddressRange left, IPAddressRange right);
/// <summary>
/// Compares two ranges.
/// </summary>
/// <param name="left">Left argument of the comparison.</param>
/// <param name="right">Right argument of the comparison.</param>
/// <returns><see langword="true" /> when not equal, <see langword="false" /> otherwise.</returns>
public static bool operator !=(IPAddressRange left, IPAddressRange right);
} API UsageWe're using this for parsing and logging, so the parsing and tostring functionality are particularly interesting. Alternative DesignsThis type could potentially implement RisksNo response
|
Should it have a |
When I saw IPAddress range, I assumed a range of address not only by a bitmask. |
This looks like a duplicate of #64033 and #36428, which were closed as being too niche. Note that ASP.NET Core does have the |
IPNetwork2 perfectly serves to that purpose. |
Definitely in the eye of the beholder :-) As I said, we've found dozens of implementations of this within Microsoft's various code bases.
Missed that one. From a composition perspective, it's a bit too high in the stack for what we'd like I think. But it does show that the concept is a useful one. |
Definitely could, and it could also implement ISpanFormattable/ISpanParsable. Note that IPAddress could also use these. |
And how about we call it |
IMO in order to make this an actual IP address range maybe we should make this more flexible in terms of the First address and the Last address in the range, i.e. cover the cases of custom address ranges that cannot be covered by CIDR notation (subset of addresses in a subnet). So we can have both |
@mddddb Then the MaskLength property would need to be removed or made optional or something, since a range might not in fact be just a mask thing. |
@geeknoid Yep, I am thinking either a nullable prop or a method with nullable return type |
Triage: Prioritizing it for 8.0 as the number of ad-hoc implementations clearly shows demand. |
This doesn't sound like a feature that would be highly used. Instead I would just go with a class name that is less confusing, eg. |
Triage:
|
@antonfirsov What's the motivation for not implementing ISpanFormattable? |
@geeknoid we would also need to implement it on |
I realized now that since |
readonly struct does not guarantee deep immutability. We generally prefer structs for things that are small and not extensible. We do have a couple scenarios which maintain a bunch of these things in collections, so having those as structs is helpful there. Not a big deal either way though. |
IPAddress already exposes both TryFormat and TryParse. Whether or not it also inherits the interfaces doesn't impact whether IPSubnet can implement TryFormat/TryParse nor whether it implements the interfaces. We should design the type in this issue the way we think it should be, inclusive of the interfaces if we think it should implement them; it'd be fine to file a separate issue to follow-up on adding the interfaces to IPAddress if we think that's desirable. |
@geeknoid are you interested to port your implementation to dotnet/runtime? If yes, any ETA? |
@antonfirsov @karelz @geeknoid A draft PR has been created, there are couple of questions I left there. There is still some polishing and documentation left to be done, although I am not sure what the preference is on that part - should I continue with the changes? Or will someone take it from here? Anyway, some feedback is appreciated before the PR gets published |
@mddddb thanks! I will take a look tomorrow. It would be nice if you can react to basic feedback, but I'm happy to take over more complicated stuff, eg. the changes needed if we decide to ship it as a NuGet package. |
@antonfirsov I am happy to make the necessary changes, as long as we don't have burning deadlines. This is an interesting exercise and a new experience to me, I just have to juggle this together with other tasks that I have🙂 |
We should consider producing a netstandard2.0-consumable OOB package for this type similarly to #65577 / #79120. However, we would need to omit namespace System.Net;
public readonly struct IPNetwork : IEquatable<IPNetwork>
#if NET8_0_OR_GREATER
ISpanFormattable, ISpanParsable<IPNetwork>
#else
IFormattable
#endif
{
public IPAddress BaseAddress { get; }
public int PrefixLength { get; }
public IPNetwork(IPAddress baseAddress, int prefixLength);
public bool Contains(IPAddress address);
#if NET8_0_OR_GREATER
// ISpanParsable (explicit)
static IPNetwork ISpanParsable<IPNetwork>.Parse(string s, IFormatProvider? provider = null);
static bool ISpanParsable<IPNetwork>.TryParse(string s, IFormatProvider? provider, out IPNetwork result);
static IPNetwork ISpanParsable<IPNetwork>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider = null);
static bool ISpanParsable<IPNetwork>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out IPNetwork result);
#endif
// Parsing - Public methods
public static IPNetwork Parse(string s);
public static IPNetwork Parse(ReadOnlySpan<char> s);
public static bool TryParse(string s, out IPNetwork result);
public static bool TryParse(ReadOnlySpan<char> s, out IPNetwork result);
// IFormattable (explicit)
string IFormattable<IPNetwork>.ToString(string? format, IFormatProvider? provider = null);
#if NET8_0_OR_GREATER
// ISpanFormattable (explicit)
bool ISpanFormattable<IPNetwork>.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
// IPAddress has on TryFormat(Span,int) under netstandard2.0, so we are excluding this overload too
public bool TryFormat(Span<char> destination, out int charsWritten);
#endif
// Formatting (public)
public override string ToString();
// Equality
public bool Equals(IPNetwork value);
public static bool operator ==(IPNetwork left, IPNetwork right);
public static bool operator !=(IPNetwork left, IPNetwork right);
} |
I think that my initial recommendation includes the concepts discussed here such as where to use struct versus class, what are the "core" behaviors of the class and what are extensions. The framework has changed much since my initial reference; am happy to extend/change the initial reference to meet the ISpanParseable and other shapes. some comments:
|
@scottleyg this is a lightweight API focusing on core functionality - parsing and
I actually considered that name, but "IP Network" seemed to be the a more widespread name for types dealing with this concern, eg. go has If you think have some valuable additions to the approved API, which are important enough for common scenarios to be included in the BCL, feel free to open a new API proposal, but bare in mind that we will likely not commit to advanced/rare stuff. |
We are keeping this open to track downlevel packaging. |
Let's please avoid using the term BCL any more publicly. It's another thing that folks new to .NET have to learn. |
We have a bunch of packages prefixed |
yeah fair enough I got that feedback offline. |
Triage: Remaining work is to decide on downlevel packaging - waiting for API Review Board's decision. |
Is this still not decided yet? just trying to track the progress, no pressure. |
I created separate issue to track the netstandard2.0 decision - #88965 |
Fixed in PR #82779 |
Background and motivation
We've been using a custom IPAddressRange in our project, but this seems like a general-purpose concern. Looking across internal code bases at Microsoft, we've found dozens of similar implementations of this type, reinforcing the notion that this is general-purpose and should be in the framework.
API Proposal
Edit by @antonfirsov: Added implementations for
ISpanFormattable
andIParsable<IPSubnet>
, harmonized naming of method arguments to match current BCL standards.API Usage
We're using this for parsing and logging, so the parsing and tostring functionality are particularly interesting.
Alternative Designs
This type could potentially implementISpanFormattable<T>
andISpanParsable<T>
, but those interfaces require an IFormatProvider instance which isn't relevant in this context. Still, those interfaces might provide better interop elsewhere in the framework. Note thatIPAddress
doesn't implement these interfaces either.Risks
No response
The text was updated successfully, but these errors were encountered: