File: util.rb

package info (click to toggle)
ruby-netaddr 2.0.6-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 296 kB
  • sloc: ruby: 2,022; makefile: 7
file content (373 lines) | stat: -rw-r--r-- 9,275 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
module NetAddr
	
	# Contains various internal util functions
	class Util
	private
	
	# backfill generates subnets between given IPv4Net/IPv6Net and the limit address.
	# limit should be < ipnet. will create subnets up to and including limit.
	def Util.backfill(ipnet,limit)
		nets = []
		cur = ipnet
		while true do
			net = cur.prev
			if (net == nil || net.network.addr < limit)
				break
			end
			nets.unshift(net)
			cur = net
		end
		return nets
	end
	
	
	# discard_subnets returns a copy of the IPv4NetList with any entries which are subnets of other entries removed.
	def Util.discard_subnets(list)
		keepers = []
		last = list[list.length-1]
		keep_last = true
		list.each do |net|
			rel = last.rel(net)
			if (!rel) # keep unrelated nets
				keepers.push(net)
			elsif (rel == -1) # keep supernets, but do not keep last
				keepers.push(net)
				keep_last = false
			end
		end
		
		# recursively clean up keepers
		if (keepers.length > 0)
			keepers = discard_subnets(keepers)
		end
		if keep_last
			keepers.unshift(last)
		end
		return keepers
	end
	
	# fill returns a copy of the given Array, stripped of any networks which are not subnets of ipnet
	# and with any missing gaps filled in.
	def Util.fill(ipnet,list)
		# sort & get rid of non subnets
		subs = []
		discard_subnets(list).each do |sub|
			r = ipnet.rel(sub)
			if (r == 1)
				subs.push(sub)
			end
		end
		subs = quick_sort(subs)
		
		filled = []
		if (subs.length > 0)
			# bottom fill if base missing
			base = ipnet.network.addr
			if (subs[0].network.addr != base)
				filled = backfill(subs[0],base)
			end
			
			# fill gaps between subnets
			0.upto(subs.length-1) do |i|
				sub = subs[i]
				if (i+1 < subs.length)
					filled.concat( fwdfill(sub,ipnet,subs[i+1]) )
				else
					filled.concat( fwdfill(sub,ipnet,nil) )
				end
			end
		end
		return filled
	end
	
	# filter_IPv4 returns a copy of list with only IPv4 objects
	def Util.filter_IPv4(list)
		filtered = []
		list.each do |ip|
			if (ip.kind_of?(IPv4))
				filtered.push(ip)
			end
		end
		return filtered
	end
	
	# filter_IPv4Net returns a copy of list with only IPv4Net objects
	def Util.filter_IPv4Net(list)
		filtered = []
		list.each do |ip|
			if (ip.kind_of?(IPv4Net))
				filtered.push(ip)
			end
		end
		return filtered
	end
	
	# filter_IPv6 returns a copy of list with only IPv6 objects
	def Util.filter_IPv6(list)
		filtered = []
		list.each do |ip|
			if (ip.kind_of?(IPv6))
				filtered.push(ip)
			end
		end
		return filtered
	end
	
	# filter_IPv6Net returns a copy of list with only IPv4Net objects
	def Util.filter_IPv6Net(list)
		filtered = []
		list.each do |ip|
			if (ip.kind_of?(IPv6Net))
				filtered.push(ip)
			end
		end
		return filtered
	end
	
	# fwdfill returns subnets between given IPv4Net/IPv6Nett and the limit address. limit should be > ipnet.
	def Util.fwdfill(ipnet,supernet,limit)
		nets = [ipnet]
		cur = ipnet
		if (limit != nil) # if limit, then fill gaps between net and limit
			while true do
				nextSub = cur.next()
				# ensure we've not exceed the total address space
				if (nextSub == nil)
					break
				end
				# ensure we've not exceeded the address space of supernet
				if (supernet.rel(nextSub) == nil)
					break
				end
				# ensure we've not hit limit
				if (nextSub.network.addr == limit.network.addr)
					break
				end
				
				# check relationship to limit
				if (nextSub.rel(limit) != nil) # if related, then nextSub must be a supernet of limit. we need to shrink it.
					prefixLen = nextSub.netmask.prefix_len
					while true do
						prefixLen += 1
						if (nextSub.kind_of?(IPv4Net))
							nextSub = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
						else
							nextSub = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
						end
						if (nextSub.rel(limit) == nil) # stop when we no longer overlap with limit
							break
						end
					end
				else # otherwise, if unrelated then grow until we hit the limit
					prefixLen = nextSub.netmask.prefix_len
					mask = nextSub.netmask.mask
					while true do
						prefixLen -= 1
						if (prefixLen == supernet.netmask.prefix_len) # break if we've hit the supernet boundary
							break
						end
						mask = mask << 1
						if (nextSub.network.addr|mask != mask) # break when bit boundary crossed (there are '1' bits in the host portion)
							break
						end
						if (nextSub.kind_of?(IPv4Net))
							grown = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
						else
							grown = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
						end
						if (grown.rel(limit) != nil) # if we've overlapped with limit in any way, then break
							break
						end
						nextSub = grown
					end
				end
				nets.push(nextSub)
				cur = nextSub
			end
		else # if no limit, then get next largest sibs until we've exceeded supernet
			while true do
				nextSub = cur.next()
				# ensure we've not exceed the total address space
				if (nextSub == nil)
					break
				end
				# ensure we've not exceeded the address space of supernet
				if (supernet.rel(nextSub) == nil)
					break
				end
				nets.push(nextSub)
				cur = nextSub
			end
		end
		return nets
	end
	
	# int_to_IPv4 converts an Integer into an IPv4 address String
	def Util.int_to_IPv4(i)
		octets = []
		3.downto(0) do |x|
			octet = (i >> 8*x) & 0xFF 
			octets.push(octet.to_s)
		end
		return octets.join('.')
	end
	
	# parse_IPv4 parses an IPv4 address String into an Integer
	def Util.parse_IPv4(ip)
	# check that only valid characters are present
		if (ip =~ /[^0-9\.]/)
			raise ValidationError, "#{ip} contains invalid characters."
		end
		
		octets = ip.strip.split('.')
		if (octets.length != 4)
			raise ValidationError, "IPv4 requires (4) octets."
		end

		ipInt = 0
		i = 4
		octets.each do |octet|
			octetI = octet.to_i()
			if ( (octetI < 0) || (octetI >= 256) )
				raise ValidationError, "#{ip} is out of bounds for IPv4."
			end
			i -= 1 
			ipInt = ipInt | (octetI << 8*i)
		end
		return ipInt
	end
	
	# parse_IPv6 parses an IPv6 address String into an Integer
	def Util.parse_IPv6(ip)
	# check that only valid characters are present
		if (ip =~ /[^0-9a-fA-F\:.]/)
			raise ValidationError, "#{ip} contains invalid characters."
		end
		
		ip = ip.strip
		if (ip == "::")
			return 0 # zero address
		end
		ipv4Int = nil
		if (ip.include?(".")) # check for ipv4 embedded addresses
			words = ip.split(":")
			begin
				ipv4Int = Util.parse_IPv4(words.last)
			rescue
				raise ValidationError, "IPv4-embedded IPv6 address is invalid."
			end
			ip = ip.sub(words.last,"0:0") # temporarily remove the ipv4 portion
		end
		words = []
		if (ip.include?("::")) # short format
			if (ip =~ /:{3,}/) # make sure only i dont have ":::"
				raise ValidationError, "#{ip} contains invalid field separator."
			end
			if (ip.scan(/::/).length != 1)
				raise ValidationError, "#{ip} contains multiple '::' sequences."
			end
			
			halves = ip.split("::")
			if (halves[0] == nil) # cases such as ::1
				halves[0] = "0"
			end
			if (halves[1] == nil) # cases such as 1::
				halves[1] = "0"
			end
			upHalf = halves[0].split(":")
			loHalf = halves[1].split(":")
			numWords = upHalf.length + loHalf.length
			if (numWords > 8)
				raise ValidationError, "#{ip} is too long."
			end
			words = upHalf
			(8-numWords).downto(1) do |i|
				words.push("0")
			end
			words.concat(loHalf)
		else
			words = ip.split(":")
			if (words.length > 8)
			   raise ValidationError, "#{ip} is too long."
			elsif (words.length < 8)
				raise ValidationError, "#{ip} is too short."
			end
		end
		ipInt = 0
		i = 8
		words.each do |word|
			i -= 1
			word = word.to_i(16) << (16*i)
			ipInt = ipInt | word
		end
		if ipv4Int # re-add ipv4 portion if present
			ipInt = ipInt | ipv4Int
		end
		return ipInt
	end
		
	# quick_sort will return a sorted copy of the provided Array.
	# The array must contain only objects which implement a cmp method and which are comparable to each other.
	def Util.quick_sort(list)
		if (list.length <= 1)
			return [].concat(list)
		end
		
		final_list = []
		lt_list = []
		gt_list = []
		eq_list = []
		pivot = list[list.length-1]
		list.each do |ip|
			cmp = pivot.cmp(ip)
			if (cmp == 1)
				lt_list.push(ip)
			elsif (cmp == -1)
				gt_list.push(ip)
			else
				eq_list.push(ip)
			end
		end
		final_list.concat( quick_sort(lt_list) )
		final_list.concat(eq_list)
		final_list.concat( quick_sort(gt_list) )
		return final_list
	end
	
	# summ_peers returns a copy of the list with any merge-able subnets summ'd together.
	def Util.summ_peers(list)
		summd = quick_sort(list)
		while true do
			list_len = summd.length
			last = list_len - 1
			tmp_list = []
			i = 0
			while (i < list_len) do
				net = summd[i]
				next_net = i+1
				if (i != last)
					# if this net and next_net summarize then discard them & keep summary
					new_net = net.summ(summd[next_net])
					if (new_net) # can summ. keep summary
						tmp_list.push(new_net)
						i += 1 # skip next_net
					else # cant summ. keep existing
						tmp_list.push(net)
					end
				else
					tmp_list.push(net) # keep last
				end
				i += 1
			end
			
			# stop when list stops getting shorter
			if (tmp_list.length == list_len)
				break
			end
			summd = tmp_list
		end
		return summd
	end
	
	end # end class
end # end module