/lib/rex/socket/range_walker.rb

https://bitbucket.org/jrossi/metasploit · Ruby · 346 lines · 207 code · 30 blank · 109 comment · 41 complexity · 44ad3b48ec2bae878efe3cc2666ed40a MD5 · raw file

  1. require 'rex/socket'
  2. module Rex
  3. module Socket
  4. ###
  5. #
  6. # This class provides an interface to enumerating an IP range
  7. #
  8. # This class uses start,stop pairs to represent ranges of addresses. This
  9. # is very efficient for large numbers of consecutive addresses, and not
  10. # show-stoppingly inefficient when storing a bunch of non-consecutive
  11. # addresses, which should be a somewhat unusual case.
  12. #
  13. ###
  14. class RangeWalker
  15. #
  16. # Initializes a walker instance using the supplied range
  17. #
  18. def initialize(parseme)
  19. if parseme.is_a? RangeWalker
  20. @ranges = parseme.ranges.dup
  21. else
  22. @ranges = parse(parseme)
  23. end
  24. reset
  25. end
  26. #
  27. # Calls the instance method
  28. #
  29. # This is basically only useful for determining if a range can be parsed
  30. #
  31. def self.parse(parseme)
  32. self.new.parse(parseme)
  33. end
  34. #
  35. # Turn a human-readable range string into ranges we can step through one address at a time.
  36. #
  37. # Allow the following formats:
  38. # "a.b.c.d e.f.g.h"
  39. # "a.b.c.d, e.f.g.h"
  40. # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see expand_nmap)
  41. #
  42. # OR this format
  43. # "a.b.c.d-e.f.g.h"
  44. # where a.b.c.d and e.f.g.h are single IPs and the second must be
  45. # bigger than the first.
  46. #
  47. def parse(parseme)
  48. return nil if not parseme
  49. ranges = []
  50. parseme.split(', ').map{ |a| a.split(' ') }.flatten.each { |arg|
  51. if arg.include?("/")
  52. # Then it's CIDR notation and needs special case
  53. return false if arg =~ /[,-]/ # Improper CIDR notation (can't mix with 1,3 or 1-3 style IP ranges)
  54. return false if arg.scan("/").size > 1 # ..but there are too many slashes
  55. ip_part,mask_part = arg.split("/")
  56. return false if ip_part.nil? or ip_part.empty? or mask_part.nil? or mask_part.empty?
  57. return false if mask_part !~ /^[0-9]{1,2}$/ # Illegal mask -- numerals only
  58. return false if mask_part.to_i > 32 # This too -- between 0 and 32.
  59. begin
  60. Rex::Socket.addr_atoi(ip_part) # This allows for "www.metasploit.com/24" which is fun.
  61. rescue Resolv::ResolvError
  62. return false # Can't resolve the ip_part, so bail.
  63. end
  64. expanded = expand_cidr(arg)
  65. if expanded
  66. ranges += expanded
  67. else
  68. return false
  69. end
  70. elsif arg.include?(":")
  71. # Then it's IPv6
  72. # Can't really do much with IPv6 right now, just return it and
  73. # hope for the best
  74. addr = Rex::Socket.addr_atoi(arg)
  75. ranges.push [addr, addr, true]
  76. elsif arg =~ /[^-0-9,.*]/
  77. # Then it's a domain name and we should send it on to addr_atoi
  78. # unmolested to force a DNS lookup.
  79. addr = Rex::Socket.addr_atoi(arg)
  80. ranges.push [addr, addr]
  81. elsif arg =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)-([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/
  82. # Then it's in the format of 1.2.3.4-5.6.7.8
  83. # Note, this will /not/ deal with DNS names, or the fancy/obscure 10...1-10...2
  84. begin
  85. addrs = [Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2)]
  86. return false if addrs[0] > addrs[1] # The end is greater than the beginning.
  87. ranges.push [addrs[0], addrs[1]]
  88. rescue Resolv::ResolvError # Something's broken, forget it.
  89. return false
  90. end
  91. else
  92. expanded = expand_nmap(arg)
  93. if expanded
  94. ranges += expanded
  95. else
  96. return false
  97. end
  98. end
  99. }
  100. return ranges
  101. end
  102. #
  103. # Resets the subnet walker back to its original state.
  104. #
  105. def reset
  106. return false if not valid?
  107. @curr_range = 0
  108. @curr_addr = @ranges[0][0]
  109. @length = 0
  110. @ranges.each { |r| @length += r[1] - r[0] + 1 }
  111. end
  112. #
  113. # Returns the next IP address.
  114. #
  115. def next_ip
  116. return false if not valid?
  117. if (@curr_addr > @ranges[@curr_range][1])
  118. if (@curr_range >= @ranges.length - 1)
  119. return nil
  120. end
  121. @curr_range += 1
  122. @curr_addr = @ranges[@curr_range][0]
  123. end
  124. addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range][2])
  125. @curr_addr += 1
  126. return addr
  127. end
  128. def valid?
  129. (@ranges and not @ranges.empty?)
  130. end
  131. #
  132. # Returns true if the argument is an ip address that falls within any of
  133. # the stored ranges.
  134. #
  135. def include?(addr)
  136. return false if not @ranges
  137. if (addr.is_a? String)
  138. addr = Rex::Socket.addr_atoi(addr)
  139. end
  140. @ranges.map { |r|
  141. if r[0] <= addr and addr <= r[1]
  142. return true
  143. end
  144. }
  145. return false
  146. end
  147. #
  148. # Returns true if this RangeWalker includes all of the addresses in the
  149. # given RangeWalker
  150. #
  151. def include_range?(range_walker)
  152. range_walker.ranges.all? do |start, stop|
  153. ranges.any? do |self_start, self_stop|
  154. r = (self_start..self_stop)
  155. r.include?(start) and r.include?(stop)
  156. end
  157. end
  158. end
  159. #
  160. # Calls the given block with each address
  161. #
  162. def each(&block)
  163. while (ip = next_ip)
  164. block.call(ip)
  165. end
  166. end
  167. #
  168. # Returns an array with one element, a Range defined by the given CIDR
  169. # block.
  170. #
  171. def expand_cidr(arg)
  172. start,stop = Rex::Socket.cidr_crack(arg)
  173. if !start or !stop
  174. return false
  175. end
  176. range = Range.new
  177. range.start = Rex::Socket.addr_atoi(start)
  178. range.stop = Rex::Socket.addr_atoi(stop)
  179. range.ipv6 = (arg.include?(":"))
  180. return [range]
  181. end
  182. #
  183. # Expands an nmap-style host range x.x.x.x where x can be simply "*" which
  184. # means 0-255 or any combination and repitition of:
  185. # i,n
  186. # n-m
  187. # i,n-m
  188. # n-m,i
  189. # ensuring that n is never greater than m.
  190. #
  191. # non-unique elements will be removed
  192. # e.g.:
  193. # 10.1.1.1-3,2-2,2 => ["10.1.1.1", "10.1.1.2", "10.1.1.3"]
  194. # 10.1.1.1-3,7 => ["10.1.1.1", "10.1.1.2", "10.1.1.3", "10.1.1.7"]
  195. #
  196. # Returns an array of Ranges
  197. #
  198. def expand_nmap(arg)
  199. # Can't really do anything with IPv6
  200. return false if arg.include?(":")
  201. # nmap calls these errors, but it's hard to catch them with our
  202. # splitting below, so short-cut them here
  203. return false if arg.include?(",-") or arg.include?("-,")
  204. bytes = []
  205. sections = arg.split('.')
  206. if sections.length != 4
  207. # Too many or not enough dots
  208. return false
  209. end
  210. sections.each { |section|
  211. if section.empty?
  212. # pretty sure this is an unintentional artifact of the C
  213. # functions that turn strings into ints, but it sort of makes
  214. # sense, so why not
  215. # "10...1" => "10.0.0.1"
  216. section = "0"
  217. end
  218. if section == "*"
  219. # I think this ought to be 1-254, but this is how nmap does it.
  220. section = "0-255"
  221. elsif section.include?("*")
  222. return false
  223. end
  224. # Break down the sections into ranges like so
  225. # "1-3,5-7" => ["1-3", "5-7"]
  226. ranges = section.split(',', -1)
  227. sets = []
  228. ranges.each { |r|
  229. bounds = []
  230. if r.include?('-')
  231. # Then it's an actual range, break it down into start,stop
  232. # pairs:
  233. # "1-3" => [ 1, 3 ]
  234. # if the lower bound is empty, start at 0
  235. # if the upper bound is empty, stop at 255
  236. #
  237. bounds = r.split('-', -1)
  238. return false if (bounds.length > 2)
  239. bounds[0] = 0 if bounds[0].nil? or bounds[0].empty?
  240. bounds[1] = 255 if bounds[1].nil? or bounds[1].empty?
  241. bounds.map!{|b| b.to_i}
  242. return false if bounds[0] > bounds[1]
  243. else
  244. # Then it's a single value
  245. bounds[0] = r.to_i
  246. end
  247. return false if bounds[0] > 255 or (bounds[1] and bounds[1] > 255)
  248. return false if bounds[1] and bounds[0] > bounds[1]
  249. if bounds[1]
  250. bounds[0].upto(bounds[1]) do |i|
  251. sets.push(i)
  252. end
  253. elsif bounds[0]
  254. sets.push(bounds[0])
  255. end
  256. }
  257. bytes.push(sets.sort.uniq)
  258. }
  259. #
  260. # Combinitorically squish all of the quads together into a big list of
  261. # ip addresses, stored as ints
  262. #
  263. # e.g.:
  264. # [[1],[1],[1,2],[1,2]]
  265. # =>
  266. # [atoi("1.1.1.1"),atoi("1.1.1.2"),atoi("1.1.2.1"),atoi("1.1.2.2")]
  267. addrs = []
  268. for a in bytes[0]
  269. for b in bytes[1]
  270. for c in bytes[2]
  271. for d in bytes[3]
  272. ip = (a << 24) + (b << 16) + (c << 8) + d
  273. addrs.push ip
  274. end
  275. end
  276. end
  277. end
  278. addrs.sort!
  279. addrs.uniq!
  280. rng = Range.new
  281. rng.start = addrs[0]
  282. ranges = []
  283. 1.upto(addrs.length - 1) do |idx|
  284. if addrs[idx - 1] + 1 == addrs[idx]
  285. # Then this address is contained in the current range
  286. next
  287. else
  288. # Then this address is the upper bound for the current range
  289. rng.stop = addrs[idx - 1]
  290. ranges.push(rng.dup)
  291. rng.start = addrs[idx]
  292. end
  293. end
  294. rng.stop = addrs[addrs.length - 1]
  295. ranges.push(rng.dup)
  296. return ranges
  297. end
  298. #
  299. # The total number of IPs within the range
  300. #
  301. attr_reader :length
  302. # for backwards compatibility
  303. alias :num_ips :length
  304. attr_reader :ranges
  305. end
  306. # :nodoc:
  307. class Range < Array
  308. def start; self[0]; end
  309. def stop; self[1]; end
  310. def ipv6; self[2]; end
  311. def start=(val); self[0] = val; end
  312. def stop=(val); self[1] = val; end
  313. def ipv6=(val); self[2] = val; end
  314. end
  315. end
  316. end