netaddr 1.5.1 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mask128.rb ADDED
@@ -0,0 +1,70 @@
1
+ module NetAddr
2
+
3
+ #Mask128 represents a 128-bit netmask.
4
+ class Mask128
5
+ # mask is the Integer representation of this netmask
6
+ attr_reader :mask
7
+
8
+ # prefix_len is the Integer prefix length of this netmask
9
+ attr_reader :prefix_len
10
+
11
+ # Create a Mask128 from an Integer prefix length. Valid values are 0-128.
12
+ # Throws ValidationError on error.
13
+ def initialize(prefix_len)
14
+ if (!prefix_len.kind_of?(Integer))
15
+ raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
16
+ elsif ( (prefix_len < 0) || (prefix_len > 128) )
17
+ raise ValidationError, "#{prefix_len} must be in the range of 0-128."
18
+ end
19
+ @prefix_len = prefix_len
20
+ @mask = NetAddr::F128 ^ (NetAddr::F128 >> @prefix_len)
21
+ end
22
+
23
+ # parse will create an Mask128 from its string representation.
24
+ # arguments:
25
+ # * mask - String representing an netmask (ie. "/64").
26
+ #
27
+ # Throws ValidationError on error.
28
+ def Mask128.parse(mask)
29
+ mask = mask.strip
30
+ if (mask.start_with?("/")) # cidr format
31
+ mask = mask[1..-1] # remove "/"
32
+ end
33
+ return Mask128.new(Integer(mask))
34
+ rescue ArgumentError
35
+ raise ValidationError, "#{mask} is not valid integer."
36
+ end
37
+
38
+ #cmp compares equality with another Mask128. Return:
39
+ #* 1 if this Mask128 is larger in capacity
40
+ #* 0 if the two are equal
41
+ #* -1 if this Mask128 is smaller in capacity
42
+ def cmp(other)
43
+ if (!other.kind_of?(Mask128))
44
+ raise ArgumentError, "Expected an Mask128 object for 'other' but got a #{other.class}."
45
+ end
46
+ if (self.prefix_len < other.prefix_len)
47
+ return 1
48
+ elsif (self.prefix_len > other.prefix_len)
49
+ return -1
50
+ end
51
+ return 0
52
+ end
53
+
54
+ #len returns the number of IP addresses in this network. This is only useful if you have a subnet
55
+ # smaller than a /64 as it will always return 0 for prefixes <= 64.
56
+ def len()
57
+ if (self.prefix_len <= 64)
58
+ return 0
59
+ end
60
+ return (self.mask ^ NetAddr::F128) + 1 # bit flip the netmask and add 1
61
+ end
62
+
63
+ # to_s returns the Mask128 as a String
64
+ def to_s()
65
+ return "/#{@prefix_len}"
66
+ end
67
+
68
+ end # end class Mask128
69
+
70
+ end # end module
data/lib/mask32.rb ADDED
@@ -0,0 +1,105 @@
1
+ module NetAddr
2
+
3
+ #Mask32 represents a 32-bit netmask.
4
+ class Mask32
5
+ # mask is the Integer representation of this netmask
6
+ attr_reader :mask
7
+
8
+ # prefix_len is the Integer prefix length of this netmask
9
+ attr_reader :prefix_len
10
+
11
+ # Create a Mask32 from an Integer prefix length. Valid values are 0-32.
12
+ # Throws ValidationError on error.
13
+ def initialize(prefix_len)
14
+ if (!prefix_len.kind_of?(Integer))
15
+ raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
16
+ elsif ( (prefix_len < 0) || (prefix_len > 32) )
17
+ raise ValidationError, "#{prefix_len} must be in the range of 0-32."
18
+ end
19
+ @prefix_len = prefix_len
20
+ @mask = NetAddr::F32 ^ (NetAddr::F32 >> @prefix_len)
21
+ end
22
+
23
+ # parse will create an Mask32 from its string representation.
24
+ # arguments:
25
+ # * mask - String representing a netmask (ie. "/24" or "255.255.255.0").
26
+ #
27
+ # Throws ValidationError on error.
28
+ def Mask32.parse(mask)
29
+ mask = mask.strip
30
+ if (mask.start_with?("/")) # cidr format
31
+ mask = mask[1..-1] # remove "/"
32
+ end
33
+
34
+ if (!mask.include?("."))
35
+ begin
36
+ return Mask32.new(Integer(mask))
37
+ rescue ArgumentError
38
+ raise ValidationError, "#{mask} is not valid integer."
39
+ end
40
+ end
41
+
42
+ # for extended netmask
43
+ # determine length of netmask by cycling through bit by bit and looking
44
+ # for the first '1' bit, tracking the length as we go. we also want to verify
45
+ # that the mask is valid (ie. not something like 255.254.255.0). we do this
46
+ # by creating a hostmask which covers the '0' bits of the mask. once we have
47
+ # separated the net vs host mask we xor them together. the result should be that
48
+ # all bits are now '1'. if not then we know we have an invalid netmask.
49
+ maskI = Util.parse_IPv4(mask)
50
+ prefix = 32
51
+ hostmask = 1
52
+ i = maskI
53
+ 32.downto(1) do
54
+ if (i&1 == 1)
55
+ hostmask = hostmask >> 1
56
+ if (maskI ^hostmask != NetAddr::F32)
57
+ raise ValidationError, "#{mask} is invalid. It contains '1' bits in its host portion."
58
+ end
59
+ break
60
+ end
61
+ hostmask = (hostmask << 1) | 1
62
+ i = i>>1
63
+ prefix -= 1
64
+ end
65
+ return Mask32.new(prefix)
66
+
67
+ end
68
+
69
+ # extended returns the Mask32 in extended format (eg. x.x.x.x)
70
+ def extended()
71
+ Util.int_to_IPv4(@mask)
72
+ end
73
+
74
+ #cmp compares equality with another Mask32. Return:
75
+ #* 1 if this Mask128 is larger in capacity
76
+ #* 0 if the two are equal
77
+ #* -1 if this Mask128 is smaller in capacity
78
+ def cmp(other)
79
+ if (!other.kind_of?(Mask32))
80
+ raise ArgumentError, "Expected an Mask32 object for 'other' but got a #{other.class}."
81
+ end
82
+ if (self.prefix_len < other.prefix_len)
83
+ return 1
84
+ elsif (self.prefix_len > other.prefix_len)
85
+ return -1
86
+ end
87
+ return 0
88
+ end
89
+
90
+ #len returns the number of IP addresses in this network. It will always return 0 for /0 networks.
91
+ def len()
92
+ if (self.prefix_len == 0)
93
+ return 0
94
+ end
95
+ return (self.mask ^ NetAddr::F32) + 1 # bit flip the netmask and add 1
96
+ end
97
+
98
+ # to_s returns the Mask32 as a String
99
+ def to_s()
100
+ return "/#{@prefix_len}"
101
+ end
102
+
103
+ end # end class Mask32
104
+
105
+ end # end module
data/lib/netaddr.rb CHANGED
@@ -1,30 +1,126 @@
1
- =begin rdoc
2
- Copyleft (c) 2006 Dustin Spinhirne
3
-
4
- Licensed under the same terms as Ruby, No Warranty is provided.
5
- =end
6
-
7
- require 'time'
8
- require 'digest/sha1'
9
- require File.join(File.dirname(__FILE__), 'validation_shortcuts.rb')
10
- require File.join(File.dirname(__FILE__), 'ip_math.rb')
11
- require File.join(File.dirname(__FILE__), 'cidr_shortcuts.rb')
12
- require File.join(File.dirname(__FILE__), 'methods.rb')
13
- require File.join(File.dirname(__FILE__), 'cidr.rb')
14
- require File.join(File.dirname(__FILE__), 'tree.rb')
15
- require File.join(File.dirname(__FILE__), 'eui.rb')
1
+ require_relative "eui48.rb"
2
+ require_relative "eui64.rb"
3
+ require_relative "ipv4.rb"
4
+ require_relative "ipv4net.rb"
5
+ require_relative "ipv6.rb"
6
+ require_relative "ipv6net.rb"
7
+ require_relative "mask32.rb"
8
+ require_relative "mask128.rb"
9
+ require_relative "util.rb"
16
10
 
17
11
  module NetAddr
12
+ # Constants
13
+
14
+ # 32 bits worth of '1'
15
+ F32 = 2**32-1
16
+
17
+ # 128 bits worth of '1'
18
+ F128 = 2**128-1
19
+
18
20
 
19
- class BoundaryError < StandardError #:nodoc:
20
- end
21
-
22
- class ValidationError < StandardError #:nodoc:
23
- end
24
-
25
- class VersionError < StandardError #:nodoc:
26
- end
27
-
28
- end # module NetAddr
29
-
30
- __END__
21
+ # ValidationError is thrown when a method fails a validation test.
22
+ class ValidationError < StandardError
23
+ end
24
+
25
+ # ipv4_prefix_len returns the prefix length needed to hold the number of IP addresses specified by "size".
26
+ def ipv4_prefix_len(size)
27
+ prefix_len = 32
28
+ 32.downto(0) do |i|
29
+ hostbits = 32 - prefix_len
30
+ max = 1 << hostbits
31
+ if (size <= max)
32
+ break
33
+ end
34
+ prefix_len -= 1
35
+ end
36
+ return prefix_len
37
+ end
38
+ module_function :ipv4_prefix_len
39
+
40
+ ## parse_ip parses a string into an IPv4 or IPv6
41
+ def parse_ip(ip)
42
+ if (ip.include?(".")) # ipv4
43
+ return IPv4.parse(ip)
44
+ end
45
+ return IPv6.parse(ip)
46
+ end
47
+ module_function :parse_ip
48
+
49
+ ## parse_net parses a string into an IPv4Net or IPv6Net
50
+ def parse_net(net)
51
+ if (net.include?(".")) # ipv4
52
+ return IPv4Net.parse(net)
53
+ end
54
+ return IPv6Net.parse(net)
55
+ end
56
+ module_function :parse_net
57
+
58
+ # sort_IPv4 sorts a list of IPv4 objects in ascending order.
59
+ # It will return a new list with any non IPv4 objects removed.
60
+ def sort_IPv4(list)
61
+ if ( !list.kind_of?(Array) )
62
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
63
+ end
64
+ filtered = Util.filter_IPv4(list)
65
+ return Util.quick_sort(filtered)
66
+ end
67
+ module_function :sort_IPv4
68
+
69
+ # sort_IPv6 sorts a list of IPv6 objects in ascending order.
70
+ # It will return a new list with any non IPv6 objects removed.
71
+ def sort_IPv6(list)
72
+ if ( !list.kind_of?(Array) )
73
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
74
+ end
75
+ filtered = Util.filter_IPv6(list)
76
+ return Util.quick_sort(filtered)
77
+ end
78
+ module_function :sort_IPv6
79
+
80
+ # sort_IPv4Net sorts a list of IPv4Net objects in ascending order.
81
+ # It will return a new list with any non IPv4Net objects removed.
82
+ def sort_IPv4Net(list)
83
+ if ( !list.kind_of?(Array) )
84
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
85
+ end
86
+ filtered = Util.filter_IPv4Net(list)
87
+ return Util.quick_sort(filtered)
88
+ end
89
+ module_function :sort_IPv4Net
90
+
91
+ # sort_IPv6Net sorts a list of IPv6Net objects in ascending order.
92
+ # It will return a new list with any non IPv6Net objects removed.
93
+ def sort_IPv6Net(list)
94
+ if ( !list.kind_of?(Array) )
95
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
96
+ end
97
+ filtered = Util.filter_IPv6Net(list)
98
+ return Util.quick_sort(filtered)
99
+ end
100
+ module_function :sort_IPv6Net
101
+
102
+ # summ_IPv4Net summarizes a list of IPv4Net objects as much as possible.
103
+ # It will return a new list with any non IPv4Net objects removed.
104
+ def summ_IPv4Net(list)
105
+ list = Util.filter_IPv4Net(list)
106
+ if (list.length>1)
107
+ list = Util.discard_subnets(list)
108
+ return Util.summ_peers(list)
109
+ end
110
+ return [].concat(list)
111
+ end
112
+ module_function :summ_IPv4Net
113
+
114
+ # summ_IPv6Net summarizes a list of IPv6Net objects as much as possible.
115
+ # It will return a new list with any non IPv6Net objects removed.
116
+ def summ_IPv6Net(list)
117
+ list = Util.filter_IPv6Net(list)
118
+ if (list.length>1)
119
+ list = Util.discard_subnets(list)
120
+ return Util.summ_peers(list)
121
+ end
122
+ return [].concat(list)
123
+ end
124
+ module_function :summ_IPv6Net
125
+
126
+ end # end module
data/lib/util.rb ADDED
@@ -0,0 +1,307 @@
1
+ module NetAddr
2
+
3
+ # Contains various internal util functions
4
+ class Util
5
+ private
6
+
7
+ # backfill generates subnets between given IPv4Net/IPv6Net and the limit address.
8
+ # limit should be < ipnet. will create subnets up to and including limit.
9
+ def Util.backfill(ipnet,limit)
10
+ nets = []
11
+ cur = ipnet
12
+ while true do
13
+ net = cur.prev
14
+ if (net == nil || net.network.addr < limit)
15
+ break
16
+ end
17
+ nets.unshift(net)
18
+ cur = net
19
+ end
20
+ return nets
21
+ end
22
+
23
+
24
+ # discard_subnets returns a copy of the IPv4NetList with any entries which are subnets of other entries removed.
25
+ def Util.discard_subnets(list)
26
+ keepers = []
27
+ last = list[list.length-1]
28
+ keep_last = true
29
+ list.each do |net|
30
+ rel = last.rel(net)
31
+ if (!rel) # keep unrelated nets
32
+ keepers.push(net)
33
+ elsif (rel == -1) # keep supernets, but do not keep last
34
+ keepers.push(net)
35
+ keep_last = false
36
+ end
37
+ end
38
+
39
+ # recursively clean up keepers
40
+ if (keepers.length > 0)
41
+ keepers = discard_subnets(keepers)
42
+ end
43
+ if keep_last
44
+ keepers.unshift(last)
45
+ end
46
+ return keepers
47
+ end
48
+
49
+ # fill returns a copy of the given Array, stripped of any networks which are not subnets of ipnet
50
+ # and with any missing gaps filled in.
51
+ def Util.fill(ipnet,list)
52
+ # sort & git rid of non subnets
53
+ subs = []
54
+ discard_subnets(list).each do |sub|
55
+ r = ipnet.rel(sub)
56
+ if (r == 1)
57
+ subs.push(sub)
58
+ end
59
+ end
60
+ subs = quick_sort(subs)
61
+
62
+ filled = []
63
+ if (subs.length > 0)
64
+ # bottom fill if base missing
65
+ base = ipnet.network.addr
66
+ if (subs[0].network.addr != base)
67
+ filled = backfill(subs[0],base)
68
+ end
69
+
70
+ # fill gaps
71
+ sib = ipnet.next_sib()
72
+ ceil = NetAddr::F32
73
+ if (sib != nil)
74
+ ceil = sib.network.addr
75
+ end
76
+
77
+ 0.upto(subs.length-1) do |i|
78
+ sub = subs[i]
79
+ filled.push(sub)
80
+ limit = ceil
81
+ if (i+1 < subs.length)
82
+ limit = subs[i+1].network.addr
83
+ end
84
+ filled.concat( fwdfill(sub,limit) )
85
+ end
86
+ end
87
+ return filled
88
+ end
89
+
90
+ # filter_IPv4 returns a copy of list with only IPv4 objects
91
+ def Util.filter_IPv4(list)
92
+ filtered = []
93
+ list.each do |ip|
94
+ if (ip.kind_of?(IPv4))
95
+ filtered.push(ip)
96
+ end
97
+ end
98
+ return filtered
99
+ end
100
+
101
+ # filter_IPv4Net returns a copy of list with only IPv4Net objects
102
+ def Util.filter_IPv4Net(list)
103
+ filtered = []
104
+ list.each do |ip|
105
+ if (ip.kind_of?(IPv4Net))
106
+ filtered.push(ip)
107
+ end
108
+ end
109
+ return filtered
110
+ end
111
+
112
+ # filter_IPv6 returns a copy of list with only IPv6 objects
113
+ def Util.filter_IPv6(list)
114
+ filtered = []
115
+ list.each do |ip|
116
+ if (ip.kind_of?(IPv6))
117
+ filtered.push(ip)
118
+ end
119
+ end
120
+ return filtered
121
+ end
122
+
123
+ # filter_IPv6Net returns a copy of list with only IPv4Net objects
124
+ def Util.filter_IPv6Net(list)
125
+ filtered = []
126
+ list.each do |ip|
127
+ if (ip.kind_of?(IPv6Net))
128
+ filtered.push(ip)
129
+ end
130
+ end
131
+ return filtered
132
+ end
133
+
134
+ # fwdfill returns subnets between given IPv4Net/IPv6Nett and the limit address.
135
+ # limit should be > ipnet. will create subnets up to limit.
136
+ def Util.fwdfill(ipnet,limit)
137
+ nets = []
138
+ cur = ipnet
139
+ while true do
140
+ net = cur.next
141
+ if (net == nil || net.network.addr >= limit)
142
+ break
143
+ end
144
+ nets.push(net)
145
+ cur = net
146
+ end
147
+ return nets
148
+ end
149
+
150
+ # int_to_IPv4 converts an Integer into an IPv4 address String
151
+ def Util.int_to_IPv4(i)
152
+ octets = []
153
+ 3.downto(0) do |x|
154
+ octet = (i >> 8*x) & 0xFF
155
+ octets.push(octet.to_s)
156
+ end
157
+ return octets.join('.')
158
+ end
159
+
160
+ # parse_IPv4 parses an IPv4 address String into an Integer
161
+ def Util.parse_IPv4(ip)
162
+ # check that only valid characters are present
163
+ if (ip =~ /[^0-9\.]/)
164
+ raise ValidationError, "#{ip} contains invalid characters."
165
+ end
166
+
167
+ octets = ip.strip.split('.')
168
+ if (octets.length != 4)
169
+ raise ValidationError, "IPv4 requires (4) octets."
170
+ end
171
+
172
+ ipInt = 0
173
+ i = 4
174
+ octets.each do |octet|
175
+ octetI = octet.to_i()
176
+ if ( (octetI < 0) || (octetI >= 256) )
177
+ raise ValidationError, "#{ip} is out of bounds for IPv4."
178
+ end
179
+ i -= 1
180
+ ipInt = ipInt | (octetI << 8*i)
181
+ end
182
+ return ipInt
183
+ end
184
+
185
+ # parse_IPv6 parses an IPv6 address String into an Integer
186
+ def Util.parse_IPv6(ip)
187
+ # check that only valid characters are present
188
+ if (ip =~ /[^0-9a-fA-F\:]/)
189
+ raise ValidationError, "#{ip} contains invalid characters."
190
+ end
191
+
192
+ ip = ip.strip
193
+ if (ip == "::")
194
+ return 0 # zero address
195
+ end
196
+ words = []
197
+ if (ip.include?("::")) # short format
198
+ if (ip =~ /:{3,}/) # make sure only i dont have ":::"
199
+ raise ValidationError, "#{ip} contains invalid field separator."
200
+ end
201
+ if (ip.scan(/::/).length != 1)
202
+ raise ValidationError, "#{ip} contains multiple '::' sequences."
203
+ end
204
+
205
+ halves = ip.split("::")
206
+ if (halves[0] == nil) # cases such as ::1
207
+ halves[0] = "0"
208
+ end
209
+ if (halves[1] == nil) # cases such as 1::
210
+ halves[1] = "0"
211
+ end
212
+ upHalf = halves[0].split(":")
213
+ loHalf = halves[1].split(":")
214
+ numWords = upHalf.length + loHalf.length
215
+ if (numWords > 8)
216
+ raise ValidationError, "#{ip} is too long."
217
+ end
218
+ words = upHalf
219
+ (8-numWords).downto(1) do |i|
220
+ words.push("0")
221
+ end
222
+ words.concat(loHalf)
223
+ else
224
+ words = ip.split(":")
225
+ if (words.length > 8)
226
+ raise ValidationError, "#{ip} is too long."
227
+ elsif (words.length < 8)
228
+ raise ValidationError, "#{ip} is too short."
229
+ end
230
+ end
231
+
232
+ ipInt = 0
233
+ i = 8
234
+ words.each do |word|
235
+ i -= 1
236
+ word = word.to_i(16) << (16*i)
237
+ ipInt = ipInt | word
238
+ end
239
+
240
+ return ipInt
241
+ end
242
+
243
+ # quick_sort will return a sorted copy of the provided Array.
244
+ # The array must contain only objects which implement a cmp method and which are comparable to each other.
245
+ def Util.quick_sort(list)
246
+ if (list.length <= 1)
247
+ return [].concat(list)
248
+ end
249
+
250
+ final_list = []
251
+ lt_list = []
252
+ gt_list = []
253
+ eq_list = []
254
+ pivot = list[list.length-1]
255
+ list.each do |ip|
256
+ cmp = pivot.cmp(ip)
257
+ if (cmp == 1)
258
+ lt_list.push(ip)
259
+ elsif (cmp == -1)
260
+ gt_list.push(ip)
261
+ else
262
+ eq_list.push(ip)
263
+ end
264
+ end
265
+ final_list.concat( quick_sort(lt_list) )
266
+ final_list.concat(eq_list)
267
+ final_list.concat( quick_sort(gt_list) )
268
+ return final_list
269
+ end
270
+
271
+ # summ_peers returns a copy of the list with any merge-able subnets summ'd together.
272
+ def Util.summ_peers(list)
273
+ summd = quick_sort(list)
274
+ while true do
275
+ list_len = summd.length
276
+ last = list_len - 1
277
+ tmp_list = []
278
+ i = 0
279
+ while (i < list_len) do
280
+ net = summd[i]
281
+ next_net = i+1
282
+ if (i != last)
283
+ # if this net and next_net summarize then discard them & keep summary
284
+ new_net = net.summ(summd[next_net])
285
+ if (new_net) # can summ. keep summary
286
+ tmp_list.push(new_net)
287
+ i += 1 # skip next_net
288
+ else # cant summ. keep existing
289
+ tmp_list.push(net)
290
+ end
291
+ else
292
+ tmp_list.push(net) # keep last
293
+ end
294
+ i += 1
295
+ end
296
+
297
+ # stop when list stops getting shorter
298
+ if (tmp_list.length == list_len)
299
+ break
300
+ end
301
+ summd = tmp_list
302
+ end
303
+ return summd
304
+ end
305
+
306
+ end # end class
307
+ end # end module