Skip to content
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

Consider allowing Cidr construction from addresses where host part is not zero #8

Closed
Qix- opened this issue Jul 10, 2021 · 12 comments
Closed

Comments

@Qix-
Copy link

Qix- commented Jul 10, 2021

I'm getting an address from a list of interfaces and then need to construct a CIDR from one of those host addresses and a specific network length.

However, this library seems to always want to return host part of address was not zero, and since bitstring is private I can't clip(24) on an Ipv4Inet object to force the address down to 24 bits, for example.

Is there a way to pass an address to the Cidr constructor to ignore the host bits?

@stbuehler
Copy link
Owner

Hi.

You should be able to construct an Ipv4Inet object and then use Inet::network to get the corresponding Ipv4Cidr.

To move from an Ipv4Inet to one with a different network length (e.g. to get the Ipv4Cidr for it) you need to construct a new Ipv4Inet with the Inet::address of the old one and the new network length.

(All that works with IPv6 too of course.)

Hope that helps :)

cheers,
Stefan

@wez
Copy link

wez commented Aug 11, 2023

Would you consider relaxing this check and instead having the type fixup the host part to be zero, or otherwise providing an alternative constructor to allow this more relaxed construction?

Alternatively, could the error message be made more actionable? For example, it already knows that for 10.0.0.1/24 the user probably should be using 10.0.0.0/24. It would be great if the error message actually said that, as the current "host part is not zero" is hard for the lay-person to understand and know how to resolve without asking for assistance.

@Qix-
Copy link
Author

Qix- commented Aug 11, 2023

Yes, agreed. Perhaps a ::new() that performs the zeroing, and an unsafe ::new_unchecked() with documentation stating that the host portion of the CIDR must be zero. This makes a lot of sense to me, personally. I ultimately had to figure out a way to work around this.

@stbuehler
Copy link
Owner

IpCidr::new explicitly documents the requirement for the host part to be zero; I'm not gonna change that, especially because I believe in properly validating input. I actually don't want a program to ignore the host part if the input should have been a network/prefix, because it indicates I screwed something up.

And this is really the way it should be, and I think many other implementations agree with me on this. (I just checked python ipaddress.ip_network and postgres cidr).

As stated above the proper way to ignore the host part is to use IpInet::new(...).network() (network() is now availably directly on the inet types too, trait not needed anymore). The same goes for the from_str constructor (instead of new), usually used through str::parse.

I think this should only be used if you actually want to accept an "interface configuration address", and want to extract the network from it, but if you really think your program should just do it's best to guess what the user meant, this is the way to go.

(I'm also not going to add unsafe *_unchecked variants. Those should do exactly the same as the safe version, just without (some) checks, and that only makes sense if the checks are a performance problem, and there are valid scenarios where the expected assertions hold but just can't be proved through the type system. This is not relevant to this issue at all.)

I also don't see a good reason to add a Cidr::new_but_ignore_hostpart constructor variant - because it would only cover new, but not from_str/parse.

I am open to improve the documentation though; I'd appreciate feedback in which places it would be useful to show how to ignore the host part.

@wez
Copy link

wez commented Aug 12, 2023

For clarity, in my use case, users provide CIDRs like 10.0.0.1/24 as string input to my program, which calls through to AnyIpCidr::from_str.

When that fails, I'd like the error message that is displayed to them to be instructive.

I understand that you don't want to change your interface, I'm just looking for a low effort way to provide maximum clarity; I'd rather not have to parse the CIDR myself and replicate the internal logic of the cidr library.

Would you consider expanding the InvalidHostPart variant to something like:

InvalidHostPart {
  suggested: IpCidr
}

and having the Display impl of NetworkParseError display something like:

host part of address was not zero, did you mean {suggested}?

@stbuehler
Copy link
Owner

Hm. I admit I don't have a good solution for AnyIpCidr, good point.

Extending the NetworkParseError::InvalidHostPart variant sounds ok (although it would increase the size of the error a lot, the size of the Result might stay about the same); but it would mean bumping the version to 0.3.*.

@stbuehler stbuehler reopened this Aug 12, 2023
stbuehler added a commit that referenced this issue Dec 3, 2023
* Separate enum for Cidr parsing
* Add inet address to InvalidHostPart variant to allow easier recovery
  from invalid host part errors in case you want to ignore them.
@wez
Copy link

wez commented Jun 20, 2024

@stbuehler db749ad looks perfect for my use case! Would you consider rolling that out as a release? In the meantime, I'm pointing my build to that branch of this repo

wez added a commit to KumoCorp/kumomta that referenced this issue Jun 20, 2024
This is done in two parts:

1. Showing the list of bad cidrs and their corresponding error messages
2. Upgrading to a yet-to-be-released branch of the underlying cidr
   library that can provide a helpful suggestion of how to fix it

refs: stbuehler/rust-cidr#8
@Qix-
Copy link
Author

Qix- commented Jun 24, 2024

I guess I still don't fully understand the rationale here. If the host part is always 0, that means it's irrelevant information. In which case, a CIDR could just be specified by a bit count.

Further it's mostly backward compatible if the host part of the CIDR is specified to always be zero as relaxing that check shouldn't break anything except code that explicitly uses the error enum for it, right?

@stbuehler
Copy link
Owner

@wez:

@stbuehler db749ad looks perfect for my use case! Would you consider rolling that out as a release? In the meantime, I'm pointing my build to that branch of this repo

I've decided against extending the error. The other errors (e.g. std::net::AddrParseError) don't contain detailed information either, so it'd be rather inconsistent. It seems you (mostly?) tried to use this for better error messages; I think you'd better wrap the parser to show the invalid input explicitly (together with the error message).

I'll expose some parser methods (including some additional ones), so you could choose to either just ignore host-bits or have your parser check the host-bits explicitly with a pretty message by using the Inet-parser (see https://stbuehler.github.io/rustdocs/cidr/cidr/parsers/index.html for a preview).

(As a sidenote: I will probably drop accepting "127" as short for "127.0.0.0/32" and similar in 0.3.0; the parsers module should provide options for this too.)

@stbuehler
Copy link
Owner

@Qix-

I guess I still don't fully understand the rationale here. If the host part is always 0, that means it's irrelevant information. In which case, a CIDR could just be specified by a bit count.

Further it's mostly backward compatible if the host part of the CIDR is specified to always be zero as relaxing that check shouldn't break anything except code that explicitly uses the error enum for it, right?

A CIDR is "mathematically" a bitstring; this bitstring is a prefix of all addresses that are part of that CIDR. Which is why it is often called a "prefix". To properly ("canonically") represent it we append 0s ("host part") to make it a full address and print that together with the length of the bitstring. And I decided (similar as other projects did) to only accept canonically formatted representations, because other representations very likely indicate a mistake in the input. If you think this isn't the case in your application, feel free to use any of the mentioned alternatives.

(This also means the CIDR is more than just the bit count.)

@stbuehler
Copy link
Owner

stbuehler commented Jun 25, 2024

New methods now available in 0.2.3.

To use a parser that ignores host bits try the new parsers module:

cidr::parsers::parse_cidr_ignore_hostbits("...", FromStr::from_str);
cidr::parsers::parse_any_cidr_ignore_hostbits("...", FromStr::from_str);

wez added a commit to KumoCorp/kumomta that referenced this issue Jun 25, 2024
The upstream cidr crate has a released a version that exposes the more
relaxed parser, so refactor the prior work from
7eb5bdc (which was pointing to a
temporary development branch) to reference its "final form".

This is done by exposing a `parse_cidr` function from our cidr-map
crate, and standardizing all callers to route through that.

refs: stbuehler/rust-cidr#8
@wez
Copy link

wez commented Jun 25, 2024

@stbuehler Thanks, that gives a lot of choice and flexibility and works for me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants