Skip to content

Commit

Permalink
Add IPAddr#unmasked
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
composerinteralia and CvX committed Sep 15, 2020
1 parent 09a6408 commit d5e866d
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 0 deletions.
7 changes: 7 additions & 0 deletions lib/ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ def succ
return self.clone.set(@addr + 1, @family)
end

# Returns a new ipaddr without a mask
def unmasked
self.class.new(@unmasked_addr, @family)
end

# Compares the ipaddr with another.
def <=>(other)
other = coerce_other(other)
Expand Down Expand Up @@ -504,6 +509,7 @@ def set(addr, *family)
raise AddressFamilyError, "unsupported address family"
end
@addr = addr
@unmasked_addr = addr
if family[0]
@family = family[0]
end
Expand Down Expand Up @@ -616,6 +622,7 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC)
if family != Socket::AF_UNSPEC && @family != family
raise AddressFamilyError, "address family mismatch"
end
@unmasked_addr = @addr
if prefixlen
mask!(prefixlen)
else
Expand Down
9 changes: 9 additions & 0 deletions test/test_ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,13 @@ def test_hash
assert_equal(true, s.include?(a5))
assert_equal(true, s.include?(a6))
end

def test_unmasked
assert_equal(IPAddr.new("1.2.3.4"), IPAddr.new("1.2.3.4").unmasked)
assert_equal(IPAddr.new("1.2.3.4"), IPAddr.new("1.2.3.4/8").unmasked)
assert_equal(IPAddr.new("1:2::3"), IPAddr.new("1:2::3").unmasked)
assert_equal(IPAddr.new("1:2::3"), IPAddr.new("1:2::3/16").unmasked)
assert_equal(IPAddr.new("1.2.3.4"), IPAddr.new(IPAddr.new("1.2.3.4").to_i, Socket::AF_INET).unmasked)
assert_equal(IPAddr.new("1.2.3.5"), IPAddr.new("1.2.3.4").succ.mask(8).unmasked)
end
end

0 comments on commit d5e866d

Please sign in to comment.