netaddr 1.5.1 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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