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

Add IPAddr#unmasked #26

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

composerinteralia
Copy link

@composerinteralia composerinteralia commented Sep 1, 2020

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

Copy link

@CvX CvX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks when used on an instance initiated with a non-string:

a = IPAddr.new(2130706433, Socket::AF_INET)
a.to_s == "127.0.0.1"
a.to_unmasked_string
# NoMethodError: undefined method `>>' for nil:NilClass

I've submitted a PR to your fork to amend this: composerinteralia#1

@composerinteralia
Copy link
Author

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.

@CvX
Copy link

CvX commented Sep 14, 2020

spotted some other cases where we call .clone.set

Good one! LGTM 🙂 :shipit:

@ioquatix
Copy link
Member

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?

@composerinteralia
Copy link
Author

composerinteralia commented Sep 15, 2020

@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 composerinteralia changed the title Add IPAddr#to_unmasked_string Add IPAddr#unmasked Sep 15, 2020
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"`.

```rb
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`

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
@ioquatix ioquatix requested a review from hsbt September 16, 2020 20:48
@ioquatix
Copy link
Member

LGTM.

@hsbt hsbt requested review from knu and removed request for hsbt September 16, 2020 23:28
@hsbt
Copy link
Member

hsbt commented Sep 16, 2020

The maintainer of ipaddr is @knu

@Sebbb
Copy link

Sebbb commented Jul 22, 2021

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

@ioquatix
Copy link
Member

@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

@composerinteralia
Copy link
Author

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
Copy link
Author

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.)

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

Successfully merging this pull request may close these issues.

None yet

5 participants