/lib/ip/base.rb

http://github.com/deploy2/ruby-ip · Ruby · 480 lines · 337 code · 55 blank · 88 comment · 39 complexity · 3eaaf5d5ff04dd3257489b78a9ed5abb MD5 · raw file

  1. # Copyright (C) 2009-2010 Brian Candler <http://www.deploy2.net/>
  2. # Licensed under the same terms as ruby. See LICENCE.txt and COPYING.txt
  3. class IP
  4. PROTO_TO_CLASS = {}
  5. class << self
  6. alias_method :orig_new, :new
  7. # Examples:
  8. # IP.new("1.2.3.4")
  9. # IP.new("1.2.3.4/28")
  10. # IP.new("1.2.3.4/28@routing_context")
  11. #
  12. # Array form (inverse of to_a and to_ah):
  13. # IP.new(["v4", 0x01020304])
  14. # IP.new(["v4", 0x01020304, 28])
  15. # IP.new(["v4", 0x01020304, 28, "routing_context"])
  16. # IP.new(["v4", "01020304", 28, "routing_context"])
  17. #
  18. # Note that this returns an instance of IP::V4 or IP::V6. IP is the
  19. # base class of both of those, but cannot be instantiated itself.
  20. def new(src)
  21. case src
  22. when String
  23. parse(src) || (fail ArgumentError, 'invalid address')
  24. when Array
  25. (PROTO_TO_CLASS[src[0]] ||
  26. (fail ArgumentError, 'invalid protocol')).new(*src[1..-1])
  27. when IP
  28. src.dup
  29. else
  30. fail ArgumentError, 'invalid address'
  31. end
  32. end
  33. # Parse a string as an IP address - return a V4/V6 object or nil
  34. def parse(str)
  35. V4.parse(str) || V6.parse(str)
  36. end
  37. end
  38. # Length of prefix (network portion) of address
  39. attr_reader :pfxlen
  40. # Routing Context indicates the scope of this address (e.g. virtual router)
  41. attr_accessor :ctx
  42. # Examples:
  43. # IP::V4.new(0x01020304)
  44. # IP::V4.new("01020304")
  45. # IP::V4.new(0x01020304, 28)
  46. # IP::V4.new(0x01020304, 28, "routing_context")
  47. def initialize(addr, pfxlen = nil, ctx = nil)
  48. @addr = addr.is_a?(String) ? addr.to_i(16) : addr.to_i
  49. if @addr < 0 || @addr > self.class::MASK
  50. fail ArgumentError, 'Invalid address value'
  51. end
  52. self.pfxlen = pfxlen
  53. self.ctx = ctx
  54. end
  55. # Return the protocol in string form, "v4" or "v6"
  56. def proto
  57. self.class::PROTO
  58. end
  59. # Return the string representation of the address, x.x.x.x[/pfxlen][@ctx]
  60. def to_s
  61. ctx ? "#{to_addrlen}@#{ctx}" : to_addrlen
  62. end
  63. # Return the string representation of the IP address and prefix, or
  64. # just the IP address if it's a single address
  65. def to_addrlen
  66. pfxlen == self.class::ADDR_BITS ? to_addr : "#{to_addr}/#{pfxlen}"
  67. end
  68. # Return the address as an Integer
  69. def to_i
  70. @addr
  71. end
  72. # returns the address in Binary
  73. def to_b
  74. @addr.to_s(2).to_i
  75. end
  76. # Return the address as a hexadecimal string (8 or 32 digits)
  77. def to_hex
  78. @addr.to_s(16).rjust(self.class::ADDR_BITS >> 2, '0')
  79. end
  80. # Return an array representation of the address, with 3 or 4 elements
  81. # depending on whether there is a routing context set.
  82. # ["v4", 16909060, 28]
  83. # ["v4", 16909060, 28, "context"]
  84. # (Removing the last element makes them Comparable, as nil.<=> doesn't exist)
  85. def to_a
  86. @ctx ? [self.class::PROTO, @addr, @pfxlen, @ctx] :
  87. [self.class::PROTO, @addr, @pfxlen]
  88. end
  89. # Return an array representation of the address, with 3 or 4 elements
  90. # depending on whether there is a routing context set, using hexadecimal.
  91. # ["v4", "01020304", 28]
  92. # ["v4", "01020304", 28, "context"]
  93. def to_ah
  94. @ctx ? [self.class::PROTO, to_hex, @pfxlen, @ctx] :
  95. [self.class::PROTO, to_hex, @pfxlen]
  96. end
  97. # Change the prefix length. If nil, the maximum is used (32 or 128)
  98. def pfxlen=(pfxlen)
  99. @mask = nil
  100. if pfxlen
  101. pfxlen = pfxlen.to_i
  102. if pfxlen < 0 || pfxlen > self.class::ADDR_BITS
  103. fail ArgumentError, 'Invalid prefix length'
  104. end
  105. @pfxlen = pfxlen
  106. else
  107. @pfxlen = self.class::ADDR_BITS
  108. end
  109. end
  110. # Return the mask for this pfxlen as an integer. For example,
  111. # a V4 /24 address has a mask of 255 (0x000000ff)
  112. def mask
  113. @mask ||= (1 << (self.class::ADDR_BITS - @pfxlen)) - 1
  114. end
  115. # Return a new IP object at the base of the subnet, with an optional
  116. # offset applied.
  117. # IP.new("1.2.3.4/24").network => #<IP::V4 1.2.3.0/24>
  118. # IP.new("1.2.3.4/24").network(7) => #<IP::V4 1.2.3.7/24>
  119. def network(offset = 0)
  120. self.class.new((@addr & ~mask) + offset, @pfxlen, @ctx)
  121. end
  122. # Return a new IP object at the top of the subnet, with an optional
  123. # offset applied.
  124. # IP.new("1.2.3.4/24").broadcast => #<IP::V4 1.2.3.255/24>
  125. # IP.new("1.2.3.4/24").broadcast(-1) => #<IP::V4 1.2.3.254/24>
  126. def broadcast(offset = 0)
  127. self.class.new((@addr | mask) + offset, @pfxlen, @ctx)
  128. end
  129. # Return a new IP object representing the netmask
  130. # IP.new("1.2.3.4/24").netmask => #<IP::V4 255.255.255.0>
  131. def netmask
  132. self.class.new(self.class::MASK & ~mask)
  133. end
  134. # Return a new IP object representing the wildmask (inverse netmask)
  135. # IP.new("1.2.3.4/24").netmask => #<IP::V4 0.0.0.255>
  136. def wildmask
  137. self.class.new(mask)
  138. end
  139. # Masks the address such that it is the base of the subnet
  140. # IP.new("1.2.3.4/24").mask! => #<IP::V4 1.2.3.0/24>
  141. def mask!
  142. @addr &= ~mask
  143. self
  144. end
  145. # Returns true if this is not the base address of the subnet implied
  146. # from the prefix length (e.g. 1.2.3.4/24 is offset, because the base
  147. # is 1.2.3.0/24)
  148. def offset?
  149. @addr != (@addr & ~mask)
  150. end
  151. # Returns offset from base of subnet to this address
  152. # IP.new("1.2.3.4/24").offset => 4
  153. def offset
  154. @addr - (@addr & ~mask)
  155. end
  156. # If the address is not on the base, turn it into a single IP.
  157. # IP.new("1.2.3.4/24").reset_pfxlen! => <IP::V4 1.2.3.4>
  158. # IP.new("1.2.3.0/24").reset_pfxlen! => <IP::V4 1.2.3.0/24>
  159. def reset_pfxlen!
  160. self.pfxlen = nil if offset?
  161. self
  162. end
  163. def to_irange
  164. a1 = @addr & ~mask
  165. a2 = a1 | mask
  166. (a1..a2)
  167. end
  168. # QUERY: IPAddr (1.9) turns 1.2.3.0/24 into 1.2.3.0/24..1.2.3.255/24
  169. # Here I turn it into 1.2.3.0..1.2.3.255. Which is better?
  170. def to_range
  171. self.class.new(@addr & ~mask, self.class::ADDR_BITS, @ctx) ..
  172. self.class.new(@addr | mask, self.class::ADDR_BITS, @ctx)
  173. end
  174. # test if the address is in the provided subnet
  175. def is_in?(subnet)
  176. subnet.network.to_i <= network.to_i &&
  177. subnet.broadcast.to_i >= broadcast.to_i
  178. end
  179. # this function sub-divides a subnet into two subnets of equal size
  180. def split
  181. nets = []
  182. if pfxlen < self.class::ADDR_BITS
  183. if self.class::ADDR_BITS == 32
  184. new_base = IP::V4.new(network.to_i, (pfxlen + 1))
  185. nets = [new_base, IP::V4.new((new_base.broadcast + 1).to_i, (pfxlen + 1))]
  186. end
  187. if self.class::ADDR_BITS == 128
  188. new_base = IP::V6.new(network.to_i, (pfxlen + 1))
  189. nets = [new_base, IP::V6.new((new_base.broadcast + 1).to_i, (pfxlen + 1))]
  190. end
  191. end
  192. nets
  193. end
  194. # subdivide a larger subnet into smaller subnets by number of subnets of equal size,
  195. # stop when subnets reach their smallest possible size (i.e. 31 for IP4)
  196. def divide_by_subnets(number_subnets)
  197. nets = []
  198. nets << self
  199. loop do
  200. new_nets = []
  201. nets.each do |net|
  202. new_nets |= net.split
  203. end
  204. nets = new_nets
  205. break if number_subnets <= nets.length &&
  206. nets[0].pfxlen <= (self.class::ADDR_BITS - 1)
  207. end
  208. nets
  209. end
  210. # subdivide a larger subnet into smaller subnets by number of hosts
  211. def divide_by_hosts(number_hosts)
  212. nets = []
  213. nets << self
  214. while number_hosts <= (nets[0].split[0].size - 2) &&
  215. nets[0].pfxlen <= (self.class::ADDR_BITS - 1)
  216. new_nets = []
  217. nets.each do |net|
  218. new_nets = new_nets | net.split
  219. end
  220. nets = new_nets
  221. end
  222. nets
  223. end
  224. # The number of IP addresses in subnet
  225. # IP.new("1.2.3.4/24").size => 256
  226. def size
  227. mask + 1
  228. end
  229. def +(other)
  230. self.class.new(@addr + other.to_i, @pfxlen, @ctx)
  231. end
  232. def -(other)
  233. self.class.new(@addr - other.to_i, @pfxlen, @ctx)
  234. end
  235. def &(other)
  236. self.class.new(@addr & other.to_i, @pfxlen, @ctx)
  237. end
  238. def |(other)
  239. self.class.new(@addr | other.to_i, @pfxlen, @ctx)
  240. end
  241. def ^(other)
  242. self.class.new(@addr ^ other.to_i, @pfxlen, @ctx)
  243. end
  244. def ~
  245. self.class.new(~@addr & self.class::MASK, @pfxlen, @ctx)
  246. end
  247. def succ
  248. self.class.new(@addr + size, @pfxlen, @ctx)
  249. end
  250. def succ!
  251. @addr += size
  252. self
  253. end
  254. def inspect
  255. "#<#{self.class} #{self}>"
  256. end
  257. def ipv4_mapped?
  258. false
  259. end
  260. def ipv4_compat?
  261. false
  262. end
  263. def native
  264. self
  265. end
  266. def hash
  267. to_a.hash
  268. end
  269. def freeze
  270. mask
  271. super
  272. end
  273. def eql?(other)
  274. to_a.eql?(other.to_a)
  275. end
  276. def <=>(other)
  277. to_a <=> other.to_a
  278. end
  279. include Comparable
  280. class V4 < IP
  281. class << self; alias_method :new, :orig_new; end
  282. PROTO = 'v4'.freeze
  283. PROTO_TO_CLASS[PROTO] = self
  284. ADDR_BITS = 32
  285. MASK = (1 << ADDR_BITS) - 1
  286. ARPA = ".in-addr.arpa."
  287. # Parse a string; return an V4 instance if it's a valid IPv4 address,
  288. # nil otherwise
  289. def self.parse(str)
  290. if str =~ /\A(\d+)\.(\d+)\.(\d+)\.(\d+)(?:\/(\d+))?(?:@(.*))?\z/
  291. pfxlen = (Regexp.last_match[5] || ADDR_BITS).to_i
  292. return nil if pfxlen > 32
  293. addrs = [Regexp.last_match[1].to_i,
  294. Regexp.last_match[2].to_i,
  295. Regexp.last_match[3].to_i,
  296. Regexp.last_match[4].to_i]
  297. return nil if addrs.find { |n| n > 255 }
  298. addr = (((((addrs[0] << 8) | addrs[1]) << 8) | addrs[2]) << 8) | addrs[3]
  299. new(addr, pfxlen, Regexp.last_match[6])
  300. end
  301. end
  302. # Return just the address part as a String in dotted decimal form
  303. def to_addr
  304. format('%d.%d.%d.%d',
  305. (@addr >> 24) & 0xff,
  306. (@addr >> 16) & 0xff,
  307. (@addr >> 8) & 0xff,
  308. @addr & 0xff)
  309. end
  310. # return the arpa version of the address for reverse DNS: http://en.wikipedia.org/wiki/Reverse_DNS_lookup
  311. def to_arpa
  312. format("%d.%d.%d.%d#{ARPA}",
  313. @addr & 0xff,
  314. (@addr >> 8) & 0xff,
  315. (@addr >> 16) & 0xff,
  316. (@addr >> 24) & 0xff)
  317. end
  318. end
  319. class V6 < IP
  320. class << self; alias_method :new, :orig_new; end
  321. PROTO = 'v6'.freeze
  322. PROTO_TO_CLASS[PROTO] = self
  323. ADDR_BITS = 128
  324. MASK = (1 << ADDR_BITS) - 1
  325. ARPA = ".ip6.arpa"
  326. # Parse a string; return an V6 instance if it's a valid IPv6 address,
  327. # nil otherwise
  328. #--
  329. # FIXME: allow larger variations of mapped addrs like 0:0:0:0:ffff:1.2.3.4
  330. #++
  331. def self.parse(str)
  332. case str
  333. when /\A\[?::(ffff:)?(\d+\.\d+\.\d+\.\d+)\]?(?:\/(\d+))?(?:@(.*))?\z/i
  334. mapped = Regexp.last_match[1]
  335. pfxlen = (Regexp.last_match[3] || 128).to_i
  336. ctx = Regexp.last_match[4]
  337. return nil if pfxlen > 128
  338. v4 = (V4.parse(Regexp.last_match[2]) || return).to_i
  339. v4 |= 0xffff00000000 if mapped
  340. new(v4, pfxlen, ctx)
  341. when /\A\[?([0-9a-f:]+)\]?(?:\/(\d+))?(?:@(.*))?\z/i
  342. addr = Regexp.last_match[1]
  343. pfxlen = (Regexp.last_match[2] || 128).to_i
  344. return nil if pfxlen > 128
  345. ctx = Regexp.last_match[3]
  346. return nil if pfxlen > 128
  347. if addr =~ /\A(.*?)::(.*)\z/
  348. left, right = Regexp.last_match[1], Regexp.last_match[2]
  349. l = left.split(':', -1)
  350. r = right.split(':', -1)
  351. rest = 8 - l.length - r.length
  352. return nil if rest < 0
  353. else
  354. l = addr.split(':')
  355. r = []
  356. rest = 0
  357. return nil if l.length != 8
  358. end
  359. out = ''
  360. l.each do |quad|
  361. return nil unless (1..4).include?(quad.length)
  362. out << quad.rjust(4, '0')
  363. end
  364. rest.times { out << '0000' }
  365. r.each do |quad|
  366. return nil unless (1..4).include?(quad.length)
  367. out << quad.rjust(4, '0')
  368. end
  369. new(out, pfxlen, ctx)
  370. else
  371. nil
  372. end
  373. end
  374. # Return just the address part as a String in compact decimal form
  375. def to_addr
  376. if ipv4_compat?
  377. "::#{native.to_addr}"
  378. elsif ipv4_mapped?
  379. "::ffff:#{native.to_addr}"
  380. elsif @addr.zero?
  381. '::'
  382. else
  383. res = to_hex.scan(/..../).join(':')
  384. res.gsub!(/\b0{1,3}/, '')
  385. res.sub!(/\b0:0:0:0(:0)*\b/, ':') ||
  386. res.sub!(/\b0:0:0\b/, ':') ||
  387. res.sub!(/\b0:0\b/, ':')
  388. res.sub!(/:::+/, '::')
  389. res
  390. end
  391. end
  392. # Return just the address in non-compact form, required for reverse IP.
  393. def to_addr_full
  394. if ipv4_compat?
  395. "::#{native.to_addr}"
  396. elsif ipv4_mapped?
  397. "::ffff:#{native.to_addr}"
  398. elsif @addr.zero?
  399. '::'
  400. else
  401. return to_hex.scan(/..../).join(':')
  402. end
  403. end
  404. # Returns the address broken into an array of 32 nibbles. Useful for
  405. # to_arpa and use in SPF - http://tools.ietf.org/html/rfc7208#section-7.3
  406. def to_nibbles
  407. to_hex.rjust(32, '0').split(//)
  408. end
  409. #return the arpa version of the address for reverse DNS: http://en.wikipedia.org/wiki/Reverse_DNS_lookup
  410. def to_arpa
  411. to_nibbles.reverse.join('.') + ARPA
  412. end
  413. def ipv4_mapped?
  414. (@addr >> 32) == 0xffff
  415. end
  416. def ipv4_compat?
  417. @addr > 1 && (@addr >> 32) == 0
  418. end
  419. # Convert an IPv6 mapped/compat address to a V4 native address
  420. def native
  421. return self unless (ipv4_mapped? || ipv4_compat?) && (@pfxlen >= 96)
  422. V4.new(@addr & V4::MASK, @pfxlen - 96, @ctx)
  423. end
  424. end
  425. end