Skip to content

Commit

Permalink
Add IPAddr#to_unmasked_string
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
`to_unmasked_string`.

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 `to_string`-like value. This seems to fit better
  with the method name. And if folks need to get the `to_s` value they
  can always wrap the return value in another `IPAddr` and call `to_s`
  on that.
- 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 14, 2020
1 parent 09a6408 commit fb337bc
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 0 deletions.
8 changes: 8 additions & 0 deletions lib/ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ def to_string
return str
end

# Returns a string containing the unmasked IP address representation in
# canonical form.
def to_unmasked_string
return _to_string(@unmasked_addr)
end

# Returns a network byte ordered string form of the IP address.
def hton
case @family
Expand Down Expand Up @@ -504,6 +510,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 +623,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("1.2.3.4", IPAddr.new("1.2.3.4").to_unmasked_string)
assert_equal("1.2.3.4", IPAddr.new("1.2.3.4/8").to_unmasked_string)
assert_equal("0001:0002:0000:0000:0000:0000:0000:0003", IPAddr.new("1:2::3").to_unmasked_string)
assert_equal("0001:0002:0000:0000:0000:0000:0000:0003", IPAddr.new("1:2::3/16").to_unmasked_string)
assert_equal("1.2.3.4", IPAddr.new(IPAddr.new("1.2.3.4").to_i, Socket::AF_INET).to_unmasked_string)
assert_equal("1.2.3.5", IPAddr.new("1.2.3.4").succ.to_unmasked_string)
end
end

0 comments on commit fb337bc

Please sign in to comment.