Skip to content

Instantly share code, notes, and snippets.

@brandt
Forked from jimsynz/ipaddr.rb
Created November 4, 2016 18:39
Show Gist options
  • Save brandt/a80c825ebb4ba9b6e0902e1f24821705 to your computer and use it in GitHub Desktop.
Save brandt/a80c825ebb4ba9b6e0902e1f24821705 to your computer and use it in GitHub Desktop.
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Ruby IPAddr Extensions.
#
# The Initial Developer of the Original Code is James Harton.
# Portions created by the Initial Developer are Copyright (C) 2009
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# ***** END LICENSE BLOCK *****
require 'ipaddr'
module Mashd
module Cc
module IPAddrExtensions
# Return the bit length of the prefix
# ie:
# IPAddr.new("2001:db8::/32").length
# => 32
# IPAddr.new("192.0.2.0/255.255.255.0").length
# => 24
def length
# nasty hack, but works well enough.
@mask_addr.to_s(2).count("1")
end
# Retrieve the first address in this prefix
# (called a network address in IPv4 land)
def first
IPAddr.new(@addr & @mask_addr, @family)
end
alias :begin :first
# Retrieve the last address in this prefix
# (called a broadcast address in IPv4 land)
def last
if @family == Socket::AF_INET
IPAddr.new(first.to_i | (@mask_addr ^ IPAddr::IN4MASK), @family)
elsif @family == Socket::AF_INET6
IPAddr.new(first.to_i | (@mask_addr ^ IPAddr::IN6MASK), @family)
else
raise "unsupported address family."
end
end
alias :end :last
# Return an EUI-64 host address for the current
# prefix (must be a 64 bit long IPv6 prefix).
def eui_64(mac)
if @family != Socket::AF_INET6
raise Exception, "EUI-64 only makes sense on IPv6 prefixes."
elsif self.length != 64
raise Exception, "EUI-64 only makes sense on 64 bit IPv6 prefixes."
end
if mac.is_a? Integer
mac = "%:012x" % mac
end
if mac.is_a? String
mac = mac.split(":").join.downcase
if mac.match(/^[0-9a-f]{12}/).nil?
raise ArgumentError, "Second argument must be a valid MAC address."
end
e64 = (mac[0..5] + "fffe" + mac[6..11]).to_i(16) ^ 0x0200000000000000
IPAddr.new(self.first.to_i + e64, Socket::AF_INET6)
end
end
MSCOPES = {
1 => "INTERFACE LOCAL MULTICAST",
2 => "LINK LOCAL MULTICAST",
4 => "ADMIN LOCAL MULTICAST",
5 => "SITE LOCAL MULTICAST",
8 => "ORGANISATION LOCAL MULTICAST",
0xe => "GLOBAL MULTICAST"
}
MDESTS = {
1 => "ALL NODES",
2 => "ALL ROUTERS",
3 => "ALL DHCP SERVERS",
4 => "DVMRP ROUTERS",
5 => "OSPFIGP",
6 => "OSPFIGP DESIGNATED ROUTERS",
7 => "ST ROUTERS",
8 => "ST HOSTS",
9 => "RIP ROUTERS",
0xa => "EIGRP ROUTERS",
0xb => "MOBILE-AGENTS",
0xc => "SSDP",
0xd => "ALL PIM ROUTERS",
0xe => "RSVP ENCAPSULATION",
0xf => "UPNP",
0x16 => "ALL MLDV2 CAPABLE ROUTERS",
0x6a => "ALL SNOOPERS",
0x6b => "PTP-PDELAY",
0x6c => "SARATOGA",
0x6d => "LL MANET ROUTERS",
0xfb => "MDNSV6",
0x100 => "VMTP MANAGERS GROUP",
0x101 => "NTP",
0x102 => "SGI-DOGFIGHT",
0x103 => "RWHOD",
0x104 => "VNP",
0x105 => "ARTIFICIAL HORIZONS",
0x106 => "NSS",
0x107 => "AUDIONEWS",
0x108 => "SUN NIS+",
0x109 => "MTP",
0x10a => "IETF-1-LOW-AUDIO",
0x10b => "IETF-1-AUDIO",
0x10c => "IETF-1-VIDEO",
0x10d => "IETF-2-LOW-AUDIO",
0x10e => "IETF-2-AUDIO",
0x10f => "IETF-2-VIDEO",
0x110 => "MUSIC-SERVICE",
0x111 => "SEANET-TELEMETRY",
0x112 => "SEANET-IMAGE",
0x113 => "MLOADD",
0x114 => "ANY PRIVATE EXPERIMENT",
0x115 => "DVMRP on MOSPF",
0x116 => "SVRLOC",
0x117 => "XINGTV",
0x118 => "MICROSOFT-DS",
0x119 => "NBC-PRO",
0x11a => "NBC-PFN",
0x10001 => "LINK NAME",
0x10002 => "ALL DHCP AGENTS",
0x10003 => "LINK LOCAL MULTICAST NAME",
0x10004 => "DTCP ANNOUNCEMENT",
}
# Returns a string describing the scope of the
# address.
def scope
if @family == Socket::AF_INET
if IPAddr.new("0.0.0.0/8").include? self
"CURRENT NETWORK"
elsif IPAddr.new("10.0.0.0/8").include? self
"RFC1918 PRIVATE"
elsif IPAddr.new("14.0.0.0/8").include? self
"PUBLIC DATA"
elsif IPAddr.new("127.0.0.0/8").include? self
"LOOPBACK"
elsif IPAddr.new("128.0.0.0/18").include? self
"RESERVED (IANA)"
elsif IPAddr.new("168.254.0.0/16").include? self
"AUTOCONF PRIVATE"
elsif IPAddr.new("172.16.0.0/16").include? self
"RFC1918 PRIVATE"
elsif IPAddr.new("191.255.0.0/16").include? self
"RESERVED (IANA)"
elsif IPAddr.new("192.0.0.0/24").include? self
"RESERVED (IANA)"
elsif IPAddr.new("192.0.2.0/24").include? self
"DOCUMENTATION"
elsif IPAddr.new("192.88.99.0/24").include? self
"6to4 ANYCAST"
elsif IPAddr.new("192.168.0.0/16").include? self
"RFC1918 PRIVATE"
elsif IPAddr.new("198.18.0.0/15").include? self
"NETWORK BENCHMARK TESTS"
elsif IPAddr.new("223.255.255.0/24").include? self
"RESERVED (IANA)"
elsif IPAddr.new("224.0.0.0/4").include? self
if IPAddr.new("239.0.0.0/8").include? self
"LOCAL MULTICAST"
else
"GLOBAL MULTICAST"
end
elsif IPAddr.new("240.0.0.0/4").include? self
"RESERVED"
elsif IPAddr.new("255.255.255.255") == self
"GLOBAL BROADCAST"
else
"GLOBAL UNICAST"
end
elsif @family == Socket::AF_INET6
if IPAddr.new("2000::/3").include? self
require 'scanf'
if IPAddr.new("2002::/16").include? self
x = self.to_string.scanf("%*4x:%4x:%4x:%*s")
"GLOBAL UNICAST (6to4: #{IPAddr.new((x[0]<<16)+x[1], Socket::AF_INET6).to_s})"
elsif IPAddr.new("2001::/32").include? self
server_ip = IPAddr.new((@addr >> 64) & ((1<<32)-1), Socket::AF_INET)
client_ip = IPAddr.new((@addr & ((1<<32)-1)) ^ ((1<<32)-1), Socket::AF_INET)
udp_port = ((@addr >> 32) & ((1<<16)-1))
"GLOBAL UNICAST (Teredo #{client_ip.to_s}:#{udp_port.to_s} -> #{server_ip.to_s}:#{udp_port.to_s})"
elsif IPAddr.new("2001:10::/28").include? self
"ORCHID"
elsif IPAddr.new("2001:db8::/32").include? self
"DOCUMENTATION"
else
"GLOBAL UNICAST"
end
elsif IPAddr.new("::/128") == self
"UNSPECIFIED ADDRESS"
elsif IPAddr.new("::1/128") == self
"LINK LOCAL LOOPBACK"
elsif IPAddr.new("::ffff:0:0/96").include? self
a,b,c,d = self.to_string.scanf("%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x:%4x:%4x")
"IPv4 MAPPED (#{a.to_s}.#{b.to_s}.#{c.to_s}.#{d.to_s})"
elsif IPAddr.new("::/96").include? self
a,b,c,d = self.to_string.scanf("%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x:%4x:%4x")
"IPv4 TRANSITION (#{a.to_s}.#{b.to_s}.#{c.to_s}.#{d.to_s}, deprecated)"
elsif IPAddr.new("fc00::/7").include? self
"UNIQUE LOCAL UNICAST"
elsif IPAddr.new("fec0::/10").include? self
"SITE LOCAL (deprecated)"
elsif IPAddr.new("fe80::/10").include? self
"LINK LOCAL UNICAST"
elsif IPAddr.new("ff00::/8").include? self
mscope,mdesta,mdestb = self.to_string.scanf("%*1x%*1x%*1x%1x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x")
mdest = (mdesta << 16) + mdestb
s = "MULTICAST"
if MSCOPES[mscope]
s += " #{MSCOPES[mscope]}"
end
if MDESTS[mdest]
s += " #{MDEST[mdest]}"
end
s
else
"RESERVED"
end
end
end
# Some scope tests
def local?
self.scope.split(' ').member? 'LOCAL'
end
def unicast?
self.scope.split(' ').member? 'UNICAST'
end
def multicast?
self.scope.split(' ').member? 'MULTICAST'
end
def link?
self.scope.split(' ').member? 'LINK'
end
def documentation?
self.scope.split(' ').member? 'DOCUMENTATION'
end
def loopback?
self.scope.split(' ').member? 'LOOPBACK'
end
def global?
self.scope.split(' ').member? 'GLOBAL'
end
def private?
self.scope.split(' ').member? 'PRIVATE'
end
# Convert an IPv4 address into an IPv6
# 6to4 address.
def to_6to4
if @family == Socket::AF_INET
IPAddr.new((0x2002 << 112) + (@addr << 80), Socket::AF_INET6)
else
self
end
end
# Return the space available inside this prefix
def space
self.last.to_i - self.first.to_i + 1
end
# Return likely reverse zones for the Address or prefix
# (differs from reverse() because it will return the correct
# number of zones to adequately delegate the prefix).
def reverses
if @family == Socket::AF_INET
if self.length == 32
[ self.reverse ]
else
boundary = self.length % 8 == 0 && self.length != 0 ? self.length / 8 - 1 : self.length / 8
divisor = (boundary + 1) * 8
count = (self.last.to_i - self.first.to_i) / (1 << 32 - divisor)
res = []
(0..count).each do |i|
octets = IPAddr.new(first.to_i + ((1<<32-divisor)*i), Socket::AF_INET).to_s.split('.')[0..boundary]
res << "#{octets * '.'}.in-addr.arpa"
end
res
end
elsif @family == Socket::AF_INET6
if self.length == 128
[ self.reverse ]
else
boundary = self.length % 16 == 0 && self.length != 0 ? self.length / 4 - 1 : self.length / 4
divisor = (boundary + 1) * 4
count = (self.last.to_i - self.first.to_i) / (1 << 128-divisor)
res = []
(0..count).each do |i|
baseaddr = self.first.to_i + (1<<128-divisor)*i
octets = ("%032x" % baseaddr).split('')[0..boundary]
res << octets.reverse * '.' + '.ip6.arpa'
end
res
end
end
end
# Extra quick tests
def host?
(@family == Socket::AF_INET && self.length == 32) ||
(@family == Socket::AF_INET6 && self.length == 128)
end
def prefix?
!self.host?
end
end
end
end
IPAddr.send(:include, Mashd::Cc::IPAddrExtensions)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment