ipadmin 0.2.1 → 0.2.2

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.
@@ -0,0 +1,735 @@
1
+ =begin rdoc
2
+ Copyright (c) 2006 Dustin Spinhirne -
3
+ Licensed under the same terms as Ruby, No Warranty is provided.
4
+ =end
5
+
6
+
7
+ module IPAdmin
8
+ class Tree
9
+
10
+ # instance variables
11
+ # @all_f
12
+ # @max_bits
13
+ # @root
14
+ # @version
15
+
16
+ #==============================================================================#
17
+ # attr_reader / attr_writer
18
+ #==============================================================================#
19
+
20
+ # IP version for this tree (4 or 6)
21
+ attr_reader :version
22
+
23
+ #======================================#
24
+ #
25
+ #======================================#
26
+
27
+
28
+ #==============================================================================#
29
+ # initialize()
30
+ #==============================================================================#
31
+
32
+ # - Arguments:
33
+ # * Hash with the following fields:
34
+ # - :Version -- IP version - Integer
35
+ #
36
+ # Example:
37
+ # table = IPAdmin::CIDRTable.new(:Version => 4)
38
+ #
39
+ def initialize(options)
40
+ if (!options.kind_of? Hash)
41
+ raise ArgumentError, "Expected Hash, but #{options.class} provided."
42
+ end
43
+
44
+ if ( options.has_key?(:Version) )
45
+ @version = options[:Version]
46
+
47
+ if ( @version != 4 && @version != 6 )
48
+ raise "IP version should be either 4 or 6."
49
+ end
50
+ else
51
+ raise ArgumentError, "Missing argument: Version."
52
+ end
53
+
54
+ if (@version == 4)
55
+ @max_bits = 32
56
+ @all_f = 2**@max_bits - 1
57
+ else
58
+ @max_bits = 128
59
+ @all_f = 2**@max_bits - 1
60
+ end
61
+
62
+ # root of our ordered IP tree
63
+ @root = []
64
+
65
+ end
66
+
67
+ #======================================#
68
+ #
69
+ #======================================#
70
+
71
+
72
+ #==============================================================================#
73
+ # add()
74
+ #==============================================================================#
75
+
76
+ # Add an IPAdmin::CIDR object to the tree.
77
+ #
78
+ # - Arguments:
79
+ # * CIDR object
80
+ #
81
+ # - Returns:
82
+ # * nothing
83
+ #
84
+ # Example:
85
+ # tree.add(cidr)
86
+ #
87
+ def add(object)
88
+
89
+ # validate object
90
+ if ( !object.kind_of?(IPAdmin::CIDR) )
91
+ raise ArgumentError, "IPAdmin::CIDR object " +
92
+ "required but #{object.class} provided."
93
+ end
94
+
95
+ if ( object.version != @version )
96
+ raise "IP version #{object.version} is incompatible with " +
97
+ "Tree IP version #{@version}."
98
+ end
99
+
100
+ net_struct = IPAdmin.create_net_struct(object)
101
+
102
+ # search tree for it's home branch
103
+ search_results = find_home(net_struct, @root)
104
+ home_struct = search_results[0] if (search_results)
105
+ home_branch = home_struct.subnets if (search_results)
106
+ home_branch = @root if (!home_branch)
107
+
108
+ # make sure we dont have a duplicate
109
+ if (home_struct)
110
+ if ( (net_struct.network == home_struct.network) &&
111
+ (net_struct.netmask == home_struct.netmask) )
112
+ raise "Attempted to add #{object.desc()} multiple times."
113
+ end
114
+ end
115
+
116
+ # now that we have our branch location, check that other entries
117
+ # of this branch are not subnets of our new object. if they are
118
+ # then make them a branch of our new object
119
+ home_branch.each do |entry|
120
+ is_contained = contains(net_struct, entry)
121
+
122
+ if (is_contained)
123
+ net_struct.subnets.push(entry)
124
+ end
125
+ end
126
+
127
+ net_struct.subnets.each do |entry|
128
+ home_branch.delete(entry)
129
+ end
130
+
131
+ # add new object as an ordered entry to home_branch
132
+ add_to_branch(net_struct,home_branch)
133
+
134
+ return(nil)
135
+ end
136
+
137
+ #======================================#
138
+ #
139
+ #======================================#
140
+
141
+
142
+ #==============================================================================#
143
+ # collapse()
144
+ #==============================================================================#
145
+
146
+ # Collapse (supernet) all subnets of the tree,
147
+ # and return a new tree. The supernetting of subnets will only occur in
148
+ # situations where the newly created supernet will not result in the
149
+ # 'creation' of additional space. For example the following blocks
150
+ # (192.168.0.0/24, 192.168.1.0/24, and 192.168.2.0/24) would merge into
151
+ # 192.168.0.0/23 and 192.168.2.0/24 rather than into 192.168.0.0/22.
152
+ #
153
+ # - Arguments:
154
+ # * none
155
+ #
156
+ # - Returns:
157
+ # * Tree object
158
+ #
159
+ # Example:
160
+ # new_tree = tree.collapse()
161
+ #
162
+ def collapse()
163
+ tree = IPAdmin::Tree.new(:Version => @version)
164
+ branch = IPAdmin.merge(:List => @root, :NetStruct => 1)
165
+ dumped = dump_branch(branch)
166
+ dumped.each do |entry|
167
+ net_struct = entry[:NetStruct]
168
+ network = IPAdmin.unpack_ip_addr(:Integer => net_struct.network,
169
+ :Version => @version)
170
+ netmask = IPAdmin.unpack_ip_netmask(:Integer => net_struct.netmask,
171
+ :Version => @version)
172
+ cidr = IPAdmin::CIDR.new(:CIDR => "#{network}/#{netmask}")
173
+ tree.add(cidr)
174
+ end
175
+ return(tree)
176
+ end
177
+
178
+ #======================================#
179
+ #
180
+ #======================================#
181
+
182
+
183
+ #==============================================================================#
184
+ # dump
185
+ #==============================================================================#
186
+
187
+ # Dump the contents of this tree.
188
+ #
189
+ # - Arguments:
190
+ # * none
191
+ #
192
+ # - Returns:
193
+ # * ordered array of hashes with the following fields:
194
+ # - :Object => CIDR object
195
+ # - :Depth => (depth level in tree)
196
+ # Example:
197
+ # dumped = tree.dump()
198
+ #
199
+ def dump()
200
+ list = []
201
+ dumped = dump_branch(@root)
202
+
203
+ dumped.each do |entry|
204
+ depth = entry[:Depth]
205
+ net_struct = entry[:NetStruct]
206
+ object = net_struct.object
207
+ list.push({:Depth => depth, :Object => object})
208
+ end
209
+
210
+ return(list)
211
+ end
212
+
213
+ #======================================#
214
+ #
215
+ #======================================#
216
+
217
+
218
+ #==============================================================================#
219
+ # exists?()
220
+ #==============================================================================#
221
+
222
+ # Has an IPAdmin::CIDR object already been added to the tree?
223
+ #
224
+ # - Arguments:
225
+ # * CIDR object
226
+ #
227
+ # - Returns:
228
+ # * true or false
229
+ #
230
+ # Example:
231
+ # added = tree.exists?(cidr)
232
+ #
233
+ def exists?(object)
234
+
235
+ # validate object
236
+ if ( !object.kind_of?(IPAdmin::CIDR) )
237
+ raise ArgumentError, "IPAdmin::CIDR object " +
238
+ "required but #{object.class} provided."
239
+ end
240
+
241
+ if ( !object.version == @version )
242
+ raise "IP version #{object.version} is incompatible with " +
243
+ "Tree IP version #{@version}."
244
+ end
245
+
246
+ net_struct = IPAdmin.create_net_struct(object)
247
+ home_struct,home_branch = find_home(net_struct, @root)
248
+
249
+ # check for match
250
+ found = false
251
+ if (home_struct)
252
+ if ( (net_struct.network == home_struct.network) &&
253
+ (net_struct.netmask == home_struct.netmask) )
254
+ found = true
255
+ end
256
+ end
257
+
258
+
259
+ return(found)
260
+ end
261
+
262
+ #======================================#
263
+ #
264
+ #======================================#
265
+
266
+
267
+ #==============================================================================#
268
+ # find()
269
+ #==============================================================================#
270
+
271
+ # Find the longest matching branch of our tree to which an
272
+ # IPAdmin::CIDR object belongs. Useful for performing 'routing table' lookups.
273
+ #
274
+ # - Arguments:
275
+ # * CIDR object
276
+ #
277
+ # - Returns:
278
+ # * CIDR object, or nil
279
+ #
280
+ # Example:
281
+ # longest_match = tree.find(ip)
282
+ #
283
+ def find(object)
284
+ if ( !object.kind_of?(IPAdmin::CIDR) )
285
+ raise ArgumentError, "IPAdmin::CIDR object " +
286
+ "required but #{object.class} provided."
287
+ end
288
+
289
+ if ( object.version != @version )
290
+ raise "IP version #{object.version} is incompatible with " +
291
+ "Tree IP version #{@version}."
292
+ end
293
+
294
+ net_struct = IPAdmin.create_net_struct(object)
295
+ home_struct,home_branch = find_home(net_struct, @root)
296
+ found_obj = home_struct.object if (home_struct)
297
+
298
+ return(found_obj)
299
+ end
300
+
301
+ #======================================#
302
+ #
303
+ #======================================#
304
+
305
+
306
+ #==============================================================================#
307
+ # find_space()
308
+ #==============================================================================#
309
+
310
+ # Find Tree entries that can hold a subnet of size X. Returns only the smallest
311
+ # matching subnets of each supernet. If neither IPCount or Subnet or provided,
312
+ # then method returns all entries that can fit a single IP.
313
+ #
314
+ # - Arguments:
315
+ # * Hash with the following fields:
316
+ # - :IPCount -- Minimum number of IP's that should fit within subnet - Integer (optional)
317
+ # - :Limit -- Maximum entries to return - Integer (optional)
318
+ # - :Subnet -- Netmask (in bits) of space to find - Integer (optional)
319
+ #
320
+ # - Returns:
321
+ # * Array of CIDR objects
322
+ #
323
+ # - Notes:
324
+ # * :Subnet always takes precedence over :IPCount.
325
+ #
326
+ # Example:
327
+ # list = tree.find_space(:Subnet => 27)
328
+ # list = tree.find_space(:IPCount => 33, :Limit => 2)
329
+ #
330
+ def find_space(options)
331
+ limit = nil
332
+ list = []
333
+
334
+ # validate options
335
+ if (!options.kind_of? Hash)
336
+ raise ArgumentError, "Expected Hash, but #{options.class} provided."
337
+ end
338
+
339
+ if ( options.has_key?(:Limit) )
340
+ limit = options[:Limit]
341
+ end
342
+
343
+ if ( options.has_key?(:IPCount) )
344
+ subnet_size = IPAdmin.minimum_size(:IPCount =>options[:IPCount],
345
+ :Version => @version)
346
+ end
347
+
348
+ if ( options.has_key?(:Subnet) )
349
+ subnet_size = options[:Subnet]
350
+ end
351
+
352
+ # set subnet_size if not already
353
+ if (!subnet_size)
354
+ if (@version == 4)
355
+ subnet_size = 32
356
+ else
357
+ subnet_size = 128
358
+ end
359
+ end
360
+
361
+ # check that subnet_size is a valid size
362
+ if ( (subnet_size < 1) || (subnet_size > @max_bits) )
363
+ raise "#{subnet_size} is out of range for an " +
364
+ "IP version #{@version} Tree."
365
+ end
366
+
367
+ # search
368
+ search_list = dump_branch(@root)
369
+ search_list.each do |entry|
370
+ depth = entry[:Depth]
371
+ net_struct = entry[:NetStruct]
372
+
373
+ # we only want leaf nodes of type IPAdmin::CIDR
374
+ if ( (net_struct.subnets.length == 0) &&
375
+ (net_struct.object.kind_of?(IPAdmin::CIDR)) )
376
+
377
+ if (subnet_size >= net_struct.object.bits)
378
+ list.push(net_struct.object)
379
+ end
380
+ end
381
+
382
+ if (limit && (list.length >= limit) )
383
+ break
384
+ end
385
+
386
+ end
387
+
388
+ return(list)
389
+
390
+ end
391
+
392
+ #======================================#
393
+ #
394
+ #======================================#
395
+
396
+
397
+ #==============================================================================#
398
+ # prune()
399
+ #==============================================================================#
400
+
401
+ # Remove an IPAdmin::CIDR object, and all child
402
+ # objects from the tree.
403
+ #
404
+ # - Arguments:
405
+ # * CIDR object
406
+ #
407
+ # - Returns:
408
+ # * true or false
409
+ #
410
+ # Example:
411
+ # did_prune = tree.prune(ip)
412
+ #
413
+ def prune(object)
414
+ if ( !object.kind_of?(IPAdmin::CIDR) )
415
+ raise ArgumentError, "IPAdmin::CIDR object " +
416
+ "required but #{object.class} provided."
417
+ end
418
+
419
+ if (object.version != @version )
420
+ raise "IP version #{object.version} is incompatible with " +
421
+ "Tree IP version #{@version}."
422
+ end
423
+
424
+ net_struct = IPAdmin.create_net_struct(object)
425
+ home_struct,home_branch = find_home(net_struct, @root)
426
+
427
+ if(!home_struct)
428
+ raise "#{object.desc} could not be found."
429
+ end
430
+
431
+ # remove if home_struct.object = object
432
+ pruned = false
433
+ if (home_struct.object = object)
434
+ home_branch.delete(home_struct)
435
+ pruned = true
436
+ end
437
+
438
+ return(pruned)
439
+ end
440
+
441
+ #======================================#
442
+ #
443
+ #======================================#
444
+
445
+
446
+ #==============================================================================#
447
+ # remove()
448
+ #==============================================================================#
449
+
450
+ # Remove a single CIDR object from the tree.
451
+ #
452
+ # - Arguments:
453
+ # * CIDR object
454
+ #
455
+ # - Returns:
456
+ # * true or false
457
+ #
458
+ # Example:
459
+ # did_remove = tree.remove(ip)
460
+ #
461
+ def remove(object)
462
+ if ( !object.kind_of?(IPAdmin::CIDR) )
463
+ raise ArgumentError, "IPAdmin::CIDR object " +
464
+ "required but #{object.class} provided."
465
+ end
466
+
467
+ if (object.version != @version )
468
+ raise "IP version #{object.version} is incompatible with " +
469
+ "Tree IP version #{@version}."
470
+ end
471
+
472
+ net_struct = IPAdmin.create_net_struct(object)
473
+ home_struct,home_branch = find_home(net_struct, @root)
474
+
475
+ # remove if home_struct.object = object
476
+ removed = false
477
+ if (home_struct.object = object)
478
+
479
+ # if we have children subnets, move them up one level
480
+ if (home_struct.subnets.length > 0)
481
+ home_struct.subnets.each do |entry|
482
+ index = 0
483
+ home_branch.each do
484
+ if(entry.network < (home_branch[index]).network)
485
+ break
486
+ end
487
+ index += 1
488
+ end
489
+ home_branch.insert(index, entry)
490
+ end
491
+ end
492
+
493
+ home_branch.delete(home_struct)
494
+ removed = true
495
+ end
496
+
497
+ return(removed)
498
+ end
499
+
500
+ #======================================#
501
+ #
502
+ #======================================#
503
+
504
+
505
+ #==============================================================================#
506
+ # show()
507
+ #==============================================================================#
508
+
509
+ # Print the tree in a nicely formatted string.
510
+ #
511
+ # - Arguments:
512
+ # * none
513
+ #
514
+ # - Returns:
515
+ # * String representing the contents of the tree
516
+ #
517
+ # Example:
518
+ # puts tree.show()
519
+ #
520
+ def show()
521
+ printed = ""
522
+ list = dump_branch(@root)
523
+
524
+ list.each do |entry|
525
+ net_struct = entry[:NetStruct]
526
+ depth = entry[:Depth]
527
+ network = IPAdmin.unpack_ip_addr(:Integer => net_struct.network,
528
+ :Version => @version)
529
+ netmask = IPAdmin.unpack_ip_netmask(:Integer => net_struct.netmask,
530
+ :Version => @version)
531
+ network = IPAdmin.shorten(network) if (@version == 6)
532
+
533
+ if (depth == 0)
534
+ indent = ""
535
+ else
536
+ indent = " " * (depth*3)
537
+ end
538
+
539
+ printed << "#{indent}#{network}/#{netmask}\n"
540
+ end
541
+
542
+ return(printed)
543
+ end
544
+
545
+ #======================================#
546
+ #
547
+ #======================================#
548
+
549
+
550
+
551
+
552
+ # PRIVATE METHODS
553
+ private
554
+
555
+ #==============================================================================#
556
+ # add_to_branch() private
557
+ #==============================================================================#
558
+
559
+ # Add Netstruct object to an array of NetStruct's
560
+ #
561
+ # - Arguments:
562
+ # * NetStruct, and array of NetStruct's in which to add it
563
+ #
564
+ # - Returns:
565
+ # * none
566
+ #
567
+ # Example:
568
+ # add_to_branch(net_struct,branch)
569
+ #
570
+ def add_to_branch(net_struct,branch)
571
+ # make sure we have appropriate types
572
+ if (!net_struct.kind_of?(IPAdmin::NetStruct) ||
573
+ !branch.kind_of?(Array) )
574
+ raise ArgumentError, "Incorrect argument types " +
575
+ "#{net_struct.class} and #{branch}."
576
+ end
577
+
578
+ index = 0
579
+ branch.each do
580
+ if(net_struct.network < (branch[index]).network)
581
+ break
582
+ end
583
+ index += 1
584
+ end
585
+
586
+ branch.insert(index, net_struct)
587
+
588
+ return()
589
+ end
590
+
591
+ #======================================#
592
+ #
593
+ #======================================#
594
+
595
+
596
+ #==============================================================================#
597
+ # contains() private
598
+ #==============================================================================#
599
+
600
+ # Is the first NetStruct object the supernet of the second?
601
+ #
602
+ # - Arguments:
603
+ # * Two NetStruct objects
604
+ #
605
+ # - Returns:
606
+ # * true if struct1 contains or is equal to struct2, or false if not.
607
+ #
608
+ # Example:
609
+ # is_contained = contains(struct1,struct2)
610
+ #
611
+ def contains(struct1,struct2)
612
+
613
+ # make sure we have appropriate types
614
+ if (!struct1.kind_of?(IPAdmin::NetStruct) ||
615
+ !struct2.kind_of?(IPAdmin::NetStruct) )
616
+ raise ArgumentError, "Incorrect argument types " +
617
+ "#{struct1.class} and #{struct2.class}."
618
+ end
619
+
620
+ network1 = struct1.network
621
+ netmask1 = struct1.netmask
622
+ network2 = struct2.network
623
+ netmask2 = struct2.netmask
624
+ hostmask1 = netmask1 ^ @all_f
625
+
626
+ # if network1 == network2, then we have to go by netmask length
627
+ # else we can tell by or'ing network1 and network2 by hostmask1
628
+ # and comparing the results
629
+ is_contained = false
630
+ if (network1 == network2)
631
+ if (netmask2 >= netmask1)
632
+ is_contained = true
633
+ end
634
+ else
635
+ if ( (network1 | hostmask1) == (network2 | hostmask1) )
636
+ is_contained = true
637
+ end
638
+ end
639
+
640
+ return(is_contained)
641
+ end
642
+
643
+ #======================================#
644
+ #
645
+ #======================================#
646
+
647
+
648
+ #==============================================================================#
649
+ # dump_branch() private
650
+ #==============================================================================#
651
+
652
+ # Dump contents of an Array of NetStruct objects
653
+ #
654
+ # - Arguments:
655
+ # * array of NetStruct objects, and an optional depth within the tree
656
+ #
657
+ # - Returns:
658
+ # * array of hashes in form: :NetStruct => IPAdmin::NetStruct object
659
+ # :Depth => (depth level in tree)
660
+ # Example:
661
+ # dumped = dump_branch(branch)
662
+ #
663
+ def dump_branch(branch,depth=nil)
664
+ list = []
665
+ depth = 0 if (!depth)
666
+
667
+ branch.each do |entry|
668
+ subnets = entry.subnets
669
+ list.push({:NetStruct => entry, :Depth => depth})
670
+
671
+ if (subnets.length > 0)
672
+ list.concat( dump_branch(subnets, (depth+1) ) )
673
+ end
674
+
675
+ end
676
+
677
+ return(list)
678
+ end
679
+
680
+ #======================================#
681
+ #
682
+ #======================================#
683
+
684
+
685
+ #==============================================================================#
686
+ # find_home() private
687
+ #==============================================================================#
688
+
689
+ # Find the branch to which a NetStruct object belongs.
690
+ #
691
+ # - Arguments:
692
+ # * IPAdmin::NetStruct to find, and Array of
693
+ # IPAdmin::NetStruct's to search
694
+ #
695
+ # - Returns:
696
+ # * array of: container IPAdmin::NetStruct object,
697
+ # and container branch, or nil
698
+ #
699
+ # Example:
700
+ # branch = find_home(net_struct,branch)
701
+ #
702
+ def find_home(net_struct,branch)
703
+ ret_val = nil
704
+
705
+ branch.each do |entry|
706
+ is_contained = contains(entry,net_struct)
707
+
708
+ if (is_contained)
709
+ home_struct = entry
710
+ home_branch = branch
711
+
712
+ if (home_struct.subnets.length > 0)
713
+ search_results = find_home(net_struct,home_struct.subnets)
714
+ if (search_results)
715
+ home_struct = search_results[0]
716
+ home_branch = search_results[1]
717
+ end
718
+ end
719
+
720
+ ret_val = [home_struct,home_branch]
721
+ break
722
+ end
723
+ end
724
+
725
+ return(ret_val)
726
+ end
727
+
728
+ #======================================#
729
+ #
730
+ #======================================#
731
+
732
+ end
733
+
734
+ end # module IPAdmin
735
+ __END__