ipaddr icon indicating copy to clipboard operation
ipaddr copied to clipboard

Add IPAddr#unmasked

Open composerinteralia opened this issue 4 years ago • 10 comments

Prior to this commit there was no way to retrieve the unmasked address for an IPAddr. For example, given the IPAddr below there is no method or instance variable that would return "1.2.3.4".

IPAddr.new("1.2.3.4/8")

This poses a problem for Rails in supporting the PostgreSQL inet type. Rails uses IPAddr for both the cidr and inet types, but at the moment cannot fully support the inet type, which "accepts values with nonzero bits to the right of the netmask". This came up originally in rails/rails#14857, and more recently in rails/rails#40138. The change in this commit would allow us to support the inet type without moving to another library.

This commit holds onto the address before the mask is applied, in a new instance variable @unmasked_addr. It then exposes this via unmasked.

This was originally implemented back in ruby/ruby#599, but it got stale and was eventually closed. There are also a few differences with that PR:

  • That PR set up the instance variable inside of mask!, which meant that the new method was broken in the case of an IPAddr with no mask.
  • That PR introduced a method that returned a to_s-like value, whereas this one returns a new, unmasked IPAddr object.
  • As a result of the above, this version doesn't need to change the signature of to_s
  • This PR also sets @unmasked_addr within set, which covers a few additional edge cases with non-string arguments and with methods like & or succ that clone the IpAdrr and call set

composerinteralia avatar Sep 01 '20 15:09 composerinteralia

Nice catch @CvX. While reviewing your changes I also spotted some other cases where we call .clone.set. I kept your test case and added you as a co-author, but moved the line to set @unmasked_addr into the set method.

composerinteralia avatar Sep 14 '20 16:09 composerinteralia

spotted some other cases where we call .clone.set

Good one! LGTM 🙂 :shipit:

CvX avatar Sep 14 '20 18:09 CvX

Do you think to_unmasked_string is the right name for this?

I agree with the functionality.

Just want to make sure this is the right name.

Is this a to_ conversion or a property of the address? e.g. address.unmasked.to_s might make more sense?

How does this work with IPv6?

ioquatix avatar Sep 15 '20 02:09 ioquatix

@ioquatix thanks for the review.

address.unmasked.to_s might make more sense?

Yeah, I think your suggestion of unmasked instead of to_unmasked_string is better. I updated the PR to take that suggestion.

How does this work with IPv6?

I have a couple IPv6 test cases. Are there additional cases that I am missing?

composerinteralia avatar Sep 15 '20 04:09 composerinteralia

LGTM.

ioquatix avatar Sep 16 '20 20:09 ioquatix

The maintainer of ipaddr is @knu

hsbt avatar Sep 16 '20 23:09 hsbt

Hello,

I have a question about this.

It actually took me quite a while to figure out what's wrong, as also Postgres' inet data type is mapped to IPAddr.

> IPAddr.new('1.2.3.4/24').to_s
=> "1.2.3.0"

Athough it's a behavior change, normally I would expect IPAddr to keep the host bits. So wouldn't it make sense to adjust the code, that the example above returns '1.2.3.4'? and to introduce a function that returns the network address (discards the host bits)?

> IPAddr.new('1.2.3.4/24').to_s
=> "1.2.3.4"
> IPAddr.new('1.2.3.4/24').network_address.to_s
=> "1.2.3.0"

Also, #succ would be more useful if you could initialize a new object with non-zero host bits.. On the proposed function to_unmasked_string you can't call it.

If the goal is to not break the (strange) behavior, maybe an unmasked function that again returns an IPAddr object (and not a string) would make sense:

> IPAddr.new('1.2.3.4/24').unmasked
=> <IPAddr: IPv4:1.2.3.4/255.255.255.0>

> IPAddr.new('1.2.3.4/24').unmasked.to_s
=> "1.2.3.4"

Both would work fine, as IPAddr already now is able to cope with non-zero host bits:

> IPAddr.new('1.2.3.4/24').succ.succ.to_s
=> "1.2.3.2"

Seb

Sebbb avatar Jul 22 '21 10:07 Sebbb

@composerinteralia do you have some time to check the above feedback? It would be awesome to merge this in time for Ruby 3.1

cc @knu

ioquatix avatar Nov 21 '21 21:11 ioquatix

I’m on vacation for the next two weeks away from a computer, but I can take a look when I get back. Anyone is also welcome to take over in the meantime.

composerinteralia avatar Nov 22 '21 01:11 composerinteralia

Athough it's a behavior change, normally I would expect IPAddr to keep the host bits. So wouldn't it make sense to adjust the code, that the example above returns '1.2.3.4'

I can revise this PR if that's the direction folks want to go, but it's a potentially disruptive breaking change for anyone relying on the existing behavior (I believe we're relying on it for the cidr type in Rails, for example). I assume that would need to be a major release, and ideally there would be some sort of deprecation cycle.

If the goal is to not break the (strange) behavior, maybe an unmasked function that again returns an IPAddr object (and not a string) would make sense:

I believe that's what this PR does. (Although it's been a while, so please correct me if I am mistaken.)

composerinteralia avatar Dec 03 '21 17:12 composerinteralia