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

Addresses can be stored in a VARBINARY(16) database column as string of bytes #20

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ Docs:: http://deploy2.github.com/ruby-ip/
ip.to_arpa # "53.2.0.192.in-addr.arpa."
ip.pfxlen # 24

* Convert from IPAddr to IP and back to IPAddr

# new IPAddr
ipaddr = IPAddr.new('192.168.2.1')
# => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>

# convert it to IP
ip_from_ipaddr = IP.new_ntoh(ipaddr.hton)
# => <IP::V4 192.168.2.1>

# and back to IPAddr
ipaddr_from_ip = IPAddr.new_ntoh(ip_from_ipaddr.hton)
# => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>

* Qualify IP address with "routing context" (VRF)

ip = IP.new("192.0.2.53/24@cust1")
Expand Down Expand Up @@ -72,19 +86,18 @@ Docs:: http://deploy2.github.com/ruby-ip/
* Advanced Subnet Operations
sn = IP.new('192.168.0.0/24')
ip = IP.new('192.168.0.48/32')
sn.split [#<IP::V4 192.168.0.0/25>,
sn.split [#<IP::V4 192.168.0.0/25>,
#<IP::V4 192.168.0.128/25>] (2 evenly divided subnets)
sn.divide_by_subnets(3) [#<IP::V4 192.168.0.0/26>,
#<IP::V4 192.168.0.64/26>,
#<IP::V4 192.168.0.128/26>,
sn.divide_by_subnets(3) [#<IP::V4 192.168.0.0/26>,
#<IP::V4 192.168.0.64/26>,
#<IP::V4 192.168.0.128/26>,
#<IP::V4 192.168.0.192/26>] (4 evenly divided subnets)
#keep in mind this always takes into account a network and broadcast address
sn.divide_by_hosts(100) [#<IP::V4 192.168.0.0/25>,
sn.divide_by_hosts(100) [#<IP::V4 192.168.0.0/25>,
#<IP::V4 192.168.0.128/25>] (128 hosts each)
ip = IP.new('192.168.0.48/32')
ip.is_in?(sn)
=> true

=> true

* Convert to and from a compact Array representation

Expand All @@ -105,6 +118,46 @@ Docs:: http://deploy2.github.com/ruby-ip/

* Addresses are Comparable, sortable, and can be used as Hash keys

* Addresses can be stored in a VARBINARY(16) database column as string of bytes

# == Schema Information
#
# Table name: ipaddresses
#
# id :integer not null, primary key
# address :binary(16)
# pfxlen :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_ipaddresses_on_address (address)
# index_ipaddresses_on_pfxlen (pfxlen)
#

require 'ip'
class IpAddress < ActiveRecord::Base
validates_uniqueness_of :address, scope: [:pfxlen]
validates_presence_of :address, :pfxlen

# IpAddress.last.address returns an IP object
def address
IP.new("#{IP.new_ntoh(super).to_s}/#{pfxlen}")
end

# Address can be set with string:
# ipaddress = IpAddress.new({address: '2001:db8:be00::/128'})
# Or IP object:
# ip = IP.new('2001:db8:be00::/128')
# ipaddress = IpAddress.new({address: ip})
def address=(arg)
ip = IP.new(arg)
self.pfxlen = ip.pfxlen
super(ip.hton)
end
end

== Why not IPAddr?

Ruby bundles an IPAddr class (ipaddr.rb). However there are a number of
Expand Down
30 changes: 30 additions & 0 deletions lib/ip/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ def proto
self.class::PROTO
end

# Creates a new ip containing the given network byte ordered
# string form of an IP address.
def IP::new_ntoh(addr)
return IP.new(IP::ntop(addr))
end

# Convert a network byte ordered string form of an IP address into
# human readable form.
def IP::ntop(addr)
case addr.size
when 4
s = addr.unpack('C4').join('.')
when 16
s = (["%.4x"] * 8).join(':') % addr.unpack('n8')
else
fail ArgumentError, 'Invalid address value'
end
return s
end

# Return the string representation of the address, x.x.x.x[/pfxlen][@ctx]
def to_s
ctx ? "#{to_addrlen}@#{ctx}" : to_addrlen
Expand Down Expand Up @@ -361,6 +381,11 @@ def to_arpa
(@addr >> 16) & 0xff,
(@addr >> 24) & 0xff)
end

# Returns a network byte ordered string form of the IP address.
def hton
[@addr].pack('N')
end
end

class V6 < IP
Expand Down Expand Up @@ -452,6 +477,11 @@ def to_addr_full
end
end

# Returns a network byte ordered string form of the IP address.
def hton
(0..7).map { |i| (@addr >> (112 - 16 * i)) & 0xffff }.pack('n8')
end

# Returns the address broken into an array of 32 nibbles. Useful for
# to_arpa and use in SPF - http://tools.ietf.org/html/rfc7208#section-7.3
def to_nibbles
Expand Down
49 changes: 49 additions & 0 deletions test/ip_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ class IPTest < Minitest::Test
assert(s1.object_id != s2.object_id)
end

it 'builds from hton' do
hton = "\x01\x02\x03\x04"
res = IP.new_ntoh(hton)
assert_equal IP::V4, res.class
assert_equal 32, res.pfxlen
assert_nil res.ctx
end

it 'disallows invalid addr' do
assert_raises(ArgumentError) { IP::V4.new(1 << 32) }
assert_raises(ArgumentError) { IP::V4.new(-1) }
Expand Down Expand Up @@ -112,6 +120,10 @@ class IPTest < Minitest::Test
assert_equal '4.3.2.1.in-addr.arpa.', @addr.to_arpa
end

it 'has hton' do
assert_equal "\x01\x02\x03\x04", @addr.hton
end

it 'has to_i' do
assert_equal 0x01020304, @addr.to_i
end
Expand Down Expand Up @@ -402,6 +414,14 @@ class IPTest < Minitest::Test
assert(s1.object_id != s2.object_id)
end

it 'builds from hton' do
hton = IP::V6.new(0xdeadbeef000000000000000000000123, 24, 'foo').hton
res = IP.new_ntoh(hton)
assert_equal IP::V6, res.class
assert_equal 128, res.pfxlen
assert_nil res.ctx
end

it 'disallows invalid addr' do
assert_raises(ArgumentError) { IP::V6.new(1 << 128) }
assert_raises(ArgumentError) { IP::V6.new(-1) }
Expand All @@ -427,6 +447,14 @@ class IPTest < Minitest::Test
res = IP.new('dead:beef::123')
assert_equal '3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.ip6.arpa', res.to_arpa
end

it 'has hton' do
res = String.new
IP.new('dead:beef::123').hton.each_byte { |c|
res += sprintf("%02x", c)
}
assert_equal 'deadbeef000000000000000000000123', res
end
end

describe 'v6 ::0' do
Expand All @@ -445,6 +473,10 @@ class IPTest < Minitest::Test
it 'has to_arpa' do
assert_equal '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa', @addr.to_arpa
end

it 'has hton' do
assert_equal "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @addr.hton
end
end

describe 'v6 ::1' do
Expand All @@ -463,6 +495,10 @@ class IPTest < Minitest::Test
it 'has to_arpa' do
assert_equal '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa', @addr.to_arpa
end

it 'has hton' do
assert_equal "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", @addr.hton
end
end

describe 'v6 compat' do
Expand All @@ -487,6 +523,10 @@ class IPTest < Minitest::Test
it 'has to_arpa' do
assert_equal '4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa', @addr.to_arpa
end

it 'has hton' do
assert_equal "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04", @addr.hton
end
end

describe 'v6 mapped' do
Expand All @@ -512,6 +552,11 @@ class IPTest < Minitest::Test
assert_equal '4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa', @addr.to_arpa
end

it 'has hton' do
assert_equal "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x01\x02\x03\x04".force_encoding("ASCII-8BIT"),
@addr.hton
end

it 'converts v6 addresses unambiguously' do
assert_equal '1:0:2::', IP.new('1:0:2:0:0:0:0:0').to_s
assert_equal '1::2:0', IP.new('1:0:0:0:0:0:2:0').to_s
Expand Down Expand Up @@ -587,6 +632,10 @@ class IPTest < Minitest::Test
assert_equal '4.3.2.1.in-addr.arpa.', @addr.to_arpa
end

it 'responds to hton withouth a TypeError' do
assert_equal "\x01\x02\x03\x04", @addr.hton
end

it 'responds to to_i withouth a TypeError' do
assert_equal 16_909_060, @addr.to_i
end
Expand Down